diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 224f673ff9..71b57ecb2d 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -103,7 +103,7 @@ jobs: working-directory: ./mobile run: printf "%s" $KEY_JKS | base64 -d > android/key.jks - - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0 with: distribution: 'zulu' java-version: '17' @@ -237,7 +237,7 @@ jobs: run: flutter build ios --config-only --no-codesign - name: Setup Ruby - uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0 + uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1.313.0 with: ruby-version: '3.3' bundler-cache: true diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml index 8a64d15541..9a93e931d3 100644 --- a/.github/workflows/check-openapi.yml +++ b/.github/workflows/check-openapi.yml @@ -25,11 +25,12 @@ jobs: persist-credentials: false - name: Check for breaking API changes - uses: oasdiff/oasdiff-action/breaking@3530478ec30f84adedbfeb28f0d9527a290f50a9 # v0.0.57 + uses: oasdiff/oasdiff-action/breaking@e24529087d93f837b28b50bb66ba9016380a7fcc # v0.1.2 with: base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json revision: open-api/immich-openapi-specs.json fail-on: ERR + review: false check-mobile-patches: runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2bfa53618d..222ac1d794 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,6 +45,8 @@ jobs: - 'server/**' - 'pnpm-lock.yaml' - 'mise.toml' + - 'packages/plugin-core/**' + - 'packages/plugin-sdk/**' cli: - 'packages/cli/**' - 'packages/sdk/**' @@ -406,7 +408,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - name: Setup Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 @@ -483,7 +485,7 @@ jobs: token: ${{ steps.token.outputs.token }} - name: Setup pnpm - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 - name: Setup Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index f59277e89d..d682ea1e6f 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -85,7 +85,7 @@ services: container_name: immich_prometheus ports: - 9090:9090 - image: prom/prometheus@sha256:69f5241418838263316593f7274a304b095c40bcf22e57272865da91bd60a8ac + image: prom/prometheus@sha256:a75c5a35bc21d7afe69551eefa3cb1e1fb1775fe759408007a66b54ec3de1f29 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus diff --git a/docs/docs/features/duplicates-utility.md b/docs/docs/features/duplicates-utility.md index f790c42708..5d68c0ace9 100644 --- a/docs/docs/features/duplicates-utility.md +++ b/docs/docs/features/duplicates-utility.md @@ -15,14 +15,14 @@ When using "Deduplicate All" or viewing suggestions, Immich automatically presel ### Synchronizing metadata -When resolving duplicates, metadata from trashed assets is automatically synchronized to the kept assets. The following metadata is synchronized: +When resolving duplicates, metadata from trashed assets is automatically synchronized to the kept asset. This synchronization only happens when **exactly one** asset is kept and at least one asset is trashed. When more than one asset is kept, metadata is not merged — the assets keep their own metadata and are simply removed from the duplicate group. The following metadata is synchronized: -| Name | Description | -| ----------- | ------------------------------------------------------------------------------------------------------------------------------- | -| Album | The kept assets will be added to _every_ album that the other assets in the group belong to. | -| Favorite | If any of the assets in the group have been added to favorites, every kept asset will also be added to favorites. | -| Rating | If one or more assets in the duplicate group have a rating, the highest rating is selected and synchronized to the kept assets. | -| Description | Descriptions from each asset are combined together and synchronized to all the kept assets. | -| Visibility | The most restrictive visibility is applied to the kept assets. | -| Location | Latitude and longitude are copied if all assets with geolocation data in the group share the same coordinates. | -| Tag | Tags from all assets in the group are merged and applied to every kept asset. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------ | +| Album | The kept asset will be added to _every_ album that the other assets in the group belong to. | +| Favorite | If any of the assets in the group have been added to favorites, the kept asset will also be added to favorites. | +| Rating | If one or more assets in the duplicate group have a rating, the highest rating is selected and synchronized to the kept asset. | +| Description | Descriptions from each asset are combined together and synchronized to the kept asset. | +| Visibility | The most restrictive visibility is applied to the kept asset. | +| Location | Latitude and longitude are copied if all assets with geolocation data in the group share the same coordinates. | +| Tag | Tags from all assets in the group are merged and applied to the kept asset. | diff --git a/docs/docs/features/mobile-app.mdx b/docs/docs/features/mobile-app.mdx index 59a4844c46..1b7c7e21d5 100644 --- a/docs/docs/features/mobile-app.mdx +++ b/docs/docs/features/mobile-app.mdx @@ -13,7 +13,9 @@ import MobileAppBackup from '/docs/partials/_mobile-app-backup.md'; :::info Android verification Below are the SHA-256 fingerprints for the certificates signing the android applications. -- Playstore / Github releases: +- Google Play releases: + `5A:22:C1:83:47:54:05:F5:49:C4:EB:9F:B2:6C:2E:93:A3:EF:9C:57:66:15:0A:7A:F3:8C:8D:3F:E5:15:CC:D6` +- GitHub releases: `86:C5:C4:55:DF:AF:49:85:92:3A:8F:35:AD:B3:1D:0C:9E:0B:95:7D:7F:94:C2:D2:AF:6A:24:38:AA:96:00:20` - F-Droid releases: `FA:8B:43:95:F4:A6:47:71:A0:53:D1:C7:57:73:5F:A2:30:13:74:F5:3D:58:0D:D1:75:AA:F7:A1:35:72:9C:BF` diff --git a/docs/mise.lock b/docs/mise.lock index 65cf1124a2..698d6e64c9 100644 --- a/docs/mise.lock +++ b/docs/mise.lock @@ -1,7 +1,7 @@ # @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html [[tools.wrangler]] -version = "4.98.0" +version = "4.100.0" backend = "npm:wrangler" [tools.wrangler.options] diff --git a/docs/mise.toml b/docs/mise.toml index 5290fe6536..3791e7b8eb 100644 --- a/docs/mise.toml +++ b/docs/mise.toml @@ -28,4 +28,4 @@ run = "prettier --write ." run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}" [tools] -wrangler = "4.98.0" +wrangler = "4.100.0" diff --git a/docs/static/archived-versions.json b/docs/static/archived-versions.json index a86a09c037..79cc186191 100644 --- a/docs/static/archived-versions.json +++ b/docs/static/archived-versions.json @@ -1,7 +1,7 @@ [ { - "label": "v3.0.0-rc.1", - "url": "https://docs.v3.0.0-rc.1.archive.immich.app" + "label": "v3.0.0-rc.2", + "url": "https://docs.v3.0.0-rc.2.archive.immich.app" }, { "label": "v2.7.5", diff --git a/e2e/package.json b/e2e/package.json index 9d54d08677..eed9f88c45 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "immich-e2e", - "version": "3.0.0-rc.1", + "version": "3.0.0-rc.2", "description": "", "main": "index.js", "type": "module", diff --git a/e2e/src/specs/server/api/album.e2e-spec.ts b/e2e/src/specs/server/api/album.e2e-spec.ts index 1d3be1248c..0aa7b7ec03 100644 --- a/e2e/src/specs/server/api/album.e2e-spec.ts +++ b/e2e/src/specs/server/api/album.e2e-spec.ts @@ -730,8 +730,8 @@ describe('/albums', () => { .set('Authorization', `Bearer ${user1.accessToken}`) .send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('User already added')); + expect(status).toBe(200); + expect(body.albumUsers.length).toEqual(1); }); it('should not be able to add existing user to shared album', async () => { @@ -745,8 +745,8 @@ describe('/albums', () => { .set('Authorization', `Bearer ${user1.accessToken}`) .send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('User already added')); + expect(status).toBe(200); + expect(body.albumUsers.length).toEqual(2); }); }); diff --git a/i18n/ar.json b/i18n/ar.json index 0f9e98efc7..5743ba9ab8 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -1,9 +1,9 @@ { "about": "حول", - "account": "حساب", + "account": "الحساب", "account_settings": "إعدادات الحساب", "acknowledge": "أُدرك ذلك", - "action": "إجراء", + "action": "الإجراء", "action_common_update": "تحديث", "action_description": "مجموعة إجراءات لتنفيذها على المحتويات المصفاة", "actions": "عمليات", @@ -189,13 +189,25 @@ "machine_learning_smart_search_enabled": "تفعيل البحث الذكي", "machine_learning_smart_search_enabled_description": "إذا تم تعطيله، فلن يتم ترميز الصور للبحث الذكي.", "machine_learning_url_description": "عنوان URL لخادم التعلم الآلي. إذا تم توفير أكثر من عنوان URL واحد، سيتم محاولة الاتصال بكل خادم على حدة حتى يستجيب أحدهم بنجاح، بدءًا من الأول إلى الأخير. سيتم تجاهل الخوادم التي لا تستجيب مؤقتًا حتى تعود للعمل.", + "maintenance_backup_management": "إدارة النسخ الاحتياطي", "maintenance_delete_backup": "حذف النسخ الاحتياطي", "maintenance_delete_backup_description": "هذا الملف سيتم حذفه بشكل لا رجعه فيه.", "maintenance_delete_error": "فشل حذف النسخ الاحتياطي.", + "maintenance_integrity_check": "تحقق", "maintenance_integrity_check_all": "تحديد الكل", "maintenance_integrity_checksum_mismatch": "عدم تطابق رمز التحقق", + "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 بدون وجود سجلات لها.", + "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!", @@ -575,6 +587,7 @@ "asset_added_to_album": "تمت إضافته إلى الألبوم", "asset_adding_to_album": "جارٍ الإضافة إلى الألبوم…", "asset_created": "انشئ اصل", + "asset_day_count": "{date}: {count, plural, zero {لا توجد ملفات} one {ملف واحد} two {ملفان} few {# ملفات} many {# ملفًا} other {# ملف}}", "asset_description_updated": "تم تحديث وصف المحتوى", "asset_filename_is_offline": "الأصل {filename} غير متصل", "asset_has_unassigned_faces": "يحتوي الأصل على وجوه غير مخصصة", @@ -704,6 +717,7 @@ "backup_settings_subtitle": "إدارة إعدادات التحميل", "backup_upload_details_page_more_details": "اضغط لتفاصيل اضافية", "backward": "الى الوراء", + "battery_optimization_backup_reliability": "إيقاف تحسين البطارية يزيد من استقرار النسخ الاحتياطي في الخلفية", "biometric_auth_enabled": "المصادقة البايومترية مفعله", "biometric_locked_out": "لقد قفلت عنك المصادقة البيومترية", "biometric_no_options": "لا توجد خيارات بايومترية متوفرة", @@ -918,6 +932,8 @@ "deduplicate_all": "إلغاء تكرار الكل", "default_locale": "الإعدادات المحلية الافتراضية", "default_locale_description": "تنسيق التواريخ والأرقام بناءً على الإعدادات المحلية للمتصفح", + "default_quality_subtitle": "الجودة المستخدمة عند الضغط على \"مشاركة\". اضغط مطولاً على زر المشاركة لاختيار الجودة في كل مرة.", + "default_share_quality": "جودة المشاركة الافتراضية", "delete": "حذف", "delete_action_confirmation_message": "هل انت متأكد من حذف هذا الملف؟ هذا سؤدي الى نقل الملف الى سلة مهملات الخادم وسيتم اشعارك ان كنت تريد حذفه على الجهاز", "delete_action_prompt": "تم حذف {count}", @@ -1227,6 +1243,7 @@ "failed": "فشل", "failed_count": "فشل: {count}", "failed_to_authenticate": "فشل في المصادقة", + "failed_to_delete_file": "فشل حذف الملف", "failed_to_load_assets": "فشل تحميل الأصول", "failed_to_load_folder": "فشل تحميل المجلد", "favorite": "مفضل", @@ -1357,6 +1374,7 @@ "individual_share": "حصة فردية", "individual_shares": "المشاركات الفردية", "info": "معلومات", + "integrity_checks": "فحوصات السلامة", "interval": { "day_at_onepm": "كل يوم الساعة الواحدة ظهرا", "hours": "كل {hours, plural, one {ساعة} other {{hours, number} ساعة}}", @@ -1404,6 +1422,7 @@ "leave": "مغادرة", "leave_album": "اترك الالبوم", "lens_model": "نموذج العدسات", + "less": "أقل", "let_others_respond": "دع الآخرين يستجيبون", "level": "المستوى", "library": "مكتبة", @@ -1428,6 +1447,7 @@ "linked_oauth_account": "حساب مرتبط بـ OAuth", "list": "قائمة", "live": "حي", + "load_more": "تحميل المزيد", "loading": "تحميل", "loading_search_results_failed": "فشل تحميل نتائج البحث", "local": "محلّي", @@ -1528,7 +1548,6 @@ "map_location_picker_page_use_location": "استخدم هذا الموقع", "map_location_service_disabled_content": "يجب تمكين خدمة الموقع لعرض الأصول من موقعك الحالي.هل تريد تمكينه الآن؟", "map_location_service_disabled_title": "خدمة الموقع معطل", - "map_marker_for_images": "علامة الخريطة للصور الملتقطة في {city}، {country}", "map_marker_with_image": "علامة الخريطة مع الصورة", "map_no_location_permission_content": "هناك حاجة إلى إذن الموقع لعرض الأصول من موقعك الحالي.هل تريد السماح به الآن؟", "map_no_location_permission_title": "تم رفض إذن الموقع", @@ -1597,6 +1616,8 @@ "merge_people_prompt": "هل تريد دمج هؤلاء الناس؟ هذا الإجراء لا رجعة فيه.", "merge_people_successfully": "تم دمج الأشخاص بنجاح", "merged_people_count": "دمج {count, plural, one {شخص واحد} other {# أشخاص}}", + "minFaces": "الحد الأدنى للوجوه", + "minFaces_description": "الحد الأدنى لعدد الوجوه المتعرف عليها لكي يتم عرض الشخص", "minimize": "تصغير", "minute": "دقيقة", "minutes": "دقائق", @@ -1692,6 +1713,7 @@ "not_selected": "لم يختار", "notes": "ملاحظات", "nothing_here_yet": "لا يوجد شيء هنا بعد", + "notification_backup_reliability": "فعل الإشعارات للزيادة من استقرار النسخ الاحتياطي في الخلفية", "notification_permission_dialog_content": "لتمكين الإخطارات ، انتقل إلى الإعدادات و اختار السماح.", "notification_permission_list_tile_content": "منح إذن لتمكين الإخطارات.", "notification_permission_list_tile_enable_button": "تمكين الإخطارات", @@ -2083,6 +2105,7 @@ "select_person": "اختر شخص", "select_person_to_tag": "اختر شخص لوضع علامة", "select_photos": "تحديد الصور", + "select_quality": "تحديد الدقة", "select_trash_all": "تحديد حذف الكلِ", "select_user_for_sharing_page_err_album": "فشل في إنشاء ألبوم", "selected": "التحديد", @@ -2146,6 +2169,8 @@ "share_assets_selected": "اختيار {count}", "share_dialog_preparing": "تحضير...", "share_link": "مشاركة رابط", + "share_original": "استخدام الملف الأصلي", + "share_preview": "استخدام الصورة المصغرة", "shared": "مُشتَرك", "shared_album_activities_input_disable": "التعليق معطل", "shared_album_activity_remove_content": "هل تريد حذف هذا النشاط؟", @@ -2247,6 +2272,7 @@ "slideshow_repeat_description": "العودة إلى البداية عند انتهاء عرض الشرائح", "slideshow_settings": "إعدادات عرض الشرائح", "smart_album": "ألبوم ذكي", + "some_assets_already_have_a_location_warning": "بعض الملفات المحددة تحتوي بالفعل على موقع جغرافي", "sort_albums_by": "رتب الألبومات حسب...", "sort_created": "تاريخ الإنشاء", "sort_items": "عدد العناصر", @@ -2367,11 +2393,13 @@ "trash_page_title": "سلة المهملات ({count})", "trashed_items_will_be_permanently_deleted_after": "سيتم حذفُ العناصر المحذوفة نِهائيًا بعد {days, plural, one {# يوم} other {# أيام }}.", "trigger": "مفعِل", + "trigger_asset_metadata_extraction": "استخراج البيانات الوصفية للملفات", + "trigger_asset_metadata_extraction_description": "يتم تفعيله عند استخراج البيانات الوصفية (EXIF) للملف", "trigger_asset_uploaded": "رفع الاصل", "trigger_asset_uploaded_description": "يتم تفعيله عند تحميل أصل جديد", "trigger_description": "حدث يبدأ سير العمل", "trigger_person_recognized": "تم التعرف على شخص", - "trigger_person_recognized_description": "يتم تفعيله عند اكتشاف شخص", + "trigger_person_recognized_description": "يتم تفعيله عند التعرف على شخص", "trigger_type": "نوع المفعل", "troubleshoot": "استكشاف المشاكل", "type": "النوع", @@ -2413,6 +2441,7 @@ "updated_password": "تم تحديث كلمة المرور", "upload": "رفع", "upload_concurrency": "الرفع المتزامن", + "upload_day_count": "{date}: {count, plural, one {# رفع الملف} other {# رفع الملفات}}", "upload_details": "تفاصيل الرفع", "upload_dialog_info": "هل تريد النسخ الاحتياطي للأصول (الأصول) المحددة إلى الخادم؟", "upload_dialog_title": "تحميل الأصول", @@ -2428,6 +2457,8 @@ "upload_to_immich": "الرفع الىImmich ‎ ‏ ({count})", "uploading": "جاري الرفع", "uploading_media": "رفع الوسائط", + "uploads": "عمليات الرفع", + "uploads_count": "{count, plural, one {# رفع الملف} other {# رفع الملفات}}", "url": "عنوان URL", "usage": "الاستخدام", "use_biometric": "استخدم البايومتري", diff --git a/i18n/be.json b/i18n/be.json index c3901e8c1b..024c4ae91c 100644 --- a/i18n/be.json +++ b/i18n/be.json @@ -192,11 +192,14 @@ "maintenance_delete_backup": "Выдаліць рэзервовую копію", "maintenance_delete_backup_description": "Гэты файл будзе беззваротна выдалены.", "maintenance_delete_error": "Не атрымалася выдаліць рэзервовую копію.", + "maintenance_integrity_check": "Праверыць", "maintenance_integrity_check_all": "Праверыць усе", "maintenance_integrity_checksum_mismatch": "Несупадзенне кантрольнай сумы", + "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_restore_backup": "Аднавіць рэзервовую копію", @@ -1517,7 +1520,6 @@ "map_location_picker_page_use_location": "Выкарыстаць гэта месцазнаходжанне", "map_location_service_disabled_content": "Каб паказваць аб’екты з вашага бягучага месцазнаходжання, трэба ўключыць службу геалакацыі. Жадаеце ўключыць яе зараз?", "map_location_service_disabled_title": "Служба геалакацыі адключана", - "map_marker_for_images": "Маркер на карце для відарысаў, зробленых у {city}, {country}", "map_marker_with_image": "Маркер на карце з відарысам", "map_no_location_permission_content": "Каб паказваць аб’екты з вашага бягучага месцазнаходжання, патрэбен дазвол на геалакацыю. Дазволіць зараз?", "map_no_location_permission_title": "Адмоўлена ў дазволе на геалакацыю", diff --git a/i18n/bg.json b/i18n/bg.json index b4aa9a0aba..48f3497783 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "Включване на Интелигентно Търсене", "machine_learning_smart_search_enabled_description": "Ако е деактивирано, изображенията няма да бъдат кодирани за Интелигентно Търсене.", "machine_learning_url_description": "URL на сървъра за машинно обучение. Ако са предоставени повече от един URL, всеки сървър ще бъде опитан един по един, докато един отговори успешно, в реда от първия до последния. Сървъри, които не отговорят, ще бъдат временно игнорирани, докато не се върнат онлайн.", + "maintenance_backup_management": "Управление на архивирането", "maintenance_delete_backup": "Изтриване на архив", "maintenance_delete_backup_description": "Този файл ще бъде безвъзвратно изтрит.", "maintenance_delete_error": "Неуспешно изтриване на архив.", + "maintenance_integrity_check": "Проверка", "maintenance_integrity_check_all": "Провери всички", "maintenance_integrity_checksum_mismatch": "Несъответствие на контролната сума", + "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": "Възстановяване на архив", @@ -1543,7 +1548,7 @@ "map_location_picker_page_use_location": "Използвай това местоположение", "map_location_service_disabled_content": "За да се показват обектите от текущото място, трябва да бъде включена услугата за местоположение. Искате ли да я включите сега?", "map_location_service_disabled_title": "Услугата за местоположение е изключена", - "map_marker_for_images": "Маркери на картата за снимки направени в {city}, {country}", + "map_marker_for_image": "Маркер на картата за снимка, направена в {city}, {country}", "map_marker_with_image": "Маркер на картата с изображение", "map_no_location_permission_content": "За да се показват обектите от текущото място, трябва разрешение за определяне на местоположението. Искате ли да предоставите разрешение сега?", "map_no_location_permission_title": "Отказан достъп до местоположение", diff --git a/i18n/bn.json b/i18n/bn.json index ea70f9ad3e..a6b0b09fa1 100644 --- a/i18n/bn.json +++ b/i18n/bn.json @@ -27,6 +27,7 @@ "add_partner": "অংশীদার যোগ করুন", "add_path": "পাথ যুক্ত করুন", "add_photos": "ছবি যুক্ত করুন", + "add_step": "ধাপ যোগ করুন", "add_tag": "ট্যাগ যুক্ত করুন", "add_to": "যুক্ত করুন…", "add_to_album": "এলবাম এ যোগ করুন", @@ -78,6 +79,7 @@ "cron_expression_description": "Cron ফরম্যাট ব্যবহার করে স্ক্যানিং ইন্টারভ্যাল নির্ধারণ করুন। আরও তথ্যের জন্য দয়া করে Crontab Guru দেখুন", "cron_expression_presets": "Cron এক্সপ্রেশন প্রিসেট", "disable_login": "লগইন অক্ষম করুন", + "download_csv": "CSV ডাউনলোড করুন", "duplicate_detection_job_description": "সদৃশ ছবি শনাক্ত করতে অ্যাসেটগুলোর উপর মেশিন লার্নিং চালান। এটি Smart Search-এর উপর নির্ভর করে", "exclusion_pattern_description": "এক্সক্লুশন প্যাটার্ন ব্যবহার করে লাইব্রেরি স্ক্যান করার সময় নির্দিষ্ট ফাইল ও ফোল্ডার উপেক্ষা করা যায়। এটি তখনই উপকারী যখন কিছু ফোল্ডারে এমন ফাইল থাকে যা আপনি ইমপোর্ট করতে চান না, যেমন RAW ফাইল।", "export_config_as_json_description": "বর্তমান সিস্টেম কনফিগারেশনটিকে একটি JSON ফাইল হিসেবে ডাউনলোড করুন", @@ -187,9 +189,11 @@ "machine_learning_smart_search_enabled": "স্মার্ট সার্চ সক্ষম করুন", "machine_learning_smart_search_enabled_description": "নিষ্ক্রিয় থাকলে, স্মার্ট সার্চের জন্য ছবিগুলো এনকোড (encode) করা হবে না।", "machine_learning_url_description": "মেশিন লার্নিং সার্ভারের URL। যদি একের বেশি URL প্রদান করা হয়, তবে একটি সফলভাবে সাড়া না দেওয়া পর্যন্ত প্রতিটি সার্ভারে এক এক করে চেষ্টা করা হবে (প্রথম থেকে শেষ ক্রমানুসারে)। যে সার্ভারগুলো সাড়া দেবে না, সেগুলো পুনরায় সচল হওয়া পর্যন্ত সাময়িকভাবে উপেক্ষা করা হবে।", + "maintenance_backup_management": "ব্যাকআপ ব্যবস্থাপনা", "maintenance_delete_backup": "ব্যাকআপ (Backup)মুছুন", "maintenance_delete_backup_description": "এই ফাইলটি চিরতরে মুছে ফেলা হবে।", "maintenance_delete_error": "ব্যাকআপ মুছে ফেলতে ব্যর্থ হয়েছে।", + "maintenance_integrity_check": "যাচাই", "maintenance_restore_backup": "ব্যাকআপ পুনরুদ্ধার(Restore) করুন", "maintenance_restore_backup_description": "Immich মুছে ফেলা হবে এবং নির্বাচিত ব্যাকআপ থেকে পুনরুদ্ধার করা হবে। কার্যক্রম চালিয়ে যাওয়ার আগে একটি ব্যাকআপ তৈরি করা হবে।", "maintenance_restore_backup_different_version": "এই ব্যাকআপটি Immich-এর একটি ভিন্ন সংস্করণের মাধ্যমে তৈরি করা হয়েছিল!", diff --git a/i18n/ca.json b/i18n/ca.json index 3c3a312fbf..01a8227b94 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "Activa la cerca intel·ligent", "machine_learning_smart_search_enabled_description": "Si està desactivada, les imatges no es codificaran per la cerca intel·ligent.", "machine_learning_url_description": "L'URL del servidor d'aprenentatge automàtic. Si es proporciona més d'un URL, s'intentarà accedir a cada servidor en ordre fins que un d'ells respongui correctament.", + "maintenance_backup_management": "Gestió de còpies de seguretat", "maintenance_delete_backup": "Elimina la còpia de seguretat", "maintenance_delete_backup_description": "Aquest fitxer s'eliminarà de forma permanent.", "maintenance_delete_error": "No s'ha pogut suprimir la còpia de seguretat.", + "maintenance_integrity_check": "Verificació", "maintenance_integrity_check_all": "Verificar tot", "maintenance_integrity_checksum_mismatch": "Checksum incorrecte", + "maintenance_integrity_checksum_mismatch_description": "Fitxers els quals la suma de verificació al disc no coincideix amb la que Immich té emmagatzemada a la base de dades.", "maintenance_integrity_checksum_mismatch_job": "Comprovar checksums", "maintenance_integrity_checksum_mismatch_refresh_job": "Actualitzar errors de checksums", "maintenance_integrity_missing_file": "Manquen fitxers", + "maintenance_integrity_missing_file_description": "Fitxers que l'Immich té registrats a la seva base de dades però que no existeixen al sistema de fitxers.", "maintenance_integrity_missing_file_job": "Verificar fitxers que falten", "maintenance_integrity_missing_file_refresh_job": "Refrescar informe de fitxers desapareguts", "maintenance_integrity_report": "Informe Integritat", "maintenance_integrity_untracked_file": "Arxius no rastrejats", + "maintenance_integrity_untracked_file_description": "Fitxers presents als directoris d'Immich que Immich no en té cap registre.", "maintenance_integrity_untracked_file_job": "Consulta de fitxers no rastrejats", "maintenance_integrity_untracked_file_refresh_job": "Actualitza els informes de fitxers no rastrejats", "maintenance_restore_backup": "Restaura la còpia de seguretat", @@ -483,7 +488,7 @@ "advanced_settings_prefer_remote_title": "Prefereix imatges remotes", "advanced_settings_proxy_headers_subtitle": "Definiu les capçaleres de proxy que Immich per enviar amb cada sol·licitud de xarxa", "advanced_settings_proxy_headers_title": "Capçaleres de proxy particulars [EXPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Habilita el només de lectura mode on les fotos poden ser només vist, a coses els agrada seleccionant imatges múltiples, compartint, càsting, elimina és tot discapacitat. Habilita/Desactiva només de lectura via avatar d'usuari des de la pantalla major", + "advanced_settings_readonly_mode_subtitle": "Activa el mode de només lectura, en què les fotos només es poden veure. Accions com seleccionar múltiples imatges, compartir, enviar a un dispositiu o eliminar queden desactivades. Activa o desactiva el mode de només lectura des de l’avatar d’usuari de la pantalla principal", "advanced_settings_readonly_mode_title": "Mode de només lectura", "advanced_settings_self_signed_ssl_subtitle": "Omet la verificació del certificat SSL del servidor. Requerit per a certificats autosignats.", "advanced_settings_self_signed_ssl_title": "Permet certificats SSL autosignats [EXPERIMENTAL]", @@ -1543,7 +1548,7 @@ "map_location_picker_page_use_location": "Utilitzar aquesta ubicació", "map_location_service_disabled_content": "El servei de localització s'ha d'activar per mostrar els elements de la teva ubicació actual. Vols activar-lo ara?", "map_location_service_disabled_title": "Servei de localització desactivat", - "map_marker_for_images": "Marcador de mapa per a imatges fetes a {city}, {country}", + "map_marker_for_image": "Marcador de mapa per a imatge obtinguda a {city}, {country}", "map_marker_with_image": "Marcador de mapa amb imatge", "map_no_location_permission_content": "Es necessita el permís de localització per mostrar els elements de la teva ubicació actual. Vols permetre-ho ara?", "map_no_location_permission_title": "Permís de localització denegat", diff --git a/i18n/cs.json b/i18n/cs.json index 44b9cf8f36..022614599b 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "Povolit chytré vyhledávání", "machine_learning_smart_search_enabled_description": "Pokud je vypnuto, obrázky nebudou kódovány pro inteligentní vyhledávání.", "machine_learning_url_description": "URL serveru strojového učení. Pokud je zadáno více URL adres, budou jednotlivé servery zkoušeny postupně, dokud jeden z nich neodpoví úspěšně, a to v pořadí od prvního k poslednímu. Servery, které neodpoví, budou dočasně ignorovány, dokud nebudou opět online.", + "maintenance_backup_management": "Správa záloh", "maintenance_delete_backup": "Smazat zálohu", "maintenance_delete_backup_description": "Tento soubor bude trvale smazán.", "maintenance_delete_error": "Nepodařilo se smazat zálohu.", + "maintenance_integrity_check": "Zkontrolovat", "maintenance_integrity_check_all": "Zkontrolovat vše", "maintenance_integrity_checksum_mismatch": "Neshoda kontrolního součtu", + "maintenance_integrity_checksum_mismatch_description": "Soubory, u nichž se kontrolní součet na disku neshoduje s kontrolním součtem, který Immich uložil do své databáze.", "maintenance_integrity_checksum_mismatch_job": "Kontrola shod kontrolních součtů", "maintenance_integrity_checksum_mismatch_refresh_job": "Obnovit hlášení o neshodách kontrolních součtů", "maintenance_integrity_missing_file": "Chybějící soubory", + "maintenance_integrity_missing_file_description": "Soubory, které Immich sleduje ve své databázi, ale které neexistují v souborovém systému.", "maintenance_integrity_missing_file_job": "Kontrola chybějících souborů", "maintenance_integrity_missing_file_refresh_job": "Obnovit hlášení o chybějících souborech", "maintenance_integrity_report": "Hlášení o integritě", "maintenance_integrity_untracked_file": "Nesledované soubory", + "maintenance_integrity_untracked_file_description": "Soubory v adresářích služby Immich, o nichž nemá Immich žádný záznam.", "maintenance_integrity_untracked_file_job": "Kontrola nesledovaných souborů", "maintenance_integrity_untracked_file_refresh_job": "Obnovit hlášení o nesledovaných souborech", "maintenance_restore_backup": "Obnovit zálohu", @@ -1543,7 +1548,7 @@ "map_location_picker_page_use_location": "Použít tuto polohu", "map_location_service_disabled_content": "Pro zobrazení fotek z vaší aktuální polohy musí být povolena služba určování polohy. Chcete ji nyní povolit?", "map_location_service_disabled_title": "Služba určování polohy je zakázána", - "map_marker_for_images": "Značka na mapě pro snímky pořízené v {city}, {country}", + "map_marker_for_image": "Značka na mapě pro obrázek pořízený ve městě {city}, {country}", "map_marker_with_image": "Značka mapy s obrázkem", "map_no_location_permission_content": "Oprávnění polohy je nutné pro zobrazení fotek z vaší aktuální polohy. Chcete oprávnění nyní povolit?", "map_no_location_permission_title": "Oprávnění polohy zamítnuto", diff --git a/i18n/cv.json b/i18n/cv.json index 52008a176f..6ceb8fb0c9 100644 --- a/i18n/cv.json +++ b/i18n/cv.json @@ -70,7 +70,6 @@ "feature_photo_updated": "Уйрӑм сӑнӳкерчӗк ҫӗнетнӗ", "manage_sharing_with_partners": "Партнерсемпе пайланассине йӗркелесе пырӑр", "map": "Карттӑ", - "map_marker_for_images": "{city}, {country} ҫинче ӳкернӗ ӳкерчӗксем валли карттӑ маркерӗ", "map_marker_with_image": "Карттӑ маркерӗ ӳкерчӗкпе", "map_settings": "Карттӑ ĕнерленĕвĕ", "no_explore_results_message": "Хӑвӑр коллекципе киленмешкӗн сӑнӳкерчӗксем ытларах тийӗр.", diff --git a/i18n/da.json b/i18n/da.json index 534d7761d9..e852945143 100644 --- a/i18n/da.json +++ b/i18n/da.json @@ -1526,7 +1526,6 @@ "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_images": "Kortmarkør for billeder 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", diff --git a/i18n/de.json b/i18n/de.json index dddb42e3f6..38b9f710c1 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -63,7 +63,7 @@ "backup_onboarding_footer": "Weitere Informationen zum Sichern von Immich findest du in der Dokumentation.", "backup_onboarding_parts_title": "Eine 3-2-1-Sicherung umfasst:", "backup_onboarding_title": "Sicherungen", - "backup_settings": "Einstellungen für Datenbanksicherung", + "backup_settings": "Einstellungen zur Datenbanksicherung", "backup_settings_description": "Einstellungen zur regelmäßigen Sicherung der Datenbank. Hinweis: Diese Jobs werden nicht überwacht und du wirst nicht über Fehler informiert.", "cleared_jobs": "Folgende Aufgaben zurückgesetzt: {job}", "config_set_by_file": "Ist derzeit in einer Konfigurationsdatei festgelegt", @@ -189,19 +189,24 @@ "machine_learning_smart_search_enabled": "Intelligente Suche aktivieren", "machine_learning_smart_search_enabled_description": "Ist diese Option deaktiviert, werden die Bilder nicht für die intelligente Suche verwendet.", "machine_learning_url_description": "Die URL des Servers für maschinelles Lernen. Wenn mehr als eine URL angegeben wird, wird jeder Server einzeln ausprobiert, bis einer erfolgreich antwortet, und zwar in der Reihenfolge vom ersten bis zum letzten. Server die nicht antworten werden temporär ignoriert, bis sie wieder verfügbar sind.", + "maintenance_backup_management": "Backup Management", "maintenance_delete_backup": "Backup löschen", "maintenance_delete_backup_description": "Diese Datei wird irreversibel gelöscht.", "maintenance_delete_error": "Die Löschung der Sicherungskopie ist fehlgeschlagen.", + "maintenance_integrity_check": "Überprüfe", "maintenance_integrity_check_all": "Überprüfe alle", "maintenance_integrity_checksum_mismatch": "Prüfsummenfehler", - "maintenance_integrity_checksum_mismatch_job": "Auf Prüfsummen-Nichtübereinstimmungen prüfen", + "maintenance_integrity_checksum_mismatch_description": "Dateien, deren Prüfsumme auf dem Datenträger nicht mit der übereinstimmt, die Immich in seiner Datenbank gespeichert hat.", + "maintenance_integrity_checksum_mismatch_job": "Auf Prüfsummenfehler überprüfen", "maintenance_integrity_checksum_mismatch_refresh_job": "Aktualisieren Sie Berichte über Prüfsummenkonflikte", "maintenance_integrity_missing_file": "Fehlende Dateien", + "maintenance_integrity_missing_file_description": "Dateien, die Immich in seiner Datenbank erfasst hat, die aber im Dateisystem nicht vorhanden sind.", "maintenance_integrity_missing_file_job": "Auf fehlende Dateien prüfen", "maintenance_integrity_missing_file_refresh_job": "Berichte über fehlende Dateien aktualisieren", "maintenance_integrity_report": "Integritätsbericht", "maintenance_integrity_untracked_file": "Nicht getrackte Dateien", - "maintenance_integrity_untracked_file_job": "Überprüfen Sie ungetrackte Dateien", + "maintenance_integrity_untracked_file_description": "Dateien in Immichs Verzeichnissen, über die Immich keine Aufzeichnungen hat.", + "maintenance_integrity_untracked_file_job": "Auf nicht verfolgte Dateien prüfen", "maintenance_integrity_untracked_file_refresh_job": "Berichte über nicht getrackte Dateien aktualisieren", "maintenance_restore_backup": "Sicherungskopie wiederherstellen", "maintenance_restore_backup_description": "Immich wird zurückgesetzt und von der ausgewählten Sicherungskopie wiederhergestellt. Ein Backup wird erstellt, bevor es weitergeht.", @@ -1543,7 +1548,7 @@ "map_location_picker_page_use_location": "Diesen Standort verwenden", "map_location_service_disabled_content": "Ortungsdienste müssen aktiviert sein, um Inhalte am aktuellen Standort anzuzeigen. Willst du die Ortungsdienste jetzt aktivieren?", "map_location_service_disabled_title": "Ortungsdienste deaktiviert", - "map_marker_for_images": "Kartenmarkierung für Bilder, die in {city}, {country} aufgenommen wurden", + "map_marker_for_image": "Kartenmarkierung für in {city}, {country} aufgenommenes Bild", "map_marker_with_image": "Kartenmarkierung mit Bild", "map_no_location_permission_content": "Ortungsdienste müssen aktiviert sein, um Inhalte am aktuellen Standort anzuzeigen. Willst du die Ortungsdienste jetzt aktivieren?", "map_no_location_permission_title": "Kein Zugriff auf den Standort", diff --git a/i18n/de_CH.json b/i18n/de_CH.json index c463b5fe36..3d70d64caa 100644 --- a/i18n/de_CH.json +++ b/i18n/de_CH.json @@ -79,6 +79,7 @@ "cron_expression_description": "Setze das Scanintervall im Cron-Format. Für mehr Informationen, siehe z. B. Crontab Guru", "cron_expression_presets": "Vorlagen für Cron-Ausdrücke", "disable_login": "Login deaktivierä", + "download_csv": "CSV herunterladen", "duplicate_detection_job_description": "Verwendet maschinelles Lernen auf den Dateien, um Duplikate zu finden. Baut auf der intelligenten Suche auf", "exclusion_pattern_description": "Mit Ausschlussmustern können Dateien und Ordner beim Scannen deiner Bibliothek ignoriert werden. Dies ist nützlich, wenn du Ordner hast, die Dateien enthalten, die du nicht importieren möchtest, wie z. B. RAW-Dateien.", "export_config_as_json_description": "Aktuelle Systemkonfiguration als JSON-Datei herunterladen", diff --git a/i18n/el.json b/i18n/el.json index b810bfccec..d08ce395ff 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -1495,7 +1495,6 @@ "map_location_picker_page_use_location": "Χρησιμοποιήστε αυτήν την τοποθεσία", "map_location_service_disabled_content": "Η υπηρεσία τοποθεσίας πρέπει να είναι ενεργοποιημένη για την εμφάνιση στοιχείων από την τρέχουσα τοποθεσία σας. Θέλετε να το ενεργοποιήσετε τώρα;", "map_location_service_disabled_title": "Η υπηρεσία τοποθεσίας απενεργοποιήθηκε", - "map_marker_for_images": "Δείκτης χάρτη για εικόνες που τραβήχτηκαν σε {city}, {country}", "map_marker_with_image": "Χάρτης δείκτη με εικόνα", "map_no_location_permission_content": "Απαιτείται άδεια τοποθεσίας για την εμφάνιση στοιχείων από την τρέχουσα τοποθεσία σας. Θέλετε να το επιτρέψετε τώρα;", "map_no_location_permission_title": "Η άδεια τοποθεσίας απορρίφθηκε", diff --git a/i18n/en.json b/i18n/en.json index 648138a878..1587fde604 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -10,22 +10,18 @@ "active": "Active", "active_count": "Active: {count}", "activity": "Activity", - "activity_changed": "Activity is {enabled, select, true {enabled} other {disabled}}", "add": "Add", "add_a_description": "Add a description", "add_a_location": "Add a location", "add_a_name": "Add a name", "add_a_title": "Add a title", "add_action": "Add action", - "add_action_description": "Click to add an action to perform", "add_assets": "Add assets", "add_birthday": "Add a birthday", "add_endpoint": "Add endpoint", "add_exclusion_pattern": "Add exclusion pattern", "add_location": "Add location", - "add_more_users": "Add more users", "add_partner": "Add partner", - "add_path": "Add path", "add_photos": "Add photos", "add_step": "Add step", "add_tag": "Add tag", @@ -34,11 +30,9 @@ "add_to_album_bottom_sheet_added": "Added to {album}", "add_to_album_bottom_sheet_already_exists": "Already in {album}", "add_to_album_bottom_sheet_some_local_assets": "Some local assets could not be added to album", - "add_to_album_toggle": "Toggle selection for {album}", "add_to_albums": "Add to albums", "add_to_albums_count": "Add to albums ({count})", "add_to_bottom_bar": "Add to", - "add_to_shared_album": "Add to shared album", "add_upload_to_stack": "Add upload to stack", "add_url": "Add URL", "added_to_archive": "Added to archive", @@ -481,8 +475,6 @@ "advanced_settings_clear_image_cache": "Clear Image Cache", "advanced_settings_clear_image_cache_error": "Failed to clear image cache", "advanced_settings_clear_image_cache_success": "Successfully cleared {size}", - "advanced_settings_enable_alternate_media_filter_subtitle": "Use this option to filter media during sync based on alternate criteria. Only try this if you have issues with the app detecting all albums.", - "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Use alternate device album sync filter", "advanced_settings_log_level_title": "Log level: {level}", "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from local assets. Activate this setting to load remote images instead.", "advanced_settings_prefer_remote_title": "Prefer remote images", @@ -490,8 +482,6 @@ "advanced_settings_proxy_headers_title": "Custom proxy headers [EXPERIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Enables the read-only mode where the photos can be only viewed, things like selecting multiple images, sharing, casting, delete are all disabled. Enable/Disable read-only via user avatar from the main screen", "advanced_settings_readonly_mode_title": "Read-only mode", - "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", - "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates [EXPERIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Automatically delete or restore an asset on this device when that action is taken on the web", "advanced_settings_sync_remote_deletions_title": "Sync remote deletions [EXPERIMENTAL]", "advanced_settings_tile_subtitle": "Advanced user's settings", @@ -507,31 +497,18 @@ "album_delete_confirmation": "Are you sure you want to delete the album {album}?", "album_delete_confirmation_description": "If this album is shared, other users will not be able to access it anymore.", "album_deleted": "Album deleted", - "album_info_card_backup_album_excluded": "EXCLUDED", - "album_info_card_backup_album_included": "INCLUDED", "album_info_updated": "Album info updated", - "album_leave": "Leave album?", - "album_leave_confirmation": "Are you sure you want to leave {album}?", "album_name": "Album Name", "album_options": "Album options", "album_remove_user": "Remove user?", "album_remove_user_confirmation": "Are you sure you want to remove {user}?", "album_search_not_found": "No albums found matching your search", - "album_selected": "Album selected", "album_share_no_users": "Looks like you have shared this album with all users or you don't have any user to share with.", "album_summary": "Album summary", "album_updated": "Album updated", "album_updated_setting_description": "Receive an email notification when a shared album has new assets", "album_upload_assets": "Upload assets from your computer and add to album", - "album_user_left": "Left {album}", - "album_user_removed": "Removed {user}", - "album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?", "album_viewer_appbar_share_err_delete": "Failed to delete album", - "album_viewer_appbar_share_err_leave": "Failed to leave album", - "album_viewer_appbar_share_err_remove": "There are problems in removing assets from album", - "album_viewer_appbar_share_err_title": "Failed to change album title", - "album_viewer_appbar_share_leave": "Leave album", - "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Add users", "album_with_link_access": "Let anyone with the link see photos and people in this album.", "albums": "Albums", @@ -540,14 +517,12 @@ "albums_default_sort_order_description": "Initial asset sort order when creating new albums.", "albums_feature_description": "Collections of assets that can be shared with other users.", "albums_on_device_count": "Albums on device ({count})", - "albums_selected": "{count, plural, one {# album selected} other {# albums selected}}", "all": "All", "all_albums": "All albums", "all_people": "All people", "all_photos": "All photos", "all_videos": "All videos", "allow_dark_mode": "Allow dark mode", - "allow_edits": "Allow edits", "allow_public_user_to_download": "Allow public user to download", "allow_public_user_to_upload": "Allow public user to upload", "allowed": "Allowed", @@ -555,14 +530,12 @@ "always_keep": "Always keep", "always_keep_photos_hint": "Free Up Space will keep all photos on this device.", "always_keep_videos_hint": "Free Up Space will keep all videos on this device.", - "anti_clockwise": "Anti-clockwise", "api_key": "API Key", "api_key_description": "This value will only be shown once. Please be sure to copy it before closing the window.", "api_key_empty": "Your API Key name shouldn't be empty", "api_keys": "API Keys", "app_architecture_variant": "Variant (Architecture)", "app_bar_signout_dialog_content": "Are you sure you want to sign out?", - "app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_title": "Sign out", "app_download_links": "App Download Links", "app_settings": "App Settings", @@ -573,28 +546,19 @@ "archive": "Archive", "archive_action_prompt": "{count} added to Archive", "archive_or_unarchive_photo": "Archive or unarchive photo", - "archive_page_no_archived_assets": "No archived assets found", - "archive_page_title": "Archive ({count})", "archive_size": "Archive size", "archive_size_description": "Configure the archive size for downloads (in GiB)", "archived": "Archived", "archived_count": "{count, plural, other {Archived #}}", "are_these_the_same_person": "Are these the same person?", "are_you_sure_to_do_this": "Are you sure you want to do this?", - "array_field_not_fully_supported": "Array fields require manual JSON editing", - "asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping", - "asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping", "asset_added_to_album": "Added to album", "asset_adding_to_album": "Adding to album…", "asset_created": "Asset created", "asset_day_count": "{date}: {count, plural, one {# asset} other {# assets}}", "asset_description_updated": "Asset description has been updated", - "asset_filename_is_offline": "Asset {filename} is offline", - "asset_has_unassigned_faces": "Asset has unassigned faces", "asset_hashing": "Hashing…", "asset_list_group_by_sub_title": "Group by", - "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", - "asset_list_layout_settings_group_automatically": "Automatic", "asset_list_layout_settings_group_by": "Group assets by", "asset_list_layout_settings_group_by_month_day": "Month + day", "asset_list_layout_sub_title": "Layout", @@ -605,36 +569,26 @@ "asset_not_found_on_icloud": "Asset not found on iCloud. the asset may be inaccessible due to bad file stored on iCloud", "asset_offline": "Asset Offline", "asset_offline_description": "This external asset is no longer found on disk. Please contact your Immich administrator for help.", - "asset_restored_successfully": "Asset restored successfully", "asset_skipped": "Skipped", "asset_skipped_in_trash": "In trash", - "asset_trashed": "Asset trashed", "asset_troubleshoot": "Asset Troubleshoot", "asset_uploaded": "Uploaded", "asset_uploading": "Uploading…", "asset_viewer_settings_subtitle": "Manage your gallery viewer settings", "asset_viewer_settings_title": "Asset Viewer", "assets": "Assets", - "assets_added_count": "Added {count, plural, one {# asset} other {# assets}}", "assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album", "assets_added_to_albums_count": "Added {assetTotal, plural, one {# asset} other {# assets}} to {albumTotal, plural, one {# album} other {# albums}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album", "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} cannot be added to any of the albums", "assets_count": "{count, plural, one {# asset} other {# assets}}", - "assets_deleted_permanently": "{count} asset(s) deleted permanently", - "assets_deleted_permanently_from_server": "{count} asset(s) deleted permanently from the Immich server", - "assets_downloaded_failed": "{count, plural, one {Downloaded # file - {error} file failed} other {Downloaded # files - {error} files failed}}", - "assets_downloaded_successfully": "{count, plural, one {Downloaded # file successfully} other {Downloaded # files successfully}}", "assets_moved_to_trash_count": "Moved {count, plural, one {# asset} other {# assets}} to trash", "assets_permanently_deleted_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}", "assets_removed_count": "Removed {count, plural, one {# asset} other {# assets}}", - "assets_removed_permanently_from_device": "{count} asset(s) removed permanently from your device", "assets_restore_confirmation": "Are you sure you want to restore all your trashed assets? You cannot undo this action! Note that any offline assets cannot be restored this way.", "assets_restored_count": "Restored {count, plural, one {# asset} other {# assets}}", - "assets_restored_successfully": "{count} asset(s) restored successfully", "assets_trashed": "{count} asset(s) trashed", "assets_trashed_count": "Trashed {count, plural, one {# asset} other {# assets}}", - "assets_trashed_from_server": "{count} asset(s) trashed from the Immich server", "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} already part of the album", "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} already part of the albums", "authorized_devices": "Authorized Devices", @@ -643,86 +597,45 @@ "autoplay_slideshow": "Autoplay slideshow", "back": "Back", "back_close_deselect": "Back, close, or deselect", - "background_backup_running_error": "Background backup is currently running, cannot start manual backup", "background_location_permission": "Background location permission", "background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", "background_options": "Background Options", "backup": "Backup", - "backup_album_selection_page_albums_device": "Albums on device ({count})", "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude", "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.", "backup_album_selection_page_select_albums": "Select albums", "backup_album_selection_page_selection_info": "Selection Info", - "backup_album_selection_page_total_assets": "Total unique assets", "backup_albums_sync": "Backup Albums Synchronization", - "backup_all": "All", - "backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…", "backup_background_service_complete_notification": "Asset backup complete", - "backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…", - "backup_background_service_current_upload_notification": "Uploading {filename}", "backup_background_service_default_notification": "Checking for new assets…", - "backup_background_service_error_title": "Backup error", "backup_background_service_in_progress_notification": "Backing up your assets…", - "backup_background_service_upload_failure_notification": "Failed to upload {filename}", "backup_controller_page_albums": "Backup Albums", - "backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.", - "backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled", - "backup_controller_page_background_app_refresh_enable_button_text": "Go to settings", "backup_controller_page_background_battery_info_link": "Show me how", "backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Battery optimizations", - "backup_controller_page_background_charging": "Only while charging", - "backup_controller_page_background_configure_error": "Failed to configure the background service", "backup_controller_page_background_delay": "Delay new assets backup: {duration}", - "backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app", - "backup_controller_page_background_is_off": "Automatic background backup is off", - "backup_controller_page_background_is_on": "Automatic background backup is on", - "backup_controller_page_background_turn_off": "Turn off background service", - "backup_controller_page_background_turn_on": "Turn on background service", - "backup_controller_page_background_wifi": "Only on Wi-Fi", "backup_controller_page_backup": "Backup", "backup_controller_page_backup_selected": "Selected: ", "backup_controller_page_backup_sub": "Backed up photos and videos", - "backup_controller_page_created": "Created on: {date}", - "backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.", "backup_controller_page_excluded": "Excluded: ", - "backup_controller_page_failed": "Failed ({count})", - "backup_controller_page_filename": "File name: {filename} [{size}]", - "backup_controller_page_id": "ID: {id}", - "backup_controller_page_info": "Backup Information", "backup_controller_page_none_selected": "None selected", "backup_controller_page_remainder": "Remainder", "backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection", "backup_controller_page_server_storage": "Server Storage", - "backup_controller_page_start_backup": "Start Backup", - "backup_controller_page_status_off": "Automatic foreground backup is off", - "backup_controller_page_status_on": "Automatic foreground backup is on", "backup_controller_page_storage_format": "{used} of {total} used", "backup_controller_page_to_backup": "Albums to be backed up", "backup_controller_page_total_sub": "All unique photos and videos from selected albums", - "backup_controller_page_turn_off": "Turn off foreground backup", - "backup_controller_page_turn_on": "Turn on foreground backup", - "backup_controller_page_uploading_file_info": "Uploading file info", - "backup_err_only_album": "Cannot remove the only album", "backup_error_sync_failed": "Sync failed. Cannot process backup.", "backup_info_card_assets": "assets", - "backup_manual_cancelled": "Cancelled", - "backup_manual_in_progress": "Upload already in progress. Try after sometime", - "backup_manual_success": "Success", - "backup_manual_title": "Upload status", "backup_options": "Backup Options", - "backup_options_page_title": "Backup options", - "backup_setting_subtitle": "Manage background and foreground upload settings", "backup_settings_subtitle": "Manage upload settings", - "backup_upload_details_page_more_details": "Tap for more details", "backward": "Backward", "battery_optimization_backup_reliability": "Disabling battery optimizations can improve the reliability of background backup", "biometric_auth_enabled": "Biometric authentication enabled", "biometric_locked_out": "You are locked out of biometric authentication", "biometric_no_options": "No biometric options available", "biometric_not_available": "Biometric authentication is not available on this device", - "birthdate_saved": "Date of birth saved successfully", "birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.", "blurred_background": "Blurred background", "browse_templates": "Browse templates", @@ -733,20 +646,6 @@ "bulk_keep_duplicates_confirmation": "Are you sure you want to keep {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will resolve all duplicate groups without deleting anything.", "bulk_trash_duplicates_confirmation": "Are you sure you want to bulk trash {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will keep the largest asset of each group and trash all other duplicates.", "buy": "Purchase Immich", - "cache_settings_clear_cache_button": "Clear cache", - "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.", - "cache_settings_duplicated_assets_clear_button": "CLEAR", - "cache_settings_duplicated_assets_subtitle": "Photos and videos that are ignore listed by the app", - "cache_settings_duplicated_assets_title": "Duplicated Assets ({count})", - "cache_settings_statistics_album": "Library thumbnails", - "cache_settings_statistics_full": "Full images", - "cache_settings_statistics_shared": "Shared album thumbnails", - "cache_settings_statistics_thumbnail": "Thumbnails", - "cache_settings_statistics_title": "Cache usage", - "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application", - "cache_settings_tile_subtitle": "Control the local storage behaviour", - "cache_settings_tile_title": "Local Storage", - "cache_settings_title": "Caching Settings", "camera": "Camera", "camera_brand": "Camera brand", "camera_model": "Camera model", @@ -763,7 +662,6 @@ "change_date": "Change date", "change_description": "Change description", "change_display_order": "Change display order", - "change_expiration_time": "Change expiration time", "change_location": "Change location", "change_name": "Change name", "change_name_successfully": "Changed name successfully", @@ -777,15 +675,10 @@ "change_password_form_password_mismatch": "Passwords do not match", "change_password_form_reenter_new_password": "Re-enter New Password", "change_pin_code": "Change PIN code", - "change_trigger": "Change trigger", - "change_trigger_prompt": "Are you sure you want to change the trigger? This will remove all existing actions and filters.", "change_your_password": "Change your password", "changed_visibility_successfully": "Changed visibility successfully", "charging": "Charging", "charging_requirement_mobile_backup": "Background backup requires the device to be charging", - "check_corrupt_asset_backup": "Check for corrupt asset backups", - "check_corrupt_asset_backup_button": "Perform check", - "check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.", "check_logs": "Check Logs", "checksum": "Checksum", "choose": "Choose", @@ -810,8 +703,6 @@ "clear_file_cache": "Clear File Cache", "clear_message": "Clear message", "clear_value": "Clear value", - "client_cert_dialog_msg_confirm": "OK", - "client_cert_enter_password": "Enter Password", "client_cert_import": "Import", "client_cert_import_success_msg": "Client certificate is imported", "client_cert_invalid_msg": "Invalid certificate file or wrong password", @@ -820,12 +711,10 @@ "client_cert_remove_msg": "Client certificate is removed", "client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate import/removal is available only before login", "client_cert_title": "SSL client certificate [EXPERIMENTAL]", - "clockwise": "Сlockwise", "close": "Close", "collapse": "Collapse", "collapse_all": "Collapse all", "color": "Color", - "color_theme": "Color theme", "command": "Command", "command_palette_prompt": "Quickly find pages, actions, or commands", "command_palette_to_close": "to close", @@ -854,18 +743,13 @@ "context": "Context", "continue": "Continue", "control_bottom_app_bar_add_tags": "Add Tags", - "control_bottom_app_bar_create_new_album": "Create new album", - "control_bottom_app_bar_delete_from_immich": "Delete from Immich", "control_bottom_app_bar_delete_from_local": "Delete from device", "control_bottom_app_bar_edit_location": "Edit Location", "control_bottom_app_bar_edit_time": "Edit Date & Time", - "control_bottom_app_bar_share_link": "Share Link", - "control_bottom_app_bar_share_to": "Share To", "control_bottom_app_bar_trash_from_immich": "Move to Trash", "copied_image_to_clipboard": "Copied image to clipboard.", "copied_to_clipboard": "Copied to clipboard!", "copy_error": "Copy error", - "copy_file_path": "Copy file path", "copy_image": "Copy Image", "copy_json": "Copy JSON", "copy_link": "Copy link", @@ -885,7 +769,6 @@ "create_link_to_share": "Create link to share", "create_link_to_share_description": "Let anyone with the link see the selected photo(s)", "create_new": "CREATE NEW", - "create_new_face": "Create new face", "create_new_person": "Create new person", "create_new_person_hint": "Assign selected assets to a new person", "create_new_user": "Create new user", @@ -902,11 +785,9 @@ "created_at": "Created", "creating_linked_albums": "Creating linked albums...", "crop": "Crop", - "crop_aspect_ratio_fixed": "Fixed", "crop_aspect_ratio_free": "Free", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Square", - "curated_object_page_title": "Things", "current_device": "Current device", "current_pin_code": "Current PIN code", "current_server_address": "Current server address", @@ -930,8 +811,6 @@ "day": "Day", "days": "Days", "deduplicate_all": "Deduplicate All", - "default_locale": "Default Locale", - "default_locale_description": "Format dates and numbers based on your browser locale", "default_quality_subtitle": "Quality used when tapping share. Long press the share button to choose each time.", "default_share_quality": "Default share quality", "delete": "Delete", @@ -942,8 +821,6 @@ "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device", "delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server", "delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device", - "delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server", - "delete_dialog_ok_force": "Delete Anyway", "delete_dialog_title": "Delete Permanently", "delete_duplicates_confirmation": "Are you sure you want to permanently delete these duplicates?", "delete_face": "Delete face", @@ -962,21 +839,16 @@ "delete_tag_confirmation_prompt": "Are you sure you want to delete {tagName} tag?", "delete_user": "Delete user", "deleted_shared_link": "Deleted shared link", - "deletes_missing_assets": "Deletes assets missing from disk", "description": "Description", - "description_input_hint_text": "Add description...", - "description_input_submit_error": "Error updating description, check the log for more details", "deselect_all": "Deselect All", "details": "Details", "direction": "Direction", "disable": "Disable", "disabled": "Disabled", - "disallow_edits": "Disallow edits", "discord": "Discord", "discover": "Discover", "discovered_devices": "Discovered devices", "dismiss_all_errors": "Dismiss all errors", - "dismiss_error": "Dismiss error", "display_options": "Display options", "display_order": "Display order", "display_original_photos": "Display original photos", @@ -985,11 +857,9 @@ "documentation": "Documentation", "done": "Done", "download": "Download", - "download_action_prompt": "Downloading {count} assets", "download_canceled": "Download canceled", "download_complete": "Download complete", "download_enqueue": "Download enqueued", - "download_error": "Download Error", "download_failed": "Download failed", "download_finished": "Download finished", "download_include_embedded_motion_videos": "Embedded videos", @@ -999,9 +869,6 @@ "download_paused": "Download paused", "download_settings": "Download", "download_settings_description": "Manage settings related to asset download", - "download_started": "Download started", - "download_sucess": "Download success", - "download_sucess_android": "The media has been downloaded to DCIM/Immich", "download_waiting_to_retry": "Waiting to retry", "downloading": "Downloading", "downloading_asset_filename": "Downloading asset {filename}", @@ -1022,9 +889,7 @@ "edit_date_and_time": "Edit date and time", "edit_date_and_time_action_prompt": "{count} date and time edited", "edit_date_and_time_by_offset": "Change date by offset", - "edit_date_and_time_by_offset_interval": "New date range: {from} - {to}", "edit_description": "Edit description", - "edit_description_prompt": "Please select a new description:", "edit_exclusion_pattern": "Edit exclusion pattern", "edit_faces": "Edit faces", "edit_key": "Edit key", @@ -1039,9 +904,6 @@ "edit_user": "Edit user", "edit_workflow": "Edit workflow", "editor": "Editor", - "editor_close_without_save_prompt": "The changes will not be saved", - "editor_close_without_save_title": "Close editor?", - "editor_confirm_reset_all_changes": "Are you sure you want to reset all changes?", "editor_discard_edits_confirm": "Discard edits", "editor_discard_edits_prompt": "You have unsaved edits. Are you sure you want to discard them?", "editor_discard_edits_title": "Discard edits?", @@ -1070,9 +932,7 @@ "enter_your_pin_code": "Enter your PIN code", "enter_your_pin_code_subtitle": "Enter your PIN code to access the locked folder", "error": "Error", - "error_change_sort_album": "Failed to change album sort order", "error_delete_face": "Error deleting face from asset", - "error_getting_places": "Error getting places", "error_loading_albums": "Error loading albums", "error_loading_image": "Error loading image", "error_loading_partners": "Error loading partners: {error}", @@ -1211,18 +1071,10 @@ "exif": "Exif", "exif_bottom_sheet_description": "Add Description...", "exif_bottom_sheet_description_error": "Error updating description", - "exif_bottom_sheet_details": "DETAILS", - "exif_bottom_sheet_location": "LOCATION", "exif_bottom_sheet_no_description": "No description", - "exif_bottom_sheet_people": "PEOPLE", - "exif_bottom_sheet_person_add_person": "Add name", "exit_slideshow": "Exit Slideshow", "expand": "Expand", "expand_all": "Expand all", - "experimental_settings_new_asset_list_subtitle": "Work in progress", - "experimental_settings_new_asset_list_title": "Enable experimental photo grid", - "experimental_settings_subtitle": "Use at your own risk!", - "experimental_settings_title": "Experimental", "expire_after": "Expire after", "expired": "Expired", "expires_date": "Expires {date}", @@ -1250,10 +1102,8 @@ "favorite_action_prompt": "{count} added to Favorites", "favorite_or_unfavorite_photo": "Favorite or unfavorite photo", "favorites": "Favorites", - "favorites_page_no_favorites": "No favorite assets found", "feature_photo_updated": "Feature photo updated", "features": "Features", - "features_in_development": "Features in Development", "features_setting_description": "Manage the app features", "file_name_or_extension": "File name or extension", "file_name_text": "File name", @@ -1261,12 +1111,10 @@ "filename": "Filename", "filetype": "Filetype", "filter": "Filter", - "filter_description": "Conditions to filter the target assets", "filter_people": "Filter people", "filter_places": "Filter places", "filter_tags": "Filter tags", "filters": "Filters", - "find_them_fast": "Find them fast by name with search", "first": "First", "fix_incorrect_match": "Fix incorrect match", "focal_length": "Focal Length", @@ -1301,7 +1149,6 @@ "group_owner": "Group by owner", "group_places_by": "Group places by...", "group_year": "Group by year", - "haptic_feedback_switch": "Enable haptic feedback", "haptic_feedback_title": "Haptic Feedback", "has_quota": "Has quota", "hash_asset": "Hash asset", @@ -1322,29 +1169,12 @@ "hide_schema": "Hide schema", "hide_text_recognition": "Hide text recognition", "hide_unnamed_people": "Hide unnamed people", - "home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.", - "home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping", - "home_page_add_to_album_success": "Added {added} assets to album {album}.", - "home_page_album_err_partner": "Can not add partner assets to an album yet, skipping", - "home_page_archive_err_local": "Can not archive local assets yet, skipping", - "home_page_archive_err_partner": "Can not archive partner assets, skipping", "home_page_building_timeline": "Building the timeline", - "home_page_delete_err_partner": "Can not delete partner assets, skipping", - "home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping", - "home_page_favorite_err_local": "Can not favorite local assets yet, skipping", - "home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping", - "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album so that the timeline can populate photos and videos in it", - "home_page_locked_error_local": "Can not move local assets to locked folder, skipping", - "home_page_locked_error_partner": "Can not move partner assets to locked folder, skipping", - "home_page_share_err_local": "Can not share local assets via link, skipping", - "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", "host": "Host", "hour": "Hour", "hours": "Hours", "id": "ID", "idle": "Idle", - "ignore_icloud_photos": "Ignore iCloud photos", - "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", "image": "Image", "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} taken on {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} taken with {person1} on {date}", @@ -1356,10 +1186,6 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1} and {person2} on {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {person3} on {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {additionalCount, number} others on {date}", - "image_saved_successfully": "Image saved", - "image_viewer_page_state_provider_download_started": "Download Started", - "image_viewer_page_state_provider_download_success": "Download Success", - "image_viewer_page_state_provider_share_error": "Share Error", "immich_logo": "Immich Logo", "immich_web_interface": "Immich Web Interface", "import_from_json": "Import from JSON", @@ -1385,17 +1211,9 @@ "invalid_date_format": "Invalid date format", "invite_people": "Invite People", "invite_to_album": "Invite to album", - "ios_debug_info_fetch_ran_at": "Fetch ran {dateTime}", - "ios_debug_info_last_sync_at": "Last sync {dateTime}", - "ios_debug_info_no_processes_queued": "No background processes queued", - "ios_debug_info_no_sync_yet": "No background sync job has run yet", - "ios_debug_info_processes_queued": "{count, plural, one {{count} background process queued} other {{count} background processes queued}}", - "ios_debug_info_processing_ran_at": "Processing ran {dateTime}", "iso": "ISO", "items_count": "{count, plural, one {# item} other {# items}}", "jobs": "Jobs", - "json_editor": "JSON editor", - "json_error": "JSON error", "keep": "Keep", "keep_albums": "Keep albums", "keep_albums_count": "Keeping {count} {count, plural, one {album} other {albums}}", @@ -1428,9 +1246,6 @@ "library": "Library", "library_add_folder": "Add folder", "library_edit_folder": "Edit folder", - "library_options": "Library options", - "library_page_device_albums": "Albums on Device", - "library_page_new_album": "New album", "library_page_sort_asset_count": "Number of assets", "library_page_sort_created": "Created date", "library_page_sort_last_modified": "Last modified", @@ -1476,11 +1291,9 @@ "login": "Login", "login_disabled": "Login has been disabled", "login_form_api_exception": "API exception. Please check the server URL and try again.", - "login_form_back_button_text": "Back", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "Server Endpoint URL", - "login_form_err_http": "Please specify http:// or https://", "login_form_err_invalid_email": "Invalid Email", "login_form_err_invalid_url": "Invalid URL", "login_form_err_leading_whitespace": "Leading whitespace", @@ -1490,10 +1303,9 @@ "login_form_failed_login": "Error logging you in, check server URL, email and password", "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", "login_form_password_hint": "password", - "login_form_save_login": "Stay logged in", - "login_form_server_empty": "Enter a server URL.", - "login_form_server_error": "Could not connect to server.", - "login_has_been_disabled": "Login has been disabled.", + "login_form_server_empty": "Enter a server URL", + "login_form_server_error": "Could not connect to server", + "login_has_been_disabled": "Login has been disabled", "login_password_changed_error": "There was an error updating your password", "login_password_changed_success": "Password updated successfully", "logout_all_device_confirmation": "Are you sure you want to log out all devices?", @@ -1508,7 +1320,6 @@ "maintenance_action_restore": "Restoring Database", "maintenance_description": "Immich has been put into maintenance mode.", "maintenance_end": "End maintenance mode", - "maintenance_end_error": "Failed to end maintenance mode.", "maintenance_logged_in_as": "Currently logged in as {user}", "maintenance_restore_from_backup": "Restore From Backup", "maintenance_restore_library": "Restore Your Library", @@ -1534,7 +1345,6 @@ "manage_media_access_settings": "Open settings", "manage_media_access_subtitle": "Allow the Immich app to manage and move media files.", "manage_media_access_title": "Media Management Access", - "manage_shared_links": "Manage shared links", "manage_sharing_with_partners": "Manage sharing with partners", "manage_the_app_settings": "Manage the app settings", "manage_your_account": "Manage your account", @@ -1542,13 +1352,11 @@ "manage_your_devices": "Manage your logged-in devices", "manage_your_oauth_connection": "Manage your OAuth connection", "map": "Map", - "map_assets_in_bounds": "{count, plural, =0 {No photos in this area} one {# photo} other {# photos}}", "map_cannot_get_user_location": "Cannot get user's location", - "map_location_dialog_yes": "Yes", "map_location_picker_page_use_location": "Use this location", "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", "map_location_service_disabled_title": "Location Service disabled", - "map_marker_for_images": "Map marker for images taken in {city}, {country}", + "map_marker_for_image": "Map marker for image taken in {city}, {country}", "map_marker_with_image": "Map marker with image", "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", "map_no_location_permission_title": "Location Permission denied", @@ -1558,14 +1366,11 @@ "map_settings_date_range_option_days": "Past {days} days", "map_settings_date_range_option_year": "Past year", "map_settings_date_range_option_years": "Past {years} years", - "map_settings_dialog_title": "Map Settings", "map_settings_include_show_archived": "Include Archived", "map_settings_include_show_partners": "Include Partners", "map_settings_only_show_favorites": "Show Favorite Only", "map_settings_theme_settings": "Map Theme", - "map_zoom_to_see_photos": "Zoom out to see photos", "mark_all_as_read": "Mark all as read", - "mark_as_read": "Mark as read", "marked_all_as_read": "Marked all as read", "matches": "Matches", "matching_assets": "Matching Assets", @@ -1622,8 +1427,6 @@ "minimize": "Minimize", "minute": "Minute", "minutes": "Minutes", - "mirror_horizontal": "Horizontal", - "mirror_vertical": "Vertical", "missing": "Missing", "mobile_app": "Mobile App", "mobile_app_download_onboarding_note": "Download the companion mobile app using the following options", @@ -1633,19 +1436,13 @@ "more": "More", "motion": "Motion", "move": "Move", - "move_down": "Move down", "move_off_locked_folder": "Move out of locked folder", "move_to": "Move to", "move_to_device_trash": "Move to device trash", "move_to_lock_folder_action_prompt": "{count} added to the locked folder", "move_to_locked_folder": "Move to locked folder", "move_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the locked folder", - "move_up": "Move up", - "moved_to_archive": "Moved {count, plural, one {# asset} other {# assets}} to archive", - "moved_to_library": "Moved {count, plural, one {# asset} other {# assets}} to library", "moved_to_trash": "Moved to trash", - "multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping", - "multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping", "mute_memories": "Mute Memories", "my_albums": "My albums", "my_immich_description": "Copy current page as a My Immich link", @@ -1664,12 +1461,10 @@ "never": "Never", "new_album": "New Album", "new_api_key": "New API Key", - "new_date_range": "New date range", "new_password": "New password", "new_person": "New person", "new_pin_code": "New PIN code", "new_pin_code_subtitle": "This is your first time accessing the locked folder. Create a PIN code to securely access this page", - "new_timeline": "New Timeline", "new_update": "New update", "new_user_created": "New user created", "new_version_available": "NEW VERSION AVAILABLE", @@ -1687,7 +1482,6 @@ "no_cast_devices_found": "No cast devices found", "no_checksum_local": "No checksum available - cannot fetch local assets", "no_checksum_remote": "No checksum available - cannot fetch remote asset", - "no_configuration_needed": "No configuration needed", "no_devices": "No authorized devices", "no_duplicates_found": "No duplicates were found.", "no_exif_info_available": "No exif info available", @@ -1695,7 +1489,6 @@ "no_favorites_message": "Add favorites to quickly find your best pictures and videos", "no_libraries_message": "Create an external library to view your photos and videos", "no_local_assets_found": "No local assets found with this checksum", - "no_location_set": "No location set", "no_locked_photos_message": "Photos and videos in the locked folder are hidden and won't show up as you browse or search your library.", "no_name": "No Name", "no_notifications": "No notifications", @@ -1706,7 +1499,6 @@ "no_results_description": "Try a synonym or more general keyword", "no_shared_albums_message": "Create an album to share photos and videos with people in your network", "no_steps": "No steps added yet", - "no_uploads_in_progress": "No uploads in progress", "none": "None", "not_allowed": "Not allowed", "not_available": "N/A", @@ -1715,6 +1507,9 @@ "notes": "Notes", "nothing_here_yet": "Nothing here yet", "notification_backup_reliability": "Enable notifications to improve background backup reliability", + "notification_enabled_list_tile_content": "Immich uses notifications for background backup. Manage them in your device settings.", + "notification_enabled_list_tile_open_button": "Open settings", + "notification_enabled_list_tile_title": "Notifications enabled", "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.", "notification_permission_list_tile_content": "Grant permission to enable notifications.", "notification_permission_list_tile_enable_button": "Enable Notifications", @@ -1756,7 +1551,6 @@ "original": "original", "other": "Other", "other_devices": "Other devices", - "other_entities": "Other entities", "other_variables": "Other variables", "owned": "Owned", "owner": "Owner", @@ -1766,12 +1560,9 @@ "partner_can_access_assets": "All your photos and videos except those in Archived and Deleted", "partner_can_access_location": "The location where your photos were taken", "partner_list_user_photos": "{user}'s photos", - "partner_list_view_all": "View all", "partner_page_empty_message": "Your photos are not yet shared with any partner.", "partner_page_no_more_users": "No more users to add", - "partner_page_partner_add_failed": "Failed to add partner", "partner_page_select_partner": "Select partner", - "partner_page_shared_to_title": "Shared to", "partner_page_stop_sharing_content": "{partner} will no longer be able to access your photos.", "partner_sharing": "Partner Sharing", "partners": "Partners", @@ -1793,8 +1584,6 @@ "people": "People", "people_edits_count": "Edited {count, plural, one {# person} other {# people}}", "people_feature_description": "Browsing photos and videos grouped by people", - "people_selected": "{count, plural, one {# person selected} other {# people selected}}", - "people_sidebar_description": "Display a link to People in the sidebar", "permanent_deletion_warning": "Permanent deletion warning", "permanent_deletion_warning_setting_description": "Show a warning when permanently deleting assets", "permanently_delete": "Permanently delete", @@ -1804,14 +1593,6 @@ "permanently_deleted_assets_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}", "permission": "Permission", "permission_empty": "Your permission shouldn't be empty", - "permission_onboarding_back": "Back", - "permission_onboarding_continue_anyway": "Continue anyway", - "permission_onboarding_get_started": "Get started", - "permission_onboarding_go_to_settings": "Go to settings", - "permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.", - "permission_onboarding_permission_granted": "Permission granted! You are all set.", - "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.", - "permission_onboarding_request": "Immich requires permission to view your photos and videos.", "person": "Person", "person_age_months": "{months, plural, one {# month} other {# months}} old", "person_age_year_months": "1 year, {months, plural, one {# month} other {# months}} old", @@ -1819,13 +1600,10 @@ "person_birthdate": "Born on {date}", "person_hidden": "{name}{hidden, select, true { (hidden)} other {}}", "person_recognized": "Person recognized", - "person_selected": "Person selected", "photo_shared_all_users": "Looks like you shared your photos with all users or you don't have any user to share with.", "photos": "Photos", "photos_and_videos": "Photos & Videos", - "photos_count": "{count, plural, one {{count, number} Photo} other {{count, number} Photos}}", "photos_from_previous_years": "Photos from previous years", - "photos_only": "Photos only", "pick_a_location": "Pick a location", "pick_custom_range": "Custom range", "pick_date_range": "Select a date range", @@ -1862,7 +1640,6 @@ "privacy": "Privacy", "profile": "Profile", "profile_drawer_app_logs": "Logs", - "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.", "profile_image_of_user": "Profile image of {user}", @@ -1886,7 +1663,6 @@ "purchase_individual_description_2": "Supporter status", "purchase_individual_title": "Individual", "purchase_input_suggestion": "Have a product key? Enter the key below", - "purchase_license_subtitle": "Buy Immich to support the continued development of the service", "purchase_lifetime_description": "Lifetime purchase", "purchase_option_title": "PURCHASE OPTIONS", "purchase_panel_info_1": "Building Immich takes a lot of time and effort, and we have full-time engineers working on it to make it as good as we possibly can. Our mission is for open-source software and ethical business practices to become a sustainable income source for developers and to create a privacy-respecting ecosystem with real alternatives to exploitative cloud services.", @@ -1919,19 +1695,16 @@ "reassigned_assets_to_new_person": "Re-assigned {count, plural, one {# asset} other {# assets}} to a new person", "reassing_hint": "Assign selected assets to an existing person", "recent": "Recent", - "recent_albums": "Recent albums", "recent_searches": "Recent searches", "recently_added": "Recently added", "recently_added_page_title": "Recently Added", "recently_taken": "Recently taken", - "recently_taken_page_title": "Recently Taken", "refresh": "Refresh", "refresh_encoded_videos": "Refresh encoded videos", "refresh_faces": "Refresh faces", "refresh_metadata": "Refresh metadata", "refresh_thumbnails": "Refresh thumbnails", "refreshed": "Refreshed", - "refreshes_every_file": "Re-reads all existing and new files", "refreshing_encoded_video": "Refreshing encoded video", "refreshing_faces": "Refreshing faces", "refreshing_metadata": "Refreshing metadata", @@ -1944,7 +1717,6 @@ "remove_assets_shared_link_confirmation": "Are you sure you want to remove {count, plural, one {# asset} other {# assets}} from this shared link?", "remove_assets_title": "Remove assets?", "remove_custom_date_range": "Remove custom date range", - "remove_deleted_assets": "Remove Deleted Assets", "remove_filter": "Remove filter", "remove_from_album": "Remove from album", "remove_from_album_action_prompt": "{count} removed from the album", @@ -1956,29 +1728,22 @@ "remove_memory": "Remove memory", "remove_photo_from_memory": "Remove photo from this memory", "remove_tag": "Remove tag", - "remove_url": "Remove URL", "remove_user": "Remove user", "removed_api_key": "Removed API Key: {name}", "removed_from_archive": "Removed from archive", "removed_from_favorites": "Removed from favorites", "removed_from_favorites_count": "{count, plural, other {Removed #}} from favorites", "removed_memory": "Removed memory", - "removed_photo_from_memory": "Removed photo from memory", "removed_tagged_assets": "Removed tag from {count, plural, one {# asset} other {# assets}}", "rename": "Rename", - "repair": "Repair", - "repair_no_results_message": "Untracked and missing files will show up here", - "replace_with_upload": "Replace with upload", "repository": "Repository", "require_password": "Require password", - "require_user_to_change_password_on_first_login": "Require user to change password on first login", "rescan": "Rescan", "reset": "Reset", "reset_password": "Reset password", "reset_people_visibility": "Reset people visibility", "reset_pin_code": "Reset PIN code", "reset_pin_code_description": "If you forgot your PIN code, you can contact the server administrator to reset it", - "reset_pin_code_success": "Successfully reset PIN code", "reset_pin_code_with_password": "You can always reset your PIN code with your password", "reset_sqlite": "Reset SQLite Database", "reset_sqlite_clear_app_data": "Clear Data", @@ -1992,12 +1757,10 @@ "resolved_all_duplicates": "Resolved all duplicates", "restore": "Restore", "restore_all": "Restore all", - "restore_trash_action_prompt": "{count} restored from trash", "restore_user": "Restore user", "restored_asset": "Restored asset", "resume": "Resume", "resume_paused_jobs": "Resume {count, plural, one {# paused job} other {# paused jobs}}", - "retry_upload": "Retry upload", "review_duplicates": "Review duplicates", "review_large_files": "Review large files", "role": "Role", @@ -2005,7 +1768,6 @@ "role_viewer": "Viewer", "running": "Running", "save": "Save", - "save_to_gallery": "Save to gallery", "saved": "Saved", "saved_api_key": "Saved API Key", "saved_profile": "Saved profile", @@ -2016,7 +1778,6 @@ "scan": "Scan", "scan_all_libraries": "Scan All Libraries", "scan_library": "Scan", - "scan_settings": "Scan Settings", "scanning": "Scanning", "scanning_for_album": "Scanning for album...", "screencast_mode_description": "Show keyboard and mouse event indicators on the screen", @@ -2049,7 +1810,6 @@ "search_filter_location_title": "Select location", "search_filter_media_type": "Media Type", "search_filter_media_type_title": "Select media type", - "search_filter_ocr": "Search by OCR", "search_filter_people_title": "Select people", "search_filter_star_rating": "Star Rating", "search_filter_tags_title": "Select tags", @@ -2060,25 +1820,13 @@ "search_no_people_named": "No people named \"{name}\"", "search_no_result": "No results found, try a different search term or combination", "search_options": "Search options", - "search_page_categories": "Categories", - "search_page_motion_photos": "Motion Photos", - "search_page_no_objects": "No Objects Info Available", - "search_page_no_places": "No Places Info Available", - "search_page_screenshots": "Screenshots", "search_page_search_photos_videos": "Search for your photos and videos", - "search_page_selfies": "Selfies", - "search_page_things": "Things", "search_page_view_all_button": "View all", - "search_page_your_activity": "Your activity", - "search_page_your_map": "Your Map", "search_people": "Search people", "search_places": "Search places", "search_rating": "Search by rating...", - "search_result_page_new_search_hint": "New Search", "search_settings": "Search settings", "search_state": "Search state...", - "search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ", - "search_suggestion_list_smart_search_hint_2": "m:your-search-term", "search_tags": "Search tags...", "search_timezone": "Search timezone...", "search_type": "Search type", @@ -2092,7 +1840,6 @@ "select_albums": "Select albums", "select_all": "Select all", "select_all_duplicates": "Select all duplicates", - "select_all_in": "Select all in {group}", "select_avatar_color": "Select avatar color", "select_count": "{count, plural, one {Select #} other {Select #}}", "select_cutoff_date": "Select cutoff date", @@ -2100,7 +1847,6 @@ "select_featured_photo": "Select featured photo", "select_from_computer": "Select from computer", "select_keep_all": "Select keep all", - "select_library_owner": "Select library owner", "select_new_face": "Select new face", "select_people": "Select people", "select_person": "Select person", @@ -2108,7 +1854,6 @@ "select_photos": "Select photos", "select_quality": "Select quality", "select_trash_all": "Select trash all", - "select_user_for_sharing_page_err_album": "Failed to create album", "selected": "Selected", "selected_count": "{count, plural, other {# selected}}", "selected_gps_coordinates": "Selected GPS Coordinates", @@ -2141,33 +1886,20 @@ "setting_image_viewer_original_title": "Load original image", "setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.", "setting_image_viewer_preview_title": "Load preview image", - "setting_image_viewer_title": "Images", "setting_languages_apply": "Apply", "setting_languages_subtitle": "Change the app's language", - "setting_notifications_notify_failures_grace_period": "Notify background backup failures: {duration}", - "setting_notifications_notify_hours": "{count} hours", - "setting_notifications_notify_immediately": "immediately", "setting_notifications_notify_minutes": "{count} minutes", - "setting_notifications_notify_never": "never", "setting_notifications_notify_seconds": "{count} seconds", - "setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset", - "setting_notifications_single_progress_title": "Show background backup detail progress", "setting_notifications_subtitle": "Adjust your notification preferences", - "setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)", - "setting_notifications_total_progress_title": "Show background backup total progress", "setting_video_viewer_auto_play_subtitle": "Automatically start playing videos when they are opened", "setting_video_viewer_auto_play_title": "Auto play videos", "setting_video_viewer_looping_title": "Looping", "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", "setting_video_viewer_original_video_title": "Force original video", "settings": "Settings", - "settings_require_restart": "Please restart Immich to apply this setting", "settings_saved": "Settings saved", "setup_pin_code": "Setup a PIN code", "share": "Share", - "share_action_prompt": "Shared {count} assets", - "share_add_photos": "Add photos", - "share_assets_selected": "{count} selected", "share_dialog_preparing": "Preparing...", "share_link": "Share Link", "share_original": "Use original (large)", @@ -2177,8 +1909,6 @@ "shared_album_activity_remove_content": "Do you want to delete this activity?", "shared_album_activity_remove_title": "Delete Activity", "shared_album_section_people_action_error": "Error leaving/removing from album", - "shared_album_section_people_action_leave": "Remove user from album", - "shared_album_section_people_action_remove_user": "Remove user from album", "shared_album_section_people_title": "PEOPLE", "shared_by": "Shared by", "shared_by_user": "Shared by {user}", @@ -2187,7 +1917,6 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Uploaded", "shared_link_app_bar_title": "Shared Links", "shared_link_clipboard_copied_massage": "Copied to clipboard", - "shared_link_clipboard_text": "Link: {link}\nPassword: {password}", "shared_link_create_error": "Error while creating shared link", "shared_link_custom_url_description": "Access this shared link with a custom URL", "shared_link_edit_description_hint": "Enter the share description", @@ -2223,12 +1952,6 @@ "shared_with_partner": "Shared with {partner}", "sharing": "Sharing", "sharing_enter_password": "Please enter the password to view this page.", - "sharing_page_album": "Shared albums", - "sharing_page_description": "Create shared albums to share photos and videos with people in your network.", - "sharing_page_empty_list": "EMPTY LIST", - "sharing_sidebar_description": "Display a link to Sharing in the sidebar", - "sharing_silver_appbar_create_shared_album": "New shared album", - "sharing_silver_appbar_share_partner": "Share with partner", "shift_to_permanent_delete": "press ⇧ to permanently delete asset", "show_album_options": "Show album options", "show_albums": "Show albums", @@ -2278,7 +2001,6 @@ "sort_created": "Date created", "sort_items": "Number of items", "sort_modified": "Date modified", - "sort_newest": "Newest photo", "sort_oldest": "Oldest photo", "sort_people_by_similarity": "Sort people by similarity", "sort_recent": "Most recent photo", @@ -2287,7 +2009,6 @@ "stack": "Stack", "stack_action_prompt": "{count} stacked", "stack_duplicates": "Stack duplicates", - "stack_select_one_photo": "Select one main photo for the stack", "stack_selected_photos": "Stack selected photos", "stacked_assets_count": "Stacked {count, plural, one {# asset} other {# assets}}", "stacktrace": "Stacktrace", @@ -2300,7 +2021,6 @@ "step_delete_confirm": "Are you sure you want to delete this step?", "step_details": "Step details", "steps": "Steps", - "steps_count": "{count, plural, one {# step} other {# steps}}", "stop_casting": "Stop casting", "stop_motion_photo": "Stop Motion Photo", "stop_photo_sharing": "Stop sharing your photos?", @@ -2321,7 +2041,6 @@ "swap_merge_direction": "Swap merge direction", "sync": "Sync", "sync_albums": "Sync albums", - "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", "sync_local": "Sync Local", "sync_remote": "Sync Remote", "sync_status": "Sync Status", @@ -2334,7 +2053,6 @@ "tag_created": "Created tag: {tag}", "tag_face": "Tag face", "tag_feature_description": "Browsing photos and videos grouped by logical tag topics", - "tag_not_found_question": "Cannot find a tag? Create a new tag.", "tag_people": "Tag People", "tag_updated": "Updated tag: {tag}", "tagged_assets": "Tagged {count, plural, one {# asset} other {# assets}}", @@ -2349,15 +2067,10 @@ "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({count})", "theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.", "theme_setting_colorful_interface_title": "Colorful interface", - "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer", - "theme_setting_image_viewer_quality_title": "Image viewer quality", "theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.", "theme_setting_primary_color_title": "Primary color", "theme_setting_system_primary_color_title": "Use system color", "theme_setting_system_theme_switch": "Automatic (Follow system setting)", - "theme_setting_theme_subtitle": "Choose the app's theme setting", - "theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load", - "theme_setting_three_stage_loading_title": "Enable three-stage loading", "then": "Then", "they_will_be_merged_together": "They will be merged together", "third_party_resources": "Third-Party Resources", @@ -2383,25 +2096,17 @@ "trash_all": "Trash All", "trash_count": "Trash {count, number}", "trash_delete_asset": "Trash/Delete Asset", - "trash_emptied": "Emptied trash", "trash_no_results_message": "Trashed photos and videos will show up here.", "trash_page_delete_all": "Delete All", - "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", "trash_page_info": "Trashed items will be permanently deleted after {days} days", - "trash_page_no_assets": "No trashed assets", - "trash_page_restore_all": "Restore All", - "trash_page_select_assets_btn": "Select assets", - "trash_page_title": "Trash ({count})", "trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.", "trigger": "Trigger", "trigger_asset_metadata_extraction": "Asset Metadata Extraction", "trigger_asset_metadata_extraction_description": "Triggered when the EXIF metadata of an asset is extracted", "trigger_asset_uploaded": "Asset Upload", "trigger_asset_uploaded_description": "Triggered when a new asset is uploaded", - "trigger_description": "An event that kicks off the workflow", "trigger_person_recognized": "Person Recognized", "trigger_person_recognized_description": "Triggered when a person is recognized", - "trigger_type": "Trigger type", "troubleshoot": "Troubleshoot", "type": "Type", "unable_to_change_pin_code": "Unable to change PIN code", @@ -2429,7 +2134,6 @@ "unsaved_change": "Unsaved change", "unselect_all": "Unselect all", "unselect_all_duplicates": "Unselect all duplicates", - "unselect_all_in": "Unselect all in {group}", "unstack": "Un-stack", "unstack_action_prompt": "{count} unstacked", "unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}", @@ -2444,8 +2148,6 @@ "upload_concurrency": "Upload concurrency", "upload_day_count": "{date}: {count, plural, one {# upload} other {# uploads}}", "upload_details": "Upload Details", - "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", - "upload_dialog_title": "Upload Asset", "upload_error_with_count": "Upload error for {count, plural, one {# asset} other {# assets}}", "upload_errors": "Upload completed with {count, plural, one {# error} other {# errors}}, refresh the page to see new upload assets.", "upload_finished": "Upload finished", @@ -2477,7 +2179,6 @@ "user_privacy": "User Privacy", "user_purchase_settings": "Purchase", "user_purchase_settings_description": "Manage your purchase", - "user_role_set": "Set {user} as {role}", "user_usage_detail": "User usage detail", "user_usage_stats": "Account usage statistics", "user_usage_stats_description": "View account usage statistics", @@ -2487,7 +2188,6 @@ "utilities": "Utilities", "validate": "Validate", "validate_endpoint_error": "Please enter a valid URL", - "validation_error": "Validation error", "variables": "Variables", "version": "Version", "version_announcement_closing": "Your friend, Alex", @@ -2500,7 +2200,6 @@ "video_quality": "Video quality", "videos": "Videos", "videos_count": "{count, plural, one {# Video} other {# Videos}}", - "videos_only": "Videos only", "view": "View", "view_album": "View Album", "view_all": "View All", @@ -2509,21 +2208,16 @@ "view_details": "View Details", "view_in_timeline": "View in timeline", "view_link": "View link", - "view_links": "View links", "view_name": "View", "view_next_asset": "View next asset", "view_previous_asset": "View previous asset", "view_qr_code": "View QR code", "view_similar_photos": "View similar photos", "view_stack": "View Stack", - "view_user": "View User", "viewer_remove_from_stack": "Remove from Stack", - "viewer_stack_use_as_main_asset": "Use as Main Asset", - "viewer_unstack": "Un-Stack", "visibility": "Visibility", "visibility_changed": "Visibility changed for {count, plural, one {# person} other {# people}}", "visual": "Visual", - "visual_builder": "Visual builder", "waiting": "Waiting", "waiting_count": "Waiting: {count}", "warning": "Warning", @@ -2545,7 +2239,6 @@ "workflow_summary": "Workflow summary", "workflow_templates": "Workflow templates", "workflow_update_success": "Workflow updated successfully", - "workflow_updated": "Workflow updated", "workflows": "Workflows", "workflows_help_text": "Workflows automate actions on your assets based on triggers and filters", "wrong_pin_code": "Wrong PIN code", @@ -2556,6 +2249,5 @@ "you_dont_have_any_shared_links": "You don't have any shared links", "your_wifi_name": "Your Wi-Fi name", "zero_to_clear_rating": "press 0 to clear asset rating", - "zoom_image": "Zoom Image", - "zoom_to_bounds": "Zoom to bounds" + "zoom_image": "Zoom Image" } diff --git a/i18n/en_GB.json b/i18n/en_GB.json index 5129a334ef..899db6395a 100644 --- a/i18n/en_GB.json +++ b/i18n/en_GB.json @@ -1548,7 +1548,7 @@ "map_location_picker_page_use_location": "Use this location", "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", "map_location_service_disabled_title": "Location Service disabled", - "map_marker_for_images": "Map marker for images taken in {city}, {country}", + "map_marker_for_image": "Map marker for image taken in {city}, {country}", "map_marker_with_image": "Map marker with image", "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", "map_no_location_permission_title": "Location Permission denied", diff --git a/i18n/eo.json b/i18n/eo.json index ce2ce71d77..5cb8ee952e 100644 --- a/i18n/eo.json +++ b/i18n/eo.json @@ -79,6 +79,7 @@ "cron_expression_description": "Agordu la intervalon de analizado pere de la formato de cron. Por pli da informoj, legu ekzemple Crontab Guru", "cron_expression_presets": "Antaŭagordoj pri la cron-esprimo", "disable_login": "Malebligi ensalutadon", + "download_csv": "Elŝuti .csv-dosieron", "duplicate_detection_job_description": "Komenci permaŝin-lernadon por trovi similajn bildojn. Uzas 'inteligentan serĉadon'", "exclusion_pattern_description": "Per skemo de ekskludo, vi povas ignori dosierojn kaj dosierujojn dum analizado de la biblioteko. Tio estas utila se vi havas ekz. RAW-dosierojn, kiujn vi ne volas importi.", "export_config_as_json_description": "Elŝuti la aktualan sistem-agordaĵaron kiel JSON-dosieron", @@ -188,9 +189,25 @@ "machine_learning_smart_search_enabled": "Ŝalti inteligentan serĉadon", "machine_learning_smart_search_enabled_description": "Se malŝaltita, tiam bildoj ne estos kodigitaj por inteligenta serĉado.", "machine_learning_url_description": "La URL-o de la maŝin-lerna servilo. Se vi donas pli ol unu URL-o, la sistemo provos ĉiun servilon unu post la alia ĝis kiam unu sukcese respondas, de la unua ĝis la lasta. Serviloj, kiuj ne respondas, estos dumtempe ignoritaj.", + "maintenance_backup_management": "Administrado de savkopioj", "maintenance_delete_backup": "Forigi savkopion", "maintenance_delete_backup_description": "La dosiero estos por ĉiam forigita.", "maintenance_delete_error": "Malsukcesis forigi savkopion.", + "maintenance_integrity_check": "Kontroli", + "maintenance_integrity_check_all": "Kontroli ĉiujn", + "maintenance_integrity_checksum_mismatch": "Nekongruaj kontrolsumoj", + "maintenance_integrity_checksum_mismatch_description": "Dosieroj pri kiuj la kontrolsumo stokita sur disko ne kongruas kun tiu en la datumbazo de Immich.", + "maintenance_integrity_checksum_mismatch_job": "Kontroli pri nekongruaj kontrolsumoj", + "maintenance_integrity_checksum_mismatch_refresh_job": "Refreŝigi la raporton pri nekongruaj kontrolsumoj", + "maintenance_integrity_missing_file": "Dosieroj mankantaj", + "maintenance_integrity_missing_file_description": "Dosieroj menciitaj en la datumbazo de Immich, sed kiuj ne (plu) ekzistas en la dosiersistemo.", + "maintenance_integrity_missing_file_job": "Detekti mankantajn dosierojn", + "maintenance_integrity_missing_file_refresh_job": "Refreŝigi raporton pri mankantaj dosieroj", + "maintenance_integrity_report": "Raporto pri integreco", + "maintenance_integrity_untracked_file": "Senspuraj dosieroj", + "maintenance_integrity_untracked_file_description": "Dosieroj en la dosierujoj de Immich, sed pri kiuj Immich havas neniun spuron.", + "maintenance_integrity_untracked_file_job": "Detekti senspurajn dosierojn", + "maintenance_integrity_untracked_file_refresh_job": "Refreŝigi raporton pri senspuraj dosieroj", "maintenance_restore_backup": "Restaŭri savkopion", "maintenance_restore_backup_description": "Immich estos forigita kaj reinstalita de la elektita savkopio. Nova savkopio estos kreita antaŭe.", "maintenance_restore_backup_different_version": "Tiu ĉi savkopio estis kreita per alia versio de Immich!", @@ -732,7 +749,7 @@ "cache_settings_title": "Agordoj pri kaŝmemoro", "camera": "Fotilo", "camera_brand": "Fabrikanto de fotilo", - "camera_model": "Modelo de fotilo", + "camera_model": "Tipo de fotilo", "cancel": "Nuligi", "cancel_search": "Nuligi serĉon", "canceled": "Nuligita", @@ -1446,6 +1463,7 @@ "log_out_all_devices": "Elsalutigi ĉiujn aparatojn", "logged_in_as": "Ensalutita kiel {user}", "logged_out_all_devices": "Ĉiuj aparatoj elsalutigitaj", + "logged_out_device": "Elsalutigita aparato", "login": "Ensaluti", "login_disabled": "Ensalutado malebligita", "login_form_api_exception": "Eraro de API. Bonvolu kontroli la URL-on de la servilo, kaj reprovi.", @@ -1521,7 +1539,6 @@ "map_location_picker_page_use_location": "Uzi tiun ĉi lokon", "map_location_service_disabled_content": "Vi devas ŝalti la lokadan servon de via aparato por vidi elementojn de via aktuala loko. Ĉu vi volas nun ŝalti tion?", "map_location_service_disabled_title": "Servo de lokado malŝaltita", - "map_marker_for_images": "Map-markilo por fotoj faritaj en {city}, {country}", "map_marker_with_image": "Map-markilo kun bildo", "map_no_location_permission_content": "Vi devas permesi al la apo detekti vian aktualan lokon por vidi tieajn elementojn. Ĉu vi volas nun permesi tion?", "map_no_location_permission_title": "Detektado de loko ne permesita", @@ -1630,6 +1647,9 @@ "navigate_to_time": "Navigi al dato/horo", "network_requirement_photos_upload": "Uzi datumojn de poŝtelefona reto por savkopii fotojn", "network_requirement_videos_upload": "Uzi datumojn de poŝtelefona reto por savkopii videojn", + "network_requirements": "Retaj postuloj", + "network_requirements_updated": "Retaj postuloj ŝanĝiĝis, savkopia vico restarigita", + "networking_settings": "Retkonektoj", "networking_subtitle": "Administri agordojn pri finpunktoj de la servilo", "never": "Neniam", "new_album": "Nova albumo", @@ -1664,14 +1684,14 @@ "no_explore_results_message": "Alŝutu pli da fotoj por esplori vian kolekton.", "no_favorites_message": "Aldoni al Preferataĵoj por rapide retrovi viajn plej bonajn bildojn kaj videojn", "no_libraries_message": "Krei eksteran biblitekon por vidi viajn fotojn kaj videojn", - "no_local_assets_found": "Neniuj lokaj elementoj trovitaj kun tiu kontrolsumo", + "no_local_assets_found": "Neniu loka elemento trovita kun tiu kontrolsumo", "no_location_set": "Neniu loko agordita", "no_locked_photos_message": "Fotoj kaj videoj en la ŝlosita dosierujo estas kaŝitaj, kaj ne montriĝos dum vi foliumas aŭ serĉas en via biblioteko.", "no_name": "Neniu nomo", "no_notifications": "Neniuj sciigoj", "no_people_found": "Neniuj kongruaj homoj trovitaj", "no_places": "Neniuj lokoj", - "no_remote_assets_found": "Neniuj foraj elementoj trovitaj kun tiu kontrolsumo", + "no_remote_assets_found": "Neniu fora elemento trovita kun tiu kontrolsumo", "no_results": "Neniuj rezultoj", "no_results_description": "Provu sinonimon aŭ pli ĝeneralan ŝlosilvorton", "no_shared_albums_message": "Krei albumon por dividi fotojn kaj videojn kun konatoj", @@ -1880,6 +1900,13 @@ "readonly_mode_disabled": "Nurlega reĝimo malŝaltita", "readonly_mode_enabled": "Nurlega reĝimo ŝaltita", "ready_for_upload": "Preta por alŝuto", + "reassign": "Reatribui", + "reassigned_assets_to_existing_person": "Reatribuis {count, plural, one {# elementon} other {# elementojn}} al {name, select, null {ekzistanta homo} other {{name}}}", + "reassigned_assets_to_new_person": "Reatribuis {count, plural, one {# elementon} other {# elementojn}} al nova homo", + "reassing_hint": "Reatribuis elektitajn elementojn al ekzistanta homo", + "recent": "Lastatempa(j)", + "recent_albums": "Lastatempaj albumoj", + "recent_searches": "Lastatempaj serĉoj", "recently_added": "Lastatempe aldonita(j)", "recently_added_page_title": "Lastatempe aldonita(j)", "recently_taken": "Lastatempe fotita(j)", @@ -1903,21 +1930,73 @@ "remove_assets_shared_link_confirmation": "Ĉu vi certas, ke vi volas forigi {count, plural, one {# elementon} other {# elementojn}} de tiu dividita ligilo?", "remove_assets_title": "Ĉu forigi elementojn?", "remove_custom_date_range": "Forigi la dat-intervalon", + "remove_filter": "Forigi filtron", + "remove_from_album": "Forpreni de albumo", + "remove_from_album_action_prompt": "{count} forprenitaj de la albumo", "remove_from_favorites": "Forigi el preferataĵoj", + "remove_from_lock_folder_action_prompt": "{count} forprenitaj de la ŝlosita dosierujo", + "remove_from_locked_folder": "Forpreni de la ŝlosita dosierujo", + "remove_from_locked_folder_confirmation": "Ĉu vi certas, ke vi volas forpreni tiujn fotojn/videojn el la ŝlosita dosierujo? Ili poste estos videblaj en via biblioteko.", "remove_from_shared_link": "Forigi el dividita ligilo", + "remove_memory": "Forigi memoraĵon", + "remove_photo_from_memory": "Forpreni foton el tiu memoraĵo", + "remove_tag": "Forigi etikedon", + "remove_url": "Forigi URL-on", + "remove_user": "Forigi uzanton", + "removed_api_key": "Forigita API-ŝlosilo: {name}", + "removed_from_archive": "Forigita de la arĥivo", "removed_from_favorites": "Forigita(j) el preferataĵoj", "removed_from_favorites_count": "{count, plural, other {Forigis #}} el Preferataĵoj", + "removed_memory": "Memoraĵo forigita", + "removed_photo_from_memory": "Forprenis foton de la memoraĵo", + "removed_tagged_assets": "Forigis etikedon de {count, plural, one {# elemento} other {# elementoj}}", + "rename": "Renomi", + "repair": "Ripari", + "repair_no_results_message": "Senspuraj kaj mankantaj dosieroj aperas ĉi tie", + "replace_with_upload": "Anstataŭigi per alŝutaĵo", + "repository": "Deponejo", + "require_password": "Postuli pasvorton", + "require_user_to_change_password_on_first_login": "Devigi al uzanto ŝanĝi pasvorton je unua ensaluto", "rescan": "Reanalizi", "reset": "Restartigi", + "reset_password": "Restarigi pasvorton", + "reset_people_visibility": "Restarigi videblecon de homoj", + "reset_pin_code": "Restarigi PIN-kodon", + "reset_pin_code_description": "Se vi forgesis vian PIN-kodon, vi povas kontakti la administranto de via servilo por restarigi ĝin", + "reset_pin_code_success": "Sukcese restarigis PIN-kodon", + "reset_pin_code_with_password": "Vi povas restarigi vian PIN-kodon pere de via pasvorto", + "reset_sqlite": "Restarigi la SQLite-datumbazon", "reset_sqlite_clear_app_data": "Forviŝi datumojn", "reset_sqlite_confirmation": "Ĉu vi certas, ke vi volas forviŝi la datumojn de la apo? Tio forigos ĉiujn agordojn kaj elsalutigos vin.", "reset_sqlite_confirmation_note": "Noto: vi devos relanĉi la apon por la forviŝo.", "reset_sqlite_done": "Datumoj de la apo estas forviŝitaj. Bonvolu relanĉi Immich kaj ensalutu denove.", + "reset_sqlite_success": "Sukcese restarigis la SQLite-datumbazon", + "reset_to_default": "Restarigi la defaŭltojn", + "resolution": "Distingivo", + "resolve_duplicates": "Solvi duoblaĵojn", + "resolved_all_duplicates": "Solvis ĉiujn duoblaĵojn", "restore": "Restaŭri", "restore_all": "Restaŭri ĉiujn", "restore_trash_action_prompt": "{count} restaŭrita(j) el rubujo", "restore_user": "Restaŭri uzanton", "restored_asset": "Restaŭri elementon", + "resume": "Daŭrigi", + "resume_paused_jobs": "Daŭrigi {count, plural, one {# paŭzitan taskon} other {# paŭzitajn taskojn}}", + "retry_upload": "Reprovi alŝuton", + "review_duplicates": "Kontroli duoblaĵojn", + "review_large_files": "Kontroli grandajn dosierojn", + "role": "Rolo", + "role_editor": "Redaktanto", + "role_viewer": "Spektanto", + "running": "Aktuale plenumata(j)", + "save": "Konservi", + "save_to_gallery": "Konservi en galerio", + "saved": "Konservita(j)", + "saved_api_key": "Konservis API-ŝlosilon", + "saved_profile": "Konservis profilon", + "saved_settings": "Konservis agordojn", + "say_something": "Skribu ion", + "scaffold_body_error_occurred": "Eraro okazis", "scaffold_body_error_unrecoverable": "Neriparebla eraro okazis. Bonvolu sendi al ni la eraron kaj la stakspuron per Discord aŭ per Github por ke ni povu helpi. Vi povas forviŝi la ĉi-subajn datumojn de la apo se vi volas.", "scan": "Analizi", "scan_all_libraries": "Analizi ĉiujn bibliotekojn", @@ -1925,7 +2004,33 @@ "scan_settings": "Agordoj pri analizado", "scanning": "Analizado", "scanning_for_album": "Serĉado de albumo...", + "screencast_mode_description": "Montri indikilojn surekrane pri tuŝoj de klavaro kaj muso", + "screencast_mode_title": "Baskuligi reĝimon de elsendo", + "search": "Serĉi", + "search_albums": "Serĉi albumojn", + "search_by_context": "Serĉi laŭ kunteksto", + "search_by_description": "Serĉi laŭ priskribo", + "search_by_description_example": "Promenado en Poznań", + "search_by_filename": "Serĉi laŭ dosiernomo aŭ sufikso", + "search_by_filename_example": "ekz. IMG_1234.jpg aŭ png", + "search_by_full_path": "Serĉi laŭ dosier-vojo aŭ dosierujo", + "search_by_full_path_example": "/Silvja/Projektoj/3D_Printado/2026-07-01 - vi povas serĉi 'Projektoj', '3D', 'Printado', '2026', ktp.", + "search_by_ocr": "Serĉi per optika signo-rekono", + "search_by_ocr_example": "Invitilo", "search_camera_lens_model": "Serĉi tipon de objektivo...", + "search_camera_make": "Serĉi fabrikanton de fotilo...", + "search_camera_model": "Serĉi tipon de fotilo...", + "search_city": "Serĉi urbon...", + "search_country": "Serĉi landon...", + "search_filter_apply": "Apliki filtrilon", + "search_filter_camera_title": "Elektu tipon de fotilo", + "search_filter_date": "Dato", + "search_filter_date_interval": "de {start} ĝis {end}", + "search_filter_date_title": "Elektu intervalon de datoj", + "search_filter_display_option_not_in_album": "Ne en albumo", + "search_filter_display_options": "Agordoj pri aranĝo sur ekrano", + "search_filter_filename": "Serĉi laŭ dosiernomo", + "search_filter_location": "Loko", "search_suggestion_list_smart_search_hint_1": "Inteligenta serĉado defaŭlte estas ŝaltita. Por serĉi metadatumojn, uzu sintakson tiel ", "select_user_for_sharing_page_err_album": "Malsukcesis krei albumon", "server_privacy": "Privateco de servilo", diff --git a/i18n/es.json b/i18n/es.json index 79d880a0f5..7dd8a91714 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -59,7 +59,7 @@ "backup_onboarding_1_description": "Copia en un lugar externo, en la nube u otra ubicación física.", "backup_onboarding_2_description": "copias locales en diferentes dispositivos. Incluye los archivos principales y una copia de seguridad local de dichos archivos.", "backup_onboarding_3_description": "copias totales de tu data, incluyendo los archivos originales. Incluye 1 copia fuera de sitio y 2 copias locales.", - "backup_onboarding_description": "Se recomienda una estrategia de copia de seguridad 3-2-1 para proteger tus datos. Deberías mantener copias de las fotos y vídeos que subas, así como de la base de datos de Immich, para contar con una solución de copia de seguridad completa.", + "backup_onboarding_description": "Una estrategia de copia de seguridad 3-2-1 es recomendada para proteger tus datos. Deberías mantener copias de las fotos y vídeos que subas, así como de la base de datos de Immich, para contar con una solución de copia de seguridad completa.", "backup_onboarding_footer": "Para obtener más información sobre cómo hacer una copia de seguridad de Immich, consulta la documentación.", "backup_onboarding_parts_title": "Una copia de seguridad 3-2-1 incluye:", "backup_onboarding_title": "Copias de seguridad", @@ -1500,7 +1500,7 @@ "logout_this_device_confirmation": "¿Estás seguro de que quieres cerrar sesión en este dispositivo?", "logs": "Registros", "longitude": "Longitud", - "look": "Mirar", + "look": "Aspecto", "loop_videos": "Vídeos en bucle", "loop_videos_description": "Habilite la reproducción automática de un video en el visor de detalles.", "main_branch_warning": "Está utilizando una versión de desarrollo; ¡le recomendamos encarecidamente que utilice una versión de lanzamiento!", @@ -1548,7 +1548,7 @@ "map_location_picker_page_use_location": "Usar esta ubicación", "map_location_service_disabled_content": "Los servicios de ubicación deben estar activados para mostrar recursos de tu ubicación actual. ¿Deseas activarlos ahora?", "map_location_service_disabled_title": "Servicios de ubicación desactivados", - "map_marker_for_images": "Marcador de mapa para imágenes tomadas en {city}, {country}", + "map_marker_for_image": "Marcador del mapa para la imagen tomada en {city}, {country}", "map_marker_with_image": "Marcador de mapa con imagen", "map_no_location_permission_content": "Se necesitan permisos de ubicación para mostrar recursos de tu ubicación actual. ¿Deseas activarlos ahora?", "map_no_location_permission_title": "Permisos de ubicación denegados", @@ -2075,7 +2075,7 @@ "search_places": "Buscar lugar", "search_rating": "Buscar por calificación...", "search_result_page_new_search_hint": "Nueva Búsqueda", - "search_settings": "Ajustes de búsqueda", + "search_settings": "Buscar ajustes", "search_state": "Buscar región/estado...", "search_suggestion_list_smart_search_hint_1": "La búsqueda inteligente está habilitada por defecto, para buscar metadatos utiliza esta sintaxis ", "search_suggestion_list_smart_search_hint_2": "m:tu-término-de-búsqueda", diff --git a/i18n/et.json b/i18n/et.json index 6f169370f3..10d3874578 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -189,11 +189,17 @@ "machine_learning_smart_search_enabled": "Luba nutiotsing", "machine_learning_smart_search_enabled_description": "Kui keelatud, siis ei kodeerita pilte nutiotsingu jaoks.", "machine_learning_url_description": "Masinõppe serveri URL. Kui ette on antud rohkem kui üks URL, proovitakse neid järjest ükshaaval, kuni üks edukalt vastab. Servereid, mis ei vasta, ignoreeritakse ajutiselt, kuni ühendus taastub.", + "maintenance_backup_management": "Varunduse haldus", "maintenance_delete_backup": "Kustuta varukoopia", "maintenance_delete_backup_description": "See fail kustutatakse jäädavalt.", "maintenance_delete_error": "Varukoopia kustutamine ebaõnnestus.", "maintenance_integrity_check_all": "Märgi kõik", + "maintenance_integrity_checksum_mismatch": "Kontrollsumma ebakõla", + "maintenance_integrity_checksum_mismatch_description": "Failid, mille kontrollsumma ei klapi sellega, mis on Immich'i andmebaasis.", + "maintenance_integrity_checksum_mismatch_job": "Otsi kontrollsumma ebakõlasid", + "maintenance_integrity_checksum_mismatch_refresh_job": "Värskenda kontrollsumma ebakõlade aruanne", "maintenance_integrity_missing_file": "Puuduvad failid", + "maintenance_integrity_missing_file_description": "Failid, mida Immich jälgib andmebaasis, kuid mida ei eksisteeri failisüsteemis.", "maintenance_integrity_missing_file_job": "Otsi puuduvaid faile", "maintenance_integrity_missing_file_refresh_job": "Värskenda puuduvate failide aruanne", "maintenance_integrity_untracked_file": "Mittejälgitavad failid", @@ -923,6 +929,8 @@ "deduplicate_all": "Dedubleeri kõik", "default_locale": "Vaikimisi lokaat", "default_locale_description": "Vorminda kuupäevad ja arvud vastavalt brauseri lokaadile", + "default_quality_subtitle": "Kvaliteet, mida jagamisel kasutada. Hoia jagamise nuppu all, et iga kord valida.", + "default_share_quality": "Vaikimisi jagamise kvaliteet", "delete": "Kustuta", "delete_action_confirmation_message": "Kas oled kindel, et soovid selle üksuse kustutada? See toiming liigutab üksuse serveri prügikasti ja küsib, kas soovid selle lokaalselt kustutada", "delete_action_prompt": "{count} kustutatud", @@ -1535,7 +1543,7 @@ "map_location_picker_page_use_location": "Kasuta seda asukohta", "map_location_service_disabled_content": "Praeguse asukoha üksuste kuvamiseks tuleb lubada asukoha teenus. Kas soovid seda praegu lubada?", "map_location_service_disabled_title": "Asukoha teenus keelatud", - "map_marker_for_images": "Kaardimarker kohas {city}, {country} tehtud piltide jaoks", + "map_marker_for_image": "Kaardimarker pildile, mis on tehtud kohas {city}, {country}", "map_marker_with_image": "Kaardimarker pildiga", "map_no_location_permission_content": "Praeguse asukoha üksuste kuvamiseks on vaja asukoha luba. Kas soovid seda praegu lubada?", "map_no_location_permission_title": "Asukoha luba keelatud", @@ -2376,6 +2384,8 @@ "trash_page_title": "Prügikast ({count})", "trashed_items_will_be_permanently_deleted_after": "Prügikasti tõstetud üksused kustutatakse jäädavalt {days, plural, one {# päeva} other {# päeva}} pärast.", "trigger": "Päästik", + "trigger_asset_metadata_extraction": "Üksuste metaandmete eraldamine", + "trigger_asset_metadata_extraction_description": "Käivitub, kui üksusest eraldatakse EXIF metaandmed", "trigger_asset_uploaded": "Üksuse üleslaadimine", "trigger_asset_uploaded_description": "Käivitub uue üksuse üleslaadimisel", "trigger_description": "Sündmus, mis käivitab töövoo", diff --git a/i18n/eu.json b/i18n/eu.json index 813a0b72bf..69f677cb7e 100644 --- a/i18n/eu.json +++ b/i18n/eu.json @@ -79,6 +79,7 @@ "cron_expression_description": "Ezarri eskaneatzeko tartea cron formatua erabiliz. Informazio gehiago lortzeko, jo mesedez Crontab Guru adibidera", "cron_expression_presets": "Cron adierazpenaren aurrezarpenak", "disable_login": "Desgaitu saio hastea", + "download_csv": "CSVa deskargatu", "duplicate_detection_job_description": "Exekutatu ikasketa automatikoa aktiboetan antzeko irudiak detektatzeko. Bilaketa Adimendunean oinarritzen da", "exclusion_pattern_description": "Bazterketa patroiek fitxategiak eta karpetak alde batera uzteko aukera ematen dizute liburutegia eskaneatzean. Oso erabilgarria da inportatu nahi ez dituzun fitxategiak dituzten karpetak badituzu, adibidez RAW fitxategiak.", "export_config_as_json_description": "Deskargatu momentuko sistema konfigurazioa JSON fitxategi moduan", @@ -87,46 +88,213 @@ "face_detection_description": "Hauteman aurpegiak elementuetan ikasketa automatikoa erabiliz. Bideoen kasuan, miniatura baino ez da kontuan hartuko. \"Freskatu\" aukerak elementu guztiak (bir-)prozesatzen ditu. \"Berrezarri\" aukerak horrez gain, uneko aurpegi-datu guztiak ezabatzen ditu. \"Falta direnak\" aukerak oraindik prozesatu ez diren elementuak jartzen ditu ilaran. Detektatutako aurpegiak Aurpegiaren Ezagutzarako ilaran jarriko dira Aurpegiaren Detekzioa amaitu ondoren, lehendik dauden edo berriak diren pertsonetan multzokatuz.", "facial_recognition_job_description": "Detektatutako aurpegiak pertsonetan multzokatu. Urrats hau Aurpegi Detekzioaren ondoren egikaritzen da. \"Berrezarri\" aukerak aurpegi guztiak (bir-)taldekatzen ditu. \"Faltan\" aukerak izendatu gabeko aurpegiak ilaran jartzen ditu.", "failed_job_command": "{command} komandoak hutsegin du {job} zereginerako", + "force_delete_user_warning": "ADI: Honek erabiltzailea eta baliabide guztiak ezabatuko lituzke. Akzio hau ezin da atzera bota eta fitxategiak ezin izango dira berreskuratu.", "image_format": "Formatua", "image_format_description": "WebP ereduak JPEG baino fitxategi txikiagoak sortzen ditu, baina motelagoa da kodifikatzen.", + "image_fullsize_description": "Tamaina osoko irudia metadaturik gabe, zoom egiterakoan erabiltzen da", + "image_fullsize_enabled": "Tamaina osoko irudien sorrera ezarri", + "image_fullsize_enabled_description": "Sortu tamaina osoko irudia web-erako egokiak ez diren formatuetan. \"Nahiago aurrebista txertatua\" gaituta dagoenean, aurrebista txertatuak zuzenean erabiltzen dira, bihurtu gabe. Ez die eragiten web-erako egokiak diren formatuei (JPEG, adibidez).", + "image_fullsize_quality_description": "Tamaina osoko irudiaren kalitatea (1-100). Zenbat eta altuagoa, orduan eta hobea, baina fitxategi handiagoak sortzen ditu.", + "image_fullsize_title": "Tamaina osoko irudien ezarpenak", "image_prefer_embedded_preview": "Nahiago aurrebista txertatua", + "image_prefer_embedded_preview_setting_description": "Erabili RAW argazkien aurrebista txertatuak irudiak prozesatzeko abiapuntu gisa (eskuragarri daudenean). Honek kolore zehatzagoak eman ditzake irudi batzuetan, baina aurrebistaren kalitatea kameraren araberakoa da eta irudiak konpresio-artefaktu gehiago izan ditzake.", "image_prefer_wide_gamut": "Nahiago gamut zabala", + "image_prefer_wide_gamut_setting_description": "Erabili Display P3 miniaturentzako (thumbnails). Honek hobeto mantentzen du kolore-espazio zabaleko irudien bizitasuna, baina irudiak ezberdin ikus daitezke arakatzaile bertsio zaharra duten gailu zaharkituetan. sRGB irudiak sRGB gisa mantentzen dira kolore-aldaketak saihesteko.", + "image_preview_description": "Tamaina baxuko irudia metadaturik gabe, baliabide bakarra bistaratzerakoan eta ikasketa automatikoan erabiltzeko", + "image_preview_quality_description": "Aurrebisten kalitatea (1-100). Zenbat eta altuagoa, orduan eta hobea, baina fitxategi handiagoak sortzen ditu eta aplikazioaren jariakortasuna moteldu dezake. Balio baxu bat ezartzeak ikasketa automatikoaren kalitatea kaltetu dezake.", "image_preview_title": "Aurreikusiaen Konfigurazioa", "image_progressive": "Progresiboa", + "image_progressive_description": "Kodetu JPEG irudiak progresiboki, pixkanaka kargatzen joan daitezen. Honek ez du eraginik WebP irudietan.", "image_quality": "Kalitatea", "image_resolution": "Erresoluzioa", + "image_resolution_description": "Bereizmen handiagoek xehetasun gehiago gorde ditzakete, baina denbora gehiago behar dute kodetzeko, fitxategi tamaina handiagoak izan eta aplikazioaren jariakortasuna murriztu dezake.", "image_settings": "Argazkien Konfigurazioa", "image_settings_description": "Kudeatu sortutako irudien kalitatea eta erresoluzioa", + "image_thumbnail_description": "Metadaturik gabeko miniatura, argazki-taldeak denbora-lerro nagusia bezala ikusten direnean erabiltzen da", + "image_thumbnail_quality_description": "Miniaturaren kalitatea 1-100. Handia hobea da, baina fitxategi handiagoak sortzen ditu eta aplikazioen jariakortasuna murriztu dezake.", "image_thumbnail_title": "Argazki Txikien Konfigurazioa", "import_config_from_json_description": "Inportatu sistema konfigurazioa JSON konfigurazio fitxategia kargatuz", "job_concurrency": "{job} konkurrentzia", "job_created": "Zeregina sortuta", + "job_not_concurrency_safe": "Ataza hau ez da segurua aldiberekotasunerako.", "job_settings": "Zereginaren konfigurazioa", "job_settings_description": "Kudeatu lanen konkurrentzia", + "jobs_delayed": "{jobCount, plural, one {Atzeratutako bat} other {# atzeratutako}}", + "jobs_failed": "{jobCount, plural, one {Oker bat} other {# oker}}", "jobs_over_time": "Lanak denboran zehar", "library_created": "Sortutako liburutegia: {library}", "library_deleted": "Liburutegia ezabatuta", "library_details": "Liburutegiaren xehetasunak", + "library_folder_description": "Zehaztu inportatzeko karpeta bat. Karpeta honetan,eta haren azpikarpetetan irudiak eta bideoak bilatuko dira.", + "library_remove_exclusion_pattern_prompt": "Ziur bazterketa-eredu hau kendu nahi duzula?", "library_remove_folder_prompt": "Ziur zaude inportazio karpeta hau ezabatu nahi duzula?", + "library_scanning": "Aldizkako eskaneatzea", + "library_scanning_description": "Konfiguratu liburutegiko aldizkako eskaneatzea", + "library_scanning_enable_description": "Aldizkako liburutegiaren eskaneoa gaitu", + "library_settings": "Kanpoko liburutegia", + "library_settings_description": "Kudeatu kanpoko liburutegiaren ezarpenak", + "library_tasks_description": "Kanpoko liburutegietan aldatutakoak baliabideak edo baliabide berriak edo bilatu", + "library_updated": "Liburutegia eguneratuta", + "library_watching_enable_description": "Behatu kanpoko liburutegien fitxategi aldaketak", + "library_watching_settings": "Liburutegien behaketa", + "library_watching_settings_description": "Fitxategi aldaketak behatu automatikoki", "logging_enable_description": "Gaitu erregistroak", "logging_level_description": "Erregistroak gaituta daudenean, nolako erregistro maila erabili.", "logging_settings": "Erregistroak", + "machine_learning_availability_checks": "Eskuragarritasun egiaztapenak", + "machine_learning_availability_checks_description": "Automatikoki detektatu eta hobetsi eskuragarri dauden ikaskuntza automatikoko zerbitzariak", + "machine_learning_availability_checks_enabled": "Eskuragarritasun egiaztapenak gaitu", + "machine_learning_availability_checks_interval": "Egiaztapen denbora-tartea", + "machine_learning_availability_checks_interval_description": "Denbora-tartea milisegundotan eskuragarritasun egiaztapenen artean", + "machine_learning_availability_checks_timeout": "Eskakizun denbora-muga", + "machine_learning_availability_checks_timeout_description": "Eskakizun denbora-muga milisegundotan eskuragarritasun egiaztapenentzat", + "machine_learning_clip_model": "CLIP Modeloa (Kontrastezko hizkuntza-irudi aurre-prestakuntza)", + "machine_learning_clip_model_description": "hemen zerrendatutako CLIP eredu baten izena. Kontuan izan 'Bilaketa adimentsua' lana berriro exekutatu behar duzula irudi guztientzat modelo bat aldatzen duzunean.", "machine_learning_duplicate_detection": "Bizkoizketa Detekzioa", "machine_learning_duplicate_detection_enabled": "Gaitu bikoizketa detekezioa", + "machine_learning_duplicate_detection_enabled_description": "Desgaituta badago, berdin-berdinak diren baliabideak desbikoiztu egingo dira.", + "machine_learning_duplicate_detection_setting_description": "Erabili CLIP txertaketak bikoiztuak aurkitzeko", + "machine_learning_enabled": "Gaitu ikaskuntza automatikoa", + "machine_learning_enabled_description": "Desgaituta badago, ML ezaugarri guztiak desgaitu egingo dira beheko ezarpenak kontuan hartu gabe.", "machine_learning_facial_recognition": "Aurpegi-Ezagutza", "machine_learning_facial_recognition_description": "Detektatu, ezagutu eta aurpegiak banatu argazkietan", "machine_learning_facial_recognition_model": "Aurpegi-Ezagutza eredua", + "machine_learning_facial_recognition_model_description": "Modeloak tamainaren beheranzko ordenan zerrendatzen dira. Modelo handiagoek motelagoak dira eta memoria gehiago erabiltzen dute, baina emaitza hobeak ematen dituzte. Kontuan izan eredu bat aldatzen duzunean aurpegiak hautemateko lana berriro exekutatu behar duzula irudi guztientzat.", "machine_learning_facial_recognition_setting": "Aurpegi-Ezagutza Gaitu", + "machine_learning_facial_recognition_setting_description": "Desgaituta badago, irudiak ez dira kodetuko aurpegia ezagutzeko eta ez dute Arakatu orriko Pertsonak atala beteko.", + "machine_learning_max_detection_distance": "Detekzio distantzia maximoa", + "machine_learning_max_detection_distance_description": "Bi irudien arteko gehienezko distantzia bikoiztutzat hartzeko, 0,001-0,1 bitartekoa. Balio altuagoek bikoiztu gehiago detektatuko dituzte, baina positibo faltsuak sor ditzakete.", + "machine_learning_max_recognition_distance": "Antzemateko distantzia maximoa", + "machine_learning_max_recognition_distance_description": "Bi aurpegiren arteko gehienezko distantzia pertsona berdintzat hartzeko, 0-2 bitartekoa. Hori gutxitzeak bi pertsona pertsona bera bezala etiketatzea ekidin dezake, eta igotzeak, berriz, pertsona bera bi pertsona ezberdin gisa etiketatzea ekidin dezake. Kontuan izan errazagoa dela bi pertsona batzea pertsona bat bitan zatitzea baino, beraz, huts egin atalase baxuago baten alde, ahal denean.", + "machine_learning_min_detection_score": "Gutxieneko detekzio puntuazioa", + "machine_learning_min_detection_score_description": "0-1etik detektatu beharreko aurpegi baten gutxieneko konfiantza puntuazioa. Balio baxuagoek aurpegi gehiago detektatuko dituzte, baina positibo faltsuak sor ditzakete.", + "machine_learning_min_recognized_faces": "Gutxieneko antzemandako aurpegiak", + "machine_learning_min_recognized_faces_description": "Sortu beharreko pertsona batek aitortutako aurpegien gutxieneko kopurua. Hau handitzeak Aurpegi-ezagutza zehatzagoa bihurtzen du aurpegia pertsona bati esleitzeko aukera areagotzearen truke.", + "machine_learning_ocr": "OCR", + "machine_learning_ocr_description": "Erabili ikaskuntza automatikoa irudietan testua ezagutzeko", + "machine_learning_ocr_enabled": "OCR gaitu", + "machine_learning_ocr_enabled_description": "Desgaituta badago, irudiek ez dute testu-ezagutzarik izango.", + "machine_learning_ocr_max_resolution": "Gehienezko bereizmena", + "machine_learning_ocr_max_resolution_description": "Bereizmen honen gaineko aurrebistak tamaina aldatuko dira aspektu-erlazioa mantenduz. Balio altuagoak zehatzagoak dira, baina denbora gehiago behar dute prozesatzeko eta memoria gehiago behar dute.", + "machine_learning_ocr_min_detection_score": "Gutxieneko detekzio puntuazioa", + "machine_learning_ocr_min_detection_score_description": "Detektatu beharreko testuaren gutxieneko konfiantza puntuazioa 0-1etik. Balio baxuagoek testu gehiago detektatuko dute, baina positibo faltsuak sor ditzakete.", + "machine_learning_ocr_min_recognition_score": "Antzemateko gutxieneko puntuazioa", + "machine_learning_ocr_min_score_recognition_description": "0-1etik antzeman beharreko detektaturiko testuaren gutxieneko konfiantza-puntuazioa. Balio baxuagoek testu gehiago ezagutuko dute, baina positibo faltsuak sor ditzakete.", + "machine_learning_ocr_model": "OCR modeloa", + "machine_learning_ocr_model_description": "Zerbitzari-modeloak mugikorren modeloak baino zehatzagoak dira, baina denbora gehiago eta memoria gehiago behar dute prozesatzeko.", + "machine_learning_settings": "Ikaskuntza automatikoaren ezarpenak", + "machine_learning_settings_description": "Kudeatu ikaskuntza automatikoaren ezaugarri eta ezarpenak", + "machine_learning_smart_search": "Bilaketa adimentsua", + "machine_learning_smart_search_description": "Bilatu irudiak semantikoki CLIP txertaketak erabiliz", "machine_learning_smart_search_enabled": "Gaitu bilaketa arina", + "machine_learning_smart_search_enabled_description": "Desgaituta badago, irudiak ez dira kodetuko bilaketa adimentsurako.", + "machine_learning_url_description": "Ikaskuntza automatikoko zerbitzariaren URLa. URL bat baino gehiago ematen bada, zerbitzari bakoitza banan-banan saiatuko da batek behar bezala erantzun arte, lehenengotik azkenera arte. Erantzuten ez duten zerbitzariei aldi baterako ez ikusi egingo zaie sarera itzuli arte.", + "maintenance_backup_management": "Segurtasun-kopien kudeaketa", + "maintenance_delete_backup": "Segurtasun-kopia ezabatu", + "maintenance_delete_backup_description": "Fitxategi hau behin-betiko ezabatuko da.", + "maintenance_delete_error": "Akatsa segurtasun-kopia ezabatzerakoan.", + "maintenance_integrity_check": "Egiaztatu", + "maintenance_integrity_check_all": "Denak egiaztatu", + "maintenance_integrity_checksum_mismatch": "Egiaztapeneko baturaren desakordioa", + "maintenance_integrity_checksum_mismatch_description": "Fitxategiak non diskoko egiaztapeneko batura Immichek bere datu-basean gordeta duenarekin desakordioan dago.", + "maintenance_integrity_checksum_mismatch_job": "Egiaztapeneko baturen desakordioak egiaztatu", + "maintenance_integrity_checksum_mismatch_refresh_job": "Freskatu kontrol-sumen desadoztasun txostenak", + "maintenance_integrity_missing_file": "Falta diren fitxategiak", + "maintenance_integrity_missing_file_description": "Immich-ek bere datu-basean jarraitu dituen baina fitxategi-sisteman existitzen ez diren fitxategiak.", + "maintenance_integrity_missing_file_job": "Egiaztatu falta diren fitxategiak", + "maintenance_integrity_missing_file_refresh_job": "Freskatu falta diren fitxategien txostenak", + "maintenance_integrity_report": "Osotasun txostena", + "maintenance_integrity_untracked_file": "Jarraitu gabeko fitxategiak", + "maintenance_integrity_untracked_file_description": "Immich-ek bere direktorioetako fitxategien erregistrorik ez duen fitxategiak.", + "maintenance_integrity_untracked_file_job": "Egiaztatu jarraitu gabeko fitxategiak", + "maintenance_integrity_untracked_file_refresh_job": "Freskatu jarraitu gabeko fitxategien txostenak", + "maintenance_restore_backup": "Segurtasun-kopia berrezarri", + "maintenance_restore_backup_description": "Immich aukeratutako babeskopiatik ezabatu eta leheneratu egingo da. Jarraitu aurretik, babeskopia bat sortuko da.", + "maintenance_restore_backup_different_version": "Babeskopia hau Immich-en beste bertsio batekin sortu da!", + "maintenance_restore_backup_unknown_version": "Ezin izan da babeskopiaren bertsioa zehaztu.", + "maintenance_restore_database_backup": "Berrezarri datu-basearen babeskopia", + "maintenance_restore_database_backup_description": "Itzuli datu-basearen aurreko egoera batera babeskopia fitxategi bat erabiliz", + "maintenance_settings": "Mantentzea", + "maintenance_settings_description": "Jarri Immich mantentze moduan.", + "maintenance_start": "Aldatu mantentze modura", + "maintenance_start_error": "Aldatu mantentze modura.", + "maintenance_upload_backup": "Kargatu datu-basearen babeskopia fitxategia", + "maintenance_upload_backup_error": "Ezin izan da babeskopia kargatu, .sql/.sql.gz fitxategia al da?", + "manage_concurrency": "Aldiberekotasuna kudeatu", + "manage_concurrency_description": "Nabigatu atazen orrira atazen aldiberekotasuna kudeatzeko", "manage_log_settings": "Kudeatu erregistroen konfigurazioa", "map_dark_style": "Beltz estiloa", + "map_enable_description": "Gaitu maparen ezaugarriak", "map_gps_settings": "Mapa eta GPS Konfigurazioa", + "map_gps_settings_description": "Kudeatu mapa eta GPS (alderantzizko geokodeketa) ezarpenak", + "map_implications": "Maparen funtzioak kanpoko fitxa-zerbitzu batean oinarritzen da (tiles.immich.cloud)", "map_light_style": "Zuri estiloa", + "map_manage_reverse_geocoding_settings": "Kudeatu Alderantzizko geokodeketa ezarpenak", + "map_reverse_geocoding": "Alderantziko geokodeketa", + "map_reverse_geocoding_enable_description": "Alderantzizko geokodeketa gaitu", + "map_reverse_geocoding_settings": "Alderantzizko geokodeketaren ezarpenak", "map_settings": "Mapa", + "map_settings_description": "Kudeatu maparen ezarpenak", + "map_style_description": "URL-a style.json Maparen itxura fitxategi batera", + "memory_cleanup_job": "Gogorapen garbiketa", + "memory_generate_job": "Gogorapen sorkuntza", + "metadata_extraction_job": "Metadatuen erauzketa", + "metadata_extraction_job_description": "Atera metadatuen informazioa baliabide bakoitzetik, hala nola GPSa, aurpegiak eta bereizmena", "metadata_faces_import_setting": "Gaitu aurpegien inportazioa", + "metadata_faces_import_setting_description": "Inportatu aurpegiak irudien EXIF datuetatik eta Sidecar fitxategietatik", "metadata_settings": "Metadata Konfigurazioa", "metadata_settings_description": "Kudeatu metadaten konfigurazioa", "migration_job": "Migrazio", + "migration_job_description": "Migratu baliabideen eta aurpegien miniaturak karpeten egitura berrienera", + "nightly_tasks_cluster_faces_setting_description": "Exekutatu aurpegi-ezagutza detektatu berri diren aurpegietan", + "nightly_tasks_cluster_new_faces_setting": "Aurpegi berriak multzokatu", + "nightly_tasks_database_cleanup_setting": "Datu-base garbiketa zereginak", + "nightly_tasks_database_cleanup_setting_description": "Garbitu datu zaharrak eta iraungitako datuak datu-basetik", + "nightly_tasks_generate_memories_setting": "Gogorapenak sortu", + "nightly_tasks_generate_memories_setting_description": "Gogorapen berriak sortu balibadeetatik", + "nightly_tasks_missing_thumbnails_setting": "Falta diren miniaturak sortu", + "nightly_tasks_missing_thumbnails_setting_description": "Jarri miniaturarik gabeko baliabideak ilaran miniaturak sortzeko", + "nightly_tasks_settings": "Gaueko atazen ezarpenak", + "nightly_tasks_settings_description": "Gaueko atazak kudeatu", + "nightly_tasks_start_time_setting": "Hasiera denbora", + "nightly_tasks_start_time_setting_description": "Zerbitzaria gaueko atazak exekutatzen hasten den ordua", + "nightly_tasks_sync_quota_usage_setting": "Sinkronizazio kuotaren erabilera", + "nightly_tasks_sync_quota_usage_setting_description": "Eguneratu erabiltzaileen biltegiratze-kuota, egungo erabileraren arabera", + "no_paths_added": "Ez da biderik sartu", + "no_pattern_added": "Ez da eredurik sartu", + "note_apply_storage_label_previous_assets": "Oharra: Aurretik kargatutako baliabideei biltegiratze-etiketa aplikatzeko, exekutatu", + "note_cannot_be_changed_later": "OHARRA: Ezin da geroago aldatu!", + "notification_email_from_address": "Nondik", + "notification_email_from_address_description": "Bidaltzailearen helbide elektronikoa, adibidez: \"Immich Photo Server \". Ziurtatu mezu elektronikoak bidaltzeko baimena daukazun helbide bat erabiltzen duzula.", + "notification_email_host_description": "Posta elektronikoko zerbitzariaren ostalaria (adibidez, smtp.immich.app)", + "notification_email_ignore_certificate_errors": "Ziurtagiri akatsak kontuan ez hartu", + "notification_email_ignore_certificate_errors_description": "TLS ziurtagiri egiaztapen akatsak kontuan ez hartu (ez da gomendatzen)", + "notification_email_password_description": "E-mail zerbitzariarekin autentikatzerakoan erabiltzeko pasahitza", + "notification_email_port_description": "E-mail zerbitzariaren portua (adibidez, 25, 465 edo 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "SMTPS erabili (SMTP TLS bitartez)", + "notification_email_sent_test_email_button": "Bidali E-mail froga mezua eta gorde", + "notification_email_setting_description": "E-mail bidezko jakinarazpenak bidaltzeko ezarpenak", + "notification_email_test_email": "E-mail froga mezua bidali", + "notification_email_test_email_failed": "Ezin izan dira probako e-maila bidali, egiaztatu zure balioak", + "notification_email_test_email_sent": "Proba-mezu bat bidali da {email} helbidera. Mesedez, egiaztatu zure sarrera-ontzia.", + "notification_email_username_description": "E-mail zerbitzariarekin autentifikatzean erabili beharreko erabiltzaile-izena", + "notification_enable_email_notifications": "E-mail jakinarazpenak gaitu", + "notification_settings": "Jakinarazpenen ezarpenak", + "notification_settings_description": "Kudeatu jakinarazpen-ezarpenak, posta elektronikoa barne", + "oauth_allow_insecure_requests": "Onartu segurtasunik gabeko eskaerak", + "oauth_allow_insecure_requests_description": "OHARRA: honek OAuth eskaeretarako TLS ziurtagirien baliozkotzea desgaitzen du eta MITM erasoak jasan ditzakezu.", + "oauth_auto_launch": "Hasieratze automatikoa", + "oauth_auto_launch_description": "Hasi OAuth saio-hasiera-fluxua automatikoki saioa hasteko orrira nabigatzean", + "oauth_auto_register": "Erregistro automatikoa", + "oauth_auto_register_description": "Erregistratu automatikoki erabiltzaile berriak OAuth-ekin saioa hasi ondoren", + "oauth_button_text": "Botoiaren testua", + "oauth_client_secret_description": "Bezero konfidentzialarentzat beharrezkoa da, edo bezero publikoarentzat PKCE (Kodeak trukatzeko froga-gakoa) onartzen ez bada.", + "oauth_enable_description": "Saioa hasi OAuth erabiliz", + "oauth_end_session_url_description": "Birbideratu erabiltzailea URI honetara saioa amaitzean.", + "oauth_mobile_redirect_uri": "Mugikorren birbideratze URIa", + "oauth_mobile_redirect_uri_override": "Mugikorreko birbideratze URIa gainidatzi", + "oauth_mobile_redirect_uri_override_description": "Gaitu OAuth hornitzaileak mugikorren URIrik onartzen ez duenean, adibidez, ''{callback}''", "oauth_prompt_description": "Prompt parametroa", "oauth_role_claim": "Rol aldarrikapena", "oauth_role_claim_description": "Eman automatikoki administratzaile sarbidea erreklamazio honen presentzian oinarrituta. Erreklamazioak 'erabiltzailea' edo 'administratzailea' izan dezake.", @@ -136,26 +304,1533 @@ "oauth_storage_label_claim": "Memoriaren etiketa eskaera", "oauth_storage_label_claim_description": "Erabiltzailearen memoria-etiketa automatikoki finkatzea, eskatutako balioan.", "oauth_storage_quota_claim": "Eskatutako memoriaren kuota", - "transcoding_acceleration_vaapi": "VAAPI" + "oauth_storage_quota_claim_description": "Ezarri automatikoki erabiltzailearen biltegiratze-kuota balio honekin.", + "oauth_storage_quota_default": "Biltegiratze-kuota lehenetsia (GiB)", + "oauth_storage_quota_default_description": "Erreklamaziorik ematen ez denean erabili beharreko biltegiratze-kuota (GiB).", + "oauth_timeout": "Eskaera denbora-muga", + "oauth_timeout_description": "Eskaeren denbora-muga milisegundotan", + "ocr_job_description": "Erabili ikaskuntza automatikoa irudietan testua antzemateko", + "password_enable_description": "Saioa hasi E-mail eta pasahitza erabiliz", + "password_settings": "Pasahitz bidezko saio hasiera", + "password_settings_description": "Kudeatu pasahitz bidezko saio hasiera", + "paths_validated_successfully": "Bide guztiak arrakastaz balioetsita", + "person_cleanup_job": "Pertsonen garbiketa", + "queue_details": "Ilararen xehetasunak", + "queues": "Ataza ilara", + "queues_page_description": "Ataza ilaren kudeaketa orria", + "quota_size_gib": "Kuota tamaina (GiB)", + "refreshing_all_libraries": "Liburutegi guztiak freskatu", + "registration": "Kudeatzaile erregistroa", + "registration_description": "Sistemako lehen erabiltzailea zarenez, Administratzaile gisa esleituko zara eta administrazio-zereginez arduratuko zara, eta erabiltzaile gehigarriak zuk sortuko dituzu.", + "release_channel_release_candidate": "Bertsio kandidatua", + "release_channel_stable": "Egonkorra", + "remove_failed_jobs": "Akatsdun atazak ezabatu", + "require_password_change_on_login": "Eskatu erabiltzaileari pasahitza aldatzea lehen saio hasieran", + "reset_settings_to_default": "Berrezarri ezarpenak lehenespenera", + "reset_settings_to_recent_saved": "Berrezarri ezarpenak azken gordetako ezarpenetara", + "scanning_library": "Liburutegia eskaneatzen", + "search_jobs": "Atazak bilatu…", + "send_welcome_email": "Ongietorri e-maila bidali", + "server_external_domain_settings": "Kanpoko domeinua", + "server_external_domain_settings_description": "Kanpoko estekentzako erabiltzen den domeinua", + "server_public_users": "Erabiltzaile publikoak", + "server_public_users_description": "Erabiltzaile guztiak (izena eta helbide elektronikoa) zerrendatzen dira erabiltzaile bat partekatutako albumetan gehitzean. Desgaituta dagoenean, erabiltzaileen zerrenda administratzaileentzako soilik egongo da erabilgarri.", + "server_settings": "Zerbizariaren ezarpenak", + "server_settings_description": "Zerbitzariaren ezarpenak kudeatu", + "server_stats_page_description": "Administrarien zerbitzariaren estatistikak", + "server_welcome_message": "Ongietorri mezua", + "server_welcome_message_description": "Saioa hasteko orrian bistaratzen den mezua.", + "settings_page_description": "Administrari ezarpenen orria", + "sidecar_job": "Sidecar metadatuak", + "sidecar_job_description": "Ezagutu edo sinkronizatu sidecar metadatuak fitxategi-sistematik", + "slideshow_duration_description": "Irudi bakoitza bistaratzeko segundo kopurua", + "smart_search_job_description": "Exekutatu ikasketa automatikoa baliabideetan bilaketa adimenduna laguntzeko", + "storage_template_date_time_description": "Baliabidearen sorreraren ordu-zigilua data-orduaren informazioarako erabiltzen da", + "storage_template_date_time_sample": "Denbora eredua {date}", + "storage_template_enable_description": "Biltegiratze txantiloien motorra gaitu", + "storage_template_hash_verification_enabled": "Hash egiaztapena gaituta dago", + "storage_template_hash_verification_enabled_description": "Hash egiaztapena gaitzen du, ez desgaitu hau ondorioen berri jakin ezean", + "storage_template_migration": "Biltegiratze txantiloi migrazioa", + "storage_template_migration_description": "Aplikatu uneko {template} aldez aurretik igotako baliabideei", + "storage_template_migration_info": "Biltegiratzeko txantiloiak luzapen guztiak letra xehetara bihurtuko ditu. Txantiloi-aldaketak baliabide berriei soilik aplikatuko zaizkie. Txantiloia aurretik kargatutako baliabideei atzeraeraginean aplikatzeko, exekutatu {job}.", + "storage_template_migration_job": "Biltegiratze-txantiloia migratzeko ataza", + "storage_template_more_details": "Ezaugarri honi buruzko xehetasun gehiago lortzeko, ikusi Biltegiratzeko txantiloia eta bere inplikazioak", + "storage_template_onboarding_description_v2": "Gaituta dagoenean, ezaugarri honek fitxategiak automatikoki antolatuko ditu erabiltzaileak definitutako txantiloi batean oinarrituta. Informazio gehiago lortzeko, ikusi dokumentazioa.", + "storage_template_path_length": "Gutxi gorabeherako bide-luzeraren muga: {length, number}/{limit, number}", + "storage_template_settings": "Biltegiratze txantiloia", + "storage_template_settings_description": "Kudeatu igotako baliabidearen karpeta-egitura eta fitxategi-izena", + "storage_template_user_label": "{label} da erabiltzailearen biltegiratze etiketa", + "system_settings": "Sistema ezarpenak", + "tag_cleanup_job": "Etiketak garbitu", + "template_email_available_tags": "Aldagai hauek erabil ditzakezu txantiloian: {tags}", + "template_email_if_empty": "Txantiloia hutsik badago, posta elektroniko lehenetsia erabiliko da.", + "template_email_invite_album": "Album gonbidapen txantiloia", + "template_email_preview": "Aurrebista", + "template_email_settings": "E-mail txantiloiak", + "template_email_update_album": "Eguneratu album txantiloia", + "template_email_welcome": "Ongietorri e-mail mezuaren txantiloia", + "template_settings": "Jakinarazpen txantiloiak", + "template_settings_description": "Kudeatu jakinarazpenetarako txantiloi pertsonalizatuak", + "theme_custom_css_settings": "CSS pertsonalizatua", + "theme_custom_css_settings_description": "Estilo-orriek Immich diseinua pertsonalizatzeko aukera ematen dute.", + "theme_settings": "Gaiaren ezarpenak", + "theme_settings_description": "Kudeatu Immich web interfazearen pertsonalizazioa", + "thumbnail_generation_job": "Sortu miniaturak", + "thumbnail_generation_job_description": "Sortu miniatura handiak, txikiak eta lausoak baliabide bakoitzeko, baita pertsona bakoitzarentzako miniaturak ere", + "transcoding_acceleration_api": "Azelerazioaren APIa", + "transcoding_acceleration_api_description": "Transkodifikazioa bizkortzeko zure gailuarekin elkarreragingo duen APIa. Ezarpen hau 'ahalegin onena' da: huts egiten badu software bidezko transkodifikaziora itzuliko da. VP9-k funtziona dezake edo ez zure hardwarearen arabera.", + "transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU-a behar du)", + "transcoding_acceleration_qsv": "Quick Sync (7. belaunaldiko Intel prozesadorea edo handiagoa behar du)", + "transcoding_acceleration_rkmpp": "RKMPP (Rockchip-en SOC-an soilik)", + "transcoding_acceleration_vaapi": "VAAPI", + "transcoding_accepted_audio_codecs": "Onartutako audio-kodekak", + "transcoding_accepted_audio_codecs_description": "Hautatu zein audio-kodek transkodetu behar ez diren. Transkodetze-politika jakin batzuetarako bakarrik erabiltzen da.", + "transcoding_accepted_containers": "Onartutako kontenedoreak", + "transcoding_accepted_containers_description": "Hautatu zein kontenedore formatu ez diren MP4ra birmixatu behar. Transkodetze-politika jakin batzuetarako bakarrik erabiltzen da.", + "transcoding_accepted_video_codecs": "Onartutako bideo-kodekak", + "transcoding_accepted_video_codecs_description": "Hautatu zein bideo-kodek transkodetu behar ez diren. Transkodetze-politika jakin batzuetarako bakarrik erabiltzen da.", + "transcoding_advanced_options_description": "Erabiltzaile gehienek aldatu behar ez dituzten aukerak", + "transcoding_audio_codec": "Audio-kodeka", + "transcoding_audio_codec_description": "Opus kalitate goreneko aukera da, baina gailu edo software zaharrekin bateragarritasun txikiagoa du.", + "transcoding_bitrate_description": "Gehienezko bit-tasa baino handiagoa edo onartutako formatuan ez dauden bideoak", + "transcoding_codecs_learn_more": "Hemen erabiltzen den terminologiari buruz gehiago jakiteko, jo FFmpeg-en dokumentaziora. H.264 kodeka, HEVC kodeka eta VP9 kodeka .", + "transcoding_constant_quality_mode": "Kalitate iraunkorreko modua", + "transcoding_constant_quality_mode_description": "ICQ CQP baino hobea da, baina hardware-azelerazio-gailu batzuek ez dute modu hau onartzen. Aukera hau ezartzeak zehaztutako modua hobetsiko du kalitatean oinarritutako kodeketa erabiltzean. NVENC-ek ez du jaramonik egingo ICQ onartzen ez duelako.", + "transcoding_constant_rate_factor": "Tasa konstantearen faktorea (-crf)", + "transcoding_constant_rate_factor_description": "Bideoaren kalitate maila. Balio tipikoak H.264rako 23, HEVCrako 28, VP9rako 31 eta AV1erako 35 dira. Baxuagoa hobea da, baina fitxategi handiagoak sortzen ditu.", + "transcoding_disabled_description": "Ez transkodetu bideorik, baliteke bezero batzuen erreprodukzioa haustea", + "transcoding_encoding_options": "Kodetze aukerak", + "transcoding_encoding_options_description": "Ezarri kodekak, bereizmena, kalitatea eta beste aukera batzuk kodetutako bideoetarako", + "transcoding_hardware_acceleration": "Hardware bidezko azelerazioa", + "transcoding_hardware_acceleration_description": "Esperimentala: transkodeketa azkarragoa dakar baina kalitatea murriztu dezake bit-tasa berean", + "transcoding_hardware_decoding": "Hardware bidezko deskodetzea", + "transcoding_hardware_decoding_setting_description": "Mutur-muturreko azelerazioa gaitzen du soilik kodeketa bizkortu beharrean. Baliteke bideo guztietan ez funtzionatzea.", + "transcoding_max_b_frames": "Gehienezko B-fotogramak", + "transcoding_max_b_frames_description": "Balio altuagoek konpresioaren eraginkortasuna hobetzen dute, baina kodeketa moteldu egiten dute. Baliteke gailu zaharretako hardware-azelerazioarekin bateragarria ez izatea. 0 balioak B-koardoak desgaitzen ditu, eta -1 balioak balio hau automatikoki ezartzen du.", + "transcoding_max_bitrate": "Gehienezko bit-tasa", + "transcoding_max_bitrate_description": "Gehienezko bit-tasa ezartzeak fitxategien tamainak aurreikusgarriagoak egite ditu kalitatearen kostu txikiarekin. 720p-n, balio tipikoak VP9 edo HEVCrako 2600 kbit/s edo H.264rako 4500 kbit/s dira. Desgaituta dago 0 moduan ezartzen bada. Unitaterik zehazten ez denean, k (kbit/s-rako) suposatzen da; beraz, 5000, 5000k eta 5M (Mbit/s-rako) baliokideak dira.", + "transcoding_max_keyframe_interval": "Gehieneko gako-fotograma tartea", + "transcoding_max_keyframe_interval_description": "Gako-fotogramen arteko gehienezko fotograma-distantzia ezartzen du. Balio baxuagoek konpresio-eraginkortasuna okertzen dute, baina bilaketa-denborak hobetzen dituzte eta baliteke kalitatea hobetzea mugimendu azkarra duten eszenetan. 0-k balio hau automatikoki ezartzen du.", + "transcoding_optimal_description": "Helburuko bereizmenetik gorako bideoak edo onartutako formatuan ez daudenak", + "transcoding_policy": "Transkodetze politika", + "transcoding_policy_description": "Ezartzen du bideo bat noiz transkodetuko den", + "transcoding_preferred_hardware_device": "Hardware-gailu hobetsia", + "transcoding_preferred_hardware_device_description": "VAAPI-ri eta QSV-i soilik aplikatzen zaie. Hardware transkodetzeko erabilitako dri nodoa ezartzen du.", + "transcoding_preset_preset": "Txantiloia (-preset)", + "transcoding_preset_preset_description": "Konpresioaren abiadura. Txantiloi motelagoak fitxategi txikiagoak sortzen ditu eta kalitatea handitzen du bit-tasa jakin batera bideratzen denean. VP9-k \"azkarrago\"-tik gorako abiadura baztertzen du.", + "transcoding_realtime": "Denbora errealeko transkodeketa [ESPERIMENTALA]", + "transcoding_realtime_description": "Bideoa erreproduzitzen ari den heinean transkodetzea denbora errealean egiteko aukera ematen du. Kalitate aldaketa gaitzen du, baina zerbitzariaren gaitasunen arabera erreprodukzio-latentzia eta toteltze handiagoak sor ditzake.", + "transcoding_realtime_enabled": "Gaitu denbora errealeko transkodeketa", + "transcoding_realtime_enabled_description": "Desgaituta badago, zerbitzariak uko egingo dio denbora errealeko transkodetze saio berriei.", + "transcoding_reference_frames": "Erreferentzia fotogramak", + "transcoding_reference_frames_description": "Forograma jakin bat konprimitzean erreferentzia beharreko fotograma kopurua. Balio altuagoek konpresioaren eraginkortasuna hobetzen dute, baina kodeketa moteltzen dute. 0-k balio hau automatikoki ezartzen du.", + "transcoding_required_description": "Onartutako formatu batean ez dauden bideoak bakarrik", + "transcoding_settings": "Bideo transkodetze ezarpenak", + "transcoding_settings_description": "Kudeatu zein bideo transkodetu eta nola prozesatu", + "transcoding_target_resolution": "Bereizmen helburua", + "transcoding_target_resolution_description": "Bereizmen handiagoek xehetasun gehiago gorde ditzakete, baina denbora gehiago behar dute kodetzeko, fitxategi-tamaina handiagoak sortu eta aplikazioen jariakortasuna murriztu dezake.", + "transcoding_temporal_aq": "Aldi baterako AQ-a", + "transcoding_temporal_aq_description": "NVENC-i soilik aplikatzen da. Denboraldiaren Kuantizazio Egokigarriak (AQak) xehetasun handiko eta mugimendu baxuko eszenen kalitatea areagotzen du. Baliteke gailu zaharrekin bateragarria ez izatea.", + "transcoding_threads": "Hariak", + "transcoding_threads_description": "Balio altuagoek kodeketa azkarragoa dakar, baina leku gutxiago uzten diote zerbitzariari beste zeregin batzuk prozesatzeko aktibo dagoen bitartean. Balio honek ez luke CPU-nukleoen kopurua baino handiagoa izan behar. Erabilera maximizatzen du 0 gisa ezartzen bada.", + "transcoding_tone_mapping": "Tonoen mapeoa", + "transcoding_tone_mapping_description": "SDR bihurtzean HDR bideoen itxura mantentzen saiatzen da. Algoritmo bakoitzak kolore, xehetasun eta distira konpromezu desberdinak egiten ditu. Hablek xehetasunak gordetzen ditu, Mobiusek kolorea eta Reinhardek distira.", + "transcoding_transcode_policy": "Transkodetze politika", + "transcoding_transcode_policy_description": "Bideo bat transkodetu behar den politika. YUV 4:2:0 ez den pixel formatua duten bideoak eta HDR bideoak beti transkodetuko dira (transkodetzea desgaituta badago izan ezik).", + "transcoding_two_pass_encoding": "Bi pasatako kodeketa", + "transcoding_two_pass_encoding_setting_description": "Transkodetu bi pasetan hobeto kodetutako bideoak sortzeko. Gehienezko bit-abiadura gaituta dagoenean (beharrezkoa da H.264 eta HEVC-ekin funtzionatzeko), modu honek bit-tasa maximoan oinarritutako bit-abiadura-tarte bat erabiltzen du eta CRF-a ez du aintzat hartzen. VP9rako, CRF erabil daiteke bit-tasa maximoa desgaituta badago.", + "transcoding_video_codec": "Bideo-kodekak", + "transcoding_video_codec_description": "VP9-k eraginkortasun eta web bateragarritasun handia du, baina denbora gehiago behar da transkodetzeko. HEVC antzera funtzionatzen du, baina web bateragarritasun txikiagoa du. H.264 oso bateragarria da eta transkodetzeko azkarra da, baina askoz fitxategi handiagoak sortzen ditu. AV1 kodeka eraginkorrena da, baina ez dira onartzen gailu zaharretan.", + "trash_enabled_description": "Zakarrontzia gaitu", + "trash_number_of_days": "Egun kopurua", + "trash_number_of_days_description": "Baliabideak zakarrontzian gorde behar diren egun kopurua behin betiko ezabatu aurretik", + "trash_settings": "Zakarrontziaren ezarpenak", + "trash_settings_description": "Kudeatu zakarrontziaren ezarpenak", + "unlink_all_oauth_accounts": "OAuth kontu guztietatik saioa itxi", + "unlink_all_oauth_accounts_description": "Gogoratu hornitzaile berri batera migratu aurretik OAuth-eko kontu guztietatik saioa ixtea.", + "unlink_all_oauth_accounts_prompt": "Ziur OAuth kontu guztiak deskonektatu nahi dituzula? Honek erabiltzaile bakoitzaren OAuth IDa berrezarriko du eta ezin da desegin.", + "user_cleanup_job": "Erabiltzaile garbiketa", + "user_delete_delay": "{user} erabiltzailearen kontua eta baliabideak {delay, plural, one {egun bat} other {# egun}} barru behin betiko ezabatzeko programatuko dira.", + "user_delete_delay_settings": "Atzerapena ezabatu", + "user_delete_delay_settings_description": "Erabiltzaile baten kontua eta baliabideak behin betiko ezabatzeko egun kopurua. Erabiltzaileak ezabatzeko ataza gauerdian abiarazten da ezabatzeko prest dauden erabiltzaileak egiaztatzeko. Ezarpen honen aldaketak hurrengo exekuzioan ebaluatuko dira.", + "user_delete_immediately": "{user}-(r)en kontua eta baliabideak behin betiko ezabatzeko ilaran jarriko dira berehala.", + "user_delete_immediately_checkbox": "Erabiltzailea eta baliabideak berehala ezabatzeko ilaran jarri", + "user_details": "Erabiltzailearen xehetasunak", + "user_management": "Erabiltzaileen kudeaketa", + "user_password_has_been_reset": "Erabiltzailearen pasahitza eguneratu da:", + "user_password_reset_description": "Mesedez, ezarri aldi baterako pasahitza erabiltzaileari eta jakinarazi hurrengo saioa hasteko pasahitza aldatu beharko duela.", + "user_restore_description": "{user} erabiltzailearen kontua leheneratu egingo da.", + "user_restore_scheduled_removal": "Berrezarri erabiltzailea - programatutako kentzea {date, date, long} egunean", + "user_settings": "Erabiltzailearen ezarpenak", + "user_settings_description": "Erabiltzailearen ezarpenak kudeatu", + "user_successfully_removed": "{email} erabiltzailea behar bezala ezabatu da.", + "users_page_description": "Administrari erabiltzaileen orria", + "version_check_channel": "Argitalpen kanala", + "version_check_channel_description": "Aukeratu bertsio-iragarpenak jaso nahi dituzun bertsio-kanalak", + "version_check_enabled_description": "Bertsio baliozkotzea gaitu", + "version_check_implications": "Bertsio baliozkotzea {server}-(a)rekin aldizkako komunikazioaren beharra du", + "version_check_settings": "Bertsio baliozkotzea", + "version_check_settings_description": "Bertsio berriaren jakinarazpena gaitu/desgaitu", + "video_conversion_job": "Bideoak transkodeatu", + "video_conversion_job_description": "Bideoak arakatzaile eta gailuekin bateragarritasun handiagoa lortzeko transkodetu" }, + "admin_email": "Administrariaren e-maila", + "admin_password": "Administrari pasahitza", + "administration": "Administrazioa", "advanced": "Aurreratua", + "advanced_settings_clear_image_cache": "Irudien cahcea garbitu", + "advanced_settings_clear_image_cache_error": "Ezin izan da argazkien cachea garbitu", + "advanced_settings_clear_image_cache_success": "{size} arrakastaz garbituta", + "advanced_settings_enable_alternate_media_filter_subtitle": "Erabili aukera hau sinkronizazioan baliabideak irizpide alternatiboetan oinarrituta iragazteko. Saiatu hau bakarrik aplikazioak album guztiak detektatzeko arazoak badituzu.", + "advanced_settings_enable_alternate_media_filter_title": "[ESPERIMENTALA] Erabili gailuaren albumen sinkronizazio-iragazki alternatiboa", + "advanced_settings_log_level_title": "Erregistro nibela: {level}", + "advanced_settings_prefer_remote_subtitle": "Gailu batzuk oso motelak dira tokiko aktiboetatik miniaturak sortzeko. Aktibatu ezarpen hau urruneko irudiak kargatzeko.", + "advanced_settings_prefer_remote_title": "Urruneko irudiak hobetsi", + "advanced_settings_proxy_headers_subtitle": "Definitu Immich-ek sareko eskaera bakoitzarekin bidali beharko lituzkeen proxy goiburuak", + "advanced_settings_proxy_headers_title": "Proxy goiburu pertsonalizatuak [ESPERIMENTALA]", + "advanced_settings_readonly_mode_subtitle": "Argazkiak soilik ikus daitezkeen irakurtzeko modua gaitzen du, hainbat irudi hautatzea, partekatzea, igortzea, ezabatzea bezalako gauzak desgaituta daude. Gaitu/Desgaitu irakurketa modua erabiltzailearen avatar bidez pantaila nagusitik", "advanced_settings_readonly_mode_title": "Irakurri-soilik modua", + "advanced_settings_self_signed_ssl_subtitle": "Zerbitzariaren amaierako SSL ziurtagiriaren egiaztapena saltatzen du. Norberak sinatutako ziurtagirietarako beharrezkoa da.", + "advanced_settings_self_signed_ssl_title": "Baimendu norberak sinatutako SSL ziurtagiriak [ESPERIMENTALA]", + "advanced_settings_sync_remote_deletions_subtitle": "Ezabatu edo leheneratu automatikoki gailu honetako baliabide bat ekintza hori sarean egiten denean", + "advanced_settings_sync_remote_deletions_title": "Sinkronizatu urruneko ezabaketak [ESPERIMENTALA]", + "advanced_settings_tile_subtitle": "Erabiltzailearen ezarpen aurreratuak", + "advanced_settings_troubleshooting_subtitle": "Gaitu ezaugarri gehigarriak arazoak konpontzeko", + "advanced_settings_troubleshooting_title": "Arazoak detektatzea eta konpontzea", + "age_months": "Denbora {months, plural, one {Hilabete #} other {# hilabete}}", + "age_year_months": "Denbora urte bat, {months, plural, one {hilabete #} other {# hilabete}}", + "age_years": "Adina {years, plural, one {urte #} other {# urte}}", + "album": "Album", + "album_added": "Albuma gehituta", + "album_added_notification_setting_description": "Jaso jakinarazpen elektroniko bat partekatutako album batera gehitzen zarenean", + "album_cover_updated": "Albumaren azala eguneratu da", + "album_delete_confirmation": "Ziur zaude {album} albuma ezabatu nahi duzula?", + "album_delete_confirmation_description": "Album hau partekatzen bada, beste erabiltzaileek ezin izango dute atzitu.", + "album_deleted": "Albuma ezabatuta", + "album_info_card_backup_album_excluded": "BAZTERTUTAKOAK", + "album_info_card_backup_album_included": "BARNEKOAK", + "album_info_updated": "Albumaren informazioa eguneratu da", + "album_leave": "Albuma utzi?", + "album_leave_confirmation": "Ziur zaude {album} albuma utzi nahi duzula?", + "album_name": "Albumaren izena", + "album_options": "Albumaren ezarpenak", + "album_remove_user": "Erabiltzailea kendu?", + "album_remove_user_confirmation": "Ziur zaude {user} erabiltzailea kendu nahi duzula?", + "album_search_not_found": "Ez da aurkitu zure bilaketarekin bat datorren albumik", + "album_selected": "Albuma aukeratuta", + "album_share_no_users": "Album hau erabiltzaile guztiekin partekatu duzula dirudi edo ez duzula erabiltzailerik partekatzeko.", + "album_summary": "Albumaren laburpena", + "album_updated": "Albuma eguneratu da", + "album_updated_setting_description": "Jaso e-posta bidezko jakinarazpena partekatutako album batek baliabide berriak dituenean", + "album_upload_assets": "Kargatu baliabideak ordenagailutik eta gehitu albumera", + "album_user_left": "{album}-a utzi du", + "album_user_removed": "{user} kendu da", + "album_viewer_appbar_delete_confirm": "Ziur zaude album hau zure kontutik ezabatu nahi duzula?", + "album_viewer_appbar_share_err_delete": "Ezin izan da albuma ezabatu", + "album_viewer_appbar_share_err_leave": "Ezin izan da albuma utzi", + "album_viewer_appbar_share_err_remove": "Arazoak daude albumetik baliabideak kentzean", + "album_viewer_appbar_share_err_title": "Ezin izan da albumaren izenburua aldatu", + "album_viewer_appbar_share_leave": "Albuma utzi", + "album_viewer_appbar_share_to": "Albuma partekatu", + "album_viewer_page_share_add_users": "Erabiltzaileak gehitu", + "album_with_link_access": "Utzi esteka duen edonori album honetako argazkiak eta jendea ikustea.", + "albums": "Albumak", + "albums_count": "{count, plural, one {Album bat} other {{count, number} Album}}", + "albums_default_sort_order": "Albumen ordena lehenetsia", + "albums_default_sort_order_description": "Album berriak sortzean baliabideak ordenatzeko hasierako ordena.", + "albums_feature_description": "Beste erabiltzaile batzuekin parteka daitezkeen baliabide bildumak.", + "albums_on_device_count": "Albumak gailuan ({count})", + "albums_selected": "{count, plural, one {album bat aukeratuta} other {# album aukeratuta}}", + "all": "Denak", + "all_albums": "Album guztiak", + "all_people": "Pertsona guztiak", + "all_photos": "Argazki guztiak", + "all_videos": "Bideo guztiak", + "allow_dark_mode": "Modu iluna baimendu", + "allow_edits": "Edizioa onartu", + "allow_public_user_to_download": "Erabiltzaile publikoei deskargatzea baimendu", + "allow_public_user_to_upload": "Erabiltzaile publikoei kargatzea baimendu", + "allowed": "Baimenduta", + "alt_text_qr_code": "QR-kodea", + "always_keep": "Beti mantendu", + "always_keep_photos_hint": "Biltegiratze garbitzaileak argazki guztiak mantenduko ditu gailu honetan.", + "always_keep_videos_hint": "Biltegiratze garbitzaileak bideo guztiak mantenduko ditu gailu honetan.", + "anti_clockwise": "Erloju-orratzen noranzkoaren aurka", + "api_key": "API Gakoa", + "api_key_description": "Balio hau behin bakarrik erakutsiko da. Mesedez, ziurtatu leihoa itxi aurretik kopiatu duzula.", + "api_key_empty": "Zure API gakoaren izenak ez du hutsik egon behar", + "api_keys": "API gakoak", + "app_architecture_variant": "Aldaera (Arkitektura)", + "app_bar_signout_dialog_content": "Ziur saioa amaitu nahi duzula?", + "app_bar_signout_dialog_ok": "Bai", + "app_bar_signout_dialog_title": "Saioa itxi", + "app_download_links": "Aplikazioa deskargatzeko estekak", + "app_settings": "Aplikazio ezarpenak", + "app_stores": "Aplikazio dendak", + "app_update_available": "Aplikazioaren eguneratzea eskuragarri dago", + "appears_in": "Hemen agertzen da", "apply_count": "Ezarri ({count, number})", + "archive": "Artxibo", + "archive_action_prompt": "{count} artxibategira bidalita", + "archive_or_unarchive_photo": "Argazkia artxibatu edo berrezarri", + "archive_page_no_archived_assets": "Ez da artxibatutako baliabiderik aurkitu", + "archive_page_title": "Artxibo ({count})", + "archive_size": "Artxibo tamaina", + "archive_size_description": "Konfiguratu artxiboaren tamaina deskargak egiteko (GiB-tan)", + "archived": "Artxibatua", + "archived_count": "{count, plural, one {artxibatu bat} other {# artxibatu}}", + "are_these_the_same_person": "Pertsona bera al dira hauek?", + "are_you_sure_to_do_this": "Ziur hau egin nahi duzula?", + "array_field_not_fully_supported": "Array-eremuek JSONaren eskuzko edizioa behar dute", + "asset_action_delete_err_read_only": "Ezin dira ezabatu irakurtzeko soilik diren aktiboak, saltatu egin da", + "asset_action_share_err_offline": "Ezin dira lineaz kanpoko baliabideak eskuratu. Saltatu egin dira", + "asset_added_to_album": "Albumera gehituta", + "asset_adding_to_album": "Albumera gehitzen…", + "asset_created": "Baliabidea sortuta", + "asset_day_count": "{date}: {count, plural, one {baliabide bat} other {# baliabide}}", + "asset_description_updated": "Baliabidearen deskribapena eguneratu da", + "asset_filename_is_offline": "{filename} baliabidea konexiorik gabe dago", + "asset_has_unassigned_faces": "Baliabideak esleitu gabeko aurpegiak ditu", + "asset_hashing": "Hasha kalkulatzen…", + "asset_list_group_by_sub_title": "Elkartu", + "asset_list_layout_settings_dynamic_layout_title": "Diseinu dinamikoa", + "asset_list_layout_settings_group_automatically": "Automatikoa", + "asset_list_layout_settings_group_by": "Honen bidez taldekatu", + "asset_list_layout_settings_group_by_month_day": "Hilabetea + eguna", + "asset_list_layout_sub_title": "Antolaketa", + "asset_list_settings_subtitle": "Argazki-sarearen diseinu-ezarpenak", + "asset_list_settings_title": "Irudi lauki-sarea", + "asset_not_found_on_device_android": "Baliabidea ez da gailuan aurkitu", + "asset_not_found_on_device_ios": "Ez da baliabidea aurkitu gailuan. iCloud erabiltzen ari bazara, baliteke baliabidea eskuraezin izatea iCloud-en gordetako fitxategi txarra dela eta", + "asset_not_found_on_icloud": "Ez da aurkitu baliabidea iCloud-en. baliteke baliabidea eskuraezin izatea iCloud-en gordetako fitxategi txarra dela eta", + "asset_offline": "Baliabidea lineaz kanpo", + "asset_offline_description": "Kanpoko baliabide hau jada ez da diskoan aurkitzen. Mesedez, jarri harremanetan Immich-eko administratzailearekin laguntza eskatzeko.", + "asset_restored_successfully": "Baliabidea arrakastaz berrezarria", + "asset_skipped": "Alde batera utzita", + "asset_skipped_in_trash": "Zakarrontzian", + "asset_trashed": "Baliabidea ezabatua", + "asset_troubleshoot": "Baliabidearen diagnosia", + "asset_uploaded": "Igota", + "asset_uploading": "Igotzen…", + "asset_viewer_settings_subtitle": "Kudeatu galeria-ikuslearen ezarpenak", + "asset_viewer_settings_title": "Baliabide ikuslea", + "assets": "Baliabideak", + "assets_added_count": "{count, plural, one {Baliabide bat} other {# baliabide}} gehituta", + "assets_added_to_album_count": "{count, plural, one {Baliabide bat} other {# baliabide}} albumera gehituta", "assets_added_to_albums_count": "Gehituta {assetTotal, plural, one {# asset} other {# assets}} to {albumTotal, plural, one {# album} other {# albums}}", + "assets_cannot_be_added_to_album_count": "{count, plural, one {Baliabidea ezin izan da albumera gehitu} other {Baliabideak ezin izan dira albumera gehitu}}", "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} ezin izan da albumetara gehitu", - "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} dagoeneko albumean dago", + "assets_count": "{count, plural, one {Baliabide bat} other {# baliabide}}", + "assets_deleted_permanently": "{count, plural, one {Baliabidea behin-betiko ezabatu da} other {# baliabide behin-betiko ezabatu dira}}", + "assets_deleted_permanently_from_server": "{count, plural, one {Baliabide bat} other {# baliabide}} Immich zerbitzaritik ezabatu dira", + "assets_downloaded_failed": "{count, plural, one {Deskargatutako fitxategia - {error} okerra} other {# deskargatutako fitxategiak - {error} oker}}", + "assets_downloaded_successfully": "{count, plural, one {Fitxategia arrakastaz deskargatu da} other {# fitxategi arrakastaz deskargatu dira}}", + "assets_moved_to_trash_count": "{count, plural, one {Baliabidea zakarrontzira eraman da} other {# baliabide zakarrontzira eraman dira}}", + "assets_permanently_deleted_count": "{count, plural, one {Baliabidea behin-betiko ezabatu da} other {# baliabide behin-betiko ezabatu dira}}", + "assets_removed_count": "{count, plural, one {Baliabidea ezabatu da} other {# baliabide ezabatu dira}}", + "assets_removed_permanently_from_device": "{count, plural, one {Baliabidea behin-betiko ezabatu da} other {# baliabide behin-betiko ezabatu dira}} zure gailutik", + "assets_restore_confirmation": "Ziur zaude zaborrontziko baliabide guztiak leheneratu nahi dituzula? Ezin duzu ekintza hau desegin! Kontuan izan konexiorik gabeko baliabideak ezin direla modu honetan berrezarri.", + "assets_restored_count": "{count, plural, one {Baliabidea berrezarri da} other {# baliabide berrezarri dira}}", + "assets_restored_successfully": "{count, plural, one {Baliabidea arrakastaz berrezarri da} other {# baliabide arrakastaz berrezarri dira}}", + "assets_trashed": "{count, plural, one {Baliabidea zakarrontzira eraman da} other {# baliabide zakarrontzira eraman dira}}", + "assets_trashed_count": "{count, plural, one {Baliabidea zakarrontzira eraman da} other {# baliabide zakarrontzira eraman dira}}", + "assets_trashed_from_server": "{count, plural, one {Baliabidea Immich zerbitzaritik zakarrontzira eraman da} other {# baliabide Immich zerbitzaritik zakarrontzira eraman dira}}", + "assets_were_part_of_album_count": "{count, plural, one {Baliabidea iada albumaren parte da} other {Baliabideak iada albumaren parte dira}}", + "assets_were_part_of_albums_count": "{count, plural, one {Baliabidea iada albumen parte da} other {Baliabideak iada albumrn parte dira}}", + "authorized_devices": "Baimendutako gailuak", + "automatic_endpoint_switching_subtitle": "Konektatu lokalean izendatutako Wi-Fi bidez erabilgarri dagoenean eta erabili beste nonbait konexio alternatiboak", + "automatic_endpoint_switching_title": "URL aldaketa automatikoa", + "autoplay_slideshow": "Erreproduzitu automatikoki diapositiba-aurkezpena", + "back": "Atzera", + "back_close_deselect": "Atzera, itxi edo desautatu", + "background_backup_running_error": "Atzeko planoko segurtasun-kopia exekutatzen ari da, ezin da eskuzko segurtasun-kopia hasi", + "background_location_permission": "Atzeko planoko kokapenaren baimena", + "background_location_permission_content": "Atzeko planoan exekutatzen denean sarez aldatzeko, Immichek *beti* izan behar du kokapen zehatzaren sarbidea, aplikazioak Wi-Fi sarearen izena irakur dezan", + "background_options": "Atzeko planoko aukerak", + "backup": "Babes-kopia", + "backup_album_selection_page_albums_device": "Albumak gailuan ({count})", + "backup_album_selection_page_albums_tap": "Sakatu sartzeko, sakatu birritan baztertzeko", + "backup_album_selection_page_assets_scatter": "Baliabideak hainbat albumetan barreiatu daitezke. Horrela, albumak babeskopia prozesuan sartu edo baztertu daitezke.", + "backup_album_selection_page_select_albums": "Albumak aukeratu", + "backup_album_selection_page_selection_info": "Aukeraren informazioa", + "backup_album_selection_page_total_assets": "Baliabide desberdinak guztira", + "backup_albums_sync": "Segurtasun-kopia albumen sinkronizazioa", + "backup_all": "Denak", + "backup_background_service_backup_failed_message": "Ezin izan da baliabideen segurtasun-kopia sortu. Berriro saiatzen…", + "backup_background_service_complete_notification": "Baliabideen babes-kopia arrakastaz osatua", + "backup_background_service_connection_failed_message": "Ezin izan da zerbitzarira konektatu. Berriro saiatzen…", + "backup_background_service_current_upload_notification": "{filename} igotzen", + "backup_background_service_default_notification": "Baliabide berririk dauden egiaztatzen…", + "backup_background_service_error_title": "Akatsa babes-kopia egiterakoan", + "backup_background_service_in_progress_notification": "Zure baliabideen segurtasun-kopia sortzen…", + "backup_background_service_upload_failure_notification": "Ezin izan da {filename} igo", + "backup_controller_page_albums": "Seguratsun-kopia albumak", + "backup_controller_page_background_app_refresh_disabled_content": "Gaitu atzeko planoko aplikazioa freskatzea Ezarpenak > Orokorra > Atzeko planoko aplikazioa freskatzea atalean, atzeko planoko babeskopiak erabiltzeko.", + "backup_controller_page_background_app_refresh_disabled_title": "Atzeko planoko aplikazioa freskatzea desgaituta dago", + "backup_controller_page_background_app_refresh_enable_button_text": "Joan ezarpenetara", + "backup_controller_page_background_battery_info_link": "Erakutsi nazazu nola", + "backup_controller_page_background_battery_info_message": "Atzeko planoko babeskopien esperientzia onena lortzeko, desgaitu Immich-en atzeko planoko jarduera murrizten duten bateriaren optimizazio guztiak.\n\nGailuari espezifikoa denez, mesedez bilatu behar den informazioa zure gailuaren fabrikatzailearentzat.", + "backup_controller_page_background_battery_info_ok": "Onartu", + "backup_controller_page_background_battery_info_title": "Bateria optimizazioak", + "backup_controller_page_background_charging": "Kargatzean bakarrik", + "backup_controller_page_background_configure_error": "Ezin izan da konfiguratu atzeko planoko zerbitzua", + "backup_controller_page_background_delay": "Atzeratu baliabide berrien babeskopia: {duration}", + "backup_controller_page_background_description": "Aktibatu atzeko planoko zerbitzua edozein baliabide berren segurtasun-kopia automatikoki egiteko, aplikazioa ireki beharrik gabe", + "backup_controller_page_background_is_off": "Atzeko planoko segurtasun-kopia automatikoa desaktibatuta dago", + "backup_controller_page_background_is_on": "Atzeko planoko segurtasun-kopia automatikoa aktibatuta dago", + "backup_controller_page_background_turn_off": "Desaktibatu atzeko planoko zerbitzua", + "backup_controller_page_background_turn_on": "Aktibatu atzeko planoko zerbitzua", + "backup_controller_page_background_wifi": "Wi-Fi bidez bakarrik", + "backup_controller_page_backup": "Babes-kopia", + "backup_controller_page_backup_selected": "Aukeratutakoa: ", + "backup_controller_page_backup_sub": "Argazkien eta bideoen segurtasun-kopiak egin dira", + "backup_controller_page_created": "Sortuta: {date}", + "backup_controller_page_desc_backup": "Aktibatu lehen planoko babeskopiak aplikazioa irekitzean baliabide berriak zerbitzarira automatikoki igotzeko.", + "backup_controller_page_excluded": "Baztertutakoa: ", + "backup_controller_page_failed": "Akatsak ({count})", + "backup_controller_page_filename": "Fitxategia: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Segurtasun-kopiaren informazioa", + "backup_controller_page_none_selected": "Aukerarik ez", + "backup_controller_page_remainder": "Gainerakoak", + "backup_controller_page_remainder_sub": "Hautapenetik babeskopiak egiteko gainerako argazki eta bideoak", + "backup_controller_page_server_storage": "Zerbitzariko memoria", + "backup_controller_page_start_backup": "Segurtasun-kopia hasi", + "backup_controller_page_status_off": "Lehen planoko babeskopia automatikoa desaktibatuta dago", + "backup_controller_page_status_on": "Lehen planoko babeskopia automatikoa aktibatuta dago", + "backup_controller_page_storage_format": "{total}-tik {used} erabilita", + "backup_controller_page_to_backup": "Segurtasun-kopia egiteko albumak", + "backup_controller_page_total_sub": "Hautatutako albumetako argazki eta bideo guztiak", + "backup_controller_page_turn_off": "Desaktibatu lehen planoko babeskopiak", + "backup_controller_page_turn_on": "Aktibatu lehen planoko babeskopiak", + "backup_controller_page_uploading_file_info": "Fitxategiaren informazioa kargatzen", + "backup_err_only_album": "Ezin da album bakarra ezabatu", + "backup_error_sync_failed": "Sinkronizazioak huts egin du. Ezin da segurtasun-kopia prozesatu.", + "backup_info_card_assets": "baliabide", + "backup_manual_cancelled": "Ezeztatuta", + "backup_manual_in_progress": "Igoera iada martxan dago. Saiatu berriro", + "backup_manual_success": "Arrakastatsua", + "backup_manual_title": "Igoera egoera", + "backup_options": "Segurtasun-kopien ezarpenak", + "backup_options_page_title": "Babes-kopia ezarpenak", + "backup_setting_subtitle": "Kudeatu atzeko planoko eta lehen planoko kargatzeko ezarpenak", + "backup_settings_subtitle": "Kudeatu karga-ezarpenak", + "backup_upload_details_page_more_details": "Sakatu xehetasun gehiago lortzeko", + "backward": "Atzeruntz", + "battery_optimization_backup_reliability": "Bateriaren optimizazioak desgaitzeak atzeko planoko babeskopien fidagarritasuna hobetu dezake", + "biometric_auth_enabled": "Autentifikazio biometrikoa gaituta", + "biometric_locked_out": "Autentifikazio biometrikotik kanpo geratu zara", + "biometric_no_options": "Ez dago aukera biometrikorik eskuragarri", + "biometric_not_available": "Gailu honetan ez dago erabilgarri autentifikazio biometrikoa", + "birthdate_saved": "Jaiotze-data ongi gorde da", + "birthdate_set_description": "Jaiotze-data argazki baten unean pertsona horren adina kalkulatzeko erabiltzen da.", + "blurred_background": "Atzeko plano lausoa", + "browse_templates": "Arakatu txantiloiak", + "bugs_and_feature_requests": "Akatsak eta ezaugarri-eskaerak", + "build": "Bertsioa", + "build_image": "Konpilazio irudia", + "bulk_delete_duplicates_confirmation": "Ziur zaude {count, plural, one {bikoiztutako baliabide bat ezabatu nahi duzula} other {bikoiztutako # baliabide ezabatu nahi dituzula}}? Honek talde bakoitzaren baliabiderik handiena gordeko du eta betiko ezabatuko ditu gainerako bikoiztuak. Ezin duzu ekintza hau desegin!", + "bulk_keep_duplicates_confirmation": "Ziur zaude {count, plural, one {bikoiztutako baliabide bat mantendu nahi duzula} other {bikoiztutako # baliabide mantendu nahi dituzula}}? Horrek bikoiztutako talde guztiak konponduko ditu ezer ezabatu gabe.", + "bulk_trash_duplicates_confirmation": "Ziur zaude {count, plural, one {bikoiztutako baliabide bat zakarrontzira bildu nahi duzula} other {bikoiztutako # baliabide zakarrontzira bildu nahi dituzula}}? Honek talde bakoitzaren aktibo handiena gordeko du eta gainerako bikoiztu guztiak zakarrontzira eramango ditu.", + "buy": "Immich erosi", + "cache_settings_clear_cache_button": "Cache-memoria garbitu", + "cache_settings_clear_cache_button_title": "Aplikazioaren cachea garbitzen du. Horrek eragin handia izango du aplikazioaren errendimenduan, cachea berreraiki arte.", + "cache_settings_duplicated_assets_clear_button": "GARBITU", + "cache_settings_duplicated_assets_subtitle": "Aplikazioak aintzat hartzen ez dituen argazkiak eta bideoak", + "cache_settings_duplicated_assets_title": "Bikoiztutako baliabideak ({count})", + "cache_settings_statistics_album": "Liburutegiaren miniaturak", + "cache_settings_statistics_full": "Tamainu osoko irudiak", + "cache_settings_statistics_shared": "Partekatutako albumen miniaturak", + "cache_settings_statistics_thumbnail": "Miniatura", + "cache_settings_statistics_title": "Cache-memoria erabilera", + "cache_settings_subtitle": "Kontrolatu Immich mugikorreko aplikazioaren cachearen portaera", + "cache_settings_tile_subtitle": "Kontrolatu tokiko biltegiratze portaera", + "cache_settings_tile_title": "Memoria lokala", + "cache_settings_title": "Cache-memoria ezarpenak", + "camera": "Kamera", + "camera_brand": "Kamera fabrikatzailea", + "camera_model": "Kamera modeloa", + "cancel": "Ezeztatu", + "cancel_search": "Bilaketa ezeztatu", + "canceled": "Ezeztatua", + "canceling": "Desgaitzen", + "cannot_merge_people": "Ezin izan dira pertsonak batu", + "cannot_undo_this_action": "Ezin duzu ekintza hau desegin!", + "cannot_update_the_description": "Ezin izan da deskribapena eguneratu", + "cast": "Transmititu", + "cast_description": "Konfiguratu eskuragarri dauden igorpen-helmugak", + "change": "Aldatu", + "change_date": "Data aldatu", + "change_description": "Deskribapena aldatu", + "change_display_order": "Bistaratzeko ordena aldatu", + "change_expiration_time": "Iraungitze denbora aldatu", + "change_location": "Kokapena aldatu", + "change_name": "Izena eguneratu", + "change_name_successfully": "Izena arrakastaz eguneratuta", + "change_password": "Pasahitza eguneratu", + "change_password_description": "Hau da sisteman saioa hasten duzun lehen aldia edo pasahitza aldatzeko eskaera egin den. Mesedez, sartu pasahitz berria behean.", + "change_password_form_confirm_password": "Pasahitza baieztu", + "change_password_form_description": "Kaixo {name},\n\nHau da sisteman saioa hasten duzun lehen aldia edo pasahitza aldatzeko eskaera egin den. Mesedez, sartu pasahitz berria behean.", + "change_password_form_log_out": "Amaitu saioa gainerako gailu guztietan", + "change_password_form_log_out_description": "Beste gailu guztietan saioa amaitzea gomendatzen da", + "change_password_form_new_password": "Pasahitza berria", + "change_password_form_password_mismatch": "Pasahitzak ez datoz bat", + "change_password_form_reenter_new_password": "Sartu berriro pasahitz berria", + "change_pin_code": "PIN kodea eguneratu", + "change_trigger": "Abiarazlea aldatu", + "change_trigger_prompt": "Ziur abiarazlea aldatu nahi duzula? Horrek lehendik dauden ekintza eta iragazki guztiak kenduko ditu.", + "change_your_password": "Eguneratu zure pasahitza", + "changed_visibility_successfully": "Ikusgarritasuna arrakastaz aldatuta", + "charging": "Kargatzen", + "charging_requirement_mobile_backup": "Atzeko planoko segurtasun-kopia sortzeko gailua kargatzen egon behar du", + "check_corrupt_asset_backup": "Egiaztatu hondatuta dauden baliabideen segurtasun-kopiak", + "check_corrupt_asset_backup_button": "Egiaztapena burutu", + "check_corrupt_asset_backup_description": "Exekutatu egiaztapen hau Wi-Fi bidez soilik eta baliabide guztien segurtasun-kopia egin ondoren. Prozedurak minutu batzuk iraun ditzake.", + "check_logs": "Erregistroak egiaztatu", + "checksum": "Egiaztapeneko batura", + "choose": "Aukeratu", + "choose_matching_people_to_merge": "Aukeratu pertsona beraren agerraldi bikoiztuak bateratzeko", + "city": "Hiria", + "cleanup_confirm_description": "Immich-ek zerbitzarian kargatuta {date} baino lehenago sortutako {count, plural, one {baliabide bat aurkitu du} other {# baliabide aurkitu ditu}}. Gailu honetatik tokiko kopiak kendu nahi dituzu?", + "cleanup_confirm_prompt_title": "Gailu honetatik kendu?", + "cleanup_deleted_assets": "{count, plural, one {baliabide bat eraman da} other {# baliabide eraman dira}} gailuaren zakarrontzira", + "cleanup_deleting": "Zakarrontzira eramaten...", + "cleanup_found_assets": "Babeskopia egin {count, plural, one {duen baliabide bat aurkitu da} other {duten # baliabide aurkitu dira}}", + "cleanup_found_assets_with_size": "Babeskopia egin {count, plural, one {duen baliabide bat aurkitu da} other {duten # baliabide aurkitu dira}} ({size})", + "cleanup_icloud_shared_albums_excluded": "iCloudeko partekatutako albumak eskaneotik kanpo geratzen dira", + "cleanup_no_assets_found": "Ez da aurkitu goiko irizpideekin bat datorren baliabiderik. Biltegiratze garbitzaileak zerbitzarian babeskopia duten baliabideak soilik kendu ditzake", + "cleanup_preview_title": "Kendu beharreko aktiboak ({count})", + "cleanup_step3_description": "Bilatu zure datarekin bat datozen babeskopiak dituzten baliabideak eta gorde ezarpenak.", + "cleanup_step4_summary": "{date} baino lehenagoko {count, plural, one {baliabide bat} other {# baliabide}} zure gailutik kentzeko. Argazkiak Immich aplikaziotik eskuragarri egongo dira.", + "cleanup_trash_hint": "Biltegiratzeko lekua guztiz berreskuratzeko, ireki sistemaren galeria aplikazioa eta hustu zakarrontzia", + "clear": "Garbitu", + "clear_all": "Denak garbitu", + "clear_all_recent_searches": "Garbitu azken bilaketa guztiak", + "clear_failed_count": "{count, plural, one {Garbitetak huts egin du} other {# garbiketek huts egin dute}}", + "clear_file_cache": "Garbitu fitxategien cachea", + "clear_message": "Mezua garbitu", + "clear_value": "Balioa garbitu", + "client_cert_dialog_msg_confirm": "Ok", + "client_cert_enter_password": "Pasahitza sartu", + "client_cert_import": "Inportatu", + "client_cert_import_success_msg": "Bezeroaren ziurtagiria inportatu da", + "client_cert_invalid_msg": "Ziurtagiri fitxategi baliogabea edo pasahitz okerra", + "client_cert_password_message": "Sartu ziurtagiri honen pasahitza", + "client_cert_password_title": "Ziurtagiriaren pasahitza", + "client_cert_remove_msg": "Bezeroaren ziurtagiria kendu da", + "client_cert_subtitle": "PKCS12 (.p12, .pfx) formatua soilik onartzen du. Ziurtagiriaren inportazioa/kentzea saioa hasi aurretik bakarrik dago erabilgarri", + "client_cert_title": "SSL bezero-ziurtagiria [ESPERIMENTALA]", + "clockwise": "Erloju-orratzen noranzkoan", + "close": "Itxi", + "collapse": "Taldekatu", + "collapse_all": "Dena tolestu", + "color": "Kolorea", + "color_theme": "Koloreen gaia", + "command": "Agindua", + "command_palette_prompt": "Orriak, ekintzak edo komandoak azkar aurkitu", + "command_palette_to_close": "ixteko", + "command_palette_to_navigate": "sartzeko", + "command_palette_to_select": "hautatzeko", + "command_palette_to_show_all": "dena erakusteko", + "comment_deleted": "Iruzkina ezabatuta", + "comment_options": "Iruzkinaren aukerak", + "comments_and_likes": "Iruzkinak eta gustokoak", + "comments_are_disabled": "Iruzkinak desgaituta daude", + "common_create_new_album": "Album berria sortu", + "completed": "Burututa", + "configuration": "Konfigurazioa", + "confirm": "Onartu", + "confirm_admin_password": "Administrari pasahitza baieztatu", + "confirm_delete_face": "Ziur zaude {name}-(r)en aurpegia baliabidetik kendu nahi duzula?", + "confirm_delete_shared_link": "Ziur zaude partekatutako esteka hau ezabatu nahi duzula?", + "confirm_keep_this_delete_others": "Pilako gainerako baliabide guztiak kenduko dira baliabide hau izan ezik. Ziur zaude jarraitu nahi duzula?", + "confirm_new_pin_code": "PIK gako berria baieztatu", + "confirm_password": "Pasahitza baieztatu", + "confirm_tag_face": "Aurpegi hau {name} gisa etiketatu nahi duzu?", + "confirm_tag_face_unnamed": "Aurpegi hau etiketatu nahi duzu?", + "connected_device": "Konektatutako gailua", + "connected_to": "Hona konektatuta", + "contain": "Egokitu", + "context": "Kontextua", + "continue": "Jarraitu", + "control_bottom_app_bar_add_tags": "Etiketak gehitu", + "control_bottom_app_bar_create_new_album": "Album berria sortu", + "control_bottom_app_bar_delete_from_immich": "Immich-etik ezabatu", + "control_bottom_app_bar_delete_from_local": "Gailutik ezabatu", + "control_bottom_app_bar_edit_location": "Kokapena eguneratu", + "control_bottom_app_bar_edit_time": "Data eta denbora eguneratu", + "control_bottom_app_bar_share_link": "Esteka partekatu", + "control_bottom_app_bar_share_to": "Partekatu honekin", + "control_bottom_app_bar_trash_from_immich": "Zakarrontzira bidali", + "copied_image_to_clipboard": "Irudia arbelean kopiatu da .", + "copied_to_clipboard": "Kopiatu da arbelean!", + "copy_error": "Kopiatzerakoan errorea", + "copy_file_path": "Fitxategi bidea kopiatu", + "copy_image": "Irudia kopiatu", + "copy_json": "JSON kopiatu", + "copy_link": "Esteka kopiatu", + "copy_link_to_clipboard": "Esteka arbelera kopiatu", + "copy_password": "Pasahitza kopiatu", + "copy_to_clipboard": "Arbelera kopiatu", + "country": "Herrialdea", + "cover": "Portada", + "covers": "Portada", + "create": "Sortu", + "create_album": "Albuma sortu", + "create_album_page_untitled": "Izengabea", + "create_api_key": "API Gakoa sortu", + "create_first_workflow": "Lehenengo lan-fluxua sortu", + "create_library": "Liburutegia sortu", + "create_link": "Esteka sortu", + "create_link_to_share": "Partekatzeko esteka sortu", + "create_link_to_share_description": "Utzi esteka duen edonori aukeratutako argazkiak ikusten", + "create_new": "BERRIA SORTU", + "create_new_face": "Aurpegi berria sortu", + "create_new_person": "Pertsona berria sortu", + "create_new_person_hint": "Esleitu hautatutako baliabideak pertsona berri bati", + "create_new_user": "Sortu erabiltzaile berria", + "create_person": "Pertsona sortu", + "create_person_subtitle": "Gehitu izen bat hautatutako aurpegiari pertsona berria sortzeko eta etiketatzeko", + "create_shared_album_page_share_add_assets": "BALIABIDEAK GEHITU", + "create_shared_album_page_share_select_photos": "Argazkiak aukeratu", + "create_shared_link": "Sortu partekatutako esteka", + "create_tag": "Sortu etiketa", + "create_tag_description": "Sortu etiketa berri bat. Habiaratutako etiketetarako, idatzi etiketaren bide osoa, barrak barne.", + "create_user": "Sortu erabiltzailea", + "create_workflow": "Sortu lan-fluxua", + "created": "Sortuta", + "created_at": "Sortze-data", + "creating_linked_albums": "Lotutako albumak sortzen...", + "crop": "Ebaki", + "crop_aspect_ratio_fixed": "Finkoa", + "crop_aspect_ratio_free": "Askea", + "crop_aspect_ratio_original": "Originala", + "crop_aspect_ratio_square": "Laukia", + "curated_object_page_title": "Objektuak", + "current_device": "Gailu hau", + "current_pin_code": "Egungo PIN kodea", + "current_server_address": "Egungo zerbitzariaren helbidea", + "custom_date": "Data pertsonalizatua", + "custom_locale": "Lokalizazio pertsonalizatua", + "custom_locale_description": "Formateatu datak, orduak eta zenbakiak hautatutako hizkuntza eta eskualdearen arabera", + "custom_url": "URL pertsonalizatua", + "cutoff_date_description": "Mantendu epe honetan sartzen diren argazkiak…", + "cutoff_day": "{count, plural, one {egun} other {egun}}", + "cutoff_year": "{count, plural, one {urte} other {urte}}", + "dark": "Iluna", + "dark_theme": "Gai iluna gaitu", + "date": "Data", + "date_after": "Data hau baino berriago", + "date_and_time": "Data eta denbora", + "date_before": "Data hau baino zaharrago", + "date_of_birth": "Jaiotze-data", + "date_of_birth_saved": "Jaiotze-data arrakastaz gordeta", + "date_range": "Data tartea", + "date_time_original": "Data/Debora Originala", + "day": "Eguna", + "days": "Egunak", + "deduplicate_all": "Denak desbikoiztu", + "default_locale": "Lokalizazio lehentsia", + "default_locale_description": "Formateatu datak eta zenbakiak zure arakatzailearen lokalizazioan oinarrituta", + "default_quality_subtitle": "Partekatzerakoan erabiltzen den kalitatea. Luze sakatu partekatzeko botoia aldi bakoitzean aukeratzeko.", + "default_share_quality": "Partekatze-kalitate lehenetsia", + "delete": "Ezabatu", + "delete_action_confirmation_message": "Ziur zaude baliabide hau ezabatu nahi duzula? Ekintza honek baliabidea zerbitzariaren zakarrontzira eramango du eta lokalean ezabatu nahi duzun galdetuko dizu", + "delete_action_prompt": "{count} ezabatuta", + "delete_album": "Albuma ezabtu", + "delete_api_key_prompt": "Ziur zaude API gako hau ezabatu nahi duzula?", + "delete_dialog_alert": "Elementu hauek betiko ezabatuko dira Immich-etik eta zure gailutik", + "delete_dialog_alert_local": "Elementu hauek betiko kenduko dira zure gailutik, baina oraindik eskuragarri egongo dira Immich zerbitzarian", + "delete_dialog_alert_local_non_backed_up": "Elementu batzuen babeskopia ez dago Immich-en eta betiko kenduko dira zure gailutik", + "delete_dialog_alert_remote": "Elementu hauek betiko ezabatuko dira Immich zerbitzaritik", + "delete_dialog_ok_force": "Hala ere ezabatu", + "delete_dialog_title": "Behin betiko ezabatu", + "delete_duplicates_confirmation": "Ziur zaude bikoiztu hauek behin betiko ezabatu nahi dituzula?", + "delete_face": "Aurpegia ezabatu", + "delete_key": "Gakoa ezabatu", + "delete_library": "Liburutegia ezabatu", + "delete_link": "Esteka ezabatu", + "delete_local_action_prompt": "{count} gailutik ezabatuta", + "delete_local_dialog_ok_backed_up_only": "Ezabatu soilik segurtasun kopia duten baliabideak", + "delete_local_dialog_ok_force": "Hala ere ezabatu", + "delete_others": "Besteak ezabatu", + "delete_permanently": "Behin betiko ezabatu", + "delete_permanently_action_prompt": "{count} behin betiko ezabatuta", + "delete_shared_link": "Partekatutako esteka ezabatu", + "delete_shared_link_dialog_title": "Partekatutako esteka ezabatu", + "delete_tag": "Etiketa ezabatu", + "delete_tag_confirmation_prompt": "Ziur zaude {tagName} etiketa ezabatu nahi duzula?", + "delete_user": "Erabiltzailea ezabatu", + "deleted_shared_link": "Partekatutako esteka ezabatuta", + "deletes_missing_assets": "Ezabatu disko gogorrean falta diren baliabideak", + "description": "Deskribapena", + "description_input_hint_text": "Deskribapena ezarri…", + "description_input_submit_error": "Errore bat gertatu da deskribapena eguneratzean, begiratu xehetasunak ikusteko", + "deselect_all": "Deshautatu guztiak", + "details": "Xehetasunak", + "direction": "Norabidea", + "disable": "Desgaitu", + "disabled": "Desgaituta", + "disallow_edits": "Ez baimendu aldaketak", + "discord": "Discord", + "discover": "Aurkitu", + "discovered_devices": "Aurkitutako gailuak", + "dismiss_all_errors": "Errore guztiak baztertu", + "dismiss_error": "Errorea baztertu", + "display_options": "Bistaratzeko aukerak", + "display_order": "Bistaratzeko ordena", + "display_original_photos": "Erakutsi jatorrizko argazkiak", + "display_original_photos_setting_description": "Irudi bat ikustean, jatorrizko argazkia hobesten da miniaturaren ordez, jatorrizkoa web bateragarria bada. Baliteke argazkiak kargatzeko denbora gehiago behar izatea.", + "do_not_show_again": "Ez erakutsi mezu hau berriz", + "documentation": "Dokumentazioa", + "done": "Amaitu", + "download": "Deskargatu", + "download_action_prompt": "{count, plural, one {Baliabide bat} other {# baliabide}} deskargatzen", + "download_canceled": "Deskarga ezeztatuta", + "download_complete": "Deskarga burututa", + "download_enqueue": "Deskarga ilaran", + "download_error": "Akatsa deskargatzerakoan", + "download_failed": "Akatsa deskargatzerakoan", + "download_finished": "Deskarga burututa", + "download_include_embedded_motion_videos": "Txertaturiko bideoak", + "download_include_embedded_motion_videos_description": "Sartu argazki bizidunetan txertatutako bideoak fitxategi bereizi gisa", + "download_notfound": "Ez da deskarga aurkitu", + "download_original": "Jatorrizkoa deskargatu", + "download_paused": "Deskarga geldituta", + "download_settings": "Deskargak", + "download_settings_description": "Kudeatu baliabideak deskargatzearekin lotutako ezarpenak", + "download_started": "Deskarga hasieratua", + "download_sucess": "Deskarga arrakastatsua", + "download_sucess_android": "Multimedia DCIM/Immich-era deskargatu da", + "download_waiting_to_retry": "Berriro saiatzeko zain", + "downloading": "Deskargatzen", + "downloading_asset_filename": "{filename} baliabidea deskargatzen", + "downloading_from_icloud": "iCloud-etik deskargatzen", + "downloading_media": "Baliabideak deskargatzen", + "drag_to_reorder": "Arrastatu berrantolatzeko", + "drop_files_to_upload": "Jaregin fitxategiak edonon kargatzeko", + "duplicate": "Kopia egin", + "duplicate_workflow": "Lan-fluxuaren kopia sortu", + "duplicates": "Kopiak", + "duplicates_description": "Ebatzi talde bakoitza, bikoiztuak dauden adieraziz.", + "duration": "Iraupena", + "edit": "Editatu", + "edit_album": "Albuma editatu", + "edit_avatar": "Abatarra editatu", + "edit_birthday": "Urtebetetze-eguna editatu", + "edit_date": "Data editatu", + "edit_date_and_time": "Data eta ordua editatu", + "edit_date_and_time_action_prompt": "{count, plural, one {Datu eta ordua eguneratu da} other {# datu eta ordu eguneratu dira}}", + "edit_date_and_time_by_offset": "Aldatu data desplazamendu bat erabiliz", + "edit_date_and_time_by_offset_interval": "Data-tarte berria: {from} - {to}", + "edit_description": "Deskribapena editatu", + "edit_description_prompt": "Mesedez aukeratu deskribapen berri bat:", + "edit_exclusion_pattern": "Editatu bazterketa eredua", + "edit_faces": "Aurpegiak kudeatu", + "edit_key": "Gakoa editatu", + "edit_link": "Esteka editatu", + "edit_location": "Kokapena editatu", + "edit_location_action_prompt": "{count, plural, one {Kokapena eguneratuta} other {# kokapen eguneratuta}}", + "edit_location_dialog_title": "Kokapena", + "edit_name": "Izena editatu", + "edit_people": "Pertsonak editatu", + "edit_tag": "Etiketa editatu", + "edit_title": "Izenburua editatu", + "edit_user": "Erabiltzailea editatu", + "edit_workflow": "Lan-fluxua editatu", + "editor": "Editorea", + "editor_close_without_save_prompt": "Aldaketak ez dira gordeko", + "editor_close_without_save_title": "Editorea itxi?", + "editor_confirm_reset_all_changes": "Ziur zaude aldaketa guztiak berrezarri nahi dituzula?", + "editor_discard_edits_confirm": "Aldaketak baztertu", + "editor_discard_edits_prompt": "Gorde gabeko aldaketak dituzu. Ziur baztertu nahi dituzula?", + "editor_discard_edits_title": "Aldaketak baztertu?", + "editor_edits_applied_error": "Ezin izan dira aldaketak aplikatu", + "editor_edits_applied_success": "Aldaketak arrakastaz aplikatuak", + "editor_flip_horizontal": "Horizontalki islatu", + "editor_flip_vertical": "Bertikalki islatu", + "editor_handle_corner": "{corner, select, top_left {Goiko ezkerreko} top_right {Goiko eskuineko} bottom_left {Beheko ezkerreko} bottom_right {Beheko eskuineko} other {Izkinako} } kontrolatzailea", + "editor_handle_edge": "{edge, select, top {Goiko} bottom {Beheko} left {Ezkerreko} right {Eskuineko} other {Ertzeko} } kontrolatzailea", + "editor_orientation": "Orientazioa", + "editor_reset_all_changes": "Aldaketak berrezarri", + "editor_rotate_left": "Biratu 90º erlojuaren kontra", + "editor_rotate_right": "Biratu 90º erlojuaren alde", + "email": "E-mail", + "email_notifications": "E-mail jakinarazpenak", + "empty_folder": "Karpeta hau hutsik dago", + "empty_trash": "Zakarrontzia hustu", + "empty_trash_confirmation": "Ziur zaude zakarrontzia hustu nahi duzula? Horrela, zakarrontziko baliabide guztiak behin betiko ezabatuko dira Immich-etik.\nEzin duzu ekintza hau desegin!", + "enable": "Gaitu", + "enable_backup": "Babeskopia gaitu", + "enable_biometric_auth_description": "Sartu zure PIN kodea autentifikazio biometrikoa gaitzeko", + "enabled": "Gaituta", + "end_date": "Amaiera data", + "enqueued": "Ilaran gehituta", + "enter_wifi_name": "Sartu Wi-Fi izena", + "enter_your_pin_code": "Sartu zure PIN kodea", + "enter_your_pin_code_subtitle": "Sartu zure PIN kodea blokeatutako karpetara sartzeko", + "error": "Akatsa", + "error_change_sort_album": "Ezin izan da albumen ordenaketa aldatu", + "error_delete_face": "Ezin izan da aurpegia baliabidetik ezabatu", + "error_getting_places": "Ezin izan dira tokiak jaso", + "error_loading_albums": "Ezin izan dira albumak kargatu", + "error_loading_image": "Ezin izan da argazkia kargatu", + "error_loading_partners": "Ezin izan dira kideak kargatu: {error}", + "error_retrieving_asset_information": "Ezin izan da baliabidearen informazioa berreskuratu", + "error_saving_image": "Akatsa: {error}", + "error_tag_face_bounding_box": "Ezin izan da irudia etiketatu - ezin dira marko koordenatuak lortu", + "error_title": "Akatsa - Zerbaitek huts egin du", + "error_while_navigating": "Ezin izan da baliabidera nabigatu", + "errors": { + "cannot_navigate_next_asset": "Ezin duzu hurrengo baliabidera nabigatu", + "cannot_navigate_previous_asset": "Ezin duzu aurreko baliabidera nabigatu", + "cant_apply_changes": "Ezin dira aldaketak aplikatu", + "cant_change_activity": "Ezin izan da jarduera {enabled, select, true {desaktibatu} other {aktibatu}}", + "cant_change_asset_favorite": "Ezin izan da gogokoa aldatu baliabide honetan", + "cant_change_metadata_assets_count": "Ezin izan da {count, plural, one {baliabidearen} other {# baliabideren}} metadatuak aldatu", + "cant_get_faces": "Ezin izan dira aurpegiak jaso", + "cant_get_number_of_comments": "Ezin izan da iruzkin kopurua jaso", + "cant_search_people": "Ezin izan dira pertsonak bilatu", + "cant_search_places": "Ezin izan dira tokiak bilatu", + "error_adding_assets_to_album": "Ezin izan dira baliabideak albumean sartu", + "error_adding_users_to_album": "Ezin izan dira erabiltzaileak albumean sartu", + "error_deleting_shared_user": "Ezin izan da partekatutako erabiltzailea ezabatu", + "error_downloading": "Ezin izan da {filename} deskargatu", + "error_hiding_buy_button": "Ezin izan da erosi botoia ezkutatu", + "error_removing_assets_from_album": "Errore bat gertatu da albumetik baliabideak ezabatzean; ikus kontsola xehetasunetarako", + "error_selecting_all_assets": "Ezin izan dira baliabide guztiak hautatu", + "exclusion_pattern_already_exists": "Bazterketa eradu hau iada existitzen da.", + "failed_to_create_album": "Ezin izan da albuma sortu", + "failed_to_create_shared_link": "Ezin izan da partekatutako esteka sortu", + "failed_to_edit_shared_link": "Ezin izan da partekatutako esteka editatu", + "failed_to_get_people": "Ezin izan dira pertsonak jaso", + "failed_to_keep_this_delete_others": "Ezin izan da baliabide hau gorde eta besteak ezabatu", + "failed_to_load_asset": "Ezin izan da baliabidea kargatu", + "failed_to_load_assets": "Ezin izan dira baliabideak kargatu", + "failed_to_load_notifications": "Ezin izan dira jakinarazpenak jaso", + "failed_to_load_people": "Ezin izan dira pertsonak kargatu", + "failed_to_remove_product_key": "Ezin izan da produktu gakoa ezabatu", + "failed_to_reset_pin_code": "Ezin izan da PIN gakoa berrezarri", + "failed_to_stack_assets": "Ezin izan dira baliabideak multzokatu", + "failed_to_tag_assets": "Ezin izan dira baliabideak etiketatu", + "failed_to_unstack_assets": "Ezin izan dira baliabideak desmultzokatu", + "failed_to_update_notification_status": "Errore bat gertatu da jakinarazpen-egoera eguneratzean", + "incorrect_email_or_password": "Pasahitz edo e-mail okerra", + "library_folder_already_exists": "Inportazio bide hau jada existitzen da.", + "page_not_found": "Ez da orria aurkitu", + "paths_validation_failed": "{paths, plural, one {Bideak} other {# bidek}} baliozkotzean huts egin {paths, plural, one {du} other {dute}}", + "profile_picture_transparent_pixels": "Profileko irudiek ezin du pixel gardenik izan. Mesedez, handitu eta/edo mugitu irudia.", + "quota_higher_than_disk_size": "Diskoaren tamaina baino kuota handiagoa ezarri da", + "something_went_wrong": "Zerbait gaizki joan da", + "unable_to_add_album_users": "Ezin izan dira erabiltzaileak albumean gehitu", + "unable_to_add_assets_to_shared_link": "Ezin dira baliabideak partekatutako estekan gehitu", + "unable_to_add_comment": "Ezin izan da iruzkina gehitu", + "unable_to_add_exclusion_pattern": "Ezin izan da bazterketa eredua gehitu", + "unable_to_add_partners": "Ezin izan dira kideak gehitu", + "unable_to_add_remove_archive": "Ezin izan da baliabidea {archived, select, true {artxibategitik kendu} other {artxibatu}}" + }, + "errors_text": "Akatsak", + "exif": "Exif", + "exif_bottom_sheet_description": "Deskribapena ezarri…", + "exif_bottom_sheet_description_error": "Ezin izan da deskribapena eguneratu", + "exif_bottom_sheet_details": "XEHETASUNAK", + "exif_bottom_sheet_location": "KOKAPENA", + "exif_bottom_sheet_people": "PERTSONAK", + "exif_bottom_sheet_person_add_person": "Izena gehitu", + "expand": "Zabaldu", + "experimental_settings_new_asset_list_subtitle": "Zirriborra garapenean", + "experimental_settings_title": "Esperimental", + "expired": "Iraungita", + "explore": "Arakatu", + "explorer": "Arakatzailea", + "export": "Esportatu", + "extension": "Hedapena", + "external": "Kanpokoa", + "external_network": "Kanpoko sarea", + "face_unassigned": "Ezarri gabea", + "failed": "Akatsduna", + "failed_to_authenticate": "Ezin izan da autentifikatu", + "favorite": "Gogokoa", + "favorites": "Gogokoenak", + "features": "Ezaugarriak", + "features_in_development": "Ezaugarriak garapen prozesuan", + "filename": "Fitxategia", + "filetype": "Fitxategi mota", + "filter": "Iragazkia", + "filter_places": "Tokiak iragazi", "first": "Lehenengo «Lehenik»", + "folder": "Karpeta", + "folder_not_found": "Ez da karpeta aurkitu", + "folders": "Karpetak", + "forgot_pin_code_question": "PIN kodea ahaztu al duzu?", + "forward": "Aurreruntz", + "free_up_space": "Biltegiratze garbitzailea", + "full_path": "Bide osoa: {path}", + "general": "General", + "get_people_error": "Ezin izan da pertsonarik jaso", "gps": "GPS", "gps_missing": "Ez dago GPS", + "grant_permission": "Baimendu", + "group_owner": "Jabearen arabera multzokatu", + "group_places_by": "Lekuan honen arabera multzokatu...", + "group_year": "Urtearen arabera multzokatu", + "haptic_feedback_switch": "Ikumen-feedback-a gaitu", + "haptic_feedback_title": "Ukipen-feedbacka", + "hashing": "Hasha sortzen", + "header_settings_add_header_tip": "Goiburua gehitu", + "header_settings_header_name_input": "Goiburu izena", + "header_settings_header_value_input": "Goiburu balioa", + "headers_settings_tile_title": "Proxy-goiburu pertsonalizatuak", + "hi_user": "Kaixo {name} ({email})", + "hide_all_people": "Pertsona guztiak ezkutatu", + "hide_named_person": "{name} pertsona ezkutatu", + "hide_text_recognition": "Testu-ezagutza ezkutatu", + "hide_unnamed_people": "Izengabeko pertsonak ezkutatu", + "home_page_building_timeline": "Denbora-lerroa sortzen", + "host": "Ostalaria", + "hour": "Ordua", + "hours": "Orduak", + "id": "ID", + "idle": "Jarduerarik gabe", + "ignore_icloud_photos": "iCloud argazkiak baztertu", + "image": "Irudia", + "image_saved_successfully": "Irudia gordeta", + "image_viewer_page_state_provider_download_started": "Deskarga hasieratua", + "image_viewer_page_state_provider_download_success": "Deskarga arrakastatsua", + "image_viewer_page_state_provider_share_error": "Share akatsa", + "immich_web_interface": "Immich Web interfazea", + "import_from_json": "JSON-etik inportatu", + "in_year_selector": "Urtea", + "include_shared_albums": "Partekatutako albumak barne hartu", + "info": "Informazioa", + "invalid_date": "Data okerra", + "invalid_date_format": "Datu formatu okerra", + "invite_to_album": "Albumera gonbidatu", + "ios_debug_info_fetch_ran_at": "Bilaketa exekutatu da {dateTime}", + "ios_debug_info_last_sync_at": "Azken sinkronizazioa {dateTime}", + "ios_debug_info_processing_ran_at": "Prozesatzea exekutatu da {dateTime}", + "jobs": "Atazak", + "keep": "Mantendu", + "language": "Hizkuntza", + "language_no_results_title": "Ez da hizkuntzarik aurkitu", "last": "Azkena", + "latitude": "Latitudea", + "leave": "Bertan behera utzi", + "less": "Gutxiago", + "let_others_respond": "Baimendu beste erabiltzaileak erantzutea", + "level": "Maila", + "library": "Liburutegia", + "library_page_device_albums": "Albumak gailuan", + "library_page_new_album": "Album berria", + "library_page_sort_asset_count": "Baliabide kopurua", + "library_page_sort_created": "Duela gutxi sortuta", + "library_page_sort_last_modified": "Duela gutxi aldatuta", + "library_page_sort_title": "Izenburuaren arabera", + "licenses": "Lizentziak", + "light": "Argia", "like": "Gustoko", + "link": "Esteka", + "link_motion_video": "Estekatu bideo biziduna", + "link_to_oauth": "OAuth-era esteka", + "linked_oauth_account": "OAuth kontua lotuta", + "list": "Zerrenda", + "loading": "Kargatzen", + "local": "Lokal", + "local_media_summary": "Bertako multimedien laburpena", + "local_network": "Bertako sarea", + "location": "Kokapena", + "location_permission": "Kokapen baimena", + "location_picker_choose_on_map": "Mapan aukeratu", + "lock": "Blokeatu", + "logged_out_device": "Gailua deskonektatua", + "login": "Saioa hasi", + "login_form_back_button_text": "Atzera", + "login_form_email_hint": "zure@emaila.com", + "login_form_endpoint_hint": "http://zerbitzariaren-ip-a:portua", + "login_form_endpoint_url": "Zerbitzariaren URL endpoint-a", + "login_form_err_invalid_email": "E-mail okerra", + "login_form_err_invalid_url": "URL okerra", + "login_form_err_leading_whitespace": "Zuriunea hasieran", + "login_form_err_trailing_whitespace": "Zuriunea amaieran", + "login_form_password_hint": "pasahitza", + "login_form_save_login": "Saioa mantendu", + "login_password_changed_success": "Pasahitza arrakastaz eguneratu", + "logs": "Erregistroak", + "longitude": "Longitudea", + "look": "Itxura", + "main_menu": "Menu nagusia", + "maintenance_end": "Mantentze modua itxi", + "maintenance_restore_from_backup": "Segurtasun-kopiatik berrezarri", + "maintenance_restore_library": "Zure liburutegia berrezarri", + "maintenance_restore_library_folder_pass": "Irakurgarria eta idazgarria", + "maintenance_task_migrations": "Datu-basearen migrazioak exekutatzen", + "make": "Marka", "manage_geolocation": "Kudeatu kokapena", + "manage_media_access_title": "Multimedia sarbidea kudeatu", + "manage_shared_links": "Partekatutako estekak kudeatu", + "manage_your_account": "Zure kontua kudeatu", + "map": "Mapa", + "map_location_dialog_yes": "Bai", + "map_location_picker_page_use_location": "Kokapen hau erabili", + "map_location_service_disabled_title": "Kokapen zerbitzua desgaitua", + "map_no_location_permission_title": "Kokapen baimena ukatuta", + "map_settings_dark_mode": "Gai iluna", + "map_settings_date_range_option_day": "Azken 24 orduak", + "map_settings_date_range_option_days": "Azken {days} egunak", + "map_settings_date_range_option_year": "Iaz", + "map_settings_date_range_option_years": "Past {years} urteak", + "map_settings_dialog_title": "Mapa ezarpenak", + "map_settings_include_show_archived": "Artxibatuak barne", + "map_settings_include_show_partners": "Kideak barne", + "map_settings_only_show_favorites": "Gogokoak soilik erakutsi", + "map_settings_theme_settings": "Maparen gaia", + "mark_as_read": "Markatu irakurritako gisa", + "matches": "Bat etorritakoak", + "media_chrome": { + "auto": "Auto", + "captions": "Azpitituluak", + "captions_off": "Desgaituta", + "loop": "Bulkea", + "mute": "Isilarazi" + }, + "media_type": "Multimedia mota", + "memories": "Gogorapenak", + "memories_all_caught_up": "Egunean zaude", + "memories_start_over": "Berriro hasi", + "memory": "Gogorapena", + "memory_lane_title": "Gogorapen kutxa {title}", + "menu": "Menua", + "merge": "Batu", + "merge_people": "Pertsonak batu", + "merge_people_successfully": "Pertsonak arrakastaz batuta", + "minimize": "Txikitu", + "minute": "Minutua", + "minutes": "Minutuak", + "mirror_horizontal": "Horizontala", + "mirror_vertical": "Bertikala", + "missing": "Hutsegiteak", + "model": "Modeloa", + "month": "Hilabetea", + "more": "Gehiago", + "move": "Mugitu", + "moved_to_trash": "Zakarrontzira eraman da", + "mute_memories": "Gogorapenak isilarazi", + "my_albums": "Nire albumak", + "name": "Izena", + "name_or_nickname": "Izena edo ezizena", + "name_required": "Izena derrigorrezkoa da", + "navigate": "Nabigatu", + "navigate_to_time": "Denborara nabigatu", + "networking_settings": "Sarea", + "never": "Inoiz ez", + "new_album": "Album berria", + "new_api_key": "API gako berria", + "new_date_range": "Data tarte berria", + "new_password": "Pasahitz berria", + "new_person": "Pertsona berria", + "new_pin_code": "PIN gako berria", + "new_user_created": "Erabiltzaile berria sortuta", + "new_version_available": "BERTSIO BERRIA ESKURAGARRI", + "newest_first": "Berriena lehenen", + "next": "Hurrengoa", + "next_memory": "Hurrengo gogorapena", + "no": "Ez", + "no_configuration_needed": "Ez da ezarpenik behar", + "no_devices": "Baimendutako gailurik ez", + "no_location_set": "Kokapena ezarri gabe", + "no_name": "Izenik ez", + "no_places": "Tokirik ez", + "no_results": "Emaitzarik ez", + "none": "Bat ere ez", + "not_selected": "Hautatu gabe", + "notes": "Oharra", + "nothing_here_yet": "Momentuz hutsik", + "notification_permission_list_tile_enable_button": "Jakinarazpenak gaitu", + "notification_permission_list_tile_title": "Jakinarazpen baimena", + "notification_toggle_setting_description": "E-mail jakinarazpenak gaitu", + "notifications": "Jakinarazpenak", + "notifications_setting_description": "Jakinarazpenak kudeatu", + "oauth": "OAuth", + "ocr": "OCR", + "official_immich_resources": "Immich-en baliabide ofizialak", + "offline": "Lineaz kanpo", + "offset": "Desbiderapena", + "ok": "Bai", + "oldest_first": "Zaharrena lehenengo", + "on_this_device": "Gailu honetan", + "onboarding": "Ezartzen", + "onboarding_welcome_user": "Ongi etorri, {user}", + "online": "Linean", + "only_favorites": "Gogokoak soilik", + "open": "Zabalik", + "open_in_openstreetmap": "OpenStreetMap-en zabaldu", + "options": "Ezarpenak", + "or": "edo", "organize_into_albums": "Albumetan antolatu", + "organize_your_library": "Zure liburutegia antolatu", + "original": "Originala", + "other": "Beste batzuk", + "other_devices": "Beste gailuak", + "other_variables": "Beste aldagaiak", + "owned": "Berezkoak", + "owner": "Jabea", + "partner": "Kidea", + "partner_can_access": "{partner}-ek sarbidea du", + "partner_list_user_photos": "{user}-(r)en argazkiak", + "partner_list_view_all": "Denak ikusi", + "partner_page_select_partner": "Kidea hautatu", + "partner_page_shared_to_title": "Honekin partekatuta", + "partner_sharing": "Kideekin partekatu", + "partners": "Kideak", + "password": "Pasahitza", + "password_required": "Pasahitza derrigorrezkoa da", + "password_reset_success": "Pasahitz eguneraketa arrakaztatsua", + "path": "Bidea", + "pattern": "Patroia", + "pause": "Gelditu", + "pause_memories": "Gogorapenak gelditu", + "paused": "Geldituta", + "pending": "Itxarotzen", + "people": "Pertsonak", + "permanent_deletion_warning": "Behin betiko ezabaketa oharra", + "permanently_delete": "Behin betiko ezabatu", + "permanently_deleted_asset": "Baliabidea behin betiko ezabatuta", + "permission": "Baimena", + "permission_onboarding_back": "Atzera", + "permission_onboarding_continue_anyway": "Halaere jarraitu", + "permission_onboarding_get_started": "Hasi", + "permission_onboarding_go_to_settings": "Jo ezarpenetara", + "person": "Pertsona", + "person_birthdate": "{date} -(a)n jaiota", + "photos": "Argazkiak", + "photos_and_videos": "Argazkiak eta bideoak", + "pick_a_location": "Toki bat aukeratu", + "pin_verification": "PIN kodearen egiaztapena", + "place": "Tokia", + "places": "Tokiak", + "play": "Erreproduzitu", + "play_memories": "Gogorapenak erreproduzitu", + "play_motion_photo": "Argazki biziduna erreproduzitu", + "play_original_video": "Jatorrizko bideoa erreproduzitu", + "play_transcoded_video": "Transkodetutako bideoa erreproduzitu", + "port": "Portua", + "preferences_settings_title": "Ezarpenak", + "preparing": "Prestatzen", + "preset": "Txantiloia", + "preview": "Aurrebista", + "previous": "Aurrekoa", + "previous_memory": "Aurreko gogorapena", + "previous_or_next_photo": "Aurreko/hurrengo irudia", + "primary": "Nagusia", + "privacy": "Pribatutasuna", + "profile": "Profila", + "profile_drawer_app_logs": "Erregistroak", + "profile_drawer_github": "GitHub", + "profile_picture_set": "Profil-argazkia ezarrita.", + "public_album": "Album publikoa", + "public_share": "Share publikoa", + "purchase_account_info": "Babeslea", + "purchase_activated_time": "{date}-(a)n aktibatuta", + "purchase_button_activate": "Aktibatu", + "purchase_button_buy": "Erosi", + "purchase_button_buy_immich": "Immich erosi", + "purchase_button_never_show_again": "Ez erakutsi berriz", + "purchase_button_remove_key": "Gakoa kendu", + "purchase_button_select": "Aukeratu", + "purchase_individual_description_1": "Erabiltzaile batentzako", + "purchase_individual_description_2": "Babesle ikurra", + "purchase_individual_title": "Banakakoa", + "purchase_lifetime_description": "Bizitza osoko ordainketa", + "purchase_option_title": "ORDAINKETA AUKERAK", + "purchase_panel_title": "Proiektua babestu", + "purchase_per_server": "Zerbitzari bakoitzeko", + "purchase_per_user": "Erabiltzaile bakoitzeko", + "purchase_remove_product_key": "Produktu gakoa ezabatu", + "purchase_server_description_2": "Babesle ikurra", + "purchase_server_title": "Zerbitzaria", "query_asset_id": "Aztertu aukeratutako ID-a", + "rating": "Izar puntuazioa", + "rating_clear": "Puntuazioa ezabatu", + "reaction_options": "Erreakzio aukerak", + "read_changelog": "Aldaketa erregistroa irakurri", "readonly_mode_disabled": "Irakurri-bakarrik modua desgaituta", "readonly_mode_enabled": "Irakurri-bakarrik modua gaituta", + "ready_for_upload": "Kargatzeko prest", + "reassign": "Berrezarri", + "recent": "Berria", + "recent_searches": "Duela gutxiko bilaketak", + "recently_added": "Duela gutxi gehitutakoak", + "recently_added_page_title": "Duela gutxi gehitutakoak", + "recently_taken": "Duela gutxi ateratakoak", + "refresh": "Freskatu", + "refresh_encoded_videos": "Kodetutako bideoak freskatu", + "refresh_faces": "Aurpegiak freskatu", + "refresh_metadata": "Metadatuak freskatu", + "refresh_thumbnails": "Miniaturak freskatu", + "refreshed": "Freskatuta", + "refreshing_encoded_video": "Kodetutako bideoak freskatzen", + "refreshing_faces": "Aurpegiak freskatzen", + "refreshing_metadata": "Metadatuak freskatzen", + "regenerating_thumbnails": "Miniaturak leheneratu", + "remote": "Urruneko zerbitzaria", + "remote_media_summary": "Kanpoko multimedien laburpena", + "remove": "Kendu", + "remove_assets_title": "Baliabideak ezabatu?", + "remove_deleted_assets": "Ezabatu lineaz kanpoko baliabideak", + "remove_from_album": "Ezabatu albumetik", + "remove_from_favorites": "Gogokoetatik ezabatu", + "remove_memory": "Gogorapena kendu", + "remove_url": "URL-a kendu", + "remove_user": "Erabiltzailea kendu", + "removed_from_archive": "Artxibategitik ezabatuta", + "removed_from_favorites": "Gogokoetatik ezabatuta", + "removed_memory": "Gogorapena kenduta", + "rename": "Izena aldatu", + "repair": "Konpondu", + "replace_with_upload": "Igo eta ordezkatu", + "repository": "Errepositorioa", + "require_password": "Pasahitza derrigortu", + "rescan": "Berreskaneatu", + "reset": "Berrasieratu", + "reset_password": "Pasahitza leheneratu", + "reset_people_visibility": "Pertsonen ikusgarritasuna berrezarri", + "reset_pin_code": "PIN gakoa berrezarri", + "reset_sqlite": "SQLite datu-basea berrasieratu", + "reset_to_default": "Balio lehenetsitara berrezarri", + "resolution": "Bereizmena", + "resolve_duplicates": "Kopiak ebatzi", + "resolved_all_duplicates": "Kopia guztiak ebatzita", + "restore": "Zaharberritu", + "restore_all": "Denak berrezarri", + "restore_user": "Erabiltzailea berrezarri", + "restored_asset": "Baliabidea berrezarri", + "resume": "Jarraitu", + "retry_upload": "Karga berriro saiatu", + "review_duplicates": "Kopiak berrikusi", + "review_large_files": "Fitxategi handiak berrikusi", + "role": "Rola", + "role_editor": "Editorea", + "role_viewer": "Ikuslea", + "running": "Martxan", + "save": "Gorde", + "save_to_gallery": "Galerian gorde", + "saved": "Gordeta", + "saved_api_key": "API gakoa arrakastaz gorde da", + "saved_profile": "Profila gordeta", + "saved_settings": "Ezarpenak gordeta", + "say_something": "Zerbait esan", + "scaffold_body_error_occurred": "Akats bat gertatu da", + "scan_all_libraries": "Liburutegi guztiak eskaneatu", + "scan_library": "Eskaneatu", + "scan_settings": "Eskaneo ezarpenak", + "scanning_for_album": "Albumak bilatzen...", + "search": "Bilatu", + "search_albums": "Albumak bilatu", + "search_by_context": "Testuinguruaren arabera bilatu", + "search_by_description": "Deskribapenaren arabera bilatu", + "search_by_ocr": "OCR bitartez bilatu", + "search_by_ocr_example": "Kafesnea", + "search_camera_lens_model": "Bilatu lente modeloa...", + "search_camera_make": "Kamera fabrikatzailea bilatu...", + "search_camera_model": "Kamara modeloa bilatu...", + "search_city": "Hiria bilatu…", + "search_country": "Herrialdea bailatu...", + "search_filter_apply": "Iragazkiak aplikatu", + "search_filter_camera_title": "Aukeratu kamera mota", + "search_filter_date": "Data", + "search_filter_date_interval": "{start}-(e)tik {end}-ra", + "search_filter_display_option_not_in_album": "Ez dago albumean", + "search_filter_display_options": "Bistaratzeko ezarpenak", + "search_filter_location": "Kokapena", + "search_filter_location_title": "Kokapena aukeratu", + "search_filter_media_type": "Multimedia mota", + "search_filter_media_type_title": "Aukeratu multimedia mota", + "search_filter_ocr": "OCR bidez bilatu", + "search_filter_people_title": "Pertsonak hautatu", + "search_for": "Bilatu", + "search_no_more_result": "Emaitza gehiagorik ez", + "search_no_people": "Pertsonarik ez", + "search_options": "Bilaketa ezarpenak", + "search_page_categories": "Kategoriak", + "search_page_motion_photos": "Argazki bizidunak", + "search_page_screenshots": "Pantaila-argazkiak", + "search_page_selfies": "Selfiak", + "search_page_things": "Gauzak eta Animaliak", + "search_page_view_all_button": "Denak ikusi", + "search_page_your_activity": "Zure jarduerak", + "search_page_your_map": "Zure mapa", + "search_people": "Pertsonak bilatu", + "search_places": "Tokiak bilatu", + "search_rating": "Puntuazioaren arabera bilatu...", + "search_result_page_new_search_hint": "Bilaketa berria", + "search_settings": "Ezarpenak bilatu", + "search_state": "Probintziaren arabera bilatu…", + "search_suggestion_list_smart_search_hint_2": "m:zure-bilaketa", + "search_tags": "Etiketak bilatu…", + "search_timezone": "Ordu-eremua bilatu…", + "search_type": "Bilaketa mota", + "search_your_photos": "Zure argazkiak bilatu", + "searching_locales": "Tokiak bilatzen…", + "second": "Segundua", + "see_all_people": "Pertsona guztiak ikusi", + "select": "Hautatu", + "select_album_cover": "Albumaren portada aukeratu", + "select_all": "Dena aukeratu", + "select_all_duplicates": "Bikoiztutako guztiak aukeratu", + "select_avatar_color": "Abatar kolorea aukeratu", + "select_cutoff_date": "Data muga ezarri", + "select_face": "Aurpegia hautatu", + "select_featured_photo": "Argazki nagusia aukeratu", + "select_from_computer": "Ordenagailutik aukeratu", + "select_keep_all": "Denak mantendu", + "select_library_owner": "Liburutegi jabea aukeratu", + "select_new_face": "Aurpegi berria aukeratu", + "select_photos": "Argazkiak aukeratu", + "select_trash_all": "Denak ezabatu", + "selected": "Aukeratuta", "selected_gps_coordinates": "GPS Koordenadak Aukeratuta", + "send_message": "Mezua bidali", + "send_welcome_email": "Ongietorri e-mail mezua bidali", + "server_endpoint": "Zerbitzariaren endpoint-a", + "server_info_box_app_version": "Aplikazio bertsioa", + "server_info_box_server_url": "Zerbitzariaren URLa", + "server_offline": "Zerbitzaria lineaz kanpo", + "server_online": "Zerbitzaria linean", + "server_restarting_title": "Zerbitzaria berrabiarazten ari da", + "server_stats": "Zerbitzariaren estatistikak", + "server_version": "Zerbitzariaren bertsioa", + "set": "Ezarri", + "set_profile_picture": "Ezarri profil-argazkia", + "setting_image_viewer_original_title": "Jatorrizko irudia kargatu", + "setting_image_viewer_preview_title": "Aurrebista irudia kargatu", + "setting_image_viewer_title": "Irudiak", + "setting_languages_apply": "Ezarri", + "setting_notifications_notify_hours": "{count, plural, one {Ordu bat} other {# ordu}}", + "setting_notifications_notify_immediately": "berehala", + "setting_notifications_notify_minutes": "{count, plural, one {Minutu bat} other {# minutu}}", + "setting_notifications_notify_never": "inoiz ez", + "setting_notifications_notify_seconds": "{count, plural, one {Segundu bat} other {# segundu}}", + "setting_video_viewer_auto_play_title": "Bideoak automatikoki erreproduzitu", + "setting_video_viewer_looping_title": "Errepikatu", + "setting_video_viewer_original_video_title": "Jatorrizko bideoa behartu", + "settings": "Ezarpenak", + "settings_saved": "Ezarpenak gordeta", + "share": "Partekatu", + "share_action_prompt": "{count, plural, one{Partekatutako baliabide bat} other{# partekatutako baliabide}}", + "share_add_photos": "Argazkiak gehitu", + "share_assets_selected": "{count} aukeratuta", + "share_dialog_preparing": "Prestatzen...", + "shared": "Partekatuta", + "shared_album_activities_input_disable": "Iruzkinak desgaituta daude", + "shared_album_activity_remove_title": "Jarduera ezabatu", + "shared_album_section_people_title": "PERTSONAK", + "shared_by": "Honek partekatuta", + "shared_by_user": "{user}-(e)k partekatuta", + "shared_by_you": "Zuk partekatuta", + "shared_from_partner": "{partner}-(e)n argazkiak", + "shared_link_app_bar_title": "Partekatutako estekak", + "shared_link_clipboard_copied_massage": "Esteka arbelera kopiatuta", + "shared_link_edit_expire_after_option_day": "Egun bat", + "shared_link_edit_expire_after_option_days": "{count} egun", + "shared_link_edit_expire_after_option_hour": "Ordu bat", + "shared_link_edit_expire_after_option_hours": "{count} ordu", + "shared_link_edit_expire_after_option_minute": "Minutu bat", + "shared_link_edit_expire_after_option_minutes": "{count} minutu", + "shared_link_edit_expire_after_option_months": "{count} hilabete", + "shared_link_edit_expire_after_option_year": "{count} urte", + "shared_link_edit_submit_button": "Esteka eguneratu", + "shared_link_expires_never": "Iraungiezina", + "shared_link_individual_shared": "Indibidualki partekatuta", + "shared_link_info_chip_metadata": "EXIF", + "shared_link_manage_links": "Partekatutako estekak kudeatu", + "shared_link_options": "Partekatutako esteken ezarpenak", + "shared_links": "Partekatutako estekak", + "shared_with_me": "Nirekin partekatutakoak", + "shared_with_partner": "{partner}-(r)ekin partekatuta", + "sharing": "Partekatzen", + "sharing_page_album": "Partekatutako albumak", + "sharing_page_empty_list": "ZERRENDA HUTSA", + "sharing_silver_appbar_create_shared_album": "Partekatutako album berria", + "sharing_silver_appbar_share_partner": "Kidearekin partekatu", + "show_album_options": "Erakutsi albumaren azarpenak", + "show_albums": "Albumak erakutsi", + "show_all_people": "Pertsona guztiak erakutsi", + "show_file_location": "Erakutsi fitxategiaren kokapena", + "show_gallery": "Galeria erakutsi", + "show_hidden_people": "Erakutsi ezkutatutako pertsonak", + "show_in_timeline": "Erakutsi denbora-lerroan", + "show_keyboard_shortcuts": "Erakutsi lasterbideak", + "show_metadata": "Metadatuak erakutsi", + "show_password": "Pasahitza erakutsi", + "show_person_options": "Erakutsi pertsonaren ezarpenak", + "show_progress_bar": "Aurrerapen-barra erakutsi", + "show_search_options": "Erakutsi bilaketa ezarpenak", + "show_shared_links": "Erakutsi partekatutako estekak", + "show_slideshow_transition": "Erakutsi diapositiba-trantsizioak", + "show_supporter_badge": "Babesle insignia", + "show_supporter_badge_description": "Erakutsi babesle insignia", + "show_text_recognition": "Testu-ezagutza erakutsi", + "shuffle": "Nahasi", + "sidebar": "Alboko panela", + "sign_out": "Saioa itxi", + "sign_up": "Erregistratu", + "size": "Tamaina", + "skip_to_content": "Edukira salto egin", + "skip_to_folders": "Karpetetara salto egin", + "skip_to_tags": "Etiketetara salto egin", + "slideshow": "Diapositibak", + "slideshow_settings": "Diapositiba ezarpenak", + "sort_albums_by": "Albumak honen bidez ordenatu...", + "sort_created": "Sortze data", + "sort_items": "Baliabide kopurua", + "sort_modified": "Egunatze data", "sort_newest": "Argazkirik berriena", + "sort_oldest": "Argazkirik zaharrena", + "sort_recent": "Argazkirik berriena", + "sort_title": "Izenburua", + "source": "Iturburua", + "stack": "Multzokatu", + "stack_duplicates": "Kopiak multzokatu", + "stack_selected_photos": "Aukeratutako argazkiak multzokatu", + "stacktrace": "Pila jarraipena", + "start": "Hasi", + "start_date": "Hasiera data", + "state": "Estatua / Probintzia", + "status": "Egoera", + "stop_motion_photo": "Gelditu argazki biziduna", + "storage": "Memoria edukiera", + "storage_label": "Memoria etiketa", + "submit": "Bidali", + "success": "Arrakastatsua", + "suggestions": "Iradokizunak", + "support": "Babesa", + "support_and_feedback": "Laguntza eta feedback-a", + "supporter": "Babeslea", + "swap_merge_direction": "Trukatu bateratze-norabidea", + "sync": "Sinkronizazioa", + "sync_albums": "Albumak sinkronizatu", + "tag": "Etiketa", + "tag_assets": "Baliabideak etiketatu", + "tag_created": "Etiketa sortuta: {tag}", + "tag_people": "Pertsonak etiketatu", + "tag_updated": "Etiketa eguneratuta: {tag}", + "tags": "Etiketak", + "template": "Txantiloia", + "theme": "Gaia", + "theme_selection": "Gaien aukeraketa", + "theme_setting_colorful_interface_title": "Interfaze koloreduna", + "theme_setting_image_viewer_quality_title": "Irudi arakatzailearen kalitatea", + "theme_setting_primary_color_title": "Kolore nagusia", + "theme_setting_system_primary_color_title": "Erabili sistemaren kolorea", + "theme_setting_three_stage_loading_title": "Erabili hiru faseko karga", + "then": "Orduan", + "third_party_resources": "Bitartekoen baliabideak", + "time": "Denbora", + "time_based_memories": "Denboran oinarritutako gogorapenak", + "timeline": "Kronologia", + "timezone": "Ordu-eremua", + "to_archive": "Artxibatu", + "to_change_password": "Pasahitza aldatu", + "to_favorite": "Gogokotzat ezarri", + "to_login": "Saioa hasi", + "to_parent": "Gurasoetara jo", "to_select": "aukeratzeko", - "view_similar_photos": "Ikusi antzeko argazkiak" + "to_trash": "Baztertu", + "toggle_settings": "Ezarpenak aldizkatu", + "total": "Guztira", + "total_usage": "Erabilpen osoa", + "trash": "Zakarrontzia", + "trash_all": "Denak ezabatu", + "trash_count": "Zakarrontzia {count, number}", + "trash_delete_asset": "Baliabidea ezabatu/zakarrontzira eraman", + "trash_emptied": "Zakarrontzia hustuta", + "trash_page_delete_all": "Denak ezabatu", + "trash_page_no_assets": "Zakarrontzian baliabiderik ez", + "trash_page_restore_all": "Denak berrezarri", + "trash_page_select_assets_btn": "Baliabideak hautatu", + "trash_page_title": "Zakarrontzia ({count})", + "troubleshoot": "Arazoak konpontzea", + "type": "Mota", + "unarchive": "Desartxibatu", + "undo": "Desegin", + "unfavorite": "Gogokoetatik kendu", + "unhide_person": "Pertsona erakutsi", + "unknown": "Ezezaguna", + "unknown_country": "Herrialde ezezaguna", + "unknown_year": "Urte ezezaguna", + "unlimited": "Mugagabea", + "unlink_motion_video": "Argazki biziduna deslotu", + "unlink_oauth": "OAuth deslotu", + "unlinked_oauth_account": "OAuth kontua deslotuta", + "unmute_memories": "Gogorapenen soinua gaitu", + "unnamed_album": "Izengabeko Albuma", + "unnamed_share": "Izengabeko baliabide partekatua", + "unsaved_change": "Gordegabeko aldaketa", + "unselect_all": "Aukeraketak garbitu", + "unselect_all_duplicates": "Bikoiztutakoak deshautatu", + "unstack": "Multzotik kendu", + "unsupported_field_type": "Eremu mota ez da bateragarria", + "untagged": "Etiketagabea", + "up_next": "Hurrengoa", + "updated_at": "Eguneratze-data", + "updated_password": "Pasahitza eguneratuta", + "upload": "Igo", + "upload_concurrency": "Igoera paraleloak", + "upload_dialog_title": "Baliabidea gora kargatu", + "upload_status_duplicates": "Kopiak", + "upload_status_errors": "Akatsak", + "upload_status_uploaded": "Igota", + "uploading": "Igotzen", + "uploads": "Igoerak", + "url": "URL-a", + "usage": "Erabilera", + "use_current_connection": "Erabili oraingo konexioa", + "user": "Erabiltzailea", + "user_id": "Erabiltzaile ID-a", + "user_purchase_settings": "Erosketa", + "user_purchase_settings_description": "Zure erosketa kudeatu", + "user_usage_detail": "Erabiltzailearen erabileraren xehetasunak", + "user_usage_stats": "Kontuaren erabilera estatistikak", + "username": "Erabiltzaile izena", + "users": "Erabiltzaileak", + "utilities": "Tresnak", + "validate": "Balioetsi", + "variables": "Aldagaiak", + "version": "Bertsioa", + "version_announcement_closing": "Zure laguna, Alex", + "version_history": "Bertsio-historia", + "video": "Bideoa", + "videos": "Bideoak", + "view": "Bista", + "view_album": "Albuma ikusi", + "view_all": "Denak ikusi", + "view_all_users": "Erabiltzaile guztiak erakutsi", + "view_asset_owners": "Baliabidearen jabeak erakutsi", + "view_in_timeline": "Denbora-lerroan ikusi", + "view_link": "Esteka ikusi", + "view_links": "Estekak ikusi", + "view_name": "Bista", + "view_next_asset": "Hurrengo baliabidea erakutsi", + "view_previous_asset": "Aurreko baliabidea erakutsi", + "view_qr_code": "QR kodea erakutsi", + "view_similar_photos": "Ikusi antzeko argazkiak", + "view_stack": "Pila ikusi", + "viewer_remove_from_stack": "Multzotik kendu", + "viewer_unstack": "Multzotik kendu", + "visibility": "Ikusgarritasuna", + "waiting": "Itxarotzen", + "warning": "Oharra", + "week": "Astea", + "welcome": "Ongi etorri", + "welcome_to_immich": "Ongi egorri Immich-era", + "when": "Noiz", + "wifi_name": "Wi-Fi izena", + "workflow_update_success": "Lan-fluxua arrakastaz eguneratua", + "wrong_pin_code": "PIN kode okerra", + "x_of_total": "{total}-(e)tik {x}", + "year": "Urtea", + "yes": "Bai", + "your_wifi_name": "Zure Wi-Fi izena", + "zoom_image": "Irudia handitu", + "zoom_to_bounds": "Muturretara doitu" } diff --git a/i18n/fa.json b/i18n/fa.json index 2a87b929c5..79284ce8f5 100644 --- a/i18n/fa.json +++ b/i18n/fa.json @@ -605,7 +605,6 @@ "map_location_picker_page_use_location": "استفاده از این موقعیت مکانی", "map_location_service_disabled_content": "برای نمایش دارایی‌ها بر اساس موقعیت مکانی، نیاز به فعال‌سازی سرویس مکان‌یابی دارید. می‌خواهید همین حالا فعال شود؟", "map_location_service_disabled_title": "سرویس مکان‌یابی غیرفعال است", - "map_marker_for_images": "نشانگر روی نقشه برای عکس‌های گرفته‌شده در {city}, {country}", "map_marker_with_image": "علامت‌گذاری نقشه با عکس", "map_no_location_permission_content": "برای نمایش عکس‌های اطرافتان، برنامه نیاز به دسترسی به موقعیت مکانی دارد. اجازه دسترسی می‌دهید؟", "map_no_location_permission_title": "دسترسی به موقعیت شما فعال نیست", diff --git a/i18n/fi.json b/i18n/fi.json index 17596c3d10..29a001f8f1 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -1504,7 +1504,6 @@ "map_location_picker_page_use_location": "Käytä tätä sijaintia", "map_location_service_disabled_content": "Paikannuspalvelun pitää olla kytkettynä päälle, jotta nykyisen sijaintisi kohteita voidaan näyttää. Haluatko kytkeä sen päälle nyt?", "map_location_service_disabled_title": "Paikannuspalvelu pois päältä", - "map_marker_for_images": "Karttamarkerointi kuville, jotka on otettu kaupungissa {city}, maassa {country}", "map_marker_with_image": "Karttamarkerointi kuvalla", "map_no_location_permission_content": "Paikannuslupa tarvitaan, jotta nykyisen sijainnin kohteita voidaan näyttää. Haluatko sallia pääsyn sijaintiin?", "map_no_location_permission_title": "Paikannuslupa estetty", diff --git a/i18n/fil.json b/i18n/fil.json index ecd9e219dd..3decd51559 100644 --- a/i18n/fil.json +++ b/i18n/fil.json @@ -15,29 +15,36 @@ "add_a_location": "Dagdagan ng lugar", "add_a_name": "Dagdagan ng pangalan", "add_a_title": "Dagdagan ng pamagat", + "add_action": "Magdagdag ng aksyon", "add_assets": "Dagdagan ng asset", "add_birthday": "Maglagay ng kaarawan", "add_endpoint": "Dagdagan ng dulo", + "add_exclusion_pattern": "Magdagdag ng exlusion pattern", "add_location": "Magdagdag ng lugar", "add_more_users": "Magdagdag ng mga user", "add_partner": "Magdagdag ng kasangga", "add_path": "Magdagdag ng path", "add_photos": "Magdagdag ng litrato", + "add_step": "Magdagdag ng step", "add_tag": "Magdagdag ng tag", "add_to": "Idagdag sa…", "add_to_album": "Idagdag sa album", "add_to_album_bottom_sheet_added": "Naidagdag sa {album}", "add_to_album_bottom_sheet_already_exists": "Nasa {album} na", + "add_to_album_bottom_sheet_some_local_assets": "May ilang mga local assets ang hindi maidagdag sa album", + "add_to_album_toggle": "Toggle selection para sa {album}", "add_to_albums": "Idagdag sa mga album", "add_to_albums_count": "Idagdag sa mga album ({count})", "add_to_bottom_bar": "Idagdag sa", "add_to_shared_album": "Idagdag sa shared album", + "add_upload_to_stack": "Magdagdag ng upload para ma-stack", "add_url": "Magdagdag ng URL", "added_to_archive": "Naidagdag sa archive", "added_to_favorites": "Naidagdag sa mga paborito", "added_to_favorites_count": "Naidagdag ang {count, number} sa mga paborito", "admin": { "add_exclusion_pattern_description": "Dagdagan ng pattern para maibukod. Supportado ang pag-tutugma gamit ang *, **, at ?. Para hindi maisama ang mga file sa direktoryo na may pangalang \"Raw\", gamitin ang \"**/Raw/**\". Para hindi maisama ang lahat ng mga file na nagtatapos sa \".tif\", gamitin ang \"**/*.tif\". Para hindi maisama ang isang tiyak na folder, gamitin ang \"/path/to/ignore/**\".", + "admin_user": "Admin User", "asset_offline_description": "Ang external library asset na ito ay hindi na makikita sa disk at nailipat na sa basurahan. Kung ang file ay nailipat sa loob ng library, tignan ang iyong timeline para sa kaukulang asset. Para maibalik ang asset na ito, siguraduhin na ang file ay maa-access ng Immich at muling i-scan ang library.", "authentication_settings": "Setting ng mga Pagkakakilanlan", "authentication_settings_description": "Pamahalaan ang password, OAuth, and iba pang setting ng pagkakakilanlan", @@ -68,6 +75,7 @@ "disable_login": "I-disable ang login", "duplicate_detection_job_description": "Hanapin ang mga magkakatulad na imahe gamit ang machine learning. Umaasa sa Smart Search", "exclusion_pattern_description": "Maaaring gamitin ang mga pattern na pangbukod para hindi pansinin ang ilang file o folder habang binabasa ang iyong library. Mainam itong solusyon para sa mga folder na may file na ayaw niyong ma-import, tulad ng mga RAW na file.", + "face_detection": "Face detection", "force_delete_user_warning": "BABALA: Tatanggalin itong user at lahat ng asset nila, Hindi ito mababawi at ang kanilang files ay hindi na mababalik", "image_format": "Format", "note_cannot_be_changed_later": "TANDAAN: Hindi na ito pwede baguhin sa susunod!", diff --git a/i18n/fr.json b/i18n/fr.json index 2abc30143e..b313e67fc5 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -1548,7 +1548,7 @@ "map_location_picker_page_use_location": "Utiliser ma position", "map_location_service_disabled_content": "Le service de localisation doit être activé pour afficher les médias de votre emplacement actuel. Souhaitez-vous l'activer maintenant ?", "map_location_service_disabled_title": "Service de localisation désactivé", - "map_marker_for_images": "Marqueur de carte pour les images prises à {city}, {country}", + "map_marker_for_image": "Marqueur de carte pour une image prise à {city}, {country}", "map_marker_with_image": "Marqueur de carte avec image", "map_no_location_permission_content": "L'autorisation de localisation est nécessaire pour afficher les médias de votre emplacement actuel. Souhaitez-vous l'autoriser maintenant ?", "map_no_location_permission_title": "Permission de localisation refusée", diff --git a/i18n/ga.json b/i18n/ga.json index 5d930c2ecc..a8f722d343 100644 --- a/i18n/ga.json +++ b/i18n/ga.json @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "Cumasaigh cuardach cliste", "machine_learning_smart_search_enabled_description": "Mura bhfuil sé sin ar fáil, ní dhéanfar íomhánna a ionchódú le haghaidh cuardaigh chliste.", "machine_learning_url_description": "URL an fhreastalaí foghlama meaisín. Má chuirtear níos mó ná URL amháin ar fáil, déanfar iarracht ar gach freastalaí ceann ag an am go dtí go bhfreagróidh ceann acu go rathúil, in ord ón gcéad cheann go dtí an ceann deireanach. Déanfar neamhaird shealadach ar fhreastalaithe nach bhfreagróidh go dtí go mbeidh siad ar líne arís.", + "maintenance_backup_management": "Bainistíocht chúltaca", "maintenance_delete_backup": "Scrios Cúltaca", "maintenance_delete_backup_description": "Scriosfar an comhad seo go neamh-inchúlghairthe.", "maintenance_delete_error": "Theip ar an gcúltaca a scriosadh.", + "maintenance_integrity_check": "Seiceáil", "maintenance_integrity_check_all": "Seiceáil Gach Rud", "maintenance_integrity_checksum_mismatch": "Mí-chomhoiriúnacht suime seiceála", + "maintenance_integrity_checksum_mismatch_description": "Comhaid nach bhfuil an tsuim seiceála ar an diosca ag teacht leis an tsuim seiceála atá stóráilte ag Immich ina bhunachar sonraí.", "maintenance_integrity_checksum_mismatch_job": "Seiceáil le haghaidh mí-oiriúnuithe suime seiceála", "maintenance_integrity_checksum_mismatch_refresh_job": "Athnuachan tuairiscí mí-oiriúnachta suime seiceála", "maintenance_integrity_missing_file": "Comhaid ar Iarraidh", + "maintenance_integrity_missing_file_description": "Comhaid atá rianaithe ag Immich ina bhunachar sonraí ach nach bhfuil ar fáil ar an gcóras comhad.", "maintenance_integrity_missing_file_job": "Seiceáil le haghaidh comhaid atá ar iarraidh", "maintenance_integrity_missing_file_refresh_job": "Athnuachan tuairiscí ar chomhaid atá ar iarraidh", "maintenance_integrity_report": "Tuarascáil Ionracais", "maintenance_integrity_untracked_file": "Comhaid Gan Rianú", + "maintenance_integrity_untracked_file_description": "Comhaid in eolairí Immich nach bhfuil aon taifead ag Immich orthu.", "maintenance_integrity_untracked_file_job": "Seiceáil le haghaidh comhaid neamhrianaithe", "maintenance_integrity_untracked_file_refresh_job": "Athnuachan tuarascálacha comhad neamhrianaithe", "maintenance_restore_backup": "Athchóirigh Cúltaca", @@ -1543,7 +1548,6 @@ "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_images": "Marcóir léarscáile le haghaidh íomhánna 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", diff --git a/i18n/gl.json b/i18n/gl.json index 9981ad5a2b..dcee16c357 100644 --- a/i18n/gl.json +++ b/i18n/gl.json @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "Activar busca intelixente", "machine_learning_smart_search_enabled_description": "Se está desactivado, as imaxes non se codificarán para a busca intelixente.", "machine_learning_url_description": "A URL do servidor de aprendizaxe automática. Se se proporciona máis dunha URL, intentarase con cada servidor un por un ata que un responda correctamente, en orde do primeiro ao último. Os servidores que non respondan ignoraranse temporalmente ata que volvan estar en liña.", + "maintenance_backup_management": "Xestión de copias de seguridade", "maintenance_delete_backup": "Eliminar copia de seguridade", "maintenance_delete_backup_description": "Este arquivo será borrado permanentemente.", "maintenance_delete_error": "Erro ao eliminar a copia de seguridade.", + "maintenance_integrity_check": "Verificar", "maintenance_integrity_check_all": "Seleccionar todos", "maintenance_integrity_checksum_mismatch": "A suma de comprobación non coincide", + "maintenance_integrity_checksum_mismatch_description": "Arquivos que teñen diferentes sumas de comprobación entre o disco e a base de datos de Immich.", "maintenance_integrity_checksum_mismatch_job": "Comproba se hai discrepancias na suma de verificación", "maintenance_integrity_checksum_mismatch_refresh_job": "Actualizar informes de discrepancias da suma de comprobación", "maintenance_integrity_missing_file": "Arquivos faltantes", + "maintenance_integrity_missing_file_description": "Arquivos que Immich ten rexistrados na base de datos pero que non existen no sistema de arquivos.", "maintenance_integrity_missing_file_job": "Comprobe os arquivos que faltan", "maintenance_integrity_missing_file_refresh_job": "Actualiza os informes de arquivos que faltan", "maintenance_integrity_report": "Informe de integridade", "maintenance_integrity_untracked_file": "Arquivos sen rastrexar", + "maintenance_integrity_untracked_file_description": "Arquivos nos directorios de Immich dos que Immich non ten ningún rexistro.", "maintenance_integrity_untracked_file_job": "Comprobar se hai arquivos sen rastrexar", "maintenance_integrity_untracked_file_refresh_job": "Actualizar informes de arquivos sen rastrexar", "maintenance_restore_backup": "Recuperar copia de seguridade", @@ -1369,6 +1374,7 @@ "individual_share": "Compartir individual", "individual_shares": "Comparticións individuais", "info": "Información", + "integrity_checks": "Verificacións de integridade", "interval": { "day_at_onepm": "Todos os días ás 13:00", "hours": "Cada {hours, plural, one {hora} other {{hours, number} horas}}", @@ -1441,6 +1447,7 @@ "linked_oauth_account": "Conta OAuth ligada", "list": "Lista", "live": "En directo", + "load_more": "Cargar máis", "loading": "Cargando", "loading_search_results_failed": "Erro ao cargar os resultados da busca", "local": "Local", @@ -1541,7 +1548,6 @@ "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_images": "Marcador de mapa para imaxes tomadas 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", @@ -2099,6 +2105,7 @@ "select_person": "Seleccionar persoa", "select_person_to_tag": "Seleccionar unha persoa para etiquetar", "select_photos": "Seleccionar fotos", + "select_quality": "Seleccionar calidade", "select_trash_all": "Seleccionar mover todo ao lixo", "select_user_for_sharing_page_err_album": "Erro ao crear o álbum", "selected": "Seleccionado", @@ -2162,6 +2169,8 @@ "share_assets_selected": "{count} seleccionados", "share_dialog_preparing": "Preparando...", "share_link": "Ligazón para Compartir", + "share_original": "Utilizar orixinal (grande)", + "share_preview": "Utilizar miniatura (pequena)", "shared": "Compartido", "shared_album_activities_input_disable": "O comentario está desactivado", "shared_album_activity_remove_content": "Quere eliminar esta actividade?", @@ -2263,6 +2272,7 @@ "slideshow_repeat_description": "Volver ao principio ao rematar a presentación de diapositivas", "slideshow_settings": "Configuración da presentación", "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...", "sort_created": "Data de creación", "sort_items": "Número de elementos", @@ -2383,11 +2393,13 @@ "trash_page_title": "Lixo ({count})", "trashed_items_will_be_permanently_deleted_after": "Os elementos no lixo eliminaranse permanentemente despois de {days, plural, one {# día} other {# días}}.", "trigger": "Disparador", + "trigger_asset_metadata_extraction": "Extracción de metadatos de activos", + "trigger_asset_metadata_extraction_description": "Activado cando se extraen os metadatos EXIF dun recurso", "trigger_asset_uploaded": "Carga de activos", "trigger_asset_uploaded_description": "Actívase cando se carga un activo novo", "trigger_description": "Un evento que inicia o fluxo de traballo", "trigger_person_recognized": "Persoa recoñecida", - "trigger_person_recognized_description": "Actívase cando se detecta a unha persoa", + "trigger_person_recognized_description": "Actívase cando se recoñece a unha persoa", "trigger_type": "TIpo de disparador", "troubleshoot": "Solucionar problemas", "type": "Tipo", diff --git a/i18n/gsw.json b/i18n/gsw.json index 4f2381054d..0841a4be59 100644 --- a/i18n/gsw.json +++ b/i18n/gsw.json @@ -1391,7 +1391,6 @@ "map_location_picker_page_use_location": "Ufnahmeort verwände", "map_location_service_disabled_content": "D’Ortigsdienscht müend aktiviert si, um Inhält am aktuelle Standort aazeige z chönne. Wotsch d’Ortigsdienscht jetzt aktiviere?", "map_location_service_disabled_title": "Ortigsdienscht deaktiviert", - "map_marker_for_images": "Charte-Markierige für Bilder, wo i {city}, {country} ufgnoh worde sind", "map_marker_with_image": "Charte-Markierig mit Bild", "map_no_location_permission_content": "D’Ortigsdienscht müend aktiviert si, um Inhält am aktuelle Standort aazeige z chönne. Wotsch d’Ortigsdienscht jetzt aktiviere?", "map_no_location_permission_title": "Kei Zuegriff uf dä Standort", @@ -1528,5 +1527,8 @@ "on_this_device": "Uf däm Grät", "onboarding": "Iistig", "onboarding_locale_description": "Wähl dini bevorzugti Sprooch. Du chasch die au spöter i dine Iistellige ändere.", - "onboarding_privacy_description": "Diä folgende (optionali) Funktione hänged vo externä Diänscht ab und chönd jederziit i de Iistellige deaktiviärt wärde." + "onboarding_privacy_description": "Diä folgende (optionali) Funktione hänged vo externä Diänscht ab und chönd jederziit i de Iistellige deaktiviärt wärde.", + "upload_finished": "Ufelade beändet", + "users": "Benutzer", + "waiting": "Usstehend" } diff --git a/i18n/he.json b/i18n/he.json index 87e7259533..e9e66275b7 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -1498,7 +1498,6 @@ "map_location_picker_page_use_location": "השתמש במיקום הזה", "map_location_service_disabled_content": "שירות המיקום צריך להיות מופעל כדי להציג תמונות מהמיקום הנוכחי שלך. האם ברצונך להפעיל אותו עכשיו?", "map_location_service_disabled_title": "שירות מיקום מבוטל", - "map_marker_for_images": "סמן מפה לתמונות שצולמו ב{city}, {country}", "map_marker_with_image": "סמן מפה עם תמונה", "map_no_location_permission_content": "יש צורך בהרשאה למיקום כדי להציג תמונות מהמיקום הנוכחי שלך. האם ברצונך לאפשר זאת עכשיו?", "map_no_location_permission_title": "הרשאה למיקום נדחתה", diff --git a/i18n/hi.json b/i18n/hi.json index f2e357bc70..4707abf338 100644 --- a/i18n/hi.json +++ b/i18n/hi.json @@ -1479,7 +1479,6 @@ "map_location_picker_page_use_location": "इस स्थान का उपयोग करें", "map_location_service_disabled_content": "आपके वर्तमान स्थान की संपत्तियाँ प्रदर्शित करने के लिए स्थान सेवा सक्षम होनी चाहिए। क्या आप इसे अभी सक्षम करना चाहते हैं?", "map_location_service_disabled_title": "स्थान सेवा अक्षम", - "map_marker_for_images": "{city}, {country} में ली गई छवियों के लिए मानचित्र मार्कर", "map_marker_with_image": "छवि के साथ मानचित्र मार्कर", "map_no_location_permission_content": "आपके वर्तमान स्थान से संपत्तियाँ प्रदर्शित करने के लिए स्थान अनुमति आवश्यक है। क्या आप इसे अभी अनुमति देना चाहते हैं?", "map_no_location_permission_title": "स्थान की अनुमति अस्वीकृत", diff --git a/i18n/hr.json b/i18n/hr.json index 21c6fc7c75..3242a9d870 100644 --- a/i18n/hr.json +++ b/i18n/hr.json @@ -1414,7 +1414,6 @@ "map_location_picker_page_use_location": "Koristi ovu lokaciju", "map_location_service_disabled_content": "Usluga lokacije mora biti omogućena za prikaz stavki s vaše trenutne lokacije. Želite li je sada omogućiti?", "map_location_service_disabled_title": "Usluga lokacije onemogućena", - "map_marker_for_images": "Oznaka karte za slike snimljene u {city}, {country}", "map_marker_with_image": "Oznaka karte sa slikom", "map_no_location_permission_content": "Potrebno je dopuštenje za lokaciju kako bi se prikazale stavke s vaše trenutne lokacije. Želite li ga sada omogućiti?", "map_no_location_permission_title": "Dopuštenje za lokaciju odbijeno", diff --git a/i18n/hu.json b/i18n/hu.json index dddcdeeee8..529faf46f8 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -196,13 +196,16 @@ "maintenance_integrity_check": "Ellenőrizze", "maintenance_integrity_check_all": "Mind ellenőrzése", "maintenance_integrity_checksum_mismatch": "Ellenőrzőösszeg-eltérés", + "maintenance_integrity_checksum_mismatch_description": "Azon fájlok, amelyek lemezen tárolt ellenőrzőösszege eltér az Immich adatbázisában szereplőtől.", "maintenance_integrity_checksum_mismatch_job": "Az ellenőrzőösszegek eltéréseinek ellenőrzése", "maintenance_integrity_checksum_mismatch_refresh_job": "Az ellenőrzőösszeg-eltérésről szóló jelentések frissítése", "maintenance_integrity_missing_file": "Hiányzó fájlok", + "maintenance_integrity_missing_file_description": "Az Immich adatbázisában szereplő, de a fájlrendszerben nem létező fájlok.", "maintenance_integrity_missing_file_job": "Hiányzó fájlok ellenőrzése", "maintenance_integrity_missing_file_refresh_job": "Hiányzó fájl riport frissítése", "maintenance_integrity_report": "Integritási jelentés", "maintenance_integrity_untracked_file": "Nem figyelt/követett fájlok", + "maintenance_integrity_untracked_file_description": "Az Immich könyvtáraiban található, de a nyilvántartásában nem szereplő fájlok.", "maintenance_integrity_untracked_file_job": "Nem követett fájlok ellenőrzése", "maintenance_integrity_untracked_file_refresh_job": "Nem nyomon követett fájlok riportjának frissítése", "maintenance_restore_backup": "Biztonsági mentés visszaállítása", @@ -1545,7 +1548,7 @@ "map_location_picker_page_use_location": "Kiválasztott hely használata", "map_location_service_disabled_content": "A helymeghatározás szolgáltatást engedélyezni kell a jelenlegi helyednél lévő elemek megjelenítéséhez. Szeretnéd most engedélyezni?", "map_location_service_disabled_title": "Helymeghatározás szolgáltatás letiltva", - "map_marker_for_images": "{country}, {city} helyen készült képek térképjelölője", + "map_marker_for_image": "Térképjelölő a következő helyen készült képhez: {city}, {country}", "map_marker_with_image": "Térképjelölő képpel", "map_no_location_permission_content": "A helymeghatározást engedélyezni kell a jelenlegi helyednél lévő elemek megjelenítéséhez. Szeretnéd most engedélyezni?", "map_no_location_permission_title": "Helymeghatározás letiltva", @@ -2119,7 +2122,7 @@ "server_privacy": "Szerver biztonság", "server_restarting_description": "Az oldal pillanatokon belül frissül.", "server_restarting_title": "A szerver újraindul", - "server_stats": "Szerver statisztikák", + "server_stats": "Szerver statisztika", "server_update_available": "Szerverfrissítés érhető el", "server_version": "Szerver verzió", "set": "Beállít", diff --git a/i18n/id.json b/i18n/id.json index 921a6ef02a..11ff302352 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -1548,7 +1548,6 @@ "map_location_picker_page_use_location": "Gunakan lokasi ini", "map_location_service_disabled_content": "Layanan lokasi perlu diaktifkan untuk menampilkan aset yang terletak di lokasi Anda saat ini. Ingin mengaktifkan layanan tersebut sekarang?", "map_location_service_disabled_title": "Layanan Lokasi nonaktif", - "map_marker_for_images": "Penanda peta untuk gambar yang diambil di {city}, {country}", "map_marker_with_image": "Penanda peta dengan gambar", "map_no_location_permission_content": "Izin lokasi diperlukan untuk menampilkan aset yang terletak di lokasi Anda. Ingin mengizinkannya sekarang?", "map_no_location_permission_title": "Izin Lokasi ditolak", diff --git a/i18n/it.json b/i18n/it.json index 1aaf0abb1f..f1521ef214 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "Attiva ricerca intelligente", "machine_learning_smart_search_enabled_description": "Se disabilitato le immagini non saranno codificate per la ricerca intelligente.", "machine_learning_url_description": "URL del server machine learning. Se sono stati forniti più di un URL, verrà testato un server alla volta finché uno non risponderà, in ordine dal primo all'ultimo. I server che non rispondono saranno temporaneamente ignorati finché non torneranno online.", + "maintenance_backup_management": "Gestione backup", "maintenance_delete_backup": "Elimina backup", "maintenance_delete_backup_description": "Questo file sarà eliminato in modo irreversibile.", "maintenance_delete_error": "Impossibile eliminare il backup.", + "maintenance_integrity_check": "Verifica", "maintenance_integrity_check_all": "Verifica Tutto", "maintenance_integrity_checksum_mismatch": "I checksum non corrispondono", + "maintenance_integrity_checksum_mismatch_description": "File il cui checksum sul disco non corrisponde al checksum memorizzato da Immich nel database.", "maintenance_integrity_checksum_mismatch_job": "Verifica corrispondenza dei checksum", "maintenance_integrity_checksum_mismatch_refresh_job": "Aggiorna report corrispondenze dei checksum", "maintenance_integrity_missing_file": "File mancanti", + "maintenance_integrity_missing_file_description": "File che Immich ha registrato nel suo database ma che non esistono sul file system.", "maintenance_integrity_missing_file_job": "Verifica file mancanti", "maintenance_integrity_missing_file_refresh_job": "Aggiorna report sui file mancanti", "maintenance_integrity_report": "Report di integrità", "maintenance_integrity_untracked_file": "File non tracciati", + "maintenance_integrity_untracked_file_description": "File nelle directory di Immich di cui Immich non ha alcuna traccia.", "maintenance_integrity_untracked_file_job": "Verifica presenza file non tracciati", "maintenance_integrity_untracked_file_refresh_job": "Aggiorna il report dei file non tracciati", "maintenance_restore_backup": "Ripristina backup", @@ -1543,7 +1548,6 @@ "map_location_picker_page_use_location": "Usa questa posizione", "map_location_service_disabled_content": "I servizi di geolocalizzazione devono essere attivati per poter visualizzare le risorse dalla tua posizione attuale. Vuoi attivarli adesso?", "map_location_service_disabled_title": "Servizio Localizzazione disattivato", - "map_marker_for_images": "Indicatore mappa per le immagini scattate in {city}, {country}", "map_marker_with_image": "Segnaposto con immagine", "map_no_location_permission_content": "L'accesso alla posizione è necessario per visualizzare le risorse dalla tua posizione attuale. Vuoi consentirlo adesso?", "map_no_location_permission_title": "Autorizzazione Posizione negata", @@ -1598,7 +1602,7 @@ }, "media_type": "Tipo Media", "memories": "Ricordi", - "memories_all_caught_up": "Tutto a posto", + "memories_all_caught_up": "Niente di nuovo", "memories_check_back_tomorrow": "Torna domani per altri ricordi", "memories_setting_description": "Gestisci cosa vedi nei tuoi ricordi", "memories_start_over": "Ricomincia", diff --git a/i18n/ja.json b/i18n/ja.json index 5244a07aeb..8fa46e1f4d 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -1532,7 +1532,6 @@ "map_location_picker_page_use_location": "この位置情報を使う", "map_location_service_disabled_content": "現在地の項目を表示するには位置情報がオンである必要があります。有効化しますか?", "map_location_service_disabled_title": "位置情報がオフです", - "map_marker_for_images": "{country} {city}で撮影された写真の地図マーカー", "map_marker_with_image": "画像の地図マーカー", "map_no_location_permission_content": "現在地の項目を表示するには位置情報へのアクセスが必要です。許可しますか?", "map_no_location_permission_title": "位置情報へのアクセスが拒否されました", diff --git a/i18n/kn.json b/i18n/kn.json index f2abb33927..cecb86b8d4 100644 --- a/i18n/kn.json +++ b/i18n/kn.json @@ -1101,7 +1101,6 @@ "map": "ನಕ್ಷೆ", "map_cannot_get_user_location": "ಬಳಕೆದಾರರ ಸ್ಥಳವನ್ನು ಪಡೆಯಲು ಸಾಧ್ಯವಿಲ್ಲ", "map_location_service_disabled_content": "ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಸ್ಥಳದಿಂದ ಸ್ವತ್ತುಗಳನ್ನು ಪ್ರದರ್ಶಿಸಲು ಸ್ಥಳ ಸೇವೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುವ ಅಗತ್ಯವಿದೆ. ನೀವು ಈಗ ಅದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ಬಯಸುವಿರಾ?", - "map_marker_for_images": "{city}, {country} ದಲ್ಲಿ ತೆಗೆದ ಚಿತ್ರಗಳಿಗಾಗಿ ನಕ್ಷೆ ಮಾರ್ಕರ್", "map_marker_with_image": "ಚಿತ್ರದೊಂದಿಗೆ ನಕ್ಷೆ ಮಾರ್ಕರ್", "map_no_location_permission_content": "ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಸ್ಥಳದಿಂದ ಸ್ವತ್ತುಗಳನ್ನು ಪ್ರದರ್ಶಿಸಲು ಸ್ಥಳ ಅನುಮತಿ ಅಗತ್ಯವಿದೆ. ನೀವು ಈಗ ಅದನ್ನು ಅನುಮತಿಸಲು ಬಯಸುವಿರಾ?", "map_zoom_to_see_photos": "ಫೋಟೋಗಳನ್ನು ನೋಡಲು ಝೂಮ್ ಔಟ್ ಮಾಡಿ", diff --git a/i18n/ko.json b/i18n/ko.json index ed75b6114f..869054d555 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -56,9 +56,9 @@ "backup_database": "데이터베이스 덤프 생성", "backup_database_enable_description": "데이터베이스 덤프 활성화", "backup_keep_last_amount": "보관할 이전 덤프 수", - "backup_onboarding_1_description": "개는 클라우드나 다른 물리적 위치에 보관합니다.", - "backup_onboarding_2_description": "개는 서로 다른 로컬 장치에 보관하고,", - "backup_onboarding_3_description": "개의 데이터 사본을 만듭니다.", + "backup_onboarding_1_description": "클라우드 또는 다른 물리적 위치에 오프사이트 사본을 보관합니다.", + "backup_onboarding_2_description": "여러 장치에 로컬 복사본이 있습니다. 여기에는 주요 파일과 해당 파일의 로컬 백업이 포함됩니다.", + "backup_onboarding_3_description": "원본 파일을 포함한 데이터의 모든 사본 수입니다. 여기에는 오프사이트 사본 1개와 로컬 사본 2개가 포함됩니다.", "backup_onboarding_description": "데이터 보호를 위해 3-2-1 백업 전략 사용을 권장합니다. 백업에는 업로드한 사진 및 동영상뿐 아니라 Immich 데이터베이스도 포함되어야 합니다.", "backup_onboarding_footer": "Immich 백업에 대한 자세한 내용은 공식 문서를 참조하세요.", "backup_onboarding_parts_title": "3-2-1 백업이란:", @@ -181,7 +181,7 @@ "machine_learning_ocr_min_recognition_score": "최소 인식 점수", "machine_learning_ocr_min_score_recognition_description": "인식할 텍스트의 최소 신뢰도 점수를 0~1 범위에서 설정합니다. 값이 작을수록 더 많은 텍스트를 인식하지만 잘못 인식될 가능성도 높아집니다.", "machine_learning_ocr_model": "OCR 모델", - "machine_learning_ocr_model_description": "서버 모델은 모바일 모델보다 정확하지만, 처리 시간이 길어지고 메모리 사용량도 늘어납니다.", + "machine_learning_ocr_model_description": "서버 모델은 모바일 모델보다 정확도가 높지만 처리 시간이 더 오래 걸리고 메모리 사용량도 더 많습니다.", "machine_learning_settings": "기계 학습 설정", "machine_learning_settings_description": "기계 학습 시 사용할 모델과 세부 설정을 관리합니다.", "machine_learning_smart_search": "스마트 검색", @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "스마트 검색 활성화", "machine_learning_smart_search_enabled_description": "비활성화하면 스마트 검색을 위한 이미지 처리를 진행하지 않습니다.", "machine_learning_url_description": "기계 학습 서버의 URL을 설정합니다. 여러 개가 입력되면 첫 번째부터 한 번에 하나씩 순서대로 응답하는 서버를 찾을 때까지 요청을 시도합니다. 응답하지 않는 서버는 다시 사용 가능할 때까지 일시적으로 제외됩니다.", + "maintenance_backup_management": "백업 관리", "maintenance_delete_backup": "백업 삭제", "maintenance_delete_backup_description": "이 파일이 영구적으로 삭제됩니다.", "maintenance_delete_error": "백업 삭제에 실패했습니다.", + "maintenance_integrity_check": "체크", "maintenance_integrity_check_all": "전체선택", "maintenance_integrity_checksum_mismatch": "체크섬 불일치", + "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": "백업 복원", @@ -1543,7 +1548,7 @@ "map_location_picker_page_use_location": "이 위치 사용", "map_location_service_disabled_content": "현재 위치의 항목을 표시하려면 위치 서비스를 활성화해야 합니다. 지금 활성화하시겠습니까?", "map_location_service_disabled_title": "위치 서비스 비활성화됨", - "map_marker_for_images": "{country}, {city}에서 촬영된 이미지의 지도 마커", + "map_marker_for_image": "{city}, {country}에서 촬영한 이미지의 지도 마커입니다.", "map_marker_with_image": "이미지가 있는 지도 마커", "map_no_location_permission_content": "현재 위치의 항목을 표시하려면 위치 권한이 필요합니다. 지금 허용하시겠습니까?", "map_no_location_permission_title": "위치 권한 거부됨", @@ -1836,7 +1841,7 @@ "play_motion_photo": "모션 포토 재생", "play_or_pause_video": "동영상 재생/일시 정지", "play_original_video": "원본 동영상 재생", - "play_original_video_setting_description": "트랜스코딩된 영상보다 원본 영상을 우선 재생합니다. 원본이 호환되지 않는 형식인 경우 정상적으로 재생되지 않을 수 있습니다.", + "play_original_video_setting_description": "변환된 영상보다는 원본 영상을 재생하는 것을 권장합니다. 원본 영상이 호환되지 않으면 제대로 재생되지 않을 수 있습니다.", "play_transcoded_video": "트랜스코딩 동영상 재생", "please_auth_to_access": "계속 진행하려면 인증하세요.", "plugin_method_filter_type": "필터", diff --git a/i18n/lt.json b/i18n/lt.json index b6194eba1c..9b7c2f02ad 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "Įjungti išmaniąją paiešką", "machine_learning_smart_search_enabled_description": "Jei išjungta, vaizdai nebus užkoduoti išmaniajai paieškai.", "machine_learning_url_description": "Mašininio mokymosi serverio URL. Jei pateikta daugiau nei vienas URL, serveriai bus bandomi eilės tvarka nuo pirmo iki paskutinio tol, kol bus rastas vienas veikiantis serveris.", + "maintenance_backup_management": "Atsarginių kopijų tvarkymas", "maintenance_delete_backup": "Ištrinti atsarginę kopiją", "maintenance_delete_backup_description": "Šis failas bus negrįžtamai ištrintas.", "maintenance_delete_error": "Nepavyko ištrinti atsarginės kopijos.", + "maintenance_integrity_check": "Tikrinti", "maintenance_integrity_check_all": "Tikrinti Visus", "maintenance_integrity_checksum_mismatch": "Checksum neatitikimas", + "maintenance_integrity_checksum_mismatch_description": "Failai, kurių kontrolinė suma diske nesutampa su Immich duomenų bazėje įrašyta kontroline suma.", "maintenance_integrity_checksum_mismatch_job": "Tikrinti checksum neatitikimų", "maintenance_integrity_checksum_mismatch_refresh_job": "Atnaujinti checksum neatitikimo ataskaitas", "maintenance_integrity_missing_file": "Trūkstami failai", + "maintenance_integrity_missing_file_description": "Failai, įtraukti į Immich duomenų bazę, tačiau neegzistuojantys failų sistemoje.", "maintenance_integrity_missing_file_job": "Tikrinti, ar nėra trūkstamų failų", "maintenance_integrity_missing_file_refresh_job": "Atnaujinti trūkstamų failų ataskaitas", "maintenance_integrity_report": "Vientisumo Ataskaita", "maintenance_integrity_untracked_file": "Nesekami Failai", + "maintenance_integrity_untracked_file_description": "Failai Immich kataloguose, apie kuriuos Immich neturi jokių įrašų.", "maintenance_integrity_untracked_file_job": "Patikrinti, ar nėra nesekamų failų", "maintenance_integrity_untracked_file_refresh_job": "Atnaujinti nesekamų failų ataskaitas", "maintenance_restore_backup": "Atstatyti atsarginę kopiją", @@ -209,7 +214,7 @@ "maintenance_restore_backup_unknown_version": "Nepavyko nustatyti atsarginės kopijos versijos.", "maintenance_restore_database_backup": "Atstatyti duomenų bazę", "maintenance_restore_database_backup_description": "Grąžinti į ankstesnę duomenų bazės būseną naudojant atsarginę kopiją", - "maintenance_settings": "Aptarnavimas", + "maintenance_settings": "Priežiūra", "maintenance_settings_description": "Perjungti „Immich“ į aptarnavimo režimą.", "maintenance_start": "Perjungti į aptarnavimo režimą", "maintenance_start_error": "Nepavyko paleisti aptarnavimo režimo.", @@ -1543,7 +1548,7 @@ "map_location_picker_page_use_location": "Naudoti šią vietovę", "map_location_service_disabled_content": "Vietovės servisas turi būti įjungtas, kad rodytų elementus iš dabartinės vietovės. Įjungti vietovės servisą?", "map_location_service_disabled_title": "Vietovės servisas išjungtas", - "map_marker_for_images": "Žemėlapio žymeklis nuotraukoms yra {city}, {country}", + "map_marker_for_image": "Žemėlapio žymeklis nuotraukai, padarytai {city}, {country}", "map_marker_with_image": "Žemėlapio žymeklis su nuotrauka", "map_no_location_permission_content": "Reikalingas vietovės leidimas, kad rodytų elementus iš dabartinės vietovės. Ar norite suteikti leidimą?", "map_no_location_permission_title": "Vietovės leidimas atmestas", diff --git a/i18n/lv.json b/i18n/lv.json index 37afd3e2dd..95e4ff12b2 100644 --- a/i18n/lv.json +++ b/i18n/lv.json @@ -1498,7 +1498,6 @@ "map_location_picker_page_use_location": "Izvēlēties šo atrašanās vietu", "map_location_service_disabled_content": "Lai tiktu rādīti jūsu pašreizējās atrašanās vietas faili, ir jāaktivizē atrašanās vietas pakalpojums. Vai vēlaties to iespējot tagad?", "map_location_service_disabled_title": "Atrašanās vietas Pakalpojums atslēgts", - "map_marker_for_images": "Kartes marķieris attēliem, kas uzņemti {city}, {country}", "map_marker_with_image": "Kartes marķieris ar attēlu", "map_no_location_permission_content": "Atrašanās vietas atļauja ir nepieciešama, lai parādītu jūsu pašreizējās atrašanās vietas aktīvus. Vai vēlaties to atļaut tagad?", "map_no_location_permission_title": "Atrašanās vietas Atļaujas liegtas", diff --git a/i18n/ml.json b/i18n/ml.json index a6365aff39..760bd8b47a 100644 --- a/i18n/ml.json +++ b/i18n/ml.json @@ -1346,7 +1346,6 @@ "map_location_picker_page_use_location": "ഈ സ്ഥലം ഉപയോഗിക്കുക", "map_location_service_disabled_content": "നിങ്ങളുടെ നിലവിലെ സ്ഥാനത്ത് നിന്നുള്ള അസറ്റുകൾ പ്രദർശിപ്പിക്കുന്നതിന് ലൊക്കേഷൻ സേവനം പ്രവർത്തനക്ഷമമാക്കേണ്ടതുണ്ട്. ഇപ്പോൾ പ്രവർത്തനക്ഷമമാക്കണോ?", "map_location_service_disabled_title": "ലൊക്കേഷൻ സേവനം പ്രവർത്തനരഹിതമാക്കി", - "map_marker_for_images": "{city}, {country} എന്നിവിടങ്ങളിൽ എടുത്ത ചിത്രങ്ങൾക്കുള്ള മാപ്പ് മാർക്കർ", "map_marker_with_image": "ചിത്രത്തോടുകൂടിയ മാപ്പ് മാർക്കർ", "map_no_location_permission_content": "നിങ്ങളുടെ നിലവിലെ സ്ഥാനത്ത് നിന്നുള്ള അസറ്റുകൾ പ്രദർശിപ്പിക്കുന്നതിന് ലൊക്കേഷൻ അനുമതി ആവശ്യമാണ്. ഇപ്പോൾ അനുവദിക്കണോ?", "map_no_location_permission_title": "ലൊക്കേഷൻ അനുമതി നിഷേധിച്ചു", diff --git a/i18n/mr.json b/i18n/mr.json index 2052cc2b5b..42260406e7 100644 --- a/i18n/mr.json +++ b/i18n/mr.json @@ -1341,7 +1341,6 @@ "map_location_picker_page_use_location": "हे लोकेशन वापरा", "map_location_service_disabled_content": "सध्याच्या लोकेशनवरील अॅसेट्स दाखवण्यासाठी लोकेशन सेवा सक्षम असणे आवश्यक आहे. तुम्हाला ती आत्ता सक्षम करायची आहे का?", "map_location_service_disabled_title": "लोकेशन सेवा बंद आहे", - "map_marker_for_images": "{city}, {country} येथे घेतलेल्या प्रतिमांसाठी नकाशा मार्कर", "map_marker_with_image": "प्रतिमेसह नकाशा मार्कर", "map_no_location_permission_content": "सध्याच्या लोकेशनवरील अॅसेट्स दाखवण्यासाठी लोकेशन परवानगी आवश्यक आहे. तुम्हाला ती परवानगी आत्ता द्यायची आहे का?", "map_no_location_permission_title": "लोकेशन परवानगी नाकारली", diff --git a/i18n/nb_NO.json b/i18n/nb_NO.json index 1ead05e75e..f4dd1592fa 100644 --- a/i18n/nb_NO.json +++ b/i18n/nb_NO.json @@ -80,7 +80,7 @@ "cron_expression_presets": "Forhåndsinnstillinger for Cron-uttrykk", "disable_login": "Deaktiver innlogging", "download_csv": "Last ned CSV", - "duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage lignende bilder. Krever bruk av Smart Søk", + "duplicate_detection_job_description": "Kjør maskinlæring på filer for å oppdage lignende bilder. Krever bruk av smartsøk", "exclusion_pattern_description": "Ekskluderingsmønstre lar deg ignorere filer og mapper når du skanner biblioteket ditt. Dette er nyttig hvis du har mapper som inneholder filer du ikke vil importere, for eksempel RAW-filer.", "export_config_as_json_description": "Last ned nåværende systemkonfigurasjon som en JSON fil", "external_libraries_page_description": "Administrering for eksterne bibliotek", @@ -187,20 +187,25 @@ "machine_learning_smart_search": "Smart søk", "machine_learning_smart_search_description": "Søk etter bilder semantisk ved å bruke CLIP-embeddings", "machine_learning_smart_search_enabled": "Aktiver smart søk", - "machine_learning_smart_search_enabled_description": "Hvis deaktivert, vil bilder ikke bli enkodet for smart søk.", + "machine_learning_smart_search_enabled_description": "Hvis deaktivert så blir ikke bilder kodet for smartsøk.", "machine_learning_url_description": "URL til maskinlærings-serveren. Hvis mer enn en URL er lagt inn, hver server vill bli forsøkt en om gangen frem til en svarer suksessfullt, i rekkefølge fra først til sist. Servere som ikke svarer vil midlertidig bli oversett frem til dem svarer igjen.", + "maintenance_backup_management": "Administrasjon av sikkerhetskopier", "maintenance_delete_backup": "Slett sikkerhetskopi", "maintenance_delete_backup_description": "Denne filen vil bli permanent slettet.", "maintenance_delete_error": "Feilet ved sletting av sikkerhetskopi.", + "maintenance_integrity_check": "Sjekk", "maintenance_integrity_check_all": "Velg alle", "maintenance_integrity_checksum_mismatch": "Sjekksum er feil", + "maintenance_integrity_checksum_mismatch_description": "Filer med en sjekksum på disken som ikke stemmer overens med sjekksummen Immich har lagret i databasen sin.", "maintenance_integrity_checksum_mismatch_job": "Sjekk for feilede sjekksummer", "maintenance_integrity_checksum_mismatch_refresh_job": "Oppdater feilede sjekksum rapporter", "maintenance_integrity_missing_file": "Manglende filer", + "maintenance_integrity_missing_file_description": "Filer som Immich har sporet i databasen sin, men som ikke eksisterer i filsystemet.", "maintenance_integrity_missing_file_job": "Sjekk etter manglende filer", "maintenance_integrity_missing_file_refresh_job": "Oppdater rapporten for manglende filer", "maintenance_integrity_report": "Integritetsrapport", "maintenance_integrity_untracked_file": "Usporede filer", + "maintenance_integrity_untracked_file_description": "Filer i Immichs mapper som Immich ikke har noen oversikt over.", "maintenance_integrity_untracked_file_job": "Sjekk etter usporede filer", "maintenance_integrity_untracked_file_refresh_job": "Oppdater rapporten for usporede filer", "maintenance_restore_backup": "Gjenopprett Sikkerhetskopi", @@ -1543,7 +1548,6 @@ "map_location_picker_page_use_location": "Bruk dette stedet", "map_location_service_disabled_content": "Lokasjonstjeneste må være aktivert for å vise elementer fra din nåværende lokasjon. Vil du aktivere det nå?", "map_location_service_disabled_title": "Lokasjonstjeneste deaktivert", - "map_marker_for_images": "Kart makeringer for bilder tatt i {city}, {country}", "map_marker_with_image": "Kartmarkør med bilde", "map_no_location_permission_content": "Lokasjonstilgang er påkrevet for å vise elementer fra din nåværende lokasjon. Vil du tillate det nå?", "map_no_location_permission_title": "Lokasjonstilgang avvist", diff --git a/i18n/ne.json b/i18n/ne.json index 0967ef424b..029f8bb9e6 100644 --- a/i18n/ne.json +++ b/i18n/ne.json @@ -1 +1,17 @@ -{} +{ + "about": "बारे", + "account": "खाता", + "account_settings": "खाता सेटिङ", + "acknowledge": "स्वीकार", + "action": "कार्य", + "action_description": "छानियेको चिजमा सामुहिक कार्य", + "add_a_description": "थप विवरण", + "add_a_location": "स्थान थप्नुहोस्", + "add_a_name": "नाम हाल्नुहोस्", + "add_a_title": "शीर्षक हाल्नुहोस्", + "add_action": "कार्य थप्नुहोस्", + "add_action_description": "कार्य गर्नको लागि थप कार्यमा क्लिक गर्नुहोस्", + "add_assets": "फोटोहरू थप्नुहोस्", + "add_birthday": "जन्मदिन हाल्नुहोस", + "add_endpoint": "अन्तिम बिन्दु थप्नुहोस्" +} diff --git a/i18n/nl.json b/i18n/nl.json index 804d07c311..b5d5198668 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -609,7 +609,7 @@ "asset_skipped": "Overgeslagen", "asset_skipped_in_trash": "In prullenbak", "asset_trashed": "Asset verwijderd", - "asset_troubleshoot": "Asset probleemoplossing", + "asset_troubleshoot": "Item probleemoplossing", "asset_uploaded": "Geüpload", "asset_uploading": "Uploaden…", "asset_viewer_settings_subtitle": "Beheer je instellingen voor galerijweergave", @@ -638,14 +638,14 @@ "assets_were_part_of_album_count": "{count, plural, one {Item was} other {Items waren}} al onderdeel van het album", "assets_were_part_of_albums_count": "{count, plural, one {Item is} other {Items zijn}} al onderdeel van de albums", "authorized_devices": "Geautoriseerde apparaten", - "automatic_endpoint_switching_subtitle": "Maak indien beschikbaar lokaal verbinding via het aangewezen wifi-netwerk en gebruik elders alternatieve verbindingen", + "automatic_endpoint_switching_subtitle": "Maak indien beschikbaar lokaal verbinding via het aangewezen wifinetwerk en gebruik elders alternatieve verbindingen", "automatic_endpoint_switching_title": "Automatische serverwissel", "autoplay_slideshow": "Diavoorstelling automatisch afspelen", "back": "Terug", "back_close_deselect": "Terug, sluiten of deselecteren", "background_backup_running_error": "Back-up draait op de achtergrond, handmatige back-up kan niet worden gestart", "background_location_permission": "Achtergrond locatie toestemming", - "background_location_permission_content": "Om van netwerk te wisselen terwijl de app op de achtergrond draait, heeft Immich *altijd* toegang tot de exacte locatie nodig om de naam van het WiFi-netwerk te kunnen lezen", + "background_location_permission_content": "Om van netwerk te wisselen terwijl de app op de achtergrond draait, heeft Immich *altijd* toegang tot de exacte locatie nodig om de naam van het wifi­netwerk te kunnen lezen", "background_options": "Achtergrond opties", "backup": "Back-up", "backup_album_selection_page_albums_device": "Albums op apparaat ({count})", @@ -680,7 +680,7 @@ "backup_controller_page_background_is_on": "Automatische achtergrond back-up staat aan", "backup_controller_page_background_turn_off": "Achtergrondservice uitzetten", "backup_controller_page_background_turn_on": "Achtergrondservice aanzetten", - "backup_controller_page_background_wifi": "Alleen op WiFi", + "backup_controller_page_background_wifi": "Alleen op wifi", "backup_controller_page_backup": "Back-up", "backup_controller_page_backup_selected": "Geselecteerd: ", "backup_controller_page_backup_sub": "Geback-upte foto's en video's", @@ -785,7 +785,7 @@ "charging_requirement_mobile_backup": "Achtergrond backup vereist dat het apparaat wordt opgeladen", "check_corrupt_asset_backup": "Controleer op corrupte back-ups van items", "check_corrupt_asset_backup_button": "Controle uitvoeren", - "check_corrupt_asset_backup_description": "Voer deze controle alleen uit via WiFi en nadat alle items zijn geback-upt. De procedure kan een paar minuten duren.", + "check_corrupt_asset_backup_description": "Voer deze controle alleen uit via wifi en nadat van alle items een back-up gemaakt is. De procedure kan een paar minuten duren.", "check_logs": "Controleer logboek", "checksum": "Controlegetal", "choose": "Kies", @@ -795,14 +795,14 @@ "cleanup_confirm_prompt_title": "Van dit apparaat verwijderen?", "cleanup_deleted_assets": "{count} items verplaats naar prullenbak van apparaat", "cleanup_deleting": "Naar prullenbak verplaatsen...", - "cleanup_found_assets": "Er zijn {count} backup bestanden gevonden", - "cleanup_found_assets_with_size": "Er zijn {count} back-upbestanden gevonden ({size})", + "cleanup_found_assets": "Er zijn {count} back-up­bestanden gevonden", + "cleanup_found_assets_with_size": "Er zijn {count} back-up­bestanden gevonden ({size})", "cleanup_icloud_shared_albums_excluded": "Gedeelde albums van iCloud zijn uitgesloten van de scan", "cleanup_no_assets_found": "Er zijn geen bestanden gevonden die aan bovenstaande criteria voldoen. Free Up Space kan alleen bestanden verwijderen die op de server zijn geback-upt", "cleanup_preview_title": "Bestanden te verwijderen ({count})", "cleanup_step3_description": "Scan naar back-upbestanden die overeenkomen met uw datum en behoud uw instellingen.", "cleanup_step4_summary": "{count} bestanden (gemaakt vóór {date}) die van uw lokale apparaat moeten worden verwijderd. Foto's blijven toegankelijk via de Immich-app.", - "cleanup_trash_hint": "Om de opslagruimte volledig vrij te maken, opent u de systeemgalerij-app en leegt u de prullenbak", + "cleanup_trash_hint": "Open de galerij-app en leeg de prullenbak om de opslagruimte volledig vrij te maken", "clear": "Wissen", "clear_all": "Alles wissen", "clear_all_recent_searches": "Wis alle recente zoekopdrachten", @@ -814,7 +814,7 @@ "client_cert_enter_password": "Voer wachtwoord in", "client_cert_import": "Importeren", "client_cert_import_success_msg": "Cliëntcertificaat is geïmporteerd", - "client_cert_invalid_msg": "Ongeldig certificaatbestand of verkeerd wachtwoord", + "client_cert_invalid_msg": "Ongeldig certificaat­bestand of verkeerd wachtwoord", "client_cert_password_message": "Voer het wachtwoord voor dit certificaat in", "client_cert_password_title": "Certificaat wachtwoord", "client_cert_remove_msg": "Clientcertificaat is verwijderd", @@ -850,7 +850,7 @@ "confirm_tag_face_unnamed": "Wil je dit gezicht taggen?", "connected_device": "Verbonden apparaat", "connected_to": "Verbonden met", - "contain": "Bevat", + "contain": "Passend", "context": "Context", "continue": "Doorgaan", "control_bottom_app_bar_add_tags": "Tags toevoegen", @@ -1066,7 +1066,7 @@ "enabled": "Ingeschakeld", "end_date": "Einddatum", "enqueued": "In de wachtrij", - "enter_wifi_name": "Voer de WiFi-naam in", + "enter_wifi_name": "Voer de naam van het wifinetwerk in", "enter_your_pin_code": "Voer uw pincode in", "enter_your_pin_code_subtitle": "Voer uw pincode in om toegang te krijgen tot de vergrendelde map", "error": "Fout", @@ -1114,7 +1114,7 @@ "failed_to_stack_assets": "Fout bij stapelen van items", "failed_to_tag_assets": "Fout bij taggen van items", "failed_to_unstack_assets": "Fout bij ontstapelen van items", - "failed_to_update_notification_status": "Kon notificatiestatus niet updaten", + "failed_to_update_notification_status": "Kan notificatie­status niet updaten", "incorrect_email_or_password": "Onjuist e-mailadres of wachtwoord", "library_folder_already_exists": "Dit import­pad bestaat al.", "page_not_found": "Pagina niet gevonden", @@ -1237,7 +1237,7 @@ "external": "Extern", "external_libraries": "Externe bibliotheken", "external_network": "Extern netwerk", - "external_network_sheet_info": "Als je niet verbonden bent met het opgegeven WiFi-netwerk, maakt de app verbinding met de server via de eerst bereikbare URL in de onderstaande lijst, van boven naar beneden", + "external_network_sheet_info": "Als je niet verbonden bent met het opgegeven wifinetwerk, maakt de app verbinding met de server via de eerst bereikbare URL in de onderstaande lijst, van boven naar beneden", "f_number": "Diafragma", "face_unassigned": "Niet toegewezen", "failed": "Mislukt", @@ -1261,7 +1261,7 @@ "filename": "Bestandsnaam", "filetype": "Bestandstype", "filter": "Filter", - "filter_description": "Filtervoorwaarden voor doel items", + "filter_description": "Filter­voorwaarden voor betreffende items", "filter_people": "Filteren op persoon", "filter_places": "Filteren op locatie", "filter_tags": "Filteren op label", @@ -1273,7 +1273,7 @@ "folder": "Map", "folder_not_found": "Map niet gevonden", "folders": "Mappen", - "folders_feature_description": "Bladeren door de mapweergave van de foto's en video's op het bestandssysteem", + "folders_feature_description": "Bladeren door de map­weergave van de foto's en video's op het bestands­systeem", "forgot_pin_code_question": "Pincode vergeten?", "forward": "Vooruit", "free_up_space": "Maak opslag vrij", @@ -1287,7 +1287,7 @@ "geolocation_instruction_location": "Klik op een item met gps-coördinaten om de locatie te gebruiken, of kies een locatie direct op de kaart", "get_help": "Hulp vragen", "get_people_error": "Fout bij ophalen mensen", - "get_wifiname_error": "Kon de WiFi-naam niet ophalen. Zorg ervoor dat je de benodigde machtigingen hebt verleend en verbonden bent met een WiFi-netwerk", + "get_wifiname_error": "Kon de naam van het netwerk niet ophalen. Zorg ervoor dat je de benodigde machtig­ingen hebt verleend en verbonden bent met een wifinetwerk", "getting_started": "Aan de slag", "go_back": "Ga terug", "go_to_folder": "Ga naar map", @@ -1387,9 +1387,9 @@ "invite_to_album": "Uitnodigen voor album", "ios_debug_info_fetch_ran_at": "Ophalen gelukt op {dateTime}", "ios_debug_info_last_sync_at": "Laatst gesynchroniseerd {dateTime}", - "ios_debug_info_no_processes_queued": "Geen achtergrondprocessen in de wachtrij", + "ios_debug_info_no_processes_queued": "Geen achtergrond­processen in de wachtrij", "ios_debug_info_no_sync_yet": "Er is nog geen achtergrondsynchronisatie uitgevoerd", - "ios_debug_info_processes_queued": "{count, plural, one {{count} achtergrondproces in de wachtrij} other {{count} achtergrondprocessen in de wachtrij}}", + "ios_debug_info_processes_queued": "{count, plural, one {{count} achtergrond­proces} other {{count} achtergrond­processen}} in de wachtrij", "ios_debug_info_processing_ran_at": "Verwerking uitgevoerd op {dateTime}", "iso": "ISO", "items_count": "{count, plural, one {# item} other {# items}}", @@ -1456,10 +1456,10 @@ "local_id": "Lokaal ID", "local_media_summary": "Lokale media samenvatting", "local_network": "Lokaal netwerk", - "local_network_sheet_info": "De app maakt verbinding met de server via deze URL wanneer het opgegeven WiFi-netwerk wordt gebruikt", + "local_network_sheet_info": "De app maakt verbinding met de server via deze URL wanneer het opgegeven wifinetwerk wordt gebruikt", "location": "Locatie", "location_permission": "Locatietoestemming", - "location_permission_content": "Om de functie voor automatische serverwissel te gebruiken, heeft Immich toegang tot de exacte locatie nodig om de naam van het huidige WiFi-netwerk te kunnen bepalen", + "location_permission_content": "Om de functie voor automatische serverwissel te gebruiken, heeft Immich toegang tot de exacte locatie nodig om de naam van het huidige wifinetwerk te kunnen bepalen", "location_picker_choose_on_map": "Kies op kaart", "location_picker_latitude_error": "Voer een geldige breedtegraad in", "location_picker_latitude_hint": "Voer hier je breedtegraad in", @@ -1502,8 +1502,8 @@ "longitude": "Lengtegraad", "look": "Uiterlijk", "loop_videos": "Video's herhalen", - "loop_videos_description": "Inschakelen om video's automatisch te herhalen in de detailweergave.", - "main_branch_warning": "Je gebruikt een ontwikkelingsversie. We raden je ten zeerste aan een releaseversie te gebruiken!", + "loop_videos_description": "Inschakelen om video's automatisch te herhalen in de detail­weergave.", + "main_branch_warning": "Je gebruikt een ontwikkelings­versie. We raden je ten zeerste aan een release­versie te gebruiken!", "main_menu": "Hoofdmenu", "maintenance_action_restore": "Database wordt hersteld", "maintenance_description": "Immich is in de onderhouds­modus gezet.", @@ -1512,7 +1512,7 @@ "maintenance_logged_in_as": "Momenteel ingelogd als {user}", "maintenance_restore_from_backup": "Herstellen vanaf backup", "maintenance_restore_library": "Bibliotheek herstellen", - "maintenance_restore_library_confirm": "Als dit er goed uit ziet ga dan verder om de backup terug te zetten!", + "maintenance_restore_library_confirm": "Als dit er goed uit ziet, ga dan verder om de back-up terug te zetten!", "maintenance_restore_library_description": "Database wordt hersteld", "maintenance_restore_library_folder_has_files": "{folder} heeft {count} map(pen)", "maintenance_restore_library_folder_no_files": "{folder} mist bestanden!", @@ -1548,7 +1548,7 @@ "map_location_picker_page_use_location": "Gebruik deze locatie", "map_location_service_disabled_content": "Locatie service moet ingeschakeld zijn om items van je huidige locatie weer te geven. Wil je het nu inschakelen?", "map_location_service_disabled_title": "Locatie service uitgeschakeld", - "map_marker_for_images": "Kaartmarkering voor afbeeldingen gemaakt in {city}, {country}", + "map_marker_for_image": "Kaartmarkering voor afbeelding gemaakt in {city}, {country}", "map_marker_with_image": "Kaartmarkering met afbeelding", "map_no_location_permission_content": "Locatietoestemming is nodig om items van je huidige locatie weer te geven. Wil je dit nu toestaan?", "map_no_location_permission_title": "Locatietoestemming geweigerd", @@ -1983,7 +1983,7 @@ "reset_sqlite": "SQLite database resetten", "reset_sqlite_clear_app_data": "Wis gegevens", "reset_sqlite_confirmation": "Weet je zeker dat je de app-gegevens wilt wissen? Hiermee worden alle instellingen verwijderd en word je uitgelogd.", - "reset_sqlite_confirmation_note": "Let op: Je moet de app opnieuw opstarten nadat je deze hebt gewist.", + "reset_sqlite_confirmation_note": "Let op: je moet de app opnieuw opstarten nadat je deze hebt gewist.", "reset_sqlite_done": "App data is gewist. Start Immich opnieuw op en log opnieuw in.", "reset_sqlite_success": "De SQLite database is succesvol gereset", "reset_to_default": "Resetten naar standaard", @@ -1996,7 +1996,7 @@ "restore_user": "Gebruiker herstellen", "restored_asset": "Item hersteld", "resume": "Hervatten", - "resume_paused_jobs": "Hervat {count, plural, one {# gepauseerde taak} other {# gepauseerde taken}}", + "resume_paused_jobs": "{count, plural, one {# gepauzeerde taak} other {# gepauzeerde taken}} hervatten", "retry_upload": "Opnieuw uploaden", "review_duplicates": "Controleer duplicaten", "review_large_files": "Grote bestanden beoordelen", @@ -2470,8 +2470,8 @@ "use_template": "Gebruik template", "user": "Gebruiker", "user_has_been_deleted": "Deze gebruiker is verwijderd.", - "user_id": "Gebruikers ID", - "user_liked": "{user} heeft {type, select, photo {deze foto} video {deze video} asset {} other {dit item}} geliket", + "user_id": "Gebruikers-ID", + "user_liked": "{user} vindt {type, select, photo {deze foto} video {deze video} asset {} other {dit item}} leuk", "user_pin_code_settings": "Pincode", "user_pin_code_settings_description": "Beheer je pincode", "user_privacy": "Gebruikersprivacy", @@ -2479,7 +2479,7 @@ "user_purchase_settings_description": "Beheer je aankoop", "user_role_set": "{user} instellen als {role}", "user_usage_detail": "Gedetailleerd gebruik van gebruikers", - "user_usage_stats": "Statistieken van accountgebruik", + "user_usage_stats": "Accountstatistieken", "user_usage_stats_description": "Bekijk statistieken van accountgebruik", "username": "Gebruikersnaam", "users": "Gebruikers", @@ -2532,7 +2532,7 @@ "welcome_to_immich": "Welkom bij Immich", "when": "Wanneer", "width": "Breedte", - "wifi_name": "WiFi-naam", + "wifi_name": "Wifinetwerk", "workflow": "Werkstroom", "workflow_delete_prompt": "Weet je zeker dat je deze werkstroom wilt verwijderen?", "workflow_deleted": "Werkstroom verwijderd", @@ -2554,7 +2554,7 @@ "years_ago": "{years, plural, one {Een jaar} other {# jaar}} geleden", "yes": "Ja", "you_dont_have_any_shared_links": "Je hebt geen gedeelde links", - "your_wifi_name": "Je WiFi-naam", + "your_wifi_name": "Je wifinetwerk", "zero_to_clear_rating": "druk op 0 om de sterwaardering te verwijderen", "zoom_image": "Inzoomen", "zoom_to_bounds": "Zoom naar randen" diff --git a/i18n/pl.json b/i18n/pl.json index a92ef20f1a..e7e5a76c8b 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -1548,7 +1548,7 @@ "map_location_picker_page_use_location": "Użyj tej lokalizacji", "map_location_service_disabled_content": "Aby wyświetlić zasoby z Twojej bieżącej lokalizacji, należy włączyć usługę lokalizacyjną. Czy chcesz to teraz włączyć?", "map_location_service_disabled_title": "Usługa lokalizacji wyłączona", - "map_marker_for_images": "Wskaźnik mapy dla zdjęć zrobionych w {city}, {country}", + "map_marker_for_image": "Znacznik na mapie dla zdjęcia wykonanego w {city}, {country}", "map_marker_with_image": "Znacznik na mapie ze zdjęciem", "map_no_location_permission_content": "Aby wyświetlić zasoby z Twojej bieżącej lokalizacji, potrzebne jest pozwolenie na lokalizację. Czy chcesz teraz na to pozwolić?", "map_no_location_permission_title": "Odmowa dostępu do lokalizacji", @@ -2031,7 +2031,7 @@ "search_by_full_path": "Wyszukaj według pełnej ścieżki lub folderu", "search_by_full_path_example": "/John/Projekty/Drukowanie_3D/2026-07-01 – możesz wyszukiwać hasła takie jak Projekty, 3D, Drukowanie, 2026 itp.", "search_by_ocr": "Wyszukaj przy użyciu OCR", - "search_by_ocr_example": "Latte", + "search_by_ocr_example": "Kawa, trampolina", "search_camera_lens_model": "Wyszukaj model obiektywu...", "search_camera_make": "Wyszukaj markę aparatu...", "search_camera_model": "Wyszukaj model aparatu...", @@ -2185,7 +2185,7 @@ "shared_by_you": "Udostępnione przez ciebie", "shared_from_partner": "Zdjęcia od {partner}", "shared_intent_upload_button_progress_text": "{current} / {total} Przesłano", - "shared_link_app_bar_title": "Udostępnione", + "shared_link_app_bar_title": "Udostępnione linki", "shared_link_clipboard_copied_massage": "Skopiowane do schowka", "shared_link_clipboard_text": "Link: {link}\nHasło: {password}", "shared_link_create_error": "Błąd podczas tworzenia linka do udostępnienia", diff --git a/i18n/pt.json b/i18n/pt.json index df05defcca..13d790f04c 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -56,7 +56,7 @@ "backup_database": "Criar Cópia da Base de Dados", "backup_database_enable_description": "Ativar cópias da base de dados", "backup_keep_last_amount": "Quantidade de cópias anteriores a manter", - "backup_onboarding_1_description": "Uma cópia remota na cloud ou outra localização física.", + "backup_onboarding_1_description": "Uma cópia remota na cloud ou noutra localização física.", "backup_onboarding_2_description": "Cópias locais em dispositivos diferentes, incluindo os ficheiros principais e uma cópia de segurança local dos mesmos.", "backup_onboarding_3_description": "Cópias completas dos seus dados, incluindo os ficheiros originais. Inclui uma cópia remota e duas cópias locais.", "backup_onboarding_description": "É recomendada a estratégia de cópia de segurança 3-2-1 para proteger os seus dados. Para uma solução de cópia de segurança completa, deve manter cópias das suas fotos e vídeos tal como da base de dados do Immich.", @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "Ativar a Pesquisa Inteligente", "machine_learning_smart_search_enabled_description": "Se desativado, as imagens não serão codificadas para Pesquisa Inteligente.", "machine_learning_url_description": "A URL do servidor de aprendizagem de máquina. Se for fornecido mais do que um URL, cada servidor será testado, um a um, até um deles responder com sucesso, por ordem do primeiro ao último. Servidores que não responderem serão temporariamente ignorados até voltarem a estar online.", + "maintenance_backup_management": "Gestão de cópias de segurança", "maintenance_delete_backup": "Eliminar Cópia de Segurança", "maintenance_delete_backup_description": "Este ficheiro irá ser apagado para sempre.", "maintenance_delete_error": "Ocorreu um erro ao eliminar a cópia de segurança.", + "maintenance_integrity_check": "Verificar", "maintenance_integrity_check_all": "Verificar tudo", "maintenance_integrity_checksum_mismatch": "Checksum não corresponde", + "maintenance_integrity_checksum_mismatch_description": "Ficheiros dos quais a verificação checksum no disco não condiz com a que o Immich tem armazenado na base de dados.", "maintenance_integrity_checksum_mismatch_job": "Verificar se existem erros de checksum", "maintenance_integrity_checksum_mismatch_refresh_job": "Atualizar relatórios de erros de checksum", "maintenance_integrity_missing_file": "Ficheiros em falta", + "maintenance_integrity_missing_file_description": "Ficheiros dos quais o Immich está a monitorizar na sua base de dados mas que não existem no sistema de ficheiros.", "maintenance_integrity_missing_file_job": "Verificar se existem ficheiros em falta", "maintenance_integrity_missing_file_refresh_job": "Atualizar relatórios de ficheiros em falta", "maintenance_integrity_report": "Relatório de integridade", "maintenance_integrity_untracked_file": "Ficheiros não monitorizados", + "maintenance_integrity_untracked_file_description": "Ficheiros nos diretórios do Immich dos quais não existe qualquer registo.", "maintenance_integrity_untracked_file_job": "Verificar se existem ficheiros não monitorizados", "maintenance_integrity_untracked_file_refresh_job": "Atualizar relatórios de ficheiros não monitorizados", "maintenance_restore_backup": "Restaurar Cópia de Segurança", @@ -655,7 +660,7 @@ "backup_background_service_complete_notification": "Cópia de conteúdos concluída", "backup_background_service_connection_failed_message": "Ocorreu um erro na ligação ao servidor. A tentar de novo…", "backup_background_service_current_upload_notification": "A enviar {filename}", - "backup_background_service_default_notification": "A verificar se há novos ficheiros…", + "backup_background_service_default_notification": "A verificar a existência de novos ficheiros…", "backup_background_service_error_title": "Erro na cópia de segurança", "backup_background_service_in_progress_notification": "A fazer cópia de segurança dos seus ficheiros…", "backup_background_service_upload_failure_notification": "Ocorreu um erro ao enviar {filename}", @@ -778,8 +783,8 @@ "changed_visibility_successfully": "Visibilidade alterada com sucesso", "charging": "A carregar", "charging_requirement_mobile_backup": "Cópia de segurança de fundo necessita que o dispositivo esteja a carregar", - "check_corrupt_asset_backup": "Verificar por backups corrompidos", - "check_corrupt_asset_backup_button": "Verificar", + "check_corrupt_asset_backup": "Verificar por cópias de segurança de ficheiros corrompidas", + "check_corrupt_asset_backup_button": "Realizar verificação", "check_corrupt_asset_backup_description": "Execute esta verificação apenas numa rede Wi-Fi e quando a cópia de segurança de todos os ficheiros já estiver concluída. O processo pode demorar alguns minutos.", "check_logs": "Verificar registos", "checksum": "Teste de soma de dados", @@ -1543,7 +1548,6 @@ "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_images": "Marcador no mapa para fotos tiradas 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", diff --git a/i18n/pt_BR.json b/i18n/pt_BR.json index cbb96657db..bedf3314ee 100644 --- a/i18n/pt_BR.json +++ b/i18n/pt_BR.json @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "Habilitar a Pesquisa Inteligente", "machine_learning_smart_search_enabled_description": "Se desativado, as imagens não serão codificadas para pesquisa inteligente.", "machine_learning_url_description": "A URL do servidor de aprendizado de máquina. Se mais de uma URL for fornecida, elas serão tentadas, uma de cada vez e na ordem indicada, até que uma responda com sucesso. Servidores que não responderem serão ignorados temporariamente até voltarem a estar conectados.", + "maintenance_backup_management": "Gerenciamento de backup", "maintenance_delete_backup": "Excluir Backup", "maintenance_delete_backup_description": "Este arquivo será excluído de forma irreversível.", "maintenance_delete_error": "Falha ao excluir o backup.", + "maintenance_integrity_check": "Verificar", "maintenance_integrity_check_all": "Verificar tudo", "maintenance_integrity_checksum_mismatch": "Checksum não corresponde", + "maintenance_integrity_checksum_mismatch_description": "Arquivos cujo o checksum atual não corresponde ao checksum que Immich armazenou no banco de dados.", "maintenance_integrity_checksum_mismatch_job": "Verificar se há erros de checksum", "maintenance_integrity_checksum_mismatch_refresh_job": "Atualizar # de checksum sem correspondência", "maintenance_integrity_missing_file": "Arquivos não encontrados", + "maintenance_integrity_missing_file_description": "Arquivos que Immich rastreou em seu banco de dados, mas que não existem no sistema de arquivos.", "maintenance_integrity_missing_file_job": "Verificar se há arquivos não encontrados", "maintenance_integrity_missing_file_refresh_job": "Atualizar # de arquivos não encontrados", "maintenance_integrity_report": "Relatório de integridade", "maintenance_integrity_untracked_file": "Arquivos não rastreados", + "maintenance_integrity_untracked_file_description": "Arquivos não rastreados dentro dos diretórios do Immich.", "maintenance_integrity_untracked_file_job": "Verificar se há arquivos não rastreados", "maintenance_integrity_untracked_file_refresh_job": "Atualizar # de arquivos não rastreados", "maintenance_restore_backup": "Restaurar Backup", @@ -1543,7 +1548,7 @@ "map_location_picker_page_use_location": "Use esta localização", "map_location_service_disabled_content": "O serviço de localização precisa estar ativado para exibir os arquivos da sua localização atual. Deseja ativar agora?", "map_location_service_disabled_title": "Serviço de localização desativado", - "map_marker_for_images": "Marcador de mapa para imagens tiradas em {city}, {country}", + "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": "É necessária a permissão de localização para exibir os arquivos da sua localização atual. Deseja conceder a permissão agora?", "map_no_location_permission_title": "Permissão de localização foi negada", diff --git a/i18n/ro.json b/i18n/ro.json index 89ddec48f8..ab092d591d 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -1523,7 +1523,6 @@ "map_location_picker_page_use_location": "Folosește această locație", "map_location_service_disabled_content": "Serviciul de localizare trebuie să fie activat pentru a afișa resursele din locația actuală. Dorești să o activezi acum?", "map_location_service_disabled_title": "Serviciul de localizare este dezactivat", - "map_marker_for_images": "Marcator de hartă pentru imaginile realizate în {city}, {country}", "map_marker_with_image": "Marcator de hartă cu imagine", "map_no_location_permission_content": "Permisiunea de localizare este necesară pentru a afișa resursele din locația actuală. Dorești să o activezi acum?", "map_no_location_permission_title": "Permisiunea de localizare este dezactivată", diff --git a/i18n/ru.json b/i18n/ru.json index 7aca149e69..095fc0fb8b 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -1548,8 +1548,8 @@ "map_location_picker_page_use_location": "Это местоположение", "map_location_service_disabled_content": "Для отображения объектов в текущем месте необходимо включить службу определения местоположения. Включить?", "map_location_service_disabled_title": "Служба определения местоположения отключена", - "map_marker_for_images": "Маркер на карте для изображений, сделанных в {city}, {country}", - "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": "Настройки карты", diff --git a/i18n/sk.json b/i18n/sk.json index d15eb00dee..866a2e668b 100644 --- a/i18n/sk.json +++ b/i18n/sk.json @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "Povoliť inteligentné vyhľadávanie", "machine_learning_smart_search_enabled_description": "Ak je vypnuté, obrázky nebudú spracované pre inteligentné vyhľadávanie.", "machine_learning_url_description": "URL adresa servera strojového učenia. Ak je zadaných viacero adries URL, každý server bude testovaný postupne, kým jeden z nich neodpovie úspešne, v poradí od prvého po posledný. Servery, ktoré neodpovedajú, budú dočasne ignorované, kým nebudú opäť online.", + "maintenance_backup_management": "Spravovanie záloh", "maintenance_delete_backup": "Vymazať zálohu", "maintenance_delete_backup_description": "Tento súbor bude nezvratne vymazaný.", "maintenance_delete_error": "Nepodarilo sa vymazať zálohu.", + "maintenance_integrity_check": "Skontrolovať", "maintenance_integrity_check_all": "Skontrolovať všetko", "maintenance_integrity_checksum_mismatch": "Nesúlad kontrolného súčtu", + "maintenance_integrity_checksum_mismatch_description": "Súbory, ktorých kontrolný súčet na disku sa nezhoduje s kontrolným súčtom, ktorý Immich uložil vo svojej databáze.", "maintenance_integrity_checksum_mismatch_job": "Skontrolovať, či sa kontrolné súčty zhodujú", "maintenance_integrity_checksum_mismatch_refresh_job": "Obnoviť správy o nezhode kontrolného súčtu", "maintenance_integrity_missing_file": "Chýbajúce súbory", + "maintenance_integrity_missing_file_description": "Súbory, ktoré Immich zaznamenal vo svojej databáze, ale ktoré v súborovom systéme neexistujú.", "maintenance_integrity_missing_file_job": "Skontrolovať chýbajúce súbory", "maintenance_integrity_missing_file_refresh_job": "Obnoviť hlásenia o chýbajúcich súboroch", "maintenance_integrity_report": "Hlásenie o integrite", "maintenance_integrity_untracked_file": "Nesledované súbory", + "maintenance_integrity_untracked_file_description": "Súbory v adresároch Immich, o ktorých Immich nemá žiadne záznamy.", "maintenance_integrity_untracked_file_job": "Skontrolovať nesledované súbory", "maintenance_integrity_untracked_file_refresh_job": "Obnoviť hlásenia o nesledovaných súboroch", "maintenance_restore_backup": "Obnoviť zálohu", @@ -1543,7 +1548,6 @@ "map_location_picker_page_use_location": "Použiť túto polohu", "map_location_service_disabled_content": "Služba určovania polohy musí byť povolená, aby sa zobrazovali položky z vašej aktuálnej polohy. Chcete ju teraz zapnúť?", "map_location_service_disabled_title": "Služba určovania polohy vypnutá", - "map_marker_for_images": "Značka na mape pre obrázky odfotené v {city}, {country}", "map_marker_with_image": "Mapová značka pre obrázok", "map_no_location_permission_content": "Na zobrazenie položiek z vašej aktuálnej polohy je potrebné povolenie na polohu. Chcete to teraz povoliť?", "map_no_location_permission_title": "Povolenie polohy zamietnuté", diff --git a/i18n/sl.json b/i18n/sl.json index aaacf5804e..d3a41f5f9a 100644 --- a/i18n/sl.json +++ b/i18n/sl.json @@ -189,18 +189,23 @@ "machine_learning_smart_search_enabled": "Omogoči pametno iskanje", "machine_learning_smart_search_enabled_description": "Če je onemogočeno, slike ne bodo kodirane za pametno iskanje.", "machine_learning_url_description": "URL strežnika za strojno učenje. Če je na voljo več kot en URL, bo vsak strežnik poskusen posamično, dokler se eden ne odzove uspešno, v vrstnem redu od prvega do zadnjega. Strežniki, ki se ne odzovejo, bodo začasno prezrti, dokler se spet ne vzpostavijo.", + "maintenance_backup_management": "Upravljanje varnostnih kopij", "maintenance_delete_backup": "Izbriši varnostno kopijo", "maintenance_delete_backup_description": "Ta datoteka bo nepreklicno izbrisana.", "maintenance_delete_error": "Varnostne kopije ni bilo mogoče izbrisati.", + "maintenance_integrity_check": "Preveri", "maintenance_integrity_check_all": "Označi vse", "maintenance_integrity_checksum_mismatch": "Neujemanje kontrolne vsote", + "maintenance_integrity_checksum_mismatch_description": "Datoteke, katerih kontrolna vsota na disku se ne ujema s kontrolno vsoto, ki jo je Immich shranil v svojo zbirko podatkov.", "maintenance_integrity_checksum_mismatch_job": "Preverite neujemanje kontrolnih vsot", "maintenance_integrity_checksum_mismatch_refresh_job": "Osveži poročila o neujemanju kontrolnih vsot", "maintenance_integrity_missing_file": "Manjkajoče datoteke", + "maintenance_integrity_missing_file_description": "Datoteke, ki jih je Immich sledil v svoji bazi podatkov, vendar ne obstajajo v datotečnem sistemu.", "maintenance_integrity_missing_file_job": "Preverite manjkajoče datoteke", "maintenance_integrity_missing_file_refresh_job": "Osveži poročila o manjkajočih datotekah", "maintenance_integrity_report": "Poročilo o integriteti", "maintenance_integrity_untracked_file": "Nesledljive datoteke", + "maintenance_integrity_untracked_file_description": "Datoteke v Immichovih imenikih, o katerih Immich nima nobenih zapisov.", "maintenance_integrity_untracked_file_job": "Preveri nesledljive datoteke", "maintenance_integrity_untracked_file_refresh_job": "Osveži poročila o nesledenih datotekah", "maintenance_restore_backup": "Obnovi varnostno kopijo", @@ -1543,7 +1548,7 @@ "map_location_picker_page_use_location": "Uporabi to lokacijo", "map_location_service_disabled_content": "Lokacijska storitev mora biti omogočena za prikaz sredstev z vaše trenutne lokacije. Ali jo želite takoj omogočiti?", "map_location_service_disabled_title": "Lokacijska storitev onemogočena", - "map_marker_for_images": "Oznaka zemljevida za slike, posnete v {city}, {country}", + "map_marker_for_image": "Oznaka na zemljevidu za sliko, posneto v {city}, {country}", "map_marker_with_image": "Oznaka zemljevida s sliko", "map_no_location_permission_content": "Za prikaz sredstev z vaše trenutne lokacije je potrebno dovoljenje za lokacijo. Ali to želite takoj dovoliti?", "map_no_location_permission_title": "Dovoljenje za lokacijo je zavrnjeno", diff --git a/i18n/sr_Cyrl.json b/i18n/sr_Cyrl.json index 3bd900a8d9..2733eb693b 100644 --- a/i18n/sr_Cyrl.json +++ b/i18n/sr_Cyrl.json @@ -1184,7 +1184,6 @@ "map_location_picker_page_use_location": "Користите ову локацију", "map_location_service_disabled_content": "Услуга локације мора бити омогућена да би се приказивала средства са ваше тренутне локације. Да ли желите да је сада омогућите?", "map_location_service_disabled_title": "Услуга локације је онемогућена", - "map_marker_for_images": "Означивач на мапи за слике снимљене у {city}, {country}", "map_marker_with_image": "Маркер на мапи са сликом", "map_no_location_permission_content": "Потребна је дозвола за локацију да би се приказали ресурси са ваше тренутне локације. Да ли желите да је сада дозволите?", "map_no_location_permission_title": "Дозвола за локацију је одбијена", diff --git a/i18n/sr_Latn.json b/i18n/sr_Latn.json index 16793f86ce..0bb3885461 100644 --- a/i18n/sr_Latn.json +++ b/i18n/sr_Latn.json @@ -1370,7 +1370,6 @@ "map_location_picker_page_use_location": "Koristite ovu lokaciju", "map_location_service_disabled_content": "Usluga lokacije mora biti omogućena da bi se prikazivala sredstva sa vaše trenutne lokacije. Da li želite da je sada omogućite?", "map_location_service_disabled_title": "Usluga lokacije je onemogućena", - "map_marker_for_images": "Označivač na mapi za slike snimljene u {city}, {country}", "map_marker_with_image": "Marker na mapi sa slikom", "map_no_location_permission_content": "Potrebna je dozvola za lokaciju da bi se prikazali resursi sa vaše trenutne lokacije. Da li želite da je sada dozvolite?", "map_no_location_permission_title": "Dozvola za lokaciju je odbijena", diff --git a/i18n/sv.json b/i18n/sv.json index d185ab241e..d702b585c9 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -79,6 +79,7 @@ "cron_expression_description": "Sätt skanningsintervallet genom att använda cron-format. För mer information se Crontab Guru", "cron_expression_presets": "Cron-uttryck förinställningar", "disable_login": "Inaktivera inloggning", + "download_csv": "Ladda ner CSV", "duplicate_detection_job_description": "Kör maskininlärning på objekt för att upptäcka liknande bilder. Bygger på Smart Search", "exclusion_pattern_description": "Exkluderingsmönster tillåter dig att ignorera filer och mappar när skanning görs av ditt album. Detta är användbart om du har mappar som innehåller filer som du inte vill importera, t.ex. RAW-filer.", "export_config_as_json_description": "Ladda ner den aktuella systemkonfigurationen som en JSON-fil", @@ -188,9 +189,25 @@ "machine_learning_smart_search_enabled": "Aktivera smart sökning", "machine_learning_smart_search_enabled_description": "Om inaktiverat kommer bilder inte att kodas för smart sökning.", "machine_learning_url_description": "Maskininlärningsserverns URL. Om det är mer än en URL tillagd så kommer ett försök per URL att utföras tills någon av dom svarar, försöken görs i kronologisk ordning. Servrar som inte svarar kommer tillfälligt ignoreras tills de är nåbara igen.", + "maintenance_backup_management": "Hantering av säkerhetskopior", "maintenance_delete_backup": "Ta bort säkerhetskopia", "maintenance_delete_backup_description": "Den här filen kommer att raderas oåterkalleligt.", "maintenance_delete_error": "Det gick inte att ta bort säkerhetskopian.", + "maintenance_integrity_check": "Kontrollera", + "maintenance_integrity_check_all": "Kontrollera alla", + "maintenance_integrity_checksum_mismatch": "Kontrollsummeavvikelse", + "maintenance_integrity_checksum_mismatch_description": "Filer vars kontrollsumma på disken inte stämmer överens med den kontrollsumma som Immich har lagrat i sin databas.", + "maintenance_integrity_checksum_mismatch_job": "Kolla efter kontrollsummor som inte matchar", + "maintenance_integrity_checksum_mismatch_refresh_job": "Uppdatera rapporter om kontrollsummor som inte matchar", + "maintenance_integrity_missing_file": "Filer saknas", + "maintenance_integrity_missing_file_description": "Filer som har registrerats i Immichs databas men som saknas i filsystemet.", + "maintenance_integrity_missing_file_job": "Kolla efter saknade filer", + "maintenance_integrity_missing_file_refresh_job": "Uppdatera rapporter om saknade filer", + "maintenance_integrity_report": "Integritetsrapport", + "maintenance_integrity_untracked_file": "Ospårade filer", + "maintenance_integrity_untracked_file_description": "Okända filer i Immichs kataloger.", + "maintenance_integrity_untracked_file_job": "Kolla efter okända filer", + "maintenance_integrity_untracked_file_refresh_job": "Uppdatera rapporter om okända filer", "maintenance_restore_backup": "Återställ säkerhetskopia", "maintenance_restore_backup_description": "Immich kommer att återställas från den valda säkerhetskopian. En ny säkerhetskopia kommer att skapas innan du fortsätter.", "maintenance_restore_backup_different_version": "Denna säkerhetskopia skapades med en annan version av Immich!", @@ -570,10 +587,11 @@ "asset_added_to_album": "Lades till i album", "asset_adding_to_album": "Lägger till i album…", "asset_created": "Objekt skapad", + "asset_day_count": "{date}: {count, plural, one {# objekt} other {# objekt}}", "asset_description_updated": "Objektbeskrivning har uppdaterats", "asset_filename_is_offline": "Objektet {filename} är offline", "asset_has_unassigned_faces": "Objektet har otilldelade ansikten", - "asset_hashing": "Hashning…", + "asset_hashing": "Hashar…", "asset_list_group_by_sub_title": "Gruppera på", "asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout", "asset_list_layout_settings_group_automatically": "Automatiskt", @@ -699,6 +717,7 @@ "backup_settings_subtitle": "Hantera uppladdningsinställningar", "backup_upload_details_page_more_details": "Tryck för mer detaljer", "backward": "Bakåt", + "battery_optimization_backup_reliability": "Inaktivera batterioptimeringar för stabilare säkerhetskopiering i bakgrunden", "biometric_auth_enabled": "Biometrisk autentisering aktiverad", "biometric_locked_out": "Du är utelåst från biometrisk autentisering", "biometric_no_options": "Inga biometriska alternativ tillgängliga", @@ -913,6 +932,8 @@ "deduplicate_all": "Deduplicera alla", "default_locale": "Standardspråk", "default_locale_description": "Formatera datum och siffror baserat på din webbläsares språkinställningar", + "default_quality_subtitle": "Kvalitet som används vid delning. Håll ner delaknappen för att välja kvalitet varje gång.", + "default_share_quality": "Standarddelningskvalitet", "delete": "Radera", "delete_action_confirmation_message": "Är du säker på att du vill ta bort det här objektet? Den här åtgärden flyttar objektet till serverns papperskorg och frågar om du vill ta bort den lokalt", "delete_action_prompt": "{count} raderade", @@ -1222,6 +1243,7 @@ "failed": "Misslyckades", "failed_count": "Misslyckade: {count}", "failed_to_authenticate": "Misslyckades med autentisering", + "failed_to_delete_file": "Misslyckades att radera fil", "failed_to_load_assets": "Det gick inte att läsa in objekten", "failed_to_load_folder": "Kunde inte ladda mappen", "favorite": "Favorit", @@ -1352,6 +1374,7 @@ "individual_share": "Enskild delning", "individual_shares": "Individuella delningar", "info": "Information", + "integrity_checks": "Integritetskontroller", "interval": { "day_at_onepm": "Alla dagar vid kl 13.00", "hours": "Vid varje {hours, plural, one {hour} other {{hours, number} hours}}", @@ -1399,6 +1422,7 @@ "leave": "Lämna", "leave_album": "Lämna albumet", "lens_model": "Objektiv", + "less": "Mindre", "let_others_respond": "Låt andra svara", "level": "Nivå", "library": "Bibliotek", @@ -1423,6 +1447,7 @@ "linked_oauth_account": "Länkat OAuth konto", "list": "Lista", "live": "Live", + "load_more": "Ladda mer", "loading": "Inläsning", "loading_search_results_failed": "Det gick inte att läsa in sökresultat", "local": "Lokalt", @@ -1523,7 +1548,7 @@ "map_location_picker_page_use_location": "Använd den här platsen", "map_location_service_disabled_content": "Platstjänst måste vara aktiverad för att visa objekt från din nuvarande plats. Vill du aktivera den nu?", "map_location_service_disabled_title": "Platstjänst inaktiverad", - "map_marker_for_images": "Kartmarkering för bilder tagna i {city}, {country}", + "map_marker_for_image": "Kartmarkör för bild tagen i {city}, {country}", "map_marker_with_image": "Kartmarkör med bild", "map_no_location_permission_content": "Platsrättighet är nödvändigt för att kunna visa objekt från din nuvarande plats. Vill du tillåta det nu?", "map_no_location_permission_title": "Platsrättighet nekad", @@ -1689,6 +1714,7 @@ "not_selected": "Ej vald", "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_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", @@ -2080,6 +2106,7 @@ "select_person": "Välj person", "select_person_to_tag": "Välj en person att tagga", "select_photos": "Välj foton", + "select_quality": "Välj kvalitet", "select_trash_all": "Släng alla", "select_user_for_sharing_page_err_album": "Kunde inte skapa nytt album", "selected": "Valda", @@ -2143,6 +2170,8 @@ "share_assets_selected": "{count} valda", "share_dialog_preparing": "Förbereder...", "share_link": "Dela Länk", + "share_original": "Använd original (stor)", + "share_preview": "Använd miniatyrbild (liten)", "shared": "Delad", "shared_album_activities_input_disable": "Kommentar är inaktiverad", "shared_album_activity_remove_content": "Vill du ta bort den här aktiviteten?", @@ -2244,6 +2273,7 @@ "slideshow_repeat_description": "Gå tillbaka till början när bildspelet slutar", "slideshow_settings": "Bildspelsinställningar", "smart_album": "Smart album", + "some_assets_already_have_a_location_warning": "Vissa av de valda objekten har redan platsdata", "sort_albums_by": "Sortera album efter...", "sort_created": "Skapat datum", "sort_items": "Antal artiklar", @@ -2364,6 +2394,8 @@ "trash_page_title": "Papperskorg ({count})", "trashed_items_will_be_permanently_deleted_after": "Objekt i papperskorgen raderas permanent efter {days, plural, one {# dag} other {# dagar}}.", "trigger": "Utlösare", + "trigger_asset_metadata_extraction": "Hämtning av objektmetadata", + "trigger_asset_metadata_extraction_description": "Aktiveras när EXIF-metadata hämtas från ett objekt", "trigger_asset_uploaded": "Objekt uppladdning", "trigger_asset_uploaded_description": "Utlöses när ett nytt objekt laddas upp", "trigger_description": "Ett evenemang som sätter igång arbetsflödet", @@ -2410,6 +2442,7 @@ "updated_password": "Lösenordet har uppdaterats", "upload": "Ladda upp", "upload_concurrency": "Uppladdning samtidighet", + "upload_day_count": "{date}: {count, plural, one {# uppladdning} other {# uppladdningar}}", "upload_details": "Uppladdningsdetaljer", "upload_dialog_info": "Vill du säkerhetskopiera de valda objekten till servern?", "upload_dialog_title": "Ladda Upp Objekt", @@ -2425,6 +2458,8 @@ "upload_to_immich": "Ladda upp till Immich ({count})", "uploading": "Laddar upp", "uploading_media": "Uppladdning av media", + "uploads": "Uppladdningar", + "uploads_count": "{count, plural, one {# uppladdning} other {# uppladdningar}}", "url": "URL", "usage": "Användning", "use_biometric": "Använd biometri", diff --git a/i18n/ta.json b/i18n/ta.json index 4812d40a7d..2e58a1ce4e 100644 --- a/i18n/ta.json +++ b/i18n/ta.json @@ -1485,7 +1485,6 @@ "map_location_picker_page_use_location": "இந்த இருப்பிடத்தைப் பயன்படுத்தவும்", "map_location_service_disabled_content": "உங்கள் தற்போதைய இருப்பிடத்திலிருந்து சொத்துக்களைக் காட்ட இருப்பிட பணி இயக்கப்பட வேண்டும். இப்போது அதை இயக்க விரும்புகிறீர்களா?", "map_location_service_disabled_title": "இருப்பிட பணி முடக்கப்பட்டது", - "map_marker_for_images": "{city}, {country}", "map_marker_with_image": "படத்துடன் வரைபட மார்க்கர்", "map_no_location_permission_content": "உங்கள் தற்போதைய இருப்பிடத்திலிருந்து சொத்துக்களைக் காட்ட இருப்பிட இசைவு தேவை. இப்போது அதை அனுமதிக்க விரும்புகிறீர்களா?", "map_no_location_permission_title": "இருப்பிட இசைவு மறுக்கப்பட்டது", diff --git a/i18n/te.json b/i18n/te.json index eb8fe59f43..6c2b46136d 100644 --- a/i18n/te.json +++ b/i18n/te.json @@ -837,7 +837,6 @@ "manage_your_devices": "మీ లాగిన్ అయిన పరికరాలను నిర్వహించండి", "manage_your_oauth_connection": "మీ OAuth కనెక్షన్‌ని నిర్వహించండి", "map": "మ్యాప్", - "map_marker_for_images": "{city}, {country} లో తీసిన చిత్రాల కోసం మ్యాప్ మార్కర్", "map_marker_with_image": "చిత్రంతో మ్యాప్ మార్కర్", "map_settings": "మ్యాప్ సెట్టింగ్‌లు", "matches": "మ్యాచ్‌లు", diff --git a/i18n/th.json b/i18n/th.json index d433f9960b..08fe16d210 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -79,6 +79,7 @@ "cron_expression_description": "ตั้งช่วงเวลาในการสแกนโดยใช้รูปแบบ cron สำหรับข้อมูลเพิ่มเติมกรุณาอิง Crontab Guru", "cron_expression_presets": "พรีเซ็ตรูปแบบ cron", "disable_login": "ปิดการล็อกอิน", + "download_csv": "ดาวน์โหลด CSV", "duplicate_detection_job_description": "ใช้ machine learning กับสี่อเพื่อตรวจจับรูปภาพที่คล้ายกัน โดยใช้การค้นหาอัจฉริยะ", "exclusion_pattern_description": "ข้อยกเว้นสามารถละเว้นไฟล์และโฟลเดอร์ขณะสแกนคลังภาพของคุณ มีประโยชน์เมื่อโฟลเดอร์มีไฟล์ที่ไม่อยากนำเข้า เช่นไฟล์ RAW", "export_config_as_json_description": "ดาวน์โหลดการตั้งค่าระบบปัจจุบันไปยังไฟล์ในรูปแบบ JSON", @@ -188,9 +189,12 @@ "machine_learning_smart_search_enabled": "เปิดใช้งานการค้นหาอัจฉริยะ", "machine_learning_smart_search_enabled_description": "หากปิดใช้งาน ภาพจะไม่ถูกใช้สําหรับการค้นหาอัจฉริยะ", "machine_learning_url_description": "URL ของเซิร์ฟเวอร์ machine learning กรณีมี URL มากกว่าหนึ่ง URL จะทำการทดลองส่งข้อมูลเรียงไปทีละอันตามลำดับจนกว่าจะพบ URL ที่ตอบสนอง และจะเลิกส่งข้อมูลชั่วคราวในส่วนของ URL ที่ไม่ตอบสนอง", + "maintenance_backup_management": "การจัดการสำรองข้อมูล", "maintenance_delete_backup": "ลบการสำรองข้อมูล", "maintenance_delete_backup_description": "ไฟล์นี้จะถูกลบและไม่สามารถย้อนกลับได้", "maintenance_delete_error": "ไม่สามารถลบการสำรองข้อมูล", + "maintenance_integrity_check": "ตรวจสอบ", + "maintenance_integrity_check_all": "ตรวจสอบทั้งหมด", "maintenance_restore_backup": "กู้คืนการสำรองข้อมูล", "maintenance_restore_backup_description": "Immich จะถูกล้าง และกู้คืนข้อมูลจากการสำรองข้อมูลที่เลือก ระบบจะสร้างการสำรองข้อมูลก่อนดำเนินการต่อ", "maintenance_restore_backup_different_version": "การสำรองข้อมูลนี้ถูกสร้างด้วย Immich เวอร์ชันต่างกัน!", @@ -638,7 +642,7 @@ "backup_controller_page_background_battery_info_title": "ประสิทธิภาพแบตเตอรี่", "backup_controller_page_background_charging": "ขณะชาร์จอย่างเดียว", "backup_controller_page_background_configure_error": "ไม่สามารถติดตั้งบริการเบื้องหลัง", - "backup_controller_page_background_delay": "ล่าช้าการลำรองทรัพยากรใหม่: {duration}", + "backup_controller_page_background_delay": "ล่าช้าการสำรองทรัพยากรใหม่: {duration}", "backup_controller_page_background_description": "เปิดบริการเบื้องหลังเพื่อที่จะสำรองทรัพยากรใหม่โดยที่ไม่จำเป็นต้องเปิดแอป", "backup_controller_page_background_is_off": "การสำรองข้อมูลอัตโนมัติปิดอยู่", "backup_controller_page_background_is_on": "การสำรองข้อมูลอัตโนมัติเปิดอยู่", @@ -1464,7 +1468,6 @@ "map_location_picker_page_use_location": "ใช้ตำแหน่งนี้", "map_location_service_disabled_content": "ต้องเปิดตำแหน่งเพื่อแสดงทรัพยากรจากตำแหน่งปัจจุบัน เปิดตอนนี้?", "map_location_service_disabled_title": "บริการตำแหน่งถูกปิด", - "map_marker_for_images": "หมุดแผนที่สำหรับรูปถ่ายที่ {city}, {country}", "map_marker_with_image": "หมุดแผนที่กับรูปถ่าย", "map_no_location_permission_content": "จำเป็นต้องมีสิทธิ์เข้าถึงตำแหน่งเพื่อแสดงทรัพยากรจากตำแหน่งปัจจุบัน อนุญาตตอนนี้?", "map_no_location_permission_title": "สิทธิ์เข้าถึงตำแหน่งถูกปฏิเสธ", @@ -2142,7 +2145,7 @@ "sort_modified": "จัดเรียงตามวันที่แก้ไข", "sort_newest": "รูปภาพใหม่ล่าสุด", "sort_oldest": "จัดเรียงตามเก่าสุด", - "sort_people_by_similarity": "จุดเรียงบุคคลตามความคล้ายคลึง", + "sort_people_by_similarity": "จัดเรียงผู้คนตามความคล้ายคลึง", "sort_recent": "จัดเรียงใหม่ล่าสุด", "sort_title": "ไตเติ้ล", "source": "แหล่ง", diff --git a/i18n/tr.json b/i18n/tr.json index e44279bb9f..be14dfedc1 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -79,11 +79,12 @@ "cron_expression_description": "Cron formatını kullanarak tarama aralığını belirle. Daha fazla bilgi için örneğin Crontab Guru’ya bakın", "cron_expression_presets": "Cron ifadesi ön ayarları", "disable_login": "Girişi devre dışı bırak", + "download_csv": "CSV’yi indir", "duplicate_detection_job_description": "Benzer fotoğrafları bulmak için makine öğrenmesini çalıştır. Bu işlem Akıllı Arama'ya bağlıdır", "exclusion_pattern_description": "Kütüphaneyi tararken dosya ve klasörleri görmezden gelmek için dışlama desenlerini kullanabilirsiniz. RAW dosyaları gibi bazı dosya ve klasörleri içe aktarmak istemediğinizde bu seçeneği kullanabilirsiniz.", "export_config_as_json_description": "Geçerli sistem yapılandırmasını JSON dosyası olarak indir", "external_libraries_page_description": "Yönetici harici kütüphane sayfası", - "face_detection": "Yüz tarama", + "face_detection": "Yüz tespiti", "face_detection_description": "Makine öğrenimi kullanarak varlıklardaki yüzleri tespit et. Videolar için sadece küçük resim (thumbnail) dikkate alınır. 'Yenile' tüm varlıkları yeniden işler. 'Sıfırla', mevcut tüm yüz verilerini temizleyerek işlemi yeniden başlatır. 'Eksik' henüz işlenmemiş varlıkları sıraya alır. Tespit edilen yüzler, Yüz Tanıma işlemi tamamlandıktan sonra mevcut ya da yeni kişilere gruplanmak üzere Yüz Tanıma için sıraya alınacaktır.", "facial_recognition_job_description": "Algılanan yüzleri kişilere grupla. Bu adım, Yüz Tespit işlemi tamamlandıktan sonra çalışır. \"Sıfırla\", tüm yüzleri yeniden gruplandırır. \"Eksik\" ise henüz bir kişiye atanmamış yüzleri sıraya alır.", "failed_job_command": "{job} görevi için {command} komutu başarısız", @@ -188,9 +189,23 @@ "machine_learning_smart_search_enabled": "Akıllı aramayı etkinleştir", "machine_learning_smart_search_enabled_description": "Eğer devre dışı bırakılırsa fotoğraflar akıllı arama için işlenmeyecek.", "machine_learning_url_description": "Makine öğrenimi sunucusunun URL’si. Birden fazla URL sağlanırsa, her sunucu sırayla tek tek denenir ve biri başarılı yanıt verene kadar devam edilir. Yanıt vermeyen sunucular, çevrimiçi duruma gelene kadar geçici olarak yok sayılır.", + "maintenance_backup_management": "Yedekleme sistemi", "maintenance_delete_backup": "Yedeği Sil", "maintenance_delete_backup_description": "Bu dosya geri alınamaz şekilde silinecektir.", "maintenance_delete_error": "Yedek silinemedi.", + "maintenance_integrity_check": "Kontrol et", + "maintenance_integrity_check_all": "Hepsini kontrol et", + "maintenance_integrity_checksum_mismatch_description": "Disk üzerindeki sağlama toplamı, Immich'in veritabanında sakladığı sağlama toplamıyla uyuşmayan dosyalar.", + "maintenance_integrity_checksum_mismatch_job": "Sağlama toplamı uyuşmazlıklarını kontrol et", + "maintenance_integrity_missing_file": "Eksik Dosyalar", + "maintenance_integrity_missing_file_description": "Immich'in veritabanında izlediği ancak dosya sisteminde bulunmayan dosyalar.", + "maintenance_integrity_missing_file_job": "Eksik dosyaları kontrol et", + "maintenance_integrity_missing_file_refresh_job": "Eksik dosya raporlarını yenile", + "maintenance_integrity_report": "Entegrasyon Raporu", + "maintenance_integrity_untracked_file": "İzlenmeyen Dosyalar", + "maintenance_integrity_untracked_file_description": "Immich'in dizinlerinde bulunan ancak Immich'in hiçbir kaydının bulunmadığı dosyalar.", + "maintenance_integrity_untracked_file_job": "İzlenmeyen dosyaları kontrol et", + "maintenance_integrity_untracked_file_refresh_job": "İzlenmeyen dosya raporlarını yenile", "maintenance_restore_backup": "Yedeği Geri Yükle", "maintenance_restore_backup_description": "Immich tamamen silinecek ve seçilen yedekten geri yüklenecektir. İşleme devam etmeden önce bir yedek oluşturulacaktır.", "maintenance_restore_backup_different_version": "Bu yedek, Immich’in farklı bir sürümüyle oluşturulmuş!", @@ -305,6 +320,7 @@ "refreshing_all_libraries": "Tüm kütüphaneler yenileniyor", "registration": "Yönetici Kaydı", "registration_description": "Sistemdeki ilk kullanıcı olduğunuz için hesabınız Yönetici olarak ayarlandı. Yeni oluşturulan üyeliklerin, ve yönetici görevlerinin sorumlusu olarak atandınız.", + "release_channel_release_candidate": "Yayın adayı", "release_channel_stable": "Stabil", "remove_failed_jobs": "Başarısız işleri kaldır", "require_password_change_on_login": "Kullanıcının ilk girişinde şifre değiştirmesini zorunlu kıl", @@ -400,6 +416,9 @@ "transcoding_preferred_hardware_device_description": "Sadece VAAPI ve QSV için uygulanır. Donanım kod çevrimi için DRI Node ayarlar.", "transcoding_preset_preset": "Ön ayar (-ön)", "transcoding_preset_preset_description": "Sıkıştırma hızı. Daha yavaş olan ayarlar belirli bitrate ayarları için daha küçük ve daha kaliteli dosya üretir. VP9 ayarı 'daha hızlı' ayarının üstündeki ayarları görmezden gelir.", + "transcoding_realtime": "Gerçek Zamanlı Kod Dönüştürme [DENEYSEL]", + "transcoding_realtime_enabled": "Gerçek zamanlı kod dönüştürmeyi etkinleştirin", + "transcoding_realtime_enabled_description": "Devre dışı bırakılırsa, sunucu yeni gerçek zamanlı kod dönüştürme oturumları başlatmayı reddedecektir.", "transcoding_reference_frames": "Referans kareler", "transcoding_reference_frames_description": "Belirli bir kareyi sıkıştırırken referans alınacak kare sayısı. Daha yüksek değerler sıkıştırma verimliliğini artırır, ancak kodlamayı yavaşlatır. 0 bu değeri otomatik olarak ayarlar.", "transcoding_required_description": "Yalnızca kabul edilen formatta olmayan videolar", @@ -443,6 +462,8 @@ "user_settings_description": "Kullanıcı ayarlarını yönet", "user_successfully_removed": "Kullanıcı {email} başarıyla kaldırıldı.", "users_page_description": "Yönetici kullanıcılar sayfası", + "version_check_channel": "Yayın kanalı", + "version_check_channel_description": "Sürüm duyurularını almak istediğiniz yayın kanalını seçin", "version_check_enabled_description": "Sürüm kontrolü etkin", "version_check_implications": "Sürüm kontrol özelliği, {server} ile periyodik iletişime dayanır", "version_check_settings": "Sürüm Kontrolü", @@ -692,6 +713,7 @@ "backup_settings_subtitle": "Yükleme ayarlarını yönet", "backup_upload_details_page_more_details": "Daha fazla ayrıntı için dokunun", "backward": "Geriye doğru", + "battery_optimization_backup_reliability": "Pil optimizasyonlarını devre dışı bırakmak, arka plan yedeklemesinin güvenilirliğini artırabilir", "biometric_auth_enabled": "Biyometrik kimlik doğrulama etkin", "biometric_locked_out": "Biyometrik kimlik doğrulaması kilitli", "biometric_no_options": "Biyometrik seçenek yok", @@ -733,6 +755,7 @@ "cannot_update_the_description": "Açıklama güncellenemiyor", "cast": "Yansıt", "cast_description": "Kullanılabilir yansıtma hedeflerini yapılandır", + "change": "Değiştir", "change_date": "Tarihi değiştir", "change_description": "Açıklamayı değiştir", "change_display_order": "Görüntüleme sırasını değiştir", @@ -779,6 +802,7 @@ "clear": "Temizle", "clear_all": "Hepsini temizle", "clear_all_recent_searches": "Son aramaların hepsini temizle", + "clear_failed_count": "Temizleme başarısız oldu ({count})", "clear_file_cache": "Dosya Önbelleğini Temizle", "clear_message": "Mesajı temizle", "clear_value": "Değeri temizle", @@ -904,6 +928,7 @@ "deduplicate_all": "Tüm kopyaları kaldır", "default_locale": "Varsayılan Dil", "default_locale_description": "Tarih ve sayıları tarayıcınızın yerel ayarlarına göre biçimlendirin", + "default_share_quality": "Varsayılan paylaşım kalitesi", "delete": "Sil", "delete_action_confirmation_message": "Bu öğeyi silmek istediğinizden emin misiniz? Bu işlem, öğeyi sunucunun çöp kutusuna taşıyacak ve yerel olarak silmek isteyip istemediğinizi soracaktır", "delete_action_prompt": "{count} silindi", @@ -977,8 +1002,10 @@ "downloading_asset_filename": "Öğe indiriliyor {filename}", "downloading_from_icloud": "iCloud’dan indiriliyor", "downloading_media": "Medya indiriliyor", + "drag_to_reorder": "Sırayı değiştirmek için sürükleyin", "drop_files_to_upload": "Dosyaları yüklemek için herhangi bir yere bırakın", "duplicate": "Kopyala", + "duplicate_workflow": "İş akışını kopyala", "duplicates": "Kopyalar", "duplicates_description": "Her bir grubu, varsa tekrarlanan öğeleri belirterek çözümleyin.", "duration": "Süre", @@ -1080,6 +1107,7 @@ "failed_to_remove_product_key": "Ürün anahtarı kaldırılamadı", "failed_to_reset_pin_code": "PIN kodu sıfırlanamadı", "failed_to_stack_assets": "Öğeler yığınlanamadı", + "failed_to_tag_assets": "Varlıkları etiketleme başarısız oldu", "failed_to_unstack_assets": "Öğelerin yığını kaldırılamadı", "failed_to_update_notification_status": "Bildirim durumu güncellenemedi", "incorrect_email_or_password": "Yanlış e-posta veya şifre", @@ -1204,10 +1232,12 @@ "external_libraries": "Harici kütüphaneler", "external_network": "Harici ağlar", "external_network_sheet_info": "Belirlenmiş Wi-Fi ağına bağlı olmadığında uygulama, yukarıdan aşağıya doğru ulaşabileceği aşağıdaki URL'lerden ilki aracılığıyla sunucuya bağlanacaktır", + "f_number": "F-Numarası", "face_unassigned": "Yüz atanmadı", "failed": "Başarısız", "failed_count": "Başarısız: {count}", "failed_to_authenticate": "Kimlik doğrulaması yapılamadı", + "failed_to_delete_file": "Dosya silme işlemi başarısız oldu", "failed_to_load_assets": "Öğeler yüklenemedi", "failed_to_load_folder": "Klasör yüklenemedi", "favorite": "Favori", @@ -1338,6 +1368,7 @@ "individual_share": "Bireysel paylaşım", "individual_shares": "Kişisel paylaşımlar", "info": "Bilgi", + "integrity_checks": "Bütünlük Kontrolleri", "interval": { "day_at_onepm": "Her gün saat 13:00'te", "hours": "{hours, plural, one {Her saat} other {Her {hours, number} saatte}}", @@ -1385,6 +1416,7 @@ "leave": "Ayrıl", "leave_album": "Albümden çık", "lens_model": "Mercek modeli", + "less": "Daha az", "let_others_respond": "Diğerlerinin yanıt vermesine izin ver", "level": "Seviye", "library": "Kütüphane", @@ -1409,6 +1441,7 @@ "linked_oauth_account": "Bağlı OAuth hesabı", "list": "Liste", "live": "Canlı", + "load_more": "Daha Fazla Yükle", "loading": "Yükleniyor", "loading_search_results_failed": "Arama sonuçları yüklenemedi", "local": "Yerel", @@ -1509,7 +1542,6 @@ "map_location_picker_page_use_location": "Bu konumu kullan", "map_location_service_disabled_content": "Mevcut konumunuzdan öğeleri görüntülemek için konum hizmetinin etkinleştirilmesi gerekiyor. Şimdi etkinleştirmek istiyor musunuz?", "map_location_service_disabled_title": "Konum hizmeti devre dışı bırakıldı", - "map_marker_for_images": "{city}, {country} şehrinde çekilen fotoğraflar için harita işaretleyicisi", "map_marker_with_image": "Resimli harita işaretleyicisi", "map_no_location_permission_content": "Mevcut konumunuzdan öğeleri görüntülemek için konum iznine ihtiyaç var. Şimdi izin vermek istiyor musunuz?", "map_no_location_permission_title": "Konum izni reddedildi", @@ -1532,8 +1564,11 @@ "matching_assets": "Eşleşen Öğeler", "media_chrome": { "auto": "Otomatik", + "captions": "Altyazılar", "captions_off": "Kapalı", + "closed_captions": "kapalı altyazılar", "decode_error": "Kod çözümleme hatası", + "disable_captions": "Altyazıları devre dışı bırak", "enable_captions": "Altyazılar açık", "enter_fullscreen_mode": "Tam ekran kipini aç", "exit_fullscreen_mode": "Tam ekran kipini kapat", @@ -1553,6 +1588,8 @@ "seconds": "saniyeler", "time_value_of_total_time": "{currentTime} / {totalTime}", "time_value_remaining": "{time} kaldı", + "unmute": "Sesini açmak", + "unsupported_error_description": "Desteklenmeyen bir hata oluştu. Sunucu veya ağ hatası ya da tarayıcınız bu formatı desteklemiyor.", "video_not_loaded_unknown_time": "video yüklenmedi, süre bilinmiyor.", "video_player": "Video oynatıcı", "volume": "Ses" @@ -1573,6 +1610,8 @@ "merge_people_prompt": "Bu kişileri birleştirmek istiyor musunuz? Bu işlem geri alınamaz.", "merge_people_successfully": "Kişiler başarılı bir şekilde birleştirildi", "merged_people_count": "{count, plural, one {# kişi} other {# kişi}} birleştirildi", + "minFaces": "Minimum yüzler", + "minFaces_description": "Bir kişinin görüntülenebilmesi için minimum tanınan yüz sayısı", "minimize": "Küçült", "minute": "Dakika", "minutes": "Dakika", @@ -1659,6 +1698,7 @@ "no_results": "Sonuç bulunamadı", "no_results_description": "Eş anlamlı ya da daha genel anlamlı bir kelime deneyin", "no_shared_albums_message": "Fotoğrafları ve videoları ağınızdaki kişilerle paylaşmak için bir albüm oluşturun", + "no_steps": "Henüz hiçbir adım eklenmedi", "no_uploads_in_progress": "Yükleme işlemi yok", "none": "Yok", "not_allowed": "İzin verilmiyor", @@ -1667,6 +1707,7 @@ "not_selected": "Seçilmedi", "notes": "Notlar", "nothing_here_yet": "Burada henüz bir şey yok", + "notification_backup_reliability": "Arka plan yedeklemelerinin güvenirliğini iyileştirmek için bildirimlere izin verin", "notification_permission_dialog_content": "Bildirimleri etkinleştirmek için cihaz ayarlarına gidin ve izin verin.", "notification_permission_list_tile_content": "Bildirimleri etkinleştirmek için izin verin.", "notification_permission_list_tile_enable_button": "Bildirimleri Etkinleştir", @@ -1796,6 +1837,7 @@ "play_transcoded_video": "Kodlanmış videoyu oynat", "please_auth_to_access": "Erişim için lütfen kimliğinizi doğrulayın", "plugin_method_filter_type": "Süzgeç", + "plugin_method_filter_type_description": "Bu yöntem olayları filtreleyebilir ve koşullu olarak sonraki adımların çalışmasını engelleyebilir", "port": "Port", "preferences_settings_subtitle": "Uygulama tercihlerini düzenle", "preferences_settings_title": "Tercihler", @@ -1978,6 +2020,8 @@ "search_by_description_example": "Sapa'da yürüyüş günü", "search_by_filename": "Dosya adına veya uzantısına göre ara", "search_by_filename_example": "Örn. IMG_1234.JPG veya PNG", + "search_by_full_path": "Tam dosya yolu veya klasöre göre arama yapın", + "search_by_full_path_example": "/John/Projeler/3D_Baskı/2026-07-01 - Projeler, 3D, Baskı, 2026 vb. kelimelerle arama yapabilirsiniz.", "search_by_ocr": "OCR'ye göre ara", "search_by_ocr_example": "Sütlü Kahve", "search_camera_lens_model": "Lens modelini ara...", @@ -2054,6 +2098,7 @@ "select_person": "Kişileri seç", "select_person_to_tag": "Etiketlemek için bir kişi seçin", "select_photos": "Fotoğrafları seç", + "select_quality": "Kaliteyi seçin", "select_trash_all": "Hepsini çöpe at", "select_user_for_sharing_page_err_album": "Albüm oluşturulamadı", "selected": "Seçildi", @@ -2117,6 +2162,8 @@ "share_assets_selected": "{count} seçili", "share_dialog_preparing": "Hazırlanıyor...", "share_link": "Bağlantıyı Paylaş", + "share_original": "Orijinal (büyük) olanı kullanın", + "share_preview": "Küçük resmi kullan", "shared": "Paylaşılan", "shared_album_activities_input_disable": "Yoruma kapalı", "shared_album_activity_remove_content": "Bu etkinliği silmek istiyor musunuz?", @@ -2210,12 +2257,14 @@ "skip_to_folders": "Klasörlere atla", "skip_to_tags": "Etiketlere atla", "slideshow": "Slayt gösterisi", + "slideshow_metadata_overlay_mode": "Yer paylaşımı içeriği", "slideshow_metadata_overlay_mode_description_only": "Sadece açıklama", "slideshow_metadata_overlay_mode_full": "Dolu", "slideshow_repeat": "Slayt gösterisini tekrarla", "slideshow_repeat_description": "Slayt gösterisi bittiğinde başa dön", "slideshow_settings": "Slayt gösterisi ayarları", "smart_album": "Akıllı albüm", + "some_assets_already_have_a_location_warning": "Seçilen varlıkların bazılarının zaten bir konumu mevcut", "sort_albums_by": "Albümleri sırala...", "sort_created": "Oluşturulma tarihi", "sort_items": "Öğe sayısı", @@ -2239,6 +2288,7 @@ "state": "Eyalet/İl", "status": "Durum", "step_delete": "Adımı sil", + "step_delete_confirm": "Bu adımı silmek istediğinizden emin misiniz?", "step_details": "Adım ayrıntıları", "steps": "Adımlar", "stop_casting": "Yansıtmayı durdur", @@ -2334,11 +2384,13 @@ "trash_page_title": "Çöp Kutusu ({count})", "trashed_items_will_be_permanently_deleted_after": "Silinen öğeler {days, plural, one {# gün} other {# gün}} sonra kalıcı olarak silinecek.", "trigger": "Tetikleyici", - "trigger_asset_uploaded": "Öğe Karşıya Yüklendi", + "trigger_asset_metadata_extraction": "Varlık Meta Veri Çıkarma", + "trigger_asset_metadata_extraction_description": "Bir varlığın EXIF meta verileri çıkarıldığında tetiklenir", + "trigger_asset_uploaded": "Öğe Karşıya Yüklenince", "trigger_asset_uploaded_description": "Yeni bir öğe karşıya yüklendiğinde tetiklenir", "trigger_description": "İş akışını başlatan bir olay", "trigger_person_recognized": "Tanınan Kişi", - "trigger_person_recognized_description": "Bir kişi algılandığında tetiklenir", + "trigger_person_recognized_description": "Bir kişi tanındığında tetiklenir", "trigger_type": "Tetikleyici türü", "troubleshoot": "Sorun giderme", "type": "Tür", @@ -2380,6 +2432,7 @@ "updated_password": "Güncellenen şifre", "upload": "Yükle", "upload_concurrency": "Yükleme eşzamanlılığı", + "upload_day_count": "{tarih}: {sayı, çoğul, bir {# yükleme} diğer {# yüklemeler}}", "upload_details": "Yükleme Ayrıntıları", "upload_dialog_info": "Seçili öğeleri sunucuya yedeklemek istiyor musunuz?", "upload_dialog_title": "Öğe Yükle", @@ -2395,6 +2448,7 @@ "upload_to_immich": "Immich'e Yükle ({count})", "uploading": "Yükleniyor", "uploading_media": "Medya yükleme", + "uploads": "Yüklemeler", "url": "URL", "usage": "Kullanım", "use_biometric": "Biyometri kullan", @@ -2402,6 +2456,7 @@ "use_browser_locale_description": "Tarih, saat ve sayılar tarayıcınızın yerel ayarlarına göre biçimlendirilsin", "use_current_connection": "Mevcut bağlantıyı kullan", "use_custom_date_range": "Bunun yerine özel tarih aralığını kullan", + "use_template": "Şablonu kullan", "user": "Kullanıcı", "user_has_been_deleted": "Bu kullanıcı silindi.", "user_id": "Kullanıcı ID", @@ -2431,6 +2486,7 @@ "video": "Video", "video_hover_setting": "Üzerinde durulduğunda video ön izlemesi oynat", "video_hover_setting_description": "Öğe üzerinde fareyle durulduğunda video küçük resmini oynatır. Bu özellik devre dışıyken, oynatma simgesine fareyle gidilerek oynatma başlatılabilir.", + "video_quality": "Video kalitesi", "videos": "Videolar", "videos_count": "{count, plural, one {# video} other {# video}}", "videos_only": "Sadece videolar", @@ -2463,6 +2519,7 @@ "week": "Hafta", "welcome": "Hoş geldiniz", "welcome_to_immich": "Immich'e hoş geldiniz", + "when": "Ne zaman", "width": "Genişlik", "wifi_name": "Wi-Fi Adı", "workflow": "İş Akışı", @@ -2475,6 +2532,7 @@ "workflow_name": "İş akışı adı", "workflow_navigation_prompt": "Değişikliklerinizi kaydetmeden ayrılmak istediğinizden emin misiniz?", "workflow_summary": "İş akışı özeti", + "workflow_templates": "İş akışı şablonları", "workflow_update_success": "İş akışı başarıyla güncellendi", "workflow_updated": "İş akışı güncellendi", "workflows": "İş akışları", diff --git a/i18n/uk.json b/i18n/uk.json index 78812084ed..09be7c817d 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -1532,7 +1532,6 @@ "map_location_picker_page_use_location": "Використати це місце", "map_location_service_disabled_content": "Служба визначення місця має бути увімкнена, щоб відображати елементи з вашого поточного місця. Увімкнути її зараз?", "map_location_service_disabled_title": "Служба визначення місця вимкнена", - "map_marker_for_images": "Маркер на мапі для зображень, знятих у {city}, {country}", "map_marker_with_image": "Маркер на мапі із зображенням", "map_no_location_permission_content": "Потрібен дозвіл, щоб показувати елементи із поточного місця. Надати його зараз?", "map_no_location_permission_title": "Доступ до місця не надано", diff --git a/i18n/ur.json b/i18n/ur.json index 62b3621f98..eae1e037e0 100644 --- a/i18n/ur.json +++ b/i18n/ur.json @@ -27,6 +27,7 @@ "add_partner": "ساتھی شامل کریں", "add_path": "راستہ شامل کریں", "add_photos": "تصاویر شامل کریں", + "add_step": "مرحلہ بنائیں", "add_tag": "ٹیگ شامل کریں", "add_to": "اس میں شامل کریں…", "add_to_album": "البم میں شامل کریں", @@ -70,7 +71,11 @@ "confirm_reprocess_all_faces": "کیا آپ واقعی تمام چہروں کو دوبارہ پروسیس کرنا چاہتے ہیں؟ اس سے نام والے افراد بھی صاف ہو جائیں گے۔", "confirm_user_password_reset": "کیا آپ {user} کا پاس ورڈ ری سیٹ کرنا چاہتے ہیں؟", "confirm_user_pin_code_reset": "کیا آپ {user} کا پن کوڈ ری سیٹ کرنا چاہتے ہیں؟", + "copy_config_to_clipboard_description": "اپنی موجودہ سسٹم کے نظام کی ترتیب JSON کی شکل میں کلپ بورڈ میں کاپی کریں", "create_job": "کام بنائیں", + "disable_login": "لاگ ان بند کریں", + "download_csv": "CSV ڈاون لوڈ کریں", + "duplicate_detection_job_description": "اپنی اجراۂ پر مشین لرننگ چلا کر ایک جیسی تصاویر کا پتہ لگا ئے۔ \"Smart Search\" پر انحصار کرتا ہے", "face_detection": "چہرے کی پہچان", "failed_job_command": "کام: {job} کے لیے کمانڈ: {command} ناکام ہو گئی", "image_preview_title": "پیش نظارہ", diff --git a/i18n/vi.json b/i18n/vi.json index 0b59ba83ce..26ce09ba51 100644 --- a/i18n/vi.json +++ b/i18n/vi.json @@ -5,7 +5,7 @@ "acknowledge": "Ghi nhận", "action": "Hành động", "action_common_update": "Cập nhật", - "action_description": "Một tập hợp các hành động cần thực hiện trên các tệp đã được lọc", + "action_description": "Một tập hợp các hành động cần thực hiện trên các tài nguyên đã được lọc", "actions": "Hành động", "active": "Đang hoạt động", "active_count": "Hoạt động: {count}", @@ -18,7 +18,7 @@ "add_a_title": "Thêm tên", "add_action": "Thêm hành động", "add_action_description": "Nhấn để thêm hành động cần thực hiện", - "add_assets": "Thêm ảnh/video", + "add_assets": "Thêm tài nguyên", "add_birthday": "Thêm sinh nhật", "add_endpoint": "Thêm endpoint", "add_exclusion_pattern": "Thêm quy tắc loại trừ", @@ -33,10 +33,10 @@ "add_to_album": "Thêm vào album", "add_to_album_bottom_sheet_added": "Đã thêm vào {album}", "add_to_album_bottom_sheet_already_exists": "Đã có sẵn trong {album}", - "add_to_album_bottom_sheet_some_local_assets": "Một số tệp trên thiết bị không thể được thêm vào album", + "add_to_album_bottom_sheet_some_local_assets": "Một số tài nguyên trên thiết bị không thể được thêm vào album", "add_to_album_toggle": "Bật tắt tùy chọn cho {album}", "add_to_albums": "Thêm vào album", - "add_to_albums_count": "Đã thêm vào album {count}", + "add_to_albums_count": "Đã thêm vào ({count}) album", "add_to_bottom_bar": "Thêm vào", "add_to_shared_album": "Thêm vào album chia sẻ", "add_upload_to_stack": "Tải lên thêm vào nhóm", @@ -47,7 +47,7 @@ "admin": { "add_exclusion_pattern_description": "Thêm quy tắc loại trừ. Hỗ trợ sử dụng ký tự *, **, và ?. Để bỏ qua bất kỳ tệp trong thư mục tên \"Raw\", hãy dùng \"**/Raw/**\". Để bỏ qua các tệp có đuôi \".tif\", hãy dùng \"**/*.tif\". Để bỏ qua một đường dẫn cố định, hãy dùng \"/path/to/ignore/**\".", "admin_user": "Quản trị viên", - "asset_offline_description": "Tệp thư viện bên ngoài này không còn trên ổ đĩa và đã bị chuyển vào thùng rác. Nếu tệp đã bị di chuyển trong thư viện, kiểm tra dòng thời gian của bạn để tìm ảnh mới tương ứng. Để khôi phục, hãy đảm bảo Immich có thể truy cập đường dẫn tệp bên dưới và quét lại thư viện.", + "asset_offline_description": "Tài nguyên thư viện ngoài này không còn trên ổ đĩa và đã bị chuyển vào thùng rác. Nếu tệp đã bị di chuyển trong thư viện, kiểm tra dòng thời gian của bạn để tìm tài nguyên mới tương ứng. Để khôi phục tài nguyên, hãy đảm bảo Immich có thể truy cập đường dẫn tệp bên dưới và quét lại thư viện.", "authentication_settings": "Xác thực", "authentication_settings_description": "Quản lý mật khẩu, OAuth và các cài đặt xác thực khác", "authentication_settings_disable_all": "Bạn có chắc muốn vô hiệu hóa mọi phương thức đăng nhập? Đăng nhập sẽ bị vô hiệu hóa hoàn toàn.", @@ -68,26 +68,27 @@ "cleared_jobs": "Đã xóa các tác vụ: {job}", "config_set_by_file": "Cấu hình hiện tại đang được đặt bởi một tệp cấu hình", "confirm_delete_library": "Bạn có chắc muốn xóa thư viện {library}?", - "confirm_delete_library_assets": "Bạn có chắc muốn xóa thư viện này? Thao tác này sẽ xóa {count, plural, one {# tệp} other {tất cả # tệp}} khỏi Immich và không thể hoàn tác. Các tệp sẽ vẫn còn trên ổ đĩa.", + "confirm_delete_library_assets": "Bạn có chắc muốn xóa thư viện này? Thao tác này sẽ xóa {count, plural, one {# tệp} other {toàn bộ # tài nguyên}} khỏi Immich và không thể hoàn tác. Các tệp sẽ vẫn còn trên ổ đĩa.", "confirm_email_below": "Để xác nhận, nhập \"{email}\" bên dưới", "confirm_reprocess_all_faces": "Bạn có chắc muốn xử lý lại tất cả khuôn mặt? Thao tác này sẽ xóa tên người đã được gán.", "confirm_user_password_reset": "Bạn có chắc muốn đặt lại mật khẩu của {user}?", "confirm_user_pin_code_reset": "Bạn có chắc muốn đặt lại mã PIN của {user}?", - "copy_config_to_clipboard_description": "Sao chép cấu hình hệ thống hiện tại dưới dạng đối tượng JSON vào bộ nhớ tạm", + "copy_config_to_clipboard_description": "Sao chép cấu hình hệ thống hiện tại dưới dạng đối tượng JSON vào clipboard", "create_job": "Tạo tác vụ", "cron_expression": "Biểu thức Cron", "cron_expression_description": "Thiết lập khoảng thời gian để quét bằng biểu thức cron. Tham khảo Crontab Guru để biết thêm thông tin", "cron_expression_presets": "Mẫu biểu thức Cron", "disable_login": "Vô hiệu hóa đăng nhập", - "duplicate_detection_job_description": "Chạy học máy để phát hiện các hình ảnh giống nhau. Dựa vào Tìm kiếm Thông Minh", + "download_csv": "Tải xuống CSV", + "duplicate_detection_job_description": "Chạy học máy để phát hiện các tài nguyên giống nhau. Dựa vào Tìm kiếm Thông Minh", "exclusion_pattern_description": "Quy tắc loại trừ dùng để bỏ qua các tệp và thư mục khi quét thư viện của bạn. Điều này hữu ích nếu bạn có các thư mục chứa tệp bạn không muốn nhập, chẳng hạn như các tệp RAW.", "export_config_as_json_description": "Tải xuống cấu hình hệ thống hiện tại dưới dạng tệp JSON", - "external_libraries_page_description": "Trang thư viện bên ngoài của quản trị viên", + "external_libraries_page_description": "Trang thư viện ngoài của quản trị viên", "face_detection": "Nhận diện khuôn mặt", - "face_detection_description": "Sử dụng học máy để nhận diện khuôn mặt trong ảnh. Đối với video, sẽ sử dụng ảnh thu nhỏ. \"Làm mới\" sẽ xử lý lại tất cả tệp. \"Đặt lại\" sẽ xóa hết tất cả dữ liệu khuôn mặt và nhận dạng lại. \"Còn thiếu\" sẽ xử lý các ảnh còn thiếu. Các khuôn mặt được phát hiện sẽ được xử lý bởi tác vụ Nhận diện khuôn mặt để nhóm chúng vào những người đã có hoặc người mới.", + "face_detection_description": "Sử dụng học máy để nhận diện khuôn mặt trong ảnh. Đối với video, sẽ sử dụng ảnh thu nhỏ. \"Làm mới\" sẽ xử lý lại tất cả tệp. \"Đặt lại\" sẽ xóa hết tất cả dữ liệu khuôn mặt và nhận dạng lại. \"Còn thiếu\" sẽ xử lý các ảnh còn thiếu. Các khuôn mặt được nhận diện sẽ được xử lý bởi tác vụ Nhận Diện Khuôn Mặt để nhóm chúng vào những người đã có hoặc người mới.", "facial_recognition_job_description": "Xếp nhóm những khuôn mặt đã nhận diện thành người. Bước này được thực hiện sau khi tác vụ Nhận diện khuôn mặt hoàn tất. \"Đặt lại\" sẽ xếp nhóm lại tất cả khuôn mặt. \"Còn thiếu\" sẽ xử lý các khuôn mặt chưa gán với người nào.", "failed_job_command": "Lệnh {command} không thực hiện được tác vụ: {job}", - "force_delete_user_warning": "CẢNH BÁO: Thao tác này sẽ ngay lập tức xóa người dùng và tất cả ảnh. Điều không thể hoàn tác và các tệp không thể khôi phục.", + "force_delete_user_warning": "CẢNH BÁO: Thao tác này sẽ ngay lập tức xóa người dùng và toàn bộ tài nguyên. Điều không thể hoàn tác và các tệp không thể khôi phục.", "image_format": "Định dạng", "image_format_description": "Định dạng WebP dung lượng nhỏ hơn JPEG, nhưng mã hóa chậm hơn.", "image_fullsize_description": "Ảnh kích thước đẩy đủ với thông tin metadata bị loại bỏ, được dùng khi phóng to", @@ -99,7 +100,7 @@ "image_prefer_embedded_preview_setting_description": "Dùng ảnh xem trước trong ảnh RAW khi có sẵn để xử lý hình ảnh. Điều này có thể giúp tái tạo màu sắc chính xác hơn cho một số ảnh, nhưng chất lượng của ảnh xem trước phụ thuộc vào máy ảnh và có thể bị nhiễu do nén.", "image_prefer_wide_gamut": "Ưu tiên gam màu mở rộng", "image_prefer_wide_gamut_setting_description": "Dùng gam màu Display P3 để hiển thị ảnh thu nhỏ. Điều này giúp giữ màu sắc rực rỡ của những hình ảnh có gam màu rộng, nhưng ảnh có thể trông khác trên các thiết bị và trình duyệt cũ. Ảnh sRGB được giữ nguyên để tránh thay đổi màu sắc.", - "image_preview_description": "Ảnh kích thước trung bình đã loại bỏ metadata, được sử dụng khi xem một tệp duy nhất và cho học máy", + "image_preview_description": "Ảnh kích thước trung bình đã loại bỏ metadata, được sử dụng khi xem một tài nguyên duy nhất và cho học máy", "image_preview_quality_description": "Chất lượng xem trước từ 1-100. Càng cao càng tốt, nhưng sẽ tạo ra các tệp lớn có thể làm giảm khả năng phản hồi của app. Sử dụng giá trị thấp có thể ảnh hưởng đến chất lượng tác vụ học máy.", "image_preview_title": "Cài đặt Xem trước", "image_progressive": "Mang tính tịnh tiến", @@ -120,7 +121,7 @@ "job_settings_description": "Quản lý số lượng tác vụ chạy đồng thời", "jobs_delayed": "{jobCount, plural, other {# bị hoãn lại}}", "jobs_failed": "{jobCount, plural, other {# thất bại}}", - "jobs_over_time": "Tác vụ quá thời gian", + "jobs_over_time": "Tác vụ qua thời gian", "library_created": "Đã tạo thư viện: {library}", "library_deleted": "Thư viện đã bị xóa", "library_details": "Chi tiết thư viện", @@ -130,11 +131,11 @@ "library_scanning": "Quét định kỳ", "library_scanning_description": "Cấu hình 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 bên ngoài", - "library_settings_description": "Quản lý cài đặt thư viện bên ngoài", - "library_tasks_description": "Quét thư viện ngoài để tìm ảnh mới thêm hoặc bị thay đổi", + "library_settings": "Thư viện ngoài", + "library_settings_description": "Quản lý cài đặt thư viện ngoài", + "library_tasks_description": "Quét thư viện ngoài để tìm tài nguyên mới thêm hoặc bị thay đổi", "library_updated": "Đã cập nhật thư viện", - "library_watching_enable_description": "Tự động cập nhật các tệp bị thay đổi trong thư viện bên ngoài", + "library_watching_enable_description": "Tự động cập nhật các tệp bị thay đổi trong thư viện ngoài", "library_watching_settings": "Theo dõi thư viện (THỬ NGHIỆM)", "library_watching_settings_description": "Tự động cập nhật khi các tệp bị thay đổi", "logging_enable_description": "Bật ghi log", @@ -151,7 +152,7 @@ "machine_learning_clip_model_description": "Tên của mô hình CLIP được liệt kê tại đây. Bạn cần chạy lại tác vụ \"Tìm kiếm thông minh\" cho tất cả ảnh sau khi thay đổi mô hình.", "machine_learning_duplicate_detection": "Tìm trùng lặp", "machine_learning_duplicate_detection_enabled": "Bật tìm ảnh trùng lặp", - "machine_learning_duplicate_detection_enabled_description": "Nếu bị tắt, các ảnh trùng lặp giống hệt nhau vẫn sẽ bị loại bỏ.", + "machine_learning_duplicate_detection_enabled_description": "Nếu bị tắt, các tài nguyên trùng lặp giống hệt nhau vẫn sẽ bị loại bỏ.", "machine_learning_duplicate_detection_setting_description": "Sử dụng vector nhúng CLIP để tìm kiếm ảnh trùng lặp", "machine_learning_enabled": "Bật học máy", "machine_learning_enabled_description": "Nếu bị tắt, tất cả tính năng và cài đặt học máy sẽ bị loại bỏ.", @@ -181,16 +182,32 @@ "machine_learning_ocr_min_score_recognition_description": "Điểm tin cậy tối thiểu để nhận dạng văn bản được phát hiện là từ 0-1. Giá trị thấp hơn sẽ nhận dạng được nhiều văn bản hơn nhưng có thể dẫn đến kết quả dương tính giả.", "machine_learning_ocr_model": "Mô hình OCR", "machine_learning_ocr_model_description": "Mô hình máy chủ chính xác hơn mô hình di động, nhưng mất nhiều thời gian xử lý hơn và sử dụng nhiều bộ nhớ hơn.", - "machine_learning_settings": "Cài đặt Học máy", + "machine_learning_settings": "Học máy", "machine_learning_settings_description": "Quản lý các tính năng và cài đặt học máy", "machine_learning_smart_search": "Tìm kiếm Thông minh", "machine_learning_smart_search_description": "Tìm kiếm hình ảnh theo ngữ cảnh với CLIP", "machine_learning_smart_search_enabled": "Bật Tìm kiếm Thông minh", "machine_learning_smart_search_enabled_description": "Nếu tắt, ảnh sẽ không được mã hóa để tìm kiếm thông minh.", "machine_learning_url_description": "Địa chỉ máy chủ học máy. Nếu có nhiều hơn một địa chỉ được cung cấp, mỗi máy chủ sẽ được kiểm tra một lần cho đến khi có một máy chủ trả lời thành công, theo thứ tự từ máy chủ đầu tiên đến máy chủ cuối cùng. Máy chủ không phản hồi sẽ tạm thời được bỏ qua cho đến khi máy chủ online trở lại.", + "maintenance_backup_management": "Quản lý sao lưu", "maintenance_delete_backup": "Xóa bản sao lưu", - "maintenance_delete_backup_description": "Tệp này sẽ bị xoá vĩnh viễn.", + "maintenance_delete_backup_description": "Tệp này sẽ bị xóa vĩnh viễn.", "maintenance_delete_error": "Lỗi khi xóa bản sao lưu.", + "maintenance_integrity_check": "Kiểm tra", + "maintenance_integrity_check_all": "Kiểm tra tất cả", + "maintenance_integrity_checksum_mismatch": "Checksum không khớp", + "maintenance_integrity_checksum_mismatch_description": "Các tệp có checksum trên đĩa không khớp với checksummà Immich đã lưu trữ trong cơ sở dữ liệu của mình.", + "maintenance_integrity_checksum_mismatch_job": "Kiểm tra checksum có khớp không", + "maintenance_integrity_checksum_mismatch_refresh_job": "Làm mới báo cáo trùng khớp checksum", + "maintenance_integrity_missing_file": "Tệp bị thiếu", + "maintenance_integrity_missing_file_description": "Các tệp mà Immich đã theo dõi trong cơ sở dữ liệu của mình nhưng không tồn tại trên hệ thống tệp.", + "maintenance_integrity_missing_file_job": "Kiểm tra các tệp bị thiếu", + "maintenance_integrity_missing_file_refresh_job": "Làm mới báo cáo tệp bị thiếu", + "maintenance_integrity_report": "Báo cáo liêm chính", + "maintenance_integrity_untracked_file": "Tệp không bị theo dõi", + "maintenance_integrity_untracked_file_description": "Các tệp trong thư mục của Immich mà Immich không có bất kỳ bản ghi nào.", + "maintenance_integrity_untracked_file_job": "Kiểm tra các tệp không theo dõi", + "maintenance_integrity_untracked_file_refresh_job": "Làm mới báo cáo tệp không theo dõi", "maintenance_restore_backup": "Khôi phục sao lưu", "maintenance_restore_backup_description": "Immich sẽ xóa sạch toàn bộ dữ liệu hiện tại và khôi phục dữ liệu từ bản sao lưu đã được chọn. Hệ thống sẽ tạo một bản sao lưu cho dữ liệu hiện tại trước khi bắt đầu.", "maintenance_restore_backup_different_version": "Bản sao lưu này đã được tạo ra bằng một phiên bản khác của Immich!", @@ -222,21 +239,21 @@ "memory_cleanup_job": "Dọn dẹp kỷ niệm", "memory_generate_job": "Tạo kỷ niệm", "metadata_extraction_job": "Trích xuất Metadata", - "metadata_extraction_job_description": "Trích xuất Metadata từ mỗi ảnh, chẳng hạn như GPS, khuôn mặt và độ phân giải", + "metadata_extraction_job_description": "Trích xuất Metadata từ mỗi tài nguyên, chẳng hạn như GPS, khuôn mặt và độ phân giải", "metadata_faces_import_setting": "Bật tính năng nhập khuôn mặt", "metadata_faces_import_setting_description": "Nhập khuôn mặt từ dữ liệu EXIF ảnh và tệp đi kèm", "metadata_settings": "Cài đặt Metadata", "metadata_settings_description": "Quản lý cài đặt Metadata", "migration_job": "Di chuyển dữ liệu", - "migration_job_description": "Di chuyển ảnh thu nhỏ của ảnh và khuôn mặt sang cấu trúc thư mục mới", + "migration_job_description": "Di chuyển ảnh thu nhỏ của tài nguyên và khuôn mặt sang cấu trúc thư mục mới", "nightly_tasks_cluster_faces_setting_description": "Chạy nhận diện khuôn mặt trên những khuôn mặt mới được phát hiện", "nightly_tasks_cluster_new_faces_setting": "Nhóm các khuôn mặt mới", "nightly_tasks_database_cleanup_setting": "Tác vụ dọn dẹp cơ sở dữ liệu", "nightly_tasks_database_cleanup_setting_description": "Làm sạch dữ liệu cũ hoặc/và hết hạn trong cơ sở dữ liệu", "nightly_tasks_generate_memories_setting": "Tạo kỷ niệm", - "nightly_tasks_generate_memories_setting_description": "Tạo ra những kỷ niệm mới từ tệp", + "nightly_tasks_generate_memories_setting_description": "Tạo ra những kỷ niệm mới từ tài nguyên", "nightly_tasks_missing_thumbnails_setting": "Tạo ảnh đại diện bị thiếu", - "nightly_tasks_missing_thumbnails_setting_description": "Xếp hàng các tệp không có ảnh thu nhỏ để tạo ảnh thu nhỏ", + "nightly_tasks_missing_thumbnails_setting_description": "Xếp hàng các tài nguyên không có ảnh thu nhỏ để tạo ảnh thu nhỏ", "nightly_tasks_settings": "Cài đặt các tác vụ hàng đêm", "nightly_tasks_settings_description": "Quản lý các tác vụ hàng đêm", "nightly_tasks_start_time_setting": "Thời gian bắt đầu", @@ -245,7 +262,7 @@ "nightly_tasks_sync_quota_usage_setting_description": "Cập nhật hạn mức dung lượng của người dùng, dựa trên mức sử dụng hiện tại", "no_paths_added": "Không có đường dẫn nào được thêm vào", "no_pattern_added": "Không có quy tắc nào được thêm vào", - "note_apply_storage_label_previous_assets": "Lưu ý: Để áp dụng Nhãn lưu trữ cho nội dung đã tải lên trước đó, hãy chạy", + "note_apply_storage_label_previous_assets": "Lưu ý: Để áp dụng Nhãn lưu trữ cho tài nguyên đã tải lên trước đó, hãy chạy", "note_cannot_be_changed_later": "LƯU Ý: Cài đặt này không thể thay đổi được sau khi lưu!", "notification_email_from_address": "Địa chỉ email người gửi", "notification_email_from_address_description": "Địa chỉ email của người gửi, ví dụ: \"Immich Photo Server \". Hãy chắc là bạn dùng một địa chỉ mà bạn được phép gửi email ra.", @@ -278,6 +295,7 @@ "oauth_mobile_redirect_uri": "URI chuyển hướng trên thiết bị di động", "oauth_mobile_redirect_uri_override": "Ghi đè URI chuyển hướng cho thiết bị di động", "oauth_mobile_redirect_uri_override_description": "Bật khi nhà cung cấp OAuth không cho phép URI di động, như ''{callback}''", + "oauth_prompt_description": "Tham số prompt (ví dụ: select_account, login, consent)", "oauth_role_claim": "Vai trò claim", "oauth_role_claim_description": "Tự động cấp quyền quản trị dựa trên sự hiện diện của yêu cầu này. Yêu cầu này có thể có tên là 'người dùng' hoặc 'quản trị viên'.", "oauth_settings": "OAuth", @@ -304,6 +322,8 @@ "refreshing_all_libraries": "Làm mới tất cả thư viện", "registration": "Đăng ký Quản trị viên", "registration_description": "Vì bạn là người dùng đầu tiên, bạn sẽ trở thành Quản trị viên và chịu trách nhiệm cho việc quản lý hệ thống. Ngoài ra, bạn có thể thêm các người dùng khác.", + "release_channel_release_candidate": "Phát hành sớm", + "release_channel_stable": "Chính thức", "remove_failed_jobs": "Xóa các tác vụ không thành công", "require_password_change_on_login": "Yêu cầu người dùng thay đổi mật khẩu trong lần đăng nhập đầu tiên", "reset_settings_to_default": "Đặt lại cài đặt về mặc định", @@ -312,7 +332,7 @@ "search_jobs": "Tìm kiếm tác vụ…", "send_welcome_email": "Gửi email chào mừng", "server_external_domain_settings": "Tên miền công khai", - "server_external_domain_settings_description": "Tên miền dành cho các liên kết chia sẻ công khai", + "server_external_domain_settings_description": "Tên miền dành cho các link chia sẻ công khai", "server_public_users": "Người dùng công khai", "server_public_users_description": "Tất cả người dùng (tên và email) được liệt kê khi thêm một người dùng vào một album được chia sẻ. Khi tắt lựa chọn này, danh sách người dùng chỉ có thể được thấy bởi người dùng quản trị.", "server_settings": "Máy chủ", @@ -324,21 +344,21 @@ "sidecar_job": "Siêu dữ liệu đi kèm", "sidecar_job_description": "Tìm hoặc đồng bộ các tệp siêu dữ liệu đi kèm từ hệ thống", "slideshow_duration_description": "Số giây hiển thị từng ảnh", - "smart_search_job_description": "Chạy học máy trên toàn bộ ảnh để hỗ trợ tìm kiếm thông minh", - "storage_template_date_time_description": "Mốc thời gian tạo tệp được dùng làm thông tin ngày giờ", + "smart_search_job_description": "Chạy học máy trên toàn bộ tài nguyên để hỗ trợ tìm kiếm thông minh", + "storage_template_date_time_description": "Mốc thời gian tạo tài nguyên được dùng làm thông tin ngày giờ", "storage_template_date_time_sample": "Thời gian mẫu {date}", "storage_template_enable_description": "Bật công cụ mẫu lưu trữ", "storage_template_hash_verification_enabled": "Bật xác minh băm", "storage_template_hash_verification_enabled_description": "Bật xác minh băm, không tắt tính năng này trừ khi bạn chắc chắn về các rủi ro có thể xảy ra", "storage_template_migration": "Di chuyển mẫu lưu trữ", - "storage_template_migration_description": "Áp dụng {template} hiện tại cho các ảnh đã được tải lên trước đây", - "storage_template_migration_info": "Mẫu lưu trữ sẽ chuyển tất cả định dạng tập tin thành chữ thường. Những thay đổi của mẫu này chỉ áp dụng cho các ảnh mới tải lên. Để áp dụng mẫu này cho các ảnh đã tải lên trước đây, hãy chạy {job}.", + "storage_template_migration_description": "Áp dụng {template} hiện tại cho các tài nguyên đã được tải lên trước đây", + "storage_template_migration_info": "Mẫu lưu trữ sẽ chuyển đổi tất cả các phần mở rộng thành chữ thường. Những thay đổi của mẫu này chỉ áp dụng cho các tài nguyên mới tải lên. Để áp dụng mẫu này cho các tài nguyên đã tải lên trước đây, hãy chạy {job}.", "storage_template_migration_job": "Tác vụ di chuyển mẫu lưu trữ", "storage_template_more_details": "Cần thêm thông tin chi tiết về tính năng này, vui lòng tham khảo Mẫu lưu trữ và các hệ quả của nó", "storage_template_onboarding_description_v2": "Khi được bật, tính năng này sẽ tự động sắp xếp các tệp dựa trên mẫu do người dùng xác định. Để biết thêm thông tin, vui lòng xem tài liệu.", "storage_template_path_length": "Giới hạn độ dài đường dẫn xấp xỉ: {length, number}/{limit, number}", "storage_template_settings": "Mẫu lưu trữ", - "storage_template_settings_description": "Quản lý cấu trúc thư mục và tên tệp của ảnh tải lên", + "storage_template_settings_description": "Quản lý cấu trúc thư mục và tên tệp của tài nguyên tải lên", "storage_template_user_label": "Cụm từ {label} là Nhãn lưu trữ của người dùng", "system_settings": "Cài đặt hệ thống", "tag_cleanup_job": "Dọn dẹp thẻ", @@ -356,7 +376,7 @@ "theme_settings": "Chủ đề", "theme_settings_description": "Tùy biến giao diện web của Immich", "thumbnail_generation_job": "Tạo ảnh thu nhỏ", - "thumbnail_generation_job_description": "Tạo ảnh thu nhỏ lớn, nhỏ và mờ cho mỗi ảnh, cũng như ảnh thu nhỏ cho mỗi người", + "thumbnail_generation_job_description": "Tạo ảnh thu nhỏ lớn, nhỏ và mờ cho mỗi tài nguyên, cũng như ảnh thu nhỏ cho mỗi người", "transcoding_acceleration_api": "API Gia tốc", "transcoding_acceleration_api_description": "API này sẽ tương tác với thiết bị của bạn để gia tốc quá trình chuyển mã. Cài đặt này hoạt động theo nguyên tắc 'cố gắng hết sức'': nó sẽ quay lại chuyển mã phần mềm nếu gặp lỗi. VP9 có thể hoạt động hoặc không tùy thuộc vào phần cứng của bạn.", "transcoding_acceleration_nvenc": "NVENC (yêu cầu GPU NVIDIA)", @@ -377,7 +397,7 @@ "transcoding_constant_quality_mode": "Chế độ chất lượng cố định", "transcoding_constant_quality_mode_description": "ICQ tốt hơn CQP, nhưng một số thiết bị tăng tốc phần cứng không hỗ trợ chế độ này. Cài đặt tùy chọn này sẽ ưu tiên chế độ được chỉ định khi sử dụng mã hóa dựa trên chất lượng. Bị bỏ qua bởi NVENC vì nó không hỗ trợ ICQ.", "transcoding_constant_rate_factor": "Hệ số tỷ lệ cố định (-crf)", - "transcoding_constant_rate_factor_description": "Mức chất lượng video. Các giá trị điển hình là 23 cho H.264, 28 cho HEVC, 31 cho VP9 và 35 cho AV1. Giá trị thấp hơn thì tốt hơn, nhưng tạo ra các tập tin lớn hơn.", + "transcoding_constant_rate_factor_description": "Chất lượng video. Giá trị điển hình là 23 (H.264), 28 (HEVC), 31 (VP9) và 35 (AV1). Giá trị thấp hơn thì tốt hơn, nhưng tạo ra các tệp lớn hơn.", "transcoding_disabled_description": "Không chuyển mã bất kỳ video nào, có thể gây lỗi phát lại trên một số thiết bị", "transcoding_encoding_options": "Các tùy chọn mã hóa", "transcoding_encoding_options_description": "Thiết lập chuẩn nén video, độ phân giải, chất lượng và các tùy chọn khác cho video", @@ -398,13 +418,17 @@ "transcoding_preferred_hardware_device_description": "Chỉ áp dụng cho VAAPI và QSV. Thiết lập nút dri được sử dụng cho chuyển mã phần cứng.", "transcoding_preset_preset": "Mẫu có sẵn (-preset)", "transcoding_preset_preset_description": "Tốc độ nén. Các mẫu có sẵn chậm hơn tạo ra các tệp nhỏ hơn và cải thiện chất lượng khi mục tiêu là một bitrate cụ thể. VP9 chỉ hỗ trợ các mẫu có sẵn từ 'ultrafast' đến 'faster'.", + "transcoding_realtime": "Chuyển mã thời gian thực [THỬ NGHIỆM]", + "transcoding_realtime_description": "Cho phép chuyển mã video theo thời gian thực khi video đang được phát trực tuyến. Cho phép chuyển đổi chất lượng, nhưng có thể gây ra độ trễ phát lại cao hơn và hiện tượng giật hình tùy thuộc vào khả năng của máy chủ.", + "transcoding_realtime_enabled": "Cho phép chuyển mã thời gian thực", + "transcoding_realtime_enabled_description": "Nếu tắt, máy chủ sẽ từ chối bắt đầu các phiên chuyển mã thời gian thực mới.", "transcoding_reference_frames": "Khung hình tham chiếu", "transcoding_reference_frames_description": "Số lượng khung hình tham chiếu khi nén một khung hình nhất định. Giá trị cao hơn cải thiện hiệu suất nén nhưng làm chậm quá trình mã hóa. Giá trị 0 để tự động thiết lập giá trị này.", "transcoding_required_description": "Chỉ video không ở định dạng được chấp nhận", "transcoding_settings": "Chuyển mã video", "transcoding_settings_description": "Quản lý video chuyển mã và cách xử lý", "transcoding_target_resolution": "Độ phân giải mục tiêu", - "transcoding_target_resolution_description": "Độ phân giải cao hơn có thể giữ lại nhiều chi tiết hơn nhưng mất nhiều thời gian hơn để mã hóa, có kích thước tập tin lớn hơn và có thể làm giảm khả năng phản hồi của app.", + "transcoding_target_resolution_description": "Độ phân giải cao hơn có thể giữ lại nhiều chi tiết hơn nhưng mất nhiều thời gian hơn để mã hóa, có kích thước tệp lớn hơn và có thể làm giảm khả năng phản hồi của app.", "transcoding_temporal_aq": "Lượng tử hóa thích ứng (Temporal AQ)", "transcoding_temporal_aq_description": "Chỉ áp dụng cho NVENC. Lượng tử hóa Thích ứng Thời gian tăng chất lượng cho các cảnh có nhiều chi tiết và ít chuyển động. Có thể không tương thích với các thiết bị cũ.", "transcoding_threads": "Luồng", @@ -412,25 +436,25 @@ "transcoding_tone_mapping": "Ánh Xạ Sắc Thái (Tone-mapping)", "transcoding_tone_mapping_description": "Cố gắng duy trì chất lượng video tốt nhất khi chuyển đổi từ HDR sang SDR. Mỗi thuật toán có sự đánh đổi khác nhau về màu sắc, chi tiết và độ sáng. Hable giữ chi tiết, Mobius giữ màu sắc và Reinhard giữ độ sáng.", "transcoding_transcode_policy": "Quy tắc chuyển mã", - "transcoding_transcode_policy_description": "Quy tắc khi nào video nên được chuyển mã. Các video HDR luôn được chuyển mã (ngoại trừ khi tính năng chuyển mã bị tắt).", + "transcoding_transcode_policy_description": "Quy tắc chuyển mã video. Các video HDR và video có định dạng pixel khác YUV 4:2:0 luôn được chuyển mã (trừ khi tính năng chuyển mã bị tắt).", "transcoding_two_pass_encoding": "Mã hóa hai lần", "transcoding_two_pass_encoding_setting_description": "Chuyển mã hai lần để tạo ra video được mã hóa tốt hơn. Khi bitrate tối đa được bật (bắt buộc để hoạt động với H.264 và HEVC), chế độ này sử dụng một phạm vi bitrate dựa trên bitrate tối đa và bỏ qua CRF. Đối với VP9, CRF có thể được sử dụng nếu bitrate tối đa bị tắt.", "transcoding_video_codec": "Chuẩn nén video", - "transcoding_video_codec_description": "VP9 có hiệu suất cao và tương thích tốt với web, nhưng thời gian chuyển mã lâu hơn. HEVC có hiệu suất tương tự, nhưng tương thích web thấp hơn. H.264 tương thích rộng rãi và chuyển mã nhanh, nhưng tạo ra các tập tin có kích thước lớn. AV1 là codec hiệu quả nhất nhưng không được hỗ trợ trên các thiết bị cũ.", + "transcoding_video_codec_description": "VP9 có hiệu suất cao và tương thích web tốt, nhưng thời gian chuyển mã lâu hơn. HEVC có hiệu suất tương tự, nhưng tương thích web thấp hơn. H.264 tương thích rộng rãi và chuyển mã nhanh, nhưng tạo ra các tệp có kích thước lớn. AV1 là codec hiệu quả nhất nhưng không được hỗ trợ trên các thiết bị cũ.", "trash_enabled_description": "Bật tính năng Thùng rác", "trash_number_of_days": "Số ngày", - "trash_number_of_days_description": "Số ngày giữ các ảnh trong thùng rác trước khi xóa chúng vĩnh viễn", + "trash_number_of_days_description": "Số ngày giữ các tài nguyên trong thùng rác trước khi xóa chúng vĩnh viễn", "trash_settings": "Thùng rác", "trash_settings_description": "Quản lý cài đặt thùng rác", "unlink_all_oauth_accounts": "Hủy liên kết tất cả tài khoản OAuth", "unlink_all_oauth_accounts_description": "Hãy nhớ hủy liên kết tất cả tài khoản OAuth trước khi di chuyển sang nhà cung cấp mới.", "unlink_all_oauth_accounts_prompt": "Bạn có chắc muốn hủy liên kết tất cả tài khoản OAuth không? Thao tác này sẽ đặt lại ID OAuth cho mỗi người dùng và không thể hoàn tác.", "user_cleanup_job": "Dọn dẹp người dùng", - "user_delete_delay": "Tài khoản và tệp của {user} sẽ được lên lịch xóa vĩnh viễn sau {delay, plural, one {# ngày} other {# ngày}}.", + "user_delete_delay": "Tài khoản và tài nguyên của {user} sẽ được lên lịch xóa vĩnh viễn sau {delay, plural, one {# ngày} other {# ngày}}.", "user_delete_delay_settings": "Thời gian xóa", - "user_delete_delay_settings_description": "Số ngày chờ xóa để xóa vĩnh viễn tài khoản và tệp của người dùng. Tác vụ xóa người dùng chạy vào giữa đêm để kiểm tra các người dùng sẵn sàng bị xóa. Thay đổi cài đặt này sẽ được đánh giá vào lần thực hiện tiếp theo.", - "user_delete_immediately": "Tài khoản và ảnh của {user} sẽ được đưa vào hàng đợi để xóa vĩnh viễn ngay lập tức.", - "user_delete_immediately_checkbox": "Xếp hàng người dùng và ảnh để xóa ngay lập tức", + "user_delete_delay_settings_description": "Số ngày chờ xóa để xóa vĩnh viễn tài khoản và tài nguyên của người dùng. Tác vụ xóa người dùng chạy vào giữa đêm để kiểm tra các người dùng sẵn sàng bị xóa. Thay đổi cài đặt này sẽ được đánh giá vào lần thực hiện tiếp theo.", + "user_delete_immediately": "Tài khoản và tài nguyên của {user} sẽ được đưa vào hàng đợi để xóa vĩnh viễn ngay lập tức.", + "user_delete_immediately_checkbox": "Xếp hàng người dùng và tài nguyên để xóa ngay lập tức", "user_details": "Chi tiết Người dùng", "user_management": "Quản lý người dùng", "user_password_has_been_reset": "Mật khẩu của người dùng đã được đặt lại:", @@ -441,24 +465,26 @@ "user_settings_description": "Quản lý cài đặt người dùng", "user_successfully_removed": "Người dùng {email} đã được xóa thành công.", "users_page_description": "Trang quản trị người dùng", + "version_check_channel": "Kênh phát hành", + "version_check_channel_description": "Chọn kênh phát hành mà bạn muốn nhận thông báo về phiên bản mới", "version_check_enabled_description": "Bật kiểm tra phiên bản", "version_check_implications": "Tính năng kiểm tra phiên bản yêu cầu kết nối thường xuyên đến {server}", "version_check_settings": "Kiểm tra phiên bản", "version_check_settings_description": "Bật/tắt thông báo phiên bản mới", "video_conversion_job": "Chuyển mã video", - "video_conversion_job_description": "Chuyển đổi định dạng video để tương thích rộng rãi hơn với trình duyệt và thiết bị" + "video_conversion_job_description": "Chuyển mã video để tương thích với nhiều trình duyệt và thiết bị hơn" }, "admin_email": "Email Quản trị viên", "admin_password": "Mật khẩu Quản trị viên", "administration": "Quản trị", "advanced": "Nâng cao", - "advanced_settings_clear_image_cache": "Giải phóng bộ nhớ đệm", - "advanced_settings_clear_image_cache_error": "Lỗi khi giải phóng bộ nhớ đệm", + "advanced_settings_clear_image_cache": "Xóa bộ nhớ đệm", + "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_enable_alternate_media_filter_subtitle": "Dùng tùy chọn này để lọc phương tiện khi đồng bộ theo tiêu chí khác. Chỉ thử khi app không nhận diện được tất cả các album.", "advanced_settings_enable_alternate_media_filter_title": "[THỬ NGHIỆM] Dùng bộ lọc đồng bộ album thay thế", "advanced_settings_log_level_title": "Phân loại log: {level}", - "advanced_settings_prefer_remote_subtitle": "Việc tải ảnh thu nhỏ từ ảnh 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": "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_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]", @@ -466,7 +492,7 @@ "advanced_settings_readonly_mode_title": "Chế độ chỉ-xem", "advanced_settings_self_signed_ssl_subtitle": "Bỏ qua xác minh chứng chỉ SSL cho endpoint máy chủ. Yêu cầu cho chứng chỉ tự ký.", "advanced_settings_self_signed_ssl_title": "Cho phép chứng chỉ SSL tự ký [THỬ NGHIỆM]", - "advanced_settings_sync_remote_deletions_subtitle": "Tự động xóa hoặc khôi phục dữ liệu trên thiết bị này khi bạn thao tác trên web", + "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ố", @@ -475,7 +501,7 @@ "age_year_months": "1 tuổi, {months, plural, one {# tháng} other {# tháng}}", "age_years": "{years, plural, other {# tuổi}}", "album": "Album", - "album_added": "Đã thêm album", + "album_added": "Được thêm vào album", "album_added_notification_setting_description": "Nhận thông báo qua email khi bạn được thêm vào một album chia sẻ", "album_cover_updated": "Đã cập nhật ảnh bìa album", "album_delete_confirmation": "Bạn có chắc muốn xóa album {album}?", @@ -494,25 +520,25 @@ "album_selected": "Album đã chọn", "album_share_no_users": "Có vẻ như bạn đã chia sẻ album này với tất cả người dùng hoặc bạn không có người dùng nào để chia sẻ.", "album_summary": "Mô tả album", - "album_updated": "Đã cập nhật album", - "album_updated_setting_description": "Nhận thông báo qua email khi một album chia sẻ có các ảnh mới", - "album_upload_assets": "Tải ảnh/video từ máy tính của bạn lên và thêm vào album", + "album_updated": "Album có cập nhật", + "album_updated_setting_description": "Nhận thông báo qua email khi một album chia sẻ có các tài nguyên mới", + "album_upload_assets": "Tải tài nguyên từ máy tính của bạn lên và thêm vào album", "album_user_left": "Đã rời khỏi {album}", "album_user_removed": "Đã xóa {user}", "album_viewer_appbar_delete_confirm": "Bạn có muốn xóa album này khỏi tài khoản của mình?", "album_viewer_appbar_share_err_delete": "Xóa album thất bại", "album_viewer_appbar_share_err_leave": "Rời khỏi album thất bại", - "album_viewer_appbar_share_err_remove": "Có vấn đề khi xóa ảnh khỏi album", + "album_viewer_appbar_share_err_remove": "Có vấn đề khi xóa tài nguyên khỏi album", "album_viewer_appbar_share_err_title": "Thay đổi tên album thất bại", "album_viewer_appbar_share_leave": "Rời khỏi album", "album_viewer_appbar_share_to": "Chia sẻ với", "album_viewer_page_share_add_users": "Thêm người dùng", - "album_with_link_access": "Ai có liên kết sẽ xem được các ảnh và người trong album này.", + "album_with_link_access": "Ai có link sẽ xem được các ảnh và người trong album này.", "albums": "Album", "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Album}}", "albums_default_sort_order": "Thứ tự sắp xếp album mặc định", - "albums_default_sort_order_description": "Thứ tự sắp xếp ban đầu cho các ảnh khi tạo album mới.", - "albums_feature_description": "Các bộ sưu tập tệp có thể được chia sẻ với những người dùng khác.", + "albums_default_sort_order_description": "Thứ tự sắp xếp ban đầu cho các tài nguyên khi tạo album mới.", + "albums_feature_description": "Các bộ sưu tập tài nguyên có thể chia sẻ được với người khác", "albums_on_device_count": "Album trên thiết bị ({count})", "albums_selected": "{count, plural, one {# album đã chọn} other {# album đã chọn}}", "all": "Tất cả", @@ -534,7 +560,7 @@ "api_key_description": "Giá trị này chỉ được hiển thị một lần. Vui lòng sao chép nó trước khi đóng cửa sổ.", "api_key_empty": "Tên khóa API của bạn không được để trống", "api_keys": "Khóa API", - "app_architecture_variant": "Variant (Kiến trúc)", + "app_architecture_variant": "Biến thể (Kiến trúc)", "app_bar_signout_dialog_content": "Bạn có muốn đăng xuất?", "app_bar_signout_dialog_ok": "Có", "app_bar_signout_dialog_title": "Đăng xuất", @@ -547,69 +573,70 @@ "archive": "Lưu trữ", "archive_action_prompt": "{count} đã được thêm vào Lưu trữ", "archive_or_unarchive_photo": "Lưu trữ hoặc bỏ lưu trữ ảnh", - "archive_page_no_archived_assets": "Không tìm thấy tệp đã lưu trữ", + "archive_page_no_archived_assets": "Không tìm thấy tài nguyên đã lưu trữ", "archive_page_title": "Kho lưu trữ ({count})", - "archive_size": "Kích cỡ lưu trữ", - "archive_size_description": "Cấu hình kích cỡ nén cho các tệp tải xuống (đơn vị GiB)", + "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)", "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?", "array_field_not_fully_supported": "Các trường mảng yêu cầu chỉnh sửa JSON thủ công", - "asset_action_delete_err_read_only": "Không thể xóa tệp chỉ có quyền đọc, bỏ qua", - "asset_action_share_err_offline": "Không thể tải tệp ngoại tuyến, bỏ qua", + "asset_action_delete_err_read_only": "Không thể xóa tài nguyên chỉ có quyền đọc, bỏ qua", + "asset_action_share_err_offline": "Không thể nạp tài nguyên ngoại tuyến, bỏ qua", "asset_added_to_album": "Đã thêm vào album", "asset_adding_to_album": "Đang thêm vào album…", - "asset_created": "Đã tạo tệp", - "asset_description_updated": "Mô tả ảnh đã được cập nhật", - "asset_filename_is_offline": "Tệp {filename} đang ngoại tuyến", - "asset_has_unassigned_faces": "Tệp chưa được gán khuôn mặt", + "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", + "asset_filename_is_offline": "Tài nguyên {filename} đang ngoại tuyến", + "asset_has_unassigned_faces": "Tài nguyên chưa được gán khuôn mặt", "asset_hashing": "Đang băm…", "asset_list_group_by_sub_title": "Nhóm theo", "asset_list_layout_settings_dynamic_layout_title": "Bố cục động", "asset_list_layout_settings_group_automatically": "Tự động", - "asset_list_layout_settings_group_by": "Nhóm tệp theo", + "asset_list_layout_settings_group_by": "Nhóm tài nguyên theo", "asset_list_layout_settings_group_by_month_day": "Tháng + ngày", "asset_list_layout_sub_title": "Bố cục", "asset_list_settings_subtitle": "Bố cục lưới ảnh", "asset_list_settings_title": "Lưới ảnh", - "asset_not_found_on_device_android": "Không tìm thấy tệp trên thiết bị", - "asset_not_found_on_device_ios": "Không tìm thấy tệp trên thiết bị. Nếu bạn đang dùng iCloud, có thể tệp lưu trên iCloud bị lỗi nên không thể được truy cập", - "asset_not_found_on_icloud": "Không tìm thấy tệp trên iCloud. Có thể tệp lưu trên iCloud bị lỗi nên không thể được truy cập", - "asset_offline": "Tệp Ngoại tuyến", - "asset_offline_description": "Tệp bên ngoài này không còn trên ổ đĩa. Vui lòng liên hệ quản trị viên Immich của bạn để được trợ giúp.", - "asset_restored_successfully": "Đã khôi phục tệp thành công", + "asset_not_found_on_device_android": "Không tìm thấy tài nguyên trên thiết bị", + "asset_not_found_on_device_ios": "Không tìm thấy tài nguyên trên thiết bị. Nếu bạn đang dùng iCloud, có thể tài nguyên lưu trên iCloud bị lỗi nên không thể được truy cập", + "asset_not_found_on_icloud": "Không tìm thấy tài nguyên trên iCloud. Có thể tài nguyên lưu trên iCloud bị lỗi nên không thể được truy cập", + "asset_offline": "Tài nguyên Ngoại tuyến", + "asset_offline_description": "Tài nguyên bên ngoài này không còn trên ổ đĩa. Vui lòng liên hệ quản trị viên Immich của bạn để được trợ giúp.", + "asset_restored_successfully": "Đã khôi phục tài nguyên thành công", "asset_skipped": "Đã bỏ qua", "asset_skipped_in_trash": "Trong thùng rác", - "asset_trashed": "Đã chuyển tệp đến thùng rác", - "asset_troubleshoot": "Khắc phục sự cố tệp", + "asset_trashed": "Đã chuyển tài nguyên vào thùng rác", + "asset_troubleshoot": "Khắc phục sự cố tài nguyên", "asset_uploaded": "Đã tải lên", "asset_uploading": "Đang tải lên…", "asset_viewer_settings_subtitle": "Cách thư viện hiển thị", - "asset_viewer_settings_title": "Trình xem ảnh", - "assets": "Tệp", - "assets_added_count": "Đã thêm {count, plural, one {# tệp} other {# tệp}}", - "assets_added_to_album_count": "Đã thêm {count, plural, one {# tệp} other {# tệp}} vào album", - "assets_added_to_albums_count": "Đã thêm {assetTotal, plural, one {# tệp} other {# tệp}} vào {albumTotal, plural, one {# album} other {# album}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Tệp} other {Tệp}} không thể thêm vào album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Tệp} other {Tệp}} không thể thêm vào bất kỳ album nào", - "assets_count": "{count, plural, one {# tệp} other {# tệp}}", - "assets_deleted_permanently": "Đã xóa vĩnh viễn {count} tệp", - "assets_deleted_permanently_from_server": "Đã xóa vĩnh viễn {count} tệp khỏi máy chủ Immich", + "asset_viewer_settings_title": "Duyệt tài nguyên", + "assets": "Tài nguyên", + "assets_added_count": "Đã thêm {count, plural, one {# tài nguyên} other {# tài nguyên}}", + "assets_added_to_album_count": "Đã thêm {count, plural, one {# tài nguyên} other {# tài nguyên}} vào album", + "assets_added_to_albums_count": "Đã thêm {assetTotal, plural, one {# tài nguyên} other {# tài nguyên}} vào {albumTotal, plural, one {# album} other {# album}}", + "assets_cannot_be_added_to_album_count": "{count, plural, one {Tài nguyên} other {Tài nguyên}} không thể thêm vào album", + "assets_cannot_be_added_to_albums": "{count, plural, one {Tài nguyên} other {Tài nguyên}} không thể thêm vào bất kỳ album nào", + "assets_count": "{count, plural, one {# tài nguyên} other {# tài nguyên}}", + "assets_deleted_permanently": "Đã xóa vĩnh viễn {count} tài nguyên", + "assets_deleted_permanently_from_server": "Đã xóa vĩnh viễn {count} tài nguyên khỏi máy chủ Immich", "assets_downloaded_failed": "{count, plural, one {Đã tải xuống # tệp - {error} tệp thất bại} other {Đã tải xuống # tệp - {error} tệp thất bại}}", "assets_downloaded_successfully": "{count, plural, one {Đã tải xuống # tệp thành công} other {Đã tải xuống # tệp thành công}}", - "assets_moved_to_trash_count": "Đã chuyển {count, plural, one {# tệp} other {# tệp}} vào thùng rác", - "assets_permanently_deleted_count": "Đã xóa vĩnh viễn {count, plural, one {# tệp} other {# tệp}}", - "assets_removed_count": "Đã xóa {count, plural, one {# tệp} other {# tệp}}", - "assets_removed_permanently_from_device": "Đã xóa vĩnh viễn {count} tệp khỏi thiết bị của bạn", - "assets_restore_confirmation": "Bạn có chắc muốn khôi phục tất cả mục đã xóa của mình không? Bạn không thể hoàn tác hành động này! Lưu ý rằng không thể khôi phục các ảnh ngoại tuyến theo cách này.", - "assets_restored_count": "Đã khôi phục {count, plural, one {# tệp} other {# tệp}}", - "assets_restored_successfully": "Đã khôi phục {count} tệp thành công", - "assets_trashed": "Đã chuyển {count} tệp vào thùng rác", - "assets_trashed_count": "Đã chuyển {count, plural, one {# tệp} other {# tệp}} vào thùng rác", - "assets_trashed_from_server": "Đã chuyển {count} tệp từ máy chủ Immich vào thùng rác", - "assets_were_part_of_album_count": "{count, plural, one {Tệp đã} other {Các tệp đã}} có sẵn trong album", - "assets_were_part_of_albums_count": "{count, plural, one {Tệp đã} other {Tệp đã}} có sẵn trong album", + "assets_moved_to_trash_count": "Đã chuyển {count, plural, one {# tài nguyên} other {# tài nguyên}} vào thùng rác", + "assets_permanently_deleted_count": "Đã xóa vĩnh viễn {count, plural, one {# tài nguyên} other {# tài nguyên}}", + "assets_removed_count": "Đã xóa {count, plural, one {# tài nguyên} other {# tài nguyên}}", + "assets_removed_permanently_from_device": "Đã xóa vĩnh viễn {count} tài nguyên khỏi thiết bị của bạn", + "assets_restore_confirmation": "Bạn có chắc muốn khôi phục tất cả tài nguyên đã xóa của mình không? Bạn không thể hoàn tác hành động này! Lưu ý rằng không thể khôi phục các tài nguyên ngoại tuyến theo cách này.", + "assets_restored_count": "Đã khôi phục {count, plural, one {# tài nguyên} other {# tài nguyên}}", + "assets_restored_successfully": "Đã khôi phục {count} tài nguyên thành công", + "assets_trashed": "Đã chuyển {count} tài nguyên vào thùng rác", + "assets_trashed_count": "Đã chuyển {count, plural, one {# tài nguyên} other {# tài nguyên}} vào thùng rác", + "assets_trashed_from_server": "Đã chuyển {count} tài nguyên từ máy chủ Immich vào thùng rác", + "assets_were_part_of_album_count": "{count, plural, one {Tài nguyên đã} other {Tài nguyên đã}} có sẵn trong album", + "assets_were_part_of_albums_count": "{count, plural, one {Tài nguyên đã} other {Tài nguyên}} có sẵn trong album", "authorized_devices": "Thiết bị", "automatic_endpoint_switching_subtitle": "Kết nối nội bộ qua Wi-Fi được chỉ định khi kết nối được và sử dụng các kết nối thay thế ở nơi khác", "automatic_endpoint_switching_title": "Tự động chuyển đổi địa chỉ máy chủ", @@ -623,19 +650,19 @@ "backup": "Sao lưu", "backup_album_selection_page_albums_device": "Album trên thiết bị ({count})", "backup_album_selection_page_albums_tap": "Nhấn để chọn, nhấn đúp để bỏ qua", - "backup_album_selection_page_assets_scatter": "Ảnh có thể có trong nhiều album khác nhau. Trong quá trình sao lưu, bạn có thể chọn để sao lưu tất cả các album hoặc chỉ một số album nhất định.", + "backup_album_selection_page_assets_scatter": "Tài nguyên có thể có trong nhiều album khác nhau. Trong quá trình sao lưu, bạn có thể chọn để sao lưu tất cả các album hoặc chỉ một số album nhất định.", "backup_album_selection_page_select_albums": "Chọn album", "backup_album_selection_page_selection_info": "Thông tin các mục đã chọn", - "backup_album_selection_page_total_assets": "Tổng số tệp không trùng lặp", + "backup_album_selection_page_total_assets": "Tổng số tài nguyên không trùng lặp", "backup_albums_sync": "Đồng bộ hóa bản sao lưu album", "backup_all": "Tất cả", - "backup_background_service_backup_failed_message": "Sao lưu tệp thất bại. Đang thử lại…", - "backup_background_service_complete_notification": "Hoàn tất sao lưu tệp", + "backup_background_service_backup_failed_message": "Sao lưu tài nguyên thất bại. Đang thử lại…", + "backup_background_service_complete_notification": "Hoàn tất sao lưu tài nguyên", "backup_background_service_connection_failed_message": "Kết nối tới máy chủ thất bại. Đang thử lại…", "backup_background_service_current_upload_notification": "Đang tải lên {filename}", - "backup_background_service_default_notification": "Đang kiểm tra tệp mới…", + "backup_background_service_default_notification": "Đang kiểm tra các tài nguyên mới…", "backup_background_service_error_title": "Sao lưu không thành công", - "backup_background_service_in_progress_notification": "Đang sao lưu tệp của bạn…", + "backup_background_service_in_progress_notification": "Đang sao lưu tài nguyên của bạn…", "backup_background_service_upload_failure_notification": "Tải lên {filename} thất bại", "backup_controller_page_albums": "Album sao lưu", "backup_controller_page_background_app_refresh_disabled_content": "Bật làm mới ứng dụng trong nền tại Cài đặt > Cài đặt chung > Làm mới ứng dụng trong nền để dùng sao lưu nền.", @@ -647,8 +674,8 @@ "backup_controller_page_background_battery_info_title": "Tiết kiệm pin", "backup_controller_page_background_charging": "Chỉ khi đang sạc", "backup_controller_page_background_configure_error": "Cấu hình dịch vụ nền thất bại", - "backup_controller_page_background_delay": "Trì hoãn sao lưu tệp mới: {duration}", - "backup_controller_page_background_description": "Bật dịch vụ nền để tự động sao lưu tệp mới mà không cần mở ứng dụng", + "backup_controller_page_background_delay": "Trì hoãn sao lưu tài nguyên mới: {duration}", + "backup_controller_page_background_description": "Bật dịch vụ nền để tự động sao lưu tài nguyên mới mà không cần mở ứng dụng", "backup_controller_page_background_is_off": "Sao lưu tự động trong nền đang tắt", "backup_controller_page_background_is_on": "Sao lưu tự động trong nền đang bật", "backup_controller_page_background_turn_off": "Tắt dịch vụ nền", @@ -658,28 +685,28 @@ "backup_controller_page_backup_selected": "Đã chọn: ", "backup_controller_page_backup_sub": "Ảnh và video đã sao lưu", "backup_controller_page_created": "Tạo vào: {date}", - "backup_controller_page_desc_backup": "Bật sao lưu khi ứng dụng hoạt động để tự động sao lưu tệp mới lên máy chủ khi mở ứng dụng.", + "backup_controller_page_desc_backup": "Bật sao lưu khi ứng dụng hoạt động để tự động sao lưu tài nguyên mới lên máy chủ khi mở ứng dụng.", "backup_controller_page_excluded": "Đã bỏ qua: ", "backup_controller_page_failed": "Thất bại ({count})", "backup_controller_page_filename": "Tên tệp: {filename} [{size}]", "backup_controller_page_id": "ID: {id}", "backup_controller_page_info": "Thông tin sao lưu", - "backup_controller_page_none_selected": "Không có mục nào được chọn", + "backup_controller_page_none_selected": "Chưa chọn mục nào", "backup_controller_page_remainder": "Còn lại", "backup_controller_page_remainder_sub": "Số lượng ảnh và video đã chọn chưa được sao lưu", "backup_controller_page_server_storage": "Dung lượng máy chủ", "backup_controller_page_start_backup": "Bắt đầu sao lưu", "backup_controller_page_status_off": "Sao lưu tự động khi ứng dụng hoạt động đang tắt", "backup_controller_page_status_on": "Sao lưu tự động khi ứng dụng hoạt động đang bật", - "backup_controller_page_storage_format": "Đã dùng {used} của {total}", - "backup_controller_page_to_backup": "Các album cần được sao lưu", + "backup_controller_page_storage_format": "Đã dùng {used} trong {total}", + "backup_controller_page_to_backup": "Các album sẽ được sao lưu", "backup_controller_page_total_sub": "Tất cả ảnh và video không trùng lập từ các album được chọn", "backup_controller_page_turn_off": "Tắt sao lưu khi ứng dụng hoạt động", "backup_controller_page_turn_on": "Bật sao lưu khi mở app", "backup_controller_page_uploading_file_info": "Thông tin tệp đang tải lên", "backup_err_only_album": "Không thể xóa album duy nhất", "backup_error_sync_failed": "Đồng bộ thất bại. Không thể tiến hành sao lưu.", - "backup_info_card_assets": "tệp", + "backup_info_card_assets": "tài nguyên", "backup_manual_cancelled": "Đã hủy", "backup_manual_in_progress": "Đang tải lên. Vui lòng thử lại sau", "backup_manual_success": "Thành công", @@ -690,6 +717,7 @@ "backup_settings_subtitle": "Cài đặt việc tải lên", "backup_upload_details_page_more_details": "Nhấn để hiện chi tiết", "backward": "Lùi 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", "biometric_no_options": "Không có tùy chọn bằng sinh trắc học", @@ -697,19 +725,19 @@ "birthdate_saved": "Sinh nhật đã được lưu thành công", "birthdate_set_description": "Sinh nhật được sử dụng để tính tuổi của người này tại thời điểm chụp ảnh.", "blurred_background": "Nền mờ", - "browse_templates": "Xem các mẫu", + "browse_templates": "Xem mẫu có sẵn", "bugs_and_feature_requests": "Báo lỗi & Đề xuất tính năng", - "build": "Dựng", + "build": "Build", "build_image": "Bản dựng", - "bulk_delete_duplicates_confirmation": "Bạn có chắc muốn xóa hàng loạt {count, plural, one {# tệp trùng lặp} other {# tệp trùng lặp}}? Điều này sẽ giữ lại ảnh chất lượng nhất của mỗi nhóm và xóa vĩnh viễn tất cả các bản trùng lặp khác. Bạn không thể hoàn tác hành động này!", - "bulk_keep_duplicates_confirmation": "Bạn có chắc muốn giữ lại {count, plural, one {# tệp trùng lặp} other {# tệp trùng lặp}}? Điều này sẽ xử lý tất cả các nhóm ảnh trùng lặp mà không xóa bất kỳ thứ gì.", - "bulk_trash_duplicates_confirmation": "Bạn có chắc muốn đưa {count, plural, one {# tệp trùng lặp} other {# tệp trùng lặp}} vào thùng rác? Điều này sẽ giữ lại ảnh chất lượng nhất của mỗi nhóm và đưa tất cả các bản trùng lặp khác vào thùng rác.", + "bulk_delete_duplicates_confirmation": "Bạn có chắc muốn xóa hàng loạt {count, plural, one {# tài nguyên trùng lặp} other {# tài nguyên trùng lặp}}? Điều này sẽ giữ lại tài nguyên lớn nhất của mỗi nhóm và xóa vĩnh viễn tất cả các bản trùng lặp khác. Bạn không thể hoàn tác hành động này!", + "bulk_keep_duplicates_confirmation": "Bạn có chắc muốn giữ lại {count, plural, one {# tài nguyên trùng lặp} other {# tài nguyên trùng lặp}}? Điều này sẽ xử lý tất cả các nhóm ảnh trùng lặp mà không xóa bất kỳ thứ gì.", + "bulk_trash_duplicates_confirmation": "Bạn có chắc muốn đưa {count, plural, one {# tài nguyên trùng lặp} other {# tài nguyên trùng lặp}} vào thùng rác? Điều này sẽ giữ lại tài nguyên lớn nhất của mỗi nhóm và đưa tất cả các bản trùng lặp khác vào thùng rác.", "buy": "Mua Immich", "cache_settings_clear_cache_button": "Xóa bộ nhớ đệm", "cache_settings_clear_cache_button_title": "Xóa bộ nhớ đệm của ứng dụng. Điều này sẽ ảnh hưởng đến hiệu suất của ứng dụng đến khi bộ nhớ đệm được tạo lại.", "cache_settings_duplicated_assets_clear_button": "XÓA", "cache_settings_duplicated_assets_subtitle": "Ảnh và video không được phép hiển thị trên ứng dụng", - "cache_settings_duplicated_assets_title": "Tệp bị trùng ({count})", + "cache_settings_duplicated_assets_title": "Tài nguyên trùng lặp ({count})", "cache_settings_statistics_album": "Ảnh thu nhỏ thư viện", "cache_settings_statistics_full": "Ảnh đầy đủ", "cache_settings_statistics_shared": "Ảnh thu nhỏ album chia sẻ", @@ -755,26 +783,26 @@ "changed_visibility_successfully": "Đã đổi trạng thái hiển thị thành công", "charging": "Sạc", "charging_requirement_mobile_backup": "Sao lưu dưới nền yêu cầu thiết bị phải đang sạc", - "check_corrupt_asset_backup": "Kiểm tra tệp bị hỏng", + "check_corrupt_asset_backup": "Kiểm tra tài nguyên bị hỏng", "check_corrupt_asset_backup_button": "Tiến hành kiểm tra", - "check_corrupt_asset_backup_description": "Chỉ chạy kiểm tra này khi có Wi-Fi và sau khi đã sao lưu toàn bộ dữ liệu. Quá trình có thể mất vài phút.", + "check_corrupt_asset_backup_description": "Chỉ chạy kiểm tra này khi có Wi-Fi và sau khi đã sao lưu toàn bộ tài nguyên. Quá trình có thể mất vài phút.", "check_logs": "Kiểm tra log", "checksum": "Checksum", "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ệp (đượ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 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_prompt_title": "Xóa khỏi thiết bị này?", - "cleanup_deleted_assets": "Đã chuyển {count} tệp vào thùng rác", + "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ệp được sao lưu", - "cleanup_found_assets_with_size": "Phát hiện {count} được sao lưu ({size})", + "cleanup_found_assets": "Phát hiện {count} tài nguyên được 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ệp nào phù hợp với điều khiện trên. Tính năng Giải phóng dung lượng chỉ có thể xóa tệp đã được sao lưu lên máy chủ", - "cleanup_preview_title": "Các tệp sẽ bị xóa ({count})", - "cleanup_step3_description": "Tìm các tệp đã được sao lưu theo bộ lọc ngày và giữ lại cài đặt của bạn.", - "cleanup_step4_summary": "Có {count} tệp (được tạo trước {date}) sẽ bị xóa khỏi thiết bị của bạn. Ảnh vẫn có thể được truy cập bằng ứng dụng Immich.", - "cleanup_trash_hint": "Để giải phóng tối đa bộ nhớ, vui lòng mở ứng dụng thư viện ảnh của hệ thống và dọn sạch thùng rác", + "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ủ", + "cleanup_preview_title": "Các tài nguyên sẽ bị xóa ({count})", + "cleanup_step3_description": "Tìm các tài nguyên đã được sao lưu theo bộ lọc ngày và giữ lại cài đặt của bạn.", + "cleanup_step4_summary": "Có {count} ttài nguyên (được tạo trước {date}) sẽ bị xóa khỏi thiết bị của bạn. Ảnh vẫn có thể được truy cập bằng ứng dụng Immich.", + "cleanup_trash_hint": "Để tiết kiệm tối đa bộ nhớ, vui lòng mở ứng dụng thư viện ảnh của hệ thống và dọn sạch thùng rác", "clear": "Xóa", "clear_all": "Xóa tất cả", "clear_all_recent_searches": "Xóa tất cả tìm kiếm gần đây", @@ -813,13 +841,13 @@ "configuration": "Cấu hình", "confirm": "Xác nhận", "confirm_admin_password": "Xác nhận mật khẩu quản trị viên", - "confirm_delete_face": "Bạn có chắc muốn xóa khuôn mặt {name} khỏi tệp?", - "confirm_delete_shared_link": "Bạn có chắc muốn xóa liên kết chia sẻ này?", - "confirm_keep_this_delete_others": "Các ảnh còn lại trong nhóm sẽ bị xóa ngoại trừ ảnh này. Bạn có chắc muốn tiếp tục?", + "confirm_delete_face": "Bạn có chắc muốn xóa khuôn mặt {name} khỏi tài nguyên?", + "confirm_delete_shared_link": "Bạn có chắc muốn xóa link chia sẻ này?", + "confirm_keep_this_delete_others": "Các tài nguyên còn lại trong nhóm sẽ bị xóa ngoại trừ tài nguyên này. Bạn có chắc muốn tiếp tục?", "confirm_new_pin_code": "Xác nhận mã PIN mới", "confirm_password": "Xác nhận mật khẩu", - "confirm_tag_face": "Bạn có muốn gắn thẻ gương mặt này là {name}?", - "confirm_tag_face_unnamed": "Bạn có muốn gắn thẻ gương mặt này?", + "confirm_tag_face": "Bạn có muốn gắn thẻ khuôn mặt này là của {name}?", + "confirm_tag_face_unnamed": "Bạn có muốn gắn thẻ khuôn mặt này?", "connected_device": "Thiết bị được kết nối", "connected_to": "Đã kết nối tới", "contain": "Vừa màn hình", @@ -833,17 +861,17 @@ "control_bottom_app_bar_edit_time": "Chỉnh sửa Ngày và Giờ", "control_bottom_app_bar_share_link": "Chia sẻ liên kết", "control_bottom_app_bar_share_to": "Chia sẻ với", - "control_bottom_app_bar_trash_from_immich": "Di chuyển đến Thùng rác", - "copied_image_to_clipboard": "Đã sao chép ảnh vào bộ nhớ tạm.", - "copied_to_clipboard": "Đã sao chép vào bộ nhớ tạm!", + "control_bottom_app_bar_trash_from_immich": "Di chuyển vào Thùng rác", + "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", "copy_file_path": "Sao chép đường dẫn tệp", "copy_image": "Sao chép ảnh", "copy_json": "Sao chép JSON", - "copy_link": "Sao chép liên kết", - "copy_link_to_clipboard": "Sao chép liên kết vào bộ nhớ tạm", + "copy_link": "Sao chép link", + "copy_link_to_clipboard": "Sao chép liên kết vào clipboard", "copy_password": "Sao chép mật khẩu", - "copy_to_clipboard": "Sao chép vào bộ nhớ tạm", + "copy_to_clipboard": "Sao chép vào clipboard", "country": "Quốc gia", "cover": "Tối đa", "covers": "Lưới", @@ -853,19 +881,19 @@ "create_api_key": "Tạo khóa API", "create_first_workflow": "Tạo workflow đầu tiên", "create_library": "Tạo thư viện", - "create_link": "Tạo liên kết", + "create_link": "Tạo link", "create_link_to_share": "Tạo liên kết để chia sẻ", - "create_link_to_share_description": "Ai có liên kết sẽ xem được các ảnh đã chọn", + "create_link_to_share_description": "Ai có link sẽ xem được các ảnh đã chọn", "create_new": "TẠO MỚI", "create_new_face": "Tạo khuôn mặt mới", "create_new_person": "Tạo người mới", - "create_new_person_hint": "Gán các ảnh đã chọn cho một người mới", + "create_new_person_hint": "Gán các tài nguyên đã chọn cho một người mới", "create_new_user": "Tạo người dùng mới", "create_person": "Tạo người", "create_person_subtitle": "Thêm tên vào khuôn mặt đã chọn để tạo và gắn thẻ người mới", - "create_shared_album_page_share_add_assets": "THÊM TỆP", + "create_shared_album_page_share_add_assets": "THÊM TÀI NGUYÊN", "create_shared_album_page_share_select_photos": "Chọn ảnh", - "create_shared_link": "Chia sẻ qua liên kết", + "create_shared_link": "Tạo link chia sẻ", "create_tag": "Tạo thẻ", "create_tag_description": "Tạo thẻ mới. Với các thẻ lồng nhau, vui lòng nhập đường dẫn đầy đủ của thẻ bao gồm dấu gạch chéo.", "create_user": "Tạo người dùng", @@ -876,7 +904,8 @@ "crop": "Cắt", "crop_aspect_ratio_fixed": "Cố định", "crop_aspect_ratio_free": "Tự do", - "crop_aspect_ratio_original": "Nguyên bản", + "crop_aspect_ratio_original": "Gốc", + "crop_aspect_ratio_square": "Vuông", "curated_object_page_title": "Đối tượng", "current_device": "Thiết bị hiện tại", "current_pin_code": "Mã PIN hiện tại", @@ -894,13 +923,19 @@ "date_after": "Ngày sau", "date_and_time": "Ngày và giờ", "date_before": "Ngày trước", + "date_of_birth": "Ngày sinh", "date_of_birth_saved": "Sinh nhật đã được lưu thành công", "date_range": "Khoảng thời gian", + "date_time_original": "Ngày/Giờ gốc", "day": "Ngày", "days": "Ngày", "deduplicate_all": "Xóa tất cả mục trùng lặp", + "default_locale": "Ngôn ngữ mặc định", + "default_locale_description": "Định dạng ngày tháng và số theo ngôn ngữ/vị trí trình duyệt của bạn", + "default_quality_subtitle": "Chất lượng khi nhấn chia sẻ. Nhấn giữ nút chia sẻ để chọn riêng từng lần.", + "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ệp này? Thao tác này sẽ chuyển tệp vào thùng rác của máy chủ và sẽ hỏi bạn có muốn xóa nó cục bộ không", + "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_album": "Xóa album", "delete_api_key_prompt": "Bạn có chắc muốn xóa khóa API này?", @@ -914,20 +949,20 @@ "delete_face": "Xóa khuôn mặt", "delete_key": "Xóa khóa", "delete_library": "Xóa Thư viện", - "delete_link": "Xóa liên kết", + "delete_link": "Xóa link", "delete_local_action_prompt": "{count} đã xóa 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_shared_link": "Xóa liên kết chia sẻ", - "delete_shared_link_dialog_title": "Xóa liên kết đã chia sẻ", + "delete_shared_link": "Xóa link đã chia sẻ", + "delete_shared_link_dialog_title": "Xóa link đã chia sẻ", "delete_tag": "Xóa thẻ", "delete_tag_confirmation_prompt": "Bạn có chắc muốn xóa thẻ {tagName}?", "delete_user": "Xóa người dùng", - "deleted_shared_link": "Đã xóa liên kết chia sẻ", - "deletes_missing_assets": "Xóa các ảnh không còn tồn tại trên ổ đĩa", + "deleted_shared_link": "Đã xóa link chia sẻ", + "deletes_missing_assets": "Xóa các tài nguyên không còn tồn tại trên ổ đĩa", "description": "Mô tả", "description_input_hint_text": "Thêm mô tả...", "description_input_submit_error": "Cập nhật mô tả không thành công, vui lòng kiểm tra log để biết thêm chi tiết", @@ -950,7 +985,7 @@ "documentation": "Tài liệu", "done": "Xong", "download": "Tải xuống", - "download_action_prompt": "Đang tải {count} tệp", + "download_action_prompt": "Đang tải {count} tài nguyên", "download_canceled": "Đã hủy tải xuống", "download_complete": "Tải xuống hoàn tất", "download_enqueue": "Tải xuống đang chờ", @@ -958,23 +993,26 @@ "download_failed": "Tải xuống thất bại", "download_finished": "Tải xuống hoàn tất", "download_include_embedded_motion_videos": "Video nhúng", - "download_include_embedded_motion_videos_description": "Gồm các video được nhúng trong ảnh chuyển động thành một tệp riêng", + "download_include_embedded_motion_videos_description": "Tách các video được nhúng trong ảnh chuyển động thành một tệp riêng", "download_notfound": "Không tìm thấy tải xuống", "download_original": "Tải xuống bản gốc", "download_paused": "Đã tạm dừng tải xuống", "download_settings": "Tải xuống", - "download_settings_description": "Quản lý cài đặt liên quan đến việc tải ảnh xuống", + "download_settings_description": "Quản lý cài đặt liên quan đến việc tải xuống tài nguyên", "download_started": "Đã bắt đầu tải xuống", "download_sucess": "Tải xuống thành công", "download_sucess_android": "Phương tiện đã được lưu vào DCIM/Immich", "download_waiting_to_retry": "Đang chờ thử lại", "downloading": "Đang tải xuống", - "downloading_asset_filename": "Đang tải xuống tệp {filename}", + "downloading_asset_filename": "Đang tải xuống tài nguyên {filename}", "downloading_from_icloud": "Đang tải xuống từ iCloud", "downloading_media": "Đang tải xuống phương tiện", + "drag_to_reorder": "Kéo để sắp xếp lại", "drop_files_to_upload": "Kéo thả các tệp để tải lên", + "duplicate": "Nhân bản", + "duplicate_workflow": "Nhân bản workflow", "duplicates": "Tệp trùng lặp", - "duplicates_description": "Xem lại các nhóm ảnh bị nghi ngờ trùng lặp và chọn những mục bạn muốn giữ hoặc xóa", + "duplicates_description": "Giải quyết từng nhóm bằng cách chỉ ra nhóm nào, nếu có, là nhóm trùng lặp.", "duration": "Thời gian", "edit": "Chỉnh sửa", "edit_album": "Chỉnh sửa album", @@ -990,7 +1028,7 @@ "edit_exclusion_pattern": "Chỉnh sửa quy tắc loại trừ", "edit_faces": "Chỉnh sửa khuôn mặt", "edit_key": "Chỉnh sửa khóa", - "edit_link": "Chỉnh sửa liên kết", + "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_dialog_title": "Vị trí", @@ -1005,21 +1043,23 @@ "editor_close_without_save_title": "Đóng trình chỉnh sửa?", "editor_confirm_reset_all_changes": "Bạn có chắc muốn đặt lại mọi thay đổi không?", "editor_discard_edits_confirm": "Bỏ thay đổi", - "editor_discard_edits_prompt": "Bạn có những thay đổi chưa được lưu. Bạn có chắc chắn muốn hủy bỏ chúng không?", + "editor_discard_edits_prompt": "Bạn có những sửa đổi chưa được lưu. Bạn có chắc muốn hủy bỏ chúng không?", "editor_discard_edits_title": "Hủy thay đổi?", "editor_edits_applied_error": "Không thể áp dụng chỉnh sửa", "editor_edits_applied_success": "Chỉnh sửa được áp dụng thành công", "editor_flip_horizontal": "Lật ngang", "editor_flip_vertical": "Lật dọc", - "editor_orientation": "Định hướng", - "editor_reset_all_changes": "Hoàn tác tất cả thay đổi", + "editor_handle_corner": "{corner, select, top_left {Góc trên bên trái} top_right {Góc trên bên phải} bottom_left {Góc dưới bên trái} bottom_right {Góc dưới bên phải} other {A}} góc", + "editor_handle_edge": "{edge, select, top {Trên} bottom {Dưới} left {Trái} right {Phải} other {An}} cạnh", + "editor_orientation": "Chiều", + "editor_reset_all_changes": "Hủy bỏ mọi thay đổi", "editor_rotate_left": "Xoay 90° ngược chiều kim đồng hồ", "editor_rotate_right": "Xoay 90° theo chiều kim đồng hồ", "email": "Email", "email_notifications": "Thông báo qua email", "empty_folder": "Thư mục trống", "empty_trash": "Dọn sạch thùng rác", - "empty_trash_confirmation": "Bạn có chắc muốn dọn sạch thùng rác? Điều này sẽ xóa vĩnh viễn tất cả các tệp trong thùng rác khỏi Immich.\nBạn không thể hoàn tác hành động này!", + "empty_trash_confirmation": "Bạn có chắc muốn dọn sạch thùng rác? Điều này sẽ xóa vĩnh viễn tất cả các tài nguyên trong thùng rác khỏi Immich.\nBạn không thể hoàn tác hành động này!", "enable": "Bật", "enable_backup": "Bật sao lưu", "enable_biometric_auth_description": "Nhập mã PIN của bạn để bật xác thực sinh trắc học", @@ -1031,48 +1071,49 @@ "enter_your_pin_code_subtitle": "Nhập mã PIN của bạn để truy cập thư mục Khóa", "error": "Lỗi", "error_change_sort_album": "Thay đổi thứ tự sắp xếp album thất bại", - "error_delete_face": "Lỗi khi xóa khuôn mặt khỏi tệp", + "error_delete_face": "Lỗi khi xóa khuôn mặt khỏi tài nguyên", "error_getting_places": "Lỗi khi lấy địa điểm", - "error_loading_albums": "Lỗi khi tải các tập ảnh (album)", + "error_loading_albums": "Xảy ra lỗi khi tải các album", "error_loading_image": "Lỗi tải ảnh", - "error_loading_partners": "Lỗi khi lấy người thân: {error}", - "error_retrieving_asset_information": "Không thể truy xuất thông tin tệp", + "error_loading_partners": "Lỗi khi tải người thân: {error}", + "error_retrieving_asset_information": "Không thể truy xuất thông tin tài nguyên", "error_saving_image": "Lỗi: {error}", "error_tag_face_bounding_box": "Lỗi gắn thẻ khuôn mặt: - không thể lấy được tọa độ khung bao", "error_title": "Lỗi - Có điều gì đó không đúng", - "error_while_navigating": "Không thể điều hướng đến tệp", + "error_while_navigating": "Không thể điều hướng đến tài nguyên", "errors": { - "cannot_navigate_next_asset": "Không thể chuyển đến tệp tiếp theo", - "cannot_navigate_previous_asset": "Không thể chuyển đến tệp trước đó", + "cannot_navigate_next_asset": "Không thể chuyển đến tài nguyên tiếp theo", + "cannot_navigate_previous_asset": "Không thể chuyển đến tài nguyên trước đó", "cant_apply_changes": "Không thể áp dụng thay đổi", "cant_change_activity": "Không thể {enabled, select, true {disable} other {enable}} hoạt động", - "cant_change_asset_favorite": "Không thể thay đổi việc thích tệp", - "cant_change_metadata_assets_count": "Không thể thay đổi siêu dữ liệu của {count, plural, one {# tệp} other {# tệp}}", + "cant_change_asset_favorite": "Không thể thay đổi việc thích tài nguyên", + "cant_change_metadata_assets_count": "Không thể thay đổi siêu dữ liệu của {count, plural, one {# tài nguyên} other {# tài nguyên}}", "cant_get_faces": "Không thể tải khuôn mặt", "cant_get_number_of_comments": "Không thể tải số lượng bình luận", "cant_search_people": "Không thể tìm người", "cant_search_places": "Không thể tìm kiếm địa điểm", - "error_adding_assets_to_album": "Lỗi khi thêm tệp vào album", + "error_adding_assets_to_album": "Lỗi khi thêm tài nguyên vào album", "error_adding_users_to_album": "Lỗi khi thêm người dùng vào album", "error_deleting_shared_user": "Lỗi khi xóa người dùng chia sẻ", "error_downloading": "Lỗi khi tải xuống {filename}", "error_hiding_buy_button": "Lỗi khi ẩn nút mua", - "error_removing_assets_from_album": "Lỗi khi xóa tệp khỏi album, kiểm tra bảng điều khiển để biết thêm chi tiết", - "error_selecting_all_assets": "Lỗi khi chọn tất cả tệp", + "error_removing_assets_from_album": "Lỗi khi xóa tài nguyên khỏi album, kiểm tra bảng điều khiển để biết thêm chi tiết", + "error_selecting_all_assets": "Lỗi khi chọn tất cả tài nguyên", "exclusion_pattern_already_exists": "Quy tắc loại trừ này đã tồn tại.", "failed_to_create_album": "Tạo album thất bại", - "failed_to_create_shared_link": "Không thể tạo liên kết chia sẻ", - "failed_to_edit_shared_link": "Không thể chỉnh sửa liên kết chia sẻ", + "failed_to_create_shared_link": "Không thể tạo link chia sẻ", + "failed_to_edit_shared_link": "Không thể chỉnh sửa link chia sẻ", "failed_to_get_people": "Không thể tải người", - "failed_to_keep_this_delete_others": "Xảy ra lỗi trong quá trình xóa tệp", - "failed_to_load_asset": "Không thể tải tệp", - "failed_to_load_assets": "Không thể tải các tệp", + "failed_to_keep_this_delete_others": "Xảy ra lỗi trong quá trình xóa tài nguyên", + "failed_to_load_asset": "Không thể tải tài nguyên", + "failed_to_load_assets": "Không thể tải các tài nguyên", "failed_to_load_notifications": "Không thể tải các thông báo", "failed_to_load_people": "Không thể tải người", "failed_to_remove_product_key": "Không thể xóa khóa sản phẩm", "failed_to_reset_pin_code": "Đặt lại mã PIN không thành công", - "failed_to_stack_assets": "Không thể xếp nhóm tệp", - "failed_to_unstack_assets": "Không thể hủy xếp nhóm tệp", + "failed_to_stack_assets": "Không thể xếp nhóm tài nguyên", + "failed_to_tag_assets": "Không thẻ gắn thẻ tài nguyên", + "failed_to_unstack_assets": "Không thể hủy xếp nhóm tài nguyên", "failed_to_update_notification_status": "Cập nhật trạng thái thông báo thất bại", "incorrect_email_or_password": "Email hoặc mật khẩu không chính xác", "library_folder_already_exists": "Đường dẫn nhập này đã tồn tại.", @@ -1082,33 +1123,33 @@ "quota_higher_than_disk_size": "Bạn đã đặt hạn mức cao hơn dung lượng ổ đĩa", "something_went_wrong": "Có gì đó không đúng", "unable_to_add_album_users": "Không thể thêm người dùng vào album", - "unable_to_add_assets_to_shared_link": "Không thể thêm tệp vào liên kết chia sẻ", + "unable_to_add_assets_to_shared_link": "Không thể thêm tài nguyên vào liên kết chia sẻ", "unable_to_add_comment": "Không thể thêm bình luận", "unable_to_add_exclusion_pattern": "Không thể thêm quy tắc loại trừ", "unable_to_add_partners": "Không thể thêm người thân", - "unable_to_add_remove_archive": "Không thể {archived, select, true {xóa tệp khỏi} other {thêm tệp vào}} Kho lưu trữ", - "unable_to_add_remove_favorites": "Không thể {favorite, select, true {thêm tệp vào} other {xóa tệp khỏi}} Mục yêu thích", + "unable_to_add_remove_archive": "Không thể {archived, select, true {xóa tài nguyên khỏi} other {thêm tài nguyên vào}} Kho lưu trữ", + "unable_to_add_remove_favorites": "Không thể {favorite, select, true {thêm tài nguyên vào} other {xóa tài nguyên khỏi}} mục Yêu thích", "unable_to_archive_unarchive": "Không thể {archived, select, true {lưu trữ} other {bỏ lưu trữ}}", "unable_to_change_album_user_role": "Không thể thay đổi vai trò của người dùng album", "unable_to_change_date": "Không thể thay đổi ngày", "unable_to_change_description": "Không thể thay đổi mô tả", - "unable_to_change_favorite": "Không thể thay đổi việc thích tệp", + "unable_to_change_favorite": "Không thể thay đổi việc thích tài nguyên", "unable_to_change_location": "Không thể thay đổi vị trí", "unable_to_change_password": "Không thể thay đổi mật khẩu", "unable_to_change_visibility": "Không thể thay đổi trạng thái hiển thị cho {count, plural, one {# người} other {# người}}", "unable_to_complete_oauth_login": "Không thể hoàn tất đăng nhập OAuth", "unable_to_connect": "Không thể kết nối", - "unable_to_copy_to_clipboard": "Không thể sao chép vào bộ nhớ tạm, hãy đảm bảo bạn đang truy cập trang qua https", + "unable_to_copy_to_clipboard": "Không thể sao chép vào clipboard, hãy đảm bảo bạn đang truy cập trang qua https", "unable_to_create": "Không thể tạo workflow", "unable_to_create_admin_account": "Không thể tạo tài khoản quản trị viên", "unable_to_create_api_key": "Không thể tạo khóa API mới", "unable_to_create_library": "Không thể tạo thư viện", "unable_to_create_user": "Không thể tạo người dùng", "unable_to_delete_album": "Không thể xóa album", - "unable_to_delete_asset": "Không thể xóa tệp", - "unable_to_delete_assets": "Lỗi khi xóa các tệp", + "unable_to_delete_asset": "Không thể xóa tài nguyên", + "unable_to_delete_assets": "Lỗi khi xóa các tài nguyên", "unable_to_delete_exclusion_pattern": "Không thể xóa quy tắc loại trừ", - "unable_to_delete_shared_link": "Không thể xóa liên kết chia sẻ", + "unable_to_delete_shared_link": "Không thể xóa link chia sẻ", "unable_to_delete_user": "Không thể xóa người dùng", "unable_to_delete_workflow": "Không thể xóa workflow", "unable_to_download_files": "Không thể tải xuống tệp", @@ -1117,7 +1158,7 @@ "unable_to_enter_fullscreen": "Không thể vào chế độ toàn màn hình", "unable_to_exit_fullscreen": "Không thể thoát chế độ toàn màn hình", "unable_to_get_comments_number": "Không thể lấy số lượng bình luận", - "unable_to_get_shared_link": "Không thể lấy liên kết chia sẻ", + "unable_to_get_shared_link": "Không thể lấy link chia sẻ", "unable_to_hide_person": "Không thể ẩn người", "unable_to_link_motion_video": "Không thể liên kết video chuyển động", "unable_to_link_oauth_account": "Không thể liên kết tài khoản OAuth", @@ -1125,19 +1166,19 @@ "unable_to_log_out_device": "Không thể đăng xuất khỏi thiết bị", "unable_to_login_with_oauth": "Không thể đăng nhập với OAuth", "unable_to_play_video": "Không thể phát video", - "unable_to_reassign_assets_existing_person": "Không thể gán lại tệp cho {name, select, null {một người hiện có} other {{name}}}", - "unable_to_reassign_assets_new_person": "Không thể gán lại ảnh cho một người mới", + "unable_to_reassign_assets_existing_person": "Không thể gán lại tài nguyên cho {name, select, null {một người hiện có} other {{name}}}", + "unable_to_reassign_assets_new_person": "Không thể gán lại tài nguyên cho một người mới", "unable_to_refresh_user": "Không thể làm mới người dùng", "unable_to_remove_album_users": "Không thể xóa người dùng khỏi album", "unable_to_remove_api_key": "Không thể xóa khóa API", - "unable_to_remove_assets_from_shared_link": "Không thể xóa các mục đã chọn khỏi liên kết chia sẻ", + "unable_to_remove_assets_from_shared_link": "Không thể xóa các tài nguyên khỏi link đã chia sẻ", "unable_to_remove_library": "Không thể xóa thư viện", "unable_to_remove_partner": "Không thể xóa người thân", "unable_to_remove_reaction": "Không thể xóa phản ứng", "unable_to_reset_password": "Không thể đặt lại mật khẩu", "unable_to_reset_pin_code": "Không thể đặt lại mã PIN", "unable_to_resolve_duplicate": "Không thể xử lý trùng lặp", - "unable_to_restore_assets": "Không thể khôi phục tệp", + "unable_to_restore_assets": "Không thể khôi phục tài nguyên", "unable_to_restore_trash": "Không thể khôi phục thùng rác", "unable_to_restore_user": "Không thể khôi phục người dùng", "unable_to_save_album": "Không thể lưu album", @@ -1150,9 +1191,9 @@ "unable_to_scan_library": "Không thể quét thư viện", "unable_to_set_feature_photo": "Không thể đặt ảnh nổi bật", "unable_to_set_profile_picture": "Không thể đặt ảnh đại diện", - "unable_to_set_rating": "Không thể đặt đánh giá", + "unable_to_set_rating": "Không thể đặt xếp hạng", "unable_to_submit_job": "Không thể gửi tác vụ", - "unable_to_trash_asset": "Không thể chuyển ảnh vào thùng rác", + "unable_to_trash_asset": "Không thể chuyển tài nguyên vào thùng rác", "unable_to_unlink_account": "Không thể hủy liên kết tài khoản", "unable_to_unlink_motion_video": "Không thể hủy liên kết video chuyển động", "unable_to_update_album_cover": "Không thể cập nhật ảnh bìa album", @@ -1184,40 +1225,43 @@ "experimental_settings_title": "Thử nghiệm", "expire_after": "Hết hạn sau", "expired": "Hết hạn", - "expires_date": "Hết hạn vào {date}", + "expires_date": "Hết hạn {date}", "explore": "Khám phá", "explorer": "Khám phá", "export": "Xuất", "export_as_json": "Xuất dưới dạng JSON", "export_database": "Xuất cơ sở dữ liệu", "export_database_description": "Xuất cơ sở dữ liệu SQLite", + "exposure_time": "Phơi sáng", "extension": "Phần mở rộng", "external": "Bên ngoài", - "external_libraries": "Thư viện bên ngoài", + "external_libraries": "Thư viện ngoài", "external_network": "Mạng bên ngoài", "external_network_sheet_info": "Khi không ở trên mạng Wi-Fi ưu tiên, app sẽ kết nối với máy chủ thông qua URL đầu tiên bên dưới mà nó có thể truy cập, bắt đầu từ trên xuống dưới", + "f_number": "Khẩu độ", "face_unassigned": "Chưa được gán", "failed": "Thất bại", "failed_count": "Thất bại: {count}", "failed_to_authenticate": "Xác thực thất bại", - "failed_to_load_assets": "Không tải được tệp", + "failed_to_delete_file": "Không thể xóa tệp", + "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 Đã thích", + "favorite_action_prompt": "{count} đã thêm vào Yêu thích", "favorite_or_unfavorite_photo": "Thích hoặc bỏ thích ảnh", - "favorites": "Đã thích", - "favorites_page_no_favorites": "Không tìm thấy tệp yêu thích", + "favorites": "Yêu thích", + "favorites_page_no_favorites": "Không tìm thấy tài nguyên yêu thích", "feature_photo_updated": "Đã cập nhật ảnh nổi bật", "features": "Tính năng", "features_in_development": "Tính năng đang được phát triển", "features_setting_description": "Quản lý các tính năng ứng dụng", - "file_name_or_extension": "Tên hoặc phần mở rộng tập tin", + "file_name_or_extension": "Tên hoặc phần mở rộng tệp", "file_name_text": "Tên tệp", "file_size": "Kích cỡ tệp tin", "filename": "Tên tệp", "filetype": "Loại tệp", "filter": "Bộ lọc", - "filter_description": "Điều kiện để lọc tệp mục tiêu", + "filter_description": "Điều kiện để lọc tài nguyên mục tiêu", "filter_people": "Lọc người", "filter_places": "Lọc địa điểm", "filter_tags": "Bộ lọc theo thẻ", @@ -1225,20 +1269,22 @@ "find_them_fast": "Tìm nhanh bằng tên với tìm kiếm", "first": "Đầu tiên", "fix_incorrect_match": "Sửa lỗi trùng khớp không chính xác", + "focal_length": "Tiêu cự", "folder": "Thư mục", "folder_not_found": "Không tìm thấy thư mục", "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", - "free_up_space": "Giải phóng Bộ nhớ", + "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": "Giải phóng bộ nhớ của thiết bị", + "free_up_space_settings_subtitle": "Tiết kiệm dung lượng 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", - "gcast_enabled_description": "Tính năng này tải các tài nguyên bên ngoài từ Google để hoạt động.", + "gcast_enabled_description": "Tính năng này tải các tài nguyên từ Google để hoạt động.", "general": "Chung", - "geolocation_instruction_location": "Nhấn vào một tệp có tọa độ GPS để sử dụng vị trí của nó hoặc chọn vị trí trực tiếp từ bản đồ", + "geolocation_instruction_location": "Nhấn vào một tài nguyên có tọa độ GPS để sử dụng vị trí của nó hoặc chọn vị trí trực tiếp từ bản đồ", "get_help": "Nhận trợ giúp", "get_people_error": "Lỗi khi lấy thông tin người", "get_wifiname_error": "Không thể lấy tên Wi-Fi. Hãy đảm bảo bạn đã cấp các quyền cần thiết và được kết nối với mạng Wi-Fi", @@ -1256,10 +1302,10 @@ "group_places_by": "Nhóm địa điểm theo…", "group_year": "Xếp nhóm theo năm", "haptic_feedback_switch": "Bật phản hồi haptic", - "haptic_feedback_title": "Phản hồi Hapic", + "haptic_feedback_title": "Phản hồi Haptic", "has_quota": "Hạn mức", - "hash_asset": "Mã hóa tệp", - "hashed_assets": "Tệp mã hóa", + "hash_asset": "Mã hóa tài nguyên", + "hashed_assets": "Tài nguyên đã mã hóa", "hashing": "Đang băm", "header_settings_add_header_tip": "Thêm header", "header_settings_field_validator_msg": "Không được để trống", @@ -1276,22 +1322,22 @@ "hide_schema": "Ẩn lược đồ", "hide_text_recognition": "Ẩn nhận dạng văn bản", "hide_unnamed_people": "Ẩn những người không tên", - "home_page_add_to_album_conflicts": "Đã thêm {added} tệp vào album {album}. {failed} tệp đã có sẵn trong album.", - "home_page_add_to_album_err_local": "Không thể thêm tệp trên thiết bị vào album, bỏ qua", - "home_page_add_to_album_success": "Đã thêm {added} tệp vào album {album}.", - "home_page_album_err_partner": "Không thể thêm tệp của nguời thân vào album, bỏ qua", - "home_page_archive_err_local": "Không thể lưu trữ tệp trên thiết bị, bỏ qua", - "home_page_archive_err_partner": "Không thể lưu trữ tệp của người thân, bỏ qua", + "home_page_add_to_album_conflicts": "Đã thêm {added} tài nguyên vào album {album}. {failed} tài nguyên đã có sẵn trong album.", + "home_page_add_to_album_err_local": "Không thể thêm tài nguyên trên thiết bị vào album, bỏ qua", + "home_page_add_to_album_success": "Đã thêm {added} tài nguyên vào album {album}.", + "home_page_album_err_partner": "Không thể thêm tài nguyên của nguời thân vào album, bỏ qua", + "home_page_archive_err_local": "Không thể lưu trữ tài nguyên trên thiết bị, bỏ qua", + "home_page_archive_err_partner": "Không thể lưu trữ tài nguyên của người thân, bỏ qua", "home_page_building_timeline": "Đang tạo dòng thời gian ảnh", - "home_page_delete_err_partner": "Không thể xóa tệp của người thân, bỏ qua", - "home_page_delete_remote_err_local": "Tệp trên thiết bị trong lựa chọn xóa từ xa, bỏ qua", - "home_page_favorite_err_local": "Không thể thích tệp trên thiết bị, bỏ qua", - "home_page_favorite_err_partner": "Không thể thích tệp của người thân, bỏ qua", + "home_page_delete_err_partner": "Không thể xóa tài nguyên của người thân, bỏ qua", + "home_page_delete_remote_err_local": "Tài nguyên trên thiết bị trong lựa chọn xóa từ xa, bỏ qua", + "home_page_favorite_err_local": "Không thể thích tài nguyên trên thiết bị, bỏ qua", + "home_page_favorite_err_partner": "Không thể thích tài nguyên của người thân, bỏ qua", "home_page_first_time_notice": "Nếu đây là lần đầu bạn dùng ứng dụng, hãy chọn một album sao lưu để dòng thời gian có thể hiển thị ảnh và video của bạn", - "home_page_locked_error_local": "Không thể di chuyển tệp trên thiết bị đến thư mục Khóa, bỏ qua", - "home_page_locked_error_partner": "Không thể di chuyển tệp của người thân đến thư mục Khóa, bỏ qua", - "home_page_share_err_local": "Không thể chia sẻ tệp trên thiết bị qua liên kết, bỏ qua", - "home_page_upload_err_limit": "Chỉ có thể tải lên tối đa 30 tệp cùng lúc, bỏ qua", + "home_page_locked_error_local": "Không thể di chuyển tài nguyên trên thiết bị đến thư mục Khóa, bỏ qua", + "home_page_locked_error_partner": "Không thể di chuyển tài nguyên của người thân đến thư mục Khóa, bỏ qua", + "home_page_share_err_local": "Không thể chia sẻ tài nguyên trên thiết bị bằng link, bỏ qua", + "home_page_upload_err_limit": "Chỉ có thể tải lên tối đa 30 tài nguyên cùng lúc, bỏ qua", "host": "Host", "hour": "Giờ", "hours": "Giờ", @@ -1324,10 +1370,11 @@ "in_year_selector": "Năm", "include_archived": "Bao gồm ảnh lưu trữ", "include_shared_albums": "Bao gồm album chia sẻ", - "include_shared_partner_assets": "Bao gồm ảnh người thân chia sẻ", + "include_shared_partner_assets": "Bao gồm tài nguyên người thân chia sẻ", "individual_share": "Chia sẻ riêng lẻ", "individual_shares": "Chia sẻ cá nhân", "info": "Thông tin", + "integrity_checks": "Kiểm tra tính toàn vẹn", "interval": { "day_at_onepm": "Mỗi ngày vào lúc 1 giờ chiều", "hours": "Mỗi {hours, plural, one {giờ} other {{hours, number} giờ}}", @@ -1344,21 +1391,22 @@ "ios_debug_info_no_sync_yet": "Chưa có tác vụ đồng bộ nền chạy", "ios_debug_info_processes_queued": "{count, plural, one {{count} tiến trình nền đã được đưa vào hàng đợi} other {{count} tiến trình nền đã được đưa vào hàng đợi}}", "ios_debug_info_processing_ran_at": "Quá trình xử lý đã chạy vào {dateTime}", + "iso": "ISO", "items_count": "{count, plural, one {# mục} other {# mục}}", "jobs": "Tác vụ", "json_editor": "Biên tập JSON", "json_error": "Lỗi JSON", "keep": "Giữ", "keep_albums": "Giữ lại các tập ảnh", - "keep_albums_count": "Giữ lại {count} {count, plural, one {tập ảnh} other {tập ảnh}}", + "keep_albums_count": "Giữ lại {count} {count, plural, one {album} other {album}}", "keep_all": "Giữ tất cả", - "keep_description": "Chọn những gì sẽ được giữ lại trên thiết bị khi giải phóng bộ nhớ.", + "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_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ệp này và xóa {count, plural, one {# tệp} other {# tệp}}", + "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}}", "keyboard_shortcuts": "Phím tắt", "language": "Ngôn ngữ", "language_no_results_subtitle": "Thử điều chỉnh quy tắc tìm kiếm", @@ -1374,6 +1422,7 @@ "leave": "Rời khỏi", "leave_album": "Rời khỏi album", "lens_model": "Lens", + "less": "Ít", "let_others_respond": "Cho phép bình luận", "level": "Mức độ", "library": "Thư viện", @@ -1382,23 +1431,28 @@ "library_options": "Tùy chọn thư viện", "library_page_device_albums": "Album trên thiết bị", "library_page_new_album": "Album mới", - "library_page_sort_asset_count": "Số lượng tệp", + "library_page_sort_asset_count": "Số lượng tài nguyên", "library_page_sort_created": "Mới tạo gần đây", "library_page_sort_last_modified": "Sửa đổi lần cuối", "library_page_sort_title": "Tên album", "licenses": "Giấy phép", "light": "Sáng", + "light_theme": "Chuyển sang chế độ sáng", "like": "Thích", "like_deleted": "Đã bỏ thích", + "link": "Link", "link_motion_video": "Liên kết video chuyển động", + "link_to_docs": "Để biết thêm thông tin, tham khảo tài liệu.", "link_to_oauth": "Liên kết đến OAuth", "linked_oauth_account": "Tài khoản OAuth đã liên kết", "list": "Danh sách", + "live": "Động", + "load_more": "Tải Thêm", "loading": "Đang tải", "loading_search_results_failed": "Tải kết quả tìm kiếm không thành công", "local": "Trên thiết bị", - "local_asset_cast_failed": "Không thể chiếu nội dung chưa được tải lên máy chủ", - "local_assets": "Tệp trên thiết bị", + "local_asset_cast_failed": "Không thể chiếu một tài nguyên chưa được tải lên máy chủ", + "local_assets": "Tài nguyên trên thiết bị", "local_id": "ID cục bộ", "local_media_summary": "Mô tả phương tiện trên thiết bị", "local_network": "Mạng nội bộ", @@ -1476,11 +1530,11 @@ "maintenance_title": "Tạm thời không khả dụng", "make": "Thương hiệu", "manage_geolocation": "Quản lý địa điểm", - "manage_media_access_rationale": "Để có thể di chuyển tệp vào thùng rác và khôi phục chúng từ đó.", + "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_shared_links": "Quản lý liên kết chia sẻ", + "manage_shared_links": "Quản lý những liên kết đã chia sẻ", "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", @@ -1492,11 +1546,11 @@ "map_cannot_get_user_location": "Không thể xác định vị trí của bạn", "map_location_dialog_yes": "Có", "map_location_picker_page_use_location": "Dùng vị trí này", - "map_location_service_disabled_content": "Cần bật dịch vụ định vị để hiển thị ảnh hoặc video từ vị trí hiện tại của bạn. Bạn có muốn bật nó ngay bây giờ không?", + "map_location_service_disabled_content": "Cần bật dịch vụ định vị để hiển thị tài nguyên từ vị trí hiện tại của bạn. Bạn có muốn bật nó ngay bây giờ không?", "map_location_service_disabled_title": "Dịch vụ vị trí bị vô hiệu hoá", - "map_marker_for_images": "Đánh dấu bản đồ cho ảnh chụp tại {city}, {country}", + "map_marker_for_image": "Đánh dấu điểm trên bản đồ cho hình ảnh được chụp tại {city}, {country}", "map_marker_with_image": "Đánh dấu bản đồ với ảnh", - "map_no_location_permission_content": "Cần quyền truy cập vị trí để hiển thị tệp từ vị trí hiện tại của bạn. Bạn có muốn cho phép ngay bây giờ không?", + "map_no_location_permission_content": "Cần quyền truy cập vị trí để hiển thị tài nguyên từ vị trí hiện tại của bạn. Bạn có muốn cho phép ngay bây giờ không?", "map_no_location_permission_title": "Ứng dụng không được phép truy cập vị trí", "map_settings": "Cài đặt bản đồ", "map_settings_dark_mode": "Chế độ tối", @@ -1507,14 +1561,46 @@ "map_settings_dialog_title": "Cài đặt bản đồ", "map_settings_include_show_archived": "Bao gồm ảnh đã lưu trữ", "map_settings_include_show_partners": "Bao gồm người thân", - "map_settings_only_show_favorites": "Chỉ hiển thị mục yêu thích", + "map_settings_only_show_favorites": "Chỉ hiển thị mục Yêu thích", "map_settings_theme_settings": "Giao diện bản đồ", "map_zoom_to_see_photos": "Thu nhỏ để xem ảnh", "mark_all_as_read": "Đánh dấu đã đọc tất cả", "mark_as_read": "Đánh dấu đã đọc", "marked_all_as_read": "Đã đánh dấu tất cả đã đọc", "matches": "Khớp", - "matching_assets": "Tệp trùng khớp", + "matching_assets": "Tài nguyên trùng khớp", + "media_chrome": { + "auto": "Tự động", + "captions": "Chú thích", + "captions_off": "Tắt", + "closed_captions": "đã đóng chú thích", + "decode_error": "Lỗi giải mã", + "disable_captions": "Tắt chú thích", + "enable_captions": "Bật chú thích", + "enter_fullscreen_mode": "Chế độ toàn màn hình", + "exit_fullscreen_mode": "Thoát chế độ toàn màn hình", + "loop": "Lặp lại", + "media_error_description": "Lỗi phương tiện đã khiến quá trình phát lại bị gián đoạn. Phương tiện có thể bị hỏng hoặc trình duyệt của bạn không hỗ trợ định dạng này.", + "media_loading": "đang tải phương tiện", + "mute": "Tắt tiếng", + "network_error": "Lỗi kết nối mạng", + "network_error_description": "Lỗi kết nối mạng đã khiến quá trình tải xuống phương tiện không thành công.", + "not_supported_error": "Nguồn không được hỗ trợ", + "playback_rate": "Tốc độ phát lại", + "playback_rate_current": "tốc độ phát lại hiện tại", + "playback_rate_value": "Tốc độ phát lại {playbackRate}", + "playback_time": "thời gian phát lại", + "quality": "Chất lượng", + "second": "giây", + "seconds": "giây", + "time_value_of_total_time": "{currentTime} trong {totalTime}", + "time_value_remaining": "{time} còn lại", + "unmute": "Bật tiếng", + "unsupported_error_description": "Đã xảy ra lỗi không được hỗ trợ. Máy chủ hoặc mạng gặp sự cố, hoặc trình duyệt của bạn không hỗ trợ định dạng này.", + "video_not_loaded_unknown_time": "video không tải được, thời gian không xác định.", + "video_player": "trình phát video", + "volume": "âm lượng" + }, "media_type": "Loại phương tiện", "memories": "Kỷ niệm", "memories_all_caught_up": "Bạn đã xem hết rồi", @@ -1531,6 +1617,8 @@ "merge_people_prompt": "Bạn có muốn hợp nhất những người này? Hành động này không thể hoàn tác.", "merge_people_successfully": "Hợp nhất người thành công", "merged_people_count": "Đã hợp nhất {count, plural, one {# người} other {# người}}", + "minFaces": "Số khuôn mặt tối thiểu", + "minFaces_description": "Số lượng khuôn mặt được nhận diện tối thiểu để một người được hiển thị", "minimize": "Thu nhỏ", "minute": "Phút", "minutes": "Phút", @@ -1540,24 +1628,28 @@ "mobile_app": "Ứng dụng di động", "mobile_app_download_onboarding_note": "Tải xuống app đồng hành bằng các tùy chọn sau", "model": "Dòng", + "modify_date": "Ngày sửa đổi", "month": "Tháng", "more": "Thêm", + "motion": "Hoạt ảnh", "move": "Di chuyển", "move_down": "Di chuyển xuống", "move_off_locked_folder": "Di chuyển ra khỏi thư mục Khóa", - "move_to": "Chuyển đến", - "move_to_device_trash": "Chuyển đến thùng rác của thiết bị", + "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_up": "Di chuyển lên", - "moved_to_archive": "Đã di chuyển {count, plural, one {# tệp} other {# tệp}} đến lưu trữ", - "moved_to_library": "Đã di chuyển {count, plural, one {# tệp} other {# tệp}} đến thư viện", + "moved_to_archive": "Đã di chuyển {count, plural, one {# tài nguyên} other {# tài nguyên}} đến lưu trữ", + "moved_to_library": "Đã di chuyển {count, plural, one {# tài nguyên} other {# tài nguyên}} đến thư viện", "moved_to_trash": "Đã chuyển vào thùng rác", - "multiselect_grid_edit_date_time_err_read_only": "Không thể chỉnh sửa ngày của tệp chỉ có quyền đọc, bỏ qua", - "multiselect_grid_edit_gps_err_read_only": "Không thể chỉnh sửa vị trí của tệp chỉ có quyền đọc, bỏ qua", + "multiselect_grid_edit_date_time_err_read_only": "Không thể chỉnh sửa ngày của tài nguyên chỉ có quyền đọc, bỏ qua", + "multiselect_grid_edit_gps_err_read_only": "Không thể chỉnh sửa vị trí của tài nguyên chỉ có quyền đọc, bỏ qua", "mute_memories": "Tắt tiếng Kỷ niệm", "my_albums": "Album của tôi", + "my_immich_description": "Sao chép trang hiện tại dưới dạng link My Immich", + "my_immich_title": "Liên kết My Immich", "name": "Tên", "name_or_nickname": "Tên hoặc biệt danh", "name_required": "Bắt buộc nhập tên", @@ -1567,7 +1659,7 @@ "network_requirement_videos_upload": "Dùng dữ liệu di động sao lưu video", "network_requirements": "Kết nối mạng", "network_requirements_updated": "Kết nối mạng đã thay đổi, đang đặt lại hàng đợi sao lưu", - "networking_settings": "Mạng", + "networking_settings": "Kết nối mạng", "networking_subtitle": "Các địa chỉ máy chủ", "never": "Không bao giờ", "new_album": "Album mới", @@ -1585,34 +1677,35 @@ "next": "Tiếp theo", "next_memory": "Kỷ niệm tiếp theo", "no": "Không", - "no_albums_found": "Không phát hiện tập ảnh nào", + "no_albums_found": "Không tìm thấy album nào", "no_albums_message": "Tạo album để sắp xếp ảnh và video của bạn", "no_albums_with_name_yet": "Có vẻ như bạn chưa có bất kỳ album nào với tên này.", "no_albums_yet": "Có vẻ như bạn chưa có bất kỳ album nào.", "no_archived_assets_message": "Lưu trữ ảnh và video để ẩn chúng khỏi thư viện Ảnh của bạn", - "no_assets_message": "Nhấn vào để tải lên ảnh của bạn lần đầu tiên", - "no_assets_to_show": "Không có gì để hiển thị", + "no_assets_message": "Nhấn vào đây để tải ảnh đầu tiên của bạn lên", + "no_assets_to_show": "Không có tài nguyên để hiển thị", "no_cast_devices_found": "Không tìm thấy thiết bị chiếu", - "no_checksum_local": "Không có checksum khả dụng - không thể truy xuất tệp trên thiết bị", - "no_checksum_remote": "Không có checksum khả dụng - không thể truy xuất tệp trên mây", + "no_checksum_local": "Không có checksum khả dụng - không thể truy xuất tài nguyên trên thiết bị", + "no_checksum_remote": "Không có checksum khả dụng - không thể truy xuất tài nguyên trên mây", "no_configuration_needed": "Không cần cấu hình", "no_devices": "Không có thiết bị được cấp quyền", "no_duplicates_found": "Không tìm thấy các mục trùng lặp.", "no_exif_info_available": "Không có thông tin exif", "no_explore_results_message": "Tải thêm ảnh lên để khám phá bộ sưu tập của bạn.", "no_favorites_message": "Thêm ảnh yêu thích để nhanh chóng tìm thấy những bức ảnh và video đẹp nhất của bạn", - "no_libraries_message": "Tạo một thư viện bên ngoài để xem ảnh và video của bạn", - "no_local_assets_found": "Không tìm thấy tệp trên thiết bị nào với checksum này", + "no_libraries_message": "Tạo một thư viện ngoài để xem ảnh và video của bạn", + "no_local_assets_found": "Không tìm thấy tài nguyên trên thiết bị nào với checksum này", "no_location_set": "Chưa có địa điểm được đặt", "no_locked_photos_message": "Ảnh và video trong thư mục Khóa sẽ được ẩn đi và không hiển thị khi bạn duyệt hay tìm kiếm trong thư viện.", "no_name": "Không có tên", "no_notifications": "Không có thông báo", "no_people_found": "Không có người nào khớp với tìm kiếm", "no_places": "Không có địa điểm", - "no_remote_assets_found": "Không tìm thấy tệp trên mây nào với checksum này", + "no_remote_assets_found": "Không tìm thấy tài nguyên trên mây nào với checksum này", "no_results": "Không có kết quả", "no_results_description": "Thử một từ đồng nghĩa hoặc từ khóa tổng quát hơn", "no_shared_albums_message": "Tạo một album để chia sẻ ảnh và video với mọi người trong mạng của bạn", + "no_steps": "Chưa có bước nào được thêm vào", "no_uploads_in_progress": "Không có tải lên nào đang tiến hành", "none": "Trống", "not_allowed": "Không cho phép", @@ -1621,6 +1714,7 @@ "not_selected": "Không được chọn", "notes": "Lưu ý", "nothing_here_yet": "Chưa có nội dung nào", + "notification_backup_reliability": "Bật thông báo để đảm bảo quá trình sao lưu nền", "notification_permission_dialog_content": "Để bật thông báo, chuyển tới Cài đặt và chọn cho phép.", "notification_permission_list_tile_content": "Cấp quyền để bật thông báo.", "notification_permission_list_tile_enable_button": "Bật thông báo", @@ -1648,7 +1742,7 @@ "online": "Trực tuyến", "only_favorites": "Chỉ lượt thích", "open": "Mở", - "open_calendar": "Hiện thị lịch", + "open_calendar": "Mở lịch", "open_in_browser": "Mở trong trình duyệt", "open_in_map_view": "Mở trong bản đồ", "open_in_openstreetmap": "Mở trong OpenStreetMap", @@ -1658,6 +1752,7 @@ "organize_into_albums": "Sắp xếp thành album", "organize_into_albums_description": "Đưa ảnh hiện có vào album bằng cách sử dụng cài đặt đồng bộ hiện tại", "organize_your_library": "Sắp xếp thư viện của bạn", + "orientation": "Hướng", "original": "gốc", "other": "Khác", "other_devices": "Thiết bị khác", @@ -1672,8 +1767,8 @@ "partner_can_access_location": "Vị trí nơi ảnh của bạn được chụp", "partner_list_user_photos": "Ảnh của {user}", "partner_list_view_all": "Xem tất cả", - "partner_page_empty_message": "Ảnh của bạn chưa được chia sẻ với bất kỳ ai.", - "partner_page_no_more_users": "Không còn người dùng nào để thêm", + "partner_page_empty_message": "Ảnh của bạn chưa được chia sẻ với người thân nào.", + "partner_page_no_more_users": "Không còn người thân nào để thêm", "partner_page_partner_add_failed": "Thêm người thân thất bại", "partner_page_select_partner": "Chọn người thân", "partner_page_shared_to_title": "Chia sẻ với", @@ -1697,16 +1792,16 @@ "pending": "Đang chờ xử lý", "people": "Mọi người", "people_edits_count": "Đã chỉnh sửa {count, plural, one {# người} other {# người}}", - "people_feature_description": "Duyệt ảnh và video được xếp nhóm theo người", + "people_feature_description": "Duyệt ảnh và video được phân loại theo người", "people_selected": "{count, plural, one {# người đã chọn} other {# người đã chọn}}", "people_sidebar_description": "Hiển thị mục Mọi người trong thanh bên", "permanent_deletion_warning": "Cảnh báo xóa vĩnh viễn", - "permanent_deletion_warning_setting_description": "Hiển thị cảnh báo khi xóa vĩnh viễn ảnh", + "permanent_deletion_warning_setting_description": "Hiển thị cảnh báo khi xóa vĩnh viễn tài nguyên", "permanently_delete": "Xóa vĩnh viễn", - "permanently_delete_assets_count": "Xóa vĩnh viễn {count, plural, one {tệp} other {tệp}}", - "permanently_delete_assets_prompt": "Bạn có chắc muốn xóa vĩnh viễn {count, plural, one {tệp này?} other {# tệp này?}} Điều này cũng sẽ xóa {count, plural, one {nó khỏi} other {chúng khỏi}} các album.", - "permanently_deleted_asset": "Tệp đã bị xóa vĩnh viễn", - "permanently_deleted_assets_count": "Đã xóa vĩnh viễn {count, plural, one {# tệp} other {# tệp}}", + "permanently_delete_assets_count": "Xóa vĩnh viễn {count, plural, one {tài nguyên} other {tài nguyên}}", + "permanently_delete_assets_prompt": "Bạn có chắc muốn xóa vĩnh viễn {count, plural, one {tài nguyên này?} other {#tài nguyên này?}} Điều này cũng sẽ xóa {count, plural, one {nó khỏi} other {chúng khỏi}} các album.", + "permanently_deleted_asset": "Tài nguyên đã bị xóa vĩnh viễn", + "permanently_deleted_assets_count": "Đã xóa vĩnh viễn {count, plural, one {# tài nguyên} other {# tài nguyên}}", "permission": "Quyền", "permission_empty": "Quyền của bạn không được để trống", "permission_onboarding_back": "Quay lại", @@ -1746,9 +1841,11 @@ "play_motion_photo": "Phát ảnh chuyển động", "play_or_pause_video": "Phát hoặc dừng video", "play_original_video": "Phát video gốc", - "play_original_video_setting_description": "Nên phát video gốc thay vì video đã chuyển mã. Nếu tệp gốc không tương thích, video có thể không phát được.", + "play_original_video_setting_description": "Nên phát video gốc thay vì video đã chuyển mã. Nếu tài nguyên gốc không tương thích, video có thể không phát được.", "play_transcoded_video": "Phát video đã chuyển mã", "please_auth_to_access": "Vui lòng xác thực để truy cập", + "plugin_method_filter_type": "Bộ lọc", + "plugin_method_filter_type_description": "Phương pháp này có thể lọc các sự kiện và ngăn chặn có điều kiện các bước tiếp theo không cho chạy", "port": "Cổng", "preferences_settings_subtitle": "Tùy chỉnh trải nghiệm ứng dụng", "preferences_settings_title": "Cá nhân hóa", @@ -1770,6 +1867,7 @@ "profile_drawer_readonly_mode": "Đã bật chế độ chỉ-xem. Nhấn giữ ảnh đại diện người dùng để tắt.", "profile_image_of_user": "Ảnh đại diện của {user}", "profile_picture_set": "Ảnh đại diện đã được đặt.", + "projection_type": "Loại chiếu", "public_album": "Album công khai", "public_share": "Chia sẻ công khai", "purchase_account_info": "Người hỗ trợ", @@ -1797,34 +1895,34 @@ "purchase_per_server": "Mỗi máy chủ", "purchase_per_user": "Mỗi người dùng", "purchase_remove_product_key": "Xóa khóa sản phẩm", - "purchase_remove_product_key_prompt": "Bạn có chắc muốn xoá khóa sản phẩm?", + "purchase_remove_product_key_prompt": "Bạn có chắc muốn xóa khóa sản phẩm?", "purchase_remove_server_product_key": "Xóa khóa sản phẩm máy chủ", - "purchase_remove_server_product_key_prompt": "Bạn có chắc muốn xoá khóa sản phẩm máy chủ?", + "purchase_remove_server_product_key_prompt": "Bạn có chắc muốn xóa khóa sản phẩm Máy chủ?", "purchase_server_description_1": "Dành cho toàn bộ máy chủ", "purchase_server_description_2": "Trạng thái người hỗ trợ", "purchase_server_title": "Máy chủ", "purchase_settings_server_activated": "Khóa sản phẩm máy chủ được quản lý bởi quản trị viên", - "query_asset_id": "Truy vấn ID tệp", + "query_asset_id": "ID tài nguyên truy vấn", "queue_status": "Xếp hàng {count}/{total}", - "rate_asset": "Asset Đánh giá", + "rate_asset": "Xếp hạng tài nguyên", "rating": "Xếp hạng sao", "rating_clear": "Xóa xếp hạng", "rating_count": "{count, plural, =0 {Chưa xếp hạng} one {# sao} other {# sao}}", "rating_description": "Hiển thị xếp hạng EXIF trong bảng thông tin", "reaction_options": "Tùy chọn phản ứng", - "read_changelog": "Đọc nhật ký thay đổi", + "read_changelog": "Đọc Ghi chú Phát hành", "readonly_mode_disabled": "Đã tắt chế độ chỉ-xem", "readonly_mode_enabled": "Đã bật chế độ chỉ-xem", "ready_for_upload": "Sẵn sàng tải lên", "reassign": "Gán lại", - "reassigned_assets_to_existing_person": "Đã gán lại {count, plural, one {# ảnh} other {# ảnh}} cho {name, select, null {một người hiện có} other {{name}}}", - "reassigned_assets_to_new_person": "Đã gán lại {count, plural, one {# ảnh} other {# ảnh}} cho một người mới", - "reassing_hint": "Gán các ảnh đã chọn cho một người hiện có", + "reassigned_assets_to_existing_person": "Đã gán lại {count, plural, one {# tài nguyên} other {# tài nguyên}} cho {name, select, null {một người hiện có} other {{name}}}", + "reassigned_assets_to_new_person": "Đã gán lại {count, plural, one {# tài nguyên} other {# tài nguyên}} cho một người mới", + "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_albums": "Album gần đây", "recent_searches": "Tìm kiếm gần đây", - "recently_added": "Thêm gần đây", - "recently_added_page_title": "Mới thêm gần đây", + "recently_added": "Được thêm gần đây", + "recently_added_page_title": "Được thêm gần đây", "recently_taken": "Chụp gần đây", "recently_taken_page_title": "Chụp Gần đây", "refresh": "Làm mới", @@ -1834,26 +1932,27 @@ "refresh_thumbnails": "Làm mới ảnh thu nhỏ", "refreshed": "Đã làm mới", "refreshes_every_file": "Đọc lại tất cả tệp mới và hiện có", - "refreshing_encoded_video": "Đang làm mới video đã mã hóa", - "refreshing_faces": "Đang làm mới khuôn mặt", - "refreshing_metadata": "Đang làm mới metadata", + "refreshing_encoded_video": "Đã làm mới video được chuyển mã", + "refreshing_faces": "Đã làm mới các khuôn mặt", + "refreshing_metadata": "Đã làm mới metadata", "regenerating_thumbnails": "Đang tạo lại ảnh thu nhỏ", "remote": "Trên mây", - "remote_assets": "Tệp trên mây", + "remote_assets": "Tài nguyên trên mây", "remote_media_summary": "Mô tả phương tiện trên máy chủ", "remove": "Xóa", - "remove_assets_album_confirmation": "Bạn có chắc muốn xóa {count, plural, one {# tệp} other {# tệp}} khỏi album?", - "remove_assets_shared_link_confirmation": "Bạn có chắc muốn xóa {count, plural, one {# tệp} other {# tệp}} khỏi liên kết chia sẻ này?", - "remove_assets_title": "Xóa tệp?", + "remove_assets_album_confirmation": "Bạn có chắc muốn xóa {count, plural, one {# tài nguyên} other {# tài nguyên}} khỏi album?", + "remove_assets_shared_link_confirmation": "Bạn có chắc muốn xóa {count, plural, one {# tài nguyên} other {# tài nguyên}} khỏi liên kết chia sẻ này?", + "remove_assets_title": "Xóa tài nguyên?", "remove_custom_date_range": "Bỏ chọn khoảng ngày tùy chỉnh", - "remove_deleted_assets": "Loại bỏ tệp ngoại tuyến", + "remove_deleted_assets": "Loại bỏ tài nguyên đã bị xóa", + "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_favorites": "Xóa khỏi Mục yêu thích", + "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_shared_link": "Xóa khỏi liên kết chia sẻ", + "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", "remove_tag": "Gỡ thẻ", @@ -1861,16 +1960,16 @@ "remove_user": "Xóa người dùng", "removed_api_key": "Khóa API đã xóa: {name}", "removed_from_archive": "Đã xóa khỏi Kho lưu trữ", - "removed_from_favorites": "Đã xóa khỏi Mục yêu thích", - "removed_from_favorites_count": "{count, plural, other {Đã xóa #}} khỏi Mục yêu thích", + "removed_from_favorites": "Đã xóa khỏi mục Yêu thích", + "removed_from_favorites_count": "{count, plural, other {Đã xóa #}} khỏi mục Yêu thích", "removed_memory": "Đã xóa kỷ niệm", "removed_photo_from_memory": "Đã xóa ảnh khỏi kỷ niệm", - "removed_tagged_assets": "Đã xóa thẻ khỏi {count, plural, one {# tệp} other {# tệp}}", + "removed_tagged_assets": "Đã xóa thẻ khỏi {count, plural, one {# tài nguyên} other {# tài nguyên}}", "rename": "Đổi tên", "repair": "Sửa chữa", "repair_no_results_message": "Các tệp không được theo dõi và bị thiếu sẽ xuất hiện ở đây", "replace_with_upload": "Thay thế tệp khác", - "repository": "Kho lưu trữ", + "repository": "Kho mã nguồn", "require_password": "Yêu cầu mật khẩu", "require_user_to_change_password_on_first_login": "Yêu cầu người dùng thay đổi mật khẩu ở lần đầu đăng nhập", "rescan": "Quét lại", @@ -1895,12 +1994,12 @@ "restore_all": "Khôi phục tất cả", "restore_trash_action_prompt": "{count} đã khôi phục từ thùng rác", "restore_user": "Khôi phục người dùng", - "restored_asset": "Tệp đã được khôi phục", + "restored_asset": "Tài nguyên đã được khôi phục", "resume": "Tiếp tục", "resume_paused_jobs": "Tiếp tục {count, plural, one {# tác vụ đã dừng} other {# tác vụ đã dừng}}", "retry_upload": "Thử tải lên lại", "review_duplicates": "Xem lại các mục trùng lặp", - "review_large_files": "Xem lại tệp dung lượng lớn", + "review_large_files": "Xem lại các tệp dung lượng lớn", "role": "Vai trò", "role_editor": "Người chỉnh sửa", "role_viewer": "Người xem", @@ -1908,25 +2007,30 @@ "save": "Lưu", "save_to_gallery": "Lưu vào thư viện", "saved": "Đã lưu", - "saved_api_key": "Khóa API đã lưu", - "saved_profile": "Hồ sơ đã lưu", - "saved_settings": "Cài đặt đã lưu", + "saved_api_key": "Đã lưu Khóa API", + "saved_profile": "Đã lưu hồ sơ", + "saved_settings": "Đã lưu cài đặt", "say_something": "Nói điều gì đó", "scaffold_body_error_occurred": "Xảy ra lỗi", + "scaffold_body_error_unrecoverable": "Đã xảy ra lỗi không thể khắc phục. Vui lòng chia sẻ lỗi và dấu vết ngăn xếp trên Discord hoặc GitHub để chúng tôi có thể trợ giúp. Nếu được yêu cầu, bạn có thể xóa dữ liệu ứng dụng bên dưới.", "scan": "Quét", "scan_all_libraries": "Quét tất cả thư viện", "scan_library": "Quét", "scan_settings": "Cài đặt quét", "scanning": "Đang quét", "scanning_for_album": "Đang quét album...", + "screencast_mode_description": "Hiển thị chỉ báo sự kiện bàn phím và chuột trên màn hình", + "screencast_mode_title": "Bật/tắt chế độ quay màn hình", "search": "Tìm kiếm", "search_albums": "Tìm album", "search_by_context": "Tìm theo ngữ cảnh", "search_by_description": "Tìm theo mô tả", - "search_by_description_example": "Dạo chơi Sa Pa", + "search_by_description_example": "Leo núi ở Sa Pa", "search_by_filename": "Tìm theo tên hoặc định dạng tệp", "search_by_filename_example": "Ví dụ: IMG_1234.JPG hoặc PNG", - "search_by_ocr": "Tìm bằng OCR", + "search_by_full_path": "Tìm kiếm theo đường dẫn đầy đủ hoặc thư mục", + "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - bạn có thể tìm Projects, 3D, Printing, 2026...", + "search_by_ocr": "Tìm theo OCR", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Tìm dòng lens...", "search_camera_make": "Tìm thương hiệu máy ảnh...", @@ -1945,13 +2049,13 @@ "search_filter_location_title": "Chọn vị trí", "search_filter_media_type": "Phương tiện", "search_filter_media_type_title": "Chọn loại phương tiện", - "search_filter_ocr": "Tìm bằng OCR", + "search_filter_ocr": "Tìm theo OCR", "search_filter_people_title": "Chọn người", "search_filter_star_rating": "Xếp hạng sao", "search_filter_tags_title": "Chọn các thẻ", "search_for": "Tìm kiếm", "search_for_existing_person": "Tìm người hiện có", - "search_no_more_result": "Không còn kết quả", + "search_no_more_result": "Không còn kết quả nào", "search_no_people": "Không có người", "search_no_people_named": "Không có người tên \"{name}\"", "search_no_result": "Không tìm thấy kết quả, hãy thử một cụm từ hoặc kết hợp với tìm kiếm khác", @@ -1972,7 +2076,7 @@ "search_rating": "Tìm kiếm theo xếp hạng…", "search_result_page_new_search_hint": "Tìm kiếm mới", "search_settings": "Tìm kiếm cài đặt", - "search_state": "Tìm tỉnh...", + "search_state": "Tìm tiểu bang...", "search_suggestion_list_smart_search_hint_1": "Tìm kiếm thông minh được bật mặc định, để tìm kiếm metadata hãy sử dụng cú pháp ", "search_suggestion_list_smart_search_hint_2": "m:cụm-từ-tìm-kiếm-của-bạn", "search_tags": "Tìm thẻ...", @@ -2002,6 +2106,7 @@ "select_person": "Chọn người", "select_person_to_tag": "Chọn người để gắn thẻ", "select_photos": "Chọn ảnh", + "select_quality": "Chọn chất lượng", "select_trash_all": "Chọn xóa tất cả", "select_user_for_sharing_page_err_album": "Tạo album thất bại", "selected": "Đã chọn", @@ -2027,15 +2132,15 @@ "set_date_of_birth": "Đặt sinh nhật", "set_profile_picture": "Đặt ảnh đại diện", "set_slideshow_to_fullscreen": "Đặt trình chiếu ở chế độ toàn màn hình", - "set_stack_primary_asset": "Đặt làm tài sản chính", - "setting_image_navigation_enable_subtitle": "Nếu được bật, bạn có thể điều hướng đến ảnh phía trước/kế tiếp bằng cách chạm vào phần tư bên trái/phải màn hình.", + "set_stack_primary_asset": "Đặt làm tài nguyên chính", + "setting_image_navigation_enable_subtitle": "Bật để chuyển sang ảnh trước/ảnh sau bằng cách nhấn mép trái/phải màn hình.", "setting_image_navigation_enable_title": "Chạm để điều hướng", "setting_image_navigation_title": "Điều hướng hình ảnh", - "setting_image_viewer_help": "Trình xem ảnh tải ảnh thu nhỏ cỡ nhỏ trước, sau đó tải cỡ trung bình (nếu được bật), cuối cùng tải bản gốc (nếu được bật).", + "setting_image_viewer_help": "Khi xem chi tiết, thứ tự nạp sẽ là: ảnh thu nhỏ (cỡ nhỏ) → ảnh xem trước (cỡ trung bình) (nếu bật) → ảnh gốc (nếu bật).", "setting_image_viewer_original_subtitle": "Bật để tải ảnh gốc ở độ phân giải đầy đủ (dung lượng lớn). Tắt để giảm mức sử dụng dữ liệu (mức sử dụng mạng và bộ nhớ đệm trên thiết bị).", - "setting_image_viewer_original_title": "Tải ảnh gốc", - "setting_image_viewer_preview_subtitle": "Bật để tải ảnh độ phân giải trung bình. Tắt để tải trực tiếp ảnh gốc hoặc chỉ sử dụng ảnh thu nhỏ.", - "setting_image_viewer_preview_title": "Tải ảnh xem trước", + "setting_image_viewer_original_title": "Ưu tiên chất lượng gốc", + "setting_image_viewer_preview_subtitle": "Bật để nạp ảnh chất lượng trung bình. Tắt để nạp thẳng ảnh gốc hoặc chỉ xem ảnh thu nhỏ.", + "setting_image_viewer_preview_title": "Nạp ảnh xem trước", "setting_image_viewer_title": "Ảnh", "setting_languages_apply": "Áp dụng", "setting_languages_subtitle": "Thay đổi ngôn ngữ ứng dụng", @@ -2045,26 +2150,28 @@ "setting_notifications_notify_minutes": "{count} phút", "setting_notifications_notify_never": "không bao giờ", "setting_notifications_notify_seconds": "{count} giây", - "setting_notifications_single_progress_subtitle": "Thông tin chi tiết của từng ảnh đang tải lên", + "setting_notifications_single_progress_subtitle": "Thông tin chi tiết của từng tài nguyên đang tải lên", "setting_notifications_single_progress_title": "Hiện chi tiết sao lưu nền đang thực hiện", "setting_notifications_subtitle": "Các loại thông báo", - "setting_notifications_total_progress_subtitle": "Toàn bộ quá trình tải lên (hoàn thành/tổng số tệp)", + "setting_notifications_total_progress_subtitle": "Toàn bộ quá trình tải lên (hoàn thành/tổng số tài nguyên)", "setting_notifications_total_progress_title": "Hiện quá trình sao lưu nền đang thực hiện", "setting_video_viewer_auto_play_subtitle": "Khi mở video", "setting_video_viewer_auto_play_title": "Tự động phát video", "setting_video_viewer_looping_title": "Lặp lại", - "setting_video_viewer_original_video_subtitle": "Khi phát trực tuyến video từ máy chủ, hãy phát video gốc ngay cả khi có bản chuyển mã. Có thể dẫn đến tình trạng chờ. Video có sẵn trên máy được phát ở chất lượng gốc bất kể cài đặt này.", - "setting_video_viewer_original_video_title": "Dùng video gốc", + "setting_video_viewer_original_video_subtitle": "Khi phát trực tuyến một video từ máy chủ, hệ thống sẽ luôn dùng video gốc thay vì bản chuyển mã (có thể gây hiện tượng giật/chờ tải). Video lưu trên thiết bị luôn được phát ở chất lượng gốc.", + "setting_video_viewer_original_video_title": "Ưu tiên chất lượng gốc", "settings": "Cài đặt", "settings_require_restart": "Vui lòng khởi động lại Immich để áp dụng cài đặt này", "settings_saved": "Đã lưu cài đặt", "setup_pin_code": "Thiết lập mã PIN", "share": "Chia sẻ", - "share_action_prompt": "Đã chia sẻ {count} tệp", + "share_action_prompt": "Đã chia sẻ {count} tài nguyên", "share_add_photos": "Thêm ảnh", "share_assets_selected": "{count} đã chọn", "share_dialog_preparing": "Đang xử lý...", - "share_link": "Liên kết chia sẻ", + "share_link": "Link chia sẻ", + "share_original": "Chất lượng gốc (lớn)", + "share_preview": "Dùng ảnh thu nhỏ (nhỏ)", "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?", @@ -2079,10 +2186,10 @@ "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_clipboard_copied_massage": "Đã sao chép vào bộ nhớ tạm", + "shared_link_clipboard_copied_massage": "Đã sao chép vào clipboard", "shared_link_clipboard_text": "Liên kết: {link}\nMật khẩu: {password}", "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 liên kết chia sẻ với một URL tùy chỉnh", + "shared_link_custom_url_description": "Truy cập link đã chia sẻ với một URL tùy chỉnh", "shared_link_edit_description_hint": "Nhập mô tả chia sẻ", "shared_link_edit_expire_after_option_day": "1 ngày", "shared_link_edit_expire_after_option_days": "{count} ngày", @@ -2106,11 +2213,11 @@ "shared_link_expires_seconds": "Hết hạn sau {count} giây", "shared_link_individual_shared": "Chia sẻ riêng tư", "shared_link_info_chip_metadata": "Dữ liệu EXIF", - "shared_link_manage_links": "Quản lý liên kết đã chia sẻ", - "shared_link_options": "Tùy chọn liên kết chia sẻ", - "shared_link_password_description": "Bắt buộc để truy cập liên kết chia sẻ này", - "shared_links": "Liên kết chia sẻ", - "shared_links_description": "Chia sẻ ảnh và video bằng liên kết", + "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_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", "shared_with_partner": "Đã chia sẻ với {partner}", @@ -2122,25 +2229,28 @@ "sharing_sidebar_description": "Hiển thị mục Chia sẻ trong thanh bên", "sharing_silver_appbar_create_shared_album": "Tạo album chia sẻ", "sharing_silver_appbar_share_partner": "Chia sẻ với người thân", - "shift_to_permanent_delete": "nhấn ⇧ để xóa vĩnh viễn tệp", + "shift_to_permanent_delete": "nhấn ⇧ để xóa vĩnh viễn tài nguyên", "show_album_options": "Hiện tùy chọn album", "show_albums": "Hiện album", "show_all_people": "Hiển thị tất cả mọi người", "show_and_hide_people": "Hiển thị & ẩn người", - "show_file_location": "Hiển thị vị trí tập tin", + "show_file_location": "Hiển thị vị trí tệp", "show_gallery": "Hiển thị thư viện ảnh", "show_hidden_people": "Hiển thị người bị ẩn", "show_in_timeline": "Hiển thị trên dòng thời gian", "show_in_timeline_setting_description": "Hiển thị ảnh và video từ người dùng này trong dòng thời gian của bạn", "show_keyboard_shortcuts": "Hiện phím tắt", + "show_less": "Hiện ít hơn", "show_metadata": "Hiển thị metadata", + "show_more_fields": "{count, plural, one {Hiện thêm # trường} other {Hiện thêm # trường}}", "show_or_hide_info": "Hiển thị hoặc ẩn thông tin", "show_password": "Hiển thị mật khẩu", "show_person_options": "Hiện tùy chọn người", "show_progress_bar": "Hiển thị thanh tiến trình", "show_schema": "Hiện lược đồ", "show_search_options": "Hiện tùy chọn tìm kiếm", - "show_shared_links": "Hiển thị các liên kết được chia sẻ", + "show_shared_links": "Hiển thị các link được chia sẻ", + "show_slideshow_metadata_overlay": "Hiển thị lớp phủ thông tin hình ảnh", "show_slideshow_transition": "Hiển thị hiệu ứng chuyển tiếp", "show_supporter_badge": "Huy hiệu người ủng hộ", "show_supporter_badge_description": "Hiển thị huy hiệu người ủng hộ", @@ -2148,7 +2258,7 @@ "show_text_search_menu": "Hiển thị menu tìm kiếm văn bản", "shuffle": "Ngẫu nhiên", "sidebar": "Thanh bên", - "sidebar_display_description": "Hiển thị liên kết đến chế độ xem trong thanh bên", + "sidebar_display_description": "Hiển thị link đến chế độ xem trong thanh bên", "sign_out": "Đăng xuất", "sign_up": "Đăng ký", "size": "Kích thước", @@ -2156,9 +2266,14 @@ "skip_to_folders": "Chuyển đến thư mục", "skip_to_tags": "Chuyển đến thẻ", "slideshow": "Trình chiếu", + "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_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", + "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...", "sort_created": "Ngày tạo", "sort_items": "Số lượng mục", @@ -2168,19 +2283,24 @@ "sort_people_by_similarity": "Sắp xếp người theo độ tương đồng", "sort_recent": "Ảnh gần đây nhất", "sort_title": "Tiêu đề", - "source": "Nguồn", + "source": "Mã nguồn", "stack": "Nhóm ảnh", "stack_action_prompt": "{count} đã được nhóm", "stack_duplicates": "Nhóm mục trùng lặp", "stack_select_one_photo": "Chọn ảnh chính cho nhóm ảnh", "stack_selected_photos": "Nhóm các ảnh đã chọn", - "stacked_assets_count": "Đã nhóm {count, plural, one {# tệp} other {# tệp}}", + "stacked_assets_count": "Đã nhóm {count, plural, one {# tài nguyên} other {# tài nguyên}}", "stacktrace": "Thông tin chi tiết lỗi", "start": "Bắt đầu", "start_date": "Ngày bắt đầu", "start_date_before_end_date": "Ngày bắt đầu phải trước ngày kết thúc", - "state": "Tỉnh", + "state": "Tiểu bang", "status": "Trạng thái", + "step_delete": "Xóa bước", + "step_delete_confirm": "Bạn có chắc muốn xóa bước này?", + "step_details": "Chi tiết bước", + "steps": "Các bước", + "steps_count": "{count, plural, one {# bước} other {# bước}}", "stop_casting": "Dừng chiếu", "stop_motion_photo": "Dừng ảnh chuyển động", "stop_photo_sharing": "Dừng chia sẻ ảnh của bạn?", @@ -2189,7 +2309,7 @@ "storage": "Bộ nhớ", "storage_label": "Nhãn lưu trữ", "storage_quota": "Hạn mức dung lượng", - "storage_usage": "Đã dùng {used} của {available}", + "storage_usage": "{used} trong {available}", "submit": "Gửi", "success": "Thành công", "suggestions": "Gợi ý", @@ -2207,15 +2327,18 @@ "sync_status": "Trạng thái đồng bộ", "sync_status_subtitle": "Thống kê về việc đồng bộ", "sync_upload_album_setting_subtitle": "Tạo và tải lên ảnh và video của bạn vào album đã chọn trên Immich", + "system_theme": "Chủ đề hệ thống", + "system_theme_command_description": "Giống chủ đề hệ thống ({value})", "tag": "Thẻ", - "tag_assets": "Gắn thẻ", + "tag_assets": "Gắn thẻ tài nguyên", "tag_created": "Đã tạo thẻ: {tag}", - "tag_feature_description": "Duyệt ảnh và video được nhóm theo chủ đề thẻ hợp lý", + "tag_face": "Gắn thẻ khuôn mặt", + "tag_feature_description": "Duyệt ảnh và video được phân loại theo chủ đề thẻ", "tag_not_found_question": "Không tìm thấy thẻ? Create a new tag. (Tạo một thẻ mới)", "tag_people": "Gắn thẻ Mọi người", "tag_updated": "Đã cập nhật thẻ: {tag}", - "tagged_assets": "Đã gắn thẻ {count, plural, one {# tệp} other {# tệp}}", - "tags": "Thẻ", + "tagged_assets": "Đã gắn thẻ {count, plural, one {# tài nguyên} other {# tài nguyên}}", + "tags": "Gắn thẻ", "tap_to_run_job": "Nhấn để chạy tác vụ", "template": "Mẫu", "text_recognition": "Nhận dạng văn bản", @@ -2223,13 +2346,13 @@ "theme_selection": "Chủ đề", "theme_selection_description": "Dựa theo trình duyệt của bạn", "theme_setting_asset_list_storage_indicator_title": "Hiện trạng thái sao lưu trên ảnh thu nhỏ", - "theme_setting_asset_list_tiles_per_row_title": "Số lượng tệp trên mỗi hàng ({count})", - "theme_setting_colorful_interface_subtitle": "Áp dụng màu chủ đạo cho nền app.", + "theme_setting_asset_list_tiles_per_row_title": "Số lượng tài nguyên trên mỗi hàng ({count})", + "theme_setting_colorful_interface_subtitle": "Áp dụng màu chủ đề cho nền app.", "theme_setting_colorful_interface_title": "Giao diện sinh động", "theme_setting_image_viewer_quality_subtitle": "Điều chỉnh chất lượng của trình xem ảnh", "theme_setting_image_viewer_quality_title": "Chất lượng trình xem ảnh", "theme_setting_primary_color_subtitle": "Chọn màu cho các hành động chính và điểm nhấn.", - "theme_setting_primary_color_title": "Màu chủ đạo", + "theme_setting_primary_color_title": "Màu chủ đề", "theme_setting_system_primary_color_title": "Dùng màu hệ thống", "theme_setting_system_theme_switch": "Tự động (Giống thiết bị)", "theme_setting_theme_subtitle": "Chọn cài đặt giao diện ứng dụng", @@ -2247,11 +2370,11 @@ "to_change_password": "Đổi mật khẩu", "to_favorite": "Thích", "to_login": "Đăng nhập", - "to_multi_select": "để chọn-nhiều", + "to_multi_select": "chọn nhiều mục", "to_parent": "Về thư mục gốc", - "to_select": "để chọn", + "to_select": "chọn", "to_trash": "Xóa", - "toggle_settings": "Chuyển đổi cài đặt", + "toggle_settings": "Đổi cài đặt", "toggle_theme_description": "Đổi chủ đề", "total": "Tổng cộng", "total_usage": "Dung lượng đã dùng", @@ -2259,22 +2382,24 @@ "trash_action_prompt": "{count} đã chuyển 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 vĩnh viễn", + "trash_delete_asset": "Chuyển vào thùng rác/Xóa tài nguyên", "trash_emptied": "Đã dọn sạch thùng rác", "trash_no_results_message": "Ảnh và video đã bị xóa sẽ hiển thị ở đây.", "trash_page_delete_all": "Xóa tất cả", "trash_page_empty_trash_dialog_content": "Bạn có muốn dọn sạch thùng rác của mình không? Những mục này sẽ bị xóa vĩnh viễn khỏi Immich", "trash_page_info": "Những mục này sẽ bị xóa sau {days} ngày", - "trash_page_no_assets": "Không có tệp nào", + "trash_page_no_assets": "Không có tài nguyên nào", "trash_page_restore_all": "Khôi phục tất cả", - "trash_page_select_assets_btn": "Chọn tệp", + "trash_page_select_assets_btn": "Chọn tài nguyên", "trash_page_title": "Thùng rác ({count})", "trashed_items_will_be_permanently_deleted_after": "Các mục đã xóa sẽ bị xóa vĩnh viễn sau {days, plural, one {# ngày} other {# ngày}}.", "trigger": "Kích hoạt", - "trigger_asset_uploaded": "Tệp đã được tải lên", - "trigger_asset_uploaded_description": "Sự kiện này được kích hoạt khi một tệp mới được tải lên", + "trigger_asset_metadata_extraction": "Trích xuất metadata tài nguyên", + "trigger_asset_metadata_extraction_description": "Sự kiện này được kích hoạt khi metadata EXIF của một tài nguyên được trích xuất", + "trigger_asset_uploaded": "Đã tải lên tài nguyên", + "trigger_asset_uploaded_description": "Được kích hoạt khi một tài nguyên mới được tải lên", "trigger_description": "Một sự kiện khởi đầu workflow", - "trigger_person_recognized": "Người được nhận diện", + "trigger_person_recognized": "Đã nhận diện người", "trigger_person_recognized_description": "Được kích hoạt khi phát hiện thấy một người", "trigger_type": "Kiểu kích hoạt", "troubleshoot": "Khắc phục sự cố", @@ -2287,7 +2412,7 @@ "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 Đã thích", + "unfavorite_action_prompt": "{count} đã bỏ khỏi Yêu thích", "unhide_person": "Hiện người", "unknown": "Không xác định", "unknown_country": "Quốc gia chưa rõ", @@ -2307,31 +2432,34 @@ "unselect_all_in": "Bỏ chọn tất cả trong {group}", "unstack": "Hủy xếp nhóm", "unstack_action_prompt": "{count} đã bỏ nhóm", - "unstacked_assets_count": "Đã hủy xếp nhóm {count, plural, one {# tệp} other {# tệp}}", + "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ợ.", "untagged": "Chưa gắn thẻ", "up_next": "Tiếp theo", - "update_location_action_prompt": "Cập nhật địa điểm của {count} tệp đã chọn với:", + "update_location_action_prompt": "Cập nhật vị trí của {count} tài nguyên đã chọn với:", "updated_at": "Đã cập nhật", "updated_password": "Đã cập nhật mật khẩu", "upload": "Tải lên", "upload_concurrency": "Tải lên đồng thời", + "upload_day_count": "{date}: {count, plural, one {# tải lên} other {# tải lên}}", "upload_details": "Chi tiết tải lên", - "upload_dialog_info": "Bạn có muốn sao lưu những tệp đã chọn lên máy chủ không?", - "upload_dialog_title": "Tải lên tệp", - "upload_error_with_count": "Không thể tải lên {count, plural, one {# tệp} other {# tệp}}", - "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ệp vừa tải lên.", + "upload_dialog_info": "Bạn có muốn sao lưu những tài nguyên đã chọn lên máy chủ không?", + "upload_dialog_title": "Tải lên tài nguyên", + "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_skipped_duplicates": "Đã bỏ qua {count, plural, one {# tệp trùng lặp} other {# tệp trùng lặp}}", - "upload_status_duplicates": "Tệp trùng lặp", + "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ệp vừa 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_immich": "Tải lên Immich ({count})", "uploading": "Đang tải lên", "uploading_media": "Đang tải lên phương tiện", + "uploads": "Tải lên", + "uploads_count": "{count, plural, one {# tải lên} other {# tải lên}}", "url": "URL", "usage": "Sử dụng", "use_biometric": "Dùng sinh trắc học", @@ -2339,10 +2467,11 @@ "use_browser_locale_description": "Định dạng ngày, thời gian và số dựa trên ngôn ngữ trình duyệt", "use_current_connection": "Dùng kết nối hiện tại", "use_custom_date_range": "Chọn khoảng thời gian tùy chỉnh", + "use_template": "Dùng mẫu có sẵn", "user": "Người dùng", "user_has_been_deleted": "Người dùng này đã bị xóa.", "user_id": "ID người dùng", - "user_liked": "{user} đã thích {type, select, photo {ảnh này} video {video này} asset {tệp này} other {nó}}", + "user_liked": "{user} đã thích {type, select, photo {ảnh này} video {video này} asset {tài nguyên này} other {nó}}", "user_pin_code_settings": "Mã PIN", "user_pin_code_settings_description": "Quản lý mã PIN của bạn", "user_privacy": "Quyền riêng tư người dùng", @@ -2362,12 +2491,13 @@ "variables": "Các tham số", "version": "Phiên bản", "version_announcement_closing": "Bạn của bạn, Alex", - "version_announcement_message": "Chào bạn! Một phiên bản mới của Immich đã ra mắt. Vui lòng dành thời gian để xem danh sách thay đổi để đảm bảo cấu hình của bạn được cập nhật để tránh lỗi cấu hình sai, đặc biệt nếu bạn sử dụng WatchTower hoặc bất kỳ cơ chế tự động cập nhật Immich của bạn.", + "version_announcement_message": "Chào bạn! Immich đã ra mắt phiên bản mới. Vui lòng dành thời gian để xem ghi chú phát hành để đảm bảo cấu hình của bạn được cập nhật nhằm tránh cấu hình sai, đặc biệt nếu bạn sử dụng WatchTower hoặc bất kỳ cơ chế tự động cập nhật Immich nào.", "version_history": "Lịch sử phiên bản", "version_history_item": "Đã cài đặt {version} vào {date}", "video": "Video", "video_hover_setting": "Xem trước video khi di chuột lên", - "video_hover_setting_description": "Phát đoạn video xem trước khi di chuột qua mục. Ngay cả khi tắt chức năng này, vẫn có thể bắt đầu phát video bằng cách di chuột qua biểu tượng phát.", + "video_hover_setting_description": "Phát đoạn video xem trước khi di chuột qua mục. Nếu tắt, vẫn có thể bắt đầu phát video bằng cách di chuột qua biểu tượng phát.", + "video_quality": "Chất lượng video", "videos": "Video", "videos_count": "{count, plural, one {# Video} other {# Video}}", "videos_only": "Chỉ video", @@ -2375,21 +2505,22 @@ "view_album": "Xem Album", "view_all": "Xem tất cả", "view_all_users": "Xem tất cả người dùng", - "view_asset_owners": "Xem chủ sở hữu tệp", + "view_asset_owners": "Xem chủ sở hữu tài nguyên", "view_details": "Xem thông tin chi tiết", "view_in_timeline": "Xem trong dòng thời gian", "view_link": "Xem liên kết", "view_links": "Xem các liên kết", "view_name": "Giao diện", - "view_next_asset": "Xem tệp tiếp theo", - "view_previous_asset": "Xem tệp trước đó", + "view_next_asset": "Xem tài nguyên tiếp theo", + "view_previous_asset": "Xem tài nguyên trước đó", "view_qr_code": "Xem mã QR", "view_similar_photos": "Xem ảnh tương tự", "view_stack": "Xem nhóm ảnh", "view_user": "Xem Người dùng", "viewer_remove_from_stack": "Xóa khỏi nhóm", - "viewer_stack_use_as_main_asset": "Đặt làm ảnh nổi bật", + "viewer_stack_use_as_main_asset": "Đặt làm tài nguyên chính", "viewer_unstack": "Hủy xếp nhóm", + "visibility": "Khả năng hiển thị", "visibility_changed": "Đã thay đổi trạng thái hiển thị cho {count, plural, one {# người} other {# người}}", "visual": "Trực quan", "visual_builder": "Tạo trực quan", @@ -2399,8 +2530,10 @@ "week": "Tuần", "welcome": "Chào mừng", "welcome_to_immich": "Chào mừng đến với Immich", + "when": "Khi nào", "width": "Chiều rộng", "wifi_name": "Tên Wi-Fi", + "workflow": "Workflow", "workflow_delete_prompt": "Bạn có chắc muốn xóa luồng công việc này?", "workflow_deleted": "Đã xóa luồng công việc", "workflow_description": "Mô tả luồng công việc", @@ -2410,17 +2543,19 @@ "workflow_name": "Tên luồng công việc", "workflow_navigation_prompt": "Bạn có chắc muốn rời đi mà không lưu lại các thay đổi của mình?", "workflow_summary": "Mô tả luồng công việc", + "workflow_templates": "Mẫu workflow có sẵn", "workflow_update_success": "Đã cập nhật luồng công việc thành công", "workflow_updated": "Đã cập nhật Luồng công việc", "workflows": "Luồng công việc", - "workflows_help_text": "Luồng công việc tự động hóa các hành động trên tập tin của bạn dựa trên các trình kích hoạt và bộ lọc", + "workflows_help_text": "Luồng công việc tự động hóa các hành động trên tài nguyên của bạn dựa trên các trình kích hoạt và bộ lọc", "wrong_pin_code": "Mã PIN không đúng", + "x_of_total": "{x}/{total}", "year": "Năm", "years_ago": "{years, plural, one {# năm} other {# năm}} trước", "yes": "Đồng ý", "you_dont_have_any_shared_links": "Bạn không có liên kết chia sẻ nào", "your_wifi_name": "Tên Wi-Fi của bạn", - "zero_to_clear_rating": "nhấn 0 để xóa đánh giá ảnh", + "zero_to_clear_rating": "nhấn 0 để xóa xếp hạng tài nguyên", "zoom_image": "Thu phóng ảnh", "zoom_to_bounds": "Thu phóng vừa khung" } diff --git a/i18n/yue_Hant.json b/i18n/yue_Hant.json index e19f05c88c..0db0b76296 100644 --- a/i18n/yue_Hant.json +++ b/i18n/yue_Hant.json @@ -51,7 +51,7 @@ "authentication_settings": "驗證設定", "authentication_settings_description": "管理密碼、OAuth 同其他驗證設定", "authentication_settings_disable_all": "你確定你要停用所有登入嘅方式嗎?你會完全冇辦法登入。", - "authentication_settings_reenable": "如果你要重新啟用嘅話,請使用嘅伺服器指令。", + "authentication_settings_reenable": "如果你要重新啟用嘅話,請使用伺服器指令。", "background_task_job": "背景操作", "backup_database": "建立資料庫備份", "backup_database_enable_description": "啟用資料庫備份", @@ -60,7 +60,7 @@ "backup_onboarding_2_description": "儲存係唔同裝置嘅本地副本。包含主要嘅檔案同埋喺本機嘅備份。", "backup_onboarding_3_description": "資料包含原始文件,總共備份嘅次數。呢個包括1份異地嘅備份同埋2份本機副本。", "backup_onboarding_description": "我哋建議你使用嘅3-2-1備份嚟保障你嘅資料。為咗可以全方位地備份你嘅資料,你應該保留已經上傳嘅相/影片嘅副本,同埋Immich嘅資料庫,。", - "backup_onboarding_footer": "有關其他Immich備份嘅資料,請參考。", + "backup_onboarding_footer": "有關其他Immich備份嘅資料,請參考說明文件。", "backup_onboarding_parts_title": "一個3-2-1備份包括:", "backup_onboarding_title": "備份", "backup_settings": "資料庫備份嘅設定", @@ -261,6 +261,55 @@ "nightly_tasks_sync_quota_usage_setting": "同步限額使用情況", "nightly_tasks_sync_quota_usage_setting_description": "根據目前嘅使用量更新使用者嘅儲存限額", "no_paths_added": "冇新增咗嘅路徑", + "no_pattern_added": "未有排除規則", + "note_apply_storage_label_previous_assets": "提示:如果您想將儲存標籤套用到上傳咗嘅項目,請執行", + "note_cannot_be_changed_later": "注意:呢個設定之後改唔到!", + "notification_email_from_address": "寄件地址", + "notification_email_from_address_description": "寄件者電郵,好似:\"Immich Photo Server \"。請確保係您有權限寄送到郵件嘅地址。", + "notification_email_host_description": "電子郵件伺服器主機(例如:smtp.immich.app)", + "notification_email_ignore_certificate_errors": "忽略唔啱嘅憑證", + "notification_email_ignore_certificate_errors_description": "忽略 TLS 錯嘅憑證驗證(唔建議)", + "notification_email_password_description": "用嚟驗證電郵伺服器嘅密碼", + "notification_email_port_description": "電郵伺服器嘅連接埠(例如 25、465 或 587)", + "notification_email_secure": "SMTPS", + "notification_email_secure_description": "用 SMTPS(基於 TLS 嘅 SMTP)", + "notification_email_sent_test_email_button": "傳送測試電郵,跟住儲存", + "notification_email_setting_description": "寄送電郵通知嘅設定", + "notification_email_test_email": "傳送測試電郵", + "notification_email_test_email_failed": "傳送唔到測試電郵,請檢查您嘅設定", + "notification_email_test_email_sent": "測試電郵已傳送咗到 {email}。請檢查您嘅收件匣。", + "notification_email_username_description": "用嚟驗證電郵伺服器嘅使用者名稱", + "notification_enable_email_notifications": "用電郵通知", + "notification_settings": "通知設定", + "notification_settings_description": "管理通知設定,包括電郵設定", + "oauth_allow_insecure_requests": "畀用唔可靠嘅請求", + "oauth_allow_insecure_requests_description": "警告:呢個選項會停用 OAuth 嘅 TLS 憑證驗證請求,可能會令您暴露於 MITM 攻擊。", + "oauth_auto_launch": "自動啟動", + "oauth_auto_launch_description": "進入登入頁面時,自動啟動 OAuth 登入流程", + "oauth_auto_register": "自動註冊", + "oauth_auto_register_description": "使用 OAuth 登入之後自動註冊新使用者", + "oauth_button_text": "按鈕文字", + "oauth_client_secret_description": "機密用戶端嘅必填項目;如果公開用戶端唔支援 PKCE (代碼交換的驗證金鑰),必須填寫。", + "oauth_enable_description": "用 OAuth 登入", + "oauth_end_session_url_description": "用戶登出時將佢導向到呢個URL。", + "oauth_mobile_redirect_uri": "流動裝置重新導向 URI", + "oauth_mobile_redirect_uri_override": "覆寫流動裝置重新導向 URI", + "oauth_mobile_redirect_uri_override_description": "當 OAuth 提供者唔俾用流動裝置 URI(例如 ''{callback}'')嘅時候啟用", + "oauth_prompt_description": "Prompt 參數(例如 select_account、login、consent)", + "oauth_role_claim": "角色宣告", + "oauth_role_claim_description": "根據呢個宣告嘅存在,自動授權管理員權限。呢啲宣告值可以係 'user' 或 'admin'。", + "oauth_settings": "OAuth", + "oauth_settings_description": "管理 OAuth 登入設定", + "oauth_settings_more_details": "如果想了解呢個功能嘅詳細資訊,請參閱 說明文件。", + "oauth_storage_label_claim": "儲存標籤宣告", + "oauth_storage_label_claim_description": "自動將使用者嘅儲存標籤設定為呢個宣告值。", + "oauth_storage_quota_claim": "儲存限額宣告", + "oauth_storage_quota_claim_description": "自動將使用者嘅儲存限額設定為呢個宣告之值。", + "oauth_storage_quota_default": "預設儲存限額(GiB)", + "oauth_storage_quota_default_description": "未提供宣告時所使用嘅限額(GiB)。", + "oauth_timeout": "請求超時", + "oauth_timeout_description": "請求超時(毫秒)", + "ocr_job_description": "用機械學習辨識影像中嘅文字", "queue_details": "隊列資訊", "queues": "任務隊列", "queues_page_description": "管理員任務隊列頁面", diff --git a/i18n/zh_Hans.json b/i18n/zh_Hans.json index a2e5ce1431..7081e73805 100644 --- a/i18n/zh_Hans.json +++ b/i18n/zh_Hans.json @@ -1548,7 +1548,6 @@ "map_location_picker_page_use_location": "使用此位置", "map_location_service_disabled_content": "需要启用定位服务才能显示您当前位置的媒体文件。是否现在启用它?", "map_location_service_disabled_title": "定位服务已禁用", - "map_marker_for_images": "标记{city}、{country}拍摄照片的地图图标", "map_marker_with_image": "带预览图的地图标记", "map_no_location_permission_content": "需要位置权限才能显示您当前位置的照片/视频。现在要允许吗?", "map_no_location_permission_title": "位置权限被拒绝", diff --git a/i18n/zh_Hant.json b/i18n/zh_Hant.json index 039f2e3631..49b30a0058 100644 --- a/i18n/zh_Hant.json +++ b/i18n/zh_Hant.json @@ -1548,7 +1548,6 @@ "map_location_picker_page_use_location": "使用此位置", "map_location_service_disabled_content": "需要啟用定位服務才能顯示您目前位置相關的項目。要現在啟用嗎?", "map_location_service_disabled_title": "定位服務已停用", - "map_marker_for_images": "在 {city}、{country} 拍攝影像的地圖標記", "map_marker_with_image": "帶有影像的地圖標記", "map_no_location_permission_content": "需要位置權限才能顯示與您目前位置相關的項目。要現在就授予位置權限嗎?", "map_no_location_permission_title": "沒有位置權限", diff --git a/machine-learning/mise.lock b/machine-learning/mise.lock index b9788e603b..e366eaa4be 100644 --- a/machine-learning/mise.lock +++ b/machine-learning/mise.lock @@ -5,38 +5,38 @@ version = "3.11.15" backend = "core:python" [tools.python."platforms.linux-arm64"] -checksum = "sha256:243f794278eff6adba96ed3677ec6877175df84c25f140e17f09f9be82d0f12a" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" +checksum = "sha256:cbce0660e88cd9c56be7aaf2a2df92bea51f359388a521838b6b01817d728df0" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.linux-arm64-musl"] -checksum = "sha256:52b4c52094ff8b383a45c694acf4c5c0e883152be6d5229a35a8186ce907c6eb" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-unknown-linux-musl-install_only_stripped.tar.gz" +checksum = "sha256:a55ea44225ee3741d4157c383f3d5c3e8eee5f9665e2ea069233486b4275d928" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-unknown-linux-musl-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.linux-x64"] -checksum = "sha256:171dffd8c0f66e8a0725364a7428015b22fc18dd298b24f541392e17dd0e561f" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" +checksum = "sha256:67a5b22f796e96f4d7fa628f95866d5fd1d524d0588f74e4601facd82b66792b" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.linux-x64-musl"] -checksum = "sha256:2ac90fef8917ebd14826a6d667593a06cf0ae5f745ba9b1147dc086dd35f5284" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-unknown-linux-musl-install_only_stripped.tar.gz" +checksum = "sha256:5a8544aa4303da3ca4b7505c98dd8453b671157039d25cd551e55abea0f83a60" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-unknown-linux-musl-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.macos-arm64"] -checksum = "sha256:fdfc363b538662eb7441a14e06f72c4a992c56af7f401f5730ea5081f8f8ad6e" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-aarch64-apple-darwin-install_only_stripped.tar.gz" +checksum = "sha256:8c56f1f59142e0f9f8861ad897bdfd97fd84403afa7b3d8b0f33b208ec471355" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-apple-darwin-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.macos-x64"] -checksum = "sha256:5f1eb247cbca2c0ad5ccbf6d299a4f54b31b5c63b492d74c3531dc4344a42f88" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-apple-darwin-install_only_stripped.tar.gz" +checksum = "sha256:8cd3878c656ba1698314cbcb65f78df4c37b7c8eabff958558115c6db11adb3d" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-apple-darwin-install_only_stripped.tar.gz" provenance = "github-attestations" [tools.python."platforms.windows-x64"] -checksum = "sha256:756d7f148498b8822f6aedf44a020613576f09983161f346ad36dcef6238cdc3" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260510/cpython-3.11.15+20260510-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" +checksum = "sha256:f081a733b4e7ba0e5e5e12d533b3c795dbef3ecbebf92f0b4202e5329bf7c8ab" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" provenance = "github-attestations" [[tools.uv]] diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index a3a1caf0b8..2e821e8b47 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "immich-ml" -version = "3.0.0rc1" +version = "3.0.0rc2" description = "" authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }] requires-python = ">=3.11,<4.0" diff --git a/machine-learning/uv.lock b/machine-learning/uv.lock index bb0d20f102..4552cb3792 100644 --- a/machine-learning/uv.lock +++ b/machine-learning/uv.lock @@ -974,7 +974,7 @@ wheels = [ [[package]] name = "immich-ml" -version = "3.0.0rc1" +version = "3.0.0rc2" source = { editable = "." } dependencies = [ { name = "aiocache" }, diff --git a/mise.lock b/mise.lock index 37df6f1048..44ecd6fdbe 100644 --- a/mise.lock +++ b/mise.lock @@ -83,17 +83,12 @@ version = "7.1.3-6" backend = "github:jellyfin/jellyfin-ffmpeg" [tools."github:jellyfin/jellyfin-ffmpeg".options] -asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz" +asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz" -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64"] -checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876" - -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64-musl"] -checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876" +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-arm64"] +checksum = "sha256:e024d5e78d5414e75f0181036cd21373fafb9270c72894dfd7dbda2572439820" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_macarm64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995838" [[tools."github:jellyfin/jellyfin-ffmpeg"]] version = "7.1.3-6" @@ -117,12 +112,17 @@ version = "7.1.3-6" backend = "github:jellyfin/jellyfin-ffmpeg" [tools."github:jellyfin/jellyfin-ffmpeg".options] -asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz" +asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz" -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-arm64"] -checksum = "sha256:e024d5e78d5414e75f0181036cd21373fafb9270c72894dfd7dbda2572439820" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_macarm64-gpl.tar.xz" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995838" +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64"] +checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876" + +[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64-musl"] +checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886" +url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz" +url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876" [[tools."github:jellyfin/jellyfin-ffmpeg"]] version = "7.1.3-6" @@ -188,30 +188,6 @@ url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288 version = "21.0.2" backend = "core:java" -[tools.java."platforms.linux-arm64"] -checksum = "sha256:08db1392a48d4eb5ea5315cf8f18b89dbaf36cda663ba882cf03c704c9257ec2" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-aarch64_bin.tar.gz" - -[tools.java."platforms.linux-x64"] -checksum = "sha256:a2def047a73941e01a73739f92755f86b895811afb1f91243db214cff5bdac3f" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz" - -[tools.java."platforms.macos-arm64"] -checksum = "sha256:b3d588e16ec1e0ef9805d8a696591bd518a5cea62567da8f53b5ce32d11d22e4" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-aarch64_bin.tar.gz" - -[tools.java."platforms.macos-x64"] -checksum = "sha256:8fd09e15dc406387a0aba70bf5d99692874e999bf9cd9208b452b5d76ac922d3" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-x64_bin.tar.gz" - -[tools.java."platforms.windows-x64"] -checksum = "sha256:b6c17e747ae78cdd6de4d7532b3164b277daee97c007d3eaa2b39cca99882664" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip" - -[[tools.java]] -version = "21.0.2" -backend = "core:java" - [tools.java.options] shorthand_vendor = "openjdk" @@ -304,37 +280,37 @@ checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c70773 url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz" [[tools.pnpm]] -version = "11.5.2" +version = "11.6.0" backend = "aqua:pnpm/pnpm" [tools.pnpm."platforms.linux-arm64"] -checksum = "sha256:7fef0c74081135d777754fccf25272f698e504b26ba0568504846c0cea402f8f" -url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-linux-arm64.tar.gz" +checksum = "sha256:2fec653ff6dadab340d1c3d2214688a7451cc471f39710839440b293ca7c53b0" +url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-arm64.tar.gz" provenance = "github-attestations" [tools.pnpm."platforms.linux-arm64-musl"] -checksum = "sha256:843beed7bca760276d29f8950ca219600995d345dbc93fad8150b3e5f83b74d4" -url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-linux-arm64-musl.tar.gz" +checksum = "sha256:56a78c08cf22adf29e7dacb6f7f100139731693863d20fb94a7883463a62169e" +url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-arm64-musl.tar.gz" provenance = "github-attestations" [tools.pnpm."platforms.linux-x64"] -checksum = "sha256:2033a702618c8576dc6bb0f6adb3a67ab506031351ddd59ca50d1bcaf5d13dc5" -url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-linux-x64.tar.gz" +checksum = "sha256:74d64c1646385fb21691f32f0ab6aca1a9f5c829ba54d3cda3a24838a228e68c" +url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-x64.tar.gz" provenance = "github-attestations" [tools.pnpm."platforms.linux-x64-musl"] -checksum = "sha256:0b794b23461c7475f7ffc29c4945692838b51ddadd857a2ace8edf0018798305" -url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-linux-x64-musl.tar.gz" +checksum = "sha256:7a0c463a09d912fba6b7d9eca0a946bc228ea50f3015a05c09a29e7e85a932d7" +url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-x64-musl.tar.gz" provenance = "github-attestations" [tools.pnpm."platforms.macos-arm64"] -checksum = "sha256:54993dae26bea0f3c1b0e15f9427f6f6a86827d56f32d1d1554d8cda59a62399" -url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-darwin-arm64.tar.gz" +checksum = "sha256:87c901635a14481fb30566a3749041134ffd4317bc6fe866c345b69fdf9b6b85" +url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-darwin-arm64.tar.gz" provenance = "github-attestations" [tools.pnpm."platforms.windows-x64"] -checksum = "sha256:b3ddff2c2bf87d3996fadf074bac58cd2259f718a17912a04ae930e3775b30e9" -url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-win32-x64.zip" +checksum = "sha256:91c753435542b04859c689304fae0dd64eba6b40937cfa426a48485b712e4e9c" +url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-win32-x64.zip" provenance = "github-attestations" [[tools.terragrunt]] diff --git a/mise.toml b/mise.toml index a4286560fd..b3d97d95f0 100644 --- a/mise.toml +++ b/mise.toml @@ -16,7 +16,7 @@ config_roots = [ [tools] node = "24.15.0" -pnpm = "11.5.2" +pnpm = "11.6.0" terragrunt = "1.0.3" opentofu = "1.11.6" "npm:oazapfts" = "7.5.0" diff --git a/mobile/README.md b/mobile/README.md index 1f0860ced6..4115891e99 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -4,20 +4,7 @@ The Immich mobile app is a Flutter-based solution leveraging the Isar Database f ## Setup -1. [Install mise](https://mise.jdx.dev/installing-mise.html). -2. Change to the immich directory and trust the mise config with `mise trust`. -3. Install tools with mise: `mise install`. -4. Run `flutter pub get` to install the dependencies. -5. Run `make translation` to generate the translation file. -6. Run `flutter run` to start the app. - -## Translation - -To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then, from the `mobile/` directory, run - -```bash -make translation -``` +See [setup](https://docs.immich.app/developer/setup) for how to set up the mobile build environment. ## Static Analysis diff --git a/mobile/android/fastlane/Fastfile b/mobile/android/fastlane/Fastfile index a8cd7c5c47..e9a52d1e82 100644 --- a/mobile/android/fastlane/Fastfile +++ b/mobile/android/fastlane/Fastfile @@ -22,8 +22,8 @@ platform :android do task: 'bundle', build_type: 'Release', properties: { - "android.injected.version.code" => 3049, - "android.injected.version.name" => "3.0.0-rc.1", + "android.injected.version.code" => 3050, + "android.injected.version.name" => "3.0.0-rc.2", } ) 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" => 3049, - "android.injected.version.name" => "3.0.0-rc.1", + "android.injected.version.code" => 3050, + "android.injected.version.name" => "3.0.0-rc.2", } ) 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') diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 42926ef45e..96d304f3d7 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 77; objects = { /* Begin PBXBuildFile section */ @@ -11,6 +11,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 467DA6EAF83F3481F8BD94AB /* Pods_ShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AB817AA297EDEC88B23F3F6 /* Pods_ShareExtension.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -19,9 +20,9 @@ B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */; }; B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */; }; B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */; }; + B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */; }; B2EE00022E72CA15008B6CA7 /* PermissionApi.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */; }; B2EE00042E72CA15008B6CA7 /* PermissionApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */; }; - B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */; }; D3BED739C0BC29BB32E18EB2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC499FBCE6B29B2DAFED7130 /* Pods_Runner.framework */; }; F02538E92DFBCBDD008C3FA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; F0B57D3A2DF764BD00DC5BCC /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0B57D392DF764BD00DC5BCC /* WidgetKit.framework */; }; @@ -39,7 +40,6 @@ FEE084F82EC172460045228E /* SQLiteData in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084F72EC172460045228E /* SQLiteData */; }; FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FA2EC1725A0045228E /* RawStructuredFieldValues */; }; FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */ = {isa = PBXBuildFile; productRef = FEE084FC2EC1725A0045228E /* StructuredFieldValues */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -94,6 +94,7 @@ 6D160F04A389B9FFBC557803 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 8AB817AA297EDEC88B23F3F6 /* Pods_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 937632897A02DE9C249F20A6 /* Pods-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.debug.xcconfig"; sourceTree = ""; }; @@ -109,9 +110,9 @@ B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.swift; sourceTree = ""; }; B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connectivity.g.swift; sourceTree = ""; }; B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityApiImpl.swift; sourceTree = ""; }; + B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.g.swift; sourceTree = ""; }; B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionApi.g.swift; sourceTree = ""; }; B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionApiImpl.swift; sourceTree = ""; }; - B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.g.swift; sourceTree = ""; }; C4A6A71F33CE37B3C913115C /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = ""; }; CC499FBCE6B29B2DAFED7130 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F0B57D382DF764BD00DC5BCC /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -130,7 +131,6 @@ FE5499F72F1198DE006016CB /* RemoteImagesImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImagesImpl.swift; sourceTree = ""; }; FE5FE4AD2F30FBC000A71243 /* ImageProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessing.swift; sourceTree = ""; }; FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -153,15 +153,11 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ B231F52D2E93A44A00BC45D1 /* Core */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = Core; sourceTree = ""; }; B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = Sync; sourceTree = ""; }; @@ -183,8 +179,6 @@ }; FEE084F22EC172080045228E /* Schemas */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = Schemas; sourceTree = ""; }; @@ -364,9 +358,6 @@ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ); isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( @@ -473,7 +464,7 @@ ); mainGroup = 97C146E51CF9000F007C117D; packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */, FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */, ); @@ -528,10 +519,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; @@ -561,10 +556,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; @@ -758,7 +757,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; NEW_SETTING = ""; SDKROOT = iphoneos; @@ -777,7 +776,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 240; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -786,7 +785,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.121.0; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.profile; PRODUCT_NAME = "Immich-Profile"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -847,7 +846,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; NEW_SETTING = ""; ONLY_ACTIVE_ARCH = YES; @@ -901,7 +900,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; NEW_SETTING = ""; SDKROOT = iphoneos; @@ -922,7 +921,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 240; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -931,7 +930,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.121.0; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = app.futo.immich.debug; PRODUCT_NAME = "Immich-Debug"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -951,7 +950,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 240; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = 2W7AC6T8T5; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -960,7 +959,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.121.0; + MARKETING_VERSION = 3.0.0; PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich; PRODUCT_NAME = Immich; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1262,7 +1261,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; @@ -1307,17 +1306,7 @@ package = FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */; productName = StructuredFieldValues; }; - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { - isa = XCSwiftPackageProductDependency; - productName = FlutterGeneratedPluginSwiftPackage; - }; /* End XCSwiftPackageProductDependency section */ -/* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; - }; -/* End XCLocalSwiftPackageReference section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index 0ca810438e..0ff269bf41 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -1,205 +1,206 @@ - - AppGroupId - $(CUSTOM_GROUP_ID) - BGTaskSchedulerPermittedIdentifiers - - app.alextran.immich.background.refreshUpload - app.alextran.immich.background.processingUpload - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - ${PRODUCT_NAME} - CFBundleDocumentTypes - - - CFBundleTypeName - ShareHandler - LSHandlerRank - Alternate - LSItemContentTypes - - public.file-url - public.image - public.text - public.movie - public.url - public.data - - - - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleLocalizations - - en - ar - ca - cs - da - de - es - fi - fr - he - hi - hu - it - ja - ko - lv - mn - nb - nl - pl - pt - ro - ru - sk - sl - sr - sv - th - uk - vi - zh - - CFBundleName - immich_mobile - CFBundlePackageType - APPL - CFBundleShortVersionString - 3.0.0 - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - Share Extension - CFBundleURLSchemes - - ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER) - - - - CFBundleTypeRole - Editor - CFBundleURLName - Deep Link - CFBundleURLSchemes - - immich - - - - CFBundleVersion - 240 - ITSAppUsesNonExemptEncryption - - LSApplicationQueriesSchemes - - https - - LSRequiresIPhoneOS - - LSSupportsOpeningDocumentsInPlace - No - MGLMapboxMetricsEnabledSettingShownInApp - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - NSBonjourServices - - _googlecast._tcp - _CC1AD845._googlecast._tcp - - NSCameraUsageDescription - We need to access the camera to let you take beautiful video using this app - NSFaceIDUsageDescription - We need to use FaceID to allow access to your locked folder - NSLocalNetworkUsageDescription - We need local network permission to connect to the local server using IP address and allow the casting feature to work - NSLocationAlwaysAndWhenInUseUsageDescription - We require this permission to access the local WiFi name for background upload mechanism - NSLocationUsageDescription - We require this permission to access the local WiFi name - NSLocationWhenInUseUsageDescription - We require this permission to access the local WiFi name - NSMicrophoneUsageDescription - We need to access the microphone to let you take beautiful video using this app - NSPhotoLibraryAddUsageDescription - We need to manage backup your photos album - NSPhotoLibraryUsageDescription - We need to manage backup your photos album - NSUserActivityTypes - - INSendMessageIntent - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneClassName - UIWindowScene - UISceneConfigurationName - flutter - UISceneDelegateClassName - FlutterSceneDelegate - UISceneStoryboardFile - Main - - - - - UIApplicationSupportsIndirectInputEvents - - UIBackgroundModes - - fetch - processing - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIStatusBarHidden - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - io.flutter.embedded_views_preview - - - + + AppGroupId + $(CUSTOM_GROUP_ID) + BGTaskSchedulerPermittedIdentifiers + + app.alextran.immich.background.refreshUpload + app.alextran.immich.background.processingUpload + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleDocumentTypes + + + CFBundleTypeName + ShareHandler + LSHandlerRank + Alternate + LSItemContentTypes + + public.file-url + public.image + public.text + public.movie + public.url + public.data + + + + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLocalizations + + en + ar + ca + cs + da + de + es + fi + fr + he + hi + hu + it + ja + ko + lv + mn + nb + nl + pl + pt + ro + ru + sk + sl + sr + sv + th + uk + vi + zh + + CFBundleName + immich_mobile + CFBundlePackageType + APPL + CFBundleShortVersionString + 3.0.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + Share Extension + CFBundleURLSchemes + + ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER) + + + + CFBundleTypeRole + Editor + CFBundleURLName + Deep Link + CFBundleURLSchemes + + immich + + + + CFBundleVersion + 4 + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + https + + LSRequiresIPhoneOS + + LSSupportsOpeningDocumentsInPlace + No + MGLMapboxMetricsEnabledSettingShownInApp + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSBonjourServices + + _googlecast._tcp + _CC1AD845._googlecast._tcp + + NSCameraUsageDescription + We need to access the camera to let you take beautiful video using this app + NSFaceIDUsageDescription + We need to use FaceID to allow access to your locked folder + NSLocalNetworkUsageDescription + We need local network permission to connect to the local server using IP address and + allow the casting feature to work + NSLocationAlwaysAndWhenInUseUsageDescription + We require this permission to access the local WiFi name for background upload mechanism + NSLocationUsageDescription + We require this permission to access the local WiFi name + NSLocationWhenInUseUsageDescription + We require this permission to access the local WiFi name + NSMicrophoneUsageDescription + We need to access the microphone to let you take beautiful video using this app + NSPhotoLibraryAddUsageDescription + We need to manage backup your photos album + NSPhotoLibraryUsageDescription + We need to manage backup your photos album + NSUserActivityTypes + + INSendMessageIntent + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + FlutterSceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + fetch + processing + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + io.flutter.embedded_views_preview + + + \ No newline at end of file diff --git a/mobile/lib/constants/locales.dart b/mobile/lib/constants/locales.dart index 1909afb19d..3082a1a0dd 100644 --- a/mobile/lib/constants/locales.dart +++ b/mobile/lib/constants/locales.dart @@ -5,6 +5,7 @@ const Map locales = { 'English (en)': Locale('en'), // Additional locales 'Arabic (ar)': Locale('ar'), + 'Basque (eu)': Locale('eu'), 'Bosnian (bl)': Locale('bn'), 'Brazilian Portuguese (pt_BR)': Locale('pt', 'BR'), 'Bulgarian (bg)': Locale('bg'), diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 75f1c2221a..f576a7c63c 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -263,6 +263,7 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve child: MaterialApp.router( title: 'Immich', debugShowCheckedModeBanner: true, + scaffoldMessengerKey: scaffoldMessengerKey, localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, diff --git a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart index f999e7671f..b0616589f1 100644 --- a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart +++ b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart @@ -19,6 +19,11 @@ import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; import 'package:logging/logging.dart'; +final backupAlbumCountProvider = FutureProvider.autoDispose((ref) async { + await ref.read(backupAlbumProvider.notifier).getAll(); + return ref.read(backupAlbumProvider).length; +}); + @RoutePage() class DriftBackupAlbumSelectionPage extends ConsumerStatefulWidget { const DriftBackupAlbumSelectionPage({super.key}); @@ -44,7 +49,6 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState p.totalCount)); } @@ -79,6 +83,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState 600) { - return _AlbumSelectionGrid(filteredAlbums: filteredAlbums, searchQuery: _searchQuery); - } else { - return _AlbumSelectionList(filteredAlbums: filteredAlbums, searchQuery: _searchQuery); - } - }, - ), + if (filteredAlbums.isEmpty) + SliverToBoxAdapter( + child: Center( + child: _searchQuery.isNotEmpty + ? Padding( + padding: const EdgeInsets.all(24.0), + child: Text('album_search_not_found'.t(context: context)), + ) + : isLoading + ? const CircularProgressIndicator() + : Padding( + padding: const EdgeInsets.all(24.0), + child: Text('no_albums_found'.t(context: context)), + ), + ), + ) + else + SliverLayoutBuilder( + builder: (context, constraints) { + if (constraints.crossAxisExtent > 600) { + return _AlbumSelectionGrid(filteredAlbums: filteredAlbums); + } else { + return _AlbumSelectionList(filteredAlbums: filteredAlbums); + } + }, + ), ], ), if (_handleLinkedAlbumFuture != null) @@ -291,27 +313,11 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState filteredAlbums; - final String searchQuery; - const _AlbumSelectionList({required this.filteredAlbums, required this.searchQuery}); + const _AlbumSelectionList({required this.filteredAlbums}); @override Widget build(BuildContext context) { - if (filteredAlbums.isEmpty && searchQuery.isNotEmpty) { - return SliverToBoxAdapter( - child: Center( - child: Padding( - padding: const EdgeInsets.all(24.0), - child: Text('album_search_not_found'.t(context: context)), - ), - ), - ); - } - - if (filteredAlbums.isEmpty) { - return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator())); - } - return SliverPadding( padding: const EdgeInsets.symmetric(vertical: 12.0), sliver: SliverList( @@ -325,27 +331,11 @@ class _AlbumSelectionList extends StatelessWidget { class _AlbumSelectionGrid extends StatelessWidget { final List filteredAlbums; - final String searchQuery; - const _AlbumSelectionGrid({required this.filteredAlbums, required this.searchQuery}); + const _AlbumSelectionGrid({required this.filteredAlbums}); @override Widget build(BuildContext context) { - if (filteredAlbums.isEmpty && searchQuery.isNotEmpty) { - return SliverToBoxAdapter( - child: Center( - child: Padding( - padding: const EdgeInsets.all(24.0), - child: Text('album_search_not_found'.t(context: context)), - ), - ), - ); - } - - if (filteredAlbums.isEmpty) { - return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator())); - } - return SliverPadding( padding: const EdgeInsets.all(12.0), sliver: SliverGrid.builder( diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index de6fda5773..2f1328d7a1 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -355,14 +355,14 @@ class SplashScreenPageState extends ConsumerState { onError: (exception) => { log.severe('Failed to update auth info with access token: $accessToken'), ref.read(authProvider.notifier).logout(), - context.replaceRoute(const LoginRoute()), + context.router.replaceAll([const LoginRoute()]), }, ), ); } else { log.severe('Missing crucial offline login info - Logging out completely'); unawaited(ref.read(authProvider.notifier).logout()); - unawaited(context.replaceRoute(const LoginRoute())); + unawaited(context.router.replaceAll([const LoginRoute()])); return; } diff --git a/mobile/lib/pages/library/partner/partner.page.dart b/mobile/lib/pages/library/partner/partner.page.dart index 0d9e8f95bd..7274b8a14e 100644 --- a/mobile/lib/pages/library/partner/partner.page.dart +++ b/mobile/lib/pages/library/partner/partner.page.dart @@ -1,23 +1,13 @@ import 'package:auto_route/auto_route.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/presentation/actions/action.widget.dart'; +import 'package:immich_mobile/presentation/actions/partner.action.dart'; import 'package:immich_mobile/presentation/widgets/people/partner_user_avatar.widget.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; - -@visibleForTesting -final candidatesStateProvider = StreamProvider.autoDispose>((ref) { - final currentUser = ref.watch(currentUserProvider); - // TODO: Refactor with a route guard to avoid this check in every provider - if (currentUser == null) { - return const Stream.empty(); - } - return ref.watch(partnerServiceProvider).getCandidates(currentUser.id); -}); @visibleForTesting final partnersStateProvider = StreamProvider.autoDispose>((ref) { @@ -30,28 +20,6 @@ final partnersStateProvider = StreamProvider.autoDispose>((ref return ref.watch(partnerServiceProvider).search(currentUser.id, .sharedBy); }); -Future _addPartner(BuildContext context, WidgetRef ref) async { - final selected = await showDialog(context: context, builder: (_) => const PartnerSelectionDialog()); - final currentUser = ref.read(currentUserProvider); - if (selected != null && currentUser != null) { - await ref.read(partnerServiceProvider).create(sharedById: currentUser.id, sharedWithId: selected.id); - } -} - -Future _removePartner(BuildContext context, WidgetRef ref, Partner partner) => showDialog( - context: context, - builder: (_) => ConfirmDialog( - title: "stop_photo_sharing", - content: context.t.partner_page_stop_sharing_content(partner: partner.name), - onOk: () { - final currentUser = ref.read(currentUserProvider); - if (currentUser != null) { - ref.read(partnerServiceProvider).delete(sharedById: currentUser.id, sharedWithId: partner.id); - } - }, - ), -); - @RoutePage() class PartnerPage extends ConsumerWidget { const PartnerPage({super.key}); @@ -65,20 +33,10 @@ class PartnerPage extends ConsumerWidget { title: Text(context.t.partners), elevation: 0, centerTitle: false, - actions: [ - IconButton( - onPressed: () => _addPartner(context, ref), - icon: const Icon(Icons.person_add), - tooltip: context.t.add_partner, - ), - ], + actions: const [ActionIconButtonWidget(action: PartnerAddAction())], ), body: sharedByAsync.when( - data: (partners) => PartnerSharedByList( - partners: partners.toList(growable: false), - onAdd: () => _addPartner(context, ref), - onRemove: (partner) => _removePartner(context, ref, partner), - ), + data: (partners) => PartnerSharedByList(partners: partners.toList(growable: false)), loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => Center(child: Text(context.t.error_loading_partners(error: error))), ), @@ -87,9 +45,7 @@ class PartnerPage extends ConsumerWidget { } class _EmptyPartners extends StatelessWidget { - const _EmptyPartners({required this.onAdd}); - - final VoidCallback onAdd; + const _EmptyPartners(); @override Widget build(BuildContext context) { @@ -102,13 +58,9 @@ class _EmptyPartners extends StatelessWidget { padding: const .symmetric(vertical: 8), child: Text(context.t.partner_page_empty_message, style: const TextStyle(fontSize: 14)), ), - Align( + const Align( alignment: .center, - child: ElevatedButton.icon( - onPressed: onAdd, - icon: const Icon(Icons.person_add), - label: Text(context.t.add_partner), - ), + child: ActionButtonWidget(action: PartnerAddAction()), ), ], ), @@ -118,16 +70,14 @@ class _EmptyPartners extends StatelessWidget { @visibleForTesting class PartnerSharedByList extends StatelessWidget { - const PartnerSharedByList({super.key, required this.partners, required this.onAdd, required this.onRemove}); + const PartnerSharedByList({super.key, required this.partners}); final List partners; - final VoidCallback onAdd; - final ValueChanged onRemove; @override Widget build(BuildContext context) { if (partners.isEmpty) { - return _EmptyPartners(onAdd: onAdd); + return const _EmptyPartners(); } return ListView.builder( @@ -138,63 +88,11 @@ class PartnerSharedByList extends StatelessWidget { leading: PartnerUserAvatar(userId: partner.id, name: partner.name), title: Text(partner.name), subtitle: Text(partner.email), - trailing: IconButton(icon: const Icon(Icons.person_remove), onPressed: () => onRemove(partner)), + trailing: ActionIconButtonWidget( + action: PartnerRemoveAction(sharedWithId: partner.id, partnerName: partner.name), + ), ); }, ); } } - -@visibleForTesting -class PartnerSelectionDialog extends ConsumerWidget { - const PartnerSelectionDialog({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final candidatesAsync = ref.watch(candidatesStateProvider); - - return SimpleDialog( - title: const Text("partner_page_select_partner").tr(), - children: candidatesAsync.when( - data: (candidates) { - final users = candidates.toList(); - if (users.isEmpty) { - return [ - Padding( - padding: const .symmetric(horizontal: 24, vertical: 8), - child: const Text("partner_page_no_more_users").tr(), - ), - ]; - } - return [ - for (final candidate in users) - SimpleDialogOption( - onPressed: () => Navigator.of(context).pop(candidate), - child: Row( - children: [ - Padding( - padding: const .only(right: 8), - child: PartnerUserAvatar(userId: candidate.id, name: candidate.name), - ), - Text(candidate.name), - ], - ), - ), - ]; - }, - loading: () => const [ - Padding( - padding: .all(24), - child: Center(child: CircularProgressIndicator()), - ), - ], - error: (error, _) => [ - Padding( - padding: const .symmetric(horizontal: 24, vertical: 8), - child: Text(context.t.error_loading_partners(error: error)), - ), - ], - ), - ); - } -} diff --git a/mobile/lib/presentation/actions/action.dart b/mobile/lib/presentation/actions/action.dart new file mode 100644 index 0000000000..5d37706aaa --- /dev/null +++ b/mobile/lib/presentation/actions/action.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; + +class ActionScope { + final BuildContext context; + final WidgetRef ref; + final UserDto authUser; + + const ActionScope({required this.context, required this.ref, required this.authUser}); +} + +abstract class BaseAction { + const BaseAction(); + + IconData get icon; + + String label(ActionScope scope); + + bool isVisible(ActionScope scope) => true; + + Future onAction(ActionScope scope); +} diff --git a/mobile/lib/presentation/actions/action.widget.dart b/mobile/lib/presentation/actions/action.widget.dart new file mode 100644 index 0000000000..0f891abde6 --- /dev/null +++ b/mobile/lib/presentation/actions/action.widget.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/presentation/actions/action.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/utils/error_handler.dart'; +import 'package:immich_ui/immich_ui.dart'; + +class _ActionWidgetScope { + final String label; + final VoidCallback onAction; + + const _ActionWidgetScope({required this.label, required this.onAction}); +} + +class _ActionWidget extends ConsumerWidget { + final BaseAction action; + final Widget Function(_ActionWidgetScope context) builder; + + const _ActionWidget({required this.action, required this.builder}); + + Future _onAction(ActionScope scope) async { + try { + await action.onAction(scope); + } catch (error, stackTrace) { + handleError(scope.context, error, stack: stackTrace, description: 'Action failed: ${action.runtimeType}'); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final authUser = ref.watch(currentUserProvider); + if (authUser == null) { + return const SizedBox.shrink(); + } + + final scope = ActionScope(context: context, ref: ref, authUser: authUser); + if (!action.isVisible(scope)) { + return const SizedBox.shrink(); + } + + return builder(.new(label: action.label(scope), onAction: () => _onAction(scope))); + } +} + +class ActionIconButtonWidget extends StatelessWidget { + final BaseAction action; + final ImmichVariant variant; + + const ActionIconButtonWidget({super.key, required this.action, this.variant = .ghost}); + + @override + Widget build(BuildContext context) => _ActionWidget( + action: action, + builder: (ctx) => ImmichIconButton(icon: action.icon, onPressed: ctx.onAction, variant: variant), + ); +} + +class ActionButtonWidget extends StatelessWidget { + final BaseAction action; + final ImmichVariant variant; + + const ActionButtonWidget({super.key, required this.action, this.variant = .ghost}); + + @override + Widget build(BuildContext context) => _ActionWidget( + action: action, + builder: (ctx) => + ImmichTextButton(labelText: ctx.label, icon: action.icon, onPressed: ctx.onAction, variant: variant), + ); +} + +class ActionColumnButtonWidget extends StatelessWidget { + final BaseAction action; + + const ActionColumnButtonWidget({super.key, required this.action}); + + @override + Widget build(BuildContext context) => _ActionWidget( + action: action, + builder: (ctx) => ImmichColumnButton(icon: action.icon, label: ctx.label, onPressed: ctx.onAction), + ); +} + +class ActionMenuItemWidget extends StatelessWidget { + final BaseAction action; + + const ActionMenuItemWidget({super.key, required this.action}); + + @override + Widget build(BuildContext context) => _ActionWidget( + action: action, + builder: (ctx) => ImmichMenuItem(icon: action.icon, label: ctx.label, onPressed: ctx.onAction), + ); +} diff --git a/mobile/lib/presentation/actions/partner.action.dart b/mobile/lib/presentation/actions/partner.action.dart new file mode 100644 index 0000000000..11fb69ee75 --- /dev/null +++ b/mobile/lib/presentation/actions/partner.action.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/presentation/actions/action.dart'; +import 'package:immich_mobile/presentation/widgets/people/partner_user_avatar.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; + +class PartnerAddAction extends BaseAction { + const PartnerAddAction(); + + @override + IconData get icon => Icons.person_add_rounded; + + @override + String label(ActionScope scope) => scope.context.t.add_partner; + + @override + Future onAction(ActionScope scope) async { + final ActionScope(:context, :ref, :authUser) = scope; + final selected = await showDialog(context: context, builder: (_) => const PartnerSelectionDialog()); + if (selected == null) { + return; + } + + await ref.read(partnerServiceProvider).create(sharedById: authUser.id, sharedWithId: selected.id); + } +} + +class PartnerRemoveAction extends BaseAction { + const PartnerRemoveAction({required this.sharedWithId, required this.partnerName}); + + final String sharedWithId; + final String partnerName; + + @override + IconData get icon => Icons.person_remove_rounded; + + @override + String label(ActionScope scope) => scope.context.t.remove; + + @override + Future onAction(ActionScope scope) async { + final ActionScope(:context, :ref, :authUser) = scope; + + final confirmed = await showDialog( + context: context, + builder: (_) => ConfirmDialog( + title: context.t.stop_photo_sharing, + content: context.t.partner_page_stop_sharing_content(partner: partnerName), + ), + ); + if (confirmed != true) { + return; + } + + await ref.read(partnerServiceProvider).delete(sharedById: authUser.id, sharedWithId: sharedWithId); + } +} + +@visibleForTesting +final candidatesStateProvider = StreamProvider.autoDispose>((ref) { + final currentUser = ref.watch(currentUserProvider); + // TODO: Refactor with a route guard to avoid this check in every provider + if (currentUser == null) { + return const Stream.empty(); + } + return ref.watch(partnerServiceProvider).getCandidates(currentUser.id); +}); + +@visibleForTesting +class PartnerSelectionDialog extends ConsumerWidget { + const PartnerSelectionDialog({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final candidatesAsync = ref.watch(candidatesStateProvider); + + return SimpleDialog( + title: Text(context.t.partner_page_select_partner), + children: candidatesAsync.when( + data: (candidates) { + final users = candidates.toList(); + if (users.isEmpty) { + return [ + Padding( + padding: const .symmetric(horizontal: 24, vertical: 8), + child: Text(context.t.partner_page_no_more_users), + ), + ]; + } + return [ + for (final candidate in users) + SimpleDialogOption( + onPressed: () => Navigator.of(context).pop(candidate), + child: Row( + children: [ + Padding( + padding: const .only(right: 8), + child: PartnerUserAvatar(userId: candidate.id, name: candidate.name), + ), + Text(candidate.name), + ], + ), + ), + ]; + }, + loading: () => const [ + Padding( + padding: .all(24), + child: Center(child: CircularProgressIndicator()), + ), + ], + error: (error, _) => [ + Padding( + padding: const .symmetric(horizontal: 24, vertical: 8), + child: Text(context.t.error_loading_partners(error: error)), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/presentation/pages/drift_locked_folder.page.dart b/mobile/lib/presentation/pages/drift_locked_folder.page.dart index b19e8468ca..9849558c94 100644 --- a/mobile/lib/presentation/pages/drift_locked_folder.page.dart +++ b/mobile/lib/presentation/pages/drift_locked_folder.page.dart @@ -7,6 +7,7 @@ import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; @RoutePage() @@ -34,11 +35,17 @@ class _DriftLockedFolderPageState extends ConsumerState w @override void didChangeAppLifecycleState(AppLifecycleState state) { - if (mounted) { - setState(() { - _showOverlay = state != AppLifecycleState.resumed; - }); + if (!mounted) { + return; } + if (state == AppLifecycleState.paused) { + ref.read(authProvider.notifier).lockPinCode(); + context.navigateTo(const TabShellRoute()); + return; + } + setState(() { + _showOverlay = state != AppLifecycleState.resumed; + }); } @override diff --git a/mobile/lib/presentation/pages/drift_memory.page.dart b/mobile/lib/presentation/pages/drift_memory.page.dart index 6919925d55..f601bf8419 100644 --- a/mobile/lib/presentation/pages/drift_memory.page.dart +++ b/mobile/lib/presentation/pages/drift_memory.page.dart @@ -12,6 +12,7 @@ import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/presentation/widgets/memory/memory_bottom_info.widget.dart'; import 'package:immich_mobile/presentation/widgets/memory/memory_card.widget.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:immich_mobile/utils/system_ui.utils.dart'; import 'package:immich_mobile/widgets/memories/memory_epilogue.dart'; import 'package:immich_mobile/widgets/memories/memory_progress_indicator.dart'; @@ -49,7 +50,7 @@ class DriftMemoryPage extends HookConsumerWidget { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); return () { // Clean up to normal edge to edge when we are done - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + restoreEdgeToEdge(); }; }); @@ -328,7 +329,7 @@ class DriftMemoryPage extends HookConsumerWidget { // turn off full screen mode here // https://github.com/Milad-Akarie/auto_route_library/issues/1799 context.maybePop(); - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + restoreEdgeToEdge(); }, shape: const CircleBorder(), color: Colors.white.withValues(alpha: 0.2), diff --git a/mobile/lib/presentation/pages/drift_slideshow.page.dart b/mobile/lib/presentation/pages/drift_slideshow.page.dart index 4fae0709aa..596b6fdf36 100644 --- a/mobile/lib/presentation/pages/drift_slideshow.page.dart +++ b/mobile/lib/presentation/pages/drift_slideshow.page.dart @@ -19,6 +19,7 @@ import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart' import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/system_ui.utils.dart'; import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; import 'package:immich_mobile/widgets/photo_view/photo_view.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; @@ -76,7 +77,7 @@ class _DriftSlideshowPageState extends ConsumerState with Si _pageController.dispose(); _crossfadeController.dispose(); unawaited(WakelockPlus.disable()); - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + unawaited(restoreEdgeToEdge()); super.dispose(); } @@ -255,7 +256,7 @@ class _DriftSlideshowPageState extends ConsumerState with Si } void _onTapUp() async { - await SystemChrome.setEnabledSystemUIMode(_showAppBar ? SystemUiMode.immersive : SystemUiMode.edgeToEdge); + await (_showAppBar ? SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive) : restoreEdgeToEdge()); WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart index 64f8a3f332..8f65822fc4 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart @@ -24,6 +24,7 @@ import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/view_intent/view_intent_current.provider.dart'; +import 'package:immich_mobile/utils/system_ui.utils.dart'; import 'package:immich_mobile/widgets/photo_view/photo_view.dart'; @RoutePage() @@ -131,7 +132,7 @@ class _AssetViewerState extends ConsumerState { _reloadSubscription?.cancel(); _stackChildrenKeepAlive?.close(); - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + unawaited(restoreEdgeToEdge()); super.dispose(); } @@ -275,10 +276,8 @@ class _AssetViewerState extends ConsumerState { } void _setSystemUIMode(bool controls, bool details) { - final mode = !controls || (CurrentPlatform.isIOS && details) - ? SystemUiMode.immersiveSticky - : SystemUiMode.edgeToEdge; - unawaited(SystemChrome.setEnabledSystemUIMode(mode)); + final immersive = !controls || (CurrentPlatform.isIOS && details); + unawaited(immersive ? SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky) : restoreEdgeToEdge()); } @override diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index bb1877cbd7..ad0940b776 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/permission.provider.dart'; @@ -115,6 +116,7 @@ class AppLifeCycleNotifier extends StateNotifier { _safeRun(backgroundManager.syncLocal(full: CurrentPlatform.isAndroid ? true : false), "syncLocal"), _safeRun(backgroundManager.syncRemote().then((success) => syncSuccess = success), "syncRemote"), ]); + _ref.invalidate(driftMemoryFutureProvider); if (syncSuccess) { await Future.wait([ _safeRun(backgroundManager.hashAssets(), "hashAssets").then((_) { diff --git a/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart b/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart index 5c9f3c92a3..6808860ffc 100644 --- a/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart +++ b/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; @@ -77,18 +79,17 @@ class AssetViewerState { } class AssetViewerStateNotifier extends Notifier { + StreamSubscription? _assetSubscription; + @override AssetViewerState build() { - ref.listen(_watchedCurrentAssetProvider, (_, next) { - final updated = next.valueOrNull; - if (updated != null) { - state = state.copyWith(currentAsset: updated); - } - }); + ref.onDispose(() => _assetSubscription?.cancel()); return const AssetViewerState(); } void reset() { + _assetSubscription?.cancel(); + _assetSubscription = null; state = const AssetViewerState(); } @@ -97,6 +98,16 @@ class AssetViewerStateNotifier extends Notifier { return; } state = state.copyWith(currentAsset: asset, stackIndex: 0, showingOcr: false); + _watchCurrentAsset(asset); + } + + void _watchCurrentAsset(BaseAsset asset) { + _assetSubscription?.cancel(); + _assetSubscription = ref.read(assetServiceProvider).watchAsset(asset).listen((updated) { + if (updated != null) { + state = state.copyWith(currentAsset: updated); + } + }); } void setOpacity(double opacity) { @@ -150,12 +161,3 @@ class AssetViewerStateNotifier extends Notifier { } final assetViewerProvider = NotifierProvider(AssetViewerStateNotifier.new); - -final _watchedCurrentAssetProvider = StreamProvider((ref) { - ref.watch(assetViewerProvider.select((s) => s.currentAsset?.heroTag)); - final asset = ref.read(assetViewerProvider).currentAsset; - if (asset == null) { - return const Stream.empty(); - } - return ref.read(assetServiceProvider).watchAsset(asset); -}); diff --git a/mobile/lib/providers/infrastructure/memory.provider.dart b/mobile/lib/providers/infrastructure/memory.provider.dart index 91495bb5ee..3dd32454b8 100644 --- a/mobile/lib/providers/infrastructure/memory.provider.dart +++ b/mobile/lib/providers/infrastructure/memory.provider.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/memory.model.dart'; import 'package:immich_mobile/domain/services/memory.service.dart'; @@ -19,6 +21,11 @@ final driftMemoryFutureProvider = FutureProvider.autoDispose>( return const []; } + final now = DateTime.now(); + final nextMidnight = DateTime(now.year, now.month, now.day + 1); + final timer = Timer(nextMidnight.difference(now) + const Duration(seconds: 5), ref.invalidateSelf); + ref.onDispose(timer.cancel); + final service = ref.watch(driftMemoryServiceProvider); return service.getMemoryLane(userId); }); diff --git a/mobile/lib/providers/view_intent/view_intent_handler_android.dart b/mobile/lib/providers/view_intent/view_intent_handler_android.dart index efa1975737..5abee2e5e2 100644 --- a/mobile/lib/providers/view_intent/view_intent_handler_android.dart +++ b/mobile/lib/providers/view_intent/view_intent_handler_android.dart @@ -1,5 +1,6 @@ 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/services/asset.service.dart'; diff --git a/mobile/lib/utils/error_handler.dart b/mobile/lib/utils/error_handler.dart new file mode 100644 index 0000000000..24387b8002 --- /dev/null +++ b/mobile/lib/utils/error_handler.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; + +import 'package:flutter/widgets.dart'; +import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; +import 'package:immich_ui/immich_ui.dart'; +import 'package:openapi/api.dart'; +// ignore: depend_on_referenced_packages +import 'package:stack_trace/stack_trace.dart'; + +void handleError(BuildContext context, Object error, {StackTrace? stack, String? description}) { + String? stackTrace; + if (stack != null) { + final trace = Trace.from(stack); + final clean = trace.foldFrames( + (frame) => frame.package == 'flutter' || frame.package == 'flutter_test' || frame.isCore, + terse: true, + ); + stackTrace = clean.toString(); + } + + dPrint( + () => '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 = context.t.login_form_server_error; + } else { + message = context.t.scaffold_body_error_occurred; + } + + snackbar.error(message); +} + +@visibleForTesting +String? serverErrorMessage(Object error) { + if (error is! ApiException || error.innerException != null || error.message == null) { + return null; + } + + try { + final body = jsonDecode(error.message!); + if (body is Map && body['message'] != null) { + final message = body['message']; + return message is List ? message.join(', ') : message.toString(); + } + } catch (_) { + // The body was not JSON; fall back to the raw payload below. + } + return error.message; +} + +@visibleForTesting +bool isConnectionError(Object error) => error is ApiException && error.innerException != null; diff --git a/mobile/lib/utils/system_ui.utils.dart b/mobile/lib/utils/system_ui.utils.dart new file mode 100644 index 0000000000..0d6d65082f --- /dev/null +++ b/mobile/lib/utils/system_ui.utils.dart @@ -0,0 +1,14 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; + +/// Restore the system bars and return to edge-to-edge layout. +/// +/// On Android 15+/API 36 edge-to-edge is enforced, so calling +/// setEnabledSystemUIMode(edgeToEdge) does NOT re-show bars that an immersive +/// mode (immersive / immersiveSticky) previously hid. Explicitly request all +/// overlays first, then return to edge-to-edge layout. +Future restoreEdgeToEdge() async { + await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); + await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); +} diff --git a/mobile/lib/utils/version_compatibility.dart b/mobile/lib/utils/version_compatibility.dart index b997b8524c..55dbdc6269 100644 --- a/mobile/lib/utils/version_compatibility.dart +++ b/mobile/lib/utils/version_compatibility.dart @@ -1,7 +1,16 @@ -String? getVersionCompatibilityMessage(int _, int appMinor, int _, int serverMinor) { +import 'package:immich_mobile/utils/semver.dart'; + +String? getVersionCompatibilityMessage(SemVer serverVersion, SemVer appVersion) { // Add latest compat info up top - if (serverMinor < 106 && appMinor >= 106) { - return 'Your app minor version is not compatible with the server! Please update your server to version v1.106.0 or newer to login'; + + // ensure mobile app major version is not behind server major version + if (appVersion.major < serverVersion.major) { + return 'Your mobile app version is not compatible with the server! Please update your mobile app to the latest version.'; + } + + // ensure mobile app major version is not ahead of server major version by more than 1 major version + if (appVersion.major > serverVersion.major + 1) { + return 'Your server version is not compatible with the mobile app! Please update your server to the latest version.'; } return null; diff --git a/mobile/lib/widgets/common/confirm_dialog.dart b/mobile/lib/widgets/common/confirm_dialog.dart index 153c124595..9c3f4344c6 100644 --- a/mobile/lib/widgets/common/confirm_dialog.dart +++ b/mobile/lib/widgets/common/confirm_dialog.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; class ConfirmDialog extends StatelessWidget { - final Function onOk; + final Function? onOk; final String title; final String content; final String cancel; @@ -11,7 +11,7 @@ class ConfirmDialog extends StatelessWidget { const ConfirmDialog({ super.key, - required this.onOk, + this.onOk, required this.title, required this.content, this.cancel = "cancel", @@ -21,7 +21,7 @@ class ConfirmDialog extends StatelessWidget { @override Widget build(BuildContext context) { void onOkPressed() { - onOk(); + onOk?.call(); context.pop(true); } diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index 7615218159..9adf15b448 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -26,6 +26,7 @@ import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/repositories/permission.repository.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/provider_utils.dart'; +import 'package:immich_mobile/utils/semver.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/utils/version_compatibility.dart'; import 'package:immich_mobile/widgets/common/immich_logo.dart'; @@ -88,18 +89,9 @@ class LoginForm extends HookConsumerWidget { checkVersionMismatch() async { try { final packageInfo = await PackageInfo.fromPlatform(); - final appVersion = packageInfo.version; - final appMajorVersion = int.parse(appVersion.split('.')[0]); - final appMinorVersion = int.parse(appVersion.split('.')[1]); - final serverMajorVersion = serverInfo.serverVersion.major; - final serverMinorVersion = serverInfo.serverVersion.minor; - - warningMessage.value = getVersionCompatibilityMessage( - appMajorVersion, - appMinorVersion, - serverMajorVersion, - serverMinorVersion, - ); + final appSemVer = SemVer.fromString(packageInfo.version); + final serverSemVer = serverInfo.serverVersion; + warningMessage.value = getVersionCompatibilityMessage(appSemVer, serverSemVer); } catch (error) { warningMessage.value = 'Error checking version compatibility'; } diff --git a/mobile/lib/widgets/settings/notification_setting.dart b/mobile/lib/widgets/settings/notification_setting.dart index cbef5ea109..8b7c652925 100644 --- a/mobile/lib/widgets/settings/notification_setting.dart +++ b/mobile/lib/widgets/settings/notification_setting.dart @@ -48,6 +48,14 @@ class NotificationSetting extends HookConsumerWidget { showPermissionsDialog(); } }), + ) + else + SettingsButtonListTile( + icon: Icons.notifications_active_outlined, + title: 'notification_enabled_list_tile_title'.tr(), + subtileText: 'notification_enabled_list_tile_content'.tr(), + buttonText: 'notification_enabled_list_tile_open_button'.tr(), + onButtonTap: () => openAppSettings(), ), ]; diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index c8ac38a9fe..15a306b9dd 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -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.1 +- API version: 3.0.0-rc.2 - Generator version: 7.22.0 - Build package: org.openapitools.codegen.languages.DartClientCodegen diff --git a/mobile/openapi/lib/api/assets_api.dart b/mobile/openapi/lib/api/assets_api.dart index e3bed71cd3..fa49d382b9 100644 --- a/mobile/openapi/lib/api/assets_api.dart +++ b/mobile/openapi/lib/api/assets_api.dart @@ -982,7 +982,9 @@ class AssetsApi { /// * [String] key: /// /// * [String] slug: - Future getMediaPlaylistWithHttpInfo(String id, String sessionId, int variantIndex, { String? key, String? slug, Future? abortTrigger, }) async { + /// + /// * [num] xImmichHlsPos: + Future getMediaPlaylistWithHttpInfo(String id, String sessionId, int variantIndex, { String? key, String? slug, num? xImmichHlsPos, Future? abortTrigger, }) async { // ignore: prefer_const_declarations final apiPath = r'/assets/{id}/video/stream/{sessionId}/{variantIndex}/playlist.m3u8' .replaceAll('{id}', id) @@ -1003,6 +1005,10 @@ class AssetsApi { queryParams.addAll(_queryParams('', 'slug', slug)); } + if (xImmichHlsPos != null) { + headerParams[r'x-immich-hls-pos'] = parameterToString(xImmichHlsPos); + } + const contentTypes = []; @@ -1033,8 +1039,10 @@ class AssetsApi { /// * [String] key: /// /// * [String] slug: - Future getMediaPlaylist(String id, String sessionId, int variantIndex, { String? key, String? slug, Future? abortTrigger, }) async { - final response = await getMediaPlaylistWithHttpInfo(id, sessionId, variantIndex, key: key, slug: slug, abortTrigger: abortTrigger,); + /// + /// * [num] xImmichHlsPos: + Future getMediaPlaylist(String id, String sessionId, int variantIndex, { String? key, String? slug, num? xImmichHlsPos, Future? abortTrigger, }) async { + final response = await getMediaPlaylistWithHttpInfo(id, sessionId, variantIndex, key: key, slug: slug, xImmichHlsPos: xImmichHlsPos, abortTrigger: abortTrigger,); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/model/activity_response_dto.dart b/mobile/openapi/lib/model/activity_response_dto.dart index 8a8f807260..15d22e56cd 100644 --- a/mobile/openapi/lib/model/activity_response_dto.dart +++ b/mobile/openapi/lib/model/activity_response_dto.dart @@ -70,7 +70,7 @@ class ActivityResponseDto { final value = this.comment.value; json[r'comment'] = value; } - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); json[r'id'] = this.id; @@ -90,7 +90,7 @@ class ActivityResponseDto { return ActivityResponseDto( assetId: mapValueOfType(json, r'assetId'), comment: json.containsKey(r'comment') ? Optional.present(mapValueOfType(json, r'comment')) : const Optional.absent(), - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, id: mapValueOfType(json, r'id')!, type: ReactionType.fromJson(json[r'type'])!, user: UserResponseDto.fromJson(json[r'user'])!, diff --git a/mobile/openapi/lib/model/api_key_response_dto.dart b/mobile/openapi/lib/model/api_key_response_dto.dart index 79099188a3..4005ca9f12 100644 --- a/mobile/openapi/lib/model/api_key_response_dto.dart +++ b/mobile/openapi/lib/model/api_key_response_dto.dart @@ -57,13 +57,13 @@ class ApiKeyResponseDto { Map toJson() { final json = {}; - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); json[r'id'] = this.id; json[r'name'] = this.name; json[r'permissions'] = this.permissions; - json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.updatedAt.millisecondsSinceEpoch : this.updatedAt.toUtc().toIso8601String(); return json; @@ -78,11 +78,11 @@ class ApiKeyResponseDto { final json = value.cast(); return ApiKeyResponseDto( - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, id: mapValueOfType(json, r'id')!, name: mapValueOfType(json, r'name')!, permissions: Permission.listFromJson(json[r'permissions']), - updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ); } return null; diff --git a/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart b/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart index ed7508b18e..572c99c11e 100644 --- a/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart +++ b/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart @@ -31,7 +31,7 @@ class AssetBulkUploadCheckResult { /// Optional assetId; - /// Asset ID + /// Client-side identifier echoed from the request to match results to inputs String id; /// Whether existing asset is trashed diff --git a/mobile/openapi/lib/model/asset_metadata_bulk_response_dto.dart b/mobile/openapi/lib/model/asset_metadata_bulk_response_dto.dart index 3e16ed8721..9afb8dda3f 100644 --- a/mobile/openapi/lib/model/asset_metadata_bulk_response_dto.dart +++ b/mobile/openapi/lib/model/asset_metadata_bulk_response_dto.dart @@ -53,7 +53,7 @@ class AssetMetadataBulkResponseDto { final json = {}; json[r'assetId'] = this.assetId; json[r'key'] = this.key; - json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.updatedAt.millisecondsSinceEpoch : this.updatedAt.toUtc().toIso8601String(); json[r'value'] = this.value; @@ -71,7 +71,7 @@ class AssetMetadataBulkResponseDto { return AssetMetadataBulkResponseDto( assetId: mapValueOfType(json, r'assetId')!, key: mapValueOfType(json, r'key')!, - updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, value: mapCastOfType(json, r'value')!, ); } diff --git a/mobile/openapi/lib/model/asset_metadata_response_dto.dart b/mobile/openapi/lib/model/asset_metadata_response_dto.dart index d3562f5a48..6b6a255ae4 100644 --- a/mobile/openapi/lib/model/asset_metadata_response_dto.dart +++ b/mobile/openapi/lib/model/asset_metadata_response_dto.dart @@ -46,7 +46,7 @@ class AssetMetadataResponseDto { Map toJson() { final json = {}; json[r'key'] = this.key; - json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.updatedAt.millisecondsSinceEpoch : this.updatedAt.toUtc().toIso8601String(); json[r'value'] = this.value; @@ -63,7 +63,7 @@ class AssetMetadataResponseDto { return AssetMetadataResponseDto( key: mapValueOfType(json, r'key')!, - updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, value: mapCastOfType(json, r'value')!, ); } diff --git a/mobile/openapi/lib/model/create_profile_image_response_dto.dart b/mobile/openapi/lib/model/create_profile_image_response_dto.dart index c6ec0d94a0..68700400e4 100644 --- a/mobile/openapi/lib/model/create_profile_image_response_dto.dart +++ b/mobile/openapi/lib/model/create_profile_image_response_dto.dart @@ -45,7 +45,7 @@ class CreateProfileImageResponseDto { Map toJson() { final json = {}; - json[r'profileChangedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'profileChangedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.profileChangedAt.millisecondsSinceEpoch : this.profileChangedAt.toUtc().toIso8601String(); json[r'profileImagePath'] = this.profileImagePath; @@ -62,7 +62,7 @@ class CreateProfileImageResponseDto { final json = value.cast(); return CreateProfileImageResponseDto( - profileChangedAt: mapDateTime(json, r'profileChangedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + profileChangedAt: mapDateTime(json, r'profileChangedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, profileImagePath: mapValueOfType(json, r'profileImagePath')!, userId: mapValueOfType(json, r'userId')!, ); diff --git a/mobile/openapi/lib/model/library_response_dto.dart b/mobile/openapi/lib/model/library_response_dto.dart index e52a752656..128469839a 100644 --- a/mobile/openapi/lib/model/library_response_dto.dart +++ b/mobile/openapi/lib/model/library_response_dto.dart @@ -85,7 +85,7 @@ class LibraryResponseDto { Map toJson() { final json = {}; json[r'assetCount'] = this.assetCount; - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); json[r'exclusionPatterns'] = this.exclusionPatterns; @@ -94,13 +94,13 @@ class LibraryResponseDto { json[r'name'] = this.name; json[r'ownerId'] = this.ownerId; if (this.refreshedAt != null) { - json[r'refreshedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'refreshedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.refreshedAt!.millisecondsSinceEpoch : this.refreshedAt!.toUtc().toIso8601String(); } else { json[r'refreshedAt'] = null; } - json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.updatedAt.millisecondsSinceEpoch : this.updatedAt.toUtc().toIso8601String(); return json; @@ -116,7 +116,7 @@ class LibraryResponseDto { return LibraryResponseDto( assetCount: mapValueOfType(json, r'assetCount')!, - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, exclusionPatterns: json[r'exclusionPatterns'] is Iterable ? (json[r'exclusionPatterns'] as Iterable).cast().toList(growable: false) : const [], @@ -126,8 +126,8 @@ class LibraryResponseDto { : const [], name: mapValueOfType(json, r'name')!, ownerId: mapValueOfType(json, r'ownerId')!, - refreshedAt: mapDateTime(json, r'refreshedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + refreshedAt: mapDateTime(json, r'refreshedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ); } return null; diff --git a/mobile/openapi/lib/model/memory_create_dto.dart b/mobile/openapi/lib/model/memory_create_dto.dart index a7be9bf70d..d032ff0c38 100644 --- a/mobile/openapi/lib/model/memory_create_dto.dart +++ b/mobile/openapi/lib/model/memory_create_dto.dart @@ -104,7 +104,7 @@ class MemoryCreateDto { json[r'data'] = this.data; if (this.hideAt.isPresent) { final value = this.hideAt.value; - json[r'hideAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'hideAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -112,18 +112,18 @@ class MemoryCreateDto { final value = this.isSaved.value; json[r'isSaved'] = value; } - json[r'memoryAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'memoryAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.memoryAt.millisecondsSinceEpoch : this.memoryAt.toUtc().toIso8601String(); if (this.seenAt.isPresent) { final value = this.seenAt.value; - json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.showAt.isPresent) { final value = this.showAt.value; - json[r'showAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'showAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -144,11 +144,11 @@ class MemoryCreateDto { ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) : const []) : const Optional.absent(), data: OnThisDayDto.fromJson(json[r'data'])!, - hideAt: json.containsKey(r'hideAt') ? Optional.present(mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + hideAt: json.containsKey(r'hideAt') ? Optional.present(mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), isSaved: json.containsKey(r'isSaved') ? Optional.present(mapValueOfType(json, r'isSaved')) : const Optional.absent(), - memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, - seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - showAt: json.containsKey(r'showAt') ? Optional.present(mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, + seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + showAt: json.containsKey(r'showAt') ? Optional.present(mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), type: MemoryType.fromJson(json[r'type'])!, ); } diff --git a/mobile/openapi/lib/model/memory_response_dto.dart b/mobile/openapi/lib/model/memory_response_dto.dart index 799c692955..41a522c773 100644 --- a/mobile/openapi/lib/model/memory_response_dto.dart +++ b/mobile/openapi/lib/model/memory_response_dto.dart @@ -127,42 +127,42 @@ class MemoryResponseDto { Map toJson() { final json = {}; json[r'assets'] = this.assets; - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); json[r'data'] = this.data; if (this.deletedAt.isPresent) { final value = this.deletedAt.value; - json[r'deletedAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'deletedAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.hideAt.isPresent) { final value = this.hideAt.value; - json[r'hideAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'hideAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } json[r'id'] = this.id; json[r'isSaved'] = this.isSaved; - json[r'memoryAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'memoryAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.memoryAt.millisecondsSinceEpoch : this.memoryAt.toUtc().toIso8601String(); json[r'ownerId'] = this.ownerId; if (this.seenAt.isPresent) { final value = this.seenAt.value; - json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.showAt.isPresent) { final value = this.showAt.value; - json[r'showAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'showAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } json[r'type'] = this.type; - json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.updatedAt.millisecondsSinceEpoch : this.updatedAt.toUtc().toIso8601String(); return json; @@ -178,18 +178,18 @@ class MemoryResponseDto { return MemoryResponseDto( assets: AssetResponseDto.listFromJson(json[r'assets']), - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, data: OnThisDayDto.fromJson(json[r'data'])!, - deletedAt: json.containsKey(r'deletedAt') ? Optional.present(mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - hideAt: json.containsKey(r'hideAt') ? Optional.present(mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + deletedAt: json.containsKey(r'deletedAt') ? Optional.present(mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + hideAt: json.containsKey(r'hideAt') ? Optional.present(mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, isSaved: mapValueOfType(json, r'isSaved')!, - memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ownerId: mapValueOfType(json, r'ownerId')!, - seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - showAt: json.containsKey(r'showAt') ? Optional.present(mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + showAt: json.containsKey(r'showAt') ? Optional.present(mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), type: MemoryType.fromJson(json[r'type'])!, - updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ); } return null; diff --git a/mobile/openapi/lib/model/memory_update_dto.dart b/mobile/openapi/lib/model/memory_update_dto.dart index e39506b78e..43e879745c 100644 --- a/mobile/openapi/lib/model/memory_update_dto.dart +++ b/mobile/openapi/lib/model/memory_update_dto.dart @@ -69,13 +69,13 @@ class MemoryUpdateDto { } if (this.memoryAt.isPresent) { final value = this.memoryAt.value; - json[r'memoryAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'memoryAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.seenAt.isPresent) { final value = this.seenAt.value; - json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'seenAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -92,8 +92,8 @@ class MemoryUpdateDto { return MemoryUpdateDto( isSaved: json.containsKey(r'isSaved') ? Optional.present(mapValueOfType(json, r'isSaved')) : const Optional.absent(), - memoryAt: json.containsKey(r'memoryAt') ? Optional.present(mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + memoryAt: json.containsKey(r'memoryAt') ? Optional.present(mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + seenAt: json.containsKey(r'seenAt') ? Optional.present(mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart index 2fd717b4f0..b0afd292ee 100644 --- a/mobile/openapi/lib/model/metadata_search_dto.dart +++ b/mobile/openapi/lib/model/metadata_search_dto.dart @@ -489,13 +489,13 @@ class MetadataSearchDto { } if (this.createdAfter.isPresent) { final value = this.createdAfter.value; - json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.createdBefore.isPresent) { final value = this.createdBefore.value; - json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -593,13 +593,13 @@ class MetadataSearchDto { } if (this.takenAfter.isPresent) { final value = this.takenAfter.value; - json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.takenBefore.isPresent) { final value = this.takenBefore.value; - json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -609,13 +609,13 @@ class MetadataSearchDto { } if (this.trashedAfter.isPresent) { final value = this.trashedAfter.value; - json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.trashedBefore.isPresent) { final value = this.trashedBefore.value; - json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -625,13 +625,13 @@ class MetadataSearchDto { } if (this.updatedAfter.isPresent) { final value = this.updatedAfter.value; - json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.updatedBefore.isPresent) { final value = this.updatedBefore.value; - json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -673,8 +673,8 @@ class MetadataSearchDto { checksum: json.containsKey(r'checksum') ? Optional.present(mapValueOfType(json, r'checksum')) : const Optional.absent(), city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), - createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), encodedVideoPath: json.containsKey(r'encodedVideoPath') ? Optional.present(mapValueOfType(json, r'encodedVideoPath')) : const Optional.absent(), id: json.containsKey(r'id') ? Optional.present(mapValueOfType(json, r'id')) : const Optional.absent(), @@ -702,14 +702,14 @@ class MetadataSearchDto { tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) : const []) : const Optional.absent(), - takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), thumbnailPath: json.containsKey(r'thumbnailPath') ? Optional.present(mapValueOfType(json, r'thumbnailPath')) : const Optional.absent(), - trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), - updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), withDeleted: json.containsKey(r'withDeleted') ? Optional.present(mapValueOfType(json, r'withDeleted')) : const Optional.absent(), withExif: json.containsKey(r'withExif') ? Optional.present(mapValueOfType(json, r'withExif')) : const Optional.absent(), diff --git a/mobile/openapi/lib/model/notification_create_dto.dart b/mobile/openapi/lib/model/notification_create_dto.dart index c6e7c02231..b7f3528156 100644 --- a/mobile/openapi/lib/model/notification_create_dto.dart +++ b/mobile/openapi/lib/model/notification_create_dto.dart @@ -93,7 +93,7 @@ class NotificationCreateDto { } if (this.readAt.isPresent) { final value = this.readAt.value; - json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -118,7 +118,7 @@ class NotificationCreateDto { data: json.containsKey(r'data') ? Optional.present(mapCastOfType(json, r'data')) : const Optional.absent(), description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), level: json.containsKey(r'level') ? Optional.present(NotificationLevel.fromJson(json[r'level'])) : const Optional.absent(), - readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), title: mapValueOfType(json, r'title')!, type: json.containsKey(r'type') ? Optional.present(NotificationType.fromJson(json[r'type'])) : const Optional.absent(), userId: mapValueOfType(json, r'userId')!, diff --git a/mobile/openapi/lib/model/notification_dto.dart b/mobile/openapi/lib/model/notification_dto.dart index 4e4fab8ee6..7609a6a021 100644 --- a/mobile/openapi/lib/model/notification_dto.dart +++ b/mobile/openapi/lib/model/notification_dto.dart @@ -85,7 +85,7 @@ class NotificationDto { Map toJson() { final json = {}; - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); if (this.data.isPresent) { @@ -100,7 +100,7 @@ class NotificationDto { json[r'level'] = this.level; if (this.readAt.isPresent) { final value = this.readAt.value; - json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -118,12 +118,12 @@ class NotificationDto { final json = value.cast(); return NotificationDto( - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, data: json.containsKey(r'data') ? Optional.present(mapCastOfType(json, r'data')) : const Optional.absent(), description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), id: mapValueOfType(json, r'id')!, level: NotificationLevel.fromJson(json[r'level'])!, - readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), title: mapValueOfType(json, r'title')!, type: NotificationType.fromJson(json[r'type'])!, ); diff --git a/mobile/openapi/lib/model/notification_update_all_dto.dart b/mobile/openapi/lib/model/notification_update_all_dto.dart index 1a95e2647a..e3d15c5519 100644 --- a/mobile/openapi/lib/model/notification_update_all_dto.dart +++ b/mobile/openapi/lib/model/notification_update_all_dto.dart @@ -42,7 +42,7 @@ class NotificationUpdateAllDto { json[r'ids'] = this.ids; if (this.readAt.isPresent) { final value = this.readAt.value; - json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -61,7 +61,7 @@ class NotificationUpdateAllDto { ids: json[r'ids'] is Iterable ? (json[r'ids'] as Iterable).cast().toList(growable: false) : const [], - readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/notification_update_dto.dart b/mobile/openapi/lib/model/notification_update_dto.dart index 4fa0cd5e97..73a14ec7cd 100644 --- a/mobile/openapi/lib/model/notification_update_dto.dart +++ b/mobile/openapi/lib/model/notification_update_dto.dart @@ -35,7 +35,7 @@ class NotificationUpdateDto { final json = {}; if (this.readAt.isPresent) { final value = this.readAt.value; - json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'readAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -51,7 +51,7 @@ class NotificationUpdateDto { final json = value.cast(); return NotificationUpdateDto( - readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + readAt: json.containsKey(r'readAt') ? Optional.present(mapDateTime(json, r'readAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), ); } return null; diff --git a/mobile/openapi/lib/model/random_search_dto.dart b/mobile/openapi/lib/model/random_search_dto.dart index d8a2a7635a..7937069578 100644 --- a/mobile/openapi/lib/model/random_search_dto.dart +++ b/mobile/openapi/lib/model/random_search_dto.dart @@ -363,13 +363,13 @@ class RandomSearchDto { } if (this.createdAfter.isPresent) { final value = this.createdAfter.value; - json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.createdBefore.isPresent) { final value = this.createdBefore.value; - json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -435,25 +435,25 @@ class RandomSearchDto { } if (this.takenAfter.isPresent) { final value = this.takenAfter.value; - json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.takenBefore.isPresent) { final value = this.takenBefore.value; - json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.trashedAfter.isPresent) { final value = this.trashedAfter.value; - json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.trashedBefore.isPresent) { final value = this.trashedBefore.value; - json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -463,13 +463,13 @@ class RandomSearchDto { } if (this.updatedAfter.isPresent) { final value = this.updatedAfter.value; - json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.updatedBefore.isPresent) { final value = this.updatedBefore.value; - json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -510,8 +510,8 @@ class RandomSearchDto { : const []) : const Optional.absent(), city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), - createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType(json, r'isEncoded')) : const Optional.absent(), isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType(json, r'isMotion')) : const Optional.absent(), @@ -531,13 +531,13 @@ class RandomSearchDto { tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) : const []) : const Optional.absent(), - takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), - updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), withDeleted: json.containsKey(r'withDeleted') ? Optional.present(mapValueOfType(json, r'withDeleted')) : const Optional.absent(), withExif: json.containsKey(r'withExif') ? Optional.present(mapValueOfType(json, r'withExif')) : const Optional.absent(), diff --git a/mobile/openapi/lib/model/server_version_history_response_dto.dart b/mobile/openapi/lib/model/server_version_history_response_dto.dart index ae5e060cff..4af2933f8d 100644 --- a/mobile/openapi/lib/model/server_version_history_response_dto.dart +++ b/mobile/openapi/lib/model/server_version_history_response_dto.dart @@ -45,7 +45,7 @@ class ServerVersionHistoryResponseDto { Map toJson() { final json = {}; - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); json[r'id'] = this.id; @@ -62,7 +62,7 @@ class ServerVersionHistoryResponseDto { final json = value.cast(); return ServerVersionHistoryResponseDto( - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, id: mapValueOfType(json, r'id')!, version: mapValueOfType(json, r'version')!, ); diff --git a/mobile/openapi/lib/model/shared_link_create_dto.dart b/mobile/openapi/lib/model/shared_link_create_dto.dart index 239db7f811..f6c7b66181 100644 --- a/mobile/openapi/lib/model/shared_link_create_dto.dart +++ b/mobile/openapi/lib/model/shared_link_create_dto.dart @@ -120,7 +120,7 @@ class SharedLinkCreateDto { } if (this.expiresAt.isPresent) { final value = this.expiresAt.value; - json[r'expiresAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'expiresAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -156,7 +156,7 @@ class SharedLinkCreateDto { ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) : const []) : const Optional.absent(), description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), - expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), showMetadata: json.containsKey(r'showMetadata') ? Optional.present(mapValueOfType(json, r'showMetadata')) : const Optional.absent(), slug: json.containsKey(r'slug') ? Optional.present(mapValueOfType(json, r'slug')) : const Optional.absent(), diff --git a/mobile/openapi/lib/model/shared_link_edit_dto.dart b/mobile/openapi/lib/model/shared_link_edit_dto.dart index a38d28b79b..6cc0353370 100644 --- a/mobile/openapi/lib/model/shared_link_edit_dto.dart +++ b/mobile/openapi/lib/model/shared_link_edit_dto.dart @@ -101,7 +101,7 @@ class SharedLinkEditDto { } if (this.expiresAt.isPresent) { final value = this.expiresAt.value; - json[r'expiresAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'expiresAt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -132,7 +132,7 @@ class SharedLinkEditDto { allowDownload: json.containsKey(r'allowDownload') ? Optional.present(mapValueOfType(json, r'allowDownload')) : const Optional.absent(), allowUpload: json.containsKey(r'allowUpload') ? Optional.present(mapValueOfType(json, r'allowUpload')) : const Optional.absent(), description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), - expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + expiresAt: json.containsKey(r'expiresAt') ? Optional.present(mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), password: json.containsKey(r'password') ? Optional.present(mapValueOfType(json, r'password')) : const Optional.absent(), showMetadata: json.containsKey(r'showMetadata') ? Optional.present(mapValueOfType(json, r'showMetadata')) : const Optional.absent(), slug: json.containsKey(r'slug') ? Optional.present(mapValueOfType(json, r'slug')) : const Optional.absent(), diff --git a/mobile/openapi/lib/model/shared_link_response_dto.dart b/mobile/openapi/lib/model/shared_link_response_dto.dart index 8d7cb74eca..2c86b21515 100644 --- a/mobile/openapi/lib/model/shared_link_response_dto.dart +++ b/mobile/openapi/lib/model/shared_link_response_dto.dart @@ -121,7 +121,7 @@ class SharedLinkResponseDto { json[r'allowDownload'] = this.allowDownload; json[r'allowUpload'] = this.allowUpload; json[r'assets'] = this.assets; - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); if (this.description != null) { @@ -130,7 +130,7 @@ class SharedLinkResponseDto { json[r'description'] = null; } if (this.expiresAt != null) { - json[r'expiresAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'expiresAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.expiresAt!.millisecondsSinceEpoch : this.expiresAt!.toUtc().toIso8601String(); } else { @@ -167,9 +167,9 @@ class SharedLinkResponseDto { allowDownload: mapValueOfType(json, r'allowDownload')!, allowUpload: mapValueOfType(json, r'allowUpload')!, assets: AssetResponseDto.listFromJson(json[r'assets']), - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, description: mapValueOfType(json, r'description'), - expiresAt: mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + expiresAt: mapDateTime(json, r'expiresAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), id: mapValueOfType(json, r'id')!, key: mapValueOfType(json, r'key')!, password: mapValueOfType(json, r'password'), diff --git a/mobile/openapi/lib/model/smart_search_dto.dart b/mobile/openapi/lib/model/smart_search_dto.dart index dfd4d60ffd..70c9a9354e 100644 --- a/mobile/openapi/lib/model/smart_search_dto.dart +++ b/mobile/openapi/lib/model/smart_search_dto.dart @@ -390,13 +390,13 @@ class SmartSearchDto { } if (this.createdAfter.isPresent) { final value = this.createdAfter.value; - json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.createdBefore.isPresent) { final value = this.createdBefore.value; - json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -478,25 +478,25 @@ class SmartSearchDto { } if (this.takenAfter.isPresent) { final value = this.takenAfter.value; - json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.takenBefore.isPresent) { final value = this.takenBefore.value; - json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.trashedAfter.isPresent) { final value = this.trashedAfter.value; - json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.trashedBefore.isPresent) { final value = this.trashedBefore.value; - json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -506,13 +506,13 @@ class SmartSearchDto { } if (this.updatedAfter.isPresent) { final value = this.updatedAfter.value; - json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.updatedBefore.isPresent) { final value = this.updatedBefore.value; - json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -545,8 +545,8 @@ class SmartSearchDto { : const []) : const Optional.absent(), city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), - createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType(json, r'isEncoded')) : const Optional.absent(), isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType(json, r'isMotion')) : const Optional.absent(), @@ -570,13 +570,13 @@ class SmartSearchDto { tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) : const []) : const Optional.absent(), - takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), - updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), withDeleted: json.containsKey(r'withDeleted') ? Optional.present(mapValueOfType(json, r'withDeleted')) : const Optional.absent(), withExif: json.containsKey(r'withExif') ? Optional.present(mapValueOfType(json, r'withExif')) : const Optional.absent(), diff --git a/mobile/openapi/lib/model/statistics_search_dto.dart b/mobile/openapi/lib/model/statistics_search_dto.dart index 9410d6ab63..37328cd7f4 100644 --- a/mobile/openapi/lib/model/statistics_search_dto.dart +++ b/mobile/openapi/lib/model/statistics_search_dto.dart @@ -312,13 +312,13 @@ class StatisticsSearchDto { } if (this.createdAfter.isPresent) { final value = this.createdAfter.value; - json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.createdBefore.isPresent) { final value = this.createdBefore.value; - json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -384,25 +384,25 @@ class StatisticsSearchDto { } if (this.takenAfter.isPresent) { final value = this.takenAfter.value; - json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'takenAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.takenBefore.isPresent) { final value = this.takenBefore.value; - json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'takenBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.trashedAfter.isPresent) { final value = this.trashedAfter.value; - json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'trashedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.trashedBefore.isPresent) { final value = this.trashedBefore.value; - json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'trashedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -412,13 +412,13 @@ class StatisticsSearchDto { } if (this.updatedAfter.isPresent) { final value = this.updatedAfter.value; - json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAfter'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } if (this.updatedBefore.isPresent) { final value = this.updatedBefore.value; - json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedBefore'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? value.millisecondsSinceEpoch : value.toUtc().toIso8601String()); } @@ -443,8 +443,8 @@ class StatisticsSearchDto { : const []) : const Optional.absent(), city: json.containsKey(r'city') ? Optional.present(mapValueOfType(json, r'city')) : const Optional.absent(), country: json.containsKey(r'country') ? Optional.present(mapValueOfType(json, r'country')) : const Optional.absent(), - createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), description: json.containsKey(r'description') ? Optional.present(mapValueOfType(json, r'description')) : const Optional.absent(), isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType(json, r'isEncoded')) : const Optional.absent(), isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType(json, r'isFavorite')) : const Optional.absent(), @@ -464,13 +464,13 @@ class StatisticsSearchDto { tagIds: json.containsKey(r'tagIds') ? Optional.present(json[r'tagIds'] is Iterable ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) : const []) : const Optional.absent(), - takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + takenAfter: json.containsKey(r'takenAfter') ? Optional.present(mapDateTime(json, r'takenAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + takenBefore: json.containsKey(r'takenBefore') ? Optional.present(mapDateTime(json, r'takenBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + trashedAfter: json.containsKey(r'trashedAfter') ? Optional.present(mapDateTime(json, r'trashedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + trashedBefore: json.containsKey(r'trashedBefore') ? Optional.present(mapDateTime(json, r'trashedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), type: json.containsKey(r'type') ? Optional.present(AssetTypeEnum.fromJson(json[r'type'])) : const Optional.absent(), - updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), - updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')) : const Optional.absent(), + updatedAfter: json.containsKey(r'updatedAfter') ? Optional.present(mapDateTime(json, r'updatedAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), + updatedBefore: json.containsKey(r'updatedBefore') ? Optional.present(mapDateTime(json, r'updatedBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(), visibility: json.containsKey(r'visibility') ? Optional.present(AssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(), ); } diff --git a/mobile/openapi/lib/model/sync_album_v1.dart b/mobile/openapi/lib/model/sync_album_v1.dart index 0a216cd93d..677795fd58 100644 --- a/mobile/openapi/lib/model/sync_album_v1.dart +++ b/mobile/openapi/lib/model/sync_album_v1.dart @@ -80,7 +80,7 @@ class SyncAlbumV1 { Map toJson() { final json = {}; - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); json[r'description'] = this.description; @@ -94,7 +94,7 @@ class SyncAlbumV1 { } else { json[r'thumbnailAssetId'] = null; } - json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.updatedAt.millisecondsSinceEpoch : this.updatedAt.toUtc().toIso8601String(); return json; @@ -109,7 +109,7 @@ class SyncAlbumV1 { final json = value.cast(); return SyncAlbumV1( - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, description: mapValueOfType(json, r'description')!, id: mapValueOfType(json, r'id')!, isActivityEnabled: mapValueOfType(json, r'isActivityEnabled')!, @@ -117,7 +117,7 @@ class SyncAlbumV1 { order: AssetOrder.fromJson(json[r'order'])!, ownerId: mapValueOfType(json, r'ownerId')!, thumbnailAssetId: mapValueOfType(json, r'thumbnailAssetId'), - updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ); } return null; diff --git a/mobile/openapi/lib/model/sync_album_v2.dart b/mobile/openapi/lib/model/sync_album_v2.dart index c0d0cf339b..701c0c94ec 100644 --- a/mobile/openapi/lib/model/sync_album_v2.dart +++ b/mobile/openapi/lib/model/sync_album_v2.dart @@ -74,7 +74,7 @@ class SyncAlbumV2 { Map toJson() { final json = {}; - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); json[r'description'] = this.description; @@ -87,7 +87,7 @@ class SyncAlbumV2 { } else { json[r'thumbnailAssetId'] = null; } - json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.updatedAt.millisecondsSinceEpoch : this.updatedAt.toUtc().toIso8601String(); return json; @@ -102,14 +102,14 @@ class SyncAlbumV2 { final json = value.cast(); return SyncAlbumV2( - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, description: mapValueOfType(json, r'description')!, id: mapValueOfType(json, r'id')!, isActivityEnabled: mapValueOfType(json, r'isActivityEnabled')!, name: mapValueOfType(json, r'name')!, order: AssetOrder.fromJson(json[r'order'])!, thumbnailAssetId: mapValueOfType(json, r'thumbnailAssetId'), - updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ); } return null; diff --git a/mobile/openapi/lib/model/sync_asset_exif_v1.dart b/mobile/openapi/lib/model/sync_asset_exif_v1.dart index fb806322c9..f5bc4d021d 100644 --- a/mobile/openapi/lib/model/sync_asset_exif_v1.dart +++ b/mobile/openapi/lib/model/sync_asset_exif_v1.dart @@ -204,7 +204,7 @@ class SyncAssetExifV1 { json[r'country'] = null; } if (this.dateTimeOriginal != null) { - json[r'dateTimeOriginal'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'dateTimeOriginal'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.dateTimeOriginal!.millisecondsSinceEpoch : this.dateTimeOriginal!.toUtc().toIso8601String(); } else { @@ -281,7 +281,7 @@ class SyncAssetExifV1 { json[r'model'] = null; } if (this.modifyDate != null) { - json[r'modifyDate'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'modifyDate'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.modifyDate!.millisecondsSinceEpoch : this.modifyDate!.toUtc().toIso8601String(); } else { @@ -332,7 +332,7 @@ class SyncAssetExifV1 { assetId: mapValueOfType(json, r'assetId')!, city: mapValueOfType(json, r'city'), country: mapValueOfType(json, r'country'), - dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), description: mapValueOfType(json, r'description'), exifImageHeight: mapValueOfType(json, r'exifImageHeight'), exifImageWidth: mapValueOfType(json, r'exifImageWidth'), @@ -347,7 +347,7 @@ class SyncAssetExifV1 { longitude: mapValueOfType(json, r'longitude'), make: mapValueOfType(json, r'make'), model: mapValueOfType(json, r'model'), - modifyDate: mapDateTime(json, r'modifyDate', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + modifyDate: mapDateTime(json, r'modifyDate', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), orientation: mapValueOfType(json, r'orientation'), profileDescription: mapValueOfType(json, r'profileDescription'), projectionType: mapValueOfType(json, r'projectionType'), diff --git a/mobile/openapi/lib/model/sync_asset_face_v2.dart b/mobile/openapi/lib/model/sync_asset_face_v2.dart index aa5f016176..3b714b7632 100644 --- a/mobile/openapi/lib/model/sync_asset_face_v2.dart +++ b/mobile/openapi/lib/model/sync_asset_face_v2.dart @@ -123,7 +123,7 @@ class SyncAssetFaceV2 { json[r'boundingBoxY1'] = this.boundingBoxY1; json[r'boundingBoxY2'] = this.boundingBoxY2; if (this.deletedAt != null) { - json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { @@ -156,7 +156,7 @@ class SyncAssetFaceV2 { boundingBoxX2: mapValueOfType(json, r'boundingBoxX2')!, boundingBoxY1: mapValueOfType(json, r'boundingBoxY1')!, boundingBoxY2: mapValueOfType(json, r'boundingBoxY2')!, - deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), id: mapValueOfType(json, r'id')!, imageHeight: mapValueOfType(json, r'imageHeight')!, imageWidth: mapValueOfType(json, r'imageWidth')!, diff --git a/mobile/openapi/lib/model/sync_asset_ocr_delete_v1.dart b/mobile/openapi/lib/model/sync_asset_ocr_delete_v1.dart index e413ea5d9e..018c134644 100644 --- a/mobile/openapi/lib/model/sync_asset_ocr_delete_v1.dart +++ b/mobile/openapi/lib/model/sync_asset_ocr_delete_v1.dart @@ -46,7 +46,7 @@ class SyncAssetOcrDeleteV1 { Map toJson() { final json = {}; json[r'assetId'] = this.assetId; - json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.deletedAt.millisecondsSinceEpoch : this.deletedAt.toUtc().toIso8601String(); json[r'id'] = this.id; @@ -63,7 +63,7 @@ class SyncAssetOcrDeleteV1 { return SyncAssetOcrDeleteV1( assetId: mapValueOfType(json, r'assetId')!, - deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, id: mapValueOfType(json, r'id')!, ); } diff --git a/mobile/openapi/lib/model/sync_asset_v1.dart b/mobile/openapi/lib/model/sync_asset_v1.dart index a9b8ca30cb..1bcfadd4e1 100644 --- a/mobile/openapi/lib/model/sync_asset_v1.dart +++ b/mobile/openapi/lib/model/sync_asset_v1.dart @@ -153,14 +153,14 @@ class SyncAssetV1 { final json = {}; json[r'checksum'] = this.checksum; if (this.createdAt != null) { - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt!.millisecondsSinceEpoch : this.createdAt!.toUtc().toIso8601String(); } else { json[r'createdAt'] = null; } if (this.deletedAt != null) { - json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { @@ -172,14 +172,14 @@ class SyncAssetV1 { json[r'duration'] = null; } if (this.fileCreatedAt != null) { - json[r'fileCreatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'fileCreatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.fileCreatedAt!.millisecondsSinceEpoch : this.fileCreatedAt!.toUtc().toIso8601String(); } else { json[r'fileCreatedAt'] = null; } if (this.fileModifiedAt != null) { - json[r'fileModifiedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'fileModifiedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.fileModifiedAt!.millisecondsSinceEpoch : this.fileModifiedAt!.toUtc().toIso8601String(); } else { @@ -204,7 +204,7 @@ class SyncAssetV1 { json[r'livePhotoVideoId'] = null; } if (this.localDateTime != null) { - json[r'localDateTime'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'localDateTime'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.localDateTime!.millisecondsSinceEpoch : this.localDateTime!.toUtc().toIso8601String(); } else { @@ -242,18 +242,18 @@ class SyncAssetV1 { return SyncAssetV1( checksum: mapValueOfType(json, r'checksum')!, - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), + deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), duration: mapValueOfType(json, r'duration'), - fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), + fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), height: mapValueOfType(json, r'height'), id: mapValueOfType(json, r'id')!, isEdited: mapValueOfType(json, r'isEdited')!, isFavorite: mapValueOfType(json, r'isFavorite')!, libraryId: mapValueOfType(json, r'libraryId'), livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), - localDateTime: mapDateTime(json, r'localDateTime', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + localDateTime: mapDateTime(json, r'localDateTime', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), originalFileName: mapValueOfType(json, r'originalFileName')!, ownerId: mapValueOfType(json, r'ownerId')!, stackId: mapValueOfType(json, r'stackId'), diff --git a/mobile/openapi/lib/model/sync_asset_v2.dart b/mobile/openapi/lib/model/sync_asset_v2.dart index 987f8ded93..37751f9f91 100644 --- a/mobile/openapi/lib/model/sync_asset_v2.dart +++ b/mobile/openapi/lib/model/sync_asset_v2.dart @@ -156,14 +156,14 @@ class SyncAssetV2 { final json = {}; json[r'checksum'] = this.checksum; if (this.createdAt != null) { - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt!.millisecondsSinceEpoch : this.createdAt!.toUtc().toIso8601String(); } else { json[r'createdAt'] = null; } if (this.deletedAt != null) { - json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { @@ -175,14 +175,14 @@ class SyncAssetV2 { json[r'duration'] = null; } if (this.fileCreatedAt != null) { - json[r'fileCreatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'fileCreatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.fileCreatedAt!.millisecondsSinceEpoch : this.fileCreatedAt!.toUtc().toIso8601String(); } else { json[r'fileCreatedAt'] = null; } if (this.fileModifiedAt != null) { - json[r'fileModifiedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'fileModifiedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.fileModifiedAt!.millisecondsSinceEpoch : this.fileModifiedAt!.toUtc().toIso8601String(); } else { @@ -207,7 +207,7 @@ class SyncAssetV2 { json[r'livePhotoVideoId'] = null; } if (this.localDateTime != null) { - json[r'localDateTime'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'localDateTime'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.localDateTime!.millisecondsSinceEpoch : this.localDateTime!.toUtc().toIso8601String(); } else { @@ -245,18 +245,18 @@ class SyncAssetV2 { return SyncAssetV2( checksum: mapValueOfType(json, r'checksum')!, - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), + deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), duration: mapValueOfType(json, r'duration'), - fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), + fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), height: mapValueOfType(json, r'height'), id: mapValueOfType(json, r'id')!, isEdited: mapValueOfType(json, r'isEdited')!, isFavorite: mapValueOfType(json, r'isFavorite')!, libraryId: mapValueOfType(json, r'libraryId'), livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), - localDateTime: mapDateTime(json, r'localDateTime', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + localDateTime: mapDateTime(json, r'localDateTime', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), originalFileName: mapValueOfType(json, r'originalFileName')!, ownerId: mapValueOfType(json, r'ownerId')!, stackId: mapValueOfType(json, r'stackId'), diff --git a/mobile/openapi/lib/model/sync_auth_user_v1.dart b/mobile/openapi/lib/model/sync_auth_user_v1.dart index 0eac7cf9b4..24e8bc897e 100644 --- a/mobile/openapi/lib/model/sync_auth_user_v1.dart +++ b/mobile/openapi/lib/model/sync_auth_user_v1.dart @@ -115,7 +115,7 @@ class SyncAuthUserV1 { json[r'avatarColor'] = value; } if (this.deletedAt != null) { - json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { @@ -132,7 +132,7 @@ class SyncAuthUserV1 { } else { json[r'pinCode'] = null; } - json[r'profileChangedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'profileChangedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.profileChangedAt.millisecondsSinceEpoch : this.profileChangedAt.toUtc().toIso8601String(); if (this.quotaSizeInBytes != null) { @@ -159,7 +159,7 @@ class SyncAuthUserV1 { return SyncAuthUserV1( avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), - deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), email: mapValueOfType(json, r'email')!, hasProfileImage: mapValueOfType(json, r'hasProfileImage')!, id: mapValueOfType(json, r'id')!, @@ -167,7 +167,7 @@ class SyncAuthUserV1 { name: mapValueOfType(json, r'name')!, oauthId: mapValueOfType(json, r'oauthId')!, pinCode: mapValueOfType(json, r'pinCode'), - profileChangedAt: mapDateTime(json, r'profileChangedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + profileChangedAt: mapDateTime(json, r'profileChangedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, quotaSizeInBytes: mapValueOfType(json, r'quotaSizeInBytes'), quotaUsageInBytes: mapValueOfType(json, r'quotaUsageInBytes')!, storageLabel: mapValueOfType(json, r'storageLabel'), diff --git a/mobile/openapi/lib/model/sync_memory_v1.dart b/mobile/openapi/lib/model/sync_memory_v1.dart index f78ad0e7ba..95f5751f1f 100644 --- a/mobile/openapi/lib/model/sync_memory_v1.dart +++ b/mobile/openapi/lib/model/sync_memory_v1.dart @@ -98,19 +98,19 @@ class SyncMemoryV1 { Map toJson() { final json = {}; - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); json[r'data'] = this.data; if (this.deletedAt != null) { - json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { json[r'deletedAt'] = null; } if (this.hideAt != null) { - json[r'hideAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'hideAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.hideAt!.millisecondsSinceEpoch : this.hideAt!.toUtc().toIso8601String(); } else { @@ -118,26 +118,26 @@ class SyncMemoryV1 { } json[r'id'] = this.id; json[r'isSaved'] = this.isSaved; - json[r'memoryAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'memoryAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.memoryAt.millisecondsSinceEpoch : this.memoryAt.toUtc().toIso8601String(); json[r'ownerId'] = this.ownerId; if (this.seenAt != null) { - json[r'seenAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'seenAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.seenAt!.millisecondsSinceEpoch : this.seenAt!.toUtc().toIso8601String(); } else { json[r'seenAt'] = null; } if (this.showAt != null) { - json[r'showAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'showAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.showAt!.millisecondsSinceEpoch : this.showAt!.toUtc().toIso8601String(); } else { json[r'showAt'] = null; } json[r'type'] = this.type; - json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.updatedAt.millisecondsSinceEpoch : this.updatedAt.toUtc().toIso8601String(); return json; @@ -152,18 +152,18 @@ class SyncMemoryV1 { final json = value.cast(); return SyncMemoryV1( - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, data: mapCastOfType(json, r'data')!, - deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - hideAt: mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), + hideAt: mapDateTime(json, r'hideAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), id: mapValueOfType(json, r'id')!, isSaved: mapValueOfType(json, r'isSaved')!, - memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + memoryAt: mapDateTime(json, r'memoryAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ownerId: mapValueOfType(json, r'ownerId')!, - seenAt: mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), - showAt: mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + seenAt: mapDateTime(json, r'seenAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), + showAt: mapDateTime(json, r'showAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), type: MemoryType.fromJson(json[r'type'])!, - updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ); } return null; diff --git a/mobile/openapi/lib/model/sync_person_v1.dart b/mobile/openapi/lib/model/sync_person_v1.dart index e79b7cfe16..6a669a0306 100644 --- a/mobile/openapi/lib/model/sync_person_v1.dart +++ b/mobile/openapi/lib/model/sync_person_v1.dart @@ -88,7 +88,7 @@ class SyncPersonV1 { Map toJson() { final json = {}; if (this.birthDate != null) { - json[r'birthDate'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'birthDate'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.birthDate!.millisecondsSinceEpoch : this.birthDate!.toUtc().toIso8601String(); } else { @@ -99,7 +99,7 @@ class SyncPersonV1 { } else { json[r'color'] = null; } - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); if (this.faceAssetId != null) { @@ -112,7 +112,7 @@ class SyncPersonV1 { json[r'isHidden'] = this.isHidden; json[r'name'] = this.name; json[r'ownerId'] = this.ownerId; - json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.updatedAt.millisecondsSinceEpoch : this.updatedAt.toUtc().toIso8601String(); return json; @@ -127,16 +127,16 @@ class SyncPersonV1 { final json = value.cast(); return SyncPersonV1( - birthDate: mapDateTime(json, r'birthDate', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + birthDate: mapDateTime(json, r'birthDate', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), color: mapValueOfType(json, r'color'), - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, faceAssetId: mapValueOfType(json, r'faceAssetId'), id: mapValueOfType(json, r'id')!, isFavorite: mapValueOfType(json, r'isFavorite')!, isHidden: mapValueOfType(json, r'isHidden')!, name: mapValueOfType(json, r'name')!, ownerId: mapValueOfType(json, r'ownerId')!, - updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ); } return null; diff --git a/mobile/openapi/lib/model/sync_stack_v1.dart b/mobile/openapi/lib/model/sync_stack_v1.dart index 3e79a55134..6da2243872 100644 --- a/mobile/openapi/lib/model/sync_stack_v1.dart +++ b/mobile/openapi/lib/model/sync_stack_v1.dart @@ -57,13 +57,13 @@ class SyncStackV1 { Map toJson() { final json = {}; - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); json[r'id'] = this.id; json[r'ownerId'] = this.ownerId; json[r'primaryAssetId'] = this.primaryAssetId; - json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.updatedAt.millisecondsSinceEpoch : this.updatedAt.toUtc().toIso8601String(); return json; @@ -78,11 +78,11 @@ class SyncStackV1 { final json = value.cast(); return SyncStackV1( - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, id: mapValueOfType(json, r'id')!, ownerId: mapValueOfType(json, r'ownerId')!, primaryAssetId: mapValueOfType(json, r'primaryAssetId')!, - updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ); } return null; diff --git a/mobile/openapi/lib/model/sync_user_v1.dart b/mobile/openapi/lib/model/sync_user_v1.dart index 2fda2fbefa..d86680179e 100644 --- a/mobile/openapi/lib/model/sync_user_v1.dart +++ b/mobile/openapi/lib/model/sync_user_v1.dart @@ -73,7 +73,7 @@ class SyncUserV1 { json[r'avatarColor'] = value; } if (this.deletedAt != null) { - json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { @@ -83,7 +83,7 @@ class SyncUserV1 { json[r'hasProfileImage'] = this.hasProfileImage; json[r'id'] = this.id; json[r'name'] = this.name; - json[r'profileChangedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'profileChangedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.profileChangedAt.millisecondsSinceEpoch : this.profileChangedAt.toUtc().toIso8601String(); return json; @@ -99,12 +99,12 @@ class SyncUserV1 { return SyncUserV1( avatarColor: json.containsKey(r'avatarColor') ? Optional.present(UserAvatarColor.fromJson(json[r'avatarColor'])) : const Optional.absent(), - deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), email: mapValueOfType(json, r'email')!, hasProfileImage: mapValueOfType(json, r'hasProfileImage')!, id: mapValueOfType(json, r'id')!, name: mapValueOfType(json, r'name')!, - profileChangedAt: mapDateTime(json, r'profileChangedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + profileChangedAt: mapDateTime(json, r'profileChangedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ); } return null; diff --git a/mobile/openapi/lib/model/system_config_nightly_tasks_dto.dart b/mobile/openapi/lib/model/system_config_nightly_tasks_dto.dart index 0db417427f..4838c86065 100644 --- a/mobile/openapi/lib/model/system_config_nightly_tasks_dto.dart +++ b/mobile/openapi/lib/model/system_config_nightly_tasks_dto.dart @@ -33,7 +33,7 @@ class SystemConfigNightlyTasksDto { /// Missing thumbnails bool missingThumbnails; - /// Start time + /// Start time (HH:MM) String startTime; /// Sync quota usage diff --git a/mobile/openapi/lib/model/user_admin_response_dto.dart b/mobile/openapi/lib/model/user_admin_response_dto.dart index 733f217b68..c8499ab0dd 100644 --- a/mobile/openapi/lib/model/user_admin_response_dto.dart +++ b/mobile/openapi/lib/model/user_admin_response_dto.dart @@ -133,11 +133,11 @@ class UserAdminResponseDto { Map toJson() { final json = {}; json[r'avatarColor'] = this.avatarColor; - json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.createdAt.millisecondsSinceEpoch : this.createdAt.toUtc().toIso8601String(); if (this.deletedAt != null) { - json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.deletedAt!.millisecondsSinceEpoch : this.deletedAt!.toUtc().toIso8601String(); } else { @@ -172,7 +172,7 @@ class UserAdminResponseDto { } else { json[r'storageLabel'] = null; } - json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'updatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.updatedAt.millisecondsSinceEpoch : this.updatedAt.toUtc().toIso8601String(); return json; @@ -188,8 +188,8 @@ class UserAdminResponseDto { return UserAdminResponseDto( avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, - createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, - deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'), + createdAt: mapDateTime(json, r'createdAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, + deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/'), email: mapValueOfType(json, r'email')!, id: mapValueOfType(json, r'id')!, isAdmin: mapValueOfType(json, r'isAdmin')!, @@ -203,7 +203,7 @@ class UserAdminResponseDto { shouldChangePassword: mapValueOfType(json, r'shouldChangePassword')!, status: UserStatus.fromJson(json[r'status'])!, storageLabel: mapValueOfType(json, r'storageLabel'), - updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + updatedAt: mapDateTime(json, r'updatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, ); } return null; diff --git a/mobile/openapi/lib/model/user_license.dart b/mobile/openapi/lib/model/user_license.dart index 8ef46a0bb5..87f5bd74da 100644 --- a/mobile/openapi/lib/model/user_license.dart +++ b/mobile/openapi/lib/model/user_license.dart @@ -45,7 +45,7 @@ class UserLicense { Map toJson() { final json = {}; - json[r'activatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/') + json[r'activatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/') ? this.activatedAt.millisecondsSinceEpoch : this.activatedAt.toUtc().toIso8601String(); json[r'activationKey'] = this.activationKey; @@ -62,7 +62,7 @@ class UserLicense { final json = value.cast(); return UserLicense( - activatedAt: mapDateTime(json, r'activatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!, + activatedAt: mapDateTime(json, r'activatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')!, activationKey: mapValueOfType(json, r'activationKey')!, licenseKey: mapValueOfType(json, r'licenseKey')!, ); diff --git a/mobile/packages/ui/lib/immich_ui.dart b/mobile/packages/ui/lib/immich_ui.dart index 6158b4f0e1..eaf7051637 100644 --- a/mobile/packages/ui/lib/immich_ui.dart +++ b/mobile/packages/ui/lib/immich_ui.dart @@ -1,12 +1,15 @@ export 'src/components/close_button.dart'; +export 'src/components/column_button.dart'; export 'src/components/form.dart'; export 'src/components/formatted_text.dart'; export 'src/components/icon_button.dart'; +export 'src/components/menu_item.dart'; export 'src/components/password_input.dart'; export 'src/components/text_button.dart'; export 'src/components/text_input.dart'; export 'src/components/url_input.dart'; export 'src/constants.dart'; +export 'src/snackbar.dart'; export 'src/theme.dart'; export 'src/translation.dart'; export 'src/types.dart'; diff --git a/mobile/packages/ui/lib/src/color_override.dart b/mobile/packages/ui/lib/src/color_override.dart new file mode 100644 index 0000000000..c0b8358bb5 --- /dev/null +++ b/mobile/packages/ui/lib/src/color_override.dart @@ -0,0 +1,13 @@ +import 'package:flutter/widgets.dart'; + +class ImmichColorOverride extends InheritedWidget { + const ImmichColorOverride({super.key, required this.color, required super.child}); + + final Color color; + + static Color? maybeOf(BuildContext context) => + context.dependOnInheritedWidgetOfExactType()?.color; + + @override + bool updateShouldNotify(ImmichColorOverride oldWidget) => color != oldWidget.color; +} diff --git a/mobile/packages/ui/lib/src/components/close_button.dart b/mobile/packages/ui/lib/src/components/close_button.dart index d23c309c91..031f3992aa 100644 --- a/mobile/packages/ui/lib/src/components/close_button.dart +++ b/mobile/packages/ui/lib/src/components/close_button.dart @@ -16,10 +16,9 @@ class ImmichCloseButton extends StatelessWidget { @override Widget build(BuildContext context) => ImmichIconButton( - key: key, - icon: Icons.close, - color: color, - variant: variant, - onPressed: onPressed ?? () => Navigator.of(context).pop(), - ); + icon: Icons.close, + color: color, + variant: variant, + onPressed: onPressed ?? () => Navigator.of(context).pop(), + ); } diff --git a/mobile/packages/ui/lib/src/components/column_button.dart b/mobile/packages/ui/lib/src/components/column_button.dart new file mode 100644 index 0000000000..bf0023c1bc --- /dev/null +++ b/mobile/packages/ui/lib/src/components/column_button.dart @@ -0,0 +1,78 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/constants.dart'; +import 'package:immich_ui/src/internal.dart'; + +class ImmichColumnButton extends StatefulWidget { + final IconData icon; + final String label; + final FutureOr Function() onPressed; + final bool disabled; + final bool? loading; + + const ImmichColumnButton({ + super.key, + required this.icon, + required this.label, + required this.onPressed, + this.disabled = false, + this.loading, + }); + + @override + State createState() => _ImmichColumnButtonState(); +} + +class _ImmichColumnButtonState extends State { + bool _loading = false; + bool get _isLoading => widget.loading ?? _loading; + + Future _onPressed() async { + setState(() => _loading = true); + try { + await widget.onPressed(); + } finally { + if (mounted) { + setState(() => _loading = false); + } + } + } + + @override + Widget build(BuildContext context) { + final foreground = context.colorOverride ?? Theme.of(context).colorScheme.onSurface; + + return TextButton( + onPressed: widget.disabled || _isLoading ? null : _onPressed, + style: TextButton.styleFrom( + foregroundColor: foreground, + padding: const .symmetric(horizontal: ImmichSpacing.sm, vertical: ImmichSpacing.md), + tapTargetSize: .shrinkWrap, + shape: const RoundedRectangleBorder(borderRadius: .all(.circular(ImmichRadius.xl))), + ), + child: ConstrainedBox( + constraints: const .new(maxWidth: 90), + child: Column( + mainAxisSize: .min, + children: [ + _isLoading + ? const SizedBox.square( + dimension: ImmichIconSize.md, + child: CircularProgressIndicator(strokeWidth: ImmichBorderWidth.lg), + ) + : Icon(widget.icon, size: ImmichIconSize.md), + const SizedBox(height: ImmichSpacing.sm), + Text( + widget.label, + maxLines: 2, + textAlign: .center, + overflow: .ellipsis, + style: const .new(fontSize: ImmichTextSize.label, fontWeight: .w500), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/packages/ui/lib/src/components/form.dart b/mobile/packages/ui/lib/src/components/form.dart index 31e9d01980..4b6e3af085 100644 --- a/mobile/packages/ui/lib/src/components/form.dart +++ b/mobile/packages/ui/lib/src/components/form.dart @@ -88,7 +88,7 @@ class _ImmichFormState extends State { builder: (context, _) => ImmichTextButton( labelText: submitText, icon: widget.submitIcon, - variant: ImmichVariant.filled, + variant: .filled, loading: _controller.isLoading, onPressed: _controller.submit, disabled: _controller.onSubmit == null, diff --git a/mobile/packages/ui/lib/src/components/formatted_text.dart b/mobile/packages/ui/lib/src/components/formatted_text.dart index 95e42d834d..1cb75c8e0a 100644 --- a/mobile/packages/ui/lib/src/components/formatted_text.dart +++ b/mobile/packages/ui/lib/src/components/formatted_text.dart @@ -94,12 +94,12 @@ class _ImmichFormattedTextState extends State { final tag = match.group(1)!.toLowerCase(); final content = match.group(2)!; - final formattedSpan = (widget.spanBuilder ?? _defaultSpanBuilder)(tag); - final style = formattedSpan.style ?? _defaultTextStyle(tag); + final span = widget.spanBuilder?.call(tag); + final style = span?.style ?? _defaultTextStyle(tag); GestureRecognizer? recognizer; - if (formattedSpan.onTap != null) { - recognizer = TapGestureRecognizer()..onTap = formattedSpan.onTap; + if (span?.onTap != null) { + recognizer = TapGestureRecognizer()..onTap = span!.onTap; _recognizers.add(recognizer); } spans.add(TextSpan(text: content, style: style, recognizer: recognizer)); @@ -114,19 +114,12 @@ class _ImmichFormattedTextState extends State { return spans; } - FormattedSpan _defaultSpanBuilder(String tag) => switch (tag) { - 'b' => const FormattedSpan(style: TextStyle(fontWeight: FontWeight.bold)), - 'link' => const FormattedSpan(style: TextStyle(decoration: TextDecoration.underline)), - _ when tag.endsWith('-link') => const FormattedSpan(style: TextStyle(decoration: TextDecoration.underline)), - _ => const FormattedSpan(), - }; - TextStyle? _defaultTextStyle(String tag) => switch (tag) { - 'b' => const TextStyle(fontWeight: FontWeight.bold), - 'link' => const TextStyle(decoration: TextDecoration.underline), - _ when tag.endsWith('-link') => const TextStyle(decoration: TextDecoration.underline), - _ => null, - }; + 'b' => const TextStyle(fontWeight: FontWeight.bold), + 'link' => const TextStyle(decoration: TextDecoration.underline), + _ when tag.endsWith('-link') => const TextStyle(decoration: TextDecoration.underline), + _ => null, + }; @override Widget build(BuildContext context) { diff --git a/mobile/packages/ui/lib/src/components/icon_button.dart b/mobile/packages/ui/lib/src/components/icon_button.dart index dc140b71f9..b9bdd3a406 100644 --- a/mobile/packages/ui/lib/src/components/icon_button.dart +++ b/mobile/packages/ui/lib/src/components/icon_button.dart @@ -1,54 +1,80 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/types.dart'; +import 'dart:async'; -class ImmichIconButton extends StatelessWidget { +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; +import 'package:immich_ui/src/internal.dart'; + +class ImmichIconButton extends StatefulWidget { final IconData icon; - final VoidCallback onPressed; + final FutureOr Function() onPressed; final ImmichVariant variant; final ImmichColor color; final bool disabled; + final bool? loading; const ImmichIconButton({ super.key, required this.icon, required this.onPressed, - this.color = ImmichColor.primary, - this.variant = ImmichVariant.filled, + this.color = .primary, + this.variant = .filled, this.disabled = false, + this.loading, }); + @override + State createState() => _ImmichIconButtonState(); +} + +class _ImmichIconButtonState extends State { + bool _loading = false; + bool get _isLoading => widget.loading ?? _loading; + + Future _onPressed() async { + setState(() => _loading = true); + try { + await widget.onPressed(); + } finally { + if (mounted) { + setState(() => _loading = false); + } + } + } + @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - final background = switch (variant) { - ImmichVariant.filled => switch (color) { - ImmichColor.primary => colorScheme.primary, - ImmichColor.secondary => colorScheme.secondary, - }, - ImmichVariant.ghost => Colors.transparent, + final background = switch (widget.variant) { + .filled => switch (widget.color) { + .primary => colorScheme.primary, + .secondary => colorScheme.secondary, + }, + .ghost => Colors.transparent, }; - final foreground = switch (variant) { - ImmichVariant.filled => switch (color) { - ImmichColor.primary => colorScheme.onPrimary, - ImmichColor.secondary => colorScheme.onSecondary, - }, - ImmichVariant.ghost => switch (color) { - ImmichColor.primary => colorScheme.primary, - ImmichColor.secondary => colorScheme.secondary, - }, - }; - - final effectiveOnPressed = disabled ? null : onPressed; + final foreground = + context.colorOverride ?? + switch (widget.variant) { + .filled => switch (widget.color) { + .primary => colorScheme.onPrimary, + .secondary => colorScheme.onSecondary, + }, + .ghost => switch (widget.color) { + .primary => colorScheme.primary, + .secondary => colorScheme.secondary, + }, + }; return IconButton( - icon: Icon(icon), - onPressed: effectiveOnPressed, - style: IconButton.styleFrom( - backgroundColor: background, - foregroundColor: foreground, - ), + icon: _isLoading + ? const SizedBox.square( + dimension: ImmichIconSize.sm, + child: CircularProgressIndicator(strokeWidth: ImmichBorderWidth.md), + ) + : Icon(widget.icon), + onPressed: widget.disabled || _isLoading ? null : _onPressed, + style: IconButton.styleFrom(backgroundColor: background, foregroundColor: foreground), ); } } diff --git a/mobile/packages/ui/lib/src/components/menu_item.dart b/mobile/packages/ui/lib/src/components/menu_item.dart new file mode 100644 index 0000000000..84a0936e5d --- /dev/null +++ b/mobile/packages/ui/lib/src/components/menu_item.dart @@ -0,0 +1,100 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/constants.dart'; +import 'package:immich_ui/src/internal.dart'; + +class ImmichMenu extends StatefulWidget { + final List children; + final MenuAnchorChildBuilder builder; + final MenuStyle? style; + final bool consumeOutsideTap; + final Widget? child; + + const ImmichMenu({ + super.key, + required this.children, + required this.builder, + this.style, + this.consumeOutsideTap = false, + this.child, + }); + + @override + State createState() => _ImmichMenuState(); +} + +class _ImmichMenuState extends State { + final _controller = MenuController(); + + @override + Widget build(BuildContext context) { + return _ImmichMenuScope( + controller: _controller, + child: MenuAnchor( + controller: _controller, + style: widget.style, + consumeOutsideTap: widget.consumeOutsideTap, + menuChildren: widget.children, + builder: widget.builder, + child: widget.child, + ), + ); + } +} + +class _ImmichMenuScope extends InheritedWidget { + final MenuController controller; + + const _ImmichMenuScope({required this.controller, required super.child}); + + static MenuController? maybeOf(BuildContext context) => + context.dependOnInheritedWidgetOfExactType<_ImmichMenuScope>()?.controller; + + @override + bool updateShouldNotify(_ImmichMenuScope oldWidget) => controller != oldWidget.controller; +} + +class ImmichMenuItem extends StatefulWidget { + final IconData icon; + final String label; + final FutureOr Function() onPressed; + final bool disabled; + + const ImmichMenuItem({ + super.key, + required this.icon, + required this.label, + required this.onPressed, + this.disabled = false, + }); + + @override + State createState() => _ImmichMenuItemState(); +} + +class _ImmichMenuItemState extends State { + Future _onPressed(MenuController? controller) async { + try { + await widget.onPressed(); + } finally { + controller?.close(); + } + } + + @override + Widget build(BuildContext context) { + final controller = _ImmichMenuScope.maybeOf(context); + return MenuItemButton( + onPressed: widget.disabled ? null : () => _onPressed(controller), + closeOnActivate: controller == null, + style: MenuItemButton.styleFrom( + foregroundColor: context.colorOverride, + alignment: .centerLeft, + padding: const .symmetric(horizontal: ImmichSpacing.lg, vertical: ImmichSpacing.md), + ), + leadingIcon: Icon(widget.icon, size: ImmichIconSize.sm), + child: Text(widget.label, style: const .new(fontSize: ImmichTextSize.body)), + ); + } +} diff --git a/mobile/packages/ui/lib/src/components/password_input.dart b/mobile/packages/ui/lib/src/components/password_input.dart index 70808472df..e99e9730a8 100644 --- a/mobile/packages/ui/lib/src/components/password_input.dart +++ b/mobile/packages/ui/lib/src/components/password_input.dart @@ -52,7 +52,6 @@ class _ImmichPasswordInputState extends State { icon: Icon(_visible ? Icons.visibility_off_rounded : Icons.visibility_rounded), ), autofillHints: [AutofillHints.password], - keyboardType: TextInputType.text, ); } } diff --git a/mobile/packages/ui/lib/src/components/text_button.dart b/mobile/packages/ui/lib/src/components/text_button.dart index 6dc677aee2..fb3459897c 100644 --- a/mobile/packages/ui/lib/src/components/text_button.dart +++ b/mobile/packages/ui/lib/src/components/text_button.dart @@ -1,85 +1,72 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:immich_ui/src/constants.dart'; -import 'package:immich_ui/src/types.dart'; +import 'package:immich_ui/immich_ui.dart'; -class ImmichTextButton extends StatelessWidget { +class ImmichTextButton extends StatefulWidget { final String labelText; final IconData? icon; final FutureOr Function() onPressed; final ImmichVariant variant; - final ImmichColor color; final bool expanded; - final bool loading; final bool disabled; + final bool? loading; const ImmichTextButton({ super.key, required this.labelText, this.icon, required this.onPressed, - this.variant = ImmichVariant.filled, - this.color = ImmichColor.primary, + this.variant = .filled, this.expanded = true, - this.loading = false, + this.disabled = false, + this.loading, }); - Widget _buildButton(ImmichVariant variant) { - final Widget? effectiveIcon = loading - ? const SizedBox.square( - dimension: ImmichIconSize.md, - child: CircularProgressIndicator(strokeWidth: ImmichBorderWidth.lg), - ) - : icon != null - ? Icon(icon, fontWeight: FontWeight.w600) - : null; - final hasIcon = effectiveIcon != null; + @override + State createState() => _ImmichTextButtonState(); +} - final label = Text(labelText, style: const TextStyle(fontSize: ImmichTextSize.body, fontWeight: FontWeight.bold)); - final style = ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: ImmichSpacing.md)); +class _ImmichTextButtonState extends State { + bool _loading = false; + bool get _isLoading => widget.loading ?? _loading; - final effectiveOnPressed = disabled || loading ? null : onPressed; - - switch (variant) { - case ImmichVariant.filled: - if (hasIcon) { - return ElevatedButton.icon( - style: style, - onPressed: effectiveOnPressed, - icon: effectiveIcon, - label: label, - ); - } - - return ElevatedButton( - style: style, - onPressed: effectiveOnPressed, - child: label, - ); - case ImmichVariant.ghost: - if (hasIcon) { - return TextButton.icon( - style: style, - onPressed: effectiveOnPressed, - icon: effectiveIcon, - label: label, - ); - } - - return TextButton( - style: style, - onPressed: effectiveOnPressed, - child: label, - ); + Future _onPressed() async { + setState(() => _loading = true); + try { + await widget.onPressed(); + } finally { + if (mounted) { + setState(() => _loading = false); + } } } @override Widget build(BuildContext context) { - final button = _buildButton(variant); - if (expanded) { + final Widget? icon = _isLoading + ? const SizedBox.square( + dimension: ImmichIconSize.md, + child: CircularProgressIndicator(strokeWidth: ImmichBorderWidth.lg), + ) + : widget.icon != null + ? Icon(widget.icon, fontWeight: .w600) + : null; + + final label = Text( + widget.labelText, + style: const .new(fontSize: ImmichTextSize.body, fontWeight: .bold), + ); + final style = ElevatedButton.styleFrom(padding: const .symmetric(vertical: ImmichSpacing.md)); + final onPressed = widget.disabled || _isLoading ? null : _onPressed; + + final button = switch (widget.variant) { + ImmichVariant.filled => ElevatedButton.icon(style: style, onPressed: onPressed, icon: icon, label: label), + ImmichVariant.ghost => TextButton.icon(style: style, onPressed: onPressed, icon: icon, label: label), + }; + + if (widget.expanded) { return SizedBox(width: double.infinity, child: button); } return button; diff --git a/mobile/packages/ui/lib/src/internal.dart b/mobile/packages/ui/lib/src/internal.dart index 7f503927ff..4ca1f4b073 100644 --- a/mobile/packages/ui/lib/src/internal.dart +++ b/mobile/packages/ui/lib/src/internal.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:immich_ui/src/color_override.dart'; import 'package:immich_ui/src/translation.dart'; extension TranslationHelper on BuildContext { ImmichTranslations get translations => ImmichTranslationProvider.of(this); } + +extension ColorHelper on BuildContext { + Color? get colorOverride => ImmichColorOverride.maybeOf(this); +} diff --git a/mobile/packages/ui/lib/src/previews/column_button.dart b/mobile/packages/ui/lib/src/previews/column_button.dart new file mode 100644 index 0000000000..95f5bfee53 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/column_button.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/components/column_button.dart'; +import 'package:immich_ui/src/previews.dart'; + +void _previewNoop() {} + +@ImmichPreview(group: 'ColumnButton', name: 'Default') +Widget previewColumnButtonDefault() => const Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichColumnButton(onPressed: _previewNoop, icon: Icons.favorite_border_rounded, label: 'Favorite'), + ImmichColumnButton(onPressed: _previewNoop, icon: Icons.archive_outlined, label: 'Archive'), + ImmichColumnButton(onPressed: _previewNoop, icon: Icons.delete_outline_rounded, label: 'Delete'), + ], +); + +@ImmichPreview(group: 'ColumnButton', name: 'Loading') +Widget previewColumnButtonLoading() => ImmichColumnButton( + onPressed: () => Future.delayed(const .new(seconds: 2)), + icon: Icons.download, + label: 'Download', +); + +@ImmichPreview(group: 'ColumnButton', name: 'Disabled') +Widget previewColumnButtonDisabled() => + const ImmichColumnButton(onPressed: _previewNoop, icon: Icons.ios_share_rounded, label: 'Share', disabled: true); diff --git a/mobile/packages/ui/lib/src/previews/menu_item.dart b/mobile/packages/ui/lib/src/previews/menu_item.dart new file mode 100644 index 0000000000..759c55cf9c --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/menu_item.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/components/menu_item.dart'; +import 'package:immich_ui/src/previews.dart'; + +void _previewNoop() {} + +@ImmichPreview(group: 'MenuItem', name: 'Default') +Widget previewMenuItemDefault() => const Column( + mainAxisSize: MainAxisSize.min, + children: [ + ImmichMenuItem(onPressed: _previewNoop, icon: Icons.info_outline, label: 'Info'), + ImmichMenuItem(onPressed: _previewNoop, icon: Icons.help_outline_rounded, label: 'Troubleshoot'), + ImmichMenuItem(onPressed: _previewNoop, icon: Icons.cast_rounded, label: 'Cast'), + ], +); + +@ImmichPreview(group: 'MenuItem', name: 'Disabled') +Widget previewMenuItemDisabled() => + const ImmichMenuItem(onPressed: _previewNoop, icon: Icons.delete_outline_rounded, label: 'Delete', disabled: true); diff --git a/mobile/packages/ui/lib/src/previews/snackbar.dart b/mobile/packages/ui/lib/src/previews/snackbar.dart new file mode 100644 index 0000000000..1f98369e32 --- /dev/null +++ b/mobile/packages/ui/lib/src/previews/snackbar.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/src/constants.dart'; +import 'package:immich_ui/src/previews.dart'; +import 'package:immich_ui/src/snackbar.dart'; + +@ImmichPreview(group: 'Snackbar', name: 'Types') +Widget previewSnackbarTypes() => const _SnackbarDemo(); + +class _SnackbarDemo extends StatelessWidget { + const _SnackbarDemo(); + + @override + Widget build(BuildContext context) { + return ScaffoldMessenger( + key: scaffoldMessengerKey, + child: Scaffold( + backgroundColor: Colors.transparent, + body: Center( + child: Wrap( + spacing: ImmichSpacing.md, + runSpacing: ImmichSpacing.md, + children: [ + ElevatedButton(onPressed: () => snackbar.info('Info message'), child: const Text('Info')), + ElevatedButton(onPressed: () => snackbar.success('Saved'), child: const Text('Success')), + ElevatedButton(onPressed: () => snackbar.error('Something failed'), child: const Text('Error')), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/packages/ui/lib/src/previews/text_button.dart b/mobile/packages/ui/lib/src/previews/text_button.dart index 46c689bbae..fc1af0be3e 100644 --- a/mobile/packages/ui/lib/src/previews/text_button.dart +++ b/mobile/packages/ui/lib/src/previews/text_button.dart @@ -15,16 +15,6 @@ Widget previewTextButtonVariants() => const Wrap( ], ); -@ImmichPreview(group: 'TextButton', name: 'Colors') -Widget previewTextButtonColors() => const Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichTextButton(onPressed: _previewNoop, labelText: 'Primary', expanded: false), - ImmichTextButton(onPressed: _previewNoop, labelText: 'Secondary', color: ImmichColor.secondary, expanded: false), - ], -); - @ImmichPreview(group: 'TextButton', name: 'With Icons') Widget previewTextButtonWithIcons() => const Wrap( spacing: 12, @@ -42,7 +32,11 @@ Widget previewTextButtonWithIcons() => const Wrap( ); @ImmichPreview(group: 'TextButton', name: 'Loading') -Widget previewTextButtonLoading() => const _PreviewLoadingDemo(); +Widget previewTextButtonLoading() => ImmichTextButton( + onPressed: () => Future.delayed(const Duration(seconds: 2)), + labelText: 'Click me', + expanded: false, +); @ImmichPreview(group: 'TextButton', name: 'Disabled') Widget previewTextButtonDisabled() => const Wrap( @@ -59,30 +53,3 @@ Widget previewTextButtonDisabled() => const Wrap( ), ], ); - -class _PreviewLoadingDemo extends StatefulWidget { - const _PreviewLoadingDemo(); - - @override - State<_PreviewLoadingDemo> createState() => _PreviewLoadingDemoState(); -} - -class _PreviewLoadingDemoState extends State<_PreviewLoadingDemo> { - bool _isLoading = false; - - @override - Widget build(BuildContext context) { - return ImmichTextButton( - onPressed: () async { - setState(() => _isLoading = true); - await Future.delayed(const Duration(seconds: 2)); - if (mounted) { - setState(() => _isLoading = false); - } - }, - labelText: _isLoading ? 'Loading...' : 'Click Me', - loading: _isLoading, - expanded: false, - ); - } -} diff --git a/mobile/packages/ui/lib/src/snackbar.dart b/mobile/packages/ui/lib/src/snackbar.dart new file mode 100644 index 0000000000..a44be8d513 --- /dev/null +++ b/mobile/packages/ui/lib/src/snackbar.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; + +final scaffoldMessengerKey = GlobalKey(); + +class SnackbarManager { + const SnackbarManager(); + + ScaffoldFeatureController? show(String message, SnackbarType type) { + final messenger = scaffoldMessengerKey.currentState; + final context = scaffoldMessengerKey.currentContext; + if (messenger == null || context == null) { + return null; + } + + messenger.hideCurrentSnackBar(); + return messenger.showSnackBar(_build(context, message, type)); + } + + SnackBar _build(BuildContext context, String message, SnackbarType type) { + final theme = Theme.of(context); + final colors = theme.extension() ?? ImmichColors.harmonized(theme.colorScheme); + final (IconData icon, Color background, Color foreground) = switch (type) { + .info => (Icons.info_rounded, colors.info, colors.onInfo), + .success => (Icons.check_circle_rounded, colors.success, colors.onSuccess), + .error => (Icons.warning_rounded, colors.error, colors.onError), + }; + + return SnackBar( + behavior: .floating, + backgroundColor: background, + duration: const .new(seconds: 4), + shape: const RoundedRectangleBorder(borderRadius: .all(.circular(ImmichRadius.sm))), + content: Row( + children: [ + Icon(icon, color: foreground, size: ImmichIconSize.sm), + const SizedBox(width: ImmichSpacing.md), + Expanded( + child: Text( + message, + maxLines: 2, + overflow: .ellipsis, + style: .new(color: foreground, fontWeight: .w600, fontSize: ImmichTextSize.body), + ), + ), + ], + ), + ); + } + + ScaffoldFeatureController? info(String message) => show(message, .info); + + ScaffoldFeatureController? success(String message) => show(message, .success); + + ScaffoldFeatureController? error(String message) => show(message, .error); +} + +const snackbar = SnackbarManager(); diff --git a/mobile/packages/ui/lib/src/theme.dart b/mobile/packages/ui/lib/src/theme.dart index 891b41ee91..6ee4729810 100644 --- a/mobile/packages/ui/lib/src/theme.dart +++ b/mobile/packages/ui/lib/src/theme.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; import 'package:immich_ui/src/constants.dart'; +import 'package:material_color_utilities/blend/blend.dart'; +import 'package:material_color_utilities/hct/hct.dart'; +import 'package:material_color_utilities/palettes/tonal_palette.dart'; class ImmichThemeProvider extends StatelessWidget { final ColorScheme colorScheme; @@ -11,6 +14,7 @@ class ImmichThemeProvider extends StatelessWidget { Widget build(BuildContext context) { return Theme( data: Theme.of(context).copyWith( + extensions: [ImmichColors.harmonized(colorScheme)], colorScheme: colorScheme, brightness: colorScheme.brightness, inputDecorationTheme: InputDecorationTheme( @@ -19,8 +23,8 @@ class ImmichThemeProvider extends StatelessWidget { final color = states.contains(WidgetState.error) ? colorScheme.error : states.contains(WidgetState.focused) - ? colorScheme.primary - : colorScheme.outline; + ? colorScheme.primary + : colorScheme.outline; return OutlineInputBorder( borderSide: BorderSide(color: color), borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), @@ -38,3 +42,71 @@ class ImmichThemeProvider extends StatelessWidget { ); } } + +class ImmichColors extends ThemeExtension { + final Color info; + final Color onInfo; + final Color success; + final Color onSuccess; + final Color error; + final Color onError; + + const ImmichColors({ + required this.info, + required this.onInfo, + required this.success, + required this.onSuccess, + required this.error, + required this.onError, + }); + + factory ImmichColors.harmonized(ColorScheme scheme) { + final (info, onInfo) = scheme.harmonized(const Color(0xFF1984E9)); + final (success, onSuccess) = scheme.harmonized(const Color(0xFF10C14D)); + final (error, onError) = scheme.harmonized(const Color(0xFFFA2921)); + return ImmichColors( + info: info, + onInfo: onInfo, + success: success, + onSuccess: onSuccess, + error: error, + onError: onError, + ); + } + + @override + ImmichColors copyWith({Color? info, Color? onInfo, Color? success, Color? onSuccess, Color? error, Color? onError}) { + return ImmichColors( + info: info ?? this.info, + onInfo: onInfo ?? this.onInfo, + success: success ?? this.success, + onSuccess: onSuccess ?? this.onSuccess, + error: error ?? this.error, + onError: onError ?? this.onError, + ); + } + + @override + ImmichColors lerp(ImmichColors? other, double t) { + if (other == null) { + return this; + } + return ImmichColors( + info: Color.lerp(info, other.info, t)!, + onInfo: Color.lerp(onInfo, other.onInfo, t)!, + success: Color.lerp(success, other.success, t)!, + onSuccess: Color.lerp(onSuccess, other.onSuccess, t)!, + error: Color.lerp(error, other.error, t)!, + onError: Color.lerp(onError, other.onError, t)!, + ); + } +} + +extension on ColorScheme { + (Color container, Color onContainer) harmonized(Color seed) { + final hct = Hct.fromInt(Blend.harmonize(seed.toARGB32(), primary.toARGB32())); + final tones = TonalPalette.of(hct.hue, hct.chroma); + final isDark = brightness == Brightness.dark; + return (Color(tones.get(isDark ? 30 : 90)), Color(tones.get(isDark ? 90 : 10))); + } +} diff --git a/mobile/packages/ui/lib/src/types.dart b/mobile/packages/ui/lib/src/types.dart index 2c0c7b7760..8193eb88af 100644 --- a/mobile/packages/ui/lib/src/types.dart +++ b/mobile/packages/ui/lib/src/types.dart @@ -1,9 +1,5 @@ -enum ImmichVariant { - filled, - ghost, -} +enum ImmichVariant { filled, ghost } -enum ImmichColor { - primary, - secondary, -} +enum ImmichColor { primary, secondary } + +enum SnackbarType { info, success, error } diff --git a/mobile/packages/ui/pubspec.lock b/mobile/packages/ui/pubspec.lock index 9d11b49253..b58a985de1 100644 --- a/mobile/packages/ui/pubspec.lock +++ b/mobile/packages/ui/pubspec.lock @@ -92,7 +92,7 @@ packages: source: hosted version: "0.12.19" material_color_utilities: - dependency: transitive + dependency: "direct main" description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" diff --git a/mobile/packages/ui/pubspec.yaml b/mobile/packages/ui/pubspec.yaml index 1f44694ace..b924becdc5 100644 --- a/mobile/packages/ui/pubspec.yaml +++ b/mobile/packages/ui/pubspec.yaml @@ -7,6 +7,7 @@ environment: dependencies: flutter: sdk: flutter + material_color_utilities: any dev_dependencies: flutter_test: diff --git a/mobile/packages/ui/test/color_override_test.dart b/mobile/packages/ui/test/color_override_test.dart new file mode 100644 index 0000000000..ebd3b19c9d --- /dev/null +++ b/mobile/packages/ui/test/color_override_test.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_ui/src/color_override.dart'; +import 'package:immich_ui/src/components/icon_button.dart'; + +import 'test_utils.dart'; + +void main() { + group('ImmichColorOverride', () { + testWidgets('exposes the override color to descendants', (tester) async { + Color? captured; + await tester.pumpTestWidget( + ImmichColorOverride( + color: Colors.green, + child: Builder( + builder: (context) { + captured = ImmichColorOverride.maybeOf(context); + return const SizedBox.shrink(); + }, + ), + ), + ); + + expect(captured, Colors.green); + }); + + testWidgets('maybeOf returns null when there is no override', (tester) async { + Color? captured = Colors.black; + await tester.pumpTestWidget( + Builder( + builder: (context) { + captured = ImmichColorOverride.maybeOf(context); + return const SizedBox.shrink(); + }, + ), + ); + + expect(captured, isNull); + }); + + testWidgets('a descendant component adopts the override as its foreground', (tester) async { + await tester.pumpTestWidget( + ImmichColorOverride( + color: Colors.green, + child: ImmichIconButton(icon: Icons.add, onPressed: () {}), + ), + ); + + final button = tester.widget(find.byType(IconButton)); + expect(button.style?.foregroundColor?.resolve({}), Colors.green); + }); + }); +} diff --git a/mobile/packages/ui/test/snackbar_test.dart b/mobile/packages/ui/test/snackbar_test.dart new file mode 100644 index 0000000000..1cc57c6d55 --- /dev/null +++ b/mobile/packages/ui/test/snackbar_test.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_ui/src/snackbar.dart'; + +import 'test_utils.dart'; + +void main() { + group('SnackbarManager', () { + testWidgets('shows the message', (tester) async { + await tester.pumpTestWidget(const SizedBox()); + + snackbar.success('hello'); + await tester.pump(); + + expect(find.text('hello'), findsOneWidget); + expect(find.byType(SnackBar), findsOneWidget); + }); + + testWidgets('replaces the current snackbar', (tester) async { + await tester.pumpTestWidget(const SizedBox()); + + snackbar.info('first'); + await tester.pump(); + snackbar.error('second'); + await tester.pump(); + + expect(find.text('first'), findsNothing); + expect(find.text('second'), findsOneWidget); + }); + + testWidgets('no-ops when the messenger is unmounted', (tester) async { + expect(snackbar.show('x', .info), isNull); + }); + }); +} diff --git a/mobile/packages/ui/test/test_utils.dart b/mobile/packages/ui/test/test_utils.dart index 42cc74da87..36c166649b 100644 --- a/mobile/packages/ui/test/test_utils.dart +++ b/mobile/packages/ui/test/test_utils.dart @@ -1,9 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_ui/src/snackbar.dart'; extension WidgetTesterExtension on WidgetTester { - /// Pumps a widget wrapped in MaterialApp and Scaffold for testing. Future pumpTestWidget(Widget widget) { - return pumpWidget(MaterialApp(home: Scaffold(body: widget))); + return pumpWidget( + MaterialApp( + scaffoldMessengerKey: scaffoldMessengerKey, + home: Scaffold(body: widget), + ), + ); } } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 2a792bd29c..406199185c 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: "direct main" description: name: background_downloader - sha256: "4cb23d9ad4f5060944f38164e7b90d4bf99b57b2472a3bd4676e59b2db4afd06" + sha256: aceacec2b2a72ec3a8862ab5895fcbbc71ab33765f3619d57963f3110dd268e3 url: "https://pub.dev" source: hosted - version: "9.5.4" + version: "9.5.5" bonsoir: dependency: "direct overridden" description: @@ -229,10 +229,10 @@ packages: dependency: transitive description: name: code_assets - sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.2.1" code_builder: dependency: transitive description: @@ -326,18 +326,18 @@ packages: dependency: transitive description: name: dbus - sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + sha256: "792974a4007974fbc5c1b5433eb2330a9db3e368c3f906253af4c007d0f49a91" url: "https://pub.dev" source: hosted - version: "0.7.12" + version: "0.7.13" desktop_webview_window: dependency: transitive description: name: desktop_webview_window - sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0" + sha256: b6fdae2cbf9571879b1761c12f27facaf82e22d0bdc74d049907c2a09a432957 url: "https://pub.dev" source: hosted - version: "0.2.3" + version: "0.3.0" device_info_plus: dependency: "direct main" description: @@ -549,18 +549,18 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" + sha256: "9db4b80b044e9af17cc4b1272137fc7ace0054d879ef8210a76adc34aaf4cdff" url: "https://pub.dev" source: hosted - version: "2.4.7" + version: "2.4.8" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785" url: "https://pub.dev" source: hosted - version: "2.0.34" + version: "2.0.35" flutter_riverpod: dependency: transitive description: @@ -642,10 +642,10 @@ packages: dependency: "direct main" description: name: flutter_web_auth_2 - sha256: d354998934ddc338e69b999b2abaeb33c6fd09999d3a5f92ead1a6b49b49712e + sha256: "8f9303471dcd96670878c9b7c0c4e14c37595b2add67465f6a868f17a5872dfc" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" flutter_web_auth_2_platform_interface: dependency: transitive description: @@ -780,10 +780,10 @@ packages: dependency: transitive description: name: hooks - sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "2.0.2" hooks_riverpod: dependency: "direct main" description: @@ -844,10 +844,10 @@ packages: dependency: transitive description: name: image - sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52" url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.9.1" image_picker: dependency: "direct main" description: @@ -860,10 +860,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f + sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a" url: "https://pub.dev" source: hosted - version: "0.8.13+17" + version: "0.8.13+19" image_picker_for_web: dependency: transitive description: @@ -1120,10 +1120,10 @@ packages: dependency: transitive description: name: native_toolchain_c - sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96 url: "https://pub.dev" source: hosted - version: "0.17.6" + version: "0.19.1" native_video_player: dependency: "direct main" description: @@ -1161,10 +1161,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" url: "https://pub.dev" source: hosted - version: "9.3.0" + version: "9.4.1" octo_image: dependency: "direct main" description: @@ -1297,10 +1297,10 @@ packages: dependency: transitive description: name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + sha256: e20daf680eef1ca62ffe8c8c526b778cc386d50137c77ac71c8ec9c88c13fb9d url: "https://pub.dev" source: hosted - version: "9.4.7" + version: "9.4.9" permission_handler_html: dependency: transitive description: @@ -1529,10 +1529,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + sha256: a2c49fc1fed7140cadd892d765bd47edbe4ac0b9c7e7e3c493dcb58126f99cf0 url: "https://pub.dev" source: hosted - version: "2.4.23" + version: "2.4.25" shared_preferences_foundation: dependency: transitive description: @@ -1791,10 +1791,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" + sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 url: "https://pub.dev" source: hosted - version: "6.3.30" + version: "6.3.32" url_launcher_ios: dependency: transitive description: @@ -1871,10 +1871,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: b9b3f391857781aa96acacef96066f2f49b4cd03cf9fce3ca4d8da2ef5ea129e + sha256: "7ee12e6dffe0fc8e755179d6d91b3b34f5924223fc104d85572ef9180d73d172" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.5" vector_math: dependency: transitive description: @@ -1991,10 +1991,10 @@ packages: dependency: transitive description: name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "7.0.1" xxh3: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 2322d0fa39..7d0f915641 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -2,7 +2,7 @@ name: immich_mobile description: Immich - selfhosted backup media file on mobile phone publish_to: 'none' -version: 3.0.0-rc.1+3049 +version: 3.0.0-rc.2+3050 environment: sdk: '>=3.12.0 <4.0.0' diff --git a/mobile/test/domain/service.mock.dart b/mobile/test/domain/service.mock.dart index 743d75f1bf..70f706f7fe 100644 --- a/mobile/test/domain/service.mock.dart +++ b/mobile/test/domain/service.mock.dart @@ -1,5 +1,7 @@ +import 'package:immich_mobile/domain/services/asset.service.dart'; import 'package:immich_mobile/domain/services/partner.service.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/utils/background_sync.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; @@ -14,3 +16,7 @@ class MockNativeSyncApi extends Mock implements NativeSyncApi {} class MockAppSettingsService extends Mock implements AppSettingsService {} class MockPartnerService extends Mock implements PartnerService {} + +class MockAssetService extends Mock implements AssetService {} + +class MockUserService extends Mock implements UserService {} diff --git a/mobile/test/modules/utils/version_compatibility_test.dart b/mobile/test/modules/utils/version_compatibility_test.dart index 6f1abc218f..70f3d9ef8c 100644 --- a/mobile/test/modules/utils/version_compatibility_test.dart +++ b/mobile/test/modules/utils/version_compatibility_test.dart @@ -1,29 +1,47 @@ import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/utils/semver.dart'; import 'package:immich_mobile/utils/version_compatibility.dart'; void main() { - test('getVersionCompatibilityMessage', () { - String? result; + group('app major version behind server', () { + const message = + 'Your mobile app version is not compatible with the server! Please update your mobile app to the latest version.'; - result = getVersionCompatibilityMessage(1, 106, 1, 105); - expect( - result, - 'Your app minor version is not compatible with the server! Please update your server to version v1.106.0 or newer to login', - ); + test('returns message when app major is behind server major', () { + final result = getVersionCompatibilityMessage( + const SemVer(major: 2, minor: 0, patch: 0), + const SemVer(major: 1, minor: 200, patch: 0), + ); + expect(result, message); + }); - result = getVersionCompatibilityMessage(1, 107, 1, 105); - expect( - result, - 'Your app minor version is not compatible with the server! Please update your server to version v1.106.0 or newer to login', - ); + test('returns null when app major matches server major', () { + final result = getVersionCompatibilityMessage( + const SemVer(major: 2, minor: 0, patch: 0), + const SemVer(major: 2, minor: 0, patch: 0), + ); + expect(result, null); + }); + }); - result = getVersionCompatibilityMessage(1, 106, 1, 106); - expect(result, null); + group('app major version too far ahead of server', () { + const message = + 'Your server version is not compatible with the mobile app! Please update your server to the latest version.'; - result = getVersionCompatibilityMessage(1, 107, 1, 106); - expect(result, null); + test('returns message when app major is more than one ahead of server', () { + final result = getVersionCompatibilityMessage( + const SemVer(major: 1, minor: 200, patch: 0), + const SemVer(major: 3, minor: 0, patch: 0), + ); + expect(result, message); + }); - result = getVersionCompatibilityMessage(1, 107, 1, 108); - expect(result, null); + test('returns null when app major is exactly one ahead of server', () { + final result = getVersionCompatibilityMessage( + const SemVer(major: 1, minor: 200, patch: 0), + const SemVer(major: 2, minor: 0, patch: 0), + ); + expect(result, null); + }); }); } diff --git a/mobile/test/providers/asset_viewer/asset_viewer_provider_test.dart b/mobile/test/providers/asset_viewer/asset_viewer_provider_test.dart new file mode 100644 index 0000000000..67eb1dd9d1 --- /dev/null +++ b/mobile/test/providers/asset_viewer/asset_viewer_provider_test.dart @@ -0,0 +1,111 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../domain/service.mock.dart'; +import '../../unit/factories/remote_asset_factory.dart'; + +void main() { + late ProviderContainer container; + late MockAssetService assetService; + + setUpAll(() => registerFallbackValue(RemoteAssetFactory.create())); + + setUp(() { + assetService = MockAssetService(); + when(() => assetService.watchAsset(any())).thenAnswer((_) => const Stream.empty()); + + container = ProviderContainer(overrides: [assetServiceProvider.overrideWithValue(assetService)]); + addTearDown(container.dispose); + }); + + group('AssetViewerStateNotifier asset watching', () { + test('propagates stream updates for the current asset into state', () async { + final controller = StreamController(); + addTearDown(controller.close); + final asset = RemoteAssetFactory.create(); + when(() => assetService.watchAsset(asset)).thenAnswer((_) => controller.stream); + + final notifier = container.read(assetViewerProvider.notifier); + notifier.setAsset(asset); + + final updated = asset.copyWith(isFavorite: true); + controller.add(updated); + await pumpEventQueue(); + + expect(container.read(assetViewerProvider).currentAsset, updated); + }); + + test('ignores null stream emissions', () async { + final controller = StreamController(); + addTearDown(controller.close); + final asset = RemoteAssetFactory.create(); + when(() => assetService.watchAsset(asset)).thenAnswer((_) => controller.stream); + + container.read(assetViewerProvider.notifier).setAsset(asset); + + controller.add(null); + await pumpEventQueue(); + + expect(container.read(assetViewerProvider).currentAsset, asset); + }); + + test('reset cancels the subscription so later emissions are dropped', () async { + final controller = StreamController(); + addTearDown(controller.close); + final asset = RemoteAssetFactory.create(); + when(() => assetService.watchAsset(asset)).thenAnswer((_) => controller.stream); + + final notifier = container.read(assetViewerProvider.notifier); + notifier.setAsset(asset); + notifier.reset(); + + controller.add(RemoteAssetFactory.create(isFavorite: true)); + await pumpEventQueue(); + + expect(container.read(assetViewerProvider).currentAsset, isNull); + }); + + test('setAsset switches the current asset, cancels the previous watch and listens to the new one', () async { + final first = StreamController(); + final second = StreamController(); + addTearDown(first.close); + addTearDown(second.close); + + final assetOne = RemoteAssetFactory.create(); + final assetTwo = RemoteAssetFactory.create(); + when(() => assetService.watchAsset(assetOne)).thenAnswer((_) => first.stream); + when(() => assetService.watchAsset(assetTwo)).thenAnswer((_) => second.stream); + + final notifier = container.read(assetViewerProvider.notifier); + notifier.setAsset(assetOne); + expect(container.read(assetViewerProvider).currentAsset, assetOne); + + // Updates to the first asset propagate into state. + final updatedOne = assetOne.copyWith(visibility: .archive); + first.add(updatedOne); + await pumpEventQueue(); + expect(container.read(assetViewerProvider).currentAsset, updatedOne); + + // Switch to new asset + notifier.setAsset(assetTwo); + expect(container.read(assetViewerProvider).currentAsset, assetTwo); + + // The previous watch is cancelled: stale emissions from the first stream are dropped. + first.add(assetOne.copyWith(isFavorite: true)); + await pumpEventQueue(); + expect(container.read(assetViewerProvider).currentAsset, assetTwo); + + // The new asset is watched instead: its emissions propagate into state. + final updatedTwo = assetTwo.copyWith(isFavorite: true); + second.add(updatedTwo); + await pumpEventQueue(); + expect(container.read(assetViewerProvider).currentAsset, updatedTwo); + }); + }); +} diff --git a/mobile/test/providers/infrastructure/memory_provider_test.dart b/mobile/test/providers/infrastructure/memory_provider_test.dart new file mode 100644 index 0000000000..e588153fc9 --- /dev/null +++ b/mobile/test/providers/infrastructure/memory_provider_test.dart @@ -0,0 +1,96 @@ +import 'package:fake_async/fake_async.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/memory.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockDriftMemoryService extends Mock implements DriftMemoryService {} + +class MockUserService extends Mock implements UserService {} + +void main() { + late MockDriftMemoryService memoryService; + late MockUserService userService; + + UserDto user({bool memoryEnabled = true}) => UserDto( + id: 'user-1', + email: 'user@test.dev', + name: 'user', + memoryEnabled: memoryEnabled, + profileChangedAt: DateTime(2026), + ); + + ProviderContainer makeContainer() { + final container = ProviderContainer( + overrides: [ + driftMemoryServiceProvider.overrideWithValue(memoryService), + currentUserProvider.overrideWith((ref) => CurrentUserProvider(userService)), + ], + ); + addTearDown(container.dispose); + return container; + } + + setUp(() { + memoryService = MockDriftMemoryService(); + userService = MockUserService(); + + when(() => memoryService.getMemoryLane('user-1')).thenAnswer((_) async => []); + when(() => userService.tryGetMyUser()).thenReturn(user()); + when(() => userService.watchMyUser()).thenAnswer((_) => const Stream.empty()); + }); + + group('driftMemoryFutureProvider', () { + test('re-queries after local midnight', () { + fakeAsync((async) { + final container = makeContainer(); + container.listen(driftMemoryFutureProvider, (_, __) {}); + async.flushMicrotasks(); + + verify(() => memoryService.getMemoryLane('user-1')).called(1); + + async.elapse(const Duration(seconds: 4)); + async.flushMicrotasks(); + verifyNever(() => memoryService.getMemoryLane('user-1')); + + async.elapse(const Duration(hours: 25)); + async.flushMicrotasks(); + verify(() => memoryService.getMemoryLane('user-1')).called(greaterThanOrEqualTo(1)); + }); + }); + + test('cancels the midnight timer when disposed', () { + fakeAsync((async) { + final container = makeContainer(); + final subscription = container.listen(driftMemoryFutureProvider, (_, __) {}); + async.flushMicrotasks(); + verify(() => memoryService.getMemoryLane('user-1')).called(1); + + subscription.close(); + async.elapse(const Duration(hours: 25)); + async.flushMicrotasks(); + + verifyNever(() => memoryService.getMemoryLane('user-1')); + }); + }); + + test('does not query or arm the timer when memories are disabled', () { + when(() => userService.tryGetMyUser()).thenReturn(user(memoryEnabled: false)); + + fakeAsync((async) { + final container = makeContainer(); + container.listen(driftMemoryFutureProvider, (_, __) {}); + async.flushMicrotasks(); + + async.elapse(const Duration(hours: 25)); + async.flushMicrotasks(); + + verifyNever(() => memoryService.getMemoryLane(any())); + }); + }); + }); +} diff --git a/mobile/test/providers/view_intent/view_intent_handler_android_test.dart b/mobile/test/providers/view_intent/view_intent_handler_android_test.dart index 9bf3fec400..1c965db68a 100644 --- a/mobile/test/providers/view_intent/view_intent_handler_android_test.dart +++ b/mobile/test/providers/view_intent/view_intent_handler_android_test.dart @@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/services/asset.service.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/services/asset.service.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/models/auth/auth_state.model.dart'; @@ -14,14 +15,15 @@ import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart' import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/view_intent/view_intent_current.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/view_intent/view_intent_handler_android.dart'; import 'package:immich_mobile/providers/view_intent/view_intent_pending.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/services/auth.service.dart'; +import 'package:immich_mobile/services/secure_storage.service.dart'; import 'package:immich_mobile/services/view_intent.service.dart'; import 'package:immich_mobile/services/view_intent_asset_resolver.service.dart'; -import 'package:immich_mobile/services/auth.service.dart'; -import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/services/secure_storage.service.dart'; import 'package:immich_mobile/services/widget.service.dart'; import 'package:mocktail/mocktail.dart'; @@ -47,6 +49,11 @@ class FakePageRouteInfo extends Fake implements PageRouteInfo {} class FakeTimelineService extends Fake implements TimelineService {} +class FakeAssetService extends Fake implements AssetService { + @override + Stream watchAsset(BaseAsset asset) => const Stream.empty(); +} + class TestViewIntentService extends ViewIntentService { ViewIntentPayload? consumedAttachment; int cleanupStaleTempFilesCalls = 0; @@ -141,6 +148,7 @@ void main() { authNotifier = TestAuthNotifier(ref, _authState(isAuthenticated: true)); return authNotifier; }), + assetServiceProvider.overrideWithValue(FakeAssetService()), ], ); diff --git a/mobile/test/unit/factories/remote_asset_factory.dart b/mobile/test/unit/factories/remote_asset_factory.dart new file mode 100644 index 0000000000..669eb3998a --- /dev/null +++ b/mobile/test/unit/factories/remote_asset_factory.dart @@ -0,0 +1,23 @@ +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; + +import '../../utils.dart'; + +class RemoteAssetFactory { + const RemoteAssetFactory(); + + static RemoteAsset create({String? id, String? name, String? ownerId, bool isFavorite = false}) { + id = TestUtils.uuid(id); + + return RemoteAsset( + id: id, + name: name ?? 'remote_$id.jpg', + ownerId: TestUtils.uuid(ownerId), + checksum: 'checksum-$id', + type: .image, + createdAt: TestUtils.yesterday(), + updatedAt: TestUtils.now(), + isFavorite: isFavorite, + isEdited: false, + ); + } +} diff --git a/mobile/test/unit/factories/user_factory.dart b/mobile/test/unit/factories/user_factory.dart index c89b03abfe..248e98dc69 100644 --- a/mobile/test/unit/factories/user_factory.dart +++ b/mobile/test/unit/factories/user_factory.dart @@ -23,4 +23,23 @@ class UserFactory { avatarColor: avatarColor ?? .primary, ); } + + static UserDto createDto({ + String? id, + String? name, + String? email, + DateTime? profileChangedAt, + bool? hasProfileImage, + AvatarColor? avatarColor, + }) { + id = TestUtils.uuid(id); + return UserDto( + id: id, + name: name ?? 'user_$id', + email: email ?? '$id@test.com', + profileChangedAt: TestUtils.date(profileChangedAt), + hasProfileImage: hasProfileImage ?? false, + avatarColor: avatarColor ?? .primary, + ); + } } diff --git a/mobile/test/unit/mocks.dart b/mobile/test/unit/mocks.dart index 4f8e608caa..69260d343d 100644 --- a/mobile/test/unit/mocks.dart +++ b/mobile/test/unit/mocks.dart @@ -1,24 +1,15 @@ -import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:mocktail/mocktail.dart' as mocktail; +import 'dart:typed_data'; + +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:mocktail/mocktail.dart' as mock; +import 'package:mocktail/mocktail.dart'; import '../domain/service.mock.dart'; import '../infrastructure/repository.mock.dart'; - -void _registerFallbacks() { - mocktail.registerFallbackValue(LocalAlbum(id: '', name: '', updatedAt: DateTime.now())); - mocktail.registerFallbackValue( - LocalAsset( - id: '', - name: '', - type: AssetType.image, - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - playbackStyle: AssetPlaybackStyle.image, - isEdited: false, - ), - ); -} +import 'factories/local_album_factory.dart'; +import 'factories/local_asset_factory.dart'; +import 'factories/user_factory.dart'; class RepositoryMocks { final localAlbum = MockLocalAlbumRepository(); @@ -28,25 +19,103 @@ class RepositoryMocks { final nativeApi = MockNativeSyncApi(); RepositoryMocks() { - _registerFallbacks(); + resetAll(); } - void reset() { - mocktail.reset(localAlbum); - mocktail.reset(localAsset); - mocktail.reset(trashedAsset); - mocktail.reset(nativeApi); + void resetAll() { + _registerFallbacks(); + reset(localAlbum); + reset(localAsset); + reset(trashedAsset); + reset(nativeApi); } } class ServiceMocks { - final partner = MockPartnerService(); + final PartnerStub partner = PartnerStub(MockPartnerService()); + final UserStub user = UserStub(MockUserService()); ServiceMocks() { - _registerFallbacks(); + resetAll(); } - void reset() { - mocktail.reset(partner); + void resetAll() { + _registerFallbacks(); + partner.reset(); + user.reset(); + _stubUserService(); + _stubPartnerService(); + } + + void _stubUserService() { + when(user.getMyUser).thenReturn(UserFactory.createDto()); + when(user.tryGetMyUser).thenReturn(null); + when(user.watchMyUser).thenAnswer((_) => const Stream.empty()); + when(user.refreshMyUser).thenAnswer((_) async => null); + when(user.createProfileImage).thenAnswer((_) async => null); + } + + void _stubPartnerService() { + registerFallbackValue(PartnerDirection.sharedBy); + when(partner.getCandidates).thenAnswer((_) => const Stream.empty()); + when(partner.search).thenAnswer((_) => const Stream.empty()); + when(partner.update).thenAnswer((_) async {}); + when(partner.create).thenAnswer((_) async {}); + when(partner.delete).thenAnswer((_) async {}); } } + +void _registerFallbacks() { + registerFallbackValue(LocalAlbumFactory.create()); + registerFallbackValue(LocalAssetFactory.create()); + registerFallbackValue(Uint8List(0)); +} + +extension type const Stub(T mockedService) { + void reset() => mock.reset(mockedService); +} + +extension type const PartnerStub(MockPartnerService service) implements Stub { + Stream> Function() get getCandidates => + () => service.getCandidates(any()); + + Stream> Function() get search => + () => service.search(any(), any()); + + Future Function() get create => + () => service.create( + sharedById: any(named: 'sharedById'), + sharedWithId: any(named: 'sharedWithId'), + inTimeline: any(named: 'inTimeline'), + ); + + Future Function() get update => + () => service.update( + sharedById: any(named: 'sharedById'), + sharedWithId: any(named: 'sharedWithId'), + inTimeline: any(named: 'inTimeline'), + ); + + Future Function() get delete => + () => service.delete( + sharedById: any(named: 'sharedById'), + sharedWithId: any(named: 'sharedWithId'), + ); +} + +extension type const UserStub(MockUserService service) implements Stub { + UserDto Function() get getMyUser => + () => service.getMyUser(); + + UserDto? Function() get tryGetMyUser => + () => service.tryGetMyUser(); + + Stream Function() get watchMyUser => + () => service.watchMyUser(); + + Future Function() get refreshMyUser => + () => service.refreshMyUser(); + + Future Function() get createProfileImage => + () => service.createProfileImage(any(), any()); +} diff --git a/mobile/test/unit/presentation/actions/partner_action_test.dart b/mobile/test/unit/presentation/actions/partner_action_test.dart new file mode 100644 index 0000000000..1eb89fe4cc --- /dev/null +++ b/mobile/test/unit/presentation/actions/partner_action_test.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/presentation/actions/partner.action.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../factories/user_factory.dart'; +import '../../mocks.dart'; +import '../../presentation_context.dart'; + +void main() { + late PresentationContext context; + late UserDto currentUser; + final mocks = ServiceMocks(); + + setUp(() async { + currentUser = UserFactory.createDto(); + context = await PresentationContext.create(); + when(mocks.user.tryGetMyUser).thenReturn(currentUser); + }); + + tearDown(() async { + mocks.resetAll(); + await context.dispose(); + }); + + List overrides({List candidates = const []}) => [ + currentUserProvider.overrideWith((ref) => CurrentUserProvider(mocks.user.service)), + partnerServiceProvider.overrideWithValue(mocks.partner.service), + candidatesStateProvider.overrideWith((ref) => Stream>.value(candidates)), + ]; + + group('PartnerAddAction', () { + testWidgets('creates a partner for the selected candidate', (tester) async { + final candidate = UserFactory.create(); + + await tester.pumpTestAction(const PartnerAddAction(), overrides: overrides(candidates: [candidate])); + await tester.pumpUntilFound(find.text(candidate.name)); + await tester.tap(find.text(candidate.name)); + await tester.pumpAndSettle(); + + verify(() => mocks.partner.service.create(sharedById: currentUser.id, sharedWithId: candidate.id)).called(1); + }); + + testWidgets('creates nothing when the selection dialog is dismissed', (tester) async { + await tester.pumpTestAction(const PartnerAddAction(), overrides: overrides(candidates: [UserFactory.create()])); + await tester.sendKeyEvent(LogicalKeyboardKey.escape); // dismiss without selecting + await tester.pumpAndSettle(); + + verifyNever(mocks.partner.create); + }); + }); + + group('PartnerRemoveAction', () { + testWidgets('deletes the partner after confirmation', (tester) async { + final partner = UserFactory.create(); + await tester.pumpTestAction( + PartnerRemoveAction(sharedWithId: partner.id, partnerName: partner.name), + overrides: overrides(), + ); + await tester.tap(find.byType(TextButton).last); // confirm + await tester.pumpAndSettle(); + + verify(() => mocks.partner.service.delete(sharedById: currentUser.id, sharedWithId: partner.id)).called(1); + }); + + testWidgets('deletes nothing when the confirmation is cancelled', (tester) async { + final partner = UserFactory.create(); + await tester.pumpTestAction( + PartnerRemoveAction(sharedWithId: partner.id, partnerName: partner.name), + overrides: overrides(), + ); + await tester.tap(find.byType(TextButton).first); // cancel + await tester.pumpAndSettle(); + + verifyNever(mocks.partner.delete); + }); + }); +} diff --git a/mobile/test/unit/presentation/partner_page_test.dart b/mobile/test/unit/presentation/partner_page_test.dart index 957a915ad3..4557388dc0 100644 --- a/mobile/test/unit/presentation/partner_page_test.dart +++ b/mobile/test/unit/presentation/partner_page_test.dart @@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/pages/library/partner/partner.page.dart'; +import 'package:immich_mobile/presentation/actions/partner.action.dart'; import '../factories/partner_user_factory.dart'; import '../factories/user_factory.dart'; @@ -16,16 +17,18 @@ void main() { group('PartnerSharedByList', () { testWidgets('shows the empty-state add button when there are no partners', (tester) async { - await tester.pumpTestWidget(PartnerSharedByList(partners: const [], onAdd: () {}, onRemove: (_) {})); + final action = const PartnerAddAction(); + + await tester.pumpTestWidget(const PartnerSharedByList(partners: []), overrides: context.overrides); expect(find.byType(ListView), findsNothing); - expect(find.widgetWithIcon(ElevatedButton, Icons.person_add), findsOneWidget); + expect(find.widgetWithIcon(TextButton, action.icon), findsOneWidget); }); testWidgets('renders a tile per partner with name and email', (tester) async { final partner1 = PartnerFactory.create(); final partner2 = PartnerFactory.create(); - await tester.pumpTestWidget(PartnerSharedByList(partners: [partner1, partner2], onAdd: () {}, onRemove: (_) {})); + await tester.pumpTestWidget(PartnerSharedByList(partners: [partner1, partner2]), overrides: context.overrides); expect(find.byType(ListTile), findsNWidgets(2)); expect(find.text(partner1.name), findsOneWidget); @@ -34,18 +37,12 @@ void main() { expect(find.text(partner2.email), findsOneWidget); }); - testWidgets('invokes onRemovePartner with the tapped partner', (tester) async { + testWidgets('renders a remove action for each partner', (tester) async { final partner1 = PartnerFactory.create(inTimeline: true); final partner2 = PartnerFactory.create(); - Partner? removed; - await tester.pumpTestWidget( - PartnerSharedByList(partners: [partner1, partner2], onAdd: () {}, onRemove: (p) => removed = p), - ); - - await tester.tap(find.byIcon(Icons.person_remove).first); - await tester.pump(); - - expect(removed, partner1); + final action = const PartnerRemoveAction(sharedWithId: '', partnerName: ''); + await tester.pumpTestWidget(PartnerSharedByList(partners: [partner1, partner2]), overrides: context.overrides); + expect(find.byIcon(action.icon), findsNWidgets(2)); }); }); @@ -65,6 +62,7 @@ void main() { } List withCandidates(List candidates) => [ + ...context.overrides, candidatesStateProvider.overrideWith((ref) => Stream>.value(candidates)), ]; diff --git a/mobile/test/unit/presentation_context.dart b/mobile/test/unit/presentation_context.dart index 97b09ba85e..e411b21802 100644 --- a/mobile/test/unit/presentation_context.dart +++ b/mobile/test/unit/presentation_context.dart @@ -6,20 +6,35 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/presentation/actions/action.dart'; +import 'package:immich_mobile/presentation/actions/action.widget.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_ui/immich_ui.dart'; +import 'package:mocktail/mocktail.dart'; import '../test_utils.dart'; +import 'factories/user_factory.dart'; +import 'mocks.dart'; class PresentationContext { - const PresentationContext._(); + PresentationContext._({required UserDto user}) : currentUser = user, mocks = ServiceMocks() { + when(mocks.user.tryGetMyUser).thenReturn(currentUser); + } static const String serverEndpoint = 'http://localhost:3000'; static Drift? _db; + final UserDto currentUser; + final ServiceMocks mocks; + + List get overrides => [currentUserProvider.overrideWith((ref) => CurrentUserProvider(mocks.user.service))]; + static Future create() async { TestUtils.init(); if (_db == null) { @@ -28,7 +43,7 @@ class PresentationContext { await StoreService.I.put(StoreKey.serverEndpoint, serverEndpoint); _db = db; } - return const PresentationContext._(); + return PresentationContext._(user: UserFactory.createDto()); } Future dispose() async { @@ -54,6 +69,7 @@ extension PumpPresentationWidget on WidgetTester { child: Builder( builder: (context) => MaterialApp( debugShowCheckedModeBanner: false, + scaffoldMessengerKey: scaffoldMessengerKey, localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, @@ -65,4 +81,23 @@ extension PumpPresentationWidget on WidgetTester { ); await pumpAndSettle(); } + + Future pumpTestAction(BaseAction action, {List overrides = const []}) async { + await pumpTestWidget( + Scaffold(body: ActionIconButtonWidget(action: action)), + overrides: overrides, + ); + await tap(find.byType(ImmichIconButton)); + await pump(); + } + + Future pumpUntilFound(Finder finder, {int maxFrames = 10}) async { + for (var i = 0; i < maxFrames; i++) { + await pump(); + if (finder.evaluate().isNotEmpty) { + return; + } + } + throw StateError('pumpUntilFound: $finder not found within $maxFrames frames'); + } } diff --git a/mobile/test/unit/services/hash_service_test.dart b/mobile/test/unit/services/hash_service_test.dart index 223aaf49af..07cf2badf9 100644 --- a/mobile/test/unit/services/hash_service_test.dart +++ b/mobile/test/unit/services/hash_service_test.dart @@ -25,7 +25,7 @@ void main() { }); tearDown(() { - mocks.reset(); + mocks.resetAll(); }); group('HashService', () { diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 481f94b3dd..f3b4e99cd9 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -4924,6 +4924,15 @@ "maximum": 9007199254740991, "type": "integer" } + }, + { + "name": "x-immich-hls-pos", + "required": false, + "in": "header", + "schema": { + "minimum": 0, + "type": "number" + } } ], "responses": { @@ -6943,7 +6952,7 @@ "description": "Filter assets created after this date", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -6955,7 +6964,7 @@ "description": "Filter assets created before this date", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -7131,7 +7140,7 @@ "description": "Filter by date", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -7300,7 +7309,7 @@ "description": "Filter by date", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -10177,7 +10186,7 @@ "description": "Filter by creation date (after)", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -10189,7 +10198,7 @@ "description": "Filter by creation date (before)", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -10391,7 +10400,7 @@ "description": "Filter by taken date (after)", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -10403,7 +10412,7 @@ "description": "Filter by taken date (before)", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -10415,7 +10424,7 @@ "description": "Filter by trash date (after)", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -10427,7 +10436,7 @@ "description": "Filter by trash date (before)", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -10447,7 +10456,7 @@ "description": "Filter by update date (after)", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -10459,7 +10468,7 @@ "description": "Filter by update date (before)", "schema": { "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "example": "2024-01-01T00:00:00.000Z", "type": "string" } @@ -16197,7 +16206,7 @@ "info": { "title": "Immich", "description": "Immich API", - "version": "3.0.0-rc.1", + "version": "3.0.0-rc.2", "contact": {} }, "tags": [ @@ -16426,7 +16435,7 @@ "description": "Creation date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "id": { @@ -16508,7 +16517,9 @@ }, "albumThumbnailAssetId": { "description": "Thumbnail asset ID", + "format": "uuid", "nullable": true, + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "albumUsers": { @@ -16551,6 +16562,8 @@ }, "id": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isActivityEnabled": { @@ -16788,11 +16801,13 @@ "description": "Creation date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "id": { "description": "API key ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "name": { @@ -16810,7 +16825,7 @@ "description": "Last update date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -16997,10 +17012,12 @@ }, "assetId": { "description": "Existing asset ID if duplicate", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "id": { - "description": "Asset ID", + "description": "Client-side identifier echoed from the request to match results to inputs", "type": "string" }, "isTrashed": { @@ -17379,6 +17396,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "error": { @@ -17443,14 +17462,14 @@ "description": "File creation date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "fileModifiedAt": { "description": "File modification date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "filename": { @@ -17494,6 +17513,8 @@ "properties": { "id": { "description": "Asset media ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "status": { @@ -17562,6 +17583,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "key": { @@ -17572,7 +17595,7 @@ "description": "Last update date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "value": { @@ -17639,7 +17662,7 @@ "description": "Last update date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "value": { @@ -17809,7 +17832,9 @@ }, "duplicateId": { "description": "Duplicate group ID", + "format": "uuid", "nullable": true, + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "duration": { @@ -17845,6 +17870,8 @@ }, "id": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isArchived": { @@ -17923,6 +17950,8 @@ }, "ownerId": { "description": "Owner user ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "people": { @@ -18020,10 +18049,14 @@ }, "id": { "description": "Stack ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "primaryAssetId": { "description": "Primary asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -18159,6 +18192,8 @@ }, "id": { "description": "ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "success": { @@ -18337,6 +18372,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -18432,7 +18469,7 @@ "description": "Profile image change date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "profileImagePath": { @@ -18441,6 +18478,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -18600,6 +18639,8 @@ "assetIds": { "description": "Asset IDs in this archive", "items": { + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "type": "array" @@ -18785,6 +18826,8 @@ }, "duplicateId": { "description": "Duplicate group ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "suggestedKeepAssetIds": { @@ -19101,6 +19144,8 @@ "properties": { "id": { "description": "Integrity report item id", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "path": { @@ -19263,7 +19308,7 @@ "description": "Creation date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "exclusionPatterns": { @@ -19275,6 +19320,8 @@ }, "id": { "description": "Library ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "importPaths": { @@ -19290,6 +19337,8 @@ }, "ownerId": { "description": "Owner user ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "refreshedAt": { @@ -19297,14 +19346,14 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "updatedAt": { "description": "Last update date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -19444,6 +19493,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -19634,6 +19685,8 @@ }, "id": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "lat": { @@ -19739,7 +19792,7 @@ "description": "Date when memory should be hidden", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string", "x-immich-history": [ { @@ -19761,21 +19814,21 @@ "description": "Memory date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "seenAt": { "description": "Date when memory was seen", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "showAt": { "description": "Date when memory should be shown", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string", "x-immich-history": [ { @@ -19812,7 +19865,7 @@ "description": "Creation date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "data": { @@ -19822,18 +19875,20 @@ "description": "Deletion date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "hideAt": { "description": "Date when memory should be hidden", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "id": { "description": "Memory ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isSaved": { @@ -19844,25 +19899,27 @@ "description": "Memory date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "ownerId": { "description": "Owner user ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "seenAt": { "description": "Date when memory was seen", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "showAt": { "description": "Date when memory should be shown", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "type": { @@ -19872,7 +19929,7 @@ "description": "Last update date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -19929,14 +19986,14 @@ "description": "Memory date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "seenAt": { "description": "Date when memory was seen", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -19988,14 +20045,14 @@ "description": "Filter by creation date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "createdBefore": { "description": "Filter by creation date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "description": { @@ -20143,14 +20200,14 @@ "description": "Filter by taken date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "takenBefore": { "description": "Filter by taken date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "thumbnailPath": { @@ -20161,14 +20218,14 @@ "description": "Filter by trash date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "trashedBefore": { "description": "Filter by trash date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "type": { @@ -20178,14 +20235,14 @@ "description": "Filter by update date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "updatedBefore": { "description": "Filter by update date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "visibility": { @@ -20249,7 +20306,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "title": { @@ -20296,7 +20353,7 @@ "description": "Creation date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "data": { @@ -20310,6 +20367,8 @@ }, "id": { "description": "Notification ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "level": { @@ -20319,7 +20378,7 @@ "description": "Date when notification was read", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "title": { @@ -20378,7 +20437,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -20394,7 +20453,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -20754,6 +20813,8 @@ }, "id": { "description": "Person ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isFavorite": { @@ -20989,6 +21050,8 @@ }, "id": { "description": "Person ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isFavorite": { @@ -21244,6 +21307,8 @@ }, "id": { "description": "Plugin ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "methods": { @@ -21708,14 +21773,14 @@ "description": "Filter by creation date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "createdBefore": { "description": "Filter by creation date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "isEncoded": { @@ -21826,28 +21891,28 @@ "description": "Filter by taken date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "takenBefore": { "description": "Filter by taken date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "trashedAfter": { "description": "Filter by trash date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "trashedBefore": { "description": "Filter by trash date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "type": { @@ -21857,14 +21922,14 @@ "description": "Filter by update date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "updatedBefore": { "description": "Filter by update date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "visibility": { @@ -22634,11 +22699,13 @@ "description": "When this version was first seen", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "id": { "description": "Version history entry ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "version": { @@ -22743,6 +22810,8 @@ }, "id": { "description": "Session ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isPendingSyncReset": { @@ -22800,6 +22869,8 @@ }, "id": { "description": "Session ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isPendingSyncReset": { @@ -22900,7 +22971,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "password": { @@ -22947,7 +23018,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "password": { @@ -23004,7 +23075,7 @@ "description": "Creation date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "description": { @@ -23017,11 +23088,13 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "id": { "description": "Shared link ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "key": { @@ -23047,6 +23120,8 @@ }, "userId": { "description": "Owner user ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23157,14 +23232,14 @@ "description": "Filter by creation date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "createdBefore": { "description": "Filter by creation date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "isEncoded": { @@ -23295,28 +23370,28 @@ "description": "Filter by taken date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "takenBefore": { "description": "Filter by taken date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "trashedAfter": { "description": "Filter by trash date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "trashedBefore": { "description": "Filter by trash date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "type": { @@ -23326,14 +23401,14 @@ "description": "Filter by update date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "updatedBefore": { "description": "Filter by update date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "visibility": { @@ -23388,10 +23463,14 @@ }, "id": { "description": "Stack ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "primaryAssetId": { "description": "Primary asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23438,14 +23517,14 @@ "description": "Filter by creation date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "createdBefore": { "description": "Filter by creation date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "description": { @@ -23554,28 +23633,28 @@ "description": "Filter by taken date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "takenBefore": { "description": "Filter by taken date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "trashedAfter": { "description": "Filter by trash date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "trashedBefore": { "description": "Filter by trash date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "type": { @@ -23585,14 +23664,14 @@ "description": "Filter by update date (after)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "updatedBefore": { "description": "Filter by update date (before)", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "visibility": { @@ -23665,6 +23744,8 @@ "properties": { "albumId": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23677,10 +23758,14 @@ "properties": { "albumId": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23694,10 +23779,14 @@ "properties": { "albumId": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23711,10 +23800,14 @@ "properties": { "albumId": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23728,6 +23821,8 @@ "properties": { "albumId": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "role": { @@ -23735,6 +23830,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23751,7 +23848,7 @@ "description": "Created at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "description": { @@ -23760,6 +23857,8 @@ }, "id": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isActivityEnabled": { @@ -23775,6 +23874,8 @@ }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "thumbnailAssetId": { @@ -23786,7 +23887,7 @@ "description": "Updated at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -23809,7 +23910,7 @@ "description": "Created at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "description": { @@ -23818,6 +23919,8 @@ }, "id": { "description": "Album ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isActivityEnabled": { @@ -23840,7 +23943,7 @@ "description": "Updated at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -23860,6 +23963,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23872,6 +23977,8 @@ "properties": { "editId": { "description": "Edit ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -23887,10 +23994,14 @@ }, "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "id": { "description": "Edit ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "parameters": { @@ -23918,6 +24029,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "city": { @@ -23935,7 +24048,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "description": { @@ -24026,7 +24139,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "orientation": { @@ -24095,6 +24208,8 @@ "properties": { "assetFaceId": { "description": "Asset face ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -24107,6 +24222,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "boundingBoxX1": { @@ -24135,6 +24252,8 @@ }, "id": { "description": "Asset face ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "imageHeight": { @@ -24177,6 +24296,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "boundingBoxX1": { @@ -24208,11 +24329,13 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "id": { "description": "Asset face ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "imageHeight": { @@ -24261,6 +24384,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "key": { @@ -24278,6 +24403,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "key": { @@ -24307,7 +24434,7 @@ "description": "Timestamp when the OCR entry was deleted", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "id": { @@ -24326,6 +24453,8 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "boxScore": { @@ -24335,6 +24464,8 @@ }, "id": { "description": "OCR entry ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isVisible": { @@ -24420,7 +24551,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "deletedAt": { @@ -24428,7 +24559,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "duration": { @@ -24441,7 +24572,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "fileModifiedAt": { @@ -24449,7 +24580,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "height": { @@ -24461,6 +24592,8 @@ }, "id": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isEdited": { @@ -24486,7 +24619,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "originalFileName": { @@ -24495,6 +24628,8 @@ }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "stackId": { @@ -24556,7 +24691,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "deletedAt": { @@ -24564,7 +24699,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "duration": { @@ -24579,7 +24714,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "fileModifiedAt": { @@ -24587,7 +24722,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "height": { @@ -24599,6 +24734,8 @@ }, "id": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isEdited": { @@ -24624,7 +24761,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "originalFileName": { @@ -24633,6 +24770,8 @@ }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "stackId": { @@ -24698,7 +24837,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "email": { @@ -24711,6 +24850,8 @@ }, "id": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isAdmin": { @@ -24734,7 +24875,7 @@ "description": "User profile changed at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "quotaSizeInBytes": { @@ -24845,10 +24986,14 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "memoryId": { "description": "Memory ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -24862,10 +25007,14 @@ "properties": { "assetId": { "description": "Asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "memoryId": { "description": "Memory ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -24879,6 +25028,8 @@ "properties": { "memoryId": { "description": "Memory ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -24893,7 +25044,7 @@ "description": "Created at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "data": { @@ -24906,7 +25057,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "hideAt": { @@ -24914,11 +25065,13 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "id": { "description": "Memory ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isSaved": { @@ -24929,11 +25082,13 @@ "description": "Memory at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "seenAt": { @@ -24941,7 +25096,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "showAt": { @@ -24949,7 +25104,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "type": { @@ -24959,7 +25114,7 @@ "description": "Updated at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -24983,10 +25138,14 @@ "properties": { "sharedById": { "description": "Shared by ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "sharedWithId": { "description": "Shared with ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25004,10 +25163,14 @@ }, "sharedById": { "description": "Shared by ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "sharedWithId": { "description": "Shared with ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25022,6 +25185,8 @@ "properties": { "personId": { "description": "Person ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25037,7 +25202,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "color": { @@ -25049,7 +25214,7 @@ "description": "Created at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "faceAssetId": { @@ -25059,6 +25224,8 @@ }, "id": { "description": "Person ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "isFavorite": { @@ -25075,13 +25242,15 @@ }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "updatedAt": { "description": "Updated at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -25140,6 +25309,8 @@ "properties": { "stackId": { "description": "Stack ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25154,26 +25325,32 @@ "description": "Created at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "id": { "description": "Stack ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "ownerId": { "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "primaryAssetId": { "description": "Primary asset ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "updatedAt": { "description": "Updated at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -25209,6 +25386,8 @@ "properties": { "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25224,6 +25403,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" } }, @@ -25240,6 +25421,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "value": { @@ -25270,7 +25453,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "email": { @@ -25283,6 +25466,8 @@ }, "id": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "name": { @@ -25293,7 +25478,7 @@ "description": "User profile changed at", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -25959,8 +26144,8 @@ "type": "boolean" }, "startTime": { - "description": "Start time", - "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$", + "description": "Start time (HH:MM)", + "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$", "type": "string" }, "syncQuotaUsage": { @@ -26466,6 +26651,8 @@ }, "id": { "description": "Tag ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "name": { @@ -26989,6 +27176,8 @@ }, "userId": { "description": "User ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "userName": { @@ -27095,7 +27284,7 @@ "description": "Creation date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "deletedAt": { @@ -27103,7 +27292,7 @@ "example": "2024-01-01T00:00:00.000Z", "format": "date-time", "nullable": true, - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "email": { @@ -27177,7 +27366,7 @@ "description": "Last update date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" } }, @@ -27278,7 +27467,7 @@ "description": "Activation date", "example": "2024-01-01T00:00:00.000Z", "format": "date-time", - "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", "type": "string" }, "activationKey": { @@ -27631,6 +27820,8 @@ }, "id": { "description": "Workflow ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "type": "string" }, "name": { diff --git a/package.json b/package.json index 9af979ad2d..a5ce179e48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "immich-monorepo", - "version": "3.0.0-rc.1", + "version": "3.0.0-rc.2", "description": "Monorepo for Immich", "type": "module", "private": true, @@ -11,7 +11,7 @@ "release": "./misc/release/pump-version.sh", "pump": "node ./misc/release/pump-wrapper.js" }, - "packageManager": "pnpm@11.5.2", + "packageManager": "pnpm@11.6.0", "engines": { "pnpm": ">=10.0.0" }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 67bf68a7d6..158093e787 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@immich/cli", - "version": "3.0.0-rc.1", + "version": "3.0.0-rc.2", "description": "Command Line Interface (CLI) for Immich", "repository": { "type": "git", diff --git a/packages/e2e-auth-server/package.json b/packages/e2e-auth-server/package.json index e999cd36b2..0cfaab1da0 100644 --- a/packages/e2e-auth-server/package.json +++ b/packages/e2e-auth-server/package.json @@ -13,5 +13,5 @@ "oidc-provider": "^9.0.0", "tsx": "^4.20.6" }, - "packageManager": "pnpm@11.5.2" + "packageManager": "pnpm@11.6.0" } diff --git a/packages/plugin-core/manifest.json b/packages/plugin-core/manifest.json index db6ab203bf..15d705b885 100644 --- a/packages/plugin-core/manifest.json +++ b/packages/plugin-core/manifest.json @@ -184,22 +184,22 @@ "uiHints": ["Filter"] }, { - "name": "filterFileType", - "title": "Filter by file type", - "description": "Filter assets by file type", + "name": "assetTypeFilter", + "title": "Filter by asset type", + "description": "Filter assets by type", "types": ["AssetV1"], "schema": { "type": "object", "properties": { - "fileTypes": { - "title": "File types", - "description": "Allowed file types", + "allowedTypes": { + "title": "Asset types", + "description": "Allowed asset types", "type": "string", "array": true, - "enum": ["image", "video"] + "enum": ["IMAGE", "VIDEO", "AUDIO", "OTHER"] } }, - "required": ["fileTypes"] + "required": ["allowedTypes"] }, "uiHints": ["Filter"] }, @@ -222,7 +222,16 @@ "name": "assetLock", "title": "Move to locked folder", "description": "Change visibility to locked", - "types": ["AssetV1"] + "types": ["AssetV1"], + "schema": { + "properties": { + "inverse": { + "title": "Inverse", + "description": "When true will unarchive any archived assets", + "type": "boolean" + } + } + } }, { "name": "assetTimeline", @@ -278,7 +287,9 @@ "title": "Album IDs", "array": true, "description": "Target album IDs", - "uiHint": "AlbumId" + "uiHint": { + "type": "AlbumId" + } }, "albumName": { "type": "string", @@ -368,14 +379,20 @@ "type": "string", "title": "Album ID", "description": "Target album ID", - "uiHint": "AlbumId" + "uiHint": { + "type": "AlbumId", + "order": 1 + } }, "albumIds": { "type": "string", "title": "Album IDs", "description": "Target album IDs", "array": true, - "uiHint": "AlbumId" + "uiHint": { + "type": "AlbumId", + "order": 2 + } } } } diff --git a/packages/plugin-core/package.json b/packages/plugin-core/package.json index 26b5124426..baddf4a6eb 100644 --- a/packages/plugin-core/package.json +++ b/packages/plugin-core/package.json @@ -5,7 +5,7 @@ "main": "src/index.ts", "scripts": { "build": "pnpm build:tsc && pnpm build:wasm", - "build:tsc": "tsc --noEmit && node esbuild.js", + "build:tsc": "mkdir -p dist && echo \"type Manifest = $(cat manifest.json); \nexport default Manifest;\" > dist/manifest.d.ts && tsc --noEmit && node esbuild.js", "build:wasm": "extism-js dist/index.js -i src/index.d.ts -o dist/plugin.wasm" }, "keywords": [], diff --git a/packages/plugin-core/src/index.d.ts b/packages/plugin-core/src/index.d.ts index 2ce414e84f..636cb03047 100644 --- a/packages/plugin-core/src/index.d.ts +++ b/packages/plugin-core/src/index.d.ts @@ -14,6 +14,7 @@ declare module 'main' { export function assetFileFilter(): I32; export function assetMissingTimeZoneFilter(): I32; export function assetLocationFilter(): I32; + export function assetTypeFilter(): I32; // updates export function assetFavorite(): I32; @@ -21,6 +22,6 @@ declare module 'main' { export function assetArchive(): I32; export function assetLock(): I32; export function assetTimeline(): I32; - export function assetTrash(): I32; + // export function assetTrash(): I32; export function assetAddToAlbums(): I32; } diff --git a/packages/plugin-core/src/index.ts b/packages/plugin-core/src/index.ts index e67434bb54..12eaab404b 100644 --- a/packages/plugin-core/src/index.ts +++ b/packages/plugin-core/src/index.ts @@ -1,13 +1,11 @@ -import { wrapper } from '@immich/plugin-sdk'; -import { AssetVisibility, WorkflowType } from '@immich/sdk'; +import { getWrapper } from '@immich/plugin-sdk'; +import { AssetVisibility } from '@immich/sdk'; +import type manifestType from '../dist/manifest'; + +const wrapper = getWrapper(); -type AssetFileFilterConfig = { - pattern: string; - matchType?: 'contains' | 'exact' | 'regex' | 'startsWith'; - caseSensitive?: boolean; -}; export const assetFileFilter = () => { - return wrapper(({ data, config }) => { + return wrapper<'assetFileFilter'>(({ data, config }) => { const { pattern, matchType = 'contains', caseSensitive = false } = config; const { asset } = data; @@ -43,7 +41,7 @@ export const assetFileFilter = () => { }; export const assetMissingTimeZoneFilter = () => { - return wrapper(({ config, data }) => { + return wrapper<'assetMissingTimeZoneFilter'>(({ config, data }) => { const hasTimeZone = !!data.asset?.exifInfo?.timeZone; const needsTimeZone = config.inverse ? true : false; return { workflow: { continue: hasTimeZone === needsTimeZone } }; @@ -51,13 +49,7 @@ export const assetMissingTimeZoneFilter = () => { }; export const assetLocationFilter = () => { - return wrapper< - WorkflowType.AssetV1, - { - region?: { country?: string; state?: string; city?: string }; - coordinate?: { latitude?: string; longitude?: string; radius?: number }; - } - >(({ config, data }) => { + return wrapper<'assetLocationFilter'>(({ config, data }) => { if ( (config.region?.country && config.region.country !== data.asset.exifInfo?.country) || (config.region?.state && config.region.state !== data.asset.exifInfo?.state) || @@ -95,8 +87,14 @@ export const assetLocationFilter = () => { }); }; +export const assetTypeFilter = () => { + return wrapper<'assetTypeFilter'>(({ config, data }) => { + return { workflow: { continue: config.allowedTypes.includes(data.asset.type) } }; + }); +}; + export const assetFavorite = () => { - return wrapper(({ config, data }) => { + return wrapper<'assetFavorite'>(({ config, data }) => { const target = config.inverse ? false : true; if (target !== data.asset.isFavorite) { return { @@ -109,13 +107,13 @@ export const assetFavorite = () => { }; export const assetVisibility = () => { - return wrapper(({ config }) => ({ - changes: { asset: { visibility: config.visibility } }, + return wrapper<'assetVisibility'>(({ config }) => ({ + changes: { asset: { visibility: config.visibility as AssetVisibility } }, })); }; export const assetArchive = () => { - return wrapper(({ config, data }) => { + return wrapper<'assetArchive'>(({ config, data }) => { if (!config.inverse && data.asset.visibility !== AssetVisibility.Archive) { return { changes: { asset: { visibility: AssetVisibility.Archive } } }; } @@ -129,7 +127,7 @@ export const assetArchive = () => { }; export const assetLock = () => { - return wrapper(({ config, data }) => { + return wrapper<'assetLock'>(({ config, data }) => { if (!config.inverse && data.asset.visibility !== AssetVisibility.Locked) { return { changes: { asset: { visibility: AssetVisibility.Locked } } }; } @@ -142,13 +140,13 @@ export const assetLock = () => { }); }; -export const assetTrash = () => { - // TODO use trash/untrash host functions - return wrapper(() => ({})); -}; +// export const assetTrash = () => { +// // TODO use trash/untrash host functions +// return wrapper(() => ({})); +// }; export const assetAddToAlbums = () => { - return wrapper(({ config, data, functions }) => { + return wrapper<'assetAddToAlbums'>(({ config, data, functions }) => { const assetId = data.asset.id; if (config.albumIds.length === 0) { diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json index 8b8d64fcd9..3d0c75b0b7 100644 --- a/packages/plugin-sdk/package.json +++ b/packages/plugin-sdk/package.json @@ -24,14 +24,14 @@ "keywords": [], "author": "", "license": "GNU Affero General Public License version 3", - "packageManager": "pnpm@11.5.2", + "packageManager": "pnpm@11.6.0", "devDependencies": { "@extism/js-pdk": "^1.1.1", "@immich/sdk": "workspace:*", "@types/node": "^24.13.2", "esbuild": "^0.28.0", "tsc-alias": "^1.8.16", - "typescript": "^5.9.3" + "typescript": "^6.0.0" }, "peerDependencies": { "@extism/js-pdk": "^1.1.1" diff --git a/packages/plugin-sdk/src/sdk.ts b/packages/plugin-sdk/src/sdk.ts index 283e9c3dd5..e428eafed5 100644 --- a/packages/plugin-sdk/src/sdk.ts +++ b/packages/plugin-sdk/src/sdk.ts @@ -1,53 +1,104 @@ import type { WorkflowType } from '@immich/sdk'; import { hostFunctions } from 'src/host-functions.js'; import type { - ConfigValue, WorkflowEventPayload, WorkflowResponse, WorkflowStepConfig, } from 'src/types.js'; -export const wrapper = < - T extends WorkflowType = WorkflowType, - TConfig extends ConfigValue = ConfigValue, ->( - fn: ( - payload: WorkflowEventPayload & { - functions: ReturnType; - }, - ) => WorkflowResponse | undefined, -) => { - const input = Host.inputString(); - - try { - const payload = JSON.parse(input) as WorkflowEventPayload; - const event = { - ...payload, - functions: hostFunctions(payload.workflow.authToken), - }; - - const eventConfigBefore = JSON.stringify(event.config); - - console.debug( - `Inputs: trigger=${event.trigger}, event=${event.type}, config=${eventConfigBefore}`, - ); - - const response = fn(event) ?? {}; - - // if config changed, notify host - const eventConfigAfter = JSON.stringify(event.config); - if (!response.config && eventConfigBefore !== eventConfigAfter) { - response.config = event.config as WorkflowStepConfig; - } - - console.debug( - `Outputs: workflow=${JSON.stringify(response.workflow)}, changes=${JSON.stringify(response.changes)}, data=${JSON.stringify(response.data)}, config=${JSON.stringify(response.config)}`, - ); - - const output = JSON.stringify(response); - Host.outputString(output); - } catch (error: Error | any) { - console.error(`Unhandled plugin exception: ${error.message || error}`); - throw error; - } +type Property = { + type: 'string' | 'boolean' | 'number'; + array?: boolean; + enum?: string[]; +} & { + type: 'object'; + properties: { [K: string]: Property }; + required?: string[]; }; + +type RequiredProperties< + Properties extends { [K: string]: unknown }, + Required extends string[] | undefined, + RequiredKeys extends string = Required extends undefined + ? never + : NonNullable[number], +> = { + properties: Pick & + Partial>; +}; + +type GetConfigType = 'enum' extends keyof T + ? NonNullable[number] + : T['type'] extends 'boolean' + ? boolean + : T['type'] extends 'number' + ? number + : T['type'] extends 'string' + ? string + : T['type'] extends 'object' + ? ConfigValue + : never; + +type ConfigValue< + T extends { properties: { [K: string]: Property }; required?: string[] }, + Properties extends { [K: string]: Property } = T['properties'], +> = T extends never + ? never + : RequiredProperties< + { + [K in keyof Properties]: Properties[K]['array'] extends true + ? Array> + : GetConfigType; + }, + 'required' extends keyof T ? T['required'] : undefined + >['properties']; + +export const getWrapper = + >() => + < + K extends T['methods'][number]['name'], + L extends WorkflowType = (T['methods'][number] & { + name: K; + })['types'][number], + TConfig = ConfigValue<(T['methods'][number] & { name: K })['schema']>, + >( + fn: ( + payload: WorkflowEventPayload & { + functions: ReturnType; + }, + ) => WorkflowResponse | undefined, + ) => { + const input = Host.inputString(); + + try { + const payload = JSON.parse(input) as WorkflowEventPayload; + const event = { + ...payload, + functions: hostFunctions(payload.workflow.authToken), + }; + + const eventConfigBefore = JSON.stringify(event.config); + + console.debug( + `Inputs: trigger=${event.trigger}, event=${event.type}, config=${eventConfigBefore}`, + ); + + const response = fn(event) ?? {}; + + // if config changed, notify host + const eventConfigAfter = JSON.stringify(event.config); + if (!response.config && eventConfigBefore !== eventConfigAfter) { + response.config = event.config as WorkflowStepConfig; + } + + console.debug( + `Outputs: workflow=${JSON.stringify(response.workflow)}, changes=${JSON.stringify(response.changes)}, data=${JSON.stringify(response.data)}, config=${JSON.stringify(response.config)}`, + ); + + const output = JSON.stringify(response); + Host.outputString(output); + } catch (error: Error | any) { + console.error(`Unhandled plugin exception: ${error.message || error}`); + throw error; + } + }; diff --git a/packages/plugin-sdk/src/types.ts b/packages/plugin-sdk/src/types.ts index 0d406ce119..9756d652c3 100644 --- a/packages/plugin-sdk/src/types.ts +++ b/packages/plugin-sdk/src/types.ts @@ -11,7 +11,7 @@ type DeepPartial = T extends Date export type WorkflowEventMap = { [WorkflowType.AssetV1]: AssetV1; // [WorkflowType.AssetPersonV1]: AssetPersonV1; -}; +} & { [K in WorkflowType]: unknown }; export type WorkflowEventData = WorkflowEventMap[T]; @@ -22,7 +22,7 @@ export enum WorkflowTrigger { } export type WorkflowEventPayload< - T extends WorkflowType = WorkflowType, + T extends WorkflowType, TConfig = WorkflowStepConfig, > = { trigger: WorkflowTrigger; @@ -37,10 +37,11 @@ export type WorkflowEventPayload< }; }; -export type WorkflowChanges = - DeepPartial>; +export type WorkflowChanges = DeepPartial< + WorkflowEventData +>; -export type WorkflowResponse = { +export type WorkflowResponse = { workflow?: { /** stop the workflow */ continue?: boolean; diff --git a/packages/plugin-sdk/tsconfig.json b/packages/plugin-sdk/tsconfig.json index 2dbd91de0a..ff27c2a2f0 100644 --- a/packages/plugin-sdk/tsconfig.json +++ b/packages/plugin-sdk/tsconfig.json @@ -20,6 +20,7 @@ "sourceMap": false, "strict": true, "target": "esnext", + "typeRoots": ["./node_modules/@types", "./node_modules"], "types": ["node", "@extism/js-pdk"], "verbatimModuleSyntax": true } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 9d9b4d8099..c4532e3048 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@immich/sdk", - "version": "3.0.0-rc.1", + "version": "3.0.0-rc.2", "description": "Auto-generated TypeScript SDK for the Immich API", "repository": { "type": "git", diff --git a/packages/sdk/src/fetch-client.ts b/packages/sdk/src/fetch-client.ts index b7202c2a1a..c673cb2369 100644 --- a/packages/sdk/src/fetch-client.ts +++ b/packages/sdk/src/fetch-client.ts @@ -1,6 +1,6 @@ /** * Immich - * 3.0.0-rc.1 + * 3.0.0-rc.2 * DO NOT MODIFY - This file has been generated using oazapfts. * See https://www.npmjs.com/package/oazapfts */ @@ -707,7 +707,7 @@ export type AssetBulkUploadCheckResult = { action: AssetUploadAction; /** Existing asset ID if duplicate */ assetId?: string; - /** Asset ID */ + /** Client-side identifier echoed from the request to match results to inputs */ id: string; /** Whether existing asset is trashed */ isTrashed?: boolean; @@ -2501,7 +2501,7 @@ export type SystemConfigNightlyTasksDto = { generateMemories: boolean; /** Missing thumbnails */ missingThumbnails: boolean; - /** Start time */ + /** Start time (HH:MM) */ startTime: string; /** Sync quota usage */ syncQuotaUsage: boolean; @@ -4452,12 +4452,13 @@ export function endSession({ id, key, sessionId, slug }: { /** * Get HLS media playlist */ -export function getMediaPlaylist({ id, key, sessionId, slug, variantIndex }: { +export function getMediaPlaylist({ id, key, sessionId, slug, variantIndex, xImmichHlsPos }: { id: string; key?: string; sessionId: string; slug?: string; variantIndex: number; + xImmichHlsPos?: number; }, opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchBlob<{ status: 200; @@ -4466,7 +4467,10 @@ export function getMediaPlaylist({ id, key, sessionId, slug, variantIndex }: { key, slug }))}`, { - ...opts + ...opts, + headers: oazapfts.mergeHeaders(opts?.headers, { + "x-immich-hls-pos": xImmichHlsPos + }) })); } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7bb2289996..15ad20d587 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -353,8 +353,8 @@ importers: specifier: ^1.8.16 version: 1.8.17 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.0 + version: 6.0.3 packages/sdk: dependencies: @@ -411,20 +411,20 @@ importers: specifier: ^2.0.0 version: 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-prometheus': - specifier: ^0.218.0 - version: 0.218.0(@opentelemetry/api@1.9.1) + specifier: ^0.219.0 + version: 0.219.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-http': - specifier: ^0.218.0 - version: 0.218.0(@opentelemetry/api@1.9.1) + specifier: ^0.219.0 + version: 0.219.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-ioredis': - specifier: ^0.66.0 - version: 0.66.0(@opentelemetry/api@1.9.1) + specifier: ^0.67.0 + version: 0.67.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-nestjs-core': - specifier: ^0.64.0 - version: 0.64.0(@opentelemetry/api@1.9.1) + specifier: ^0.65.0 + version: 0.65.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation-pg': - specifier: ^0.70.0 - version: 0.70.0(@opentelemetry/api@1.9.1) + specifier: ^0.71.0 + version: 0.71.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': specifier: ^2.0.1 version: 2.8.0(@opentelemetry/api@1.9.1) @@ -432,8 +432,8 @@ importers: specifier: ^2.0.1 version: 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-node': - specifier: ^0.218.0 - version: 0.218.0(@opentelemetry/api@1.9.1) + specifier: ^0.219.0 + version: 0.219.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': specifier: ^1.34.0 version: 1.41.1 @@ -532,7 +532,7 @@ importers: version: 0.40.4 multer: specifier: ^2.0.2 - version: 2.1.1 + version: 2.2.0 nest-commander: specifier: ^3.16.0 version: 3.20.1(@nestjs/common@11.1.27(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)(@types/inquirer@8.2.13)(@types/node@24.13.2)(typescript@6.0.3) @@ -549,8 +549,8 @@ importers: specifier: ^5.3.0 version: 5.4.0(@nestjs/common@11.1.27(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.4.4(@nestjs/common@11.1.27(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.27)(reflect-metadata@0.2.2)(typescript@6.0.3))(rxjs@7.8.2)(zod@4.3.6) nodemailer: - specifier: ^8.0.0 - version: 8.0.11 + specifier: ^9.0.0 + version: 9.0.1 openid-client: specifier: ^6.3.3 version: 6.8.4 @@ -770,7 +770,7 @@ importers: version: link:../packages/sdk '@immich/ui': specifier: ^0.81.1 - version: 0.81.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + version: 0.81.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0)) '@mapbox/mapbox-gl-rtl-text': specifier: 0.4.0 version: 0.4.0 @@ -806,7 +806,7 @@ importers: version: 0.42.0 '@zoom-image/svelte': specifier: ^0.3.0 - version: 0.3.9(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + version: 0.3.9(svelte@5.56.3(@typescript-eslint/types@8.61.0)) dom-to-image: specifier: ^2.6.0 version: 2.6.0 @@ -866,16 +866,16 @@ importers: version: 5.2.2 svelte-i18n: specifier: ^4.0.1 - version: 4.0.1(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + version: 4.0.1(svelte@5.56.3(@typescript-eslint/types@8.61.0)) svelte-jsoneditor: specifier: ^3.10.0 - version: 3.12.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + version: 3.12.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)) svelte-maplibre: specifier: ^1.2.5 - version: 1.3.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + version: 1.3.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)) svelte-persisted-store: specifier: ^0.12.0 - version: 0.12.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + version: 0.12.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)) tabbable: specifier: ^6.2.0 version: 6.4.0 @@ -909,16 +909,16 @@ importers: version: 3.1.2 '@sveltejs/adapter-static': specifier: ^3.0.8 - version: 3.0.10(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))) + version: 3.0.10(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))) '@sveltejs/enhanced-img': specifier: ^0.10.4 - version: 0.10.4(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(rollup@4.62.0)(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 0.10.4(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(rollup@4.62.0)(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) '@sveltejs/kit': specifier: ^2.56.1 - version: 2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) '@sveltejs/vite-plugin-svelte': specifier: 7.1.2 - version: 7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + version: 7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) '@tailwindcss/vite': specifier: ^4.2.4 version: 4.3.1(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) @@ -927,13 +927,13 @@ importers: version: 6.9.1 '@testing-library/svelte': specifier: ^5.2.8 - version: 5.3.1(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9) + version: 5.3.1(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9) '@testing-library/user-event': specifier: ^14.5.2 version: 14.6.1(@testing-library/dom@10.4.1) '@trivago/prettier-plugin-sort-imports': specifier: ^6.0.2 - version: 6.0.2(prettier-plugin-svelte@4.1.0(prettier@3.8.4)(svelte@5.56.2(@typescript-eslint/types@8.61.0)))(prettier@3.8.4)(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + version: 6.0.2(prettier-plugin-svelte@4.1.0(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0)))(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0)) '@types/chromecast-caf-sender': specifier: ^1.0.11 version: 1.0.11 @@ -972,7 +972,7 @@ importers: version: 7.0.2(eslint@10.5.0(jiti@2.7.0)) eslint-plugin-svelte: specifier: ^3.12.4 - version: 3.19.0(eslint@10.5.0(jiti@2.7.0))(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + version: 3.19.0(eslint@10.5.0(jiti@2.7.0))(svelte@5.56.3(@typescript-eslint/types@8.61.0)) eslint-plugin-unicorn: specifier: ^64.0.0 version: 64.0.0(eslint@10.5.0(jiti@2.7.0)) @@ -990,19 +990,19 @@ importers: version: 4.2.0(prettier@3.8.4) prettier-plugin-svelte: specifier: ^4.0.0 - version: 4.1.0(prettier@3.8.4)(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + version: 4.1.0(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0)) rollup-plugin-visualizer: specifier: ^7.0.0 version: 7.0.1(rolldown@1.0.3)(rollup@4.62.0) svelte: - specifier: 5.56.2 - version: 5.56.2(@typescript-eslint/types@8.61.0) + specifier: 5.56.3 + version: 5.56.3(@typescript-eslint/types@8.61.0) svelte-check: specifier: ^4.4.6 - version: 4.6.0(picomatch@4.0.4)(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3) + version: 4.6.0(picomatch@4.0.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3) svelte-eslint-parser: specifier: ^1.3.3 - version: 1.8.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + version: 1.8.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)) tailwindcss: specifier: ^4.2.4 version: 4.3.1 @@ -3880,106 +3880,94 @@ packages: '@oazapfts/runtime@1.2.0': resolution: {integrity: sha512-fi7dp7dNayyh/vzqhf0ZdoPfC7tJvYfjaE8MBL1yR+iIsH7cFoqHt+DV70VU49OMCqLc7wQa+yVJcSmIRnV4wA==} - '@opentelemetry/api-logs@0.218.0': - resolution: {integrity: sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==} + '@opentelemetry/api-logs@0.219.0': + resolution: {integrity: sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg==} engines: {node: '>=8.0.0'} '@opentelemetry/api@1.9.1': resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} - '@opentelemetry/configuration@0.218.0': - resolution: {integrity: sha512-W8wIz7H2R1pufR5jfjb3gU2XkMpm2x/7b1RJcsuzvd70Il/rWWE+g5/Od7hQKrxRTSrTrOWlru101PWXz5I1EQ==} + '@opentelemetry/configuration@0.219.0': + resolution: {integrity: sha512-wXZUYv4ngu43nA4WEhuXNacm46LW+17LRM8nKyIhBzroRA24PBYjMnakwzR/w777nFUB5xlgsYTTeuXxumZM1Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.9.0 - '@opentelemetry/context-async-hooks@2.7.1': - resolution: {integrity: sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/context-async-hooks@2.8.0': resolution: {integrity: sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.7.1': - resolution: {integrity: sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.8.0': resolution: {integrity: sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/exporter-logs-otlp-grpc@0.218.0': - resolution: {integrity: sha512-hoxrNH1l/Xy6F9WTJ5IK+6j1r9nQFlPOmrnTlhYHTySdunfXLmUCPv3bQtKYntxag9h3wLYBZQ2HI6FOx+BT2g==} + '@opentelemetry/exporter-logs-otlp-grpc@0.219.0': + resolution: {integrity: sha512-7SvzDCIclHWAcCwZ1MTOLcwn4BVNPGI3QxS/DJraPNe1TTL+4TvUBq5zeQV8tsnYvtDN7wKW2qocVmaCP2l7sQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-logs-otlp-http@0.218.0': - resolution: {integrity: sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw==} + '@opentelemetry/exporter-logs-otlp-http@0.219.0': + resolution: {integrity: sha512-mhl2HL6GmZI8b8PwPfqMws/5ovJfbRTxwc9Y5agVVHiQ+e5SL1btsFr/kJDgt7YCexDtsUn5HAreHQO9szFS0A==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-logs-otlp-proto@0.218.0': - resolution: {integrity: sha512-1/noQNsp9gXD75HPzgjBrcF1+XTtry7pFAUfxVEJgg7mPv2AawKQuYkhMmJ8qjxz4Ubc3Y8bwvfxevXsKTq4cg==} + '@opentelemetry/exporter-logs-otlp-proto@0.219.0': + resolution: {integrity: sha512-Ayw4Gf71PS9jhBVaYywa4WsajnqfDehMkTdVH3TSAVHqPcsAv/AhH/wTNRYNt99szeYr6Gbd/D6RjZD77wAxHg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-grpc@0.218.0': - resolution: {integrity: sha512-YapQ9vNMX0NSZF6LK5pWAFfjpJleV2O9uYWfYGeb/5F1Kb9rPGK8tZDMJFa/sOksgdFuflDvYuA0B4qjDB4fjQ==} + '@opentelemetry/exporter-metrics-otlp-grpc@0.219.0': + resolution: {integrity: sha512-6LaaSrPxK5L55bXevWajvOMxGOpNm0n12tG53TeZaUeNzXwLPg6d2KCC1zAlGsojan+xRG71mA4Qqs9K2VVrKQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-http@0.218.0': - resolution: {integrity: sha512-bV7d2OuMpZu2+gAaxUAhzfZ0h3WVZk8ETQUEE3DNSntbTaMpuITjtm8I0rNyHFdm7Ax57K6ty7SgFXlBmOLIvQ==} + '@opentelemetry/exporter-metrics-otlp-http@0.219.0': + resolution: {integrity: sha512-6CaDRbMVHZSDWzNXwrR8y/H4B/Z1eMNnkHiPQlTx3Ojz2OHY4X/aff/UC4P/3pHUQSuTfi3oh2UsPPZppw+Vrg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-metrics-otlp-proto@0.218.0': - resolution: {integrity: sha512-ubLddKjWULhla9YZRCj/rTBeppjJYE4e9w0icx5mTu3eFhWjQzbV75NYjXuIlEG+NJsBl6d+sTFw5Qu+oej4oQ==} + '@opentelemetry/exporter-metrics-otlp-proto@0.219.0': + resolution: {integrity: sha512-DUS7XyIiEnoeccQUvuKy0G2/YqeKhpN8FVIrGbrLNIVMj10yeIFLRzRv0tibCI2kXXvlTTABVexGAk78wHk2ug==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-prometheus@0.218.0': - resolution: {integrity: sha512-RT5oEyu1kddZJ1vt7/BUo5wV+P7hpNAESsR3dUd3+8deHuX7gWNoCOZn+SfDT+hJHlIJ5h/AxiCLXIrutswDJg==} + '@opentelemetry/exporter-prometheus@0.219.0': + resolution: {integrity: sha512-TxOnJ85eWJY5JyOJsNMXiRTYlkDcOv0u3KbXEzWCc+tUS9sjL/BC6BcdxZ0B9r2OFVqsrZFXUzSD2sZUy42Ucw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-grpc@0.218.0': - resolution: {integrity: sha512-3fXxVQEj9TNAFaCi79JeFKfeLd0sDtInaR3gaZDVlzNSPHtz8PZuCV34JKWjD4XXzT20IdMe8IpX6mRVNDA4Tw==} + '@opentelemetry/exporter-trace-otlp-grpc@0.219.0': + resolution: {integrity: sha512-BkDNv1UD6BscW19MxbAxVmSYSSFuyeqR6buV2/HTYqA7GrR0EbTFzqG6h86T3PtXmpdbsWjMGLDdjG2rikG27Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-http@0.218.0': - resolution: {integrity: sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==} + '@opentelemetry/exporter-trace-otlp-http@0.219.0': + resolution: {integrity: sha512-9t6SvBXXBEjOBcIzgozvBbd3jWrv3Gt3ngGhl1fhdZ/zRc7oZDVOFEqbi2zlBpW9BXhgDMKv422J0DL/3iQWfw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-trace-otlp-proto@0.218.0': - resolution: {integrity: sha512-r1Msf8SNLRmwh9J6XQ5uh82D7CdDWMNHnPB7LAVHjzut0TkSeKc5KcIvr4SvHvfk/xwN5gxC+VLKQ1k0o8PSPw==} + '@opentelemetry/exporter-trace-otlp-proto@0.219.0': + resolution: {integrity: sha512-lF/LUBfhOFmxJa+SQsLN7ziV4MHa2pyKgOM6JNehSOfU+npjM4gwm9oIKEJrzrWcexMcqydiyoFy0XCb1Ql3wQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/exporter-zipkin@2.7.1': - resolution: {integrity: sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg==} + '@opentelemetry/exporter-zipkin@2.8.0': + resolution: {integrity: sha512-Mj84UkEa17BK2o903VTXW3wM8CrSZexGs4tRGVZVIMM9ni1T6TuGx5IrRfoWKAbshx42D5/kc7YV+axypLPYyA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.0.0 @@ -3990,62 +3978,62 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-http@0.218.0': - resolution: {integrity: sha512-x9djaqdzpT8WAboep1H9nCAQ1E+MMsm08TNfA02TqM3bNNddZeiim+E3KMWVQFaX6JpUy7V0nm/wfN/K2Em+Zw==} + '@opentelemetry/instrumentation-http@0.219.0': + resolution: {integrity: sha512-nNt1fqpyah/OKjNHdEOu8xLwISppRU2qJuF8aR+fCcftVwdFkPgtworBLA+TI1HU2iF508jcQBF2gerWczJAXg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-ioredis@0.66.0': - resolution: {integrity: sha512-UfTAcaBKCzLUZ9opvfOLV4bH46XiNFqUsKykfPCIefDIxJ1iUYtMOucNaiZ+/kjQdPy5i6Ef5tk2IAjxol4X1w==} + '@opentelemetry/instrumentation-ioredis@0.67.0': + resolution: {integrity: sha512-dv64vQ4aXbJvRMMAFrMUSzDeJrNv/uQMLjfaav4LHAOar7Xn08W3pkoYoYEnzq/n2+fGgG96rp9S0gQ+VnLDiw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-nestjs-core@0.64.0': - resolution: {integrity: sha512-PW1ArxryMwF8/IXq1nzlQs7tmr/fWd1tf71AHevZT3Fm0hW7jRX9JEfYgIAcKDvmbqcJEr5K1224NEimrRPbuQ==} + '@opentelemetry/instrumentation-nestjs-core@0.65.0': + resolution: {integrity: sha512-/q8fN2M2zGl+gQRaF79dzqvyvVqHAI11c7xAQZy9W1eAtQScNwaQtPm0EUo5+aOgakaTksBrsiHUF0rQS24NsQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-pg@0.70.0': - resolution: {integrity: sha512-g8WXwwOUXfjiEmATwjB/33QKE2AkIpNe4KIuJJh4djtXgCL0Wne+AzAfjuDIAspGvO1txQp8ibKsLd3SBmcvJA==} + '@opentelemetry/instrumentation-pg@0.71.0': + resolution: {integrity: sha512-jAhfyZeOkEKh3cQ5nm1tNWqHg7HFARyAe+p4BSoDHnB79c1woyEvDKqS11Hj/DjtceP+vrurIfcDs7Fqiy11mQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.218.0': - resolution: {integrity: sha512-mIZil8Es+sYDK5m+DQiwAwF57F14TF2YlEqvIjZ/RQWcxDBwRGsKfdK2Tv65OU9meQKCMzSIFS9mxAcnAb6Bkg==} + '@opentelemetry/instrumentation@0.219.0': + resolution: {integrity: sha512-X5t7I8GyIO9rmGHwoedZLREpQqrF1WW2nxzNNym6HOKpFiE+rvqV3ngC0xcZVO2YwIGf3KKmRdWrYwdwz3H9RQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-exporter-base@0.218.0': - resolution: {integrity: sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==} + '@opentelemetry/otlp-exporter-base@0.219.0': + resolution: {integrity: sha512-zvIxQX/AZUVKDU+hCuYx+7UkiP7GRdnk1ZbFQRYzHvYp47cAWR4j3IhoPhV9KaeXEv2xdGq3IA6PnpzDmLcmSA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-grpc-exporter-base@0.218.0': - resolution: {integrity: sha512-H/lCGJ536N98VpYJOaWTQOkv4Dx6TnmStK6Rqfu1W7KkFbPAx04hjdYEMZF/YbnHzPUSIK4kM6OE2GKGBTpV9A==} + '@opentelemetry/otlp-grpc-exporter-base@0.219.0': + resolution: {integrity: sha512-iIk/s8QQu39zpTrRRmsW/Eg3SE2+Hg8tLWepr2FLRgmwUpNd0IpCTLJEHJ77hpt4hgIS8MAh44UYI4xQPZwWlw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/otlp-transformer@0.218.0': - resolution: {integrity: sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==} + '@opentelemetry/otlp-transformer@0.219.0': + resolution: {integrity: sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/propagator-b3@2.7.1': - resolution: {integrity: sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A==} + '@opentelemetry/propagator-b3@2.8.0': + resolution: {integrity: sha512-SazlvuSKi5533rPHTW2TwBwdMakhjZST4SYs0YauuvfGDkT13KbG1gJS75hV0uWVeevhtVP9sAIlaZLTHdSbMg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/propagator-jaeger@2.7.1': - resolution: {integrity: sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==} + '@opentelemetry/propagator-jaeger@2.8.0': + resolution: {integrity: sha512-Xnz9zZvvQzUw+9DrOn0MomR7BxFCkA2pcfXBQuHC28ndJpSbjLs7knzYb05kw5SyCjSsEWombkZMgGcJSk8JVg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -4054,50 +4042,38 @@ packages: resolution: {integrity: sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw==} engines: {node: ^18.19.0 || >=20.6.0} - '@opentelemetry/resources@2.7.1': - resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/resources@2.8.0': resolution: {integrity: sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-logs@0.218.0': - resolution: {integrity: sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==} + '@opentelemetry/sdk-logs@0.219.0': + resolution: {integrity: sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.4.0 <1.10.0' - '@opentelemetry/sdk-metrics@2.7.1': - resolution: {integrity: sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.9.0 <1.10.0' - '@opentelemetry/sdk-metrics@2.8.0': resolution: {integrity: sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.9.0 <1.10.0' - '@opentelemetry/sdk-node@0.218.0': - resolution: {integrity: sha512-tPMjHrLV5gsfNdYqoRHjeGbCAZBXXD9c1Qo/2ut7VwnUABDNh76xNxrT0SEhkIIJuCN45bbN1vZnYL1gY0IkOg==} + '@opentelemetry/sdk-node@0.219.0': + resolution: {integrity: sha512-NWLpWLEb8gV3+JBHYoIrktbM385wyHpRJoh3J/4Q52d4PR+AlPMNGJT3DzBUrDSUEVbKAXoHR+EDAPxtiNcj8g==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-base@2.7.1': - resolution: {integrity: sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==} + '@opentelemetry/sdk-trace-base@2.8.0': + resolution: {integrity: sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' - '@opentelemetry/sdk-trace-node@2.7.1': - resolution: {integrity: sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==} + '@opentelemetry/sdk-trace-node@2.8.0': + resolution: {integrity: sha512-nZt9OGufioAc3AfoLTqA9bsAeaMJAictYDdI2VcNQ+PmT+3rfKjAZDZvgPfd8VPX0O5Bw1hdQF6kDK8VSpZiWg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -4106,8 +4082,8 @@ packages: resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} - '@opentelemetry/sql-common@0.41.2': - resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} + '@opentelemetry/sql-common@0.42.0': + resolution: {integrity: sha512-nwUwUU+8O8a4bnLqk6CodWeegGMEANgC94KTAhXcpGWLrW/2/hek/0ajNbjXnSOoNuCX+nteUPs46HFHhou9Xw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': ^1.1.0 @@ -9673,6 +9649,10 @@ packages: resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} engines: {node: '>= 10.16.0'} + multer@2.2.0: + resolution: {integrity: sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==} + engines: {node: '>= 10.16.0'} + multicast-dns@7.2.5: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true @@ -9833,8 +9813,8 @@ packages: resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==} engines: {node: '>=18'} - nodemailer@8.0.11: - resolution: {integrity: sha512-nrO/pDAUKl+wXX+lx16tDLbnm0fW6sK/x8mgohaCpg+CdCEl482bD4tCuAZk2DyliruiNTIZxRCoWkDqJEnAiA==} + nodemailer@9.0.1: + resolution: {integrity: sha512-Gwv8SQewT616ZM/URn0H54b8PWo/Wum7md3EW2aWy1lO27+WZCX+Xyak3J+NlmHUjDh5ME+uesJUDRbR3Ye8Bw==} engines: {node: '>=6.0.0'} nopt@1.0.10: @@ -11787,8 +11767,8 @@ packages: peerDependencies: svelte: ^5.30.2 - svelte@5.56.2: - resolution: {integrity: sha512-1lDf8TLqpxyAt3xgybfytWPJQbaUD6TiDgpiCLH0BKrKEwzecB9pjuNVnEJMpzH018xUzo6oxheK2HT0oa2RoQ==} + svelte@5.56.3: + resolution: {integrity: sha512-w7JvrM5IFl5cmfbY0TLik9o7mjRUJmRMhOR51tBPu708Gr/MjbGs7VnJnr/B0CaXeI4vtnOh7RKxDr0cwhMdDA==} engines: {node: '>=18'} svg-parser@2.0.4: @@ -16006,15 +15986,15 @@ snapshots: pg-connection-string: 2.13.0 postgres: 3.4.9 - '@immich/ui@0.81.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))': + '@immich/ui@0.81.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))': dependencies: '@internationalized/date': 3.12.2 '@mdi/js': 7.4.47 - '@sveltejs/kit': 2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) - bits-ui: 2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + '@sveltejs/kit': 2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + bits-ui: 2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0)) luxon: 3.7.2 simple-icons: 16.23.0 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) svelte-highlight: 7.11.0 tailwind-merge: 3.6.0 tailwind-variants: 3.2.2(tailwind-merge@3.6.0)(tailwindcss@4.3.1) @@ -16739,140 +16719,131 @@ snapshots: '@oazapfts/runtime@1.2.0': {} - '@opentelemetry/api-logs@0.218.0': + '@opentelemetry/api-logs@0.219.0': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/api@1.9.1': {} - '@opentelemetry/configuration@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/configuration@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) yaml: 2.9.0 - '@opentelemetry/context-async-hooks@2.7.1(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/context-async-hooks@2.8.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/exporter-logs-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-grpc@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.4 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.219.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-http@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.218.0 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.219.0 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.219.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-proto@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-logs-otlp-proto@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.218.0 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.219.0 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-grpc@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.4 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-http@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-proto@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-metrics-otlp-proto@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-prometheus@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-prometheus@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/exporter-trace-otlp-grpc@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-grpc@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.4 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-http@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-trace-otlp-proto@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-zipkin@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/exporter-zipkin@2.8.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.41.1 '@opentelemetry/host-metrics@0.38.3(@opentelemetry/api@1.9.1)': @@ -16880,170 +16851,159 @@ snapshots: '@opentelemetry/api': 1.9.1 systeminformation: 5.31.7 - '@opentelemetry/instrumentation-http@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-http@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.219.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.41.1 forwarded-parse: 2.1.2 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-ioredis@0.66.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-ioredis@0.67.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.219.0(@opentelemetry/api@1.9.1) '@opentelemetry/redis-common': 0.38.3 '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-nestjs-core@0.64.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-nestjs-core@0.65.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.219.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-pg@0.70.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation-pg@0.71.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.219.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.1) + '@opentelemetry/sql-common': 0.42.0(@opentelemetry/api@1.9.1) '@types/pg': 8.15.6 '@types/pg-pool': 2.0.7 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/instrumentation@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.218.0 + '@opentelemetry/api-logs': 0.219.0 import-in-the-middle: 3.0.2 require-in-the-middle: 8.0.1 transitivePeerDependencies: - supports-color - '@opentelemetry/otlp-exporter-base@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-exporter-base@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-grpc-exporter-base@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.4 '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer': 0.218.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.219.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/otlp-transformer@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.218.0 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.219.0 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-b3@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/propagator-b3@2.8.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-jaeger@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/propagator-jaeger@2.8.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/redis-common@0.38.3': {} - '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/resources@2.8.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/sdk-logs@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-logs@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.218.0 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.219.0 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.1)': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics@2.8.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-node@0.218.0(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-node@0.219.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/api-logs': 0.218.0 - '@opentelemetry/configuration': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-prometheus': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-grpc': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-zipkin': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-b3': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-jaeger': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': 0.218.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-node': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/api-logs': 0.219.0 + '@opentelemetry/configuration': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/context-async-hooks': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-grpc': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-http': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-proto': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-proto': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-prometheus': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-grpc': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-proto': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-zipkin': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-b3': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-jaeger': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.219.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.41.1 transitivePeerDependencies: - supports-color - '@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.41.1 - '@opentelemetry/sdk-trace-node@2.7.1(@opentelemetry/api@1.9.1)': + '@opentelemetry/sdk-trace-node@2.8.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/context-async-hooks': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions@1.41.1': {} - '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.1)': + '@opentelemetry/sql-common@0.42.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) @@ -17609,28 +17569,28 @@ snapshots: dependencies: acorn: 8.17.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))': dependencies: - '@sveltejs/kit': 2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/kit': 2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) - '@sveltejs/enhanced-img@0.10.4(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(rollup@4.62.0)(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/enhanced-img@0.10.4(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(rollup@4.62.0)(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) magic-string: 0.30.21 sharp: 0.34.5 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) - svelte-parse-markup: 0.1.5(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) + svelte-parse-markup: 0.1.5(svelte@5.56.3(@typescript-eslint/types@8.61.0)) vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) vite-imagetools: 9.0.3(rollup@4.62.0) zimmerframe: 1.1.4 transitivePeerDependencies: - rollup - '@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.10(acorn@8.17.0) - '@sveltejs/vite-plugin-svelte': 7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) '@types/cookie': 0.6.0 acorn: 8.17.0 cookie: 0.6.0 @@ -17641,7 +17601,7 @@ snapshots: mrmime: 2.0.1 set-cookie-parser: 3.1.0 sirv: 3.0.2 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) optionalDependencies: '@opentelemetry/api': 1.9.1 @@ -17649,12 +17609,12 @@ snapshots: '@sveltejs/load-config@0.1.1': {} - '@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.3 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) vitefu: 1.1.3(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) @@ -17908,15 +17868,15 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/svelte-core@1.0.0(svelte@5.56.2(@typescript-eslint/types@8.61.0))': + '@testing-library/svelte-core@1.0.0(svelte@5.56.3(@typescript-eslint/types@8.61.0))': dependencies: - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) - '@testing-library/svelte@5.3.1(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)': + '@testing-library/svelte@5.3.1(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0))(vitest@4.1.9)': dependencies: '@testing-library/dom': 10.4.1 - '@testing-library/svelte-core': 1.0.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)) - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + '@testing-library/svelte-core': 1.0.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) optionalDependencies: vite: 8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0) vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(happy-dom@20.10.3)(jsdom@26.1.0(canvas@3.2.3))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) @@ -17934,7 +17894,7 @@ snapshots: '@tokenizer/token@0.3.0': {} - '@trivago/prettier-plugin-sort-imports@6.0.2(prettier-plugin-svelte@4.1.0(prettier@3.8.4)(svelte@5.56.2(@typescript-eslint/types@8.61.0)))(prettier@3.8.4)(svelte@5.56.2(@typescript-eslint/types@8.61.0))': + '@trivago/prettier-plugin-sort-imports@6.0.2(prettier-plugin-svelte@4.1.0(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0)))(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0))': dependencies: '@babel/generator': 7.29.7 '@babel/parser': 7.29.7 @@ -17946,8 +17906,8 @@ snapshots: parse-imports-exports: 0.2.4 prettier: 3.8.4 optionalDependencies: - prettier-plugin-svelte: 4.1.0(prettier@3.8.4)(svelte@5.56.2(@typescript-eslint/types@8.61.0)) - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + prettier-plugin-svelte: 4.1.0(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0)) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) transitivePeerDependencies: - supports-color @@ -18818,10 +18778,10 @@ snapshots: dependencies: '@namnode/store': 0.1.0 - '@zoom-image/svelte@0.3.9(svelte@5.56.2(@typescript-eslint/types@8.61.0))': + '@zoom-image/svelte@0.3.9(svelte@5.56.3(@typescript-eslint/types@8.61.0))': dependencies: '@zoom-image/core': 0.42.0 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) abbrev@1.1.1: {} @@ -19213,15 +19173,15 @@ snapshots: binary-extensions@2.3.0: {} - bits-ui@2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + bits-ui@2.18.1(@internationalized/date@3.12.2)(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: '@floating-ui/core': 1.7.5 '@floating-ui/dom': 1.7.6 '@internationalized/date': 3.12.2 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0)) - svelte: 5.56.2(@typescript-eslint/types@8.61.0) - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + runed: 0.35.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0)) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0)) tabbable: 6.4.0 transitivePeerDependencies: - '@sveltejs/kit' @@ -20777,7 +20737,7 @@ snapshots: '@types/eslint': 9.6.1 eslint-config-prettier: 10.1.8(eslint@10.5.0(jiti@2.7.0)) - eslint-plugin-svelte@3.19.0(eslint@10.5.0(jiti@2.7.0))(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + eslint-plugin-svelte@3.19.0(eslint@10.5.0(jiti@2.7.0))(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0)) '@jridgewell/sourcemap-codec': 1.5.5 @@ -20789,9 +20749,9 @@ snapshots: postcss-load-config: 3.1.4(postcss@8.5.15) postcss-safe-parser: 7.0.1(postcss@8.5.15) semver: 7.8.4 - svelte-eslint-parser: 1.8.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + svelte-eslint-parser: 1.8.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)) optionalDependencies: - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) transitivePeerDependencies: - ts-node @@ -23323,6 +23283,13 @@ snapshots: concat-stream: 2.0.0 type-is: 1.6.18 + multer@2.2.0: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 2.0.0 + type-is: 1.6.18 + multicast-dns@7.2.5: dependencies: dns-packet: 5.6.1 @@ -23475,7 +23442,7 @@ snapshots: node-releases@2.0.47: {} - nodemailer@8.0.11: {} + nodemailer@9.0.1: {} nopt@1.0.10: dependencies: @@ -24451,10 +24418,10 @@ snapshots: dependencies: prettier: 3.8.4 - prettier-plugin-svelte@4.1.0(prettier@3.8.4)(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + prettier-plugin-svelte@4.1.0(prettier@3.8.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: prettier: 3.8.4 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) prettier@3.8.4: {} @@ -25072,14 +25039,14 @@ snapshots: dependencies: queue-microtask: 1.2.3 - runed@0.35.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + runed@0.35.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) optionalDependencies: - '@sveltejs/kit': 2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) + '@sveltejs/kit': 2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)) rw@1.3.3: {} @@ -25671,11 +25638,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-awesome@3.3.5(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + svelte-awesome@3.3.5(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) - svelte-check@4.6.0(picomatch@4.0.4)(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3): + svelte-check@4.6.0(picomatch@4.0.4)(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 '@sveltejs/load-config': 0.1.1 @@ -25683,12 +25650,12 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) typescript: 6.0.3 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.8.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + svelte-eslint-parser@1.8.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -25698,7 +25665,7 @@ snapshots: postcss-selector-parser: 7.1.4 semver: 7.8.4 optionalDependencies: - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) svelte-floating-ui@1.5.8: dependencies: @@ -25711,7 +25678,7 @@ snapshots: dependencies: highlight.js: 11.11.1 - svelte-i18n@4.0.1(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + svelte-i18n@4.0.1(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: cli-color: 2.0.4 deepmerge: 4.3.1 @@ -25719,10 +25686,10 @@ snapshots: estree-walker: 2.0.2 intl-messageformat: 10.7.18 sade: 1.8.1 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) tiny-glob: 0.2.9 - svelte-jsoneditor@3.12.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + svelte-jsoneditor@3.12.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: '@codemirror/autocomplete': 6.20.3 '@codemirror/commands': 6.10.3 @@ -25749,42 +25716,42 @@ snapshots: memoize-one: 6.0.0 natural-compare-lite: 1.4.0 sass: 1.101.0 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) - svelte-awesome: 3.3.5(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) + svelte-awesome: 3.3.5(svelte@5.56.3(@typescript-eslint/types@8.61.0)) svelte-select: 5.8.3 vanilla-picker: 2.12.3 - svelte-maplibre@1.3.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + svelte-maplibre@1.3.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: d3-geo: 3.1.1 dequal: 2.0.3 just-compare: 2.3.0 maplibre-gl: 5.24.0 pmtiles: 3.2.1 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) - svelte-parse-markup@0.1.5(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + svelte-parse-markup@0.1.5(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) - svelte-persisted-store@0.12.0(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + svelte-persisted-store@0.12.0(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) svelte-select@5.8.3: dependencies: svelte-floating-ui: 1.5.8 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0)): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0)): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.2(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.2(@typescript-eslint/types@8.61.0)) + runed: 0.35.1(@sveltejs/kit@2.65.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.56.3(@typescript-eslint/types@8.61.0))(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0))(typescript@6.0.3)(vite@8.0.16(@types/node@24.13.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(tsx@4.22.4)(yaml@2.9.0)))(svelte@5.56.3(@typescript-eslint/types@8.61.0)) style-to-object: 1.0.14 - svelte: 5.56.2(@typescript-eslint/types@8.61.0) + svelte: 5.56.3(@typescript-eslint/types@8.61.0) transitivePeerDependencies: - '@sveltejs/kit' - svelte@5.56.2(@typescript-eslint/types@8.61.0): + svelte@5.56.3(@typescript-eslint/types@8.61.0): dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 diff --git a/readme_i18n/README_tr_TR.md b/readme_i18n/README_tr_TR.md index 5ca61df023..7cafb48025 100644 --- a/readme_i18n/README_tr_TR.md +++ b/readme_i18n/README_tr_TR.md @@ -38,8 +38,8 @@

> [!WARNING] -> ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos! -> +> ⚠️ Değerli fotoğraflarınız ve videolarınız için daima [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) yedekleme planını uygulayın! +> > [!NOTE] > Kurulum dahil olmak üzere resmi belgeleri https://immich.app/ adresinde bulabilirsiniz. diff --git a/server/Dockerfile b/server/Dockerfile index 058938a42a..1df476dccd 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -56,7 +56,7 @@ FROM builder AS plugins ARG TARGETPLATFORM -COPY --from=ghcr.io/jdx/mise:2026.5.18@sha256:5bb3311994fa78cef307ca3077cdb18f9551da0886371fc26ea91ab56220ffc5 /usr/local/bin/mise /usr/local/bin/mise +COPY --from=ghcr.io/jdx/mise:2026.6.10@sha256:f57ac375a262f52f8ac3f9101348dbff2187d5e4b59612154f2f2808dbe46ef6 /usr/local/bin/mise /usr/local/bin/mise WORKDIR /app COPY ./mise.toml ./mise.toml diff --git a/server/Dockerfile.dev b/server/Dockerfile.dev index 83fdc3eb02..b54e10831c 100644 --- a/server/Dockerfile.dev +++ b/server/Dockerfile.dev @@ -2,7 +2,7 @@ FROM ghcr.io/immich-app/base-server-dev:202606161235@sha256:9f88b07acc8b7bf37a1dd3d5a19193f664443eaaab4e08e9f9341414c5e4b23f AS dev -COPY --from=ghcr.io/jdx/mise:2026.5.18@sha256:5bb3311994fa78cef307ca3077cdb18f9551da0886371fc26ea91ab56220ffc5 /usr/local/bin/mise /usr/local/bin/mise +COPY --from=ghcr.io/jdx/mise:2026.6.10@sha256:f57ac375a262f52f8ac3f9101348dbff2187d5e4b59612154f2f2808dbe46ef6 /usr/local/bin/mise /usr/local/bin/mise RUN echo "devdir=/buildcache/node-gyp" >> /usr/local/etc/npmrc && \ echo "store-dir=/buildcache/pnpm-store" >> /usr/local/etc/npmrc && \ diff --git a/server/eslint.config.mjs b/server/eslint.config.mjs index bdbac34da6..c86bc3d72f 100644 --- a/server/eslint.config.mjs +++ b/server/eslint.config.mjs @@ -51,6 +51,7 @@ export default typescriptEslint.config([ 'unicorn/no-array-sort': 'off', '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/no-misused-promises': 'error', + '@typescript-eslint/switch-exhaustiveness-check': ['error', { considerDefaultExhaustiveForUnions: true }], 'require-await': 'off', '@typescript-eslint/require-await': 'error', curly: 2, diff --git a/server/package.json b/server/package.json index 30eaa22d4f..c399dabe5c 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "immich", - "version": "3.0.0-rc.1", + "version": "3.0.0-rc.2", "description": "", "author": "", "private": true, @@ -49,14 +49,14 @@ "@nestjs/websockets": "^11.0.4", "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.0.0", - "@opentelemetry/exporter-prometheus": "^0.218.0", - "@opentelemetry/instrumentation-http": "^0.218.0", - "@opentelemetry/instrumentation-ioredis": "^0.66.0", - "@opentelemetry/instrumentation-nestjs-core": "^0.64.0", - "@opentelemetry/instrumentation-pg": "^0.70.0", + "@opentelemetry/exporter-prometheus": "^0.219.0", + "@opentelemetry/instrumentation-http": "^0.219.0", + "@opentelemetry/instrumentation-ioredis": "^0.67.0", + "@opentelemetry/instrumentation-nestjs-core": "^0.65.0", + "@opentelemetry/instrumentation-pg": "^0.71.0", "@opentelemetry/resources": "^2.0.1", "@opentelemetry/sdk-metrics": "^2.0.1", - "@opentelemetry/sdk-node": "^0.218.0", + "@opentelemetry/sdk-node": "^0.219.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@react-email/components": "^1.0.0", "@react-email/render": "^2.0.0", @@ -95,7 +95,7 @@ "nestjs-kysely": "3.1.2", "nestjs-otel": "^8.0.0", "nestjs-zod": "^5.3.0", - "nodemailer": "^8.0.0", + "nodemailer": "^9.0.0", "openid-client": "^6.3.3", "pg": "^8.11.3", "picomatch": "^4.0.2", diff --git a/server/src/commands/schema-check.ts b/server/src/commands/schema-check.ts index e0ccae8469..fc38254dde 100644 --- a/server/src/commands/schema-check.ts +++ b/server/src/commands/schema-check.ts @@ -28,9 +28,13 @@ export class SchemaCheck extends CommandRunner { } case 'missing': { - console.log(` - ${migration.name} exists, but has not been applied to the database`); + console.log(` - ${migration.name} exists on disk, but has not been applied to the database`); break; } + + case 'applied': { + break; // happy path, do nothing + } } } } diff --git a/server/src/constants.ts b/server/src/constants.ts index 8a0f979ff0..7760fa8e8c 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -223,6 +223,12 @@ export const SUPPORTED_HWA_CODECS: Record = { + [VideoCodec.H264]: 23, + [VideoCodec.Hevc]: 28, + [VideoCodec.Vp9]: 31, + [VideoCodec.Av1]: 35, +}; export const HLS_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; export const HLS_LEASE_DURATION_MS = 30 * 60 * 1000; export const HLS_PLAYLIST_CONTENT_TYPE = 'application/vnd.apple.mpegurl'; diff --git a/server/src/controllers/asset-media.controller.spec.ts b/server/src/controllers/asset-media.controller.spec.ts index 056c3d4df7..668d88acbf 100644 --- a/server/src/controllers/asset-media.controller.spec.ts +++ b/server/src/controllers/asset-media.controller.spec.ts @@ -112,6 +112,38 @@ describe(AssetMediaController.name, () => { ); }); + it('should accept a non-UTC timezone offset', async () => { + const fileCreatedAt = '2026-05-28T19:51:20.555+02:00'; + const { status } = await request(ctx.getHttpServer()) + .post('/assets') + .attach('assetData', assetData, filename) + .field({ ...makeUploadDto(), fileCreatedAt }); + + expect(status).toBe(200); + expect(service.uploadAsset).toHaveBeenCalledWith( + undefined, + expect.objectContaining({ + fileCreatedAt: new Date(fileCreatedAt), + }), + expect.objectContaining({ originalName: 'example.png' }), + undefined, + ); + }); + + it('should reject a timezone-less datetime', async () => { + const { status, body } = await request(ctx.getHttpServer()) + .post('/assets') + .attach('assetData', assetData, filename) + .field({ ...makeUploadDto(), fileCreatedAt: '2026-05-28T19:51:20.555706' }); + + expect(status).toBe(400); + expect(body).toEqual( + factory.responses.validationError([ + { path: ['fileCreatedAt'], message: 'Invalid input: expected ISO 8601 datetime string, received string' }, + ]), + ); + }); + it('should throw if `isFavorite` is not a boolean', async () => { const { status, body } = await request(ctx.getHttpServer()) .post('/assets') diff --git a/server/src/controllers/integrity-admin.controller.ts b/server/src/controllers/integrity-admin.controller.ts index 07a074ac58..a0ef6c1072 100644 --- a/server/src/controllers/integrity-admin.controller.ts +++ b/server/src/controllers/integrity-admin.controller.ts @@ -7,13 +7,14 @@ import { IntegrityGetReportDto, IntegrityReportResponseDto, IntegrityReportSummaryResponseDto, + IntegrityReportTypeParamDto, } from 'src/dtos/integrity.dto'; import { ApiTag, Permission } from 'src/enum'; import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { IntegrityService } from 'src/services/integrity.service'; import { sendFile } from 'src/utils/file'; -import { IntegrityReportTypeParamDto, UUIDv7ParamDto } from 'src/validation'; +import { UUIDv7ParamDto } from 'src/validation'; @ApiTags(ApiTag.Maintenance) @Controller('admin/integrity') diff --git a/server/src/controllers/system-config.controller.spec.ts b/server/src/controllers/system-config.controller.spec.ts index 4e86aa56db..1770bdd89e 100644 --- a/server/src/controllers/system-config.controller.spec.ts +++ b/server/src/controllers/system-config.controller.spec.ts @@ -70,7 +70,7 @@ describe(SystemConfigController.name, () => { errorDto.validationError([ { path: ['nightlyTasks', 'startTime'], - message: 'Invalid input: expected string in HH:mm format, received string', + message: 'Invalid input: expected string in HH:MM format, received string', }, ]), ); diff --git a/server/src/controllers/video-stream.controller.ts b/server/src/controllers/video-stream.controller.ts index c5f447af88..ed99f67296 100644 --- a/server/src/controllers/video-stream.controller.ts +++ b/server/src/controllers/video-stream.controller.ts @@ -6,6 +6,7 @@ import { HLS_PLAYLIST_CONTENT_TYPE } from 'src/constants'; import { Endpoint, HistoryBuilder } from 'src/decorators'; import { AuthDto } from 'src/dtos/auth.dto'; import { + HlsPlaylistHeaderDto, HlsSegmentHeaderDto, HlsSegmentParamDto, HlsSessionParamDto, @@ -50,8 +51,17 @@ export class VideoStreamController { description: 'Returns an HLS media playlist for one variant of the streaming session.', history: new HistoryBuilder().added('v3').alpha('v3'), }) - getMediaPlaylist(@Auth() auth: AuthDto, @Param() { id, sessionId }: HlsVariantParamDto) { - return this.service.getMediaPlaylist(auth, id, sessionId); + getMediaPlaylist( + @Auth() auth: AuthDto, + @Param() { id, sessionId, variantIndex }: HlsVariantParamDto, + @Headers() headers: HlsPlaylistHeaderDto, + ) { + try { + headers = HlsPlaylistHeaderDto.create(headers); + } catch (error) { + throw new ZodValidationException(error); + } + return this.service.getMediaPlaylist(auth, id, sessionId, variantIndex, headers[ImmichHeader.HlsPosition]); } @Get(':id/video/stream/:sessionId/:variantIndex/:filename') diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts index 8802145020..6c7d6f0e3e 100644 --- a/server/src/cores/storage.core.ts +++ b/server/src/cores/storage.core.ts @@ -9,6 +9,7 @@ import { PersonPathType, RawExtractedFormat, StorageFolder, + UserPathType, } from 'src/enum'; import { AssetRepository } from 'src/repositories/asset.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; @@ -327,13 +328,19 @@ export class StorageCore { case AssetFileType.EncodedVideo: case AssetFileType.Thumbnail: case AssetFileType.Preview: - case AssetFileType.Sidecar: { + case AssetFileType.Sidecar: + case AssetPathType.EncodedVideo: { return this.assetRepository.upsertFile({ assetId: id, type: pathType as AssetFileType, path: newPath }); } case PersonPathType.Face: { return this.personRepository.update({ id, thumbnailPath: newPath }); } + + case UserPathType.Profile: { + this.logger.warn('Unexpected path type:', pathType); + return; + } } } diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts index ee6f2c07eb..a66f912972 100644 --- a/server/src/dtos/album.dto.ts +++ b/server/src/dtos/album.dto.ts @@ -100,21 +100,21 @@ const AlbumUserResponseSchema = z const ContributorCountResponseSchema = z .object({ - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), assetCount: z.int().min(0).describe('Number of assets contributed'), }) .meta({ id: 'ContributorCountResponseDto' }); export const AlbumResponseSchema = z .object({ - id: z.string().describe('Album ID'), + id: z.uuidv4().describe('Album ID'), albumName: z.string().describe('Album name'), description: z.string().describe('Album description'), // TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers. createdAt: z.string().meta({ format: 'date-time' }).describe('Creation date'), // TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers. updatedAt: z.string().meta({ format: 'date-time' }).describe('Last update date'), - albumThumbnailAssetId: z.string().nullable().describe('Thumbnail asset ID'), + albumThumbnailAssetId: z.uuidv4().nullable().describe('Thumbnail asset ID'), shared: z.boolean().describe('Is shared album'), albumUsers: z .array(AlbumUserResponseSchema) diff --git a/server/src/dtos/api-key.dto.ts b/server/src/dtos/api-key.dto.ts index 176812cc4f..470808e430 100644 --- a/server/src/dtos/api-key.dto.ts +++ b/server/src/dtos/api-key.dto.ts @@ -21,7 +21,7 @@ const ApiKeyUpdateSchema = z const ApiKeyResponseSchema = z .object({ - id: z.string().describe('API key ID'), + id: z.uuidv4().describe('API key ID'), name: z.string().describe('API key name'), createdAt: isoDatetimeToDate.describe('Creation date'), updatedAt: isoDatetimeToDate.describe('Last update date'), diff --git a/server/src/dtos/asset-ids.response.dto.ts b/server/src/dtos/asset-ids.response.dto.ts index 346829e644..e86eee0973 100644 --- a/server/src/dtos/asset-ids.response.dto.ts +++ b/server/src/dtos/asset-ids.response.dto.ts @@ -16,7 +16,7 @@ const AssetIdErrorReasonSchema = z /** @deprecated Use `BulkIdResponseDto` instead */ const AssetIdsResponseSchema = z .object({ - assetId: z.string().describe('Asset ID'), + assetId: z.uuidv4().describe('Asset ID'), success: z.boolean().describe('Whether operation succeeded'), error: AssetIdErrorReasonSchema.optional(), }) @@ -43,7 +43,7 @@ export const BulkIdsSchema = z const BulkIdResponseSchema = z .object({ - id: z.string().describe('ID'), + id: z.uuidv4().describe('ID'), success: z.boolean().describe('Whether operation succeeded'), error: BulkIdErrorReasonSchema.optional(), errorMessage: z.string().optional(), diff --git a/server/src/dtos/asset-media-response.dto.ts b/server/src/dtos/asset-media-response.dto.ts index 0f27fc5974..a35d75fb5f 100644 --- a/server/src/dtos/asset-media-response.dto.ts +++ b/server/src/dtos/asset-media-response.dto.ts @@ -11,7 +11,7 @@ const AssetMediaStatusSchema = z.enum(AssetMediaStatus).describe('Upload status' const AssetMediaResponseSchema = z .object({ status: AssetMediaStatusSchema, - id: z.string().describe('Asset media ID'), + id: z.uuidv4().describe('Asset media ID'), }) .meta({ id: 'AssetMediaResponseDto' }); @@ -34,10 +34,10 @@ const AssetRejectReasonSchema = z const AssetBulkUploadCheckResultSchema = z .object({ - id: z.string().describe('Asset ID'), + id: z.string().describe('Client-side identifier echoed from the request to match results to inputs'), action: AssetUploadActionSchema, reason: AssetRejectReasonSchema.optional(), - assetId: z.string().optional().describe('Existing asset ID if duplicate'), + assetId: z.uuidv4().optional().describe('Existing asset ID if duplicate'), isTrashed: z.boolean().optional().describe('Whether existing asset is trashed'), }) .meta({ id: 'AssetBulkUploadCheckResult' }); diff --git a/server/src/dtos/asset-response.dto.ts b/server/src/dtos/asset-response.dto.ts index b5b0b04a2d..ba64286647 100644 --- a/server/src/dtos/asset-response.dto.ts +++ b/server/src/dtos/asset-response.dto.ts @@ -24,7 +24,7 @@ import z from 'zod'; const SanitizedAssetResponseSchema = z .object({ - id: z.string().describe('Asset ID'), + id: z.uuidv4().describe('Asset ID'), type: AssetTypeSchema, thumbhash: z .string() @@ -52,8 +52,8 @@ export class SanitizedAssetResponseDto extends createZodDto(SanitizedAssetRespon const AssetStackResponseSchema = z .object({ - id: z.string().describe('Stack ID'), - primaryAssetId: z.string().describe('Primary asset ID'), + id: z.uuidv4().describe('Stack ID'), + primaryAssetId: z.uuidv4().describe('Primary asset ID'), assetCount: z.int().min(0).describe('Number of assets in stack'), }) .meta({ id: 'AssetStackResponseDto' }); @@ -65,7 +65,7 @@ export const AssetResponseSchema = SanitizedAssetResponseSchema.extend( .string() .meta({ format: 'date-time' }) .describe('The UTC timestamp when the asset was originally uploaded to Immich.'), - ownerId: z.string().describe('Owner user ID'), + ownerId: z.uuidv4().describe('Owner user ID'), owner: UserResponseSchema.optional(), libraryId: z .uuidv4() @@ -103,7 +103,7 @@ export const AssetResponseSchema = SanitizedAssetResponseSchema.extend( people: z.array(PersonResponseSchema).optional(), checksum: z.string().describe('Base64 encoded SHA1 hash'), stack: AssetStackResponseSchema.nullish(), - duplicateId: z.string().nullish().describe('Duplicate group ID'), + duplicateId: z.uuidv4().nullish().describe('Duplicate group ID'), resized: z .boolean() .optional() diff --git a/server/src/dtos/asset.dto.ts b/server/src/dtos/asset.dto.ts index 3dddcfaa3e..26c983a7de 100644 --- a/server/src/dtos/asset.dto.ts +++ b/server/src/dtos/asset.dto.ts @@ -148,7 +148,7 @@ const AssetMetadataResponseSchema = z .meta({ id: 'AssetMetadataResponseDto' }); const AssetMetadataBulkResponseSchema = AssetMetadataResponseSchema.extend({ - assetId: z.string().describe('Asset ID'), + assetId: z.uuidv4().describe('Asset ID'), }).meta({ id: 'AssetMetadataBulkResponseDto' }); const AssetCopySchema = z diff --git a/server/src/dtos/auth.dto.ts b/server/src/dtos/auth.dto.ts index 1f75401e33..40b4e25b6d 100644 --- a/server/src/dtos/auth.dto.ts +++ b/server/src/dtos/auth.dto.ts @@ -29,7 +29,7 @@ const LoginCredentialSchema = z const LoginResponseSchema = z .object({ accessToken: z.string().describe('Access token'), - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), userEmail: toEmail.describe('User email'), name: z.string().describe('User name'), profileImagePath: z.string().describe('Profile image path'), diff --git a/server/src/dtos/download.dto.ts b/server/src/dtos/download.dto.ts index b44a6a7afc..c37fa5c113 100644 --- a/server/src/dtos/download.dto.ts +++ b/server/src/dtos/download.dto.ts @@ -14,7 +14,7 @@ const DownloadInfoSchema = z const DownloadArchiveInfoSchema = z .object({ size: z.int().describe('Archive size in bytes'), - assetIds: z.array(z.string()).describe('Asset IDs in this archive'), + assetIds: z.array(z.uuidv4()).describe('Asset IDs in this archive'), }) .meta({ id: 'DownloadArchiveInfo' }); diff --git a/server/src/dtos/duplicate.dto.ts b/server/src/dtos/duplicate.dto.ts index 55427e36aa..66d14e605b 100644 --- a/server/src/dtos/duplicate.dto.ts +++ b/server/src/dtos/duplicate.dto.ts @@ -4,7 +4,7 @@ import z from 'zod'; const DuplicateResponseSchema = z .object({ - duplicateId: z.string().describe('Duplicate group ID'), + duplicateId: z.uuidv4().describe('Duplicate group ID'), assets: z.array(AssetResponseSchema).describe('Duplicate assets'), suggestedKeepAssetIds: z.array(z.uuidv4()).describe('Suggested asset IDs to keep based on file size and EXIF data'), }) diff --git a/server/src/dtos/integrity.dto.ts b/server/src/dtos/integrity.dto.ts index b4faf6005e..52df5bf794 100644 --- a/server/src/dtos/integrity.dto.ts +++ b/server/src/dtos/integrity.dto.ts @@ -10,8 +10,6 @@ const IntegrityReportSummaryResponseSchema = z }) .meta({ id: 'IntegrityReportSummaryResponseDto' }); -export class IntegrityReportSummaryResponseDto extends createZodDto(IntegrityReportSummaryResponseSchema) {} - const IntegrityGetReportSchema = z .object({ type: IntegrityReportSchema, @@ -20,17 +18,14 @@ const IntegrityGetReportSchema = z }) .meta({ id: 'IntegrityGetReportDto' }); -export class IntegrityGetReportDto extends createZodDto(IntegrityGetReportSchema) {} - const IntegrityDeleteReportSchema = z.object({ type: IntegrityReport }).meta({ id: 'IntegrityDeleteReportDto' }); -export class IntegrityDeleteReportDto extends createZodDto(IntegrityDeleteReportSchema) {} - const IntegrityReportResponseItemSchema = z.object({ - id: z.string().describe('Integrity report item id'), + id: z.uuidv4().describe('Integrity report item id'), type: IntegrityReportSchema, path: z.string().describe('Integrity report item path'), }); + const IntegrityReportResponseSchema = z .object({ items: z.array(IntegrityReportResponseItemSchema), @@ -38,4 +33,10 @@ const IntegrityReportResponseSchema = z }) .meta({ id: 'IntegrityReportResponseDto' }); +const IntegrityReportParamSchema = z.object({ type: IntegrityReportSchema }).meta({ id: 'IntegrityReportDto' }); + +export class IntegrityReportSummaryResponseDto extends createZodDto(IntegrityReportSummaryResponseSchema) {} +export class IntegrityGetReportDto extends createZodDto(IntegrityGetReportSchema) {} +export class IntegrityDeleteReportDto extends createZodDto(IntegrityDeleteReportSchema) {} export class IntegrityReportResponseDto extends createZodDto(IntegrityReportResponseSchema) {} +export class IntegrityReportTypeParamDto extends createZodDto(IntegrityReportParamSchema) {} diff --git a/server/src/dtos/json-schema.dto.ts b/server/src/dtos/json-schema.dto.ts index 65b92277d5..81b14ad62a 100644 --- a/server/src/dtos/json-schema.dto.ts +++ b/server/src/dtos/json-schema.dto.ts @@ -14,7 +14,12 @@ const JsonSchemaPropertySchema = z enum: z.array(z.string()).optional().describe('Valid choices for enum types'), array: z.boolean().optional().describe('Type is an array type'), required: z.array(z.string()).optional().describe('A list of required properties'), - uiHint: z.string().optional(), + uiHint: z + .object({ + type: z.string().optional(), + order: z.int().optional(), + }) + .optional(), get properties() { return z.record(z.string(), JsonSchemaPropertySchema).optional(); }, diff --git a/server/src/dtos/library.dto.ts b/server/src/dtos/library.dto.ts index aafdd9f793..0819be278e 100644 --- a/server/src/dtos/library.dto.ts +++ b/server/src/dtos/library.dto.ts @@ -62,8 +62,8 @@ const ValidateLibraryResponseSchema = z const LibraryResponseSchema = z .object({ - id: z.string().describe('Library ID'), - ownerId: z.string().describe('Owner user ID'), + id: z.uuidv4().describe('Library ID'), + ownerId: z.uuidv4().describe('Owner user ID'), name: z.string().describe('Library name'), assetCount: z.int().describe('Number of assets'), importPaths: z.array(z.string()).describe('Import paths'), diff --git a/server/src/dtos/map.dto.ts b/server/src/dtos/map.dto.ts index 6a4776d49d..f442850936 100644 --- a/server/src/dtos/map.dto.ts +++ b/server/src/dtos/map.dto.ts @@ -30,7 +30,7 @@ const MapMarkerSchema = z const MapMarkerResponseSchema = z .object({ - id: z.string().describe('Asset ID'), + id: z.uuidv4().describe('Asset ID'), lat: z.number().meta({ format: 'double' }).describe('Latitude'), lon: z.number().meta({ format: 'double' }).describe('Longitude'), city: z.string().nullable().describe('City name'), diff --git a/server/src/dtos/memory.dto.ts b/server/src/dtos/memory.dto.ts index ce2e9fda6c..324474a1e1 100644 --- a/server/src/dtos/memory.dto.ts +++ b/server/src/dtos/memory.dto.ts @@ -59,7 +59,7 @@ const MemoryStatisticsResponseSchema = z const MemoryResponseSchema = z .object({ - id: z.string().describe('Memory ID'), + id: z.uuidv4().describe('Memory ID'), createdAt: isoDatetimeToDate.describe('Creation date'), updatedAt: isoDatetimeToDate.describe('Last update date'), deletedAt: isoDatetimeToDate.optional().describe('Deletion date'), @@ -67,7 +67,7 @@ const MemoryResponseSchema = z seenAt: isoDatetimeToDate.optional().describe('Date when memory was seen'), showAt: isoDatetimeToDate.optional().describe('Date when memory should be shown'), hideAt: isoDatetimeToDate.optional().describe('Date when memory should be hidden'), - ownerId: z.string().describe('Owner user ID'), + ownerId: z.uuidv4().describe('Owner user ID'), type: MemoryTypeSchema, data: OnThisDaySchema, isSaved: z.boolean().describe('Is memory saved'), diff --git a/server/src/dtos/notification.dto.ts b/server/src/dtos/notification.dto.ts index f474cfc0a1..04f937fc00 100644 --- a/server/src/dtos/notification.dto.ts +++ b/server/src/dtos/notification.dto.ts @@ -24,7 +24,7 @@ const TemplateSchema = z const NotificationSchema = z .object({ - id: z.string().describe('Notification ID'), + id: z.uuidv4().describe('Notification ID'), createdAt: isoDatetimeToDate.describe('Creation date'), level: NotificationLevelSchema, type: NotificationTypeSchema, diff --git a/server/src/dtos/person.dto.ts b/server/src/dtos/person.dto.ts index 38e856b8c1..fa56ded176 100644 --- a/server/src/dtos/person.dto.ts +++ b/server/src/dtos/person.dto.ts @@ -33,7 +33,7 @@ const PersonUpdateSchema = PersonCreateSchema.extend({ }).meta({ id: 'PersonUpdateDto' }); const PeopleUpdateItemSchema = PersonUpdateSchema.extend({ - id: z.string().describe('Person ID'), + id: z.uuidv4().describe('Person ID'), }).meta({ id: 'PeopleUpdateItem' }); const PeopleUpdateSchema = z @@ -60,7 +60,7 @@ const PersonSearchSchema = z export const PersonResponseSchema = z .object({ - id: z.string().describe('Person ID'), + id: z.uuidv4().describe('Person ID'), name: z.string().describe('Person name'), // TODO: use `isoDateToDate` when using `ZodSerializerDto` on the controllers. birthDate: z.string().meta({ format: 'date' }).describe('Person date of birth').nullable(), diff --git a/server/src/dtos/plugin.dto.ts b/server/src/dtos/plugin.dto.ts index 8ee695d61c..777380a107 100644 --- a/server/src/dtos/plugin.dto.ts +++ b/server/src/dtos/plugin.dto.ts @@ -32,7 +32,7 @@ const PluginMethodResponseSchema = z const PluginResponseSchema = z .object({ - id: z.string().describe('Plugin ID'), + id: z.uuidv4().describe('Plugin ID'), name: z.string().describe('Plugin name'), title: z.string().describe('Plugin title'), description: z.string().describe('Plugin description'), diff --git a/server/src/dtos/server.dto.ts b/server/src/dtos/server.dto.ts index 03d45fab1c..0215558aab 100644 --- a/server/src/dtos/server.dto.ts +++ b/server/src/dtos/server.dto.ts @@ -73,7 +73,7 @@ const ServerVersionResponseSchema = z const ServerVersionHistoryResponseSchema = z .object({ - id: z.string().describe('Version history entry ID'), + id: z.uuidv4().describe('Version history entry ID'), createdAt: isoDatetimeToDate.describe('When this version was first seen'), version: z.string().describe('Version string'), }) @@ -81,7 +81,7 @@ const ServerVersionHistoryResponseSchema = z const UsageByUserSchema = z .object({ - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), userName: z.string().describe('User name'), photos: z.int().describe('Number of photos'), videos: z.int().describe('Number of videos'), diff --git a/server/src/dtos/session.dto.ts b/server/src/dtos/session.dto.ts index 424d104053..c81dac6d79 100644 --- a/server/src/dtos/session.dto.ts +++ b/server/src/dtos/session.dto.ts @@ -18,7 +18,7 @@ const SessionUpdateSchema = z const SessionResponseSchema = z .object({ - id: z.string().describe('Session ID'), + id: z.uuidv4().describe('Session ID'), createdAt: z.string().describe('Creation date'), updatedAt: z.string().describe('Last update date'), expiresAt: z.string().optional().describe('Expiration date'), diff --git a/server/src/dtos/shared-link.dto.ts b/server/src/dtos/shared-link.dto.ts index aebe6fcab1..6763f6ab74 100644 --- a/server/src/dtos/shared-link.dto.ts +++ b/server/src/dtos/shared-link.dto.ts @@ -53,10 +53,10 @@ const SharedLinkLoginSchema = z const SharedLinkResponseSchema = z .object({ - id: z.string().describe('Shared link ID'), + id: z.uuidv4().describe('Shared link ID'), description: z.string().nullable().describe('Link description'), password: z.string().nullable().describe('Has password'), - userId: z.string().describe('Owner user ID'), + userId: z.uuidv4().describe('Owner user ID'), key: z.string().describe('Encryption key (base64url)'), type: SharedLinkTypeSchema, createdAt: isoDatetimeToDate.describe('Creation date'), diff --git a/server/src/dtos/stack.dto.ts b/server/src/dtos/stack.dto.ts index 48354cec6b..1956974ba1 100644 --- a/server/src/dtos/stack.dto.ts +++ b/server/src/dtos/stack.dto.ts @@ -24,8 +24,8 @@ const StackUpdateSchema = z const StackResponseSchema = z .object({ - id: z.string().describe('Stack ID'), - primaryAssetId: z.string().describe('Primary asset ID'), + id: z.uuidv4().describe('Stack ID'), + primaryAssetId: z.uuidv4().describe('Primary asset ID'), assets: z.array(AssetResponseSchema), }) .describe('Stack response') diff --git a/server/src/dtos/streaming.dto.ts b/server/src/dtos/streaming.dto.ts index e1d98745f8..c83c3e38af 100644 --- a/server/src/dtos/streaming.dto.ts +++ b/server/src/dtos/streaming.dto.ts @@ -32,3 +32,11 @@ const HlsSegmentHeaderSchema = z.object({ }); export class HlsSegmentHeaderDto extends createZodDto(HlsSegmentHeaderSchema) {} + +const HlsPlaylistHeaderSchema = z.object({ + // Lets the client hint at which segment will be loaded after the playlist. + // A position rather than a segment index since indices aren't comparable across variants. + [ImmichHeader.HlsPosition]: z.coerce.number().min(0).optional(), +}); + +export class HlsPlaylistHeaderDto extends createZodDto(HlsPlaylistHeaderSchema) {} diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts index 531ac6277c..1d5fe64aa1 100644 --- a/server/src/dtos/sync.dto.ts +++ b/server/src/dtos/sync.dto.ts @@ -19,7 +19,7 @@ import z from 'zod'; const SyncUserV1Schema = z .object({ - id: z.string().describe('User ID'), + id: z.uuidv4().describe('User ID'), name: z.string().describe('User name'), email: z.string().describe('User email'), avatarColor: UserAvatarColorSchema.nullish(), @@ -40,27 +40,27 @@ const SyncAuthUserV1Schema = SyncUserV1Schema.merge( }), ).meta({ id: 'SyncAuthUserV1' }); -const SyncUserDeleteV1Schema = z.object({ userId: z.string().describe('User ID') }).meta({ id: 'SyncUserDeleteV1' }); +const SyncUserDeleteV1Schema = z.object({ userId: z.uuidv4().describe('User ID') }).meta({ id: 'SyncUserDeleteV1' }); const SyncPartnerV1Schema = z .object({ - sharedById: z.string().describe('Shared by ID'), - sharedWithId: z.string().describe('Shared with ID'), + sharedById: z.uuidv4().describe('Shared by ID'), + sharedWithId: z.uuidv4().describe('Shared with ID'), inTimeline: z.boolean().describe('In timeline'), }) .meta({ id: 'SyncPartnerV1' }); const SyncPartnerDeleteV1Schema = z .object({ - sharedById: z.string().describe('Shared by ID'), - sharedWithId: z.string().describe('Shared with ID'), + sharedById: z.uuidv4().describe('Shared by ID'), + sharedWithId: z.uuidv4().describe('Shared with ID'), }) .meta({ id: 'SyncPartnerDeleteV1' }); const SyncAssetV1Schema = z .object({ - id: z.string().describe('Asset ID'), - ownerId: z.string().describe('Owner ID'), + id: z.uuidv4().describe('Asset ID'), + ownerId: z.uuidv4().describe('Owner ID'), originalFileName: z.string().describe('Original file name'), thumbhash: z.string().nullable().describe('Thumbhash'), checksum: z.string().describe('Checksum'), @@ -84,8 +84,8 @@ const SyncAssetV1Schema = z const SyncAssetV2Schema = z .object({ - id: z.string().describe('Asset ID'), - ownerId: z.string().describe('Owner ID'), + id: z.uuidv4().describe('Asset ID'), + ownerId: z.uuidv4().describe('Owner ID'), originalFileName: z.string().describe('Original file name'), thumbhash: z.string().nullable().describe('Thumbhash'), checksum: z.string().describe('Checksum'), @@ -123,12 +123,12 @@ export class SyncAssetV1 extends createZodDto(SyncAssetV1Schema) {} export class SyncAssetV2 extends createZodDto(SyncAssetV2Schema) {} const SyncAssetDeleteV1Schema = z - .object({ assetId: z.string().describe('Asset ID') }) + .object({ assetId: z.uuidv4().describe('Asset ID') }) .meta({ id: 'SyncAssetDeleteV1' }); const SyncAssetExifV1Schema = z .object({ - assetId: z.string().describe('Asset ID'), + assetId: z.uuidv4().describe('Asset ID'), description: z.string().nullable().describe('Description'), exifImageWidth: z.int().nullable().describe('Exif image width'), exifImageHeight: z.int().nullable().describe('Exif image height'), @@ -158,7 +158,7 @@ const SyncAssetExifV1Schema = z const SyncAssetMetadataV1Schema = z .object({ - assetId: z.string().describe('Asset ID'), + assetId: z.uuidv4().describe('Asset ID'), key: z.string().describe('Key'), value: z.record(z.string(), z.unknown()).describe('Value'), }) @@ -166,15 +166,15 @@ const SyncAssetMetadataV1Schema = z const SyncAssetMetadataDeleteV1Schema = z .object({ - assetId: z.string().describe('Asset ID'), + assetId: z.uuidv4().describe('Asset ID'), key: z.string().describe('Key'), }) .meta({ id: 'SyncAssetMetadataDeleteV1' }); const SyncAssetEditV1Schema = z .object({ - id: z.string().describe('Edit ID'), - assetId: z.string().describe('Asset ID'), + id: z.uuidv4().describe('Edit ID'), + assetId: z.uuidv4().describe('Asset ID'), action: AssetEditActionSchema, parameters: z.record(z.string(), z.unknown()).describe('Edit parameters'), sequence: z.int().describe('Edit sequence'), @@ -182,7 +182,7 @@ const SyncAssetEditV1Schema = z .meta({ id: 'SyncAssetEditV1' }); const SyncAssetEditDeleteV1Schema = z - .object({ editId: z.string().describe('Edit ID') }) + .object({ editId: z.uuidv4().describe('Edit ID') }) .meta({ id: 'SyncAssetEditDeleteV1' }); @ExtraModel() @@ -199,28 +199,28 @@ export class SyncAssetEditV1 extends createZodDto(SyncAssetEditV1Schema) {} class SyncAssetEditDeleteV1 extends createZodDto(SyncAssetEditDeleteV1Schema) {} const SyncAlbumDeleteV1Schema = z - .object({ albumId: z.string().describe('Album ID') }) + .object({ albumId: z.uuidv4().describe('Album ID') }) .meta({ id: 'SyncAlbumDeleteV1' }); const SyncAlbumUserDeleteV1Schema = z .object({ - albumId: z.string().describe('Album ID'), - userId: z.string().describe('User ID'), + albumId: z.uuidv4().describe('Album ID'), + userId: z.uuidv4().describe('User ID'), }) .meta({ id: 'SyncAlbumUserDeleteV1' }); const SyncAlbumUserV1Schema = z .object({ - albumId: z.string().describe('Album ID'), - userId: z.string().describe('User ID'), + albumId: z.uuidv4().describe('Album ID'), + userId: z.uuidv4().describe('User ID'), role: AlbumUserRoleSchema, }) .meta({ id: 'SyncAlbumUserV1' }); const SyncAlbumV1Schema = z .object({ - id: z.string().describe('Album ID'), - ownerId: z.string().describe('Owner ID'), + id: z.uuidv4().describe('Album ID'), + ownerId: z.uuidv4().describe('Owner ID'), name: z.string().describe('Album name'), description: z.string().describe('Album description'), createdAt: isoDatetimeToDate.describe('Created at'), @@ -233,7 +233,7 @@ const SyncAlbumV1Schema = z const SyncAlbumV2Schema = z .object({ - id: z.string().describe('Album ID'), + id: z.uuidv4().describe('Album ID'), name: z.string().describe('Album name'), description: z.string().describe('Album description'), createdAt: isoDatetimeToDate.describe('Created at'), @@ -246,15 +246,15 @@ const SyncAlbumV2Schema = z const SyncAlbumToAssetV1Schema = z .object({ - albumId: z.string().describe('Album ID'), - assetId: z.string().describe('Asset ID'), + albumId: z.uuidv4().describe('Album ID'), + assetId: z.uuidv4().describe('Asset ID'), }) .meta({ id: 'SyncAlbumToAssetV1' }); const SyncAlbumToAssetDeleteV1Schema = z .object({ - albumId: z.string().describe('Album ID'), - assetId: z.string().describe('Asset ID'), + albumId: z.uuidv4().describe('Album ID'), + assetId: z.uuidv4().describe('Asset ID'), }) .meta({ id: 'SyncAlbumToAssetDeleteV1' }); @@ -284,11 +284,11 @@ export function syncAlbumV2ToV1( const SyncMemoryV1Schema = z .object({ - id: z.string().describe('Memory ID'), + id: z.uuidv4().describe('Memory ID'), createdAt: isoDatetimeToDate.describe('Created at'), updatedAt: isoDatetimeToDate.describe('Updated at'), deletedAt: isoDatetimeToDate.nullable().describe('Deleted at'), - ownerId: z.string().describe('Owner ID'), + ownerId: z.uuidv4().describe('Owner ID'), type: MemoryTypeSchema, data: z.record(z.string(), z.unknown()).describe('Data'), isSaved: z.boolean().describe('Is saved'), @@ -300,43 +300,43 @@ const SyncMemoryV1Schema = z .meta({ id: 'SyncMemoryV1' }); const SyncMemoryDeleteV1Schema = z - .object({ memoryId: z.string().describe('Memory ID') }) + .object({ memoryId: z.uuidv4().describe('Memory ID') }) .meta({ id: 'SyncMemoryDeleteV1' }); const SyncMemoryAssetV1Schema = z .object({ - memoryId: z.string().describe('Memory ID'), - assetId: z.string().describe('Asset ID'), + memoryId: z.uuidv4().describe('Memory ID'), + assetId: z.uuidv4().describe('Asset ID'), }) .meta({ id: 'SyncMemoryAssetV1' }); const SyncMemoryAssetDeleteV1Schema = z .object({ - memoryId: z.string().describe('Memory ID'), - assetId: z.string().describe('Asset ID'), + memoryId: z.uuidv4().describe('Memory ID'), + assetId: z.uuidv4().describe('Asset ID'), }) .meta({ id: 'SyncMemoryAssetDeleteV1' }); const SyncStackV1Schema = z .object({ - id: z.string().describe('Stack ID'), + id: z.uuidv4().describe('Stack ID'), createdAt: isoDatetimeToDate.describe('Created at'), updatedAt: isoDatetimeToDate.describe('Updated at'), - primaryAssetId: z.string().describe('Primary asset ID'), - ownerId: z.string().describe('Owner ID'), + primaryAssetId: z.uuidv4().describe('Primary asset ID'), + ownerId: z.uuidv4().describe('Owner ID'), }) .meta({ id: 'SyncStackV1' }); const SyncStackDeleteV1Schema = z - .object({ stackId: z.string().describe('Stack ID') }) + .object({ stackId: z.uuidv4().describe('Stack ID') }) .meta({ id: 'SyncStackDeleteV1' }); const SyncPersonV1Schema = z .object({ - id: z.string().describe('Person ID'), + id: z.uuidv4().describe('Person ID'), createdAt: isoDatetimeToDate.describe('Created at'), updatedAt: isoDatetimeToDate.describe('Updated at'), - ownerId: z.string().describe('Owner ID'), + ownerId: z.uuidv4().describe('Owner ID'), name: z.string().describe('Person name'), birthDate: isoDatetimeToDate.nullable().describe('Birth date'), isHidden: z.boolean().describe('Is hidden'), @@ -347,13 +347,13 @@ const SyncPersonV1Schema = z .meta({ id: 'SyncPersonV1' }); const SyncPersonDeleteV1Schema = z - .object({ personId: z.string().describe('Person ID') }) + .object({ personId: z.uuidv4().describe('Person ID') }) .meta({ id: 'SyncPersonDeleteV1' }); const SyncAssetFaceV1Schema = z .object({ - id: z.string().describe('Asset face ID'), - assetId: z.string().describe('Asset ID'), + id: z.uuidv4().describe('Asset face ID'), + assetId: z.uuidv4().describe('Asset ID'), personId: z.string().nullable().describe('Person ID'), imageWidth: z.int().describe('Image width'), imageHeight: z.int().describe('Image height'), @@ -371,12 +371,12 @@ const SyncAssetFaceV2Schema = SyncAssetFaceV1Schema.extend({ }).meta({ id: 'SyncAssetFaceV2' }); const SyncAssetFaceDeleteV1Schema = z - .object({ assetFaceId: z.string().describe('Asset face ID') }) + .object({ assetFaceId: z.uuidv4().describe('Asset face ID') }) .meta({ id: 'SyncAssetFaceDeleteV1' }); const SyncUserMetadataV1Schema = z .object({ - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), key: UserMetadataKeySchema, value: z.record(z.string(), z.unknown()).describe('User metadata value'), }) @@ -384,7 +384,7 @@ const SyncUserMetadataV1Schema = z const SyncUserMetadataDeleteV1Schema = z .object({ - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), key: UserMetadataKeySchema, }) .meta({ id: 'SyncUserMetadataDeleteV1' }); @@ -404,8 +404,8 @@ class SyncMemoryAssetDeleteV1 extends createZodDto(SyncMemoryAssetDeleteV1Schema const SyncAssetOcrV1Schema = z .object({ - id: z.string().describe('OCR entry ID'), - assetId: z.string().describe('Asset ID'), + id: z.uuidv4().describe('OCR entry ID'), + assetId: z.uuidv4().describe('Asset ID'), x1: z.number().meta({ format: 'double' }).describe('Top-left X coordinate (normalized 0–1)'), y1: z.number().meta({ format: 'double' }).describe('Top-left Y coordinate (normalized 0–1)'), diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts index 7e1efdc862..85618723b2 100644 --- a/server/src/dtos/system-config.dto.ts +++ b/server/src/dtos/system-config.dto.ts @@ -20,7 +20,6 @@ import { VideoCodecSchema, VideoContainerSchema, } from 'src/enum'; -import { isValidTime } from 'src/validation'; import z from 'zod'; /** Coerces 'true'/'false' strings to boolean, but also allows booleans. */ @@ -204,7 +203,12 @@ const SystemConfigNewVersionCheckSchema = z const SystemConfigNightlyTasksSchema = z .object({ - startTime: isValidTime.describe('Start time'), + startTime: z.iso + .time({ + precision: -1, + error: (iss) => `Invalid input: expected string in HH:MM format, received ${typeof iss.input}`, + }) + .describe('Start time (HH:MM)'), databaseCleanup: configBool.describe('Database cleanup'), missingThumbnails: configBool.describe('Missing thumbnails'), clusterNewFaces: configBool.describe('Cluster new faces'), diff --git a/server/src/dtos/tag.dto.ts b/server/src/dtos/tag.dto.ts index dd679c97cb..cfbae551a8 100644 --- a/server/src/dtos/tag.dto.ts +++ b/server/src/dtos/tag.dto.ts @@ -40,7 +40,7 @@ const TagBulkAssetsResponseSchema = z export const TagResponseSchema = z .object({ - id: z.string().describe('Tag ID'), + id: z.uuidv4().describe('Tag ID'), parentId: z.string().optional().describe('Parent tag ID'), name: z.string().describe('Tag name'), value: z.string().describe('Tag value (full path)'), diff --git a/server/src/dtos/user-profile.dto.ts b/server/src/dtos/user-profile.dto.ts index c3c91d3d95..e4ab29b6d5 100644 --- a/server/src/dtos/user-profile.dto.ts +++ b/server/src/dtos/user-profile.dto.ts @@ -11,7 +11,7 @@ export class CreateProfileImageDto { const CreateProfileImageResponseSchema = z .object({ - userId: z.string().describe('User ID'), + userId: z.uuidv4().describe('User ID'), profileChangedAt: isoDatetimeToDate.describe('Profile image change date'), profileImagePath: z.string().describe('Profile image file path'), }) diff --git a/server/src/dtos/workflow.dto.ts b/server/src/dtos/workflow.dto.ts index 1a2c2ac9f9..b4269ea0c9 100644 --- a/server/src/dtos/workflow.dto.ts +++ b/server/src/dtos/workflow.dto.ts @@ -58,7 +58,7 @@ const WorkflowUpdateSchema = z const WorkflowResponseSchema = z .object({ - id: z.string().describe('Workflow ID'), + id: z.uuidv4().describe('Workflow ID'), trigger: WorkflowTriggerSchema.describe('Workflow trigger type'), name: z.string().nullable().describe('Workflow name'), description: z.string().nullable().describe('Workflow description'), diff --git a/server/src/enum.ts b/server/src/enum.ts index 45b77769c6..491beca10b 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -25,6 +25,7 @@ export enum ImmichHeader { Checksum = 'x-immich-checksum', CorrelationId = 'X-Correlation-ID', HlsInitSegment = 'x-immich-hls-msn', + HlsPosition = 'x-immich-hls-pos', } export enum ImmichQuery { diff --git a/server/src/maintenance/maintenance-worker.service.ts b/server/src/maintenance/maintenance-worker.service.ts index 61958a0d8a..f2c9086e87 100644 --- a/server/src/maintenance/maintenance-worker.service.ts +++ b/server/src/maintenance/maintenance-worker.service.ts @@ -281,17 +281,20 @@ export class MaintenanceWorkerService { async runAction(action: SetMaintenanceModeDto) { switch (action.action) { - case MaintenanceAction.Start: { + case MaintenanceAction.Start: + case MaintenanceAction.SelectDatabaseRestore: { return; } case MaintenanceAction.End: { return this.endMaintenance(); } - case MaintenanceAction.SelectDatabaseRestore: { - return; + case MaintenanceAction.RestoreDatabase: { + return this.runRestoreDatabase(action); } } + } + async runRestoreDatabase(action: SetMaintenanceModeDto) { const lock = await this.databaseRepository.tryLock(DatabaseLock.MaintenanceOperation); if (!lock) { return; diff --git a/server/src/queries/integrity.repository.sql b/server/src/queries/integrity.repository.sql index b9ff92b091..d9940970d9 100644 --- a/server/src/queries/integrity.repository.sql +++ b/server/src/queries/integrity.repository.sql @@ -129,6 +129,7 @@ from and "integrity_report"."type" = $1 where "asset"."deletedAt" is null + and "asset"."isExternal" = false and "integrity_report"."createdAt" >= $2 and "integrity_report"."createdAt" <= $3 order by diff --git a/server/src/queries/sync.repository.sql b/server/src/queries/sync.repository.sql index d29ea47f55..69f7981bf3 100644 --- a/server/src/queries/sync.repository.sql +++ b/server/src/queries/sync.repository.sql @@ -88,7 +88,7 @@ from where "album_asset"."updateId" < $3 and "album_asset"."updateId" <= $4 - and "album_asset"."updateId" >= $5 + and "album_asset"."updateId" > $5 and "album_asset"."albumId" = $6 order by "album_asset"."updateId" asc @@ -202,7 +202,7 @@ from where "album_asset"."updateId" < $1 and "album_asset"."updateId" <= $2 - and "album_asset"."updateId" >= $3 + and "album_asset"."updateId" > $3 and "album_asset"."albumId" = $4 order by "album_asset"."updateId" asc @@ -297,7 +297,7 @@ from where "album_asset"."updateId" < $1 and "album_asset"."updateId" <= $2 - and "album_asset"."updateId" >= $3 + and "album_asset"."updateId" > $3 and "album_asset"."albumId" = $4 order by "album_asset"."updateId" asc @@ -349,7 +349,7 @@ from where "album_user"."updateId" < $1 and "album_user"."updateId" <= $2 - and "album_user"."updateId" >= $3 + and "album_user"."updateId" > $3 and "albumId" = $4 order by "album_user"."updateId" asc @@ -810,7 +810,7 @@ from where "asset"."updateId" < $2 and "asset"."updateId" <= $3 - and "asset"."updateId" >= $4 + and "asset"."updateId" > $4 and "ownerId" = $5 order by "asset"."updateId" asc @@ -908,7 +908,7 @@ from where "asset_exif"."updateId" < $1 and "asset_exif"."updateId" <= $2 - and "asset_exif"."updateId" >= $3 + and "asset_exif"."updateId" > $3 and "asset"."ownerId" = $4 order by "asset_exif"."updateId" asc @@ -997,7 +997,7 @@ from where "stack"."updateId" < $1 and "stack"."updateId" <= $2 - and "stack"."updateId" >= $3 + and "stack"."updateId" > $3 and "ownerId" = $4 order by "stack"."updateId" asc diff --git a/server/src/repositories/config.repository.ts b/server/src/repositories/config.repository.ts index f88deca09d..909d6d62dd 100644 --- a/server/src/repositories/config.repository.ts +++ b/server/src/repositories/config.repository.ts @@ -243,14 +243,16 @@ const getEnv = (): EnvData => { }; let vectorExtension: VectorExtension | undefined; - switch (dto.DB_VECTOR_EXTENSION) { - case 'pgvector': { - vectorExtension = DatabaseExtension.Vector; - break; - } - case 'vectorchord': { - vectorExtension = DatabaseExtension.VectorChord; - break; + if (dto.DB_VECTOR_EXTENSION) { + switch (dto.DB_VECTOR_EXTENSION) { + case 'pgvector': { + vectorExtension = DatabaseExtension.Vector; + break; + } + case 'vectorchord': { + vectorExtension = DatabaseExtension.VectorChord; + break; + } } } diff --git a/server/src/repositories/integrity.repository.ts b/server/src/repositories/integrity.repository.ts index 84ba4bdf48..12f0b98634 100644 --- a/server/src/repositories/integrity.repository.ts +++ b/server/src/repositories/integrity.repository.ts @@ -177,6 +177,7 @@ export class IntegrityRepository { 'asset.id as assetId', 'integrity_report.id as reportId', ]) + .where('asset.isExternal', '=', sql.lit(false)) .$if(startMarker !== undefined, (qb) => qb.where('integrity_report.createdAt', '>=', startMarker!)) .$if(endMarker !== undefined, (qb) => qb.where('integrity_report.createdAt', '<=', endMarker!)) .orderBy('integrity_report.createdAt', 'asc') diff --git a/server/src/repositories/media.repository.ts b/server/src/repositories/media.repository.ts index c2ec95636a..5f8449e692 100644 --- a/server/src/repositories/media.repository.ts +++ b/server/src/repositories/media.repository.ts @@ -478,8 +478,10 @@ export class MediaRepository { case 'av1': { return this.parseEnum(Av1Profile, profile); } + default: { + return null; + } } - return null; } private compareStreams(a: FfprobeStream, b: FfprobeStream): number { diff --git a/server/src/repositories/plugin.repository.ts b/server/src/repositories/plugin.repository.ts index 43006c6aa6..154266b965 100644 --- a/server/src/repositories/plugin.repository.ts +++ b/server/src/repositories/plugin.repository.ts @@ -224,6 +224,7 @@ export class PluginRepository { error: (message) => logger.error(message), } as Console, logLevel: asExtismLogLevel(logger.getLogLevel()), + enableWasiOutput: true, }, ), destroy: (plugin) => plugin.close(), diff --git a/server/src/repositories/server-info.repository.ts b/server/src/repositories/server-info.repository.ts index c8fb896281..0570c9d2cc 100644 --- a/server/src/repositories/server-info.repository.ts +++ b/server/src/repositories/server-info.repository.ts @@ -69,7 +69,16 @@ export class ServerInfoRepository { try { const { versionCheck } = this.configRepository.getEnv(); const url = new URL(versionCheck.url); - url.searchParams.append('channel', channel); + switch (channel) { + case ReleaseChannel.Stable: { + url.searchParams.append('channel', 'stable'); + break; + } + case ReleaseChannel.ReleaseCandidate: { + url.searchParams.append('channel', 'rc'); + break; + } + } const response = await fetch(url); if (!response.ok) { diff --git a/server/src/repositories/sync.repository.ts b/server/src/repositories/sync.repository.ts index 6a469f7af1..eca8d18e66 100644 --- a/server/src/repositories/sync.repository.ts +++ b/server/src/repositories/sync.repository.ts @@ -106,7 +106,7 @@ export class BaseSync { .selectFrom(table(t).as(t)) .where(updateIdRef, '<', nowId) .where(updateIdRef, '<=', beforeUpdateId) - .$if(!!afterUpdateId, (qb) => qb.where(updateIdRef, '>=', afterUpdateId!)) + .$if(!!afterUpdateId, (qb) => qb.where(updateIdRef, '>', afterUpdateId!)) .orderBy(updateIdRef, 'asc'); } diff --git a/server/src/schema/migrations/1776848612954-MigrateAlbumOwnerIdToAlbumUser.ts b/server/src/schema/migrations/1776848612954-MigrateAlbumOwnerIdToAlbumUser.ts index bb9d68a4ec..60c543f401 100644 --- a/server/src/schema/migrations/1776848612954-MigrateAlbumOwnerIdToAlbumUser.ts +++ b/server/src/schema/migrations/1776848612954-MigrateAlbumOwnerIdToAlbumUser.ts @@ -26,6 +26,7 @@ export async function up(db: Kysely): Promise { .selectFrom('album') .select(['album.id as albumId', 'album.ownerId as userId', eb.val(AlbumUserRole.Owner).as('role')]), ) + .onConflict((cb) => cb.columns(['albumId', 'userId']).doUpdateSet({ role: AlbumUserRole.Owner })) .execute(); await sql`ALTER TABLE "album" DROP CONSTRAINT "album_ownerId_fkey";`.execute(db); await sql`ALTER TABLE "album" DROP COLUMN "ownerId";`.execute(db); diff --git a/server/src/services/album.service.spec.ts b/server/src/services/album.service.spec.ts index 374cd58a0e..f1d125993c 100644 --- a/server/src/services/album.service.spec.ts +++ b/server/src/services/album.service.spec.ts @@ -472,17 +472,19 @@ describe(AlbumService.name, () => { expect(mocks.album.update).not.toHaveBeenCalled(); }); - it('should throw an error if the userId is already added', async () => { + it('should skip if the userId is already added', async () => { const userId = newUuid(); const album = AlbumFactory.from().albumUser({ userId }).build(); const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); mocks.album.getById.mockResolvedValue(getForAlbum(album)); - await expect( - sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId }] }), - ).rejects.toBeInstanceOf(BadRequestException); + await expect(sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId }] })).resolves.toEqual( + expect.objectContaining({ id: album.id }), + ); expect(mocks.album.update).not.toHaveBeenCalled(); expect(mocks.user.get).not.toHaveBeenCalled(); + expect(mocks.albumUser.create).not.toHaveBeenCalled(); + expect(mocks.event.emit).not.toHaveBeenCalled(); }); it('should throw an error if the userId does not exist', async () => { @@ -498,7 +500,7 @@ describe(AlbumService.name, () => { expect(mocks.user.get).toHaveBeenCalledWith('unknown-user', {}); }); - it('should throw an error if the userId is the ownerId', async () => { + it('should skip if the userId is the ownerId', async () => { const album = AlbumFactory.create(); const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); @@ -507,9 +509,11 @@ describe(AlbumService.name, () => { sut.addUsers(AuthFactory.create(owner), album.id, { albumUsers: [{ userId: owner.id }], }), - ).rejects.toBeInstanceOf(BadRequestException); + ).resolves.toEqual(expect.objectContaining({ id: album.id })); expect(mocks.album.update).not.toHaveBeenCalled(); expect(mocks.user.get).not.toHaveBeenCalled(); + expect(mocks.albumUser.create).not.toHaveBeenCalled(); + expect(mocks.event.emit).not.toHaveBeenCalled(); }); it('should add valid shared users', async () => { @@ -534,6 +538,35 @@ describe(AlbumService.name, () => { senderName: owner.name, }); }); + + it('should add new users when already-added users are included', async () => { + const existingUserId = newUuid(); + const album = AlbumFactory.from().albumUser({ userId: existingUserId }).build(); + const { user: owner } = album.albumUsers.find(({ role }) => role === AlbumUserRole.Owner)!; + const user = UserFactory.create(); + mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([album.id])); + mocks.album.getById.mockResolvedValue(getForAlbum(album)); + mocks.user.get.mockResolvedValue(user); + mocks.albumUser.create.mockResolvedValue(AlbumUserFactory.from().album(album).user(user).build()); + + await sut.addUsers(AuthFactory.create(owner), album.id, { + albumUsers: [{ userId: existingUserId }, { userId: user.id }], + }); + + expect(mocks.user.get).toHaveBeenCalledTimes(1); + expect(mocks.user.get).toHaveBeenCalledWith(user.id, {}); + expect(mocks.albumUser.create).toHaveBeenCalledTimes(1); + expect(mocks.albumUser.create).toHaveBeenCalledWith({ + userId: user.id, + albumId: album.id, + }); + expect(mocks.event.emit).toHaveBeenCalledTimes(1); + expect(mocks.event.emit).toHaveBeenCalledWith('AlbumInvite', { + id: album.id, + userId: user.id, + senderName: owner.name, + }); + }); }); describe('removeUser', () => { diff --git a/server/src/services/album.service.ts b/server/src/services/album.service.ts index cc0b0d899c..5f4bc56d98 100644 --- a/server/src/services/album.service.ts +++ b/server/src/services/album.service.ts @@ -290,7 +290,7 @@ export class AlbumService extends BaseService { const exists = album.albumUsers.find(({ user: { id } }) => id === userId); if (exists) { - throw new BadRequestException('User already added'); + continue; } const user = await this.userRepository.get(userId, {}); diff --git a/server/src/services/database-backup.service.ts b/server/src/services/database-backup.service.ts index 6afc46c7da..55d69d37b7 100644 --- a/server/src/services/database-backup.service.ts +++ b/server/src/services/database-backup.service.ts @@ -163,6 +163,9 @@ export class DatabaseBackupService { ); switch (bin) { + case 'pg_dump': { + break; + } case 'pg_dumpall': { args.push('--database'); break; diff --git a/server/src/services/hls.service.spec.ts b/server/src/services/hls.service.spec.ts index 9d9536f848..a18f7c6d22 100644 --- a/server/src/services/hls.service.spec.ts +++ b/server/src/services/hls.service.spec.ts @@ -8,6 +8,7 @@ import { newTestService, ServiceMocks } from 'test/utils'; // EXTINF values come from FFmpeg's playlist to enforce an exact match const eiffelExpectedMediaPlaylist = `#EXTM3U #EXT-X-VERSION:7 +#EXT-X-INDEPENDENT-SEGMENTS #EXT-X-TARGETDURATION:2 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD @@ -41,6 +42,7 @@ seg_11.m4s const waterfallExpectedMediaPlaylist = `#EXTM3U #EXT-X-VERSION:7 +#EXT-X-INDEPENDENT-SEGMENTS #EXT-X-TARGETDURATION:2 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD @@ -62,6 +64,7 @@ seg_5.m4s const trainExpectedMediaPlaylist = `#EXTM3U #EXT-X-VERSION:7 +#EXT-X-INDEPENDENT-SEGMENTS #EXT-X-TARGETDURATION:2 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD @@ -95,6 +98,7 @@ const sessionId = '00000000-0000-0000-0000-000000000000'; const eiffelExpectedMasterDisabled = `#EXTM3U #EXT-X-VERSION:7 +#EXT-X-INDEPENDENT-SEGMENTS #EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=480x852,CODECS="av01.0.04M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 ${sessionId}/0/playlist.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 @@ -117,6 +121,7 @@ ${sessionId}/8/playlist.m3u8 const eiffelExpectedMasterRkmpp = `#EXTM3U #EXT-X-VERSION:7 +#EXT-X-INDEPENDENT-SEGMENTS #EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 ${sessionId}/1/playlist.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=480x852,CODECS="avc1.64001e,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=24.910 @@ -133,6 +138,7 @@ ${sessionId}/8/playlist.m3u8 const waterfallExpectedMasterDisabled = `#EXTM3U #EXT-X-VERSION:7 +#EXT-X-INDEPENDENT-SEGMENTS #EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=480x852,CODECS="av01.0.04M.08,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830 ${sessionId}/0/playlist.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=1200000,RESOLUTION=480x852,CODECS="hvc1.1.6.L90.B0,mp4a.40.2",VIDEO-RANGE=SDR,FRAME-RATE=29.830 @@ -218,12 +224,58 @@ describe(HlsService.name, () => { it.each(fixtures)('matches FFmpeg for $data.originalPath', async ({ data, playlist }) => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); mocks.videoStream.getForMediaPlaylist.mockResolvedValue(data); - await expect(sut.getMediaPlaylist(auth, assetId, sessionId)).resolves.toBe(playlist); + await expect(sut.getMediaPlaylist(auth, assetId, sessionId, 0)).resolves.toBe(playlist); }); it('throws NotFoundException when the session/asset cannot be loaded', async () => { mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); - await expect(sut.getMediaPlaylist(auth, assetId, sessionId)).rejects.toBeInstanceOf(NotFoundException); + await expect(sut.getMediaPlaylist(auth, assetId, sessionId, 0)).rejects.toBeInstanceOf(NotFoundException); + }); + + it('prewarms transcoding at the segment containing the hinted position', async () => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + mocks.videoStream.getForMediaPlaylist.mockResolvedValue(eiffelTower); + await sut.getMediaPlaylist(auth, assetId, sessionId, 1, 10.5); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsSegmentRequest', { + sessionId, + assetId, + variantIndex: 1, + segmentIndex: 5, + }); + }); + + it('prewarms from the last requested segment when no hint is given', async () => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + mocks.videoStream.getSession.mockResolvedValue({ id: sessionId, assetId } as never); + mocks.storage.checkFileExists.mockResolvedValue(true); + await sut.getSegment(auth, assetId, sessionId, 0, 'seg_5.m4s'); + + mocks.videoStream.getForMediaPlaylist.mockResolvedValue(eiffelTower); + await sut.getMediaPlaylist(auth, assetId, sessionId, 1); + expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsSegmentRequest', { + sessionId, + assetId, + variantIndex: 1, + segmentIndex: 6, + }); + }); + + it('does not prewarm without a hint or prior segment traffic', async () => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + mocks.videoStream.getForMediaPlaylist.mockResolvedValue(eiffelTower); + await sut.getMediaPlaylist(auth, assetId, sessionId, 1); + expect(mocks.websocket.serverSend).not.toHaveBeenCalled(); + }); + + it('does not prewarm the variant the session is already playing', async () => { + mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetId])); + mocks.videoStream.getSession.mockResolvedValue({ id: sessionId, assetId } as never); + mocks.storage.checkFileExists.mockResolvedValue(true); + await sut.getSegment(auth, assetId, sessionId, 1, 'seg_5.m4s'); + + mocks.videoStream.getForMediaPlaylist.mockResolvedValue(eiffelTower); + await sut.getMediaPlaylist(auth, assetId, sessionId, 1, 12.5); + expect(mocks.websocket.serverSend).not.toHaveBeenCalledWith('HlsSegmentRequest', expect.anything()); }); }); @@ -314,7 +366,7 @@ describe(HlsService.name, () => { ); }); - it('uses the target segment for init.mp4 when provided', async () => { + it('uses the initSegment hint for init.mp4', async () => { await sut.getSegment(auth, assetId, sessionId, variantIndex, 'init.mp4', 7); expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsHeartbeat', { sessionId, @@ -323,18 +375,18 @@ describe(HlsService.name, () => { }); }); - it('prefers the target segment over the lastRequested + 1 fallback', async () => { + it('prefers the initSegment hint over the lastRequested + 1 fallback', async () => { await sut.getSegment(auth, assetId, sessionId, variantIndex, 'seg_5.m4s'); // fallback would be 6 mocks.websocket.serverSend.mockClear(); - await sut.getSegment(auth, assetId, sessionId, variantIndex, 'init.mp4', 12); + await sut.getSegment(auth, assetId, sessionId, variantIndex, 'init.mp4', 10); expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsHeartbeat', { sessionId, variantIndex, - segmentIndex: 12, + segmentIndex: 10, }); }); - it('ignores the target segment for media segment requests (the filename wins)', async () => { + it('ignores the initSegment hint for media segment requests (the filename wins)', async () => { await sut.getSegment(auth, assetId, sessionId, variantIndex, 'seg_5.m4s', 99); expect(mocks.websocket.serverSend).toHaveBeenCalledWith('HlsHeartbeat', { sessionId, diff --git a/server/src/services/hls.service.ts b/server/src/services/hls.service.ts index 034f05bf6c..0cbb53e1cd 100644 --- a/server/src/services/hls.service.ts +++ b/server/src/services/hls.service.ts @@ -21,6 +21,7 @@ import { ImmichFileResponse } from 'src/utils/file'; import { getOutputSize } from 'src/utils/media'; type AssetWithStreamInfo = { videoStream: VideoStreamInfo & { timeBase: number }; packets: VideoPacketInfo }; +type Segmentation = { fps: number; framesPerSegment: number; segmentCount: number; segmentDuration: number }; type ApiSession = { lastRequestedSegment: number | null; lastVariantIndex: number | null }; @Injectable() @@ -71,7 +72,7 @@ export class HlsService extends BaseService { return this.generateMainPlaylist(sessionId, ffmpeg, asset); } - async getMediaPlaylist(auth: AuthDto, assetId: string, sessionId: string) { + async getMediaPlaylist(auth: AuthDto, assetId: string, sessionId: string, variantIndex: number, position?: number) { await this.requireAccess({ auth, permission: Permission.AssetView, ids: [assetId] }); const asset = await this.videoStreamRepository.getForMediaPlaylist(assetId, sessionId); @@ -79,7 +80,11 @@ export class HlsService extends BaseService { throw new NotFoundException('Asset not found or metadata not yet ready for streaming'); } - return this.generateMediaPlaylist(asset); + const segmentation = this.getSegmentation(asset); + const hintedSegment = position === undefined ? undefined : this.positionToSegmentIndex(segmentation, position); + this.prewarmVariant(assetId, sessionId, variantIndex, hintedSegment); + + return this.generateMediaPlaylist(asset, segmentation); } async getSegment( @@ -129,7 +134,7 @@ export class HlsService extends BaseService { const fps = ((asset.packets.packetCount * asset.videoStream.timeBase) / asset.packets.totalDuration).toFixed(3); const sourceResolution = Math.min(asset.videoStream.height, asset.videoStream.width); const targetResolution = Math.max(sourceResolution, HLS_VARIANTS[0].resolution); - const lines = ['#EXTM3U', `#EXT-X-VERSION:${HLS_VERSION}`]; + const lines = ['#EXTM3U', `#EXT-X-VERSION:${HLS_VERSION}`, '#EXT-X-INDEPENDENT-SEGMENTS']; for (let i = 0; i < HLS_VARIANTS.length; i++) { const { resolution, bitrate, codec, codecString } = HLS_VARIANTS[i]; if (resolution > targetResolution || !SUPPORTED_HWA_CODECS[ffmpeg.accel].includes(codec)) { @@ -143,24 +148,33 @@ export class HlsService extends BaseService { } lines.push(''); - if (lines.length === 3) { + if (lines.length === 4) { throw new NotFoundException('No supported variants for this video'); } return lines.join('\n'); } - private generateMediaPlaylist({ videoStream, packets }: AssetWithStreamInfo) { + private getSegmentation({ videoStream, packets }: AssetWithStreamInfo): Segmentation { const fps = (packets.packetCount * videoStream.timeBase) / packets.totalDuration; const framesPerSegment = Math.ceil(HLS_SEGMENT_DURATION * fps); - const fullSegmentDuration = framesPerSegment / fps; const segmentCount = Math.ceil(packets.outputFrames / framesPerSegment); + return { fps, framesPerSegment, segmentCount, segmentDuration: framesPerSegment / fps }; + } + + private positionToSegmentIndex({ segmentDuration, segmentCount }: Segmentation, position: number) { + return Math.min(Math.max(Math.floor(position / segmentDuration), 0), segmentCount - 1); + } + + private generateMediaPlaylist({ packets }: AssetWithStreamInfo, segmentation: Segmentation) { + const { fps, framesPerSegment, segmentCount, segmentDuration: fullSegmentDuration } = segmentation; const lastSegmentFrames = packets.outputFrames - framesPerSegment * (segmentCount - 1); const lastSegmentDuration = lastSegmentFrames / fps; const lines = [ '#EXTM3U', `#EXT-X-VERSION:${HLS_VERSION}`, + '#EXT-X-INDEPENDENT-SEGMENTS', `#EXT-X-TARGETDURATION:${HLS_SEGMENT_DURATION}`, '#EXT-X-MEDIA-SEQUENCE:0', '#EXT-X-PLAYLIST-TYPE:VOD', @@ -175,6 +189,19 @@ export class HlsService extends BaseService { return lines.join('\n'); } + private prewarmVariant(assetId: string, sessionId: string, variantIndex: number, hintedSegment?: number) { + const session = this.sessions.get(sessionId); + if (session?.lastVariantIndex === variantIndex) { + return; + } + + const nextSegment = session && session.lastRequestedSegment !== null ? session.lastRequestedSegment + 1 : undefined; + const segmentIndex = hintedSegment ?? nextSegment; + if (segmentIndex !== undefined) { + this.websocketRepository.serverSend('HlsSegmentRequest', { sessionId, assetId, variantIndex, segmentIndex }); + } + } + private getSegmentKey({ sessionId, variantIndex, segmentIndex }: ArgOf<'HlsSegmentResult'>) { return `${sessionId}:${variantIndex}:${segmentIndex}`; } diff --git a/server/src/services/job.service.ts b/server/src/services/job.service.ts index 00105103e8..6d4f04df37 100644 --- a/server/src/services/job.service.ts +++ b/server/src/services/job.service.ts @@ -257,6 +257,8 @@ export class JobService extends BaseService { } break; } + + // no default } } } diff --git a/server/src/services/library.service.ts b/server/src/services/library.service.ts index 085650be6f..7e84a8ec52 100644 --- a/server/src/services/library.service.ts +++ b/server/src/services/library.service.ts @@ -498,6 +498,9 @@ export class LibraryService extends BaseService { const stat = stats[i]; const action = this.checkExistingAsset(asset, stat); switch (action) { + case AssetSyncResult.DO_NOTHING: { + break; + } case AssetSyncResult.OFFLINE: { if (asset.status === AssetStatus.Trashed) { trashedAssetIdsToOffline.push(asset.id); diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index 994ba436ad..b453082574 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -2939,6 +2939,8 @@ describe(MediaService.name, () => { '7', '-global_quality:v', '23', + '-b:v', + '6897k', '-maxrate', '10000k', '-bufsize', diff --git a/server/src/services/metadata.service.ts b/server/src/services/metadata.service.ts index dfa254b78a..a4639db262 100644 --- a/server/src/services/metadata.service.ts +++ b/server/src/services/metadata.service.ts @@ -1138,7 +1138,9 @@ export class MetadataService extends BaseService { case 3: { return ExifOrientation.Rotate90CW; } + default: { + return null; + } } - return null; } } diff --git a/server/src/services/transcoding.service.spec.ts b/server/src/services/transcoding.service.spec.ts index 349cba6a7d..8909a43342 100644 --- a/server/src/services/transcoding.service.spec.ts +++ b/server/src/services/transcoding.service.spec.ts @@ -381,8 +381,6 @@ describe(TranscodingService.name, () => { '50', '-keyint_min', '50', - '-crf', - '23', '-copyts', '-r', '50130000/2012441', @@ -417,6 +415,8 @@ describe(TranscodingService.name, () => { 'aac', '-preset', '12', + '-crf', + '35', '-svtav1-params', 'hierarchical-levels=3:lookahead=0:enable-tf=0:mbr=4000k', '-hls_segment_filename', @@ -436,6 +436,8 @@ describe(TranscodingService.name, () => { 'hvc1', '-preset', 'ultrafast', + '-crf', + '28', '-maxrate', '2500k', '-bufsize', @@ -459,6 +461,8 @@ describe(TranscodingService.name, () => { 'aac', '-preset', 'ultrafast', + '-crf', + '23', '-maxrate', '2500k', '-bufsize', diff --git a/server/src/services/transcoding.service.ts b/server/src/services/transcoding.service.ts index dae14f6257..c79eec7884 100644 --- a/server/src/services/transcoding.service.ts +++ b/server/src/services/transcoding.service.ts @@ -5,6 +5,7 @@ import { HLS_BACKPRESSURE_PAUSE_SEGMENTS, HLS_BACKPRESSURE_RESUME_SEGMENTS, HLS_CLEANUP_INTERVAL_MS, + HLS_CRF, HLS_INACTIVITY_TIMEOUT_MS, HLS_LEASE_DURATION_MS, HLS_SEGMENT_DURATION, @@ -221,6 +222,7 @@ export class TranscodingService extends BaseService { targetResolution: String(variant.resolution), maxBitrate: `${Math.round(variant.bitrate / 1000)}k`, gopSize: gop, + crf: HLS_CRF[variant.codec], }, this.videoInterfaces, { strictGop: true, lowLatency: true }, diff --git a/server/src/services/workflow-execution.service.ts b/server/src/services/workflow-execution.service.ts index 0a5f025fc1..221c3d4f5d 100644 --- a/server/src/services/workflow-execution.service.ts +++ b/server/src/services/workflow-execution.service.ts @@ -369,7 +369,7 @@ export class WorkflowExecutionService extends BaseService { const readResult = await read(type); let data = readResult.data; for (const step of workflow.steps) { - const payload: WorkflowEventPayload = { + const payload: WorkflowEventPayload = { trigger: workflow.trigger, type, config: step.config ?? {}, diff --git a/server/src/utils/media.ts b/server/src/utils/media.ts index b3a617c36e..eb775e20d3 100644 --- a/server/src/utils/media.ts +++ b/server/src/utils/media.ts @@ -788,6 +788,12 @@ export class QsvSwDecodeConfig extends BaseHWConfig { const options = [`-${this.useCQP() ? 'q:v' : 'global_quality:v'}`, `${this.config.crf}`]; const bitrates = this.getBitrateDistribution(); if (bitrates.max > 0) { + // Workaround for https://github.com/immich-app/immich/issues/29220, to be revisited + // QSV seems to ignore -maxrate without -b:v + // -b:v alongside global_quality uses QVBR + if (!this.useCQP()) { + options.push('-b:v', `${bitrates.target}${bitrates.unit}`); + } options.push('-maxrate', `${bitrates.max}${bitrates.unit}`, '-bufsize', `${bitrates.max * 2}${bitrates.unit}`); } return options; diff --git a/server/src/validation.ts b/server/src/validation.ts index f94ad4f2ae..220d89e4a0 100644 --- a/server/src/validation.ts +++ b/server/src/validation.ts @@ -1,7 +1,6 @@ import { ArgumentMetadata, FileValidator, Injectable, ParseUUIDPipe } from '@nestjs/common'; import { createZodDto } from 'nestjs-zod'; import sanitize from 'sanitize-filename'; -import { IntegrityReportSchema } from 'src/enum'; import { isIP, isIPRange } from 'validator'; import z from 'zod'; @@ -132,10 +131,6 @@ const FilenameParamSchema = z.object({ export class FilenameParamDto extends createZodDto(FilenameParamSchema) {} -const IntegrityReportParamSchema = z.object({ type: IntegrityReportSchema }).meta({ id: 'IntegrityReportDto' }); - -export class IntegrityReportTypeParamDto extends createZodDto(IntegrityReportParamSchema) {} - /** * Unified email validation * Converts email strings to lowercase and validates against HTML5 email regex @@ -156,6 +151,7 @@ export const isoDatetimeToDate = z .codec( z.iso.datetime({ error: (iss) => `Invalid input: expected ISO 8601 datetime string, received ${typeof iss.input}`, + offset: true, }), z.date(), { @@ -187,10 +183,6 @@ export const isoDateToDate = z ) .meta({ example: '2024-01-01' }); -export const isValidTime = z - .string() - .regex(/^([01]\d|2[0-3]):[0-5]\d$/, 'Invalid input: expected string in HH:mm format, received string'); - /** * Latitude in range [-90, 90]. Reuse for body or query params. * diff --git a/server/test/medium/specs/services/integrity.service.spec.ts b/server/test/medium/specs/services/integrity.service.spec.ts index 758fb5ab00..db0f6f3ff2 100644 --- a/server/test/medium/specs/services/integrity.service.spec.ts +++ b/server/test/medium/specs/services/integrity.service.spec.ts @@ -686,6 +686,22 @@ describe(IntegrityService.name, () => { nextCursor: undefined, }); }); + + it('should skip external library files', async () => { + const { sut, ctx } = setup(); + const job = ctx.getMock(JobRepository); + job.queue.mockResolvedValue(void 0); + + const { user } = await ctx.newUser(); + + await ctx.newAsset({ ownerId: user.id, isExternal: true }); + + await sut.handleChecksumFiles({ refreshOnly: false }); + + await expect( + ctx.get(IntegrityRepository).getIntegrityReport({ limit: 100 }, IntegrityReport.ChecksumFail), + ).resolves.toEqual({ items: [], nextCursor: undefined }); + }); }); describe('handleChecksumRefresh', () => { diff --git a/server/test/medium/specs/sync/sync-album-to-asset.spec.ts b/server/test/medium/specs/sync/sync-album-to-asset.spec.ts index b6bd9db010..a0802abe73 100644 --- a/server/test/medium/specs/sync/sync-album-to-asset.spec.ts +++ b/server/test/medium/specs/sync/sync-album-to-asset.spec.ts @@ -155,6 +155,57 @@ describe(SyncRequestType.AlbumToAssetsV1, () => { await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumToAssetsV1]); }); + it('should not resend an already-acked item when backfill resumes', async () => { + const { auth, ctx } = await setup(); + const { user: user2 } = await ctx.newUser(); + + // backfill needs assets with an older updateId + const { asset: sharedAsset1 } = await ctx.newAsset({ ownerId: user2.id }); + const { asset: sharedAsset2 } = await ctx.newAsset({ ownerId: user2.id }); + + await wait(2); + + const { album: sharedAlbum } = await ctx.newAlbum({ ownerId: user2.id }); + await ctx.newAlbumAsset({ albumId: sharedAlbum.id, assetId: sharedAsset1.id }); + await ctx.newAlbumAsset({ albumId: sharedAlbum.id, assetId: sharedAsset2.id }); + + await wait(2); + + // backfill needs an initial ack, otherwise it syncs everything + const { asset: ownedAsset } = await ctx.newAsset({ ownerId: auth.user.id }); + const { album: ownedAlbum } = await ctx.newAlbum({ ownerId: auth.user.id }); + await ctx.newAlbumAsset({ albumId: ownedAlbum.id, assetId: ownedAsset.id }); + + const setupResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumToAssetsV1]); + await ctx.syncAckAll(auth, setupResponse); + + // share album to trigger backfill + await ctx.newAlbumUser({ albumId: sharedAlbum.id, userId: auth.user.id, role: AlbumUserRole.Editor }); + + const response1 = await ctx.syncStream(auth, [SyncRequestType.AlbumToAssetsV1]); + expect(response1).toEqual([ + // receive both + expect.objectContaining({ data: { albumId: sharedAlbum.id, assetId: sharedAsset1.id } }), + expect.objectContaining({ data: { albumId: sharedAlbum.id, assetId: sharedAsset2.id } }), + expect.objectContaining({ type: SyncEntityType.SyncAckV1 }), + expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), + ]); + + // ack 1st + await ctx.sut.setAcks(auth, { acks: [response1[0].ack] }); + + const response2 = await ctx.syncStream(auth, [SyncRequestType.AlbumToAssetsV1]); + expect(response2).toEqual([ + // receive 2nd + expect.objectContaining({ data: { albumId: sharedAlbum.id, assetId: sharedAsset2.id } }), + expect.objectContaining({ type: SyncEntityType.SyncAckV1 }), + expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), + ]); + + await ctx.syncAckAll(auth, response2); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumToAssetsV1]); + }); + it('should detect and sync a deleted album to asset relation', async () => { const { auth, ctx } = await setup(); const albumRepo = ctx.get(AlbumRepository); diff --git a/server/test/medium/specs/sync/sync-partner-asset.spec.ts b/server/test/medium/specs/sync/sync-partner-asset.spec.ts index ec510f1b0d..f2c0de74e4 100644 --- a/server/test/medium/specs/sync/sync-partner-asset.spec.ts +++ b/server/test/medium/specs/sync/sync-partner-asset.spec.ts @@ -279,6 +279,68 @@ describe(SyncRequestType.PartnerAssetsV2, () => { await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); }); + it('should not resend an already-acked item when backfill resumes', async () => { + const { auth, ctx } = await setup(); + const { user: user2 } = await ctx.newUser(); + const { user: user3 } = await ctx.newUser(); + + // backfill needs assets with an older updateId + const { asset: partnerAsset1 } = await ctx.newAsset({ ownerId: user3.id }); + await wait(2); + const { asset: partnerAsset2 } = await ctx.newAsset({ ownerId: user3.id }); + + await wait(2); + + // backfill needs an initial ack, otherwise it syncs everything + const { asset: initialAsset } = await ctx.newAsset({ ownerId: user2.id }); + await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id }); + + const setupResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]); + expect(setupResponse).toEqual([ + expect.objectContaining({ + data: expect.objectContaining({ id: initialAsset.id }), + type: SyncEntityType.PartnerAssetV2, + }), + expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), + ]); + await ctx.syncAckAll(auth, setupResponse); + + // partner share to trigger backfill + await ctx.newPartner({ sharedById: user3.id, sharedWithId: auth.user.id }); + + const response1 = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]); + expect(response1).toEqual([ + // receive both + expect.objectContaining({ + data: expect.objectContaining({ id: partnerAsset1.id }), + type: SyncEntityType.PartnerAssetBackfillV2, + }), + expect.objectContaining({ + data: expect.objectContaining({ id: partnerAsset2.id }), + type: SyncEntityType.PartnerAssetBackfillV2, + }), + expect.objectContaining({ type: SyncEntityType.SyncAckV1 }), + expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), + ]); + + // ack 1st + await ctx.sut.setAcks(auth, { acks: [response1[0].ack] }); + + const response2 = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]); + expect(response2).toEqual([ + // receive 2nd + expect.objectContaining({ + data: expect.objectContaining({ id: partnerAsset2.id }), + type: SyncEntityType.PartnerAssetBackfillV2, + }), + expect.objectContaining({ type: SyncEntityType.SyncAckV1 }), + expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }), + ]); + + await ctx.syncAckAll(auth, response2); + await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]); + }); + it('should hide isFavorite for partner assets', async () => { const { auth, ctx } = await setup(); const { user: user2 } = await ctx.newUser(); diff --git a/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts index 7b1859e4c3..02556f66aa 100644 --- a/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts +++ b/server/test/medium/specs/workflow/workflow-core-plugin.spec.ts @@ -2,7 +2,7 @@ import { WorkflowStepConfig, WorkflowTrigger } from '@immich/plugin-sdk'; import { Kysely } from 'kysely'; import { readFileSync } from 'node:fs'; import { PluginManifestDto } from 'src/dtos/plugin-manifest.dto'; -import { AssetVisibility, LogLevel } from 'src/enum'; +import { AssetType, AssetVisibility, LogLevel } from 'src/enum'; import { AccessRepository } from 'src/repositories/access.repository'; import { AlbumRepository } from 'src/repositories/album.repository'; import { AssetRepository } from 'src/repositories/asset.repository'; @@ -403,4 +403,28 @@ describe('core plugin', () => { await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ isFavorite: true }); }); }); + + describe('assetTypeFilter', () => { + it('should favorite asset if it is a video', async () => { + const { user } = await ctx.newUser(); + const { asset } = await ctx.newAsset({ ownerId: user.id, type: AssetType.Video }); + + const workflow = await createWorkflow({ + ownerId: user.id, + trigger: WorkflowTrigger.AssetCreate, + steps: [ + { + method: 'immich-plugin-core#assetTypeFilter', + config: { allowedTypes: ['VIDEO'] }, + }, + { + method: 'immich-plugin-core#assetFavorite', + }, + ], + }); + + await expect(ctx.sut.handleAssetTrigger({ workflowId: workflow.id, assetId: asset.id })).resolves.toBeUndefined(); + await expect(ctx.get(AssetRepository).getById(asset.id)).resolves.toMatchObject({ isFavorite: true }); + }); + }); }); diff --git a/web/package.json b/web/package.json index de74a5d6ed..4f68634b22 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "immich-web", - "version": "3.0.0-rc.1", + "version": "3.0.0-rc.2", "license": "GNU Affero General Public License version 3", "type": "module", "scripts": { @@ -105,7 +105,7 @@ "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^4.0.0", "rollup-plugin-visualizer": "^7.0.0", - "svelte": "5.56.2", + "svelte": "5.56.3", "svelte-check": "^4.4.6", "svelte-eslint-parser": "^1.3.3", "tailwindcss": "^4.2.4", diff --git a/web/src/app.css b/web/src/app.css index 4435b255d2..31ac9eb4da 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -159,7 +159,9 @@ } .text-white-shadow { - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); + text-shadow: + 0 0 4px rgba(0, 0, 0, 0.9), + 0 1px 3px rgba(0, 0, 0, 0.8); } .icon-white-drop-shadow { diff --git a/web/src/lib/components/SchemaConfiguration.svelte b/web/src/lib/components/SchemaConfiguration.svelte index 37e799984f..1e85e6d945 100644 --- a/web/src/lib/components/SchemaConfiguration.svelte +++ b/web/src/lib/components/SchemaConfiguration.svelte @@ -51,6 +51,8 @@ }; const setUiHintValue = (values: string[]) => setValue(schema.array ? values : values[0]); + const getSchemaProperties = (schema: JSONSchemaProperty) => + Object.entries(schema.properties ?? {}).sort((a, b) => (a[1].uiHint?.order ?? 0) - (b[1].uiHint?.order ?? 0)); const getBoolean = (defaultValue = false) => getValue(defaultValue); const getString = () => getValue(); @@ -72,11 +74,11 @@ {/if}
- {#each Object.entries(schema.properties ?? {}) as [childKey, childSchema] (childKey)} + {#each getSchemaProperties(schema) as [childKey, childSchema] (childKey)} {/each}
-{:else if schema.uiHint === 'AlbumId'} +{:else if schema.uiHint?.type === 'AlbumId'} {:else if schema.enum && schema.array} diff --git a/web/src/lib/components/asset-viewer/AssetViewer.svelte b/web/src/lib/components/asset-viewer/AssetViewer.svelte index f63b919633..e99bf13148 100644 --- a/web/src/lib/components/asset-viewer/AssetViewer.svelte +++ b/web/src/lib/components/asset-viewer/AssetViewer.svelte @@ -110,11 +110,11 @@ let sharedLink = getSharedLink(); let fullscreenElement = $state(); - let playOriginalVideo = $state($alwaysLoadOriginalVideo); + let isPlayingOriginalVideo = $state($alwaysLoadOriginalVideo); let slideshowStartAssetId = $state(); const setPlayOriginalVideo = (value: boolean) => { - playOriginalVideo = value; + isPlayingOriginalVideo = value; }; const refreshStack = async () => { @@ -499,14 +499,12 @@ {album} {person} {stack} - showSlideshow={true} preAction={handlePreAction} onAction={handleAction} {onUndoDelete} - onPlaySlideshow={() => ($slideshowState = SlideshowState.PlaySlideshow)} onClose={onClose ? () => onClose(stack?.primaryAssetId ?? asset.id) : undefined} {onRemoveFromAlbum} - {playOriginalVideo} + {isPlayingOriginalVideo} {setPlayOriginalVideo} /> @@ -544,7 +542,7 @@ onClose={closeViewer} onVideoEnded={() => navigateAsset()} onVideoStarted={handleVideoStarted} - {playOriginalVideo} + playOriginalVideo={isPlayingOriginalVideo} /> {:else if viewerKind === 'LiveVideoViewer'} navigateAsset('previous')} onNextAsset={() => navigateAsset('next')} onVideoEnded={() => (assetViewerManager.isPlayingMotionPhoto = false)} - {playOriginalVideo} + playOriginalVideo={isPlayingOriginalVideo} /> {:else if viewerKind === 'ImagePanaramaViewer'} @@ -576,7 +574,7 @@ onClose={closeViewer} onVideoEnded={() => navigateAsset()} onVideoStarted={handleVideoStarted} - {playOriginalVideo} + playOriginalVideo={isPlayingOriginalVideo} /> {/if} diff --git a/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte b/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte index 75504765e2..241ab6a4f2 100644 --- a/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte +++ b/web/src/lib/components/asset-viewer/AssetViewerNavBar.svelte @@ -1,5 +1,4 @@ @@ -147,9 +136,7 @@ {#if !sharedLink} - {#if showSlideshow && !isLocked} - - {/if} + @@ -177,46 +164,27 @@ {/if} {/if} {#if album} - + {@const { SetCover } = getAlbumAssetActions($t, album, asset)} + {/if} {#if person} {/if} - {#if asset.type === AssetTypeEnum.Image && !isLocked} - - {/if} - {#if !isLocked} - {#if isOwner} - - {#if !asset.isArchived && !asset.isTrashed} - goto(Route.photos({ at: stack?.primaryAssetId ?? asset.id }))} - text={$t('view_in_timeline')} - /> - {/if} - {/if} - {#if !asset.isArchived && !asset.isTrashed && smartSearchEnabled} - goto(Route.search({ queryAssetId: stack?.primaryAssetId ?? asset.id }))} - text={$t('view_similar_photos')} - /> - {/if} + + + {#if isOwner && !isLocked} + {/if} + + {#if !asset.isTrashed && isOwner} {/if} - {#if asset.type === AssetTypeEnum.Video} - setPlayOriginalVideo(!playOriginalVideo)} - text={playOriginalVideo ? $t('play_transcoded_video') : $t('play_original_video')} - /> - {/if} + + {#if isOwner}
diff --git a/web/src/lib/components/asset-viewer/DetailPanel.svelte b/web/src/lib/components/asset-viewer/DetailPanel.svelte index 6766292766..98b64d5aa0 100644 --- a/web/src/lib/components/asset-viewer/DetailPanel.svelte +++ b/web/src/lib/components/asset-viewer/DetailPanel.svelte @@ -23,7 +23,7 @@ type AlbumResponseDto, type AssetResponseDto, } from '@immich/sdk'; - import { Icon, IconButton, LoadingSpinner, Text } from '@immich/ui'; + import { Icon, IconButton, Link, LoadingSpinner, Text } from '@immich/ui'; import { mdiCamera, mdiCameraIris, mdiClose, mdiImageOutline, mdiInformationOutline } from '@mdi/js'; import { onDestroy } from 'svelte'; import { t } from 'svelte-i18n'; @@ -310,14 +310,13 @@ {#snippet popup({ marker })} {@const { lat, lon } = marker} {/snippet} diff --git a/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte b/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte index d218b512b6..ca0b8d3ef7 100644 --- a/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte +++ b/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte @@ -324,6 +324,18 @@ shortcut: { key: ' ' }, onShortcut: () => (videoPlayer?.paused ? videoPlayer?.play() : videoPlayer?.pause()), }, + { + shortcut: { shift: true, key: 'ArrowLeft' }, + onShortcut: () => + videoPlayer ? (videoPlayer.currentTime = Math.max(videoPlayer.currentTime - 0.4, 0)) : undefined, + }, + { + shortcut: { shift: true, key: 'ArrowRight' }, + onShortcut: () => + videoPlayer + ? (videoPlayer.currentTime = Math.min(videoPlayer.currentTime + 0.4, videoPlayer.duration)) + : undefined, + }, ]} /> diff --git a/web/src/lib/components/asset-viewer/actions/SetAlbumCoverAction.svelte b/web/src/lib/components/asset-viewer/actions/SetAlbumCoverAction.svelte deleted file mode 100644 index b4d3d19fb5..0000000000 --- a/web/src/lib/components/asset-viewer/actions/SetAlbumCoverAction.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - - diff --git a/web/src/lib/components/asset-viewer/actions/SetProfilePictureAction.svelte b/web/src/lib/components/asset-viewer/actions/SetProfilePictureAction.svelte deleted file mode 100644 index d647e67bf8..0000000000 --- a/web/src/lib/components/asset-viewer/actions/SetProfilePictureAction.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - - modalManager.show(ProfileImageCropperModal, { asset })} - text={$t('set_as_profile_picture')} -/> diff --git a/web/src/lib/components/assets/thumbnail/Thumbnail.svelte b/web/src/lib/components/assets/thumbnail/Thumbnail.svelte index 347ff3e75f..8583b9f8ac 100644 --- a/web/src/lib/components/assets/thumbnail/Thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/Thumbnail.svelte @@ -342,7 +342,7 @@ {#if !!assetOwner}
-

+

{assetOwner.name}

diff --git a/web/src/lib/components/shared-components/gallery-viewer/GalleryViewer.svelte b/web/src/lib/components/shared-components/gallery-viewer/GalleryViewer.svelte index cf08afc7dc..29e7e6458c 100644 --- a/web/src/lib/components/shared-components/gallery-viewer/GalleryViewer.svelte +++ b/web/src/lib/components/shared-components/gallery-viewer/GalleryViewer.svelte @@ -13,6 +13,7 @@ import AssetDeleteConfirmModal from '$lib/modals/AssetDeleteConfirmModal.svelte'; import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte'; import { Route } from '$lib/route'; + import { keyboardManager } from '$lib/stores/keyboard-manager.svelte'; import { showDeleteModal } from '$lib/stores/preferences.store'; import { handlePromiseError } from '$lib/utils'; import { deleteAssets } from '$lib/utils/actions'; @@ -85,7 +86,6 @@ return top + pageHeaderOffset < window.bottom && top + geometry.getHeight(index) > window.top; }; - let shiftKeyIsDown = $state(false); let lastAssetMouseEvent: TimelineAsset | null = $state(null); let scrollTop = $state(0); @@ -122,21 +122,6 @@ assetInteraction.selectAssets(assets.map((a) => toTimelineAsset(a))); }; - const onKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Shift') { - event.preventDefault(); - - shiftKeyIsDown = true; - } - }; - - const onKeyUp = (event: KeyboardEvent) => { - if (event.key === 'Shift') { - event.preventDefault(); - shiftKeyIsDown = false; - } - }; - const handleSelectAssets = (asset: TimelineAsset) => { if (!asset) { return; @@ -168,7 +153,7 @@ }; const selectAssetCandidates = (endAsset: TimelineAsset) => { - if (!shiftKeyIsDown) { + if (!keyboardManager.shift) { return; } @@ -188,7 +173,7 @@ }; const onSelectStart = (event: Event) => { - if (assetInteraction.selectionActive && shiftKeyIsDown) { + if (assetInteraction.selectionActive && keyboardManager.shift) { event.preventDefault(); } }; @@ -333,13 +318,13 @@ }); $effect(() => { - if (!shiftKeyIsDown) { + if (!keyboardManager.shift) { assetInteraction.clearCandidates(); } }); $effect(() => { - if (shiftKeyIsDown && lastAssetMouseEvent) { + if (keyboardManager.shift && lastAssetMouseEvent) { selectAssetCandidates(lastAssetMouseEvent); } }); @@ -351,13 +336,7 @@ }); - updateSlidingWindow()} -/> + updateSlidingWindow()} /> {#if assets.length > 0}
- {/if} @@ -401,13 +399,13 @@ > {#snippet children({ feature }: { feature: Feature })} {#if useLocationPin} - + {:else} {feature.properties?.city - {@render popup?.({ marker: asMarker(feature) })} + {@render popup({ marker: asMarker(feature) })} {/if} {/snippet} diff --git a/web/src/lib/components/shared-components/settings/SettingsLanguageSelector.svelte b/web/src/lib/components/shared-components/settings/SettingsLanguageSelector.svelte index 269edb2204..146c37b7ec 100644 --- a/web/src/lib/components/shared-components/settings/SettingsLanguageSelector.svelte +++ b/web/src/lib/components/shared-components/settings/SettingsLanguageSelector.svelte @@ -3,7 +3,7 @@ import Combobox from '$lib/components/shared-components/Combobox.svelte'; import { defaultLang } from '$lib/constants'; import { lang } from '$lib/stores/preferences.store'; - import { getClosestAvailableLocale, langCodes, langs } from '$lib/utils/i18n'; + import { convertBCP47, getClosestAvailableLocale, langCodes, langs } from '$lib/utils/i18n'; import { Label, Text } from '@immich/ui'; import { locale as i18nLocale, t } from 'svelte-i18n'; @@ -13,14 +13,14 @@ let { showSettingDescription = false }: Props = $props(); - const langOptions = langs.map((lang) => ({ label: lang.name, value: lang.code })); + const langOptions = langs.map((lang) => ({ label: lang.name, value: convertBCP47(lang.code) })); const defaultLangOption = { label: defaultLang.name, value: defaultLang.code }; const handleLanguageChange = async (newLang: string | undefined) => { if (newLang) { $lang = newLang; - await i18nLocale.set(newLang); + await i18nLocale.set(convertBCP47(newLang)); await invalidateAll(); } }; diff --git a/web/src/lib/components/timeline/Timeline.svelte b/web/src/lib/components/timeline/Timeline.svelte index b3acb09e13..7949e2ff1b 100644 --- a/web/src/lib/components/timeline/Timeline.svelte +++ b/web/src/lib/components/timeline/Timeline.svelte @@ -19,6 +19,7 @@ import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import type { TimelineAsset, TimelineManagerOptions, ViewportTopMonth } from '$lib/managers/timeline-manager/types'; import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte'; + import { keyboardManager } from '$lib/stores/keyboard-manager.svelte'; import { mediaQueryManager } from '$lib/stores/media-query-manager.svelte'; import { isAssetViewerRoute, navigate } from '$lib/utils/navigation'; import { getTimes, type ScrubberListener } from '$lib/utils/timeline-util'; @@ -370,21 +371,6 @@ let lastAssetMouseEvent: TimelineAsset | null = $state(null); - let shiftKeyIsDown = $state(false); - - const onKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Shift') { - event.preventDefault(); - shiftKeyIsDown = true; - } - }; - - const onKeyUp = (event: KeyboardEvent) => { - if (event.key === 'Shift') { - event.preventDefault(); - shiftKeyIsDown = false; - } - }; const handleSelectAssetCandidates = (asset: TimelineAsset | null) => { if (asset) { void selectAssetCandidates(asset); @@ -485,7 +471,7 @@ }; const selectAssetCandidates = async (endAsset: TimelineAsset) => { - if (!shiftKeyIsDown) { + if (!keyboardManager.shift) { return; } @@ -505,13 +491,13 @@ }); $effect(() => { - if (!shiftKeyIsDown) { + if (!keyboardManager.shift) { assetInteraction.clearCandidates(); } }); $effect(() => { - if (shiftKeyIsDown && lastAssetMouseEvent) { + if (keyboardManager.shift && lastAssetMouseEvent) { void selectAssetCandidates(lastAssetMouseEvent); } }); @@ -560,8 +546,6 @@ }; - - { const asset = page.url.searchParams.get('at'); @@ -599,12 +583,12 @@ onScrubKeyDown={(evt) => { evt.preventDefault(); let amount = 50; - if (shiftKeyIsDown) { + if (keyboardManager.shift) { amount = 500; } if (evt.key === 'ArrowUp') { amount = -amount; - if (shiftKeyIsDown) { + if (keyboardManager.shift) { scrollableElement?.scrollBy({ top: amount, behavior: 'smooth' }); } } else if (evt.key === 'ArrowDown') { diff --git a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte index ebb671d491..32d32083ce 100644 --- a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte +++ b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte @@ -15,6 +15,7 @@ import NavigateToDateModal from '$lib/modals/NavigateToDateModal.svelte'; import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte'; import { Route } from '$lib/route'; + import { keyboardManager } from '$lib/stores/keyboard-manager.svelte'; import { showDeleteModal } from '$lib/stores/preferences.store'; import { searchStore } from '$lib/stores/search.svelte'; import { handlePromiseError } from '$lib/utils'; @@ -73,32 +74,8 @@ assetInteraction.clear(); }; - let shiftKeyIsDown = $state(false); - - const onKeyDown = (event: KeyboardEvent) => { - if (searchStore.isSearchEnabled) { - return; - } - - if (event.key === 'Shift') { - event.preventDefault(); - shiftKeyIsDown = true; - } - }; - - const onKeyUp = (event: KeyboardEvent) => { - if (searchStore.isSearchEnabled) { - return; - } - - if (event.key === 'Shift') { - event.preventDefault(); - shiftKeyIsDown = false; - } - }; - const onSelectStart = (e: Event) => { - if (assetInteraction.selectionActive && shiftKeyIsDown) { + if (!searchStore.isSearchEnabled && assetInteraction.selectionActive && keyboardManager.shift) { e.preventDefault(); } }; @@ -171,4 +148,4 @@ }); - + diff --git a/web/src/lib/services/album.service.ts b/web/src/lib/services/album.service.ts index 8cd46bd13d..09796d440a 100644 --- a/web/src/lib/services/album.service.ts +++ b/web/src/lib/services/album.service.ts @@ -10,12 +10,13 @@ import { updateAlbumUser, type AlbumResponseDto, type AlbumsAddAssetsResponseDto, + type AssetResponseDto, type BulkIdResponseDto, type UpdateAlbumDto, type UserResponseDto, } from '@immich/sdk'; import { modalManager, toastManager, type ActionItem } from '@immich/ui'; -import { mdiLink, mdiPlus, mdiPlusBoxOutline, mdiShareVariantOutline, mdiUpload } from '@mdi/js'; +import { mdiImageOutline, mdiLink, mdiPlus, mdiPlusBoxOutline, mdiShareVariantOutline, mdiUpload } from '@mdi/js'; import { type MessageFormatter } from 'svelte-i18n'; import { goto } from '$app/navigation'; import { authManager } from '$lib/managers/auth-manager.svelte'; @@ -68,6 +69,16 @@ export const getAlbumActions = ($t: MessageFormatter, album: AlbumResponseDto) = return { Share, AddUsers, CreateSharedLink }; }; +export const getAlbumAssetActions = ($t: MessageFormatter, album: AlbumResponseDto, asset: AssetResponseDto) => { + const SetCover: ActionItem = { + title: $t('set_as_album_cover'), + icon: mdiImageOutline, + onAction: () => handleUpdateThumbnail(album, asset.id), + }; + + return { SetCover }; +}; + export const getAlbumAssetsActions = ($t: MessageFormatter, album: AlbumResponseDto, assets: TimelineAsset[]) => { const AddAssets: ActionItem = { title: $t('add_assets'), @@ -206,6 +217,23 @@ export const handleRemoveUserFromAlbum = async (album: AlbumResponseDto, albumUs } }; +const handleUpdateThumbnail = async (album: AlbumResponseDto, assetId: string) => { + const $t = await getFormatter(); + + try { + const response = await updateAlbumInfo({ + id: album.id, + updateAlbumDto: { + albumThumbnailAssetId: assetId, + }, + }); + eventManager.emit('AlbumUpdate', response); + toastManager.primary($t('album_cover_updated')); + } catch (error) { + handleError(error, $t('errors.unable_to_update_album_cover')); + } +}; + export const handleUpdateAlbum = async ({ id }: { id: string }, dto: UpdateAlbumDto) => { const $t = await getFormatter(); diff --git a/web/src/lib/services/asset.service.spec.ts b/web/src/lib/services/asset.service.spec.ts index 7c533997b1..68a3b0db09 100644 --- a/web/src/lib/services/asset.service.spec.ts +++ b/web/src/lib/services/asset.service.spec.ts @@ -31,6 +31,12 @@ vitest.mock('$lib/utils', async () => { }; }); +vi.mock(import('$lib/managers/feature-flags-manager.svelte'), function () { + return { + featureFlagsManager: { init: vi.fn(), loadFeatureFlags: vi.fn(), value: {} } as never, + }; +}); + describe('AssetService', () => { describe('getAssetActions', () => { beforeEach(() => { diff --git a/web/src/lib/services/asset.service.ts b/web/src/lib/services/asset.service.ts index 8804c23ec2..1288aa6100 100644 --- a/web/src/lib/services/asset.service.ts +++ b/web/src/lib/services/asset.service.ts @@ -11,8 +11,10 @@ import { } from '@immich/sdk'; import { modalManager, toastManager, type ActionItem } from '@immich/ui'; import { + mdiAccountCircleOutline, mdiAlertOutline, mdiCogRefreshOutline, + mdiCompare, mdiContentCopy, mdiDatabaseRefreshOutline, mdiDownload, @@ -22,25 +24,32 @@ import { mdiHeart, mdiHeartOutline, mdiImageRefreshOutline, + mdiImageSearch, mdiInformationOutline, mdiMagnifyMinusOutline, mdiMagnifyPlusOutline, mdiMotionPauseOutline, mdiMotionPlayOutline, mdiPlus, + mdiPresentationPlay, mdiShareVariantOutline, mdiTagPlusOutline, mdiTune, } from '@mdi/js'; import type { MessageFormatter } from 'svelte-i18n'; +import { goto } from '$app/navigation'; import { ProjectionType } from '$lib/constants'; import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte'; import { eventManager } from '$lib/managers/event-manager.svelte'; +import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import AssetAddToAlbumModal from '$lib/modals/AssetAddToAlbumModal.svelte'; import AssetTagModal from '$lib/modals/AssetTagModal.svelte'; +import ProfileImageCropperModal from '$lib/modals/ProfileImageCropperModal.svelte'; import SharedLinkCreateModal from '$lib/modals/SharedLinkCreateModal.svelte'; +import { Route } from '$lib/route'; +import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { getAssetMediaUrl, getSharedLink, sleep } from '$lib/utils'; import { downloadUrl } from '$lib/utils'; import { handleError } from '$lib/utils/handle-error'; @@ -90,10 +99,11 @@ export const getAssetBulkActions = ($t: MessageFormatter) => { return { AddToAlbum, RefreshFacesJob, RefreshMetadataJob, RegenerateThumbnailJob, TranscodeVideoJob }; }; -export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) => { +export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto & { stackPrimaryAssetId?: string }) => { const sharedLink = getSharedLink(); const authUser = authManager.authenticated ? authManager.user : undefined; const isOwner = !!(authUser && authUser.id === asset.ownerId); + const smartSearchEnabled = featureFlagsManager.value.smartSearch; const Share: ActionItem = { title: $t('share'), @@ -140,6 +150,13 @@ export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) = }, }; + const PlaySlideshow: ActionItem = { + title: $t('slideshow'), + icon: mdiPresentationPlay, + $if: () => asset.visibility !== AssetVisibility.Locked, + onAction: () => slideshowStore.slideshowState.set(SlideshowState.PlaySlideshow), + }; + const Favorite: ActionItem = { title: $t('to_favorite'), icon: mdiHeartOutline, @@ -233,6 +250,28 @@ export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) = shortcuts: [{ key: 'e' }], }; + const SetProfilePicture: ActionItem = { + title: $t('set_as_profile_picture'), + icon: mdiAccountCircleOutline, + $if: () => asset.type === AssetTypeEnum.Image && asset.visibility !== AssetVisibility.Locked, + onAction: () => modalManager.show(ProfileImageCropperModal, { asset }), + }; + + const ViewInTimeline: ActionItem = { + title: $t('view_in_timeline'), + icon: mdiImageSearch, + $if: () => isOwner && asset.visibility !== AssetVisibility.Locked && !asset.isArchived && !asset.isTrashed, + onAction: () => goto(Route.photos({ at: asset.stackPrimaryAssetId ?? asset.id })), + }; + + const ViewSimilar: ActionItem = { + title: $t('view_similar_photos'), + icon: mdiCompare, + $if: () => + asset.visibility !== AssetVisibility.Locked && !asset.isArchived && !asset.isTrashed && smartSearchEnabled, + onAction: () => goto(Route.search({ queryAssetId: asset.stackPrimaryAssetId ?? asset.id })), + }; + const RefreshFacesJob: ActionItem = { title: $t('refresh_faces'), icon: mdiHeadSyncOutline, @@ -269,6 +308,7 @@ export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) = Unfavorite, PlayMotionPhoto, StopMotionPhoto, + PlaySlideshow, AddToAlbum, ZoomIn, ZoomOut, @@ -276,6 +316,9 @@ export const getAssetActions = ($t: MessageFormatter, asset: AssetResponseDto) = Tag, TagPeople, Edit, + SetProfilePicture, + ViewInTimeline, + ViewSimilar, RefreshFacesJob, RefreshMetadataJob, RegenerateThumbnailJob, diff --git a/web/src/lib/stores/face.svelte.ts b/web/src/lib/stores/face.svelte.ts index dc86a2821f..e9f026bbea 100644 --- a/web/src/lib/stores/face.svelte.ts +++ b/web/src/lib/stores/face.svelte.ts @@ -24,7 +24,8 @@ class FaceManager { }); readonly people = $derived.by(() => { - const people = new SvelteMap(); + // eslint-disable-next-line svelte/prefer-svelte-reactivity + const people = new Map(); for (const face of this.data) { if (face.person) { @@ -32,7 +33,7 @@ class FaceManager { } } - return people.values(); + return Array.from(people.values()); }); readonly facesByPersonId = $derived.by(() => { diff --git a/web/src/lib/stores/keyboard-manager.svelte.ts b/web/src/lib/stores/keyboard-manager.svelte.ts new file mode 100644 index 0000000000..e2ed5bae8c --- /dev/null +++ b/web/src/lib/stores/keyboard-manager.svelte.ts @@ -0,0 +1,47 @@ +class KeyboardManager { + #shift = $state(false); + #ctrl = $state(false); + #meta = $state(false); + #alt = $state(false); + + constructor() { + if (globalThis.window === undefined) { + return; + } + globalThis.addEventListener('keydown', this.#update); + globalThis.addEventListener('keyup', this.#update); + globalThis.addEventListener('blur', this.#clear); + } + + get shift() { + return this.#shift; + } + + get ctrl() { + return this.#ctrl; + } + + get meta() { + return this.#meta; + } + + get alt() { + return this.#alt; + } + + #update = (event: KeyboardEvent) => { + this.#shift = event.shiftKey; + this.#ctrl = event.ctrlKey; + this.#meta = event.metaKey; + this.#alt = event.altKey; + }; + + #clear = () => { + this.#shift = false; + this.#ctrl = false; + this.#meta = false; + this.#alt = false; + }; +} + +export const keyboardManager = new KeyboardManager(); diff --git a/web/src/lib/stores/preferences.store.ts b/web/src/lib/stores/preferences.store.ts index d08dfc0475..2584312220 100644 --- a/web/src/lib/stores/preferences.store.ts +++ b/web/src/lib/stores/preferences.store.ts @@ -1,12 +1,12 @@ import { persisted } from 'svelte-persisted-store'; import { browser } from '$app/environment'; import { defaultLang } from '$lib/constants'; -import { getPreferredLocale } from '$lib/utils/i18n'; +import { convertBCP47, getPreferredLocale } from '$lib/utils/i18n'; // Locale to use for formatting dates, numbers, etc. export const locale = persisted('locale', 'default', { serializer: { - parse: (text) => text || 'default', + parse: (text) => convertBCP47(text) || 'default', stringify: (object) => object ?? '', }, }); @@ -14,7 +14,7 @@ export const locale = persisted('locale', 'default', { const preferredLocale = browser ? getPreferredLocale() : undefined; export const lang = persisted('lang', preferredLocale || defaultLang.code, { serializer: { - parse: (text) => text, + parse: (text) => convertBCP47(text), stringify: (object) => object ?? '', }, }); diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 41d98df097..ee5a03bb1b 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -96,7 +96,10 @@ export type JSONSchemaProperty = { array?: boolean; properties?: Record; required?: string[]; - uiHint?: 'AlbumId' | 'AssetId' | 'PersonId'; + uiHint?: { + type?: 'AlbumId' | 'AssetId' | 'PersonId'; + order?: number; + }; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts index 4ff3564ffa..0e31782164 100644 --- a/web/src/lib/utils.ts +++ b/web/src/lib/utils.ts @@ -28,7 +28,7 @@ import { downloadManager } from '$lib/managers/download-manager.svelte'; import { alwaysLoadOriginalFile, lang } from '$lib/stores/preferences.store'; import { isWebCompatibleImage } from '$lib/utils/asset-utils'; import { handleError } from '$lib/utils/handle-error'; -import { langs } from '$lib/utils/i18n'; +import { convertBCP47, langs } from '$lib/utils/i18n'; interface DownloadRequestOptions { method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; @@ -47,7 +47,7 @@ interface DateFormatter { export const initLanguage = async () => { const preferenceLang = get(lang); for (const { code, loader } of langs) { - register(code, loader); + register(convertBCP47(code), loader); } await init({ fallbackLocale: preferenceLang === 'dev' ? 'dev' : defaultLang.code, initialLocale: preferenceLang }); diff --git a/web/src/lib/utils/i18n.ts b/web/src/lib/utils/i18n.ts index d179efb5bd..85331ddf7c 100644 --- a/web/src/lib/utils/i18n.ts +++ b/web/src/lib/utils/i18n.ts @@ -19,7 +19,7 @@ const fileCodes = Object.keys(modules) .map((path) => path.match(/\/(\w+)\.json$/)?.[1]) .filter(Boolean) as string[]; -const convertBCP47 = (code: string) => code.replaceAll('_', '-'); +export const convertBCP47 = (code: string) => code.replaceAll('_', '-'); export const langCodes = fileCodes.map((code) => convertBCP47(code)); diff --git a/web/src/lib/utils/places-utils.ts b/web/src/lib/utils/places-utils.ts index b100d004d0..08ba2588a8 100644 --- a/web/src/lib/utils/places-utils.ts +++ b/web/src/lib/utils/places-utils.ts @@ -13,38 +13,6 @@ export interface PlacesGroup { places: AssetResponseDto[]; } -export interface PlacesGroupOptionMetadata { - id: PlacesGroupBy; - isDisabled: () => boolean; -} - -export const groupOptionsMetadata: PlacesGroupOptionMetadata[] = [ - { - id: PlacesGroupBy.None, - isDisabled: () => false, - }, - { - id: PlacesGroupBy.Country, - isDisabled: () => false, - }, -]; - -export const findGroupOptionMetadata = (groupBy: string) => { - // Default is no grouping - const defaultGroupOption = groupOptionsMetadata[0]; - return groupOptionsMetadata.find(({ id }) => groupBy === id) ?? defaultGroupOption; -}; - -export const getSelectedPlacesGroupOption = (settings: PlacesViewSettings) => { - const defaultGroupOption = PlacesGroupBy.None; - const albumGroupOption = settings.groupBy ?? defaultGroupOption; - - if (findGroupOptionMetadata(albumGroupOption).isDisabled()) { - return defaultGroupOption; - } - return albumGroupOption; -}; - /** * ---------------------------- * Places Groups Collapse/Expand diff --git a/web/src/routes/(user)/people/manage/+page.svelte b/web/src/routes/(user)/people/manage/+page.svelte index 1e6b917768..4c311f638c 100644 --- a/web/src/routes/(user)/people/manage/+page.svelte +++ b/web/src/routes/(user)/people/manage/+page.svelte @@ -169,7 +169,9 @@ preload={false} /> {#if person.name} - + {person.name} {/if} diff --git a/web/src/routes/(user)/places/PlacesControls.svelte b/web/src/routes/(user)/places/PlacesControls.svelte index ae1308fdd3..f3fa38cb28 100644 --- a/web/src/routes/(user)/places/PlacesControls.svelte +++ b/web/src/routes/(user)/places/PlacesControls.svelte @@ -1,24 +1,11 @@ - - - ({ - title: placesGroupByNames[id], - icon: groupIcon, - disabled: isDisabled(), - })} -/> +
+