From 765b0982edb95f1abfa082d30e2cadf3c6dad9ac Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 2 Jun 2025 01:05:16 +0000 Subject: [PATCH 001/194] New Crowdin translations by GitHub Action --- .../metadata/android/sr/full_description.txt | 36 ++++ .../metadata/android/sr/short_description.txt | 1 + mobile/fastlane/metadata/android/sr/title.txt | 1 + .../fastlane/metadata/ios/sr/description.txt | 33 ++++ mobile/fastlane/metadata/ios/sr/keywords.txt | 1 + mobile/fastlane/metadata/ios/sr/name.txt | 1 + mobile/fastlane/metadata/ios/sr/subtitle.txt | 1 + .../playstore/sr/full_description.txt | 30 +++ .../playstore/sr/short_description.txt | 1 + .../fastlane/metadata/playstore/sr/title.txt | 1 + mobile/lib/l10n/intl_ar.arb | 86 +++++---- mobile/lib/l10n/intl_cs.arb | 5 +- mobile/lib/l10n/intl_de.arb | 10 - mobile/lib/l10n/intl_es.arb | 10 - mobile/lib/l10n/intl_fr.arb | 18 +- mobile/lib/l10n/intl_id.arb | 2 + mobile/lib/l10n/intl_ja.arb | 12 +- mobile/lib/l10n/intl_lt.arb | 172 ++++++++++++++++-- mobile/lib/l10n/intl_nl.arb | 10 - mobile/lib/l10n/intl_no.arb | 12 +- mobile/lib/l10n/intl_pt_BR.arb | 10 - mobile/lib/l10n/intl_ru.arb | 10 - mobile/lib/l10n/intl_sr.arb | 3 + mobile/lib/l10n/intl_tr.arb | 24 +-- mobile/lib/l10n/intl_zh.arb | 12 +- 25 files changed, 330 insertions(+), 172 deletions(-) create mode 100644 mobile/fastlane/metadata/android/sr/full_description.txt create mode 100644 mobile/fastlane/metadata/android/sr/short_description.txt create mode 100644 mobile/fastlane/metadata/android/sr/title.txt create mode 100644 mobile/fastlane/metadata/ios/sr/description.txt create mode 100644 mobile/fastlane/metadata/ios/sr/keywords.txt create mode 100644 mobile/fastlane/metadata/ios/sr/name.txt create mode 100644 mobile/fastlane/metadata/ios/sr/subtitle.txt create mode 100644 mobile/fastlane/metadata/playstore/sr/full_description.txt create mode 100644 mobile/fastlane/metadata/playstore/sr/short_description.txt create mode 100644 mobile/fastlane/metadata/playstore/sr/title.txt create mode 100644 mobile/lib/l10n/intl_sr.arb diff --git a/mobile/fastlane/metadata/android/sr/full_description.txt b/mobile/fastlane/metadata/android/sr/full_description.txt new file mode 100644 index 0000000000..9ba4fe3143 --- /dev/null +++ b/mobile/fastlane/metadata/android/sr/full_description.txt @@ -0,0 +1,36 @@ +ente is a simple app to backup and share your photos and videos. + +If you've been looking for a privacy-friendly alternative to Google Photos, you've come to the right place. With ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them. + +We have open-source apps across Android, iOS, web and desktop, and your photos will seamlessly sync between all of them in an end-to-end encrypted (e2ee) manner. + +ente also makes it simple to share your albums with your loved ones, even if they aren't on ente. You can share publicly viewable links, where they can view your album and collaborate by adding photos to it, even without an account or app. + +Your encrypted data is replicated to 3 different locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you. + +We are here to make the safest photos app ever, come join our journey! + +FEATURES +- Original quality backups, because every pixel is important +- Family plans, so you can share storage with your family +- Collaborative albums, so you can pool together photos after a trip +- Shared folders, in case you want your partner to enjoy your "Camera" clicks +- Album links, that can be protected with a password +- Ability to free up space, by removing files that have been safely backed up +- Human support, because you're worth it +- Descriptions, so you can caption your memories and find them easily +- Image editor, to add finishing touches +- Favorite, hide and relive your memories, for they are precious +- One-click import from Google, Apple, your hard drive and more +- Dark theme, because your photos look good in it +- 2FA, 3FA, biometric auth +- and a LOT more! + +PERMISSIONS +ente requests for certain permissions to serve the purpose of a photo storage provider, which can be reviewed here: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md + +PRICING +We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io. + +SUPPORT +We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours. diff --git a/mobile/fastlane/metadata/android/sr/short_description.txt b/mobile/fastlane/metadata/android/sr/short_description.txt new file mode 100644 index 0000000000..7a5fe973db --- /dev/null +++ b/mobile/fastlane/metadata/android/sr/short_description.txt @@ -0,0 +1 @@ +ente is an end-to-end encrypted photo storage app \ No newline at end of file diff --git a/mobile/fastlane/metadata/android/sr/title.txt b/mobile/fastlane/metadata/android/sr/title.txt new file mode 100644 index 0000000000..3a4fed48fe --- /dev/null +++ b/mobile/fastlane/metadata/android/sr/title.txt @@ -0,0 +1 @@ +ente - encrypted photo storage \ No newline at end of file diff --git a/mobile/fastlane/metadata/ios/sr/description.txt b/mobile/fastlane/metadata/ios/sr/description.txt new file mode 100644 index 0000000000..a98a74300a --- /dev/null +++ b/mobile/fastlane/metadata/ios/sr/description.txt @@ -0,0 +1,33 @@ +Ente is a simple app to automatically backup and organize your photos and videos. + +If you've been looking for a privacy-friendly alternative to preserve your memories, you've come to the right place. With Ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them. + +We have apps across all platforms, and your photos will seamlessly sync between all your devices in an end-to-end encrypted (e2ee) manner. + +Ente also makes it simple to share your albums with your loved ones. You can either share them directly with other Ente users, end-to-end encrypted; or with publicly viewable links. + +Your encrypted data is stored across multiple locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you. + +We are here to make the safest photos app ever, come join our journey! + +FEATURES +- Original quality backups, because every pixel is important +- Family plans, so you can share storage with your family +- Shared folders, in case you want your partner to enjoy your "Camera" clicks +- Album links, that can be protected with a password and set to expire +- Ability to free up space, by removing files that have been safely backed up +- Image editor, to add finishing touches +- Favorite, hide and relive your memories, for they are precious +- One-click import from all major storage providers +- Dark theme, because your photos look good in it +- 2FA, 3FA, biometric auth +- and a LOT more! + +PRICING +We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io. + +SUPPORT +We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours. + +TERMS +https://ente.io/terms diff --git a/mobile/fastlane/metadata/ios/sr/keywords.txt b/mobile/fastlane/metadata/ios/sr/keywords.txt new file mode 100644 index 0000000000..e1462baf51 --- /dev/null +++ b/mobile/fastlane/metadata/ios/sr/keywords.txt @@ -0,0 +1 @@ +photos,photography,family,privacy,cloud,backup,videos,photo,encryption,storage,album,alternative diff --git a/mobile/fastlane/metadata/ios/sr/name.txt b/mobile/fastlane/metadata/ios/sr/name.txt new file mode 100644 index 0000000000..3a991c4abc --- /dev/null +++ b/mobile/fastlane/metadata/ios/sr/name.txt @@ -0,0 +1 @@ +Ente Photos diff --git a/mobile/fastlane/metadata/ios/sr/subtitle.txt b/mobile/fastlane/metadata/ios/sr/subtitle.txt new file mode 100644 index 0000000000..958a35f1c9 --- /dev/null +++ b/mobile/fastlane/metadata/ios/sr/subtitle.txt @@ -0,0 +1 @@ +Encrypted photo storage diff --git a/mobile/fastlane/metadata/playstore/sr/full_description.txt b/mobile/fastlane/metadata/playstore/sr/full_description.txt new file mode 100644 index 0000000000..ec999a783c --- /dev/null +++ b/mobile/fastlane/metadata/playstore/sr/full_description.txt @@ -0,0 +1,30 @@ +Ente is a simple app to automatically backup and organize your photos and videos. + +If you've been looking for a privacy-friendly alternative to preserve your memories, you've come to the right place. With Ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them. + +We have apps across Android, iOS, web and Desktop, and your photos will seamlessly sync between all your devices in an end-to-end encrypted (e2ee) manner. + +Ente also makes it simple to share your albums with your loved ones. You can either share them directly with other Ente users, end-to-end encrypted; or with publicly viewable links. + +Your encrypted data is stored across multiple locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you. + +We are here to make the safest photos app ever, come join our journey! + +✨ FEATURES +- Original quality backups, because every pixel is important +- Family plans, so you can share storage with your family +- Shared folders, in case you want your partner to enjoy your "Camera" clicks +- Album links, that can be protected with a password and set to expire +- Ability to free up space, by removing files that have been safely backed up +- Image editor, to add finishing touches +- Favorite, hide and relive your memories, for they are precious +- One-click import from Google, Apple, your hard drive and more +- Dark theme, because your photos look good in it +- 2FA, 3FA, biometric auth +- and a LOT more! + +💲 PRICING +We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io. + +🙋 SUPPORT +We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours. \ No newline at end of file diff --git a/mobile/fastlane/metadata/playstore/sr/short_description.txt b/mobile/fastlane/metadata/playstore/sr/short_description.txt new file mode 100644 index 0000000000..6c00229894 --- /dev/null +++ b/mobile/fastlane/metadata/playstore/sr/short_description.txt @@ -0,0 +1 @@ +Encrypted photo storage - backup, organize and share your photos and videos \ No newline at end of file diff --git a/mobile/fastlane/metadata/playstore/sr/title.txt b/mobile/fastlane/metadata/playstore/sr/title.txt new file mode 100644 index 0000000000..97fdef3be7 --- /dev/null +++ b/mobile/fastlane/metadata/playstore/sr/title.txt @@ -0,0 +1 @@ +Ente Photos \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ar.arb b/mobile/lib/l10n/intl_ar.arb index 2ab97549b1..bc05cd3609 100644 --- a/mobile/lib/l10n/intl_ar.arb +++ b/mobile/lib/l10n/intl_ar.arb @@ -1,6 +1,7 @@ { "@@locale ": "en", "enterYourEmailAddress": "أدخل عنوان بريدك الإلكتروني", + "enterYourNewEmailAddress": "أدخل عنوان بريدك الإلكتروني الجديد", "accountWelcomeBack": "أهلاً بعودتك!", "emailAlreadyRegistered": "البريد الإلكتروني مُسجل من قبل.", "emailNotRegistered": "البريد الإلكتروني غير مسجل.", @@ -35,7 +36,7 @@ "activeSessions": "الجلسات النشطة", "oops": "عفوًا", "somethingWentWrongPleaseTryAgain": "حدث خطأ ما، يرجى المحاولة مرة أخرى", - "thisWillLogYouOutOfThisDevice": "سيؤدي هذا إلى تسجيل خروجك من هذا الجهاز.", + "thisWillLogYouOutOfThisDevice": "سيؤدي هذا إلى تسجيل خروجك من هذا الجهاز!", "thisWillLogYouOutOfTheFollowingDevice": "سيؤدي هذا إلى تسجيل خروجك من الجهاز التالي:", "terminateSession": "إنهاء الجَلسةِ؟", "terminate": "إنهاء", @@ -45,7 +46,7 @@ "decrypting": "جارٍ فك التشفير...", "incorrectRecoveryKeyTitle": "مفتاح الاسترداد غير صحيح", "incorrectRecoveryKeyBody": "مفتاح الاسترداد الذي أدخلته غير صحيح", - "forgotPassword": "نسيت كلمة المرور؟", + "forgotPassword": "نسيت كلمة المرور", "enterYourRecoveryKey": "أدخل مفتاح الاسترداد", "noRecoveryKey": "لا تملك مفتاح استرداد؟", "sorry": "عفوًا", @@ -120,7 +121,7 @@ "recoveryKeyCopiedToClipboard": "تم نسخ مفتاح الاسترداد إلى الحافظة", "recoverAccount": "استعادة الحساب", "recover": "استعادة", - "dropSupportEmail": "يرجى إرسال بريد إلكتروني إلى {supportEmail} من عنوان بريدك الإلكتروني المسجل.", + "dropSupportEmail": "يرجى إرسال بريد إلكتروني إلى {supportEmail} من عنوان بريدك الإلكتروني المسجل", "@dropSupportEmail": { "placeholders": { "supportEmail": { @@ -140,12 +141,12 @@ "enterThe6digitCodeFromnyourAuthenticatorApp": "أدخل الرمز المكون من 6 أرقام من\n تطبيق المصادقة الخاص بك", "confirm": "تأكيد", "setupComplete": "اكتمل الإعداد", - "saveYourRecoveryKeyIfYouHaventAlready": "احفظ مفتاح الاسترداد إذا لم تكن قد فعلت ذلك بالفعل.", - "thisCanBeUsedToRecoverYourAccountIfYou": "يمكن استخدام هذا المفتاح لاستعادة حسابك إذا فقدت جهاز المصادقة الثنائية.", + "saveYourRecoveryKeyIfYouHaventAlready": "احفظ مفتاح الاسترداد إذا لم تكن قد فعلت ذلك", + "thisCanBeUsedToRecoverYourAccountIfYou": "يمكن استخدام هذا المفتاح لاستعادة حسابك إذا فقدت العامل الثاني للمصادقة", "twofactorAuthenticationPageTitle": "المصادقة الثنائية", "lostDevice": "جهاز مفقود؟", "verifyingRecoveryKey": "جارٍ التحقق من مفتاح الاسترداد...", - "recoveryKeyVerified": "تم التحقق من مفتاح الاسترداد.", + "recoveryKeyVerified": "تم التحقق من مفتاح الاسترداد", "recoveryKeySuccessBody": "مفتاح الاسترداد الخاص بك صالح. شكرًا على التحقق.\n\nيرجى تذكر الاحتفاظ بنسخة احتياطية آمنة من مفتاح الاسترداد.", "invalidRecoveryKey": "مفتاح الاسترداد الذي أدخلته غير صالح. يرجى التأكد من أنه يحتوي على 24 كلمة، والتحقق من كتابة كل كلمة بشكل صحيح.\n\nإذا كنت تستخدم مفتاح استرداد قديمًا، تأكد من أنه مكون من 64 حرفًا، وتحقق من صحة كل حرف.", "invalidKey": "المفتاح غير صالح", @@ -190,7 +191,7 @@ "canNotOpenTitle": "لا يمكن فتح هذا الألبوم", "canNotOpenBody": "عذرًا، لا يمكن فتح هذا الألبوم في التطبيق.", "disableDownloadWarningTitle": "يرجى الملاحظة", - "disableDownloadWarningBody": "لا يزال بإمكان المشاهدين التقاط لقطات شاشة أو حفظ نسخة من صورك باستخدام أدوات خارجية.", + "disableDownloadWarningBody": "لا يزال بإمكان المشاهدين التقاط لقطات شاشة أو حفظ نسخة من صورك باستخدام أدوات خارجية", "allowDownloads": "السماح بالتنزيلات", "linkDeviceLimit": "حد الأجهزة", "noDeviceLimit": "لا شيء", @@ -220,7 +221,7 @@ "after1Month": "بعد شهر", "after1Year": "بعد سنة", "manageParticipants": "إدارة المشاركين", - "albumParticipantsCount": "{count, plural, =0 {لا يوجد مشاركون} =1 {مشارك واحد} two {مشاركان} few {{count} مشاركين} many {{count} مشاركًا} other {{count} مشارك}}", + "albumParticipantsCount": "{count, plural, =0 {لا يوجد مُشاركون}=1 {مُشارك واحد} other {{count} مُشاركين}}", "@albumParticipantsCount": { "placeholders": { "count": { @@ -230,7 +231,7 @@ }, "description": "Number of participants in an album, including the album owner." }, - "collabLinkSectionDescription": "أنشئ رابطًا يسمح للأشخاص بإضافة الصور ومشاهدتها في ألبومك المشترك دون الحاجة إلى تطبيق Ente أو حساب. خيار مثالي لجمع صور الفعاليات بسهولة.", + "collabLinkSectionDescription": "أنشئ رابطًا يسمح للأشخاص بإضافة الصور ومشاهدتها في ألبومك المشترك دون الحاجة إلى تطبيق أو حساب Ente. خيار مثالي لجمع صور الفعاليات بسهولة.", "collectPhotos": "جمع الصور", "collaborativeLink": "رابط تعاوني", "shareWithNonenteUsers": "المشاركة مع غير مستخدمي Ente", @@ -241,7 +242,7 @@ "publicLinkEnabled": "تمكين الرابط العام", "shareALink": "مشاركة رابط", "sharedAlbumSectionDescription": "أنشئ ألبومات مشتركة وتعاونية مع مستخدمي Ente الآخرين، بما في ذلك المستخدمين ذوي الاشتراكات المجانية.", - "shareWithPeopleSectionTitle": "{numberOfPeople, plural, =0 {مشاركة مع أشخاص محددين} =1 {تمت المشاركة مع شخص واحد} two {تمت المشاركة مع شخصين} few {تمت المشاركة مع {numberOfPeople} أشخاص} many {تمت المشاركة مع {numberOfPeople} شخصًا} other {تمت المشاركة مع {numberOfPeople} شخصًا}}", + "shareWithPeopleSectionTitle": "{numberOfPeople, plural, =0 {مشاركة مع أشخاص مُحددين}=1 {مُشارَك مع شخص واحد} other {مُشارَك مع {numberOfPeople} أشخاص}}", "@shareWithPeopleSectionTitle": { "placeholders": { "numberOfPeople": { @@ -266,13 +267,13 @@ "verifyEmailID": "التحقق من {email}", "emailNoEnteAccount": "{email} لا يملك حسابًا على Ente.\n\nأرسل له دعوة لمشاركة الصور.", "shareMyVerificationID": "إليك معرّف التحقق الخاص بي لـ ente.io: {verificationID}", - "shareTextConfirmOthersVerificationID": "مرحبًا، هل يمكنك تأكيد أن هذا هو معرّف التحقق الخاص بك على ente.io: {verificationID}؟", + "shareTextConfirmOthersVerificationID": "مرحبًا، هل يمكنك تأكيد أن هذا هو معرّف التحقق الخاص بك على ente.io: {verificationID}", "somethingWentWrong": "حدث خطأ ما", "sendInvite": "إرسال دعوة", "shareTextRecommendUsingEnte": "قم بتنزيل تطبيق Ente حتى نتمكن من مشاركة الصور ومقاطع الفيديو بالجودة الأصلية بسهولة.\n\nhttps://ente.io", "done": "تم", "applyCodeTitle": "تطبيق الرمز", - "enterCodeDescription": "أدخل الرمز المقدم من صديقك للمطالبة بمساحة تخزين مجانية لكما.", + "enterCodeDescription": "أدخل الرمز المقدم من صديقك للمطالبة بمساحة تخزين مجانية لكما", "apply": "تطبيق", "failedToApplyCode": "فشل تطبيق الرمز", "enterReferralCode": "أدخل رمز الإحالة", @@ -354,8 +355,8 @@ "importing": "جارٍ الاستيراد...", "failedToLoadAlbums": "فشل تحميل الألبومات", "hidden": "المخفية", - "authToViewYourHiddenFiles": "يرجى المصادقة للوصول إلى ملفاتك المخفية.", - "authToViewTrashedFiles": "يرجى المصادقة لعرض ملفاتك المحذوفة.", + "authToViewYourHiddenFiles": "يرجى المصادقة للوصول إلى ملفاتك المخفية", + "authToViewTrashedFiles": "يرجى المصادقة لعرض ملفاتك المحذوفة", "trash": "سلة المهملات", "uncategorized": "غير مصنف", "videoSmallCase": "فيديو", @@ -364,7 +365,7 @@ "singleFileInBothLocalAndRemote": "{fileType} موجود في Ente وعلى جهازك.", "singleFileInRemoteOnly": "سيتم حذف {fileType} من Ente.", "singleFileDeleteFromDevice": "سيتم حذف {fileType} من جهازك.", - "deleteFromEnte": "الحذف من Ente", + "deleteFromEnte": "حذف من Ente", "yesDelete": "نعم، حذف", "movedToTrash": "تم النقل إلى سلة المهملات", "deleteFromDevice": "الحذف من الجهاز", @@ -494,14 +495,14 @@ "youAreOnTheLatestVersion": "أنت تستخدم أحدث إصدار.", "account": "الحساب", "manageSubscription": "إدارة الاشتراك", - "authToChangeYourEmail": "يرجى المصادقة لتغيير بريدك الإلكتروني.", + "authToChangeYourEmail": "يرجى المصادقة لتغيير بريدك الإلكتروني", "changePassword": "تغيير كلمة المرور", - "authToChangeYourPassword": "يرجى المصادقة لتغيير كلمة المرور الخاصة بك.", + "authToChangeYourPassword": "يرجى المصادقة لتغيير كلمة المرور الخاصة بك", "emailVerificationToggle": "تأكيد عنوان البريد الإلكتروني", - "authToChangeEmailVerificationSetting": "يرجى المصادقة لتغيير إعداد التحقق من البريد الإلكتروني.", + "authToChangeEmailVerificationSetting": "يرجى المصادقة لتغيير إعداد التحقق من البريد الإلكتروني", "exportYourData": "تصدير بياناتك", "logout": "تسجيل الخروج", - "authToInitiateAccountDeletion": "يرجى المصادقة لبدء عملية حذف الحساب.", + "authToInitiateAccountDeletion": "يرجى المصادقة لبدء عملية حذف الحساب", "areYouSureYouWantToLogout": "هل أنت متأكد من رغبتك في تسجيل الخروج؟", "yesLogout": "نعم، تسجيل الخروج", "aNewVersionOfEnteIsAvailable": "يتوفر إصدار جديد من Ente.", @@ -511,24 +512,24 @@ "updateAvailable": "يتوفر تحديث", "ignoreUpdate": "تجاهل", "downloading": "جارٍ التنزيل...", - "cannotDeleteSharedFiles": "لا يمكن حذف الملفات المشتركة.", - "theDownloadCouldNotBeCompleted": "تعذر إكمال التنزيل.", + "cannotDeleteSharedFiles": "لا يمكن حذف الملفات المشتركة", + "theDownloadCouldNotBeCompleted": "تعذر إكمال التنزيل", "retry": "إعادة المحاولة", "backedUpFolders": "المجلدات المنسوخة احتياطيًا", "backup": "النسخ الاحتياطي", "freeUpDeviceSpace": "تحرير مساحة على الجهاز", "freeUpDeviceSpaceDesc": "وفر مساحة على جهازك عن طريق مسح الملفات التي تم نسخها احتياطيًا.", "allClear": "✨ كل شيء واضح", - "noDeviceThatCanBeDeleted": "لا توجد ملفات على هذا الجهاز يمكن حذفها.", + "noDeviceThatCanBeDeleted": "لا توجد ملفات على هذا الجهاز يمكن حذفها", "removeDuplicates": "إزالة النسخ المكررة", "removeDuplicatesDesc": "مراجعة وإزالة الملفات المتطابقة تمامًا.", "viewLargeFiles": "الملفات الكبيرة", "viewLargeFilesDesc": "عرض الملفات التي تستهلك أكبر قدر من مساحة التخزين.", "noDuplicates": "✨ لا توجد ملفات مكررة", - "youveNoDuplicateFilesThatCanBeCleared": "لا توجد لديك أي ملفات مكررة يمكن مسحها.", + "youveNoDuplicateFilesThatCanBeCleared": "لا توجد لديك أي ملفات مكررة يمكن مسحها", "success": "تم بنجاح", "rateUs": "تقييم التطبيق", - "remindToEmptyDeviceTrash": "تذكر أيضًا إفراغ \"المحذوفة مؤخرًا\" من \"الإعدادات\" -> \"التخزين\" لاستعادة المساحة المحررة.", + "remindToEmptyDeviceTrash": "تذكر أيضًا إفراغ \"المحذوفة مؤخرًا\" من \"الإعدادات\" -> \"التخزين\" لاستعادة المساحة المحررة", "youHaveSuccessfullyFreedUp": "لقد حررت {storageSaved} بنجاح!", "@youHaveSuccessfullyFreedUp": { "description": "The text to display when the user has successfully freed up storage", @@ -598,7 +599,7 @@ "selectYourPlan": "اختر خطتك", "enteSubscriptionPitch": "يحفظ Ente ذكرياتك، بحيث تظل دائمًا متاحة لك حتى لو فقدت جهازك.", "enteSubscriptionShareWithFamily": "يمكنك أيضًا إضافة أفراد عائلتك إلى خطتك.", - "currentUsageIs": "استخدامك الحالي هو", + "currentUsageIs": "استخدامك الحالي هو ", "@currentUsageIs": { "description": "This text is followed by storage usage", "examples": { @@ -682,7 +683,7 @@ "areYouSureYouWantToExit": "هل أنت متأكد من رغبتك في الخروج؟", "thankYou": "شكرًا لك", "failedToVerifyPaymentStatus": "فشل التحقق من حالة الدفع.", - "pleaseWaitForSometimeBeforeRetrying": "يرجى الانتظار لبعض الوقت قبل إعادة المحاولة.", + "pleaseWaitForSometimeBeforeRetrying": "يرجى الانتظار لبعض الوقت قبل إعادة المحاولة", "paymentFailedMessage": "للأسف، فشلت عملية الدفع الخاصة بك. يرجى الاتصال بالدعم وسوف نساعدك!", "youAreOnAFamilyPlan": "أنت مشترك في خطة عائلية!", "contactFamilyAdmin": "يرجى الاتصال بـ {familyAdminEmail} لإدارة اشتراكك.", @@ -691,9 +692,9 @@ "leave": "مغادرة", "rateTheApp": "تقييم التطبيق", "startBackup": "بدء النسخ الاحتياطي", - "noPhotosAreBeingBackedUpRightNow": "لا يتم نسخ أي صور احتياطيًا في الوقت الحالي.", + "noPhotosAreBeingBackedUpRightNow": "لا يتم نسخ أي صور احتياطيًا في الوقت الحالي", "preserveMore": "حفظ المزيد", - "grantFullAccessPrompt": "يرجى السماح بالوصول إلى جميع الصور في تطبيق الإعدادات.", + "grantFullAccessPrompt": "الرجاء السماح بالوصول إلى جميع الصور في تطبيق الإعدادات", "allowPermTitle": "السماح بالوصول إلى الصور", "allowPermBody": "يرجى السماح بالوصول إلى صورك من الإعدادات حتى يتمكن Ente من عرض نسختك الاحتياطية ومكتبتك.", "openSettings": "فتح الإعدادات", @@ -710,10 +711,10 @@ "androidIosWebDesktop": "أندرويد، iOS، الويب، سطح المكتب", "mobileWebDesktop": "الهاتف المحمول، الويب، سطح المكتب", "newToEnte": "جديد في Ente", - "pleaseLoginAgain": "يرجى تسجيل الدخول مرة أخرى.", + "pleaseLoginAgain": "يرجى تسجيل الدخول مرة أخرى", "autoLogoutMessage": "بسبب خلل تقني، تم تسجيل خروجك. نعتذر عن الإزعاج.", - "yourSubscriptionHasExpired": "انتهت صلاحية اشتراكك.", - "storageLimitExceeded": "تم تجاوز حد التخزين.", + "yourSubscriptionHasExpired": "انتهت صلاحية اشتراكك", + "storageLimitExceeded": "تم تجاوز حد التخزين", "upgrade": "ترقية", "raiseTicket": "فتح تذكرة دعم", "@raiseTicket": { @@ -723,8 +724,8 @@ "backupFailed": "فشل النسخ الاحتياطي", "sorryBackupFailedDesc": "عذرًا، لم نتمكن من عمل نسخة احتياطية لهذا الملف الآن، سنعيد المحاولة لاحقًا.", "couldNotBackUpTryLater": "لم نتمكن من نسخ بياناتك احتياطيًا.\nسنحاول مرة أخرى لاحقًا.", - "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "يمكن لـ Ente تشفير وحفظ الملفات فقط إذا منحت الإذن بالوصول إليها.", - "pleaseGrantPermissions": "يرجى منح الأذونات.", + "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "يمكن لـ Ente تشفير وحفظ الملفات فقط إذا منحت الإذن بالوصول إليها", + "pleaseGrantPermissions": "يرجى منح الأذونات", "grantPermission": "منح الإذن", "privateSharing": "مشاركة خاصة", "shareOnlyWithThePeopleYouWant": "شارك فقط مع الأشخاص الذين تريدهم.", @@ -1030,7 +1031,7 @@ "didYouKnow": "هل تعلم؟", "loadingMessage": "جارٍ تحميل صورك...", "loadMessage1": "يمكنك مشاركة اشتراكك مع عائلتك.", - "loadMessage2": "لقد حفظنا أكثر من 200 مليون ذكرى حتى الآن.", + "loadMessage2": "لقد حفظنا أكثر من 200 مليون ذكرى حتى الآن", "loadMessage3": "نحتفظ بـ 3 نسخ من بياناتك، إحداها في ملجأ للطوارئ تحت الأرض.", "loadMessage4": "جميع تطبيقاتنا مفتوحة المصدر.", "loadMessage5": "تم تدقيق شفرتنا المصدرية والتشفير الخاص بنا خارجيًا.", @@ -1729,15 +1730,10 @@ "onTheRoad": "على الطريق مرة أخرى", "food": "متعة الطهي", "pets": "رفاق فروي", - "cLIcon": "أيقونة جديدة", - "cLIconDesc": "أخيرًا، أيقونة تطبيق جديدة، نعتقد أنها تمثل عملنا على أفضل وجه. أضفنا أيضًا مبدل أيقونات حتى تتمكن من الاستمرار في استخدام الأيقونة القديمة.", - "cLMemories": "الذكريات", - "cLMemoriesDesc": "أعد اكتشاف لحظاتك الخاصة - تسليط الضوء على الأشخاص المفضلين لديك، رحلاتك وعطلاتك، أفضل لقطاتك، وأكثر من ذلك بكثير. قم بتشغيل تعلم الآلة، ضع علامة على نفسك وقم بتسمية أصدقائك للحصول على أفضل تجربة.", - "cLWidgets": "الأدوات المصغرة (Widgets)", - "cLWidgetsDesc": "الأدوات المصغرة للشاشة الرئيسية المدمجة مع الذكريات متاحة الآن. ستعرض لحظاتك الخاصة دون فتح التطبيق.", - "cLFamilyPlan": "حدود الخطة العائلية", - "cLFamilyPlanDesc": "يمكنك الآن تعيين حدود لمقدار التخزين الذي يمكن لأفراد عائلتك استخدامه.", - "cLBulkEdit": "تعديل التواريخ بشكل جماعي", - "cLBulkEditDesc": "يمكنك الآن تحديد صور متعددة، وتعديل التاريخ/الوقت لجميعها بإجراء سريع واحد. تغيير التواريخ مدعوم أيضًا.", - "curatedMemories": "ذكريات منسقة" + "curatedMemories": "ذكريات منسقة", + "onThisDay": "في هذا اليوم", + "deleteMultipleAlbumDialog": "هل تريد أيضًا حذف الصور (والمقاطع) الموجودة في هذه الألبومات {count} من كافة الألبومات الأخرى التي تشترك فيها؟", + "addParticipants": "إضافة مشاركين", + "selectedAlbums": "{count} تم تحديد", + "actionNotSupportedOnFavouritesAlbum": "الإجراء غير مدعوم في ألبوم المفضلة" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_cs.arb b/mobile/lib/l10n/intl_cs.arb index cfe52cf83c..fa046acab2 100644 --- a/mobile/lib/l10n/intl_cs.arb +++ b/mobile/lib/l10n/intl_cs.arb @@ -436,8 +436,5 @@ "previous": "Předchozí", "newRange": "Nový rozsah", "youAndThem": "Vy a {name}", - "selfiesWithThem": "Selfie s {name}", - "cLIcon": "Nová ikona", - "cLMemories": "Vzpomínky", - "cLWidgets": "Widgety" + "selfiesWithThem": "Selfie s {name}" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_de.arb b/mobile/lib/l10n/intl_de.arb index 205cbc65ec..d8099fcb2f 100644 --- a/mobile/lib/l10n/intl_de.arb +++ b/mobile/lib/l10n/intl_de.arb @@ -1730,16 +1730,6 @@ "onTheRoad": "Wieder unterwegs", "food": "Kulinarische Genüsse", "pets": "Pelzige Begleiter", - "cLIcon": "Neues Icon", - "cLIconDesc": "Endlich ein neues App-Icon, das unserer Meinung nach unser Werk am besten repräsentiert. Zudem ist es möglich, weiterhin das alte App-Icon zu verwenden.", - "cLMemories": "Erinnerungen", - "cLMemoriesDesc": "Entdecke Deine besonderen Momente neu – Spot auf Deine liebsten Personen, Deine Reisen und Urlaube, Deine besten Schnappschüsse und vieles mehr. Aktiviere das maschinelle Lernen, tagge Dich selbst und benenne Deine Freunde für die besten Ergebnisse.", - "cLWidgets": "Widgets", - "cLWidgetsDesc": "Homescreen-Widgets mit integrierten Erinnerungen sind nun verfügbar. Sie zeigen dir deine besonderen Momente an, ohne die App zu öffnen.", - "cLFamilyPlan": "Obergrenzen für den Familientarif", - "cLFamilyPlanDesc": "Du kannst jetzt festlegen, wie viel Speicherplatz deine Familienmitglieder nutzen können.", - "cLBulkEdit": "Massenbearbeitung von Datumsangaben", - "cLBulkEditDesc": "Du kannst jetzt mehrere Fotos auswählen, und das Datum/Uhrzeit für alle mit einer Aktion ändern. Das Verschieben von Daten wird auch unterstützt.", "curatedMemories": "Ausgewählte Erinnerungen", "onThisDay": "An diesem Tag", "deleteMultipleAlbumDialog": "Sollen die Fotos (und Videos) aus diesen {count} Alben auch aus allen anderen Alben gelöscht werden, in denen sie enthalten sind?", diff --git a/mobile/lib/l10n/intl_es.arb b/mobile/lib/l10n/intl_es.arb index f5348065ba..665724372d 100644 --- a/mobile/lib/l10n/intl_es.arb +++ b/mobile/lib/l10n/intl_es.arb @@ -1724,15 +1724,5 @@ "onTheRoad": "De nuevo en la carretera", "food": "Delicia culinaria", "pets": "Compañeros peludos", - "cLIcon": "Nuevo ícono", - "cLIconDesc": "Por fin, un nuevo icono de la aplicación, que creemos que representa mejor nuestro trabajo. También hemos añadido una opción para que puedas seguir utilizando el icono anterior.", - "cLMemories": "Recuerdos", - "cLMemoriesDesc": "Redescubre tus momentos especiales: enfócate en tu gente favorita, tus viajes y vacaciones, tus mejores clics, y mucho más. Activa el aprendizaje de automático, etiquétate a ti mismo y etiqueta a tus amigos para la mejor experiencia.", - "cLWidgets": "Widgets", - "cLWidgetsDesc": "Ya están disponibles los widgets de pantalla de inicio con tus recuerdos. Podrás ver tus momentos especiales sin abrir la aplicación.", - "cLFamilyPlan": "Límites de plan familiar", - "cLFamilyPlanDesc": "Ahora puede establecer límites en cuanto al almacenamiento que los miembros de tu familia pueden utilizar.", - "cLBulkEdit": "Edición masiva de fechas", - "cLBulkEditDesc": "Ahora puedes seleccionar múltiples fotos y editar la fecha/hora para todas ellas con una acción rápida. También es posible cambiar las fechas.", "curatedMemories": "Memorias revisadas" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_fr.arb b/mobile/lib/l10n/intl_fr.arb index 3f4dc5004c..e3983fcbb4 100644 --- a/mobile/lib/l10n/intl_fr.arb +++ b/mobile/lib/l10n/intl_fr.arb @@ -794,6 +794,14 @@ "share": "Partager", "unhideToAlbum": "Afficher dans l'album", "restoreToAlbum": "Restaurer vers l'album", + "moveItem": "{count, plural,=1 {Déplacer un élément} other {Déplacer des éléments}}", + "@moveItem": { + "description": "Page title while moving one or more items to an album" + }, + "addItem": "{count, plural, =1 {Ajouter un élément} other {Ajouter des éléments}}", + "@addItem": { + "description": "Page title while adding one or more items to album" + }, "createOrSelectAlbum": "Créez ou sélectionnez un album", "selectAlbum": "Sélectionner album", "searchByAlbumNameHint": "Nom de l'album", @@ -1722,16 +1730,6 @@ "onTheRoad": "De nouveau sur la route", "food": "Plaisir culinaire", "pets": "Compagnons à quatre pattes", - "cLIcon": "Nouvel icône", - "cLIconDesc": "Finalement, création d'un nouvel icône d'application qui, selon nous, représente au mieux notre travail. Nous avons également ajouté un changeur d'icône pour que vous puissiez continuer à utiliser l'ancien.", - "cLMemories": "Souvenirs", - "cLMemoriesDesc": "Redécouvrez vos précieux souvenirs - focus sur vos connaissances préférées, vos voyages et vos vacances, vos meilleurs clics et bien plus encore. Activez l'apprentissage automatique, taguez-vous et nommez vos amis pour une meilleure expérience.", - "cLWidgets": "Widgets", - "cLWidgetsDesc": "Les widgets (ou gadgets) de l'écran d'accueil, qui sont intégrés à des souvenirs, sont maintenant disponibles. Ils montreront vos moments spéciaux sans nécessité d'ouvrir l'application.", - "cLFamilyPlan": "Limites pour le forfait Famille", - "cLFamilyPlanDesc": "Vous pouvez maintenant fixer des limites sur la quantité de stockage que les membres de votre famille peuvent utiliser.", - "cLBulkEdit": "Dates de modification multiples", - "cLBulkEditDesc": "Vous pouvez maintenant sélectionner plusieurs photos et modifier la date/heure pour toutes celles-ci, en une seule action rapide. Les dates de décalage sont également prises en charge.", "curatedMemories": "Souvenirs conservés", "onThisDay": "Ce jour-ci", "deleteMultipleAlbumDialog": "Supprimer également les photos (et les vidéos) présentes dans ces {count} albums de tous les autres albums dont ils font partie ?", diff --git a/mobile/lib/l10n/intl_id.arb b/mobile/lib/l10n/intl_id.arb index 4515504579..9beff51a08 100644 --- a/mobile/lib/l10n/intl_id.arb +++ b/mobile/lib/l10n/intl_id.arb @@ -1,7 +1,9 @@ { "@@locale ": "en", "enterYourEmailAddress": "Masukkan alamat email kamu", + "enterYourNewEmailAddress": "Masukkan alamat email baru anda", "accountWelcomeBack": "Selamat datang kembali!", + "emailAlreadyRegistered": "Email sudah terdaftar.", "email": "Email", "cancel": "Batal", "verify": "Verifikasi", diff --git a/mobile/lib/l10n/intl_ja.arb b/mobile/lib/l10n/intl_ja.arb index e4082bd51b..63a175ed0f 100644 --- a/mobile/lib/l10n/intl_ja.arb +++ b/mobile/lib/l10n/intl_ja.arb @@ -1666,15 +1666,5 @@ "moon": "月明かりの中", "onTheRoad": "再び道で", "food": "料理を楽しむ", - "pets": "毛むくじゃらな仲間たち", - "cLIcon": "新しいアイコン", - "cLIconDesc": "新しいアプリのアイコンが登場です!ちなみに、古いアイコンが好きだった人のためにアイコンの切り替え機能も用意がございます。", - "cLMemories": "思い出", - "cLMemoriesDesc": "あなたにとって特別な瞬間を再発見しましょう。 - あなたの好きな人々、あなたの旅行や休日。 機械学習をオンにすると、自分自身にタグを付けたり、友達の顔に名前をつけたりすることができます。", - "cLWidgets": "ウィジェット", - "cLWidgetsDesc": "Enteの「思い出」機能と統合されたホーム画面ウィジェットが利用可能になりました!", - "cLFamilyPlan": "ファミリープランの制限", - "cLFamilyPlanDesc": "ファミリーメンバーが使用できるストレージ容量の制限を設定できるようになりました。", - "cLBulkEdit": "日付を一括編集", - "cLBulkEditDesc": "複数の写真を、1回の操作で日付/時刻を編集することもできるようになりました。" + "pets": "毛むくじゃらな仲間たち" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_lt.arb b/mobile/lib/l10n/intl_lt.arb index 5b71c6fba1..3996e8b997 100644 --- a/mobile/lib/l10n/intl_lt.arb +++ b/mobile/lib/l10n/intl_lt.arb @@ -1,6 +1,7 @@ { "@@locale ": "en", "enterYourEmailAddress": "Įveskite savo el. pašto adresą", + "enterYourNewEmailAddress": "Įveskite savo naują el. pašto adresą", "accountWelcomeBack": "Sveiki sugrįžę!", "emailAlreadyRegistered": "El. paštas jau užregistruotas.", "emailNotRegistered": "El. paštas neregistruotas.", @@ -156,7 +157,7 @@ "confirmYourRecoveryKey": "Patvirtinkite savo atkūrimo raktą", "addViewer": "Pridėti žiūrėtoją", "addCollaborator": "Pridėti bendradarbį", - "addANewEmail": "Pridėti naują el. paštą", + "addANewEmail": "Įtraukite naują el. paštą", "orPickAnExistingOne": "Arba pasirinkite esamą", "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": "Bendradarbiai gali pridėti nuotraukų ir vaizdo įrašų į bendrintą albumą.", "enterEmail": "Įveskite el. paštą", @@ -561,7 +562,7 @@ "referrals": "Rekomendacijos", "notifications": "Pranešimai", "sharedPhotoNotifications": "Naujos bendrintos nuotraukos", - "sharedPhotoNotificationsExplanation": "Gaukite pranešimus, kai kas nors prideda nuotrauką į bendrinamą albumą, kuriame dalyvaujate.", + "sharedPhotoNotificationsExplanation": "Gaukite pranešimus, kai kas nors įtraukia nuotrauką į bendrinamą albumą, kuriame dalyvaujate.", "advanced": "Išplėstiniai", "general": "Bendrieji", "security": "Saugumas", @@ -859,6 +860,7 @@ "toHideAPhotoOrVideo": "Kad paslėptumėte nuotrauką ar vaizdo įrašą", "openTheItem": "• Atverkite elementą.", "clickOnTheOverflowMenu": "• Spustelėkite ant perpildymo meniu", + "click": "• Spauskite", "nothingToSeeHere": "Čia nėra nieko, ką pamatyti. 👀", "unarchiveAlbum": "Išarchyvuoti albumą", "archiveAlbum": "Archyvuoti albumą", @@ -913,6 +915,21 @@ } } }, + "filesBackedUpFromDevice": "{count, plural, one {{formattedNumber} failas šiame įrenginyje saugiai sukurta atsarginė kopija} few {{formattedNumber} failai šiame įrenginyje saugiai sukurtos atsarginės kopijos} many {{formattedNumber} failo šiame įrenginyje saugiai sukurtos atsargines kopijos} other {{formattedNumber} failų šiame įrenginyje saugiai sukurta atsarginių kopijų}}.", + "@filesBackedUpFromDevice": { + "description": "Text to tell user how many files have been backed up from this device", + "placeholders": { + "count": { + "example": "1", + "type": "int" + }, + "formattedNumber": { + "content": "{formattedNumber}", + "example": "1,000", + "type": "String" + } + } + }, "@freeUpSpaceSaving": { "description": "Text to tell user how much space they can free up by deleting items from the device" }, @@ -936,6 +953,9 @@ "theRecoveryKeyYouEnteredIsIncorrect": "Įvestas atkūrimo raktas yra neteisingas.", "twofactorAuthenticationSuccessfullyReset": "Dvigubas tapatybės nustatymas sėkmingai iš naujo nustatytas.", "pleaseVerifyTheCodeYouHaveEntered": "Patvirtinkite įvestą kodą.", + "pleaseContactSupportIfTheProblemPersists": "Jei problema išlieka, susisiekite su pagalbos komanda.", + "twofactorAuthenticationHasBeenDisabled": "Dvigubas tapatybės nustatymas išjungtas.", + "sorryTheCodeYouveEnteredIsIncorrect": "Atsiprašome, įvestas kodas yra neteisingas.", "yourVerificationCodeHasExpired": "Jūsų patvirtinimo kodas nebegaliojantis.", "emailChangedTo": "El. paštas pakeistas į {newEmail}", "verifying": "Patvirtinama...", @@ -972,12 +992,20 @@ "successfullyArchived": "Sėkmingai suarchyvuota", "successfullyUnarchived": "Sėkmingai išarchyvuota", "renameFile": "Pervadinti failą", + "enterFileName": "Įveskite failo pavadinimą", + "filesDeleted": "Failai ištrinti", "selectedFilesAreNotOnEnte": "Pasirinkti failai nėra platformoje „Ente“", + "thisActionCannotBeUndone": "Šio veiksmo negalima anuliuoti.", "emptyTrash": "Ištuštinti šiukšlinę?", + "permDeleteWarning": "Visi elementai šiukšlinėje bus negrįžtamai ištrinti.\n\nŠio veiksmo negalima anuliuoti.", "empty": "Ištuštinti", "couldNotFreeUpSpace": "Nepavyko atlaisvinti vietos.", "permanentlyDeleteFromDevice": "Ištrinti negrįžtamai iš įrenginio?", "someOfTheFilesYouAreTryingToDeleteAre": "Kai kurie failai, kuriuos bandote ištrinti, yra pasiekiami tik jūsų įrenginyje ir jų negalima atkurti, jei jie buvo ištrinti.", + "theyWillBeDeletedFromAllAlbums": "Jie bus ištrinti iš visų albumų.", + "someItemsAreInBothEnteAndYourDevice": "Kai kurie elementai yra ir platformoje „Ente“ bei jūsų įrenginyje.", + "selectedItemsWillBeDeletedFromAllAlbumsAndMoved": "Pasirinkti elementai bus ištrinti iš visų albumų ir perkelti į šiukšlinę.", + "theseItemsWillBeDeletedFromYourDevice": "Šie elementai bus ištrinti iš jūsų įrenginio.", "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Atrodo, kad kažkas nutiko ne taip. Bandykite pakartotinai po kurio laiko. Jei klaida tęsiasi, susisiekite su mūsų palaikymo komanda.", "error": "Klaida", "tempErrorContactSupportIfPersists": "Atrodo, kad kažkas nutiko ne taip. Bandykite dar kartą po kurio laiko. Jei klaida tęsiasi, susisiekite su mūsų palaikymo komanda.", @@ -998,7 +1026,10 @@ "pleaseSendTheLogsTo": "Siųskite žurnalus adresu\n{toEmail}", "copyEmailAddress": "Kopijuoti el. pašto adresą", "exportLogs": "Eksportuoti žurnalus", + "pleaseEmailUsAt": "Siųskite el. laišką mums adresu {toEmail}.", + "dismiss": "Atmesti", "didYouKnow": "Ar žinojote?", + "loadingMessage": "Įkeliamos jūsų nuotraukos...", "loadMessage1": "Galite bendrinti savo prenumeratą su šeima.", "loadMessage2": "Iki šiol išsaugojome daugiau nei 200 milijonų prisiminimų.", "loadMessage3": "Laikome 3 jūsų duomenų kopijas, vieną iš jų – požeminėje priešgaisrinėje slėptuvėje.", @@ -1008,28 +1039,48 @@ "loadMessage7": "Mūsų mobiliosios programos veikia fone, kad užšifruotų ir sukurtų atsarginę kopiją visų naujų nuotraukų, kurias spustelėjate.", "loadMessage8": "web.ente.io turi sklandų įkėlėją", "loadMessage9": "Naudojame „Xchacha20Poly1305“, kad saugiai užšifruotume jūsų duomenis.", + "photoDescriptions": "Nuotraukų aprašai", + "fileTypesAndNames": "Failų tipai ir pavadinimai", "location": "Vietovė", "moments": "Akimirkos", + "searchFaceEmptySection": "Asmenys bus rodomi čia, kai bus užbaigtas indeksavimas.", + "searchDatesEmptySection": "Ieškokite pagal datą, mėnesį arba metus", "searchLocationEmptySection": "Grupės nuotraukos, kurios padarytos tam tikru spinduliu nuo nuotraukos", "searchPeopleEmptySection": "Pakvieskite asmenis ir čia matysite visas jų bendrinamas nuotraukas.", + "searchAlbumsEmptySection": "Albumai", + "searchFileTypesAndNamesEmptySection": "Failų tipai ir pavadinimai", "searchCaptionEmptySection": "Pridėkite aprašymus, pavyzdžiui, „#kelionė“, į nuotraukos informaciją, kad greičiau jas čia rastumėte.", "language": "Kalba", "selectLanguage": "Pasirinkite kalbą", "locationName": "Vietovės pavadinimas", "addLocation": "Pridėti vietovę", + "groupNearbyPhotos": "Grupuoti netoliese nuotraukas", "kiloMeterUnit": "km", "addLocationButton": "Pridėti", + "radius": "Spindulys", "locationTagFeatureDescription": "Vietos žymė grupuoja visas nuotraukas, kurios buvo padarytos tam tikru spinduliu nuo nuotraukos", "galleryMemoryLimitInfo": "Galerijoje rodoma iki 1000 prisiminimų", - "centerPoint": "Vidurio taškas", + "save": "Išsaugoti", + "centerPoint": "Centro taškas", + "pickCenterPoint": "Pasirinkite centro tašką", + "useSelectedPhoto": "Naudoti pasirinktą nuotrauką", "resetToDefault": "Atkurti numatytąsias reikšmes", "@resetToDefault": { "description": "Button text to reset cover photo to default" }, "edit": "Redaguoti", "deleteLocation": "Ištrinti vietovę", + "rotateLeft": "Sukti į kairę", + "flip": "Apversti", + "rotateRight": "Sukti į dešinę", + "saveCopy": "Išsaugoti kopiją", "light": "Šviesi", "color": "Spalva", + "yesDiscardChanges": "Taip, atmesti pakeitimus", + "doYouWantToDiscardTheEditsYouHaveMade": "Ar norite atmesti atliktus pakeitimus?", + "saving": "Išsaugoma...", + "editsSaved": "Redagavimai išsaugoti", + "oopsCouldNotSaveEdits": "Ups, nepavyko išsaugoti redagavimų.", "distanceInKMUnit": "km", "@distanceInKMUnit": { "description": "Unit for distance in km" @@ -1038,10 +1089,16 @@ "dayYesterday": "Vakar", "storage": "Saugykla", "usedSpace": "Naudojama vieta", + "storageBreakupFamily": "Šeima", "storageBreakupYou": "Jūs", "@storageBreakupYou": { "description": "Label to indicate how much storage you are using when you are part of a family plan" }, + "storageUsageInfo": "{usedAmount} {usedStorageUnit} iš {totalAmount} {totalStorageUnit} naudojama", + "@storageUsageInfo": { + "description": "Example: 1.2 GB of 2 GB used or 100 GB or 2TB used" + }, + "availableStorageSpace": "{freeAmount} {storageUnit} laisva", "appVersion": "Versija: {versionValue}", "verifyIDLabel": "Patvirtinti", "fileInfoAddDescHint": "Pridėti aprašymą...", @@ -1050,12 +1107,21 @@ "@setLabel": { "description": "Label of confirm button to add a new custom radius to the radius selector of a location tag" }, + "setRadius": "Nustatyti spindulį", "familyPlanPortalTitle": "Šeima", "familyPlanOverview": "Įtraukite 5 šeimos narius į jūsų esamą planą nemokėdami papildomai.\n\nKiekvienas narys gauna savo asmeninę vietą ir negali matyti vienas kito failų, nebent jie bendrinami.\n\nŠeimos planai pasiekiami klientams, kurie turi mokamą „Ente“ prenumeratą.\n\nPrenumeruokite dabar, kad pradėtumėte!", "androidBiometricHint": "Patvirtinkite tapatybę", "@androidBiometricHint": { "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." }, + "androidBiometricNotRecognized": "Neatpažinta. Bandykite dar kartą.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Sėkmė", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, "androidCancelButton": "Atšaukti", "@androidCancelButton": { "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." @@ -1096,6 +1162,8 @@ "@iOSOkButton": { "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." }, + "openstreetmapContributors": "„OpenStreetMap“ bendradarbiai", + "hostedAtOsmFrance": "Talpinama OSM Prancūzijoje", "map": "Žemėlapis", "@map": { "description": "Label for the map view" @@ -1103,15 +1171,32 @@ "maps": "Žemėlapiai", "enableMaps": "Įjungti žemėlapius", "enableMapsDesc": "Tai parodys jūsų nuotraukas pasaulio žemėlapyje.\n\nŠį žemėlapį talpina „OpenStreetMap“, o tiksliomis nuotraukų vietovėmis niekada nebendrinama.\n\nŠią funkciją bet kada galite išjungti iš nustatymų.", + "quickLinks": "Sparčios nuorodos", + "selectItemsToAdd": "Pasirinkite elementus įtraukti", + "addSelected": "Pridėti pasirinktus", + "addFromDevice": "Pridėti iš įrenginio", + "addPhotos": "Įtraukti nuotraukų", + "noPhotosFoundHere": "Nuotraukų čia nerasta", + "zoomOutToSeePhotos": "Padidinkite mastelį, kad matytumėte nuotraukas", "noImagesWithLocation": "Nėra vaizdų su vietove", "unpinAlbum": "Atsegti albumą", "pinAlbum": "Prisegti albumą", "create": "Kurti", "viewAll": "Peržiūrėti viską", "nothingSharedWithYouYet": "Kol kas su jumis niekuo nesibendrinama.", + "noAlbumsSharedByYouYet": "Dar nėra albumų, kuriais bendrinotės.", "sharedWithYou": "Bendrinta su jumis", "sharedByYou": "Bendrinta iš jūsų", + "inviteYourFriendsToEnte": "Pakvieskite savo draugus į „Ente“", + "failedToDownloadVideo": "Nepavyko atsisiųsti vaizdo įrašo.", "hiding": "Slepiama...", + "unhiding": "Rodoma...", + "successfullyHid": "Sėkmingai paslėptas", + "successfullyUnhid": "Sėkmingai atslėptas", + "crashReporting": "Pranešti apie strigčius", + "resumableUploads": "Tęstiniai įkėlimai", + "addToHiddenAlbum": "Įtraukti į paslėptą albumą", + "moveToHiddenAlbum": "Perkelti į paslėptą albumą", "fileTypes": "Failų tipai", "deleteConfirmDialogBody": "Ši paskyra susieta su kitomis „Ente“ programomis, jei jas naudojate. Jūsų įkelti duomenys per visas „Ente“ programas bus planuojama ištrinti, o jūsų paskyra bus ištrinta negrįžtamai.", "hearUsWhereTitle": "Kaip išgirdote apie „Ente“? (nebūtina)", @@ -1120,14 +1205,18 @@ "addOns": "Priedai", "addOnPageSubtitle": "Išsami informacija apie priedus", "yourMap": "Jūsų žemėlapis", + "modifyYourQueryOrTrySearchingFor": "Modifikuokite užklausą arba bandykite ieškoti", "blackFridaySale": "Juodojo penktadienio išpardavimas", "upto50OffUntil4thDec": "Iki 50% nuolaida, gruodžio 4 d.", "photos": "Nuotraukos", "videos": "Vaizdo įrašai", "livePhotos": "Gyvos nuotraukos", + "searchHint1": "Sparti paieška įrenginyje", + "searchHint2": "Nuotraukų datos ir aprašai", "searchHint3": "Albumai, failų pavadinimai ir tipai", "searchHint4": "Vietovė", "searchHint5": "Jau netrukus: veidų ir magiškos paieškos ✨", + "addYourPhotosNow": "Įtraukite savo nuotraukas dabar", "searchResultCount": "{count, plural, one{Rastas {count} rezultatas} other{Rasta {count} rezultatų}}", "@searchResultCount": { "description": "Text to tell user how many results were found for their search query", @@ -1140,6 +1229,7 @@ }, "faces": "Veidai", "people": "Asmenys", + "contents": "Turinys", "addNew": "Pridėti naują", "@addNew": { "description": "Text to add a new item (location tag, album, caption etc)" @@ -1147,7 +1237,9 @@ "contacts": "Kontaktai", "noInternetConnection": "Nėra interneto ryšio", "pleaseCheckYourInternetConnectionAndTryAgain": "Patikrinkite savo interneto ryšį ir bandykite dar kartą.", + "signOutFromOtherDevices": "Atsijungti iš kitų įrenginių", "signOutOtherBody": "Jei manote, kad kas nors gali žinoti jūsų slaptažodį, galite priverstinai atsijungti iš visų kitų įrenginių, naudojančių jūsų paskyrą.", + "signOutOtherDevices": "Atsijungti kitus įrenginius", "doNotSignOut": "Neatsijungti", "editLocation": "Redaguoti vietovę", "selectALocation": "Pasirinkite vietovę", @@ -1190,6 +1282,8 @@ "createCollaborativeLink": "Kurti bendradarbiavimo nuorodą", "search": "Ieškokite", "enterPersonName": "Įveskite asmens vardą", + "editEmailAlreadyLinked": "Šis el. paštas jau susietas su {name}.", + "viewPersonToUnlink": "Peržiūrėkite {name}, kad atsietumėte", "enterName": "Įveskite vardą", "savePerson": "Išsaugoti asmenį", "editPerson": "Redaguoti asmenį", @@ -1202,6 +1296,7 @@ "manualPairDesc": "Susieti su PIN kodu veikia bet kuriame ekrane, kuriame norite peržiūrėti albumą.", "connectToDevice": "Prijungti prie įrenginio", "autoCastDialogBody": "Čia matysite pasiekiamus perdavimo įrenginius.", + "autoCastiOSPermission": "Įsitikinkite, kad programai „Ente“ nuotraukos yra įjungti vietinio tinklo leidimai, nustatymuose.", "noDeviceFound": "Įrenginys nerastas", "stopCastingTitle": "Stabdyti perdavimą", "stopCastingBody": "Ar norite sustabdyti perdavimą?", @@ -1222,6 +1317,7 @@ "right": "Dešinė", "whatsNew": "Kas naujo", "reviewSuggestions": "Peržiūrėti pasiūlymus", + "review": "Peržiūrėti", "useAsCover": "Naudoti kaip viršelį", "notPersonLabel": "Ne {name}?", "@notPersonLabel": { @@ -1299,6 +1395,7 @@ "configuration": "Konfiguracija", "localIndexing": "Vietinis indeksavimas", "processed": "Apdorota", + "resetPerson": "Šalinti", "areYouSureYouWantToResetThisPerson": "Ar tikrai norite iš naujo nustatyti šį asmenį?", "allPersonGroupingWillReset": "Visi šio asmens grupavimai bus iš naujo nustatyti, o jūs neteksite visų šiam asmeniui pateiktų pasiūlymų", "yesResetPerson": "Taip, nustatyti asmenį iš naujo", @@ -1307,6 +1404,16 @@ "enableMachineLearningBanner": "Įjunkite mašininį mokymąsi magiškai paieškai ir veidų atpažinimui", "searchDiscoverEmptySection": "Vaizdai bus rodomi čia, kai bus užbaigtas apdorojimas ir sinchronizavimas.", "searchPersonsEmptySection": "Asmenys bus rodomi čia, kai bus užbaigtas apdorojimas ir sinchronizavimas.", + "viewersSuccessfullyAdded": "{count, plural, one {Įtrauktas {count} žiūrėtojas} few {Įtraukti {count} žiūrėtojai} many {Įtraukta {count} žiūrėtojo} =0 {Įtraukta 0 žiūrėtojų} =1 {Įtrauktas 1 žiūrėtojas} other {Įtraukta {count} žiūrėtojų}}", + "@viewersSuccessfullyAdded": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + }, + "description": "Number of viewers that were successfully added to an album." + }, "collaboratorsSuccessfullyAdded": "{count, plural, =0 {Pridėta 0 bendradarbių} =1 {Pridėtas 1 bendradarbis} other {Pridėta {count} bendradarbių}}", "@collaboratorsSuccessfullyAdded": { "placeholders": { @@ -1382,6 +1489,15 @@ }, "currentlyRunning": "šiuo metu vykdoma", "ignored": "ignoruota", + "photosCount": "{count, plural, one {{count} nuotrauka} few {{count} nuotraukos} many {{count} nuotraukos} =0 {0 nuotraukų} =1 {1 nuotrauka} other {{count} nuotraukų}}", + "@photosCount": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + } + }, "file": "Failas", "searchSectionsLengthMismatch": "Sekcijų ilgio neatitikimas: {snapshotLength} != {searchLength}", "@searchSectionsLengthMismatch": { @@ -1458,7 +1574,7 @@ "trustedInviteBody": "Buvote pakviesti tapti {email} palikimo kontaktu.", "warning": "Įspėjimas", "proceed": "Tęsti", - "confirmAddingTrustedContact": "Ketinate pridėti {email} kaip patikimą kontaktą. Jie galės atkurti jūsų paskyrą, jei jūsų nebus {numOfDays} dienų.", + "confirmAddingTrustedContact": "Ketinate įtraukti {email} kaip patikimą kontaktą. Jie galės atkurti jūsų paskyrą, jei jūsų nebus {numOfDays} dienų.", "@confirmAddingTrustedContact": { "placeholders": { "email": { @@ -1502,7 +1618,21 @@ } } }, + "reassignMe": "Perskirstyti „Aš“", "me": "Aš", + "linkEmailToContactBannerCaption": "spartesniam bendrinimui", + "@linkEmailToContactBannerCaption": { + "description": "Caption for the 'Link email' title. It should be a continuation of the 'Link email' title. Just like how 'Link email' + 'for faster sharing' forms a proper sentence in English, the combination of these two strings should also be a proper sentence in other languages." + }, + "selectPersonToLink": "Pasirinkite asmenį, kurį susieti.", + "linkPersonToEmail": "Susieti asmenį su {email}", + "@linkPersonToEmail": { + "placeholders": { + "email": { + "type": "String" + } + } + }, "linkPersonToEmailConfirmation": "Tai susies {personName} su {email}.", "@linkPersonToEmailConfirmation": { "description": "Confirmation message when linking a person to an email", @@ -1515,6 +1645,8 @@ } } }, + "selectYourFace": "Pasirinkite savo veidą", + "reassigningLoading": "Perskirstoma...", "reassignedToName": "Perskirstė jus į {name}", "@reassignedToName": { "placeholders": { @@ -1527,7 +1659,7 @@ "dontSave": "Neišsaugoti", "thisIsMeExclamation": "Tai aš!", "linkPerson": "Susiekite asmenį,", - "linkPersonCaption": "kad geriau bendrintumėte patirtį", + "linkPersonCaption": "geresniam bendrinimo patirčiai", "@linkPersonCaption": { "description": "Caption for the 'Link person' title. It should be a continuation of the 'Link person' title. Just like how 'Link person' + 'for better sharing experience' forms a proper sentence in English, the combination of these two strings should also be a proper sentence in other languages." }, @@ -1572,7 +1704,20 @@ "thisWeekXYearsAgo": "{count, plural, =1 {Šią savaitę, prieš {count} metus} other {Šią savaitę, prieš {count} metų}}", "youAndThem": "Jūs ir {name}", "admiringThem": "Žavisi {name}", + "embracingThem": "Apkabinat {name}", + "partyWithThem": "Vakarėlis su {name}", + "hikingWithThem": "Žygiavimas su {name}", + "feastingWithThem": "Vaišiavimas su {name}", + "selfiesWithThem": "Asmenukės su {name}", + "posingWithThem": "Pozavimas su {name}", "backgroundWithThem": "Gražūs vaizdai su {name}", + "sportsWithThem": "Sportai su {name}", + "roadtripWithThem": "Kelionė su {name}", + "spotlightOnYourself": "Dėmesys į save", + "spotlightOnThem": "Dėmesys {name}", + "personIsAge": "{name} yra {age} m.!", + "personTurningAge": "{name} netrukus sulauks {age} m.", + "lastTimeWithThem": "Paskutinį kartą su {name}", "tripToLocation": "Kelionė į {location}", "tripInYear": "Kelionė per {year}", "lastYearsTrip": "Pastarųjų metų kelionė", @@ -1584,16 +1729,11 @@ "moon": "Mėnulio šviesoje", "onTheRoad": "Vėl kelyje", "food": "Kulinarinis malonumas", - "cLIcon": "Nauja piktograma", - "cLIconDesc": "Pagaliau – nauja programos piktograma, kuri, mūsų manymu, geriausiai atspindi mūsų kūrybą. Taip pat pridėjome piktogramos perjungiklį, tad galite ir toliau naudoti senąją piktogramą.", - "cLMemories": "Prisiminimai", - "cLMemoriesDesc": "Iš naujo atraskite ypatingas akimirkas – atkreipkite dėmesį į mėgstamus asmenis, keliones ir atostogas, geriausias nuotraukas bei daug daugiau. Įjunkite mašininį mokymąsi, pažymėkite save ir įvardykite draugus dėl geriausios patirties.", - "cLWidgets": "Valdikliai", - "cLWidgetsDesc": "Dabar galima naudoti su prisiminimais integruotus pagrindinio ekrano valdiklius. Jie parodys jūsų ypatingas akimirkas neatvėrus programos.", - "cLFamilyPlan": "Šeimos plano ribos", - "cLFamilyPlanDesc": "Dabar galite nustatyti ribas, kiek saugyklos gali naudoti jūsų šeimos nariai.", - "cLBulkEdit": "Masiškai redaguokite datas", - "cLBulkEditDesc": "Dabar galite pasirinkti kelias nuotraukas ir vienu sparčiu veiksmu redaguoti visų nuotraukų datą ir laiką. Taip pat palaikomas datų perkėlimas.", + "pets": "Furio draugai", "curatedMemories": "Kuruoti prisiminimai", - "onThisDay": "Šią dieną" + "onThisDay": "Šią dieną", + "deleteMultipleAlbumDialog": "Taip pat ištrinti nuotraukas (ir vaizdo įrašus), esančias šiuose {count} albumuose, iš visų kitų albumų, kuriuose jos yra dalis?", + "addParticipants": "Įtraukti dalyvių", + "selectedAlbums": "{count} pasirinkta", + "actionNotSupportedOnFavouritesAlbum": "Veiksmas nepalaikomas Mėgstamų albume." } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_nl.arb b/mobile/lib/l10n/intl_nl.arb index 2afe314f56..8e313bfcb3 100644 --- a/mobile/lib/l10n/intl_nl.arb +++ b/mobile/lib/l10n/intl_nl.arb @@ -1730,16 +1730,6 @@ "onTheRoad": "Onderweg", "food": "Culinaire vreugde", "pets": "Harige kameraden", - "cLIcon": "Nieuw icoon", - "cLIconDesc": "Tot slot, een nieuwe app icoon waarvan we denken dat het ons werk het beste weergeeft. We hebben ook een icon-switcher toegevoegd zodat je het oude icoon kunt blijven gebruiken.", - "cLMemories": "Herinneringen", - "cLMemoriesDesc": "Ontdek nog eens je speciale momenten - spotlicht op je favoriete mensen, op je reizen en vakanties, op de beste plaatjes, en nog veel meer. Zet Machine Learning aan, tag jezelf en benoem je vrienden voor de beste ervaring.", - "cLWidgets": "Widgets", - "cLWidgetsDesc": "Widgets die zijn geïntegreerd met herinneringen zijn nu beschikbaar. Ze tonen je speciale momenten zonder de app te openen.", - "cLFamilyPlan": "Familieplan limieten", - "cLFamilyPlanDesc": "Je kunt nu limieten instellen voor hoeveel opslag je familieleden kunnen gebruiken.", - "cLBulkEdit": "Bulk datums wijzigen", - "cLBulkEditDesc": "Je kunt nu meerdere foto's selecteren en de datum/tijd van ze allemaal bewerken met één snelle actie. Verschuiven van datums wordt ook ondersteund.", "curatedMemories": "Samengestelde herinneringen", "onThisDay": "Op deze dag", "deleteMultipleAlbumDialog": "Verwijder de foto's (en video's) van deze {count} albums ook uit alle andere albums waar deze deel van uitmaken?", diff --git a/mobile/lib/l10n/intl_no.arb b/mobile/lib/l10n/intl_no.arb index f6d8a704c3..b5c93dab92 100644 --- a/mobile/lib/l10n/intl_no.arb +++ b/mobile/lib/l10n/intl_no.arb @@ -1689,15 +1689,5 @@ "moon": "I månelyset", "onTheRoad": "På veien igjen", "food": "Kulinær glede", - "pets": "Pelsvenner", - "cLIcon": "Nytt ikon", - "cLIconDesc": "Endelig er et nytt appikon, som vi tror best representerer arbeidet vårt. Vi har også lagt til en icon-switcher slik at du kan fortsette å bruke det gamle ikonet.", - "cLMemories": "Minner", - "cLMemoriesDesc": "Gjenoppdag dine spesielle øyeblikk - fremhev dine favorittpersoner, dine turer og ferier, de beste bildene dine, og mye mer. Skru på maskinlæring, merk deg selv og navngi vennene dine for best mulig opplevelse.", - "cLWidgets": "Widgeter", - "cLWidgetsDesc": "Hjemmeskjermwidgeter som er integrert med minner er nå tilgjengelige. De vil vise dine spesielle øyeblikk uten å åpne appen.", - "cLFamilyPlan": "Begrensninger for familieabonnement", - "cLFamilyPlanDesc": "Du kan nå sette grenser for hvor mye lagringsplass familiemedlemmer kan bruke.", - "cLBulkEdit": "Masseendring av datoer", - "cLBulkEditDesc": "Du kan nå velge flere bilder, og redigere dato/klokkeslett for alle med en rask handling. Forskyving av datoer støttes også." + "pets": "Pelsvenner" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_pt_BR.arb b/mobile/lib/l10n/intl_pt_BR.arb index a8754df05f..3de37e2ca5 100644 --- a/mobile/lib/l10n/intl_pt_BR.arb +++ b/mobile/lib/l10n/intl_pt_BR.arb @@ -1730,16 +1730,6 @@ "onTheRoad": "Na estrada novamente", "food": "Amor por culinária", "pets": "Companhias peludas", - "cLIcon": "Novo Ícone", - "cLIconDesc": "Finalmente, um novo ícone para o ente que acreditamos que represente melhor nosso trabalho. Também, adicionamos um alterador de ícone para que você ainda consiga utilizar o ícone antigo.", - "cLMemories": "Memórias", - "cLMemoriesDesc": "Relembre momentos especiais - destaque pessoas favoritas, suas viagens e feriados, melhores fotos, e muito mais. Ative o aprendizado automático, marque-se e nomeie seus amigos para melhorar a experiência.", - "cLWidgets": "Widgets", - "cLWidgetsDesc": "Widgets integrados com memórias já estão disponíveis. Eles apareceram com seus melhores momentos sem precisar abrir o ente.", - "cLFamilyPlan": "Limites de planos familiares", - "cLFamilyPlanDesc": "Agora você pode definir um limite de quanto armazenamento os seus entes queridos podem usar.", - "cLBulkEdit": "Editar todas as datas", - "cLBulkEditDesc": "Agora você pode selecionar várias fotos, editar data e hora de todos com um só clique. Alternar datas também são suportados.", "curatedMemories": "Memórias restauradas", "onThisDay": "Neste dia", "deleteMultipleAlbumDialog": "E também excluir todas as fotos (e vídeos) presente dentro desses {count} álbuns e de todos os álbuns que eles fazem parte?", diff --git a/mobile/lib/l10n/intl_ru.arb b/mobile/lib/l10n/intl_ru.arb index b58ff60291..98bcdac583 100644 --- a/mobile/lib/l10n/intl_ru.arb +++ b/mobile/lib/l10n/intl_ru.arb @@ -1724,15 +1724,5 @@ "onTheRoad": "Снова в пути", "food": "Кулинарное наслаждение", "pets": "Пушистые спутники", - "cLIcon": "Новая иконка", - "cLIconDesc": "Наконец-то новая иконка приложения, которая, как мы считаем, лучше всего отражает нашу работу. Мы также добавили переключатель иконок, чтобы вы могли продолжать использовать старую иконку.", - "cLMemories": "Воспоминания", - "cLMemoriesDesc": "Откройте заново свои особенные моменты — в центре внимания ваши любимые люди, поездки и праздники, лучшие снимки и многое другое. Для наилучших впечатлений включите машинное обучение и отметьте себя и своих друзей.", - "cLWidgets": "Виджеты", - "cLWidgetsDesc": "Теперь доступны виджеты домашнего экрана, интегрированные с воспоминаниями. Они покажут ваши особенные моменты, не открывая приложения.", - "cLFamilyPlan": "Ограничения семейного тарифа", - "cLFamilyPlanDesc": "Теперь вы можете установить ограничения на объём хранилища, которое могут использовать члены вашей семьи.", - "cLBulkEdit": "Массовое редактирование дат", - "cLBulkEditDesc": "Теперь вы можете выбрать несколько фото и отредактировать дату/время быстро и сразу для всех. Также поддерживается смещение дат.", "curatedMemories": "Отобранные воспоминания" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_sr.arb b/mobile/lib/l10n/intl_sr.arb new file mode 100644 index 0000000000..c8494661c6 --- /dev/null +++ b/mobile/lib/l10n/intl_sr.arb @@ -0,0 +1,3 @@ +{ + "@@locale ": "en" +} \ No newline at end of file diff --git a/mobile/lib/l10n/intl_tr.arb b/mobile/lib/l10n/intl_tr.arb index c3ae396543..6aa4d6a317 100644 --- a/mobile/lib/l10n/intl_tr.arb +++ b/mobile/lib/l10n/intl_tr.arb @@ -1,6 +1,7 @@ { "@@locale ": "en", "enterYourEmailAddress": "E-posta adresinizi girin", + "enterYourNewEmailAddress": "Yeni e-posta adresinizi girin", "accountWelcomeBack": "Tekrar hoş geldiniz!", "emailAlreadyRegistered": "Bu e-posta adresi zaten kayıtlı.", "emailNotRegistered": "Bu e-posta adresi sistemde kayıtlı değil.", @@ -340,7 +341,7 @@ "deleteSharedAlbumDialogBody": "Albüm herkes için silinecek\n\nBu albümdeki başkalarına ait paylaşılan fotoğraflara erişiminizi kaybedeceksiniz", "yesRemove": "Evet, sil", "creatingLink": "Bağlantı oluşturuluyor...", - "removeWithQuestionMark": "Kaldır?", + "removeWithQuestionMark": "Kaldırılsın mı?", "removeParticipantBody": "{userEmail} bu paylaşılan albümden kaldırılacaktır\n\nOnlar tarafından eklenen tüm fotoğraflar da albümden kaldırılacaktır", "keepPhotos": "Fotoğrafları sakla", "deletePhotos": "Fotoğrafları sil", @@ -655,7 +656,7 @@ "askCancelReason": "Aboneliğiniz iptal edilmiştir. Bunun sebebini paylaşmak ister misiniz?", "thankYouForSubscribing": "Abone olduğunuz için teşekkürler!", "yourPurchaseWasSuccessful": "Satın alım başarılı", - "yourPlanWasSuccessfullyUpgraded": "Planınız başarılı şekilde yükseltildi", + "yourPlanWasSuccessfullyUpgraded": "Planınız başarıyla yükseltildi", "yourPlanWasSuccessfullyDowngraded": "Planınız başarıyla düşürüldü", "yourSubscriptionWasUpdatedSuccessfully": "Aboneliğiniz başarıyla güncellendi", "googlePlayId": "Google Play ID", @@ -700,7 +701,7 @@ "selectMorePhotos": "Daha Fazla Fotoğraf Seç", "existingUser": "Mevcut kullanıcı", "privateBackups": "Özel yedeklemeler", - "forYourMemories": "anıların için", + "forYourMemories": "anılarınız için", "endtoendEncryptedByDefault": "Varsayılan olarak uçtan uca şifrelenmiş", "safelyStored": "Güvenle saklanır", "atAFalloutShelter": "serpinti sığınağında", @@ -1729,15 +1730,10 @@ "onTheRoad": "Yeniden yollarda", "food": "Yemek keyfi", "pets": "Tüylü dostlar", - "cLIcon": "Yeni Simge", - "cLIconDesc": "Son olarak, çalışmalarımızı en iyi şekilde temsil ettiğini düşündüğümüz yeni bir uygulama simgesi. Eski simgeyi kullanmaya devam edebilmeniz için bir simge değiştirici de ekledik.", - "cLMemories": "Anılar", - "cLMemoriesDesc": "Özel anlarınızı yeniden keşfedin - en sevdiğiniz kişilere, seyahatlerinize ve tatillerinize, en iyi tıklamalarınıza ve çok daha fazlasına odaklanın. En iyi deneyim için makine öğrenimini açın, kendinizi etiketleyin ve arkadaşlarınızı adlandırın.", - "cLWidgets": "Widget'lar", - "cLWidgetsDesc": "Anılarla entegre edilmiş ana ekran widget'ları artık kullanılabilir. Uygulamayı açmadan özel anlarınızı gösterir.", - "cLFamilyPlan": "Aile Planı Sınırları", - "cLFamilyPlanDesc": "Artık aile üyelerinizin ne kadar depolama alanı kullanabileceğine dair sınırlar belirleyebilirsiniz.", - "cLBulkEdit": "Tarihleri toplu düzenle", - "cLBulkEditDesc": "Artık birden fazla fotoğraf seçebilir ve tek bir hızlı işlemle hepsi için tarih/saat düzenleyebilirsiniz. Tarih kaydırma da desteklenmektedir.", - "curatedMemories": "Seçilmiş anılar" + "curatedMemories": "Seçilmiş anılar", + "onThisDay": "Bu günde", + "deleteMultipleAlbumDialog": "Ayrıca bu {count} albümde bulunan fotoğrafları (ve videoları) parçası oldukları tüm diğer albümlerden silmek istiyor musunuz?", + "addParticipants": "Katılımcı ekle", + "selectedAlbums": "{count} seçildi", + "actionNotSupportedOnFavouritesAlbum": "Favoriler albümünde eylem desteklenmiyor" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_zh.arb b/mobile/lib/l10n/intl_zh.arb index b2b4649b1e..650f26d333 100644 --- a/mobile/lib/l10n/intl_zh.arb +++ b/mobile/lib/l10n/intl_zh.arb @@ -1723,15 +1723,5 @@ "moon": "月光之下", "onTheRoad": "再次踏上旅途", "food": "美食盛宴", - "pets": "毛茸茸的伙伴", - "cLIcon": "新图标", - "cLIconDesc": "终于迎来了一个全新的应用图标,我们认为它最能代表我们的作品。同时,我们还添加了图标切换功能,所以您可以继续使用旧图标。", - "cLMemories": "回忆", - "cLMemoriesDesc": "重新发现你的珍贵时刻——聚焦你最爱的亲友、旅行与假期、美妙瞬间等精彩回忆。启用机器学习,标记自己并为朋友命名,享受最佳体验。", - "cLWidgets": "小组件", - "cLWidgetsDesc": "全新首页小组件,与回忆深度集成。无需打开应用,即可在主屏幕上查看你的特别时刻。", - "cLFamilyPlan": "家庭计划存储限制", - "cLFamilyPlanDesc": "你现在可以为家庭成员设置存储空间使用上限。", - "cLBulkEdit": "批量编辑日期", - "cLBulkEditDesc": "你现在可以选择多张照片,一键批量修改日期/时间,并支持日期顺移。" + "pets": "毛茸茸的伙伴" } \ No newline at end of file From 9d87b8f3038d6bc03714341139e9bb97312d8328 Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Mon, 2 Jun 2025 11:43:21 +0530 Subject: [PATCH 002/194] Rename to clarify Android --- .../machine_learning/machine_learning_controller.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mobile/lib/services/machine_learning/machine_learning_controller.dart b/mobile/lib/services/machine_learning/machine_learning_controller.dart index 43b24dc9a5..6db75a232d 100644 --- a/mobile/lib/services/machine_learning/machine_learning_controller.dart +++ b/mobile/lib/services/machine_learning/machine_learning_controller.dart @@ -12,7 +12,7 @@ import "package:photos/events/machine_learning_control_event.dart"; class MachineLearningController { final _logger = Logger("MachineLearningController"); - static const kMaximumTemperature = 42; // 42 degree celsius + static const kMaximumTemperatureAndroid = 42; // 42 degree celsius static const kMinimumBatteryLevel = 20; // 20% final kDefaultInteractionTimeout = Duration(seconds: Platform.isIOS ? 5 : 15); static const kUnhealthyStates = ["over_heat", "over_voltage", "dead"]; @@ -109,7 +109,9 @@ class MachineLearningController { bool _computeIsAndroidDeviceHealthy(AndroidBatteryInfo info) { return _hasSufficientBattery(info.batteryLevel ?? kMinimumBatteryLevel) && - _isAcceptableTemperature(info.temperature ?? kMaximumTemperature) && + _isAcceptableTemperatureAndroid( + info.temperature ?? kMaximumTemperatureAndroid, + ) && _isBatteryHealthy(info.health ?? ""); } @@ -121,8 +123,8 @@ class MachineLearningController { return batteryLevel >= kMinimumBatteryLevel; } - bool _isAcceptableTemperature(int temperature) { - return temperature <= kMaximumTemperature; + bool _isAcceptableTemperatureAndroid(int temperature) { + return temperature <= kMaximumTemperatureAndroid; } bool _isBatteryHealthy(String health) { From b0bb5fc916da2be688943004d75cc48a4e423619 Mon Sep 17 00:00:00 2001 From: Keerthana Date: Mon, 2 Jun 2025 13:58:49 +0530 Subject: [PATCH 003/194] [docs]: lint and refactor navigation and structure for overview --- docs/docs/.vitepress/config.ts | 10 +- docs/docs/.vitepress/sidebar.ts | 33 ++-- .../docs/auth/migration-guides/authy/index.md | 24 ++- docs/docs/overview/community.md | 33 ++++ docs/docs/{about => overview}/contribute.md | 0 docs/docs/{about => overview}/ducky.png | Bin docs/docs/{about => overview}/index.md | 30 +--- docs/docs/photos/faq/desktop.md | 11 +- docs/docs/photos/faq/export.md | 7 +- docs/docs/photos/faq/face-recognition.md | 3 +- docs/docs/photos/faq/general.md | 43 ++++-- docs/docs/photos/faq/metadata.md | 1 + docs/docs/photos/faq/video-streaming.md | 4 +- docs/docs/photos/features/background.md | 4 +- docs/docs/photos/features/family-plans.md | 18 +-- docs/docs/self-hosting/creating-accounts.md | 4 +- docs/docs/self-hosting/faq/environment.md | 39 ++--- docs/docs/self-hosting/guides/Tailscale.md | 146 +++++++++++++----- docs/docs/self-hosting/guides/admin.md | 11 +- .../self-hosting/guides/configuring-s3.md | 27 ++-- .../guides/custom-server/index.md | 4 +- docs/docs/self-hosting/guides/from-source.md | 32 ++-- docs/docs/self-hosting/guides/web-app.md | 32 ++-- docs/docs/self-hosting/museum.md | 28 ++-- docs/docs/self-hosting/reverse-proxy.md | 17 +- .../troubleshooting/bucket-cors.md | 16 +- .../self-hosting/troubleshooting/docker.md | 43 +++--- .../self-hosting/troubleshooting/keyring.md | 4 +- .../docs/self-hosting/troubleshooting/misc.md | 10 +- .../self-hosting/troubleshooting/uploads.md | 37 ++--- .../docs/self-hosting/troubleshooting/yarn.md | 4 +- 31 files changed, 407 insertions(+), 268 deletions(-) create mode 100644 docs/docs/overview/community.md rename docs/docs/{about => overview}/contribute.md (100%) rename docs/docs/{about => overview}/ducky.png (100%) rename docs/docs/{about => overview}/index.md (61%) diff --git a/docs/docs/.vitepress/config.ts b/docs/docs/.vitepress/config.ts index a2df3c23a1..10632e4c49 100644 --- a/docs/docs/.vitepress/config.ts +++ b/docs/docs/.vitepress/config.ts @@ -9,11 +9,11 @@ export default defineConfig({ cleanUrls: true, ignoreDeadLinks: "localhostLinks", vite: { - build: { - rollupOptions: { - external: ['client-museum-s3.png'] // Added to handle static asset import - } - } + build: { + rollupOptions: { + external: ["client-museum-s3.png"], // Added to handle static asset import + }, + }, }, themeConfig: { // We use the default theme (with some CSS color overrides). This diff --git a/docs/docs/.vitepress/sidebar.ts b/docs/docs/.vitepress/sidebar.ts index 0a81c8efc6..448541cc2e 100644 --- a/docs/docs/.vitepress/sidebar.ts +++ b/docs/docs/.vitepress/sidebar.ts @@ -2,6 +2,23 @@ // appropriate place here. export const sidebar = [ + { + text: "Overview", + items: [ + { + text: "Introduction", + link: "/overview/", + }, + { + text: "Contributing", + link: "/overview/contribute", + }, + { + text: "Help", + link: "/overview/community", + }, + ], + }, { text: "Photos", items: [ @@ -292,7 +309,7 @@ export const sidebar = [ }, { text: "Bucket CORS", - link: '/self-hosting/troubleshooting/bucket-cors' + link: "/self-hosting/troubleshooting/bucket-cors", }, { text: "Uploads", @@ -311,7 +328,7 @@ export const sidebar = [ { text: "Community Guides", collapsed: true, - items :[ + items: [ { text: "Ente via Tailscale", link: "/self-hosting/guides/Tailscale", @@ -319,8 +336,8 @@ export const sidebar = [ { text: "Ente with External S3", link: "/self-hosting/guides/external-s3", - } - ] + }, + ], }, { text: "FAQ", @@ -347,12 +364,4 @@ export const sidebar = [ }, ], }, - { - text: "About", - link: "/about/", - }, - { - text: "Contribute", - link: "/about/contribute", - }, ]; diff --git a/docs/docs/auth/migration-guides/authy/index.md b/docs/docs/auth/migration-guides/authy/index.md index 27baa97ab1..724fbdc1d2 100644 --- a/docs/docs/auth/migration-guides/authy/index.md +++ b/docs/docs/auth/migration-guides/authy/index.md @@ -10,8 +10,9 @@ A guide written by Green, an ente.io lover > [!WARNING] > > Authy has dropped all support for its desktop apps. It is no longer possible -> to export data from Authy using methods 1 and 2. You will need either an iOS device -> and computer (method 4) or a rooted Android phone (method 3) to follow this guide. +> to export data from Authy using methods 1 and 2. You will need either an iOS +> device and computer (method 4) or a rooted Android phone (method 3) to follow +> this guide. --- @@ -204,11 +205,24 @@ This uses the tool [Aegis Authenticator](https://getaegis.app/) from ## Method 4: Authy-iOS-MiTM -**Who should use this?** Technical iOS users of Authy that cannot export their tokens with methods 1 or 2 (due to those methods being patched) or method 3 (due to that method requiring a rooted Android device). +**Who should use this?** Technical iOS users of Authy that cannot export their +tokens with methods 1 or 2 (due to those methods being patched) or method 3 (due +to that method requiring a rooted Android device). -This method works by intercepting the data the Authy app receives while logging in for the first time, which contains your encrypted authenticator tokens. After the encrypted authenticator tokens are dumped, you can decrypt them using your backup password and convert them to an Ente token file. +This method works by intercepting the data the Authy app receives while logging +in for the first time, which contains your encrypted authenticator tokens. After +the encrypted authenticator tokens are dumped, you can decrypt them using your +backup password and convert them to an Ente token file. -For an up-to-date guide of how to retrieve the encrypted authenticator tokens and decrypt them, please see [Authy-iOS-MiTM](https://github.com/AlexTech01/Authy-iOS-MiTM). To convert the `decrypted_tokens.json` file from that guide into a format Ente Authenticator can recognize, use [this](https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93?permalink_comment_id=5317087#gistcomment-5317087) Python script. Once you have the `ente_auth_import.plain` file from that script, transfer it to your device and follow the instructions below to import it into Ente Authenticator. +For an up-to-date guide of how to retrieve the encrypted authenticator tokens +and decrypt them, please see +[Authy-iOS-MiTM](https://github.com/AlexTech01/Authy-iOS-MiTM). To convert the +`decrypted_tokens.json` file from that guide into a format Ente Authenticator +can recognize, use +[this](https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93?permalink_comment_id=5317087#gistcomment-5317087) +Python script. Once you have the `ente_auth_import.plain` file from that script, +transfer it to your device and follow the instructions below to import it into +Ente Authenticator. ## Importing to Ente Authenticator (Method 1, method 2.1, method 4) diff --git a/docs/docs/overview/community.md b/docs/docs/overview/community.md new file mode 100644 index 0000000000..806aa45d6a --- /dev/null +++ b/docs/docs/overview/community.md @@ -0,0 +1,33 @@ +--- +title: Get in touch +description: > + Get in touch with Ente for support, stay updated, suggest new features and + join the community +--- + +# Get in touch + +## Support + +If you have a support query that is not answered by these docs, please reach out +to our Customer Support by sending an email to +[support@ente.io](mailto:support@ente.io) + +## Blog + +To stay up to date with new product launches, and behind the scenes details of +how we're building Ente, you can read our [blog](https://ente.io/blog) (or +subscribe to it via [RSS](https://ente.io/blog/rss.xml)) + +## Suggest a feature + +To suggest new features and/or offer your perspective on how we should design +planned and upcoming features, use our +[GitHub discussions](https://github.com/ente-io/ente/discussions) + +## Community + +Or if you'd just like to hang out, join our +[Discord](https://discord.gg/z2YVKkycX3), follow us on +[Twitter](https://twitter.com/enteio) or give us a shout out on +[Mastodon](https://mstdn.social/@ente) diff --git a/docs/docs/about/contribute.md b/docs/docs/overview/contribute.md similarity index 100% rename from docs/docs/about/contribute.md rename to docs/docs/overview/contribute.md diff --git a/docs/docs/about/ducky.png b/docs/docs/overview/ducky.png similarity index 100% rename from docs/docs/about/ducky.png rename to docs/docs/overview/ducky.png diff --git a/docs/docs/about/index.md b/docs/docs/overview/index.md similarity index 61% rename from docs/docs/about/index.md rename to docs/docs/overview/index.md index 18455f6b5a..3ba1aa3193 100644 --- a/docs/docs/about/index.md +++ b/docs/docs/overview/index.md @@ -1,5 +1,5 @@ --- -title: About Ente +title: Introduction description: > An overview of Ente: the company, and the people behind it, and the products that we make. @@ -7,9 +7,9 @@ description: > # About -Ente is a end-to-end encrypted platform for privately, reliably, and securely -storing your data on the cloud. On top of this platform, Ente offers two -products: +Ente (pronounced en-_tay_. Like cafe) is a end-to-end encrypted platform +for privately, reliably, and securely storing your data on the cloud. On top of +this platform, Ente offers two products: - **Ente Photos** - An alternative to Google Photos and Apple Photos @@ -48,25 +48,3 @@ the name, and also led to the adoption of "Ducky", Ente's mascot: For the full origin story of Ducky you can check out [this blog post](https://ente.io/blog/ducky/). - -### How do I pronounce Ente? - -en-_tay_. Like cafe. - -## Get in touch - -If you have a support query that is not answered by these docs, please reach out -to our Customer Support by sending an email to support@ente.io - -To stay up to date with new product launches, and behind the scenes details of -how we're building Ente, you can read our [blog](https://ente.io/blog) (or -subscribe to it via [RSS](https://ente.io/blog/rss.xml)) - -To suggest new features and/or offer your perspective on how we should design -planned and upcoming features, use our -[GitHub discussions](https://github.com/ente-io/ente/discussions) - -Or if you'd just like to hang out, join our -[Discord](https://discord.gg/z2YVKkycX3), follow us on -[Twitter](https://twitter.com/enteio) or give us a shout out on -[Mastodon](https://mstdn.social/@ente) diff --git a/docs/docs/photos/faq/desktop.md b/docs/docs/photos/faq/desktop.md index 71a575bdfb..c0ef805584 100644 --- a/docs/docs/photos/faq/desktop.md +++ b/docs/docs/photos/faq/desktop.md @@ -1,6 +1,7 @@ --- title: Desktop app FAQ -description: An assortment of frequently asked questions about Ente Photos desktop app +description: + An assortment of frequently asked questions about Ente Photos desktop app --- # Desktop app FAQ @@ -15,7 +16,8 @@ to manually update the software. ### Upload errors -**How do I identify which files experienced upload issues within the desktop app?** +**How do I identify which files experienced upload issues within the desktop +app?** Check the sections within the upload progress bar for "Failed Uploads," "Ignored Uploads," and "Unsuccessful Uploads." @@ -33,6 +35,5 @@ be specific to your distro (e.g. `xdg-desktop-menu forceupdate`). > [!NOTE] > -> If you're using an AppImage and not seeing the icon, you'll need to [enable -> AppImage desktop -> integration](/photos/troubleshooting/desktop-install/#appimage-desktop-integration). +> If you're using an AppImage and not seeing the icon, you'll need to +> [enable AppImage desktop integration](/photos/troubleshooting/desktop-install/#appimage-desktop-integration). diff --git a/docs/docs/photos/faq/export.md b/docs/docs/photos/faq/export.md index e6ed64b4fa..5f37640f83 100644 --- a/docs/docs/photos/faq/export.md +++ b/docs/docs/photos/faq/export.md @@ -7,9 +7,10 @@ description: Frequently asked questions about keeping extra backups of your data ## How can I backup my data in a local drive outside Ente? -You can use our CLI tool or our desktop app to set up exports of your data -to your local drive. This way, you can use Ente in your day to day use, with an additional guarantee that a copy of your original photos and videos are -always available on your machine. +You can use our CLI tool or our desktop app to set up exports of your data to +your local drive. This way, you can use Ente in your day to day use, with an +additional guarantee that a copy of your original photos and videos are always +available on your machine. - You can use [Ente's CLI](https://github.com/ente-io/ente/tree/main/cli#export) to export your data in a cron job to a location of your choice. The exports diff --git a/docs/docs/photos/faq/face-recognition.md b/docs/docs/photos/faq/face-recognition.md index 24c9650345..8559bf310d 100644 --- a/docs/docs/photos/faq/face-recognition.md +++ b/docs/docs/photos/faq/face-recognition.md @@ -1,7 +1,6 @@ --- title: Face recognition -description: - Frequently asked questions about Ente's face recognition +description: Frequently asked questions about Ente's face recognition --- # Face recognition diff --git a/docs/docs/photos/faq/general.md b/docs/docs/photos/faq/general.md index aaf1451efd..4204d6ccf8 100644 --- a/docs/docs/photos/faq/general.md +++ b/docs/docs/photos/faq/general.md @@ -26,7 +26,6 @@ unsupported file format and we will do our best to help you out. Yes, we currently do not support files larger than 4 GB. - ## Does Ente support videos? Ente supports backing up and downloading of videos in their original format and @@ -101,29 +100,51 @@ clicking on "Your map" under "Locations" on the search screen. ## How to reset my password if I lost it? -On the login page, enter your email and click on Forgot Password. Then, enter your recovery key and create a new password. +On the login page, enter your email and click on Forgot Password. Then, enter +your recovery key and create a new password. - # iOS Album Backup and Organization in Ente +# iOS Album Backup and Organization in Ente - ### How does Ente handle photos that are part of multiple iOS albums? -When you select multiple albums for backup, Ente prioritizes uploading each photo to the album with the fewest photos. This means a photo will only be uploaded once, even if it exists in multiple albums on your device. If you create new albums on your device after the initial backup, those photos may not appear in the corresponding Ente album if they were already uploaded to a different album. +### How does Ente handle photos that are part of multiple iOS albums? +When you select multiple albums for backup, Ente prioritizes uploading each +photo to the album with the fewest photos. This means a photo will only be +uploaded once, even if it exists in multiple albums on your device. If you +create new albums on your device after the initial backup, those photos may not +appear in the corresponding Ente album if they were already uploaded to a +different album. ### Why don’t all photos from a new iOS album appear in the corresponding Ente album? -If you create a new album on your device after the initial backup, the photos in that album may have already been uploaded to another album in Ente. To fix this, go to the "On Device" album in Ente, select all photos, and manually add them to the corresponding album in Ente. + +If you create a new album on your device after the initial backup, the photos in +that album may have already been uploaded to another album in Ente. To fix this, +go to the "On Device" album in Ente, select all photos, and manually add them to +the corresponding album in Ente. ### What happens if I reorganize my photos in the iOS Photos app after backing up? -Reorganizing photos in the iOS Photos app (e.g., moving photos to new albums) won’t automatically reflect in Ente. You’ll need to manually add those photos to the corresponding albums in Ente to maintain consistency. + +Reorganizing photos in the iOS Photos app (e.g., moving photos to new albums) +won’t automatically reflect in Ente. You’ll need to manually add those photos to +the corresponding albums in Ente to maintain consistency. ### Can I search for photos using the descriptions I’ve added? -Yes, descriptions are searchable, making it easier to find specific photos later. -To do this, open the photo, tap the (i) button, and enter your description. + +Yes, descriptions are searchable, making it easier to find specific photos +later. To do this, open the photo, tap the (i) button, and enter your +description. ### How does the deduplication feature work on the desktop app? -If the app finds exact duplicates, it will show them in the deduplication. When you delete a duplicate, the app keeps one copy and creates a symlink for the other duplicate. This helps save storage space. + +If the app finds exact duplicates, it will show them in the deduplication. When +you delete a duplicate, the app keeps one copy and creates a symlink for the +other duplicate. This helps save storage space. ### What happens if I lose access to my email address? Can I use my recovery key to bypass email verification? -No, the recovery key does not bypass email verification. For security reasons, we do not disable or bypass email verification unless the account owner reaches out to us and successfully verifies their identity by providing details about their account. + +No, the recovery key does not bypass email verification. For security reasons, +we do not disable or bypass email verification unless the account owner reaches +out to us and successfully verifies their identity by providing details about +their account. If you lose access to your email, please contact our support team at support@ente.io diff --git a/docs/docs/photos/faq/metadata.md b/docs/docs/photos/faq/metadata.md index 03cf10da65..c7e122301b 100644 --- a/docs/docs/photos/faq/metadata.md +++ b/docs/docs/photos/faq/metadata.md @@ -62,6 +62,7 @@ the upload time as the photo's creation time. ## Modifications Ente supports modifications to the following metadata: + - File name - Date & time - Location diff --git a/docs/docs/photos/faq/video-streaming.md b/docs/docs/photos/faq/video-streaming.md index 3d07223a14..62d9764dbb 100644 --- a/docs/docs/photos/faq/video-streaming.md +++ b/docs/docs/photos/faq/video-streaming.md @@ -1,7 +1,6 @@ --- title: Video streaming FAQ -description: - Frequently asked questions about Ente's video streaming feature +description: Frequently asked questions about Ente's video streaming feature --- # Video streaming @@ -78,6 +77,7 @@ generated stream. While this feature is in beta, we will not count the storage consumed by your streams against your storage quota. This may change in the future. If it does, we will provide an option to opt-in to one of the following: + 1. Original videos only 2. Compressed streams only 3. Both diff --git a/docs/docs/photos/features/background.md b/docs/docs/photos/features/background.md index ffed413f74..2decf9cb65 100644 --- a/docs/docs/photos/features/background.md +++ b/docs/docs/photos/features/background.md @@ -43,8 +43,8 @@ need to disable this "Optimize battery usage" mode in the system settings for Ente if you wish for Ente to automatically back up your photos in the background. -On Android versions 15 and later, if an app is in private space and the private -space is locked, Android doesn’t allow the app to run any background processes. +On Android versions 15 and later, if an app is in private space and the private +space is locked, Android doesn’t allow the app to run any background processes. As a result, background sync will not work. ### Desktop diff --git a/docs/docs/photos/features/family-plans.md b/docs/docs/photos/features/family-plans.md index 711b9e3094..cc5ee52aff 100644 --- a/docs/docs/photos/features/family-plans.md +++ b/docs/docs/photos/features/family-plans.md @@ -24,19 +24,19 @@ In brief, ## Storage Limits -If you're an admin of a family, you will be able to set storage limits for the +If you're an admin of a family, you will be able to set storage limits for the members in your family plan. -In brief, +In brief, -- For example, once you set a limit of 10GB for a member, their Storage - quota for uploading photos will be limited to 10GB. +- For example, once you set a limit of 10GB for a member, their Storage quota + for uploading photos will be limited to 10GB. -- Once the invited member accepts the Family invite, you will be able to see - an edit icon in the Members List. Click on it to setup a family limit. +- Once the invited member accepts the Family invite, you will be able to see an + edit icon in the Members List. Click on it to setup a family limit. - If the admin has set a limit for any user, that limit value will be prefilled - in the input box. + in the input box. -- If you want to remove any storage limit from a members account, you - can click on the "Remove Limit" and they can upload photos without any limit. +- If you want to remove any storage limit from a members account, you can click + on the "Remove Limit" and they can upload photos without any limit. diff --git a/docs/docs/self-hosting/creating-accounts.md b/docs/docs/self-hosting/creating-accounts.md index 5c3a53d3a3..550409106e 100644 --- a/docs/docs/self-hosting/creating-accounts.md +++ b/docs/docs/self-hosting/creating-accounts.md @@ -3,7 +3,7 @@ title: Creating accounts description: Creating accounts on your deployment --- -# Creating accounts +# Creating accounts Once Ente is up and running, the Ente Photos web app will be accessible on `http://localhost:3000`. Open this URL in your browser and proceed with creating @@ -20,7 +20,7 @@ This code can be found in the server logs, which should already be shown in your quickstart terminal. Alternatively, you can open the server logs with the following command from inside the `my-ente` folder: -```sh +```sh sudo docker compose logs ``` diff --git a/docs/docs/self-hosting/faq/environment.md b/docs/docs/self-hosting/faq/environment.md index b086c07b3f..a8dcf18d06 100644 --- a/docs/docs/self-hosting/faq/environment.md +++ b/docs/docs/self-hosting/faq/environment.md @@ -1,10 +1,14 @@ --- title: "Environment Variables and Ports" -description: "Information about all the Environment Variables needed to run Ente" +description: + "Information about all the Environment Variables needed to run Ente" --- # Environment variables and ports -A self-hosted Ente instance requires specific endpoints in both Museum (the server) and web apps. This document outlines the essential environment variables and port mappings of the web apps. + +A self-hosted Ente instance requires specific endpoints in both Museum (the +server) and web apps. This document outlines the essential environment variables +and port mappings of the web apps. Here's the list of important variables that a self hoster should know about: @@ -12,34 +16,33 @@ Here's the list of important variables that a self hoster should know about: 1. `NEXT_PUBLIC_ENTE_ENDPOINT` -The above environment variable is used to configure Museums endpoint. Where Museum is -running and which port it is listening on. This endpoint should be configured for -all the apps to connect to your self hosted endpoint. +The above environment variable is used to configure Museums endpoint. Where +Museum is running and which port it is listening on. This endpoint should be +configured for all the apps to connect to your self hosted endpoint. -All the apps (regardless of platform) by default connect to api.ente.io - which is -our production instance of Museum. +All the apps (regardless of platform) by default connect to api.ente.io - which +is our production instance of Museum. ### Web Apps -> [!IMPORTANT] -> Web apps don't need to be configured with the below endpoints. Web app environment -> variables are being documented here just so that the users know everything in detail. -> Checkout [Configuring your Server](/self-hosting/museum) to configure endpoints for +> [!IMPORTANT] Web apps don't need to be configured with the below endpoints. +> Web app environment variables are being documented here just so that the users +> know everything in detail. Checkout +> [Configuring your Server](/self-hosting/museum) to configure endpoints for > particular app. -In Ente, all the web apps are separate NextJS applications. Therefore, they are all -configured via environment variables. The photos app (Ente Photos) has information -about and connects to other web apps like albums, cast, etc. - +In Ente, all the web apps are separate NextJS applications. Therefore, they are +all configured via environment variables. The photos app (Ente Photos) has +information about and connects to other web apps like albums, cast, etc. 1. `NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT` -This environment variable is used to configure and declare the endpoint for the Albums -web app. +This environment variable is used to configure and declare the endpoint for the +Albums web app. ## Ports -The below format is according to how ports are mapped in Docker. +The below format is according to how ports are mapped in Docker. Typically,`:` 1. `8080:8080`: Museum (Ente's server) diff --git a/docs/docs/self-hosting/guides/Tailscale.md b/docs/docs/self-hosting/guides/Tailscale.md index 1f0a7593ed..ba31ac5f8f 100644 --- a/docs/docs/self-hosting/guides/Tailscale.md +++ b/docs/docs/self-hosting/guides/Tailscale.md @@ -2,39 +2,63 @@ title: Self Hosting with Tailscale (Community) description: Guides for self-hosting Ente Photos and/or Ente Auth with Tailscale --- + # Guide -This guide aims to achieve self-hosting Ente photos or Ente-Auth with tailscale (TSDPROXY) without exposing any port OR if someone is behind CGNAT and cannot open any port on the internet but want to run their own selfhosted service for themselves, friends and family only. +This guide aims to achieve self-hosting Ente photos or Ente-Auth with tailscale +(TSDPROXY) without exposing any port OR if someone is behind CGNAT and cannot +open any port on the internet but want to run their own selfhosted service for +themselves, friends and family only. Before getting start keep the following NOTE in mind. -> [!NOTE] -> If someone is behind double or triple CGNAT; must install tailscale system wide by running `curl -fsSL https://tailscale.com/install.sh | sh` in your linux terminal and `sudo tailscale up` otherwise dns resolver will fail and uploading will not work. This is not necessary for those who are not behing CGNAT. -> This guide also work on docker rootless and normal. +> [!NOTE] If someone is behind double or triple CGNAT; must install tailscale +> system wide by running `curl -fsSL https://tailscale.com/install.sh | sh` in +> your linux terminal and `sudo tailscale up` otherwise dns resolver will fail +> and uploading will not work. This is not necessary for those who are not +> behing CGNAT. This guide also work on docker rootless and normal. -> [!CAUTION] -Remember that current docker update 28.0.0 has some bug and cannot connect to external network. Make sure to install docker-ce 27.5.0, docker-ce-rootless-extras 27.5.0 and docker-ce-cli 27.5.0. Hopefully docker 28.1.0 will resolve this issue in next week. Refrence links are [Moby Github Repo Issues 49511](https://github.com/moby/moby/issues/49511) and [Moby Github Repo Issues 49519](https://github.com/moby/moby/issues/49519) +> [!CAUTION] Remember that current docker update 28.0.0 has some bug and cannot +> connect to external network. Make sure to install docker-ce 27.5.0, +> docker-ce-rootless-extras 27.5.0 and docker-ce-cli 27.5.0. Hopefully docker +> 28.1.0 will resolve this issue in next week. Refrence links are +> [Moby Github Repo Issues 49511](https://github.com/moby/moby/issues/49511) and +> [Moby Github Repo Issues 49519](https://github.com/moby/moby/issues/49519) -> [!IMPORTANT] -> For Docker rootless, the user must have local permissions for all directories required by the Ente-photos self-hosted server. This can be achieved by running `sudo chown -R 1000:1000 /home/ubuntu/docker/ente`. In the Linux terminal, you can check the UID with `id -u` or simply `id`. The first user typically has UID 1000. -> To allow listening and pinging on any port without root privileges, create a file called `/etc/sysctl.d/99-rootless.conf` with the following content: +> [!IMPORTANT] For Docker rootless, the user must have local permissions for all +> directories required by the Ente-photos self-hosted server. This can be +> achieved by running `sudo chown -R 1000:1000 /home/ubuntu/docker/ente`. In the +> Linux terminal, you can check the UID with `id -u` or simply `id`. The first +> user typically has UID 1000. To allow listening and pinging on any port +> without root privileges, create a file called `/etc/sysctl.d/99-rootless.conf` +> with the following content: +> > ``` > net.ipv4.ip_unprivileged_port_start=0 > net.ipv4.ping_group_range = 0 2147483647 > ``` -> than run `sudo sysctl --system`. -> Create `~/.config/systemd/user/docker.service.d/override.conf` with the following content: +> +> than run `sudo sysctl --system`. Create +> `~/.config/systemd/user/docker.service.d/override.conf` with the following +> content: +> > ``` > [Service] > Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns" > Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=slirp4netns" > ``` -> and Restart the docker daemon -> `systemctl --user restart docker` -> Instead of `--volume /var/run/docker.sock:/var/run/docker.sock` in TSDPROXY compose.yaml, use `--volume $XDG_RUNTIME_DIR/docker.sock:/var/run/docker.sock` +> +> and Restart the docker daemon `systemctl --user restart docker` Instead of +> `--volume /var/run/docker.sock:/var/run/docker.sock` in TSDPROXY compose.yaml, +> use `--volume $XDG_RUNTIME_DIR/docker.sock:/var/run/docker.sock` + +## GETTING START WITH SETUP + +First of all create a directory +`sudo mkdir -p /home/ubuntu/docker/tsdproxy/config` than `cd docker/tsdproxy` +and create compose.yaml file by running `sudo nano compose.yaml`. Populate it +with the following: -## GETTING START WITH SETUP ## -First of all create a directory `sudo mkdir -p /home/ubuntu/docker/tsdproxy/config` than `cd docker/tsdproxy` and create compose.yaml file by running `sudo nano compose.yaml`. Populate it with the following: ``` services: tsdproxy: @@ -62,9 +86,18 @@ networks: proxy: name: proxy ``` -Now login into your tailscale account admin counsle > settings > keys > Generate authkey. Give any description and must select resuable, because the key get purged if not selected after rebooting machine. It is advisable to create **Tags** in **ACLs settings** `tag: tsdproxy` `tag: ente` `tag: minio` as well. This will create a tag nodes with no key expirory. One is safe to reboot restart docker or machine. -> Copy the generated authkey as it is shown only once. -Make tsdproxy.yaml file in `cd docker/tsdproxy/config` by running `sudo nano tsdproxy.yaml` and pupolate it with the following contant: + +Now login into your tailscale account admin counsle > settings > keys > Generate +authkey. Give any description and must select resuable, because the key get +purged if not selected after rebooting machine. It is advisable to create +**Tags** in **ACLs settings** `tag: tsdproxy` `tag: ente` `tag: minio` as well. +This will create a tag nodes with no key expirory. One is safe to reboot restart +docker or machine. + +> Copy the generated authkey as it is shown only once. Make tsdproxy.yaml file +> in `cd docker/tsdproxy/config` by running `sudo nano tsdproxy.yaml` and +> pupolate it with the following contant: + ``` defaultproxyprovider: default docker: @@ -87,12 +120,20 @@ log: json: false proxyaccesslog: true ``` -In the same directory run `sudo nano authkey` and paste the authkey just copied earlier from tailscale admin counsel. -> Here Tailscale (TSDPROXY) setup is complet in all respect. Just run `docker compose up -d`. Check your tailscale amdin counsel and you will see tsdproxy node up and running. Make sure that **HTTPS** is enabled in tailscale DNS settings. -> You can visit the TSDPROXY web GUI by https://tsdproxy.xyz.ts.net. (xyz is change value for everyone) -## ente Part ## +In the same directory run `sudo nano authkey` and paste the authkey just copied +earlier from tailscale admin counsel. + +> Here Tailscale (TSDPROXY) setup is complet in all respect. Just run +> `docker compose up -d`. Check your tailscale amdin counsel and you will see +> tsdproxy node up and running. Make sure that **HTTPS** is enabled in tailscale +> DNS settings. You can visit the TSDPROXY web GUI by +> https://tsdproxy.xyz.ts.net. (xyz is change value for everyone) + +## ente Part + First make the following necessary files/directories: + ``` sudo mkdir -p /home/ubuntu/docker/ente/custom-logs sudo mkdir -p /home/ubuntu/docker/ente/data @@ -100,9 +141,14 @@ sudo mkdir -p /home/ubuntu/docker/ente/minio-data sudo mkdir -p /home/ubuntu/docker/ente/postgres-data sudo mkdir -p /home/ubuntu/docker/ente/scripts/compose ``` -Than give user permission for each of the above directory. `sudo chown -R 1000:1000 /home/ubuntu/docker/ente/custom-logs` etc etc. Make sure not to skip `/home/ubuntu/docker/tsdproxy/config` -`cd docker/ente/script/compose` and run `sudo nano credentials.yaml` than populate it with the following: +Than give user permission for each of the above directory. +`sudo chown -R 1000:1000 /home/ubuntu/docker/ente/custom-logs` etc etc. Make +sure not to skip `/home/ubuntu/docker/tsdproxy/config` + +`cd docker/ente/script/compose` and run `sudo nano credentials.yaml` than +populate it with the following: + ``` db: host: postgres @@ -134,7 +180,9 @@ s3: bucket: scw-eu-fr-v3 ``` -In the same directory run `sudo nano minio-provision.sh` and populate it with the following contant: +In the same directory run `sudo nano minio-provision.sh` and populate it with +the following contant: + ``` #!/bin/sh @@ -154,7 +202,9 @@ mc mb -p wasabi-eu-central-2-v3 mc mb -p scw-eu-fr-v3 ``` -Now `cd docker/ente` and run `sudo nano docker-compose.yaml` and populate it with the following: +Now `cd docker/ente` and run `sudo nano docker-compose.yaml` and populate it +with the following: + ``` services: museum: @@ -255,32 +305,52 @@ services: networks: ente: name: ente - + proxy: external: true ``` -> Thats it. Run `docker compose up -d`. Wait till every container become healthy. Open web browser. Make sure tailscale is installed on the machine. Visit https://ente.xyz.ts.net/ping. It will pong. All good if you see it. First time it will take minute or two to get SSL cert. Downnload Desktop or mobile app. Tap 7 time on the screen, which will prompt developer mode. Add https://ente.xyz.ts.net. Add new user. When asked for OTP. Just go to linux terminal and run `docker logs ente-museum-1`. Search for userauth. Feed the six digit and Done. +> Thats it. Run `docker compose up -d`. Wait till every container become +> healthy. Open web browser. Make sure tailscale is installed on the machine. +> Visit https://ente.xyz.ts.net/ping. It will pong. All good if you see it. +> First time it will take minute or two to get SSL cert. Downnload Desktop or +> mobile app. Tap 7 time on the screen, which will prompt developer mode. Add +> https://ente.xyz.ts.net. Add new user. When asked for OTP. Just go to linux +> terminal and run `docker logs ente-museum-1`. Search for userauth. Feed the +> six digit and Done. + +> For getting 100TB (limitless) storage. Just Install ente-cli for windows. +> Extract it and add folder. Name it **export**. Add config.yaml file along and +> populate it with the following: -> For getting 100TB (limitless) storage. Just Install ente-cli for windows. Extract it and add folder. Name it **export**. Add config.yaml file along and populate it with the following: ``` endpoint: api: "https://ente.xyz.ts.net" accounts: "http://localhost:3001" - + log: false ``` -Right-Click in the directory where you have extracted ente-cli. Select `open in terminal`. Run + +Right-Click in the directory where you have extracted ente-cli. Select +`open in terminal`. Run + ``` .\ente.exe account bob # change bob to yours ``` -Hit Enter twice. -For export directory, just write export. As already created **export** folder earlier. -**Write email. The one which is already used befor when creating ente account in ente desktop app.** -Type the same Password used before for the account.Run + +Hit Enter twice. For export directory, just write export. As already created +**export** folder earlier. **Write email. The one which is already used befor +when creating ente account in ente desktop app.** Type the same Password used +before for the account.Run + ``` .\ente.ext account list ``` + This will list all account details. Copy Acount ID. -> Navigate to museum.yaml file. `cd docker/ente`. Run `sudo nano museum.yaml` and add the account ID under Admins. Delete any previous entries. -Restart ente-museum-1 container from linux terminal. Run `docker restart ente-museum-1`. All well, now you will have 100TB storage. Repeat if for any other accounts you want to give unlimited storage access. + +> Navigate to museum.yaml file. `cd docker/ente`. Run `sudo nano museum.yaml` +> and add the account ID under Admins. Delete any previous entries. Restart +> ente-museum-1 container from linux terminal. Run +> `docker restart ente-museum-1`. All well, now you will have 100TB storage. +> Repeat if for any other accounts you want to give unlimited storage access. diff --git a/docs/docs/self-hosting/guides/admin.md b/docs/docs/self-hosting/guides/admin.md index 3f80d1478e..10d05fb4d0 100644 --- a/docs/docs/self-hosting/guides/admin.md +++ b/docs/docs/self-hosting/guides/admin.md @@ -14,7 +14,7 @@ explicit whitelist of admins. > [!NOTE] > -> The first user is only treated as the admin if the list of admins in the +> The first user is only treated as the admin if the list of admins in the > configuration is empty. > > Also, if at some point you delete the first user, then you will need to define @@ -54,11 +54,10 @@ command to find the user id of any account. # Administering your custom server -> [!NOTE] -> For the first user (admin) to perform administrative actions using the CLI, their -> userID must be whitelisted in the `museum.yaml` configuration file under -> `internal.admins`. While the first user is automatically granted admin privileges -> on the server, this additional step is required for CLI operations. +> [!NOTE] For the first user (admin) to perform administrative actions using the +> CLI, their userID must be whitelisted in the `museum.yaml` configuration file +> under `internal.admins`. While the first user is automatically granted admin +> privileges on the server, this additional step is required for CLI operations. You can use [Ente's CLI](https://github.com/ente-io/ente/releases?q=tag%3Acli-v0) to diff --git a/docs/docs/self-hosting/guides/configuring-s3.md b/docs/docs/self-hosting/guides/configuring-s3.md index f4095db2eb..55f2f3b356 100644 --- a/docs/docs/self-hosting/guides/configuring-s3.md +++ b/docs/docs/self-hosting/guides/configuring-s3.md @@ -29,7 +29,7 @@ A file upload flows as follows: The upshot of this is that _both_ the client and museum should be able to reach your S3 bucket. -## Configuring S3 +## Configuring S3 The URL for the S3 bucket is configured in [scripts/compose/credentials.yaml](https://github.com/ente-io/ente/blob/main/server/scripts/compose/credentials.yaml#L10). @@ -38,9 +38,8 @@ You can edit this file directly while testing, though it is more robust to create a `museum.yaml` (in the same folder as the Docker compose file) and to setup your custom configuration there. -> [!TIP] -> For more details about these configuration objects, see the documentation for -> the `s3` object in +> [!TIP] For more details about these configuration objects, see the +> documentation for the `s3` object in > [configurations/local.yaml](https://github.com/ente-io/ente/blob/main/server/configurations/local.yaml). By default, you only need to configure the endpoint for the first bucket. @@ -56,13 +55,14 @@ components of the setup to communicate with each other seamlessly. The same principle applies if you're deploying to your custom domain. -## Replication +## Replication ![Replication](/replication.png) +

Community contributed diagram of Ente's replication process

> [!IMPORTANT] -> +> > As of now, replication works only if all the 3 storage type needs are > fulfilled (1 hot, 1 cold and 1 glacier storage). > @@ -72,10 +72,10 @@ If you're wondering why there are 3 buckets on the MinIO UI - that's because our production instance uses these to perform [replication](https://ente.io/reliability/). -If you're also wondering about why the bucket names are specifically what they are, -it's because that is exactly what we are using on our production instance. -We use `b2-eu-cen` as hot, `wasabi-eu-central-2-v3` as cold (also the secondary hot) -and `scw-eu-fr-v3` as glacier storage. As of now, all of this is hardcoded. +If you're also wondering about why the bucket names are specifically what they +are, it's because that is exactly what we are using on our production instance. +We use `b2-eu-cen` as hot, `wasabi-eu-central-2-v3` as cold (also the secondary +hot) and `scw-eu-fr-v3` as glacier storage. As of now, all of this is hardcoded. Hence, the same hardcoded configuration is applied when you self host Ente. In a self hosted Ente instance replication is turned off by default. When @@ -84,16 +84,15 @@ other two are ignored. Only the names here are specifically fixed, but in the configuration body you can put any other keys. It does not have any relation with `b2`, `wasabi` or even `scaleway`. -Use the `s3.hot_storage.primary` option if you'd like to set one of the other +Use the `s3.hot_storage.primary` option if you'd like to set one of the other predefined buckets as the primary bucket. -## SSL Configuration +## SSL Configuration > [!NOTE] > > If you need to configure SSL, you'll need to turn off `s3.are_local_buckets` > (which disables SSL in the default starter compose template). -> Disabling `s3.are_local_buckets` also switches to the subdomain style URLs for the buckets. However, not all S3 providers support these. In particular, MinIO @@ -121,4 +120,4 @@ s3: endpoint: http://:3200 region: eu-central-2 bucket: b2-eu-cen -``` \ No newline at end of file +``` diff --git a/docs/docs/self-hosting/guides/custom-server/index.md b/docs/docs/self-hosting/guides/custom-server/index.md index 86060ba909..63ba371eb8 100644 --- a/docs/docs/self-hosting/guides/custom-server/index.md +++ b/docs/docs/self-hosting/guides/custom-server/index.md @@ -111,5 +111,5 @@ network, you need to use the public IP or hostname. > [!TIP] > > If you're having trouble uploading from your mobile app, it is likely that -> museum is not able to connect to your S3 storage. See the [Configuring -> S3](/self-hosting/guides/configuring-s3) guide for more details. +> museum is not able to connect to your S3 storage. See the +> [Configuring S3](/self-hosting/guides/configuring-s3) guide for more details. diff --git a/docs/docs/self-hosting/guides/from-source.md b/docs/docs/self-hosting/guides/from-source.md index df3ec7e5fb..4284124487 100644 --- a/docs/docs/self-hosting/guides/from-source.md +++ b/docs/docs/self-hosting/guides/from-source.md @@ -3,13 +3,12 @@ title: Ente from Source description: Getting started self hosting Ente Photos and/or Ente Auth --- - # Ente from Source -> [!WARNING] NOTE -> The below documentation will cover instructions about self-hosting the web app manually. If you -> want to deploy Ente hassle free, use the [one line](https://ente.io/blog/self-hosting-quickstart/) -> command to setup Ente. This guide might be deprecated in the near future. +> [!WARNING] NOTE The below documentation will cover instructions about +> self-hosting the web app manually. If you want to deploy Ente hassle free, use +> the [one line](https://ente.io/blog/self-hosting-quickstart/) command to setup +> Ente. This guide might be deprecated in the near future. ## Installing Docker @@ -63,8 +62,9 @@ apps and configure them to use your ## Web app with Docker and Compose -The instructoins in previous section were just a temporary way to run the web app locally. -To run the web apps as services, the user has to build a docker image manually. +The instructoins in previous section were just a temporary way to run the web +app locally. To run the web apps as services, the user has to build a docker +image manually. > [!IMPORTANT] > @@ -144,7 +144,7 @@ docker build -t : --no-cache --progress plain . You can always edit the Dockerfile and remove the steps for apps which you do not intend to install on your system (like auth or cast) and opt out of those. -Regarding Albums App, take a note that they are not apps with navigable pages, +Regarding Albums App, take a note that they are not apps with navigable pages, if accessed on the web-browser they will simply redirect to ente.web.io. ## compose.yaml @@ -175,17 +175,17 @@ docker compose up -d # --build docker compose logs ``` -## Configure App Endpoints +## Configure App Endpoints -> [!NOTE] -> Previously, this was dependent on the env variables `NEXT_ENTE_PUBLIC_ACCOUNTS_ENDPOINT` -> and etc. Please check the below documentation to update your setup configurations +> [!NOTE] Previously, this was dependent on the env variables +> `NEXT_ENTE_PUBLIC_ACCOUNTS_ENDPOINT` and etc. Please check the below +> documentation to update your setup configurations -You can configure the web endpoints for the other apps including Accounts, Albums -Family and Cast in your `museum.yaml` configuration file. Checkout +You can configure the web endpoints for the other apps including Accounts, +Albums Family and Cast in your `museum.yaml` configuration file. Checkout [`local.yaml`](https://github.com/ente-io/ente/blob/543411254b2bb55bd00a0e515dcafa12d12d3b35/server/configurations/local.yaml#L76-L89) -to configure the endpoints. Make sure to setup up your DNS Records accordingly to the -similar URL's you set up in `museum.yaml`. +to configure the endpoints. Make sure to setup up your DNS Records accordingly +to the similar URL's you set up in `museum.yaml`. Next part is to configure the web server. diff --git a/docs/docs/self-hosting/guides/web-app.md b/docs/docs/self-hosting/guides/web-app.md index 015cb7d986..a3061e3004 100644 --- a/docs/docs/self-hosting/guides/web-app.md +++ b/docs/docs/self-hosting/guides/web-app.md @@ -5,22 +5,20 @@ description: server --- - -> [!WARNING] NOTE -> This page covers documentation around self-hosting the web app manually. If you -> want to deploy Ente hassle free, please use the [one line](https://ente.io/blog/self-hosting-quickstart/) -> command to setup Ente. This guide might be deprecated in the near future. +> [!WARNING] NOTE This page covers documentation around self-hosting the web app +> manually. If you want to deploy Ente hassle free, please use the +> [one line](https://ente.io/blog/self-hosting-quickstart/) command to setup +> Ente. This guide might be deprecated in the near future. # Web app The getting started instructions mention using `yarn dev` (which is an alias of `yarn dev:photos`) to serve your web app. ->[!IMPORTANT] -> Please note that Ente's Web App supports the Yarn version 1.22.xx or 1.22.22 specifically. -> Make sure to install the right version or modify your yarn installation to meet the requirements. -> The user might end up into unknown version and dependency related errors if yarn -> is on different version. +> [!IMPORTANT] Please note that Ente's Web App supports the Yarn version 1.22.xx +> or 1.22.22 specifically. Make sure to install the right version or modify your +> yarn installation to meet the requirements. The user might end up into unknown +> version and dependency related errors if yarn is on different version. ```sh cd ente/web @@ -146,15 +144,15 @@ docker compose logs ## Configure App Endpoints -> [!NOTE] -> Previously, this was dependent on the env variables `NEXT_ENTE_PUBLIC_ACCOUNTS_ENDPOINT` -> and etc. Please check the below documentation to update your setup configurations +> [!NOTE] Previously, this was dependent on the env variables +> `NEXT_ENTE_PUBLIC_ACCOUNTS_ENDPOINT` and etc. Please check the below +> documentation to update your setup configurations -You can configure the web endpoints for the other apps including Accounts, Albums -Family and Cast in your `museum.yaml` configuration file. Checkout +You can configure the web endpoints for the other apps including Accounts, +Albums Family and Cast in your `museum.yaml` configuration file. Checkout [`local.yaml`](https://github.com/ente-io/ente/blob/543411254b2bb55bd00a0e515dcafa12d12d3b35/server/configurations/local.yaml#L76-L89) -to configure the endpoints. Make sure to setup up your DNS Records accordingly to the -similar URL's you set up in `museum.yaml`. +to configure the endpoints. Make sure to setup up your DNS Records accordingly +to the similar URL's you set up in `museum.yaml`. Next part is to configure the web server. diff --git a/docs/docs/self-hosting/museum.md b/docs/docs/self-hosting/museum.md index 515616ac32..cb5263104f 100644 --- a/docs/docs/self-hosting/museum.md +++ b/docs/docs/self-hosting/museum.md @@ -16,10 +16,10 @@ If you used our quickstart script, your `my-ente` directory will include a PostgreSQL and MinIO. > [!TIP] -> +> > Always do `docker compose down` inside your `my-ente` directory. If you've -> made changes to `museum.yaml`, restart the containers with `docker compose up -> -d ` to see your changes in action. +> made changes to `museum.yaml`, restart the containers with +> `docker compose up -d ` to see your changes in action. ## S3 buckets @@ -33,19 +33,20 @@ Check out [Configuring S3](/self-hosting/guides/configuring-s3.md) to understand more about configuring S3 buckets. MinIO uses the port `3200` for API Endpoints and their web app runs over -`:3201`. You can login to MinIO Web Console by opening `localhost:3201` in your browser. +`:3201`. You can login to MinIO Web Console by opening `localhost:3201` in your +browser. -If you face any issues related to uploads then checkout [Troubleshooting bucket -CORS](/self-hosting/troubleshooting/bucket-cors) and [Frequently encountered S3 -errors](/self-hosting/guides/configuring-s3#frequently-encountered-errors). +If you face any issues related to uploads then checkout +[Troubleshooting bucket CORS](/self-hosting/troubleshooting/bucket-cors) and +[Frequently encountered S3 errors](/self-hosting/guides/configuring-s3#frequently-encountered-errors). ## Web apps The web apps for Ente Photos is divided into multiple sub-apps like albums, -cast, auth, etc. These endpoints are configurable in the museum.yaml under the +cast, auth, etc. These endpoints are configurable in `museum.yaml` under the `apps.*` section. -For example, +For example, ```yaml apps: @@ -55,17 +56,16 @@ apps: family: https://family.myente.xyz ``` ->[!IMPORTANT] ->By default, all the values redirect to our publicly hosted production services. ->For example, if `public-albums` is not configured your shared album will ->use the `albums.ente.io` URL. +> [!IMPORTANT] By default, all the values redirect to our publicly hosted +> production services. For example, if `public-albums` is not configured your +> shared album will use the `albums.ente.io` URL. After you are done with filling the values, restart museum and the app will start utilizing those endpoints instead of Ente's production instances. Once you have configured all the necessary endpoints, `cd` into `my-ente` and stop all the Docker containers with `docker compose down` and restart them with -`docker compose up -d`. +`docker compose up -d`. Similarly, you can use the default [`local.yaml`](https://github.com/ente-io/ente/tree/main/server/configurations/local.yaml) diff --git a/docs/docs/self-hosting/reverse-proxy.md b/docs/docs/self-hosting/reverse-proxy.md index 7ef79ac412..5ee0ca97fd 100644 --- a/docs/docs/self-hosting/reverse-proxy.md +++ b/docs/docs/self-hosting/reverse-proxy.md @@ -22,28 +22,25 @@ server on your machine. Setting up a reverse proxy with Caddy is easy and straightforward. -Firstly, install Caddy on your server. +Firstly, install Caddy on your server. ```sh sudo apt install caddy -``` +``` After the installation is complete, a `Caddyfile` is created on the path `/etc/caddy/`. This file is used to configure reverse proxies among other things. -```yaml +```yaml # Caddyfile - myente.xyz is just an example. -api.myente.xyz { - reverse_proxy http://localhost:8080 -} -ente.myente.xyz { - reverse_proxy http://localhost:3000 -} +api.myente.xyz { reverse_proxy http://localhost:8080 } ente.myente.xyz { +reverse_proxy http://localhost:3000 } #...and so on for other endpoints ``` -After a hard-reload, the Ente Photos web app should be up on https://ente.myente.xyz. +After a hard-reload, the Ente Photos web app should be up on +https://ente.myente.xyz. If you are using a different tool for reverse proxy (like nginx), please check out their documentation. diff --git a/docs/docs/self-hosting/troubleshooting/bucket-cors.md b/docs/docs/self-hosting/troubleshooting/bucket-cors.md index 19c1dbff47..8bab1f7012 100644 --- a/docs/docs/self-hosting/troubleshooting/bucket-cors.md +++ b/docs/docs/self-hosting/troubleshooting/bucket-cors.md @@ -37,13 +37,21 @@ aws s3api put-bucket-cors --bucket YOUR_S3_BUCKET --cors-configuration /path/to/ ## For Self-hosted Minio Instance -> Important: MinIO does not take JSON CORS file as the input, instead you will -> have to build a CORS.xml file or just convert the above `cors.json` to XML. +::: warning + +- MinIO does not support bucket CORS in the community edition which is used by + default. For more information, check + [this discussion](https://github.com/minio/minio/discussions/20841). However, + global CORS configuration is possible. +- MinIO does not take JSON CORS file as the input, instead you will have to + build a CORS.xml file or just convert the above `cors.json` to XML. + +::: A minor requirement here is the tool `mc` for managing buckets via command line interface. Checkout the `mc set alias` document to configure alias for your instance and bucket. After this you will be prompted for your AccessKey and -Secret, which is your username and password, go ahead and enter that. +Secret, which is your username and password. ```sh mc cors set // api cors_allow_origin="*" You can create also `.csv` file and dump the list of origins you would like to allow and replace the `*` with `path` to the CSV file. -Now, uploads should be working fine. \ No newline at end of file +Now, uploads should be working fine. diff --git a/docs/docs/self-hosting/troubleshooting/docker.md b/docs/docs/self-hosting/troubleshooting/docker.md index 6c80070b34..902e8c9cfd 100644 --- a/docs/docs/self-hosting/troubleshooting/docker.md +++ b/docs/docs/self-hosting/troubleshooting/docker.md @@ -1,5 +1,5 @@ --- -title: Docker errors +title: Docker Errors description: Fixing docker related errors when trying to self host Ente --- @@ -34,30 +34,30 @@ perform the same configuration by removing the "post_start" hook, and adding a new service definition: ```yaml - minio-provision: +minio-provision: image: minio/mc depends_on: - - minio + - minio volumes: - - minio-data:/data + - minio-data:/data networks: - - internal + - internal entrypoint: | - sh -c ' - #!/bin/sh + sh -c ' + #!/bin/sh - while ! mc config host add h0 http://minio:3200 changeme changeme1234 - do - echo "waiting for minio..." - sleep 0.5 - done + while ! mc config host add h0 http://minio:3200 changeme changeme1234 + do + echo "waiting for minio..." + sleep 0.5 + done - cd /data + cd /data - mc mb -p b2-eu-cen - mc mb -p wasabi-eu-central-2-v3 - mc mb -p scw-eu-fr-v3 - ' + mc mb -p b2-eu-cen + mc mb -p wasabi-eu-central-2-v3 + mc mb -p scw-eu-fr-v3 + ' ``` ## start_interval @@ -114,7 +114,7 @@ volumes. If you're sure of what you're doing, the volumes can be deleted by -``` +```sh docker volume ls ``` @@ -124,6 +124,13 @@ to list them, and then delete the ones that begin with `my-ente` using that'll delete all volumes (Ente or otherwise) on your machine that are not currently in use by a running docker container. +An alternative way is to delete the volumes along with removal of cluster's +containers using `docker compose` inside `my-ente` directory. + +```sh +docker compose down --volumes +``` + If you're unsure about removing volumes, another alternative is to rename your `my-ente` folder. Docker uses the folder name to determine the volume name prefix, so giving it a different name will cause Docker to create a volume diff --git a/docs/docs/self-hosting/troubleshooting/keyring.md b/docs/docs/self-hosting/troubleshooting/keyring.md index 399595ba3f..56a5807fa5 100644 --- a/docs/docs/self-hosting/troubleshooting/keyring.md +++ b/docs/docs/self-hosting/troubleshooting/keyring.md @@ -5,8 +5,8 @@ description: A quick hotfix for keyring errors while running Ente CLI. # Ente CLI Secrets -Ente CLI makes use of keyring for storing sensitive information like your -passwords. And running the cli straight out of the box might give you some +Ente CLI makes use of system keyring for storing sensitive information like your +passwords. And running the CLI straight out of the box might give you some errors related to keyrings in some case. Follow the below steps to run Ente CLI and also avoid keyrings errors. diff --git a/docs/docs/self-hosting/troubleshooting/misc.md b/docs/docs/self-hosting/troubleshooting/misc.md index 65c1e7ffd1..ff6ceda762 100644 --- a/docs/docs/self-hosting/troubleshooting/misc.md +++ b/docs/docs/self-hosting/troubleshooting/misc.md @@ -3,14 +3,14 @@ title: General troubleshooting cases description: Fixing various errors when trying to self host Ente --- -## Functionality not working on self hosted +## Functionality not working on self hosted instance If some specific functionality (e.g. album listing, video playback) does not work on your self hosted instance, it is possible that you have set _some_, but not _all_ needed CSP headers (by default, CSP is not enabled). To expand on it - by default, currently the generated build does not enable CSP -headers. The generated build includes a _headers file that Cloudflare will use +headers. The generated build includes a \_headers file that Cloudflare will use to set HTTP response headers, but even these do not enable CSP, it is set to a report only mode. @@ -18,7 +18,7 @@ However, your web server might be setting some CSP policy. If so, then you will need to ensure that all necessary CSP headers are set. You can see the current -[_headers](https://github.com/ente-io/ente/blob/main/web/apps/photos/public/_headers) +[\_headers](https://github.com/ente-io/ente/blob/main/web/apps/photos/public/_headers) file contents to use a template for your CSP policy. The `Content-Security-Policy-Report-Only` value will show you the CSP headers in "dry run" report-only mode we're setting - you can use that as a template, @@ -28,8 +28,8 @@ How do you know if this is the problem you're facing? The browser console _might_ be giving you errors when you try to open the page and perform the corresponding function. -> Refused to load https://subdomain.example.org/... because it does not appear -> in the script-src directive of the Content Security Policy. +> Refused to load https://subdomain.example.org/... because it does not appear +> in the script-src directive of the Content Security Policy. This is not guaranteed, each browsers handles CSP errors differently, and some may silently swallow it. diff --git a/docs/docs/self-hosting/troubleshooting/uploads.md b/docs/docs/self-hosting/troubleshooting/uploads.md index 3dbc7454f4..6cad97f201 100644 --- a/docs/docs/self-hosting/troubleshooting/uploads.md +++ b/docs/docs/self-hosting/troubleshooting/uploads.md @@ -10,27 +10,27 @@ context and potential fixes. Fundamentally in most situations, the problem is because of minor mistakes or misconfiguration. Please make sure to reverse proxy museum and MinIO API -endpoint to a domain and check your S3 credentials and whole configuration -file for any minor misconfigurations. +endpoint to a domain and check your S3 credentials and whole configuration file +for any minor misconfigurations. -It is also suggested that the user setups bucket CORS on MinIO or any external -S3 service provider they are connecting to. To setup bucket CORS, please [read -this](/self-hosting/troubleshooting/bucket-cors). +It is also suggested that the user setups bucket CORS or global CORS on MinIO or +any external S3 service provider they are connecting to. To setup bucket CORS, +please [read this](/self-hosting/troubleshooting/bucket-cors). ## What is S3 and how is it incorporated in Ente ? -S3 is an cloud storage protocol made by Amazon (specifically AWS). S3 is designed to store -files and data as objects inside Buckets and it is mostly used for Online -Backups and storing different types of files. +S3 is an cloud storage protocol made by Amazon (specifically AWS). S3 is +designed to store files and data as objects inside buckets and it is mostly used +for online backups and storing different types of files. -Ente's Docker setup is shipped with [MinIO](https://min.io/) as its default S3 provider. -MinIO supports the Amazon S3 protocol and leverages your disk storage to -dump all the uploaded files as encrypted object blobs. +Ente's Docker setup is shipped with [MinIO](https://min.io/) as its default S3 +provider. MinIO supports the Amazon S3 protocol and leverages your disk storage +to dump all the uploaded files as encrypted object blobs. ## 403 Forbidden -If museum is able to make a network connection to your S3 bucket but -uploads are still failing, it could be a credentials or permissions issue. +If museum is able to make a network connection to your S3 bucket but uploads are +still failing, it could be a credentials or permissions issue. A telltale sign of this is that in the museum logs you can see `403 Forbidden` errors about it not able to find the size of a file even though the @@ -41,14 +41,15 @@ This could be because 1. The bucket CORS rules do not allow museum to access these objects. For uploading files from the browser, you will need to set `allowedOrigins` to `*`, and allow the `X-Auth-Token`, `X-Client-Package`, `X-Client-Version` - headers configuration too. [Here is an example of a working - configuration](https://github.com/ente-io/ente/discussions/1764#discussioncomment-9478204). + headers configuration too. + [Here is an example of a working configuration](https://github.com/ente-io/ente/discussions/1764#discussioncomment-9478204). 2. The credentials are not being picked up (you might be setting the correct credentials, but not in the place where museum reads them from). ## Mismatch in file size -The "Mismatch in file size" error mostly occurs in a situation where the client is re-uploading a file which is already in the bucket with a different -file size. The reason for re-upload could be anything including network issue, -sudden killing of app before the upload is complete and etc. +The "Mismatch in file size" error mostly occurs in a situation where the client +is re-uploading a file which is already in the bucket with a different file +size. The reason for re-upload could be anything including network issue, sudden +killing of app before the upload is complete and etc. diff --git a/docs/docs/self-hosting/troubleshooting/yarn.md b/docs/docs/self-hosting/troubleshooting/yarn.md index b4205beb0e..4cc62c405b 100644 --- a/docs/docs/self-hosting/troubleshooting/yarn.md +++ b/docs/docs/self-hosting/troubleshooting/yarn.md @@ -5,8 +5,8 @@ description: Fixing yarn install errors when trying to self host Ente # Yarn -If your `yarn install` is failing, make sure you are using Yarn v1 (also known -as "Yarn Classic"): +If `yarn install` is failing, make sure you are using Yarn v1 (also known as +"Yarn Classic"): - https://classic.yarnpkg.com/lang/en/docs/install From 088afe7f2ae402b34616ccc3c82c30e933024556 Mon Sep 17 00:00:00 2001 From: Keerthana Date: Mon, 2 Jun 2025 14:32:53 +0530 Subject: [PATCH 004/194] [fix]: dead links in overview --- docs/docs/de/auth/index.md | 2 +- docs/docs/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/de/auth/index.md b/docs/docs/de/auth/index.md index 1948a9126d..b7abeec8d8 100644 --- a/docs/docs/de/auth/index.md +++ b/docs/docs/de/auth/index.md @@ -10,4 +10,4 @@ Ende-zu-Ende-verschlüsselte Authenticator-App für jedermann. Wir sind froh, da du hier bist! **Please note that this German translation is currently just a placeholder.** -Know German? [Help us fill this in!](/about/contribute). +Know German? [Help us fill this in!](/overview/contribute). diff --git a/docs/docs/index.md b/docs/docs/index.md index af5fd2cddf..681b2a16df 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -11,5 +11,5 @@ Use the **sidebar** menu to navigate to information about the product (Photos or Auth) you'd like to know more about. Or use the **search** at the top to try and jump directly to page that might contain the information you need. -To know more about Ente, see [about](/about/) or visit our website +To know more about Ente, see [overview](/overview/) or visit our website [ente.io](https://ente.io). From 5fe0e424cddcf0aa7426bc6b6ac88e75b745dfda Mon Sep 17 00:00:00 2001 From: Keerthana Date: Mon, 2 Jun 2025 18:16:58 +0530 Subject: [PATCH 005/194] [docs] refactor community and help pages in overview --- docs/docs/.vitepress/sidebar.ts | 6 +++++- docs/docs/overview/community.md | 19 +++---------------- docs/docs/overview/contribute.md | 10 +++++++++- docs/docs/overview/help.md | 13 +++++++++++++ docs/docs/overview/index.md | 4 ++-- docs/package.json | 3 ++- 6 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 docs/docs/overview/help.md diff --git a/docs/docs/.vitepress/sidebar.ts b/docs/docs/.vitepress/sidebar.ts index 448541cc2e..8a0754ad7e 100644 --- a/docs/docs/.vitepress/sidebar.ts +++ b/docs/docs/.vitepress/sidebar.ts @@ -9,13 +9,17 @@ export const sidebar = [ text: "Introduction", link: "/overview/", }, + { + text: "Community", + link: "/overview/community", + }, { text: "Contributing", link: "/overview/contribute", }, { text: "Help", - link: "/overview/community", + link: "/overview/help", }, ], }, diff --git a/docs/docs/overview/community.md b/docs/docs/overview/community.md index 806aa45d6a..aecc9a0df5 100644 --- a/docs/docs/overview/community.md +++ b/docs/docs/overview/community.md @@ -1,17 +1,10 @@ --- -title: Get in touch +title: Community description: > - Get in touch with Ente for support, stay updated, suggest new features and - join the community + Information regarding Ente's community channels --- -# Get in touch - -## Support - -If you have a support query that is not answered by these docs, please reach out -to our Customer Support by sending an email to -[support@ente.io](mailto:support@ente.io) +# Community ## Blog @@ -19,12 +12,6 @@ To stay up to date with new product launches, and behind the scenes details of how we're building Ente, you can read our [blog](https://ente.io/blog) (or subscribe to it via [RSS](https://ente.io/blog/rss.xml)) -## Suggest a feature - -To suggest new features and/or offer your perspective on how we should design -planned and upcoming features, use our -[GitHub discussions](https://github.com/ente-io/ente/discussions) - ## Community Or if you'd just like to hang out, join our diff --git a/docs/docs/overview/contribute.md b/docs/docs/overview/contribute.md index 3addd2e606..406de6c480 100644 --- a/docs/docs/overview/contribute.md +++ b/docs/docs/overview/contribute.md @@ -1,10 +1,18 @@ --- title: Contribute -description: Details about how to contribute to Ente's docs +description: Details about how to contribute to Ente --- # Contributing +## Suggest a feature + +To suggest new features and/or offer your perspective on how we should design +(planned and upcoming features), use our +[GitHub discussions](https://github.com/ente-io/ente/discussions) + +## Documentation + To contribute to these docs, you can use the "Edit this page" button at the bottom of each page. This will allow you to directly edit the markdown file that is used to generate this documentation and open a quick pull request directly diff --git a/docs/docs/overview/help.md b/docs/docs/overview/help.md new file mode 100644 index 0000000000..43aeded5af --- /dev/null +++ b/docs/docs/overview/help.md @@ -0,0 +1,13 @@ +--- +title: Help +description: Get help from Ente via customer support and community +--- + +# Help + +If you encounter any issues with any of the products that's not answered by our +[documentation](/), please reach out to our Customer Support by sending an email +to [support@ente.io](mailto:support@ente.io) + +For community support, please post your queries on +[Discord](https://discord.gg/z2YVKkycX3) diff --git a/docs/docs/overview/index.md b/docs/docs/overview/index.md index 3ba1aa3193..063d2c6675 100644 --- a/docs/docs/overview/index.md +++ b/docs/docs/overview/index.md @@ -16,8 +16,8 @@ this platform, Ente offers two products: - **Ente Auth** - A free 2FA alternative to Authy Both these apps are available for all desktop (Linux, Mac, Windows) and mobile -(Android, iOS and F-Droid) platforms. They also work directly in your web -browser without you needing to install anything. +(Android and iOS) platforms. They also work directly in your web browser without +you needing to install anything. More products are in the pipeline. diff --git a/docs/package.json b/docs/package.json index bd7635b126..a714aea110 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,7 +5,8 @@ "dev": "vitepress dev docs", "build": "vitepress build docs", "preview": "vitepress preview docs", - "pretty": "prettier --write ." + "pretty": "prettier --write .", + "pretty:check": "prettier -c ." }, "devDependencies": { "prettier": "^3.3.4", From 2d6c754c15bcbe37f43fe0ad1ef178386bb7eab4 Mon Sep 17 00:00:00 2001 From: Keerthana Date: Mon, 2 Jun 2025 18:18:35 +0530 Subject: [PATCH 006/194] [docs][actions] add lint checks for PRs --- .github/workflows/docs-lint.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/docs-lint.yml diff --git a/.github/workflows/docs-lint.yml b/.github/workflows/docs-lint.yml new file mode 100644 index 0000000000..f227f2115b --- /dev/null +++ b/.github/workflows/docs-lint.yml @@ -0,0 +1,32 @@ +name: "Lint (docs)" + +on: + # Run on every pull request (open or push to it) that changes docs/ + pull_request: + paths: + - "docs/**" + - ".github/workflows/docs-lint.yml" + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + defaults: + run: + working-directory: docs + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup node and enable yarn caching + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: "yarn" + cache-dependency-path: "web/yarn.lock" + + - run: yarn install + + - run: yarn pretty:check From 1b8a8a271767bcd06fd62f45cde7549ab5d73185 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 2 Jun 2025 18:58:34 +0530 Subject: [PATCH 007/194] [web] Fix trash open --- web/apps/photos/src/components/Collections/CollectionHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/photos/src/components/Collections/CollectionHeader.tsx b/web/apps/photos/src/components/Collections/CollectionHeader.tsx index 35fcb89986..d164791595 100644 --- a/web/apps/photos/src/components/Collections/CollectionHeader.tsx +++ b/web/apps/photos/src/components/Collections/CollectionHeader.tsx @@ -521,7 +521,7 @@ const CollectionOptions: React.FC = ({ title={t("rename_album")} label={t("album_name")} autoFocus - initialValue={activeCollection.name} + initialValue={activeCollection?.name} submitButtonTitle={t("rename")} onSubmit={renameCollection} /> From b567dddfc396931050979d7178f01646f88f8682 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 2 Jun 2025 19:13:41 +0530 Subject: [PATCH 008/194] routine dep update --- web/apps/cast/package.json | 2 +- web/apps/payments/package.json | 2 +- web/apps/photos/package.json | 6 +- web/package.json | 2 +- web/packages/accounts/package.json | 2 +- web/packages/base/package.json | 8 +- web/packages/build-config/package.json | 8 +- web/packages/gallery/package.json | 6 +- web/packages/new/package.json | 2 +- web/yarn.lock | 332 +++++++++++++------------ 10 files changed, 193 insertions(+), 177 deletions(-) diff --git a/web/apps/cast/package.json b/web/apps/cast/package.json index a2e74a0f8d..3e466fccd0 100644 --- a/web/apps/cast/package.json +++ b/web/apps/cast/package.json @@ -9,7 +9,7 @@ "ente-shared": "*" }, "devDependencies": { - "@types/chromecast-caf-receiver": "^6.0.21", + "@types/chromecast-caf-receiver": "^6.0.22", "ente-build-config": "*" } } diff --git a/web/apps/payments/package.json b/web/apps/payments/package.json index 96d8ea8204..afa3830aa7 100644 --- a/web/apps/payments/package.json +++ b/web/apps/payments/package.json @@ -14,7 +14,7 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.5", + "@types/react": "^19.1.6", "@types/react-dom": "^19.1.5", "@vitejs/plugin-react": "^4.5.0", "ente-build-config": "*", diff --git a/web/apps/photos/package.json b/web/apps/photos/package.json index b4a894dd41..0c0ecb80a9 100644 --- a/web/apps/photos/package.json +++ b/web/apps/photos/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "dependencies": { - "chrono-node": "^2.8.0", + "chrono-node": "^2.8.2", "debounce": "^2.2.0", "ente-accounts": "*", "ente-base": "*", @@ -25,8 +25,8 @@ "similarity-transformation": "^0.0.1" }, "devDependencies": { - "@types/node": "^22.15.21", - "@types/react": "^19.1.5", + "@types/node": "^22.15.29", + "@types/react": "^19.1.6", "@types/react-dom": "^19.1.5", "@types/react-window": "^1.8.8", "ente-build-config": "*" diff --git a/web/package.json b/web/package.json index c0d8f14bc9..255aee9987 100644 --- a/web/package.json +++ b/web/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "concurrently": "^9.1.2", - "eslint": "^9.27.0", + "eslint": "^9.28.0", "prettier": "^3.5.3", "typescript": "^5.8.3" }, diff --git a/web/packages/accounts/package.json b/web/packages/accounts/package.json index fb2c04b017..ee5c6f6cf0 100644 --- a/web/packages/accounts/package.json +++ b/web/packages/accounts/package.json @@ -15,7 +15,7 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { - "@types/react": "^19.1.5", + "@types/react": "^19.1.6", "@types/react-dom": "^19.1.5", "ente-build-config": "*" } diff --git a/web/packages/base/package.json b/web/packages/base/package.json index 73a0db5e57..0759336e30 100644 --- a/web/packages/base/package.json +++ b/web/packages/base/package.json @@ -12,21 +12,21 @@ "ente-utils": "*", "formik": "^2.4.6", "get-user-locale": "^3.0.0", - "i18next": "^25.2.0", + "i18next": "^25.2.1", "i18next-resources-to-backend": "^1.2.1", "idb": "^8.0.3", "libsodium-wrappers-sumo": "^0.7.15", "nanoid": "^5.1.5", - "next": "^15.3.2", + "next": "^15.3.3", "react": "^19.1.0", "react-dom": "^19.1.0", "react-i18next": "^15.5.2", "yup": "^1.6.1", - "zod": "^3.25.23" + "zod": "^3.25.48" }, "devDependencies": { "@types/libsodium-wrappers-sumo": "^0.7.8", - "@types/react": "^19.1.5", + "@types/react": "^19.1.6", "@types/react-dom": "^19.1.5", "ente-build-config": "*" } diff --git a/web/packages/build-config/package.json b/web/packages/build-config/package.json index 9b66ba0489..122e14c855 100644 --- a/web/packages/build-config/package.json +++ b/web/packages/build-config/package.json @@ -4,15 +4,15 @@ "private": true, "type": "module", "devDependencies": { - "@eslint/js": "^9.27.0", - "eslint": "^9.27.0", + "@eslint/js": "^9.28.0", + "eslint": "^9.28.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "prettier": "^3.5.3", "prettier-plugin-organize-imports": "^4.1.0", - "prettier-plugin-packagejson": "^2.5.14", + "prettier-plugin-packagejson": "^2.5.15", "typescript": "^5.8.3", - "typescript-eslint": "^8.32.1" + "typescript-eslint": "^8.33.0" } } diff --git a/web/packages/gallery/package.json b/web/packages/gallery/package.json index 3bab643a54..52f88ee70c 100644 --- a/web/packages/gallery/package.json +++ b/web/packages/gallery/package.json @@ -7,8 +7,8 @@ "bs58": "^6.0.0", "ente-base": "*", "ente-utils": "*", - "exifreader": "^4.30.1", - "hls-video-element": "^1.5.1", + "exifreader": "^4.31.0", + "hls-video-element": "^1.5.2", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.2", "media-chrome": "^4.10.0", @@ -19,7 +19,7 @@ }, "devDependencies": { "@types/leaflet": "^1.9.18", - "@types/react": "^19.1.5", + "@types/react": "^19.1.6", "@types/react-dom": "^19.1.5", "ente-build-config": "*" } diff --git a/web/packages/new/package.json b/web/packages/new/package.json index b4dff01d1f..6c2ca39a18 100644 --- a/web/packages/new/package.json +++ b/web/packages/new/package.json @@ -15,7 +15,7 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.5", + "@types/react": "^19.1.6", "@types/react-dom": "^19.1.5", "ente-build-config": "*" } diff --git a/web/yarn.lock b/web/yarn.lock index ef43afabed..6d0e5457a3 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -517,10 +517,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.27.0", "@eslint/js@^9.27.0": - version "9.27.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.27.0.tgz#181a23460877c484f6dd03890f4e3fa2fdeb8ff0" - integrity sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA== +"@eslint/js@9.28.0", "@eslint/js@^9.28.0": + version "9.28.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.28.0.tgz#7822ccc2f8cae7c3cd4f902377d520e9ae03f844" + integrity sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg== "@eslint/object-schema@^2.1.6": version "2.1.6" @@ -872,50 +872,50 @@ "@babel/runtime" "^7.25.7" "@mui/utils" "^5.16.6 || ^6.0.0 || ^7.0.0" -"@next/env@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/env/-/env-15.3.2.tgz#7143eafa9b11cfdf3d3c7318b0facb9dfdb2948f" - integrity sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g== +"@next/env@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/env/-/env-15.3.3.tgz#8c5548756df93efff1bf4bb4e9e430a763978155" + integrity sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw== -"@next/swc-darwin-arm64@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.2.tgz#1a7b36bf3c439f899065c878a580bc57a3630ec7" - integrity sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g== +"@next/swc-darwin-arm64@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.3.tgz#994de8515cdfb74d337bdad645c33605de44c68b" + integrity sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg== -"@next/swc-darwin-x64@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.2.tgz#3742026344f49128cf1b0f43814c67e880db7361" - integrity sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w== +"@next/swc-darwin-x64@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.3.tgz#71588bad245180ffd1af1e1f894477287e739eb0" + integrity sha512-XHdzH/yBc55lu78k/XwtuFR/ZXUTcflpRXcsu0nKmF45U96jt1tsOZhVrn5YH+paw66zOANpOnFQ9i6/j+UYvw== -"@next/swc-linux-arm64-gnu@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.2.tgz#fb29d45c034e3d2eef89b0e2801d62eb86155823" - integrity sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA== +"@next/swc-linux-arm64-gnu@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.3.tgz#66a15f749c14f04a89f8c7e21c7a8d343fc34e6e" + integrity sha512-VZ3sYL2LXB8znNGcjhocikEkag/8xiLgnvQts41tq6i+wql63SMS1Q6N8RVXHw5pEUjiof+II3HkDd7GFcgkzw== -"@next/swc-linux-arm64-musl@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.2.tgz#396784ef312666600ab1ae481e34cb1f6e3ae730" - integrity sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg== +"@next/swc-linux-arm64-musl@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.3.tgz#14bd66213f7f33d6909574750bcb05037221a2ac" + integrity sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA== -"@next/swc-linux-x64-gnu@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz#ac01fda376878e02bc6b57d1e88ab8ceae9f868e" - integrity sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg== +"@next/swc-linux-x64-gnu@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.3.tgz#4a19434545e5e752d9a3ed71f9b34982725f6293" + integrity sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw== -"@next/swc-linux-x64-musl@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz#327a5023003bcb3ca436efc08733f091bba2b1e8" - integrity sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w== +"@next/swc-linux-x64-musl@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.3.tgz#41ab140dd0a04ab7291adbec5836c1ce251a588c" + integrity sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw== -"@next/swc-win32-arm64-msvc@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.2.tgz#ce3a6588bd9c020960704011ab20bd0440026965" - integrity sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ== +"@next/swc-win32-arm64-msvc@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.3.tgz#fcd1d7e0007b7b73d1acdbf0ad6d91f7aa2deb15" + integrity sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ== -"@next/swc-win32-x64-msvc@15.3.2": - version "15.3.2" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.2.tgz#43cc36097ac27639e9024a5ceaa6e7727fa968c8" - integrity sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA== +"@next/swc-win32-x64-msvc@15.3.3": + version "15.3.3" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.3.tgz#c0e33e069d7922dd0546cac77a0247ad81d4a1aa" + integrity sha512-4QZG6F8enl9/S2+yIiOiju0iCTFd93d8VC1q9LZS4p/Xuk81W2QDjCFeoogmrWWkAD59z8ZxepBQap2dKS5ruw== "@noble/hashes@^1.2.0": version "1.7.0" @@ -1113,10 +1113,10 @@ dependencies: "@babel/types" "^7.20.7" -"@types/chromecast-caf-receiver@^6.0.21": - version "6.0.21" - resolved "https://registry.yarnpkg.com/@types/chromecast-caf-receiver/-/chromecast-caf-receiver-6.0.21.tgz#5638de51697ee4e44e2df3253d9bbd578bd0a9f8" - integrity sha512-T7jJyNUv3gHBc3oT5XGTYVyP/JDdCL8Rlgbf3uGLFLpfSwq11+AL3PVeQC7lgZuEUqxuphqZQf29BZ4SZeHLCw== +"@types/chromecast-caf-receiver@^6.0.22": + version "6.0.22" + resolved "https://registry.yarnpkg.com/@types/chromecast-caf-receiver/-/chromecast-caf-receiver-6.0.22.tgz#2e783ce14ba6d2f7f27e2c419bf78b73251ef187" + integrity sha512-V6AIfLm7KSHpFN/sAfzevP+/8wLGNbymDPedmf4YNaw6l32dJmjB2AZqXJRHeRaAi7ZWEZ8oJxqU79VKZBYAgg== "@types/estree@1.0.7": version "1.0.7" @@ -1170,10 +1170,10 @@ resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.14.tgz#f688f8d44e46ed61c401f82ff757581655fbcc42" integrity sha512-5Kv68fXuXK0iDuUir1WPGw2R9fOZUlYlSAa0ztMcL0s0BfIDTqg9GXz8K30VJpPP3sxWhbolnQma2x+/TfkzDQ== -"@types/node@^22.15.21": - version "22.15.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.21.tgz#196ef14fe20d87f7caf1e7b39832767f9a995b77" - integrity sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ== +"@types/node@^22.15.29": + version "22.15.29" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.29.tgz#c75999124a8224a3f79dd8b6ccfb37d74098f678" + integrity sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ== dependencies: undici-types "~6.21.0" @@ -1204,7 +1204,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^19.1.0", "@types/react@^19.1.5": +"@types/react@*", "@types/react@^19.1.0", "@types/react@^19.1.6": version "19.1.0" resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.0.tgz#73c43ad9bc43496ca8184332b111e2aef63fc9da" integrity sha512-UaicktuQI+9UKyA4njtDOGBD/67t8YEBt2xdfqu8+gP9hqPUPsiXlNPcpS2gVdjmis5GKPG3fCxbQLVgxsQZ8w== @@ -1216,62 +1216,78 @@ resolved "https://registry.yarnpkg.com/@types/zxcvbn/-/zxcvbn-4.4.5.tgz#8ce8623ed7a36e3a76d1c0b539708dfb2e859bc0" integrity sha512-FZJgC5Bxuqg7Rhsm/bx6gAruHHhDQ55r+s0JhDh8CQ16fD7NsJJ+p8YMMQDhSQoIrSmjpqqYWA96oQVMNkjRyA== -"@typescript-eslint/eslint-plugin@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz#9185b3eaa3b083d8318910e12d56c68b3c4f45b4" - integrity sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg== +"@typescript-eslint/eslint-plugin@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz#51ed03649575ba51bcee7efdbfd85283249b5447" + integrity sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.32.1" - "@typescript-eslint/type-utils" "8.32.1" - "@typescript-eslint/utils" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" + "@typescript-eslint/scope-manager" "8.33.0" + "@typescript-eslint/type-utils" "8.33.0" + "@typescript-eslint/utils" "8.33.0" + "@typescript-eslint/visitor-keys" "8.33.0" graphemer "^1.4.0" ignore "^7.0.0" natural-compare "^1.4.0" ts-api-utils "^2.1.0" -"@typescript-eslint/parser@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.32.1.tgz#18b0e53315e0bc22b2619d398ae49a968370935e" - integrity sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg== +"@typescript-eslint/parser@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.33.0.tgz#8e523c2b447ad7cd6ac91b719d8b37449481784d" + integrity sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ== dependencies: - "@typescript-eslint/scope-manager" "8.32.1" - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/typescript-estree" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" + "@typescript-eslint/scope-manager" "8.33.0" + "@typescript-eslint/types" "8.33.0" + "@typescript-eslint/typescript-estree" "8.33.0" + "@typescript-eslint/visitor-keys" "8.33.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz#9a6bf5fb2c5380e14fe9d38ccac6e4bbe17e8afc" - integrity sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA== +"@typescript-eslint/project-service@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.33.0.tgz#71f37ef9010de47bf20963914743c5cbef851e08" + integrity sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A== dependencies: - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" + "@typescript-eslint/tsconfig-utils" "^8.33.0" + "@typescript-eslint/types" "^8.33.0" + debug "^4.3.4" -"@typescript-eslint/type-utils@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz#b9292a45f69ecdb7db74d1696e57d1a89514d21e" - integrity sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA== +"@typescript-eslint/scope-manager@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz#459cf0c49d410800b1a023b973c62d699b09bf4c" + integrity sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw== dependencies: - "@typescript-eslint/typescript-estree" "8.32.1" - "@typescript-eslint/utils" "8.32.1" + "@typescript-eslint/types" "8.33.0" + "@typescript-eslint/visitor-keys" "8.33.0" + +"@typescript-eslint/tsconfig-utils@8.33.0", "@typescript-eslint/tsconfig-utils@^8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz#316adab038bbdc43e448781d5a816c2973eab73e" + integrity sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug== + +"@typescript-eslint/type-utils@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz#f06124b2d6db8a51b24990cb123c9543af93fef5" + integrity sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ== + dependencies: + "@typescript-eslint/typescript-estree" "8.33.0" + "@typescript-eslint/utils" "8.33.0" debug "^4.3.4" ts-api-utils "^2.1.0" -"@typescript-eslint/types@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.32.1.tgz#b19fe4ac0dc08317bae0ce9ec1168123576c1d4b" - integrity sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg== +"@typescript-eslint/types@8.33.0", "@typescript-eslint/types@^8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.33.0.tgz#02a7dbba611a8abf1ad2a9e00f72f7b94b5ab0ee" + integrity sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg== -"@typescript-eslint/typescript-estree@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz#9023720ca4ecf4f59c275a05b5fed69b1276face" - integrity sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg== +"@typescript-eslint/typescript-estree@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz#abcc1d3db75a8e9fd2e274ee8c4099fa2399abfd" + integrity sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ== dependencies: - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" + "@typescript-eslint/project-service" "8.33.0" + "@typescript-eslint/tsconfig-utils" "8.33.0" + "@typescript-eslint/types" "8.33.0" + "@typescript-eslint/visitor-keys" "8.33.0" debug "^4.3.4" fast-glob "^3.3.2" is-glob "^4.0.3" @@ -1279,22 +1295,22 @@ semver "^7.6.0" ts-api-utils "^2.1.0" -"@typescript-eslint/utils@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.32.1.tgz#4d6d5d29b9e519e9a85e9a74e9f7bdb58abe9704" - integrity sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA== +"@typescript-eslint/utils@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.33.0.tgz#574ad5edee371077b9e28ca6fb804f2440f447c1" + integrity sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw== dependencies: "@eslint-community/eslint-utils" "^4.7.0" - "@typescript-eslint/scope-manager" "8.32.1" - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/typescript-estree" "8.32.1" + "@typescript-eslint/scope-manager" "8.33.0" + "@typescript-eslint/types" "8.33.0" + "@typescript-eslint/typescript-estree" "8.33.0" -"@typescript-eslint/visitor-keys@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz#4321395cc55c2eb46036cbbb03e101994d11ddca" - integrity sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w== +"@typescript-eslint/visitor-keys@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz#fbae16fd3594531f8cad95d421125d634e9974fe" + integrity sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ== dependencies: - "@typescript-eslint/types" "8.32.1" + "@typescript-eslint/types" "8.33.0" eslint-visitor-keys "^4.2.0" "@vercel/edge@^1.2.1": @@ -1579,10 +1595,10 @@ chalk@^4.0.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chrono-node@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-2.8.0.tgz#3617452d3e871b40720dc9dee242b2933e3bfb77" - integrity sha512-//a/HhnCQ4zFHxRfi1m+jQwr8o0Gxsg0GUjZ39O6ud9lkhrnuLGX1oOKjGsivm9AVMS79cn0PmTa6JCRlzgfWA== +chrono-node@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-2.8.2.tgz#3d80af3208738ec42fe6021ea1f0362769337fff" + integrity sha512-7BJ6TRHs4sFtOSsTISWXGKjg7C70wD+NU+44uA6euGnbvZnECvgzUv+5TS9sICta2sBQWej4UYy8XAg4W9E7rQ== dependencies: dayjs "^1.10.0" @@ -1703,10 +1719,10 @@ csstype@^3.0.2, csstype@^3.1.3: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== -custom-media-element@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/custom-media-element/-/custom-media-element-1.4.2.tgz#5d70d357b7605deadce72341b7c0b97a447908d4" - integrity sha512-AM6FRWqJyW7pWTvXb4uJj6yvHE7C6UutdhJ5o3XO5NEl5aWFcfnpz8/TuW8qr1+/wfbj50wRvdArnSNjTmjmVw== +custom-media-element@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/custom-media-element/-/custom-media-element-1.4.3.tgz#aaa3a1ddb6b73a24ac49057414776e4485877880" + integrity sha512-6DAQAC9VfpdUEc684Kr1Uvw+49vKUY3+hAQcBrqMNL577PeA8eM8YgpOmbySoTZ517EIqSVFrXH2nu8gqFJS4g== data-view-buffer@^1.0.2: version "1.0.2" @@ -2056,10 +2072,10 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@^9.27.0: - version "9.27.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.27.0.tgz#a587d3cd5b844b68df7898944323a702afe38979" - integrity sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q== +eslint@^9.28.0: + version "9.28.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.28.0.tgz#b0bcbe82a16945a40906924bea75e8b4980ced7d" + integrity sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.12.1" @@ -2067,7 +2083,7 @@ eslint@^9.27.0: "@eslint/config-helpers" "^0.2.1" "@eslint/core" "^0.14.0" "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.27.0" + "@eslint/js" "9.28.0" "@eslint/plugin-kit" "^0.3.1" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" @@ -2130,10 +2146,10 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -exifreader@^4.30.1: - version "4.30.1" - resolved "https://registry.yarnpkg.com/exifreader/-/exifreader-4.30.1.tgz#4480d073bce4079e154a57d07cb0c46051d264e5" - integrity sha512-XoEKKQ0FmJwCKHnuErceFAM+MSfZ+px7Nci5BhBP1cgEHi/fHSBvQySsdfd0MaFHzNh8ITsRNwpnvkMuIPicrg== +exifreader@^4.31.0: + version "4.31.0" + resolved "https://registry.yarnpkg.com/exifreader/-/exifreader-4.31.0.tgz#d8e5e7cf1f8b4886c31eee6f9b990c8001b438ff" + integrity sha512-nwtWnoheEI0IuPQ9mascB105aMtj/Gg4GMP0Tbks2vw6UXrK2Bhab4Ogo9MKYW1KoyGj/FV6KVIhVbNCoj0WQA== optionalDependencies: "@xmldom/xmldom" "^0.9.4" @@ -2454,12 +2470,12 @@ heic-decode@^2.0.0: dependencies: libheif-js "^1.17.1" -hls-video-element@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/hls-video-element/-/hls-video-element-1.5.1.tgz#378ac0b92eefae90cc2ec314428783ccceb26384" - integrity sha512-9uY2PitTR1Pg8DX43NHz7BMuygXmoRfvaK7O/hno4VidcfAnm29pwgcIi5Cj4pkFwbu6FctSV1hyR5KEgkvtVQ== +hls-video-element@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/hls-video-element/-/hls-video-element-1.5.2.tgz#293bb185e7dc9006732639243e272f966e29ef71" + integrity sha512-IkKYLRQV6JBFHNmw6KdjNjbWE7AjT8BhU1pA/IfpayVOmiyMofWv5lNVOrUK8UxOnDiOucowgkd+w+IsfFuaJQ== dependencies: - custom-media-element "^1.4.2" + custom-media-element "^1.4.3" hls.js "^1.6.2" media-tracks "^0.3.3" @@ -2489,10 +2505,10 @@ i18next-resources-to-backend@^1.2.1: dependencies: "@babel/runtime" "^7.23.2" -i18next@^25.2.0: - version "25.2.0" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-25.2.0.tgz#9758164e64abfb9166fca70cba11347d50231ca8" - integrity sha512-ERhJICsxkw1vE7G0lhCUYv4ZxdBEs03qblt1myJs94rYRK9loJF3xDj8mgQz3LmCyp0yYrNjbN/1/GWZTZDGCA== +i18next@^25.2.1: + version "25.2.1" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-25.2.1.tgz#23cf8794904f551f577558d93c84b0fb6cd489a2" + integrity sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw== dependencies: "@babel/runtime" "^7.27.1" @@ -3082,12 +3098,12 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -next@^15.3.2: - version "15.3.2" - resolved "https://registry.yarnpkg.com/next/-/next-15.3.2.tgz#97510629e38a058dd154782a5c2ec9c9ab94d0d8" - integrity sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ== +next@^15.3.3: + version "15.3.3" + resolved "https://registry.yarnpkg.com/next/-/next-15.3.3.tgz#90ee73600af106796989136827a7a40f61dadd1f" + integrity sha512-JqNj29hHNmCLtNvd090SyRbXJiivQ+58XjCcrC50Crb5g5u2zi7Y2YivbsEfzk6AtVI80akdOQbaMZwWB1Hthw== dependencies: - "@next/env" "15.3.2" + "@next/env" "15.3.3" "@swc/counter" "0.1.3" "@swc/helpers" "0.5.15" busboy "1.6.0" @@ -3095,14 +3111,14 @@ next@^15.3.2: postcss "8.4.31" styled-jsx "5.1.6" optionalDependencies: - "@next/swc-darwin-arm64" "15.3.2" - "@next/swc-darwin-x64" "15.3.2" - "@next/swc-linux-arm64-gnu" "15.3.2" - "@next/swc-linux-arm64-musl" "15.3.2" - "@next/swc-linux-x64-gnu" "15.3.2" - "@next/swc-linux-x64-musl" "15.3.2" - "@next/swc-win32-arm64-msvc" "15.3.2" - "@next/swc-win32-x64-msvc" "15.3.2" + "@next/swc-darwin-arm64" "15.3.3" + "@next/swc-darwin-x64" "15.3.3" + "@next/swc-linux-arm64-gnu" "15.3.3" + "@next/swc-linux-arm64-musl" "15.3.3" + "@next/swc-linux-x64-gnu" "15.3.3" + "@next/swc-linux-x64-musl" "15.3.3" + "@next/swc-win32-arm64-msvc" "15.3.3" + "@next/swc-win32-x64-msvc" "15.3.3" sharp "^0.34.1" node-releases@^2.0.19: @@ -3319,13 +3335,13 @@ prettier-plugin-organize-imports@^4.1.0: resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz#f3d3764046a8e7ba6491431158b9be6ffd83b90f" integrity sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A== -prettier-plugin-packagejson@^2.5.14: - version "2.5.14" - resolved "https://registry.yarnpkg.com/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.14.tgz#8ada09114ff60c7f42c3f8755ffb2f8152f3624f" - integrity sha512-h+3tSpr2nVpp+YOK1MDIYtYhHVXr8/0V59UUbJpIJFaqi3w4fvUokJo6eV8W+vELrUXIZzJ+DKm5G7lYzrMcKQ== +prettier-plugin-packagejson@^2.5.15: + version "2.5.15" + resolved "https://registry.yarnpkg.com/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.15.tgz#7ea880d4bb1681b5331ea7044efd3d653776f469" + integrity sha512-2QSx6y4IT6LTwXtCvXAopENW5IP/aujC8fobEM2pDbs0IGkiVjW/ipPuYAHuXigbNe64aGWF7vIetukuzM3CBw== dependencies: sort-package-json "3.2.1" - synckit "0.11.6" + synckit "0.11.8" prettier@^3.5.3: version "3.5.3" @@ -3970,10 +3986,10 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -synckit@0.11.6: - version "0.11.6" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.6.tgz#e742a0c27bbc1fbc96f2010770521015cca7ed5c" - integrity sha512-2pR2ubZSV64f/vqm9eLPz/KOvR9Dm+Co/5ChLgeHl0yEDRc6h5hXHoxEQH8Y5Ljycozd3p1k5TTSVdzYGkPvLw== +synckit@0.11.8: + version "0.11.8" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.8.tgz#b2aaae998a4ef47ded60773ad06e7cb821f55457" + integrity sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A== dependencies: "@pkgr/core" "^0.2.4" @@ -4094,14 +4110,14 @@ typed-array-length@^1.0.7: possible-typed-array-names "^1.0.0" reflect.getprototypeof "^1.0.6" -typescript-eslint@^8.32.1: - version "8.32.1" - resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.32.1.tgz#1784335c781491be528ff84ab666e2f0f7591fd1" - integrity sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg== +typescript-eslint@^8.33.0: + version "8.33.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.33.0.tgz#89f733a90edc6abe0994b6130b964e781a1ba82f" + integrity sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ== dependencies: - "@typescript-eslint/eslint-plugin" "8.32.1" - "@typescript-eslint/parser" "8.32.1" - "@typescript-eslint/utils" "8.32.1" + "@typescript-eslint/eslint-plugin" "8.33.0" + "@typescript-eslint/parser" "8.33.0" + "@typescript-eslint/utils" "8.33.0" typescript@^5.8.3: version "5.8.3" @@ -4298,10 +4314,10 @@ yup@^1.6.1: toposort "^2.0.2" type-fest "^2.19.0" -zod@^3.25.23: - version "3.25.23" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.23.tgz#128fb02f3619a8bca6bbbf6b07b457236cf33391" - integrity sha512-Od2bdMosahjSrSgJtakrwjMDb1zM1A3VIHCPGveZt/3/wlrTWBya2lmEh2OYe4OIu8mPTmmr0gnLHIWQXdtWBg== +zod@^3.25.48: + version "3.25.48" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.48.tgz#6c2b536fbb519905e8f4a4ac58743de4d5331bb2" + integrity sha512-0X1mz8FtgEIvaxGjdIImYpZEaZMrund9pGXm3M6vM7Reba0e2eI71KPjSCGXBfwKDPwPoywf6waUKc3/tFvX2Q== zxcvbn@^4.4.2: version "4.4.2" From a2c032e77b99cdb4f01da41ce87a4ad7aceeffd5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 2 Jun 2025 19:27:11 +0530 Subject: [PATCH 009/194] desktop deps --- desktop/package.json | 8 +-- desktop/yarn.lock | 164 ++++++++++++++++++++++++------------------- 2 files changed, 94 insertions(+), 78 deletions(-) diff --git a/desktop/package.json b/desktop/package.json index 143e1b9160..936b42bf5e 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -39,10 +39,10 @@ "next-electron-server": "^1.0.0", "node-stream-zip": "^1.15.0", "onnxruntime-node": "^1.20.1", - "zod": "^3.25.23" + "zod": "^3.25.48" }, "devDependencies": { - "@eslint/js": "^9.27.0", + "@eslint/js": "^9.28.0", "@tsconfig/node22": "^22.0.2", "@types/auto-launch": "^5.0.5", "@types/ffmpeg-static": "^3.0.3", @@ -54,10 +54,10 @@ "eslint": "^9", "prettier": "3.5.3", "prettier-plugin-organize-imports": "^4.1.0", - "prettier-plugin-packagejson": "^2.5.14", + "prettier-plugin-packagejson": "^2.5.15", "shx": "^0.4.0", "typescript": "^5.8.3", - "typescript-eslint": "^8.32.1" + "typescript-eslint": "^8.33.0" }, "packageManager": "yarn@1.22.22", "productName": "ente" diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 00bd49e933..d6987ec772 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -184,10 +184,10 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.9.1.tgz#4a97e85e982099d6c7ee8410aacb55adaa576f06" integrity sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ== -"@eslint/js@^9.27.0": - version "9.27.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.27.0.tgz#181a23460877c484f6dd03890f4e3fa2fdeb8ff0" - integrity sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA== +"@eslint/js@^9.28.0": + version "9.28.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.28.0.tgz#7822ccc2f8cae7c3cd4f902377d520e9ae03f844" + integrity sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg== "@eslint/object-schema@^2.1.4": version "2.1.4" @@ -392,62 +392,78 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz#9185b3eaa3b083d8318910e12d56c68b3c4f45b4" - integrity sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg== +"@typescript-eslint/eslint-plugin@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz#51ed03649575ba51bcee7efdbfd85283249b5447" + integrity sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.32.1" - "@typescript-eslint/type-utils" "8.32.1" - "@typescript-eslint/utils" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" + "@typescript-eslint/scope-manager" "8.33.0" + "@typescript-eslint/type-utils" "8.33.0" + "@typescript-eslint/utils" "8.33.0" + "@typescript-eslint/visitor-keys" "8.33.0" graphemer "^1.4.0" ignore "^7.0.0" natural-compare "^1.4.0" ts-api-utils "^2.1.0" -"@typescript-eslint/parser@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.32.1.tgz#18b0e53315e0bc22b2619d398ae49a968370935e" - integrity sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg== +"@typescript-eslint/parser@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.33.0.tgz#8e523c2b447ad7cd6ac91b719d8b37449481784d" + integrity sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ== dependencies: - "@typescript-eslint/scope-manager" "8.32.1" - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/typescript-estree" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" + "@typescript-eslint/scope-manager" "8.33.0" + "@typescript-eslint/types" "8.33.0" + "@typescript-eslint/typescript-estree" "8.33.0" + "@typescript-eslint/visitor-keys" "8.33.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz#9a6bf5fb2c5380e14fe9d38ccac6e4bbe17e8afc" - integrity sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA== +"@typescript-eslint/project-service@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.33.0.tgz#71f37ef9010de47bf20963914743c5cbef851e08" + integrity sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A== dependencies: - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" + "@typescript-eslint/tsconfig-utils" "^8.33.0" + "@typescript-eslint/types" "^8.33.0" + debug "^4.3.4" -"@typescript-eslint/type-utils@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.32.1.tgz#b9292a45f69ecdb7db74d1696e57d1a89514d21e" - integrity sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA== +"@typescript-eslint/scope-manager@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz#459cf0c49d410800b1a023b973c62d699b09bf4c" + integrity sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw== dependencies: - "@typescript-eslint/typescript-estree" "8.32.1" - "@typescript-eslint/utils" "8.32.1" + "@typescript-eslint/types" "8.33.0" + "@typescript-eslint/visitor-keys" "8.33.0" + +"@typescript-eslint/tsconfig-utils@8.33.0", "@typescript-eslint/tsconfig-utils@^8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz#316adab038bbdc43e448781d5a816c2973eab73e" + integrity sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug== + +"@typescript-eslint/type-utils@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz#f06124b2d6db8a51b24990cb123c9543af93fef5" + integrity sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ== + dependencies: + "@typescript-eslint/typescript-estree" "8.33.0" + "@typescript-eslint/utils" "8.33.0" debug "^4.3.4" ts-api-utils "^2.1.0" -"@typescript-eslint/types@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.32.1.tgz#b19fe4ac0dc08317bae0ce9ec1168123576c1d4b" - integrity sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg== +"@typescript-eslint/types@8.33.0", "@typescript-eslint/types@^8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.33.0.tgz#02a7dbba611a8abf1ad2a9e00f72f7b94b5ab0ee" + integrity sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg== -"@typescript-eslint/typescript-estree@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz#9023720ca4ecf4f59c275a05b5fed69b1276face" - integrity sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg== +"@typescript-eslint/typescript-estree@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz#abcc1d3db75a8e9fd2e274ee8c4099fa2399abfd" + integrity sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ== dependencies: - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/visitor-keys" "8.32.1" + "@typescript-eslint/project-service" "8.33.0" + "@typescript-eslint/tsconfig-utils" "8.33.0" + "@typescript-eslint/types" "8.33.0" + "@typescript-eslint/visitor-keys" "8.33.0" debug "^4.3.4" fast-glob "^3.3.2" is-glob "^4.0.3" @@ -455,22 +471,22 @@ semver "^7.6.0" ts-api-utils "^2.1.0" -"@typescript-eslint/utils@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.32.1.tgz#4d6d5d29b9e519e9a85e9a74e9f7bdb58abe9704" - integrity sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA== +"@typescript-eslint/utils@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.33.0.tgz#574ad5edee371077b9e28ca6fb804f2440f447c1" + integrity sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw== dependencies: "@eslint-community/eslint-utils" "^4.7.0" - "@typescript-eslint/scope-manager" "8.32.1" - "@typescript-eslint/types" "8.32.1" - "@typescript-eslint/typescript-estree" "8.32.1" + "@typescript-eslint/scope-manager" "8.33.0" + "@typescript-eslint/types" "8.33.0" + "@typescript-eslint/typescript-estree" "8.33.0" -"@typescript-eslint/visitor-keys@8.32.1": - version "8.32.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz#4321395cc55c2eb46036cbbb03e101994d11ddca" - integrity sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w== +"@typescript-eslint/visitor-keys@8.33.0": + version "8.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz#fbae16fd3594531f8cad95d421125d634e9974fe" + integrity sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ== dependencies: - "@typescript-eslint/types" "8.32.1" + "@typescript-eslint/types" "8.33.0" eslint-visitor-keys "^4.2.0" "@xmldom/xmldom@^0.8.8": @@ -2664,13 +2680,13 @@ prettier-plugin-organize-imports@^4.1.0: resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz#f3d3764046a8e7ba6491431158b9be6ffd83b90f" integrity sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A== -prettier-plugin-packagejson@^2.5.14: - version "2.5.14" - resolved "https://registry.yarnpkg.com/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.14.tgz#8ada09114ff60c7f42c3f8755ffb2f8152f3624f" - integrity sha512-h+3tSpr2nVpp+YOK1MDIYtYhHVXr8/0V59UUbJpIJFaqi3w4fvUokJo6eV8W+vELrUXIZzJ+DKm5G7lYzrMcKQ== +prettier-plugin-packagejson@^2.5.15: + version "2.5.15" + resolved "https://registry.yarnpkg.com/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.15.tgz#7ea880d4bb1681b5331ea7044efd3d653776f469" + integrity sha512-2QSx6y4IT6LTwXtCvXAopENW5IP/aujC8fobEM2pDbs0IGkiVjW/ipPuYAHuXigbNe64aGWF7vIetukuzM3CBw== dependencies: sort-package-json "3.2.1" - synckit "0.11.6" + synckit "0.11.8" prettier@3.5.3: version "3.5.3" @@ -3108,10 +3124,10 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -synckit@0.11.6: - version "0.11.6" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.6.tgz#e742a0c27bbc1fbc96f2010770521015cca7ed5c" - integrity sha512-2pR2ubZSV64f/vqm9eLPz/KOvR9Dm+Co/5ChLgeHl0yEDRc6h5hXHoxEQH8Y5Ljycozd3p1k5TTSVdzYGkPvLw== +synckit@0.11.8: + version "0.11.8" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.8.tgz#b2aaae998a4ef47ded60773ad06e7cb821f55457" + integrity sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A== dependencies: "@pkgr/core" "^0.2.4" @@ -3235,14 +3251,14 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript-eslint@^8.32.1: - version "8.32.1" - resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.32.1.tgz#1784335c781491be528ff84ab666e2f0f7591fd1" - integrity sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg== +typescript-eslint@^8.33.0: + version "8.33.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.33.0.tgz#89f733a90edc6abe0994b6130b964e781a1ba82f" + integrity sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ== dependencies: - "@typescript-eslint/eslint-plugin" "8.32.1" - "@typescript-eslint/parser" "8.32.1" - "@typescript-eslint/utils" "8.32.1" + "@typescript-eslint/eslint-plugin" "8.33.0" + "@typescript-eslint/parser" "8.33.0" + "@typescript-eslint/utils" "8.33.0" typescript@^5.4.3, typescript@^5.8.3: version "5.8.3" @@ -3405,7 +3421,7 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zod@^3.25.23: - version "3.25.23" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.23.tgz#128fb02f3619a8bca6bbbf6b07b457236cf33391" - integrity sha512-Od2bdMosahjSrSgJtakrwjMDb1zM1A3VIHCPGveZt/3/wlrTWBya2lmEh2OYe4OIu8mPTmmr0gnLHIWQXdtWBg== +zod@^3.25.48: + version "3.25.48" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.48.tgz#6c2b536fbb519905e8f4a4ac58743de4d5331bb2" + integrity sha512-0X1mz8FtgEIvaxGjdIImYpZEaZMrund9pGXm3M6vM7Reba0e2eI71KPjSCGXBfwKDPwPoywf6waUKc3/tFvX2Q== From 414265de4a3d23c26b2343db2ac44abf3e103177 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 2 Jun 2025 19:28:55 +0530 Subject: [PATCH 010/194] mui update --- web/packages/base/package.json | 4 ++-- web/packages/new/package.json | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/packages/base/package.json b/web/packages/base/package.json index 0759336e30..a159981787 100644 --- a/web/packages/base/package.json +++ b/web/packages/base/package.json @@ -6,8 +6,8 @@ "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@fontsource-variable/inter": "^5.2.5", - "@mui/icons-material": "^6.4.11", - "@mui/material": "^6.4.11", + "@mui/icons-material": "^7.1.1", + "@mui/material": "^7.1.1", "comlink": "^4.4.2", "ente-utils": "*", "formik": "^2.4.6", diff --git a/web/packages/new/package.json b/web/packages/new/package.json index 6c2ca39a18..896fbf1179 100644 --- a/web/packages/new/package.json +++ b/web/packages/new/package.json @@ -3,9 +3,9 @@ "version": "0.0.0", "private": true, "dependencies": { - "@mui/material": "^6.4.11", - "@mui/system": "^6.4.11", - "@mui/x-date-pickers": "^7.29.3", + "@mui/material": "^7.1.1", + "@mui/system": "^7.1.1", + "@mui/x-date-pickers": "^8.5.0", "dayjs": "^1.11.13", "ente-base": "*", "ente-gallery": "*", From 7cbc80adc6e0da1bd377929c0b46f49414eee1e5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 2 Jun 2025 19:46:52 +0530 Subject: [PATCH 011/194] Stick to non-private imports --- web/packages/base/components/utils/theme.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/web/packages/base/components/utils/theme.ts b/web/packages/base/components/utils/theme.ts index 4792d4d8d4..c90cbf243c 100644 --- a/web/packages/base/components/utils/theme.ts +++ b/web/packages/base/components/utils/theme.ts @@ -1,7 +1,6 @@ -import type { Theme } from "@mui/material"; +import type { Theme, TypographyVariantsOptions } from "@mui/material"; import { createTheme } from "@mui/material"; -import type { Components } from "@mui/material/styles/components"; -import type { TypographyOptions } from "@mui/material/styles/createTypography"; +import type { Components } from "@mui/material/styles"; import type { AppName } from "ente-base/app"; const getTheme = (appName: AppName): Theme => { @@ -411,7 +410,7 @@ const getColorSchemes = (colors: ReturnType) => ({ * to bother with the light variant (though for consistency of specifying every * value, we alias it the same weight as regular, 500). */ -const typography: TypographyOptions = { +const typography: TypographyVariantsOptions = { fontFamily: '"Inter Variable", sans-serif', fontWeightLight: 500, fontWeightRegular: 500 /* CSS baseline reset sets this as the default */, From 4bfc495f398d7ee80e74ea598d5b6397f950a371 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 2 Jun 2025 19:58:40 +0530 Subject: [PATCH 012/194] Fix errors on ci [eslint] /home/runner/work/ente/ente/web/packages/new/photos/components/ImageEditorOverlay.tsx [eslint] 1313:41 error This assertion is unnecessary since it does not change the type of the expression @typescript-eslint/no-unnecessary-type-assertion [eslint] 1325:39 error This assertion is unnecessary since it does not change the type of the expression @typescript-eslint/no-unnecessary-type-assertion [eslint] 1338:35 error This assertion is unnecessary since it does not change the type of the expression @typescript-eslint/no-unnecessary-type-assertion [eslint] 1350:41 error This assertion is unnecessary since it does not change the type of the expression @typescript-eslint/no-unnecessary-type-assertion --- web/packages/new/photos/components/ImageEditorOverlay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/new/photos/components/ImageEditorOverlay.tsx b/web/packages/new/photos/components/ImageEditorOverlay.tsx index aac0019815..4db3ef883f 100644 --- a/web/packages/new/photos/components/ImageEditorOverlay.tsx +++ b/web/packages/new/photos/components/ImageEditorOverlay.tsx @@ -4,6 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable @typescript-eslint/prefer-promise-reject-errors */ +/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import CloseIcon from "@mui/icons-material/Close"; @@ -767,7 +768,6 @@ const canvasToFile = async ( break; } - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const blob = (await new Promise((resolve) => // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore From d0ddce2803118a59df6b62da192c68f1f6228cf1 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Mon, 2 Jun 2025 20:16:12 +0200 Subject: [PATCH 013/194] Avoid flashing dialog when leaving newly created album --- mobile/lib/ui/collections/album/new_row_item.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mobile/lib/ui/collections/album/new_row_item.dart b/mobile/lib/ui/collections/album/new_row_item.dart index 3e000cd12d..0415f37dcf 100644 --- a/mobile/lib/ui/collections/album/new_row_item.dart +++ b/mobile/lib/ui/collections/album/new_row_item.dart @@ -42,6 +42,10 @@ class NewAlbumRowItemWidget extends StatelessWidget { try { final Collection c = await CollectionsService.instance.createAlbum(text); + + // Close the dialog now so that it does not flash when leaving the album again. + Navigator.of(context).pop(); + // ignore: unawaited_futures await routeToPage( context, From fda4f47cba691b91a800386a9211f43f6a639287 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 08:45:41 +0530 Subject: [PATCH 014/194] disabled is not a color lint errors on ci --- web/packages/base/components/utils/theme.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/web/packages/base/components/utils/theme.ts b/web/packages/base/components/utils/theme.ts index c90cbf243c..6962c90617 100644 --- a/web/packages/base/components/utils/theme.ts +++ b/web/packages/base/components/utils/theme.ts @@ -696,11 +696,8 @@ const components: Components = { props: { color: "secondary" }, style: { color: "var(--mui-palette-stroke-muted)" }, }, - { - props: { color: "disabled" }, - style: { color: "var(--mui-palette-stroke-faint)" }, - }, ], + "&.Mui-disabled": { color: "var(--mui-palette-stroke-faint)" }, }, }, }, From dbbcd44f5b6891d5ddf7d93495bbceec945ac8d6 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 09:23:41 +0530 Subject: [PATCH 015/194] Fwd headers --- infra/workers/files/src/index.ts | 15 +++++++++------ infra/workers/public-albums/src/index.ts | 7 +++++++ infra/workers/thumbnails/src/index.ts | 10 +++++++++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/infra/workers/files/src/index.ts b/infra/workers/files/src/index.ts index 5b9452a450..d6c6aacb48 100644 --- a/infra/workers/files/src/index.ts +++ b/infra/workers/files/src/index.ts @@ -21,7 +21,8 @@ const handleOPTIONS = (request: Request) => { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, OPTIONS", - "Access-Control-Allow-Headers": "X-Auth-Token, X-Client-Package, X-Client-Version", + "Access-Control-Allow-Headers": + "X-Auth-Token, X-Client-Package, X-Client-Version", "Access-Control-Max-Age": "86400", }, }); @@ -71,13 +72,15 @@ const handleGET = async (request: Request) => { const params = new URLSearchParams(); if (token) params.set("token", token); + const headers = { + "X-Client-Package": request.headers.get("X-Client-Package") ?? "", + "X-Client-Version": request.headers.get("X-Client-Version") ?? "", + "User-Agent": request.headers.get("User-Agent") ?? "", + }; + let response = await fetch( `https://api.ente.io/files/download/${fileID}?${params.toString()}`, - { - headers: { - "User-Agent": request.headers.get("User-Agent") ?? "", - }, - }, + { headers }, ); if (!response.ok) console.log("Upstream error", response.status); diff --git a/infra/workers/public-albums/src/index.ts b/infra/workers/public-albums/src/index.ts index 48fed6e38f..75751a867a 100644 --- a/infra/workers/public-albums/src/index.ts +++ b/infra/workers/public-albums/src/index.ts @@ -70,8 +70,15 @@ const handleGET = async (request: Request) => { if (accessToken) params.set("accessToken", accessToken); if (accessTokenJWT) params.set("accessTokenJWT", accessTokenJWT); + const headers = { + "X-Client-Package": request.headers.get("X-Client-Package") ?? "", + "X-Client-Version": request.headers.get("X-Client-Version") ?? "", + "User-Agent": request.headers.get("User-Agent") ?? "", + }; + let response = await fetch( `https://api.ente.io/public-collection/files${pathname}${fileID}?${params.toString()}`, + { headers }, ); if (!response.ok) console.log("Upstream error", response.status); diff --git a/infra/workers/thumbnails/src/index.ts b/infra/workers/thumbnails/src/index.ts index 2108e50258..2e2bd89733 100644 --- a/infra/workers/thumbnails/src/index.ts +++ b/infra/workers/thumbnails/src/index.ts @@ -21,7 +21,8 @@ const handleOPTIONS = (request: Request) => { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, OPTIONS", - "Access-Control-Allow-Headers": "X-Auth-Token, X-Client-Package, X-Client-Version", + "Access-Control-Allow-Headers": + "X-Auth-Token, X-Client-Package, X-Client-Version", "Access-Control-Max-Age": "86400", }, }); @@ -64,8 +65,15 @@ const handleGET = async (request: Request) => { const params = new URLSearchParams(); if (token) params.set("token", token); + const headers = { + "X-Client-Package": request.headers.get("X-Client-Package") ?? "", + "X-Client-Version": request.headers.get("X-Client-Version") ?? "", + "User-Agent": request.headers.get("User-Agent") ?? "", + }; + let response = await fetch( `https://api.ente.io/files/preview/${fileID}?${params.toString()}`, + { headers }, ); if (!response.ok) console.log("Upstream error", response.status); From a5f266421a5e81453d1690ec07ad1ee7ad9e7d47 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 09:31:14 +0530 Subject: [PATCH 016/194] Also from cast --- infra/workers/cast-albums/src/index.ts | 10 +++++++++- web/apps/cast/src/services/render.ts | 10 ++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/infra/workers/cast-albums/src/index.ts b/infra/workers/cast-albums/src/index.ts index 5111d446cd..fc93d93443 100644 --- a/infra/workers/cast-albums/src/index.ts +++ b/infra/workers/cast-albums/src/index.ts @@ -22,7 +22,8 @@ const handleOPTIONS = (request: Request) => { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, OPTIONS", "Access-Control-Max-Age": "86400", - "Access-Control-Allow-Headers": "X-Cast-Access-Token", + "Access-Control-Allow-Headers": + "X-Cast-Access-Token, X-Client-Package, X-Client-Version", }, }); }; @@ -60,8 +61,15 @@ const handleGET = async (request: Request) => { const pathname = url.pathname; const params = new URLSearchParams({ castToken }); + const headers = { + "X-Client-Package": request.headers.get("X-Client-Package") ?? "", + "X-Client-Version": request.headers.get("X-Client-Version") ?? "", + "User-Agent": request.headers.get("User-Agent") ?? "", + }; + let response = await fetch( `https://api.ente.io/cast/files${pathname}${fileID}?${params.toString()}`, + { headers }, ); if (!response.ok) console.log("Upstream error", response.status); diff --git a/web/apps/cast/src/services/render.ts b/web/apps/cast/src/services/render.ts index f3358b5bba..60fc7d5ea2 100644 --- a/web/apps/cast/src/services/render.ts +++ b/web/apps/cast/src/services/render.ts @@ -8,6 +8,7 @@ import type { AxiosResponse } from "axios"; import { sharedCryptoWorker } from "ente-base/crypto"; import { nameAndExtension } from "ente-base/file-name"; +import { publicRequestHeaders } from "ente-base/http"; import log from "ente-base/log"; import { apiURL, customAPIOrigin } from "ente-base/origins"; import type { @@ -320,13 +321,18 @@ const downloadFile = async ( const baseURL = shouldUseThumbnail ? `${customOrigin}/cast/files/preview/${file.id}` : `${customOrigin}/cast/files/download/${file.id}`; - return fetch(`${baseURL}?${params.toString()}`); + return fetch(`${baseURL}?${params.toString()}`, { + headers: publicRequestHeaders(), + }); } else { const url = shouldUseThumbnail ? `https://cast-albums.ente.io/preview/?fileID=${file.id}` : `https://cast-albums.ente.io/download/?fileID=${file.id}`; return fetch(url, { - headers: { "X-Cast-Access-Token": castToken }, + headers: { + ...publicRequestHeaders(), + "X-Cast-Access-Token": castToken, + }, }); } }; From 1d7baf9dd6e6c67ef70fcff9fe44591b684acd7d Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Tue, 3 Jun 2025 09:38:38 +0530 Subject: [PATCH 017/194] chore: remove unwanted author copyright --- mobile/ios/EnteAlbumWidget/EnteAlbumWidget.swift | 4 ---- mobile/ios/EnteMemoryWidget/EnteMemoryWidget.swift | 4 ---- mobile/ios/EntePeopleWidget/EntePeopleWidget.swift | 4 ---- 3 files changed, 12 deletions(-) diff --git a/mobile/ios/EnteAlbumWidget/EnteAlbumWidget.swift b/mobile/ios/EnteAlbumWidget/EnteAlbumWidget.swift index 0a4f9d6e76..76fde7c103 100644 --- a/mobile/ios/EnteAlbumWidget/EnteAlbumWidget.swift +++ b/mobile/ios/EnteAlbumWidget/EnteAlbumWidget.swift @@ -1,10 +1,6 @@ // // EnteAlbumWidget.swift // EnteAlbumWidget -// -// Created by Prateek Sunal on 5/15/25. -// Copyright © 2025 The Chromium Authors. All rights reserved. -// import SwiftUI import UIKit diff --git a/mobile/ios/EnteMemoryWidget/EnteMemoryWidget.swift b/mobile/ios/EnteMemoryWidget/EnteMemoryWidget.swift index 4382deab9b..354383b3b5 100644 --- a/mobile/ios/EnteMemoryWidget/EnteMemoryWidget.swift +++ b/mobile/ios/EnteMemoryWidget/EnteMemoryWidget.swift @@ -1,10 +1,6 @@ // // EnteMemoryWidget.swift // EnteMemoryWidget -// -// Created by Prateek Sunal on 3/7/25. -// Copyright © 2025 The Chromium Authors. All rights reserved. -// import SwiftUI import UIKit diff --git a/mobile/ios/EntePeopleWidget/EntePeopleWidget.swift b/mobile/ios/EntePeopleWidget/EntePeopleWidget.swift index 951c75767f..231356181b 100644 --- a/mobile/ios/EntePeopleWidget/EntePeopleWidget.swift +++ b/mobile/ios/EntePeopleWidget/EntePeopleWidget.swift @@ -1,10 +1,6 @@ // // EntePeopleWidget.swift // EntePeopleWidget -// -// Created by Prateek Sunal on 5/15/25. -// Copyright © 2025 The Chromium Authors. All rights reserved. -// import SwiftUI import UIKit From 9ee52b5deea6129ea274a5f8c65cd1b780056fee Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Tue, 3 Jun 2025 09:43:06 +0530 Subject: [PATCH 018/194] fix: don't depend on FaceFilter for fetching personId --- mobile/lib/services/people_home_widget_service.dart | 8 +++----- .../ui/viewer/search/result/people_section_all_page.dart | 7 +++---- mobile/lib/ui/viewer/search_tab/people_section.dart | 5 ++--- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/mobile/lib/services/people_home_widget_service.dart b/mobile/lib/services/people_home_widget_service.dart index ee758caac0..39eddd0694 100644 --- a/mobile/lib/services/people_home_widget_service.dart +++ b/mobile/lib/services/people_home_widget_service.dart @@ -7,7 +7,7 @@ import 'package:logging/logging.dart'; import 'package:photos/db/files_db.dart'; import 'package:photos/models/file/file.dart'; import "package:photos/models/search/generic_search_result.dart"; -import "package:photos/models/search/hierarchical/face_filter.dart"; +import "package:photos/models/search/search_constants.dart"; import "package:photos/models/search/search_types.dart"; import 'package:photos/service_locator.dart'; import 'package:photos/services/home_widget_service.dart'; @@ -292,16 +292,14 @@ class PeopleHomeWidgetService { // Search Filter with face and pick top two faces final searchFilter = await SectionType.face.getData(null).then( (value) => List.from(value).where( - (element) => - (element.hierarchicalSearchFilter as FaceFilter).personId != - null, + (element) => (element.params[kPersonParamID] as String?) != null, ), ); if (searchFilter.isNotEmpty) { peopleIds = searchFilter .take(2) - .map((e) => (e.hierarchicalSearchFilter as FaceFilter).personId!) + .map((e) => e.params[kPersonParamID] as String) .toList(); } else { _logger.warning("No selected people found"); diff --git a/mobile/lib/ui/viewer/search/result/people_section_all_page.dart b/mobile/lib/ui/viewer/search/result/people_section_all_page.dart index a24e02d63b..5237a62890 100644 --- a/mobile/lib/ui/viewer/search/result/people_section_all_page.dart +++ b/mobile/lib/ui/viewer/search/result/people_section_all_page.dart @@ -5,7 +5,7 @@ import "package:flutter_animate/flutter_animate.dart"; import "package:photos/events/event.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/models/search/generic_search_result.dart"; -import "package:photos/models/search/hierarchical/face_filter.dart"; +import "package:photos/models/search/search_constants.dart"; import "package:photos/models/search/search_types.dart"; import "package:photos/models/selected_people.dart"; import "package:photos/theme/ente_theme.dart"; @@ -70,14 +70,13 @@ class _PeopleSectionAllWidgetState extends State { if (widget.namedOnly) { results.removeWhere( - (element) => - (element.hierarchicalSearchFilter as FaceFilter).personId == null, + (element) => element.params[kPersonParamID] == null, ); if (widget.selectedPeople?.personIds.isEmpty ?? false) { widget.selectedPeople!.select( results .take(2) - .map((e) => (e.hierarchicalSearchFilter as FaceFilter).personId!) + .map((e) => e.params[kPersonParamID] as String) .toSet(), ); } diff --git a/mobile/lib/ui/viewer/search_tab/people_section.dart b/mobile/lib/ui/viewer/search_tab/people_section.dart index b90b5d1efa..4bd45e2889 100644 --- a/mobile/lib/ui/viewer/search_tab/people_section.dart +++ b/mobile/lib/ui/viewer/search_tab/people_section.dart @@ -203,9 +203,8 @@ class PersonSearchExample extends StatelessWidget { }); void toggleSelection() { - selectedPeople?.toggleSelection( - (searchResult.hierarchicalSearchFilter as FaceFilter).personId!, - ); + selectedPeople + ?.toggleSelection(searchResult.params[kPersonParamID]! as String); } @override From 265bad28b08084be592abeba503fc4dab2d1dcfe Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Tue, 3 Jun 2025 09:45:02 +0530 Subject: [PATCH 019/194] fix: people selection logic as well --- mobile/lib/ui/viewer/search_tab/people_section.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mobile/lib/ui/viewer/search_tab/people_section.dart b/mobile/lib/ui/viewer/search_tab/people_section.dart index 4bd45e2889..993c31a99a 100644 --- a/mobile/lib/ui/viewer/search_tab/people_section.dart +++ b/mobile/lib/ui/viewer/search_tab/people_section.dart @@ -6,7 +6,6 @@ import "package:photos/events/event.dart"; import "package:photos/models/file/file.dart"; import "package:photos/models/ml/face/person.dart"; import "package:photos/models/search/generic_search_result.dart"; -import "package:photos/models/search/hierarchical/face_filter.dart"; import "package:photos/models/search/recent_searches.dart"; import "package:photos/models/search/search_constants.dart"; import "package:photos/models/search/search_result.dart"; @@ -217,9 +216,9 @@ class PersonSearchExample extends StatelessWidget { return ListenableBuilder( listenable: selectedPeople ?? ValueNotifier(false), builder: (context, _) { - final filter = (searchResult.hierarchicalSearchFilter as FaceFilter); - final id = filter.personId ?? filter.clusterId ?? ""; - final bool isSelected = selectedPeople?.isPersonSelected(id) ?? false; + final id = searchResult.params[kPersonParamID] as String?; + final bool isSelected = + id != null ? selectedPeople?.isPersonSelected(id) ?? false : false; return GestureDetector( onTap: selectedPeople != null From a227e8541dbc4f876cc77585781669ff7e4da837 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 10:12:40 +0530 Subject: [PATCH 020/194] + range for files --- infra/workers/files/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infra/workers/files/src/index.ts b/infra/workers/files/src/index.ts index d6c6aacb48..904a669acc 100644 --- a/infra/workers/files/src/index.ts +++ b/infra/workers/files/src/index.ts @@ -22,7 +22,7 @@ const handleOPTIONS = (request: Request) => { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, OPTIONS", "Access-Control-Allow-Headers": - "X-Auth-Token, X-Client-Package, X-Client-Version", + "X-Auth-Token, X-Client-Package, X-Client-Version, Range", "Access-Control-Max-Age": "86400", }, }); @@ -76,6 +76,7 @@ const handleGET = async (request: Request) => { "X-Client-Package": request.headers.get("X-Client-Package") ?? "", "X-Client-Version": request.headers.get("X-Client-Version") ?? "", "User-Agent": request.headers.get("User-Agent") ?? "", + "Range": request.headers.get("Range") ?? "", }; let response = await fetch( From 9dd8cd35585d6719397753a2204273b3e53a4a8b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 10:27:52 +0530 Subject: [PATCH 021/194] up --- infra/workers/cast-albums/wrangler.toml | 2 +- infra/workers/files/wrangler.toml | 2 +- infra/workers/package.json | 4 ++-- infra/workers/public-albums/wrangler.toml | 2 +- infra/workers/thumbnails/wrangler.toml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/infra/workers/cast-albums/wrangler.toml b/infra/workers/cast-albums/wrangler.toml index f81f8b52bf..3496aebe7a 100644 --- a/infra/workers/cast-albums/wrangler.toml +++ b/infra/workers/cast-albums/wrangler.toml @@ -1,6 +1,6 @@ name = "cast-albums" main = "src/index.ts" -compatibility_date = "2024-06-14" +compatibility_date = "2025-06-03" routes = [ { pattern = "cast-albums.ente.io", custom_domain = true } diff --git a/infra/workers/files/wrangler.toml b/infra/workers/files/wrangler.toml index 52349d8d03..12e27ade88 100644 --- a/infra/workers/files/wrangler.toml +++ b/infra/workers/files/wrangler.toml @@ -1,6 +1,6 @@ name = "files" main = "src/index.ts" -compatibility_date = "2024-06-14" +compatibility_date = "2025-06-03" routes = [ { pattern = "files.ente.io", custom_domain = true } diff --git a/infra/workers/package.json b/infra/workers/package.json index e9e27cde23..7b61f25bd8 100644 --- a/infra/workers/package.json +++ b/infra/workers/package.json @@ -2,9 +2,9 @@ "name": "workers", "private": true, "devDependencies": { - "@cloudflare/workers-types": "^4.20250519.0", + "@cloudflare/workers-types": "^4.20250603.0", "typescript": "^5.8.3", - "wrangler": "^4.15.2", + "wrangler": "^4.18.0", "prettier": "^3.5.3" }, "workspaces": [ diff --git a/infra/workers/public-albums/wrangler.toml b/infra/workers/public-albums/wrangler.toml index 9adad20f04..4643736fd6 100644 --- a/infra/workers/public-albums/wrangler.toml +++ b/infra/workers/public-albums/wrangler.toml @@ -1,6 +1,6 @@ name = "public-albums" main = "src/index.ts" -compatibility_date = "2024-06-14" +compatibility_date = "2025-06-03" routes = [ { pattern = "public-albums.ente.io", custom_domain = true } diff --git a/infra/workers/thumbnails/wrangler.toml b/infra/workers/thumbnails/wrangler.toml index 8f45b859e8..8c7117c848 100644 --- a/infra/workers/thumbnails/wrangler.toml +++ b/infra/workers/thumbnails/wrangler.toml @@ -1,6 +1,6 @@ name = "thumbnails" main = "src/index.ts" -compatibility_date = "2024-06-14" +compatibility_date = "2025-06-03" routes = [ { pattern = "thumbnails.ente.io", custom_domain = true } From 55fd87face871a3ca6791b6b96db6aa393056027 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Tue, 3 Jun 2025 12:42:25 +0530 Subject: [PATCH 022/194] [server] Fix migration index --- .../{99_add_upgraded_at.down.sql => 100_add_upgraded_at.down.sql} | 0 .../{99_add_upgraded_at.up.sql => 100_add_upgraded_at.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename server/migrations/{99_add_upgraded_at.down.sql => 100_add_upgraded_at.down.sql} (100%) rename server/migrations/{99_add_upgraded_at.up.sql => 100_add_upgraded_at.up.sql} (100%) diff --git a/server/migrations/99_add_upgraded_at.down.sql b/server/migrations/100_add_upgraded_at.down.sql similarity index 100% rename from server/migrations/99_add_upgraded_at.down.sql rename to server/migrations/100_add_upgraded_at.down.sql diff --git a/server/migrations/99_add_upgraded_at.up.sql b/server/migrations/100_add_upgraded_at.up.sql similarity index 100% rename from server/migrations/99_add_upgraded_at.up.sql rename to server/migrations/100_add_upgraded_at.up.sql From d6060e119458dde1d0c1e269080b0a1f84ba081a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 12:50:39 +0530 Subject: [PATCH 023/194] Extract --- web/packages/accounts/utils/recovery-key.ts | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 web/packages/accounts/utils/recovery-key.ts diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts new file mode 100644 index 0000000000..15bcd13e90 --- /dev/null +++ b/web/packages/accounts/utils/recovery-key.ts @@ -0,0 +1,49 @@ +import { sharedCryptoWorker } from "ente-base/crypto"; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const bip39 = require("bip39"); +// mobile client library only supports english. +bip39.setDefaultWordlist("english"); + +/** + * Decrypt the provided data that was encrypted with the user's recovery key + * using the recovery key derived from the provided mnemonic string. + * + * @param recoveryKey The BIP-39 mnemonic (24 word) string representing the + * recovery key. For legacy compatibility, the function also works if provided + * the hex representation of the recovery key. + * + * @param encryptedData The data to decrypt. The data should've been encrypted + * using the same recovery key otherwise the decryption would fail. + * + * @param decryptionNonce The nonce that was using encryption. + * + * @returns A base64 string representing the decrypted data. + */ +export const decryptUsingRecoveryKeyMnemonic = async ( + recoveryKey: string, + encryptedData: any, + decryptionNonce: any, +) => { + recoveryKey = recoveryKey + .trim() + .split(" ") + .map((part) => part.trim()) + .filter((part) => !!part) + .join(" "); + + // Check if user is entering mnemonic recovery key. + if (recoveryKey.indexOf(" ") > 0) { + if (recoveryKey.split(" ").length != 24) { + throw new Error("recovery code should have 24 words"); + } + recoveryKey = bip39.mnemonicToEntropy(recoveryKey); + } + + const cryptoWorker = await sharedCryptoWorker(); + return cryptoWorker.decryptB64( + encryptedData, + decryptionNonce, + await cryptoWorker.fromHex(recoveryKey), + ); +}; From 54b5100e89d66762b95d5efeb1bae35894b29215 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 12:55:36 +0530 Subject: [PATCH 024/194] Use --- web/packages/accounts/pages/recover.tsx | 25 +++------------------ web/packages/accounts/utils/recovery-key.ts | 10 ++++----- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 179edbc155..3b3410d3b7 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -7,7 +7,6 @@ import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import { sendOTT } from "ente-accounts/services/user"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; -import { sharedCryptoWorker } from "ente-base/crypto"; import log from "ente-base/log"; import SingleInputForm, { type SingleInputFormProps, @@ -22,11 +21,7 @@ import type { KeyAttributes, User } from "ente-shared/user/types"; import { t } from "i18next"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; - -// eslint-disable-next-line @typescript-eslint/no-require-imports -const bip39 = require("bip39"); -// mobile client library only supports english. -bip39.setDefaultWordlist("english"); +import { decryptUsingRecoveryKeyMnemonic } from "../utils/recovery-key"; const Page: React.FC = () => { const { showMiniDialog } = useBaseContext(); @@ -65,25 +60,11 @@ const Page: React.FC = () => { setFieldError, ) => { try { - recoveryKey = recoveryKey - .trim() - .split(" ") - .map((part) => part.trim()) - .filter((part) => !!part) - .join(" "); - // check if user is entering mnemonic recovery key - if (recoveryKey.indexOf(" ") > 0) { - if (recoveryKey.split(" ").length !== 24) { - throw new Error("recovery code should have 24 words"); - } - recoveryKey = bip39.mnemonicToEntropy(recoveryKey); - } - const cryptoWorker = await sharedCryptoWorker(); const keyAttr = keyAttributes!; - const masterKey = await cryptoWorker.decryptB64( + const masterKey = await decryptUsingRecoveryKeyMnemonic( keyAttr.masterKeyEncryptedWithRecoveryKey!, keyAttr.masterKeyDecryptionNonce!, - await cryptoWorker.fromHex(recoveryKey), + recoveryKey, ); await saveKeyInSessionStore("encryptionKey", masterKey); await decryptAndStoreToken(keyAttr, masterKey); diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index 15bcd13e90..bff8084035 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -9,21 +9,21 @@ bip39.setDefaultWordlist("english"); * Decrypt the provided data that was encrypted with the user's recovery key * using the recovery key derived from the provided mnemonic string. * - * @param recoveryKey The BIP-39 mnemonic (24 word) string representing the - * recovery key. For legacy compatibility, the function also works if provided - * the hex representation of the recovery key. - * * @param encryptedData The data to decrypt. The data should've been encrypted * using the same recovery key otherwise the decryption would fail. * * @param decryptionNonce The nonce that was using encryption. * + * @param recoveryKey The BIP-39 mnemonic (24 word) string representing the + * recovery key. For legacy compatibility, the function also works if provided + * the hex representation of the recovery key. + * * @returns A base64 string representing the decrypted data. */ export const decryptUsingRecoveryKeyMnemonic = async ( - recoveryKey: string, encryptedData: any, decryptionNonce: any, + recoveryKey: string, ) => { recoveryKey = recoveryKey .trim() From c92141b9dc45b692d1bf65f934e082c813d0f09f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 12:59:01 +0530 Subject: [PATCH 025/194] Use --- web/packages/accounts/pages/recover.tsx | 2 +- .../accounts/pages/two-factor/recover.tsx | 26 +++---------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 3b3410d3b7..24ac4c328a 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -5,6 +5,7 @@ import { } from "ente-accounts/components/layouts/centered-paper"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import { sendOTT } from "ente-accounts/services/user"; +import { decryptUsingRecoveryKeyMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; import log from "ente-base/log"; @@ -21,7 +22,6 @@ import type { KeyAttributes, User } from "ente-shared/user/types"; import { t } from "i18next"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import { decryptUsingRecoveryKeyMnemonic } from "../utils/recovery-key"; const Page: React.FC = () => { const { showMiniDialog } = useBaseContext(); diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index f0aba30440..f24f2f64b2 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -10,10 +10,10 @@ import { removeTwoFactor, type TwoFactorType, } from "ente-accounts/services/user"; +import { decryptUsingRecoveryKeyMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import type { MiniDialogAttributes } from "ente-base/components/MiniDialog"; import { useBaseContext } from "ente-base/context"; -import { sharedCryptoWorker } from "ente-base/crypto"; import type { B64EncryptionResult } from "ente-base/crypto/libsodium"; import log from "ente-base/log"; import SingleInputForm, { @@ -26,11 +26,6 @@ import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { Trans } from "react-i18next"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -const bip39 = require("bip39"); -// mobile client library only supports english. -bip39.setDefaultWordlist("english"); - export interface RecoverPageProps { twoFactorType: TwoFactorType; } @@ -95,26 +90,13 @@ const Page: React.FC = ({ twoFactorType }) => { setFieldError, ) => { try { - recoveryKey = recoveryKey - .trim() - .split(" ") - .map((part) => part.trim()) - .filter((part) => !!part) - .join(" "); - // check if user is entering mnemonic recovery key - if (recoveryKey.indexOf(" ") > 0) { - if (recoveryKey.split(" ").length !== 24) { - throw new Error("recovery code should have 24 words"); - } - recoveryKey = bip39.mnemonicToEntropy(recoveryKey); - } - const cryptoWorker = await sharedCryptoWorker(); const { encryptedData, nonce } = encryptedTwoFactorSecret!; - const twoFactorSecret = await cryptoWorker.decryptB64( + const twoFactorSecret = await decryptUsingRecoveryKeyMnemonic( encryptedData, nonce, - await cryptoWorker.fromHex(recoveryKey), + recoveryKey, ); + const resp = await removeTwoFactor( sessionID!, twoFactorSecret, From 8c99a3e5af2d7e9e5591404d5a5a4bd76e3d8c10 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 13:11:54 +0530 Subject: [PATCH 026/194] Other way --- web/packages/accounts/components/RecoveryKey.tsx | 7 ++----- web/packages/accounts/utils/recovery-key.ts | 14 +++++++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/web/packages/accounts/components/RecoveryKey.tsx b/web/packages/accounts/components/RecoveryKey.tsx index 45722ac170..329ee0c9e2 100644 --- a/web/packages/accounts/components/RecoveryKey.tsx +++ b/web/packages/accounts/components/RecoveryKey.tsx @@ -6,7 +6,6 @@ import { Stack, Typography, } from "@mui/material"; -import * as bip39 from "bip39"; import { type MiniDialogAttributes } from "ente-base/components/MiniDialog"; import { SpacedRow } from "ente-base/components/containers"; import { DialogCloseIconButton } from "ente-base/components/mui/DialogCloseIconButton"; @@ -19,11 +18,9 @@ import { downloadString } from "ente-base/utils/web"; import { getRecoveryKey } from "ente-shared/crypto/helpers"; import { t } from "i18next"; import { useCallback, useEffect, useState } from "react"; +import { convertRecoveryKeyToMnemonic } from "../utils/recovery-key"; import { CodeBlock } from "./CodeBlock"; -// mobile client library only supports english. -bip39.setDefaultWordlist("english"); - type RecoveryKeyProps = ModalVisibilityProps & { showMiniDialog: (attributes: MiniDialogAttributes) => void; }; @@ -116,7 +113,7 @@ export const RecoveryKey: React.FC = ({ }; const getRecoveryKeyMnemonic = async () => - bip39.entropyToMnemonic(await getRecoveryKey()); + convertRecoveryKeyToMnemonic(await getRecoveryKey()); const downloadRecoveryKeyMnemonic = (key: string) => downloadString(key, "ente-recovery-key.txt"); diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index bff8084035..1951bd0d87 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -1,8 +1,7 @@ +import * as bip39 from "bip39"; import { sharedCryptoWorker } from "ente-base/crypto"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -const bip39 = require("bip39"); -// mobile client library only supports english. +// Mobile client library only supports English. bip39.setDefaultWordlist("english"); /** @@ -47,3 +46,12 @@ export const decryptUsingRecoveryKeyMnemonic = async ( await cryptoWorker.fromHex(recoveryKey), ); }; + +/** + * Convert the provided recovery key into its BIP-39 mnemonic (24-word) string. + * + * @returns A string containing 24-words that serves as the user visible + * recovery key. + */ +export const convertRecoveryKeyToMnemonic = (recoveryKeyHex: string) => + bip39.entropyToMnemonic(recoveryKeyHex); From 241577739a7ea3f76c944b58745b199c757a06e3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 13:21:18 +0530 Subject: [PATCH 027/194] doc --- web/docs/dependencies.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/docs/dependencies.md b/web/docs/dependencies.md index 92cf8acd2d..14d59c5bfc 100644 --- a/web/docs/dependencies.md +++ b/web/docs/dependencies.md @@ -205,6 +205,9 @@ via [@fontsource-variable/inter](https://fontsource.org/fonts/inter/install). [pDebounce](https://github.com/sindresorhus/p-debounce) are used for debouncing operations (See also: `[Note: Throttle and debounce]`). +- [bip39](https://github.com/bitcoinjs/bip39) is used for generating the 24-word + recovery key mnemonic. + - [zxcvbn](https://github.com/dropbox/zxcvbn) is used for password strength estimation. From 32bffcb80b20912f1f8309583cbd1a14d575b014 Mon Sep 17 00:00:00 2001 From: Vishnu Mohandas Date: Tue, 3 Jun 2025 13:50:40 +0530 Subject: [PATCH 028/194] [docs] Update deduplicate.md --- docs/docs/photos/features/deduplicate.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/photos/features/deduplicate.md b/docs/docs/photos/features/deduplicate.md index c1b55e4cc7..ea931d174d 100644 --- a/docs/docs/photos/features/deduplicate.md +++ b/docs/docs/photos/features/deduplicate.md @@ -52,6 +52,11 @@ Ente also provides a tool for manual de-duplication in _Settings → Backup → Remove duplicates_. This is useful if you have an existing library with duplicates across different albums, but wish to keep only one copy. +During this operation, Ente will discard duplicates across all albums, retain a +single copy, and add symlinks to this copy within all existing albums. So your +existing album structure remains unchanged, while the space consumed by the +duplicate data is freed up. + ## Adding to Ente album creates symlinks Note that once a file is in Ente, adding it to another Ente album will create a From 0e0044693c63d96e320777f36ad5f8212e819e9c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 13:51:14 +0530 Subject: [PATCH 029/194] Remove unnecessary roundtrip --- web/packages/accounts/components/RecoveryKey.tsx | 4 ++-- web/packages/accounts/services/passkey.ts | 9 ++------- web/packages/accounts/utils/recovery-key.ts | 14 +++++++++----- web/packages/shared/crypto/helpers.ts | 7 +++---- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/web/packages/accounts/components/RecoveryKey.tsx b/web/packages/accounts/components/RecoveryKey.tsx index 329ee0c9e2..5d9f25c7b6 100644 --- a/web/packages/accounts/components/RecoveryKey.tsx +++ b/web/packages/accounts/components/RecoveryKey.tsx @@ -18,7 +18,7 @@ import { downloadString } from "ente-base/utils/web"; import { getRecoveryKey } from "ente-shared/crypto/helpers"; import { t } from "i18next"; import { useCallback, useEffect, useState } from "react"; -import { convertRecoveryKeyToMnemonic } from "../utils/recovery-key"; +import { recoveryKeyB64ToMnemonic } from "../utils/recovery-key"; import { CodeBlock } from "./CodeBlock"; type RecoveryKeyProps = ModalVisibilityProps & { @@ -113,7 +113,7 @@ export const RecoveryKey: React.FC = ({ }; const getRecoveryKeyMnemonic = async () => - convertRecoveryKeyToMnemonic(await getRecoveryKey()); + recoveryKeyB64ToMnemonic(await getRecoveryKey()); const downloadRecoveryKeyMnemonic = (key: string) => downloadString(key, "ente-recovery-key.txt"); diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 3e7cd99683..e7175c316a 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -1,6 +1,5 @@ import { TwoFactorAuthorizationResponse } from "ente-accounts/services/user"; import { clientPackageName, isDesktop } from "ente-base/app"; -import { sharedCryptoWorker } from "ente-base/crypto"; import { encryptToB64, generateEncryptionKey, @@ -111,16 +110,12 @@ export const openAccountsManagePasskeysPage = async () => { if (!recoveryEnabled) { // If not, enable it for them by creating the necessary recovery // information to prevent them from getting locked out. - const recoveryKey = await getRecoveryKey(); - + const recoveryKeyB64 = await getRecoveryKey(); const resetSecret = await generateEncryptionKey(); - - const cryptoWorker = await sharedCryptoWorker(); const encryptionResult = await encryptToB64( resetSecret, - await cryptoWorker.fromHex(recoveryKey), + recoveryKeyB64, ); - await configurePasskeyRecovery( resetSecret, encryptionResult.encryptedData, diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index 1951bd0d87..36eab04786 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -48,10 +48,14 @@ export const decryptUsingRecoveryKeyMnemonic = async ( }; /** - * Convert the provided recovery key into its BIP-39 mnemonic (24-word) string. + * Convert the provided base64 encoded recovery key into its BIP-39 mnemonic. * - * @returns A string containing 24-words that serves as the user visible - * recovery key. + * @param recoveryKeyB64 The base64 encoded recovery key to mnemonize. + * + * @returns A 24-word mnemonic that serves as the user visible recovery key. */ -export const convertRecoveryKeyToMnemonic = (recoveryKeyHex: string) => - bip39.entropyToMnemonic(recoveryKeyHex); +export const recoveryKeyB64ToMnemonic = async (recoveryKeyB64: string) => { + const cryptoWorker = await sharedCryptoWorker(); + const recoveryKeyHex = await cryptoWorker.toHex(recoveryKeyB64); + return bip39.entropyToMnemonic(recoveryKeyHex); +}; diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index af5fd71d29..7d9cdd66b3 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -110,9 +110,8 @@ export const saveKeyInSessionStore = async ( export const encryptWithRecoveryKey = async (data: string) => { const cryptoWorker = await sharedCryptoWorker(); - const hexRecoveryKey = await getRecoveryKey(); - const recoveryKey = await cryptoWorker.fromHex(hexRecoveryKey); - return cryptoWorker.encryptBoxB64(data, recoveryKey); + const recoveryKeyB64 = await getRecoveryKey(); + return cryptoWorker.encryptBoxB64(data, recoveryKeyB64); }; export const getRecoveryKey = async () => { @@ -135,7 +134,6 @@ export const getRecoveryKey = async () => { } else { recoveryKey = await createNewRecoveryKey(); } - recoveryKey = await cryptoWorker.toHex(recoveryKey); return recoveryKey; } catch (e) { log.error("getRecoveryKey failed", e); @@ -145,6 +143,7 @@ export const getRecoveryKey = async () => { // Used only for legacy users for whom we did not generate recovery keys during // sign up +// @returns a new base64 encoded recovery key. async function createNewRecoveryKey() { const masterKey = await getActualKey(); const existingAttributes = getData("keyAttributes"); From 25cadce651779af44ee2324bb8aaca7f3510feea Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 13:56:05 +0530 Subject: [PATCH 030/194] Vars --- web/packages/accounts/utils/recovery-key.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index 36eab04786..9ce52da233 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -22,28 +22,31 @@ bip39.setDefaultWordlist("english"); export const decryptUsingRecoveryKeyMnemonic = async ( encryptedData: any, decryptionNonce: any, - recoveryKey: string, + recoveryKeyMnemonicOrHex: string, ) => { - recoveryKey = recoveryKey + const trimmedInput = recoveryKeyMnemonicOrHex .trim() .split(" ") .map((part) => part.trim()) .filter((part) => !!part) .join(" "); + let recoveryKeyHex: string; // Check if user is entering mnemonic recovery key. - if (recoveryKey.indexOf(" ") > 0) { - if (recoveryKey.split(" ").length != 24) { + if (trimmedInput.indexOf(" ") > 0) { + if (trimmedInput.split(" ").length != 24) { throw new Error("recovery code should have 24 words"); } - recoveryKey = bip39.mnemonicToEntropy(recoveryKey); + recoveryKeyHex = bip39.mnemonicToEntropy(trimmedInput); + } else { + recoveryKeyHex = trimmedInput; } const cryptoWorker = await sharedCryptoWorker(); return cryptoWorker.decryptB64( encryptedData, decryptionNonce, - await cryptoWorker.fromHex(recoveryKey), + await cryptoWorker.fromHex(recoveryKeyHex), ); }; From 5df1b12ef5819568e088a9389c1983a37c640a3c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 14:02:14 +0530 Subject: [PATCH 031/194] use new api --- web/packages/accounts/utils/recovery-key.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index 9ce52da233..5d008ee0a3 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -1,5 +1,5 @@ import * as bip39 from "bip39"; -import { sharedCryptoWorker } from "ente-base/crypto"; +import { fromHex, sharedCryptoWorker, toHex } from "ente-base/crypto"; // Mobile client library only supports English. bip39.setDefaultWordlist("english"); @@ -46,7 +46,7 @@ export const decryptUsingRecoveryKeyMnemonic = async ( return cryptoWorker.decryptB64( encryptedData, decryptionNonce, - await cryptoWorker.fromHex(recoveryKeyHex), + await fromHex(recoveryKeyHex), ); }; @@ -57,8 +57,5 @@ export const decryptUsingRecoveryKeyMnemonic = async ( * * @returns A 24-word mnemonic that serves as the user visible recovery key. */ -export const recoveryKeyB64ToMnemonic = async (recoveryKeyB64: string) => { - const cryptoWorker = await sharedCryptoWorker(); - const recoveryKeyHex = await cryptoWorker.toHex(recoveryKeyB64); - return bip39.entropyToMnemonic(recoveryKeyHex); -}; +export const recoveryKeyB64ToMnemonic = async (recoveryKeyB64: string) => + bip39.entropyToMnemonic(await toHex(recoveryKeyB64)); From 9d9ed0f01f78b8334f4fb076a35fd2eb4f877f9e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 14:09:08 +0530 Subject: [PATCH 032/194] Refactor API --- web/packages/accounts/pages/recover.tsx | 8 +++-- .../accounts/pages/two-factor/recover.tsx | 9 +++--- web/packages/accounts/utils/recovery-key.ts | 29 +++++-------------- 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 24ac4c328a..356cf8deb3 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -5,9 +5,10 @@ import { } from "ente-accounts/components/layouts/centered-paper"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import { sendOTT } from "ente-accounts/services/user"; -import { decryptUsingRecoveryKeyMnemonic } from "ente-accounts/utils/recovery-key"; +import { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; +import { sharedCryptoWorker } from "ente-base/crypto"; import log from "ente-base/log"; import SingleInputForm, { type SingleInputFormProps, @@ -61,10 +62,11 @@ const Page: React.FC = () => { ) => { try { const keyAttr = keyAttributes!; - const masterKey = await decryptUsingRecoveryKeyMnemonic( + const cryptoWorker = await sharedCryptoWorker(); + const masterKey = await cryptoWorker.decryptB64( keyAttr.masterKeyEncryptedWithRecoveryKey!, keyAttr.masterKeyDecryptionNonce!, - recoveryKey, + await recoveryKeyB64FromMnemonic(recoveryKey), ); await saveKeyInSessionStore("encryptionKey", masterKey); await decryptAndStoreToken(keyAttr, masterKey); diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index f24f2f64b2..1d207b2990 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -10,10 +10,11 @@ import { removeTwoFactor, type TwoFactorType, } from "ente-accounts/services/user"; -import { decryptUsingRecoveryKeyMnemonic } from "ente-accounts/utils/recovery-key"; +import { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import type { MiniDialogAttributes } from "ente-base/components/MiniDialog"; import { useBaseContext } from "ente-base/context"; +import { sharedCryptoWorker } from "ente-base/crypto"; import type { B64EncryptionResult } from "ente-base/crypto/libsodium"; import log from "ente-base/log"; import SingleInputForm, { @@ -91,12 +92,12 @@ const Page: React.FC = ({ twoFactorType }) => { ) => { try { const { encryptedData, nonce } = encryptedTwoFactorSecret!; - const twoFactorSecret = await decryptUsingRecoveryKeyMnemonic( + const cryptoWorker = await sharedCryptoWorker(); + const twoFactorSecret = await cryptoWorker.decryptB64( encryptedData, nonce, - recoveryKey, + await recoveryKeyB64FromMnemonic(recoveryKey), ); - const resp = await removeTwoFactor( sessionID!, twoFactorSecret, diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index 5d008ee0a3..59738da782 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -1,27 +1,19 @@ import * as bip39 from "bip39"; -import { fromHex, sharedCryptoWorker, toHex } from "ente-base/crypto"; +import { fromHex, toHex } from "ente-base/crypto"; // Mobile client library only supports English. bip39.setDefaultWordlist("english"); /** - * Decrypt the provided data that was encrypted with the user's recovery key - * using the recovery key derived from the provided mnemonic string. + * Convert the provided BIP-39 mnemonic string into its base64 representation. * - * @param encryptedData The data to decrypt. The data should've been encrypted - * using the same recovery key otherwise the decryption would fail. + * @param recoveryKeyMnemonicOrHex The BIP-39 mnemonic (24 word) string + * representing the recovery key. For legacy compatibility, the function also + * works if provided the hex representation of the recovery key. * - * @param decryptionNonce The nonce that was using encryption. - * - * @param recoveryKey The BIP-39 mnemonic (24 word) string representing the - * recovery key. For legacy compatibility, the function also works if provided - * the hex representation of the recovery key. - * - * @returns A base64 string representing the decrypted data. + * @returns A base64 string representing the underlying bytes of the recovery key. */ -export const decryptUsingRecoveryKeyMnemonic = async ( - encryptedData: any, - decryptionNonce: any, +export const recoveryKeyB64FromMnemonic = ( recoveryKeyMnemonicOrHex: string, ) => { const trimmedInput = recoveryKeyMnemonicOrHex @@ -42,12 +34,7 @@ export const decryptUsingRecoveryKeyMnemonic = async ( recoveryKeyHex = trimmedInput; } - const cryptoWorker = await sharedCryptoWorker(); - return cryptoWorker.decryptB64( - encryptedData, - decryptionNonce, - await fromHex(recoveryKeyHex), - ); + return fromHex(recoveryKeyHex); }; /** From 97314b7dc1b644d87ae1b3a2efca2fbe39e3ce1f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 14:13:48 +0530 Subject: [PATCH 033/194] conv --- web/packages/accounts/pages/recover.tsx | 11 ++++++----- web/packages/accounts/pages/two-factor/recover.tsx | 8 +++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 356cf8deb3..e0ed5eced5 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -8,7 +8,7 @@ import { sendOTT } from "ente-accounts/services/user"; import { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; -import { sharedCryptoWorker } from "ente-base/crypto"; +import { decryptBoxB64 } from "ente-base/crypto"; import log from "ente-base/log"; import SingleInputForm, { type SingleInputFormProps, @@ -62,10 +62,11 @@ const Page: React.FC = () => { ) => { try { const keyAttr = keyAttributes!; - const cryptoWorker = await sharedCryptoWorker(); - const masterKey = await cryptoWorker.decryptB64( - keyAttr.masterKeyEncryptedWithRecoveryKey!, - keyAttr.masterKeyDecryptionNonce!, + const masterKey = await decryptBoxB64( + { + encryptedData: keyAttr.masterKeyEncryptedWithRecoveryKey!, + nonce: keyAttr.masterKeyDecryptionNonce!, + }, await recoveryKeyB64FromMnemonic(recoveryKey), ); await saveKeyInSessionStore("encryptionKey", masterKey); diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index 1d207b2990..90d93ab23c 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -14,7 +14,7 @@ import { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import type { MiniDialogAttributes } from "ente-base/components/MiniDialog"; import { useBaseContext } from "ente-base/context"; -import { sharedCryptoWorker } from "ente-base/crypto"; +import { decryptBoxB64 } from "ente-base/crypto"; import type { B64EncryptionResult } from "ente-base/crypto/libsodium"; import log from "ente-base/log"; import SingleInputForm, { @@ -92,10 +92,8 @@ const Page: React.FC = ({ twoFactorType }) => { ) => { try { const { encryptedData, nonce } = encryptedTwoFactorSecret!; - const cryptoWorker = await sharedCryptoWorker(); - const twoFactorSecret = await cryptoWorker.decryptB64( - encryptedData, - nonce, + const twoFactorSecret = await decryptBoxB64( + { encryptedData, nonce }, await recoveryKeyB64FromMnemonic(recoveryKey), ); const resp = await removeTwoFactor( From 7cdef46385a017fc3c948b72f07dc9eaceff4a8f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 14:42:33 +0530 Subject: [PATCH 034/194] Update --- web/packages/accounts/services/user.ts | 37 +++++++++++++++++--------- web/packages/shared/crypto/helpers.ts | 32 +++++++++++++--------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index 6f5d55f85e..f02eabf71c 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -61,13 +61,6 @@ export interface UpdatedKey { opsLimit: number; } -export interface RecoveryKey { - masterKeyEncryptedWithRecoveryKey: string; - masterKeyDecryptionNonce: string; - recoveryKeyEncryptedWithMasterKey: string; - recoveryKeyDecryptionNonce: string; -} - /** * Ask remote to send a OTP / OTT to the given email to verify that the user has * access to it. Subsequent the app will pass this OTT back via the @@ -311,10 +304,28 @@ export const enableTwoFactor = async (req: EnableTwoFactorRequest) => }), ); -export const setRecoveryKey = async (token: string, recoveryKey: RecoveryKey) => - HTTPService.put( - await apiURL("/users/recovery-key"), - recoveryKey, - undefined, - { "X-Auth-Token": token }, +export interface RecoveryKeyAttributes { + masterKeyEncryptedWithRecoveryKey: string; + masterKeyDecryptionNonce: string; + recoveryKeyEncryptedWithMasterKey: string; + recoveryKeyDecryptionNonce: string; +} + +/** + * Update the encrypted recovery key attributes for the logged in user. + * + * In practice, this is not expected to be called and is meant as a rare + * fallback for very old accounts created prior to recovery key related + * attributes being assigned on account setup. Even for these, it'll be called + * only once. + */ +export const putUserRecoveryKeyAttributes = async ( + recoveryKeyAttributes: RecoveryKeyAttributes, +) => + ensureOk( + await fetch(await apiURL("/users/recovery-key"), { + method: "PUT", + headers: await authenticatedRequestHeaders(), + body: JSON.stringify(recoveryKeyAttributes), + }), ); diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index 7d9cdd66b3..e57ae944d7 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -1,9 +1,8 @@ -import { setRecoveryKey } from "ente-accounts/services/user"; +import { putUserRecoveryKeyAttributes } from "ente-accounts/services/user"; import { sharedCryptoWorker } from "ente-base/crypto"; import log from "ente-base/log"; import { masterKeyFromSession } from "ente-base/session"; import { getData, setData, setLSUser } from "ente-shared/storage/localStorage"; -import { getToken } from "ente-shared/storage/localStorage/helpers"; import { type SessionKey, setKey } from "ente-shared/storage/sessionStorage"; import { getActualKey } from "ente-shared/user"; import type { KeyAttributes } from "ente-shared/user/types"; @@ -141,10 +140,16 @@ export const getRecoveryKey = async () => { } }; -// Used only for legacy users for whom we did not generate recovery keys during -// sign up -// @returns a new base64 encoded recovery key. -async function createNewRecoveryKey() { +/** + * Generate a new recovery key, tell remote about it, update our local state, + * and then return it. + * + * This function will be used only for legacy users for whom we did not generate + * recovery keys during sign up. + * + * @returns a new base64 encoded recovery key. + */ +const createNewRecoveryKey = async () => { const masterKey = await getActualKey(); const existingAttributes = getData("keyAttributes"); @@ -159,22 +164,23 @@ async function createNewRecoveryKey() { recoveryKey, masterKey, ); + const recoveryKeyAttributes = { masterKeyEncryptedWithRecoveryKey: encryptedMasterKey.encryptedData, masterKeyDecryptionNonce: encryptedMasterKey.nonce, recoveryKeyEncryptedWithMasterKey: encryptedRecoveryKey.encryptedData, recoveryKeyDecryptionNonce: encryptedRecoveryKey.nonce, }; - await setRecoveryKey(getToken(), recoveryKeyAttributes); - const updatedKeyAttributes = Object.assign( - existingAttributes, - recoveryKeyAttributes, - ); - setData("keyAttributes", updatedKeyAttributes); + await putUserRecoveryKeyAttributes(recoveryKeyAttributes); + + setData("keyAttributes", { + ...existingAttributes, + ...recoveryKeyAttributes, + }); return recoveryKey; -} +}; /** * Decrypt the {@link encryptedChallenge} sent by remote during the delete From e493702c6450ddaea5ca9e9c135008aaa2e20147 Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Tue, 3 Jun 2025 14:59:23 +0530 Subject: [PATCH 035/194] Integrate thermal state in check --- .../machine_learning_controller.dart | 65 +++++++++++++++---- mobile/pubspec.lock | 8 +++ mobile/pubspec.yaml | 1 + 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/mobile/lib/services/machine_learning/machine_learning_controller.dart b/mobile/lib/services/machine_learning/machine_learning_controller.dart index 6db75a232d..c1c61d804f 100644 --- a/mobile/lib/services/machine_learning/machine_learning_controller.dart +++ b/mobile/lib/services/machine_learning/machine_learning_controller.dart @@ -8,6 +8,7 @@ import "package:flutter/foundation.dart"; import "package:logging/logging.dart"; import "package:photos/core/event_bus.dart"; import "package:photos/events/machine_learning_control_event.dart"; +import "package:thermal/thermal.dart"; class MachineLearningController { final _logger = Logger("MachineLearningController"); @@ -17,6 +18,11 @@ class MachineLearningController { final kDefaultInteractionTimeout = Duration(seconds: Platform.isIOS ? 5 : 15); static const kUnhealthyStates = ["over_heat", "over_voltage", "dead"]; + static final _thermal = Thermal(); + IosBatteryInfo? _iosLastBatteryInfo; + AndroidBatteryInfo? _androidLastBatteryInfo; + ThermalStatus? _lastThermalStatus; + bool _isDeviceHealthy = true; bool _isUserInteracting = true; bool _canRunML = false; @@ -49,6 +55,9 @@ class MachineLearningController { _onAndroidBatteryStateUpdate(batteryInfo); }); } + _thermal.onThermalStatusChanged.listen((ThermalStatus thermalState) { + _onThermalStateUpdate(thermalState); + }); _logger.info('init done '); } @@ -96,27 +105,61 @@ class MachineLearningController { } void _onAndroidBatteryStateUpdate(AndroidBatteryInfo? batteryInfo) { + _androidLastBatteryInfo = batteryInfo; _logger.info("Battery info: ${batteryInfo!.toJson()}"); - _isDeviceHealthy = _computeIsAndroidDeviceHealthy(batteryInfo); + _isDeviceHealthy = _computeIsAndroidDeviceHealthy(); _fireControlEvent(); } void _oniOSBatteryStateUpdate(IosBatteryInfo? batteryInfo) { + _iosLastBatteryInfo = batteryInfo; _logger.info("Battery info: ${batteryInfo!.toJson()}"); - _isDeviceHealthy = _computeIsiOSDeviceHealthy(batteryInfo); + _isDeviceHealthy = _computeIsiOSDeviceHealthy(); _fireControlEvent(); } - bool _computeIsAndroidDeviceHealthy(AndroidBatteryInfo info) { - return _hasSufficientBattery(info.batteryLevel ?? kMinimumBatteryLevel) && - _isAcceptableTemperatureAndroid( - info.temperature ?? kMaximumTemperatureAndroid, - ) && - _isBatteryHealthy(info.health ?? ""); + void _onThermalStateUpdate(ThermalStatus? thermalStatus) { + _lastThermalStatus = thermalStatus; + _logger.info("Thermal status: $thermalStatus"); + _isDeviceHealthy = _computeIsAndroidDeviceHealthy(); + _fireControlEvent(); } - bool _computeIsiOSDeviceHealthy(IosBatteryInfo info) { - return _hasSufficientBattery(info.batteryLevel ?? kMinimumBatteryLevel); + bool _computeIsAndroidDeviceHealthy() { + return _hasSufficientBattery( + _androidLastBatteryInfo?.batteryLevel ?? kMinimumBatteryLevel, + ) && + _isAcceptableTemperatureAndroid( + _androidLastBatteryInfo?.temperature ?? kMaximumTemperatureAndroid, + ) && + _isBatteryHealthyAndroid(_androidLastBatteryInfo?.health ?? "") && + _isAcceptableThermalState(); + } + + bool _computeIsiOSDeviceHealthy() { + return _hasSufficientBattery( + _iosLastBatteryInfo?.batteryLevel ?? kMinimumBatteryLevel, + ) && + _isAcceptableThermalState(); + } + + bool _isAcceptableThermalState() { + switch (_lastThermalStatus) { + case null: + _logger.info("Thermal status is null, assuming acceptable temperature"); + return true; + case ThermalStatus.none: + case ThermalStatus.light: + case ThermalStatus.moderate: + _logger.info("Thermal status is acceptable: $_lastThermalStatus"); + return true; + case ThermalStatus.severe: + case ThermalStatus.critical: + case ThermalStatus.emergency: + case ThermalStatus.shutdown: + _logger.warning("Thermal status is unacceptable: $_lastThermalStatus"); + return false; + } } bool _hasSufficientBattery(int batteryLevel) { @@ -127,7 +170,7 @@ class MachineLearningController { return temperature <= kMaximumTemperatureAndroid; } - bool _isBatteryHealthy(String health) { + bool _isBatteryHealthyAndroid(String health) { return !kUnhealthyStates.contains(health); } } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 14ad1f5347..3e8a1e8b31 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -2525,6 +2525,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.4" + thermal: + dependency: "direct main" + description: + name: thermal + sha256: c6275ebc609e75f1bbbfc354d56d6c559a0c6045a06a8e8c3939c03306a07e18 + url: "https://pub.dev" + source: hosted + version: "1.1.11" timezone: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 25701c6c44..aecaee0afe 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -190,6 +190,7 @@ dependencies: syncfusion_flutter_sliders: ^25.2.5 synchronized: ^3.3.0+3 system_info_plus: ^0.0.6 + thermal: ^1.1.11 timezone: ^0.9.4 tuple: ^2.0.0 ua_client_hints: ^1.4.0 From 3d5c53b041630e6401bd4bb0cfc1f48982cf4fca Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Tue, 3 Jun 2025 15:04:56 +0530 Subject: [PATCH 036/194] Simplify --- .../machine_learning_controller.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mobile/lib/services/machine_learning/machine_learning_controller.dart b/mobile/lib/services/machine_learning/machine_learning_controller.dart index c1c61d804f..7850c02e1c 100644 --- a/mobile/lib/services/machine_learning/machine_learning_controller.dart +++ b/mobile/lib/services/machine_learning/machine_learning_controller.dart @@ -129,10 +129,8 @@ class MachineLearningController { return _hasSufficientBattery( _androidLastBatteryInfo?.batteryLevel ?? kMinimumBatteryLevel, ) && - _isAcceptableTemperatureAndroid( - _androidLastBatteryInfo?.temperature ?? kMaximumTemperatureAndroid, - ) && - _isBatteryHealthyAndroid(_androidLastBatteryInfo?.health ?? "") && + _isAcceptableTemperatureAndroid() && + _isBatteryHealthyAndroid() && _isAcceptableThermalState(); } @@ -166,11 +164,13 @@ class MachineLearningController { return batteryLevel >= kMinimumBatteryLevel; } - bool _isAcceptableTemperatureAndroid(int temperature) { - return temperature <= kMaximumTemperatureAndroid; + bool _isAcceptableTemperatureAndroid() { + return (_androidLastBatteryInfo?.temperature ?? + kMaximumTemperatureAndroid) <= + kMaximumTemperatureAndroid; } - bool _isBatteryHealthyAndroid(String health) { - return !kUnhealthyStates.contains(health); + bool _isBatteryHealthyAndroid() { + return !kUnhealthyStates.contains(_androidLastBatteryInfo?.health ?? ""); } } From 78669a855039297dde1218fd174b0b020cffc982 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:18:15 +0530 Subject: [PATCH 037/194] Match mobile and architecture docs From libsodium source crypto_secretbox_keygen(unsigned char k[crypto_secretbox_KEYBYTES]) { randombytes_buf(k, crypto_secretbox_KEYBYTES); } crypto_kdf_keygen(unsigned char k[crypto_kdf_KEYBYTES]) { randombytes_buf(k, crypto_kdf_KEYBYTES); } --- web/packages/base/crypto/libsodium.ts | 34 ++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 62d8fe1860..89801a6f90 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -130,12 +130,34 @@ const bytes = async (bob: BytesOrB64) => typeof bob == "string" ? fromB64(bob) : bob; /** - * Generate a new key for use with the *Box encryption functions, and return its - * base64 string representation. + * Generate a new randomly generated 256-bit key for use as a general encryption + * key and return its base64 string representation. * - * This returns a new randomly generated 256-bit key suitable for being used - * with libsodium's secretbox APIs. + * From the architecture docs: + * + * > [`crypto_secretbox_keygen`](https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes) + * > is used to generate all random keys within the application. Your + * > `masterKey`, `recoveryKey`, `collectionKey`, `fileKey` are all 256-bit keys + * > generated using this API. + * + * {@link generateKey} can be contrasted with {@link generateBlobOrStreamKey} + * and can be thought of as a hypothetical "generateBoxKey". That is, the key + * returned by this function is suitable for being used with the *Box encryption + * functions (which eventually delegate to the libsodium's secretbox APIs). + * + * While this is a reasonable semantic distinction, in terms of implementation + * there is no difference: currently both {@link generateKey} (or the + * hypothetical "generateBoxKey") and {@link generateBlobOrStreamKey} produce + * 256-bits of entropy that does not have any ties to a particular algorithm. + * + * @returns A new randomly generated 256-bit key. */ +export const generateKey = async () => { + await sodium.ready; + return toB64(sodium.crypto_secretbox_keygen()); +}; + +/** Deprecated, use generateKey */ export const generateBoxKey = async () => { await sodium.ready; return toB64(sodium.crypto_secretbox_keygen()); @@ -863,9 +885,9 @@ export const deriveInteractiveKey = async ( return { key, opsLimit, memLimit }; }; +/** Deprecated, use generateKey */ export async function generateEncryptionKey() { - await sodium.ready; - return await toB64(sodium.crypto_kdf_keygen()); + return generateKey(); } export async function generateSaltToDeriveKey() { From f749e1ee226286c0fb9085c6658e21c6fde80330 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 3 Jun 2025 11:59:02 +0200 Subject: [PATCH 038/194] trim search query --- mobile/lib/ui/viewer/search/search_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/ui/viewer/search/search_widget.dart b/mobile/lib/ui/viewer/search/search_widget.dart index 1461d37f99..ec297ce0a7 100644 --- a/mobile/lib/ui/viewer/search/search_widget.dart +++ b/mobile/lib/ui/viewer/search/search_widget.dart @@ -114,7 +114,7 @@ class SearchWidgetState extends State { isLoading.value = true; _debouncer.run(() async { if (mounted) { - query = textController.text; + query = textController.text.trim(); IndexOfStackNotifier().isSearchQueryEmpty = query.isEmpty; searchResultsStreamNotifier.value = _getSearchResultsStream(context, query); From d9a4ffd8f7527650c990b38579639db62efa736a Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Tue, 3 Jun 2025 15:32:47 +0530 Subject: [PATCH 039/194] rename to ComputeController --- mobile/lib/app.dart | 4 ++-- mobile/lib/service_locator.dart | 10 +++++----- ...earning_controller.dart => compute_controller.dart} | 4 ++-- .../ui/settings/ml/machine_learning_settings_page.dart | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) rename mobile/lib/services/machine_learning/{machine_learning_controller.dart => compute_controller.dart} (98%) diff --git a/mobile/lib/app.dart b/mobile/lib/app.dart index 3b8e3346d8..19fe79fef7 100644 --- a/mobile/lib/app.dart +++ b/mobile/lib/app.dart @@ -107,7 +107,7 @@ class _EnteAppState extends State with WidgetsBindingObserver { if (Platform.isAndroid || kDebugMode) { return Listener( onPointerDown: (event) { - machineLearningController.onUserInteraction(); + computeController.onUserInteraction(); }, child: AdaptiveTheme( light: lightThemeData, @@ -142,7 +142,7 @@ class _EnteAppState extends State with WidgetsBindingObserver { } else { return Listener( onPointerDown: (event) { - machineLearningController.onUserInteraction(); + computeController.onUserInteraction(); }, child: MaterialApp( title: "ente", diff --git a/mobile/lib/service_locator.dart b/mobile/lib/service_locator.dart index 84e5eb6a18..bc96877eb2 100644 --- a/mobile/lib/service_locator.dart +++ b/mobile/lib/service_locator.dart @@ -9,8 +9,8 @@ import "package:photos/services/account/billing_service.dart"; import "package:photos/services/entity_service.dart"; import "package:photos/services/filedata/filedata_service.dart"; import "package:photos/services/location_service.dart"; +import "package:photos/services/machine_learning/compute_controller.dart"; import "package:photos/services/machine_learning/face_ml/face_recognition_service.dart"; -import "package:photos/services/machine_learning/machine_learning_controller.dart"; import "package:photos/services/magic_cache_service.dart"; import "package:photos/services/memories_cache_service.dart"; import "package:photos/services/permission/service.dart"; @@ -142,10 +142,10 @@ BillingService get billingService { return _billingService!; } -MachineLearningController? _machineLearningController; -MachineLearningController get machineLearningController { - _machineLearningController ??= MachineLearningController(); - return _machineLearningController!; +ComputeController? _computeController; +ComputeController get computeController { + _computeController ??= ComputeController(); + return _computeController!; } FaceRecognitionService? _faceRecognitionService; diff --git a/mobile/lib/services/machine_learning/machine_learning_controller.dart b/mobile/lib/services/machine_learning/compute_controller.dart similarity index 98% rename from mobile/lib/services/machine_learning/machine_learning_controller.dart rename to mobile/lib/services/machine_learning/compute_controller.dart index 7850c02e1c..2368f11579 100644 --- a/mobile/lib/services/machine_learning/machine_learning_controller.dart +++ b/mobile/lib/services/machine_learning/compute_controller.dart @@ -10,7 +10,7 @@ import "package:photos/core/event_bus.dart"; import "package:photos/events/machine_learning_control_event.dart"; import "package:thermal/thermal.dart"; -class MachineLearningController { +class ComputeController { final _logger = Logger("MachineLearningController"); static const kMaximumTemperatureAndroid = 42; // 42 degree celsius @@ -31,7 +31,7 @@ class MachineLearningController { bool get isDeviceHealthy => _isDeviceHealthy; - MachineLearningController() { + ComputeController() { _logger.info('MachineLearningController constructor'); _startInteractionTimer(kDefaultInteractionTimeout); if (Platform.isIOS) { diff --git a/mobile/lib/ui/settings/ml/machine_learning_settings_page.dart b/mobile/lib/ui/settings/ml/machine_learning_settings_page.dart index 5db7a028ac..82c81e3967 100644 --- a/mobile/lib/ui/settings/ml/machine_learning_settings_page.dart +++ b/mobile/lib/ui/settings/ml/machine_learning_settings_page.dart @@ -54,7 +54,7 @@ class _MachineLearningSettingsPageState enable: true, wakeLockFor: WakeLockFor.machineLearningSettingsScreen, ); - machineLearningController.forceOverrideML(turnOn: true); + computeController.forceOverrideML(turnOn: true); if (!MLIndexingIsolate.instance.areModelsDownloaded) { _timer = Timer.periodic(const Duration(seconds: 10), (timer) { if (mounted) { @@ -74,7 +74,7 @@ class _MachineLearningSettingsPageState enable: false, wakeLockFor: WakeLockFor.machineLearningSettingsScreen, ); - machineLearningController.forceOverrideML(turnOn: false); + computeController.forceOverrideML(turnOn: false); _timer?.cancel(); _advancedOptionsTimer?.cancel(); } @@ -429,13 +429,13 @@ class MLStatusWidget extends StatefulWidget { class MLStatusWidgetState extends State { Timer? _timer; - bool _isDeviceHealthy = machineLearningController.isDeviceHealthy; + bool _isDeviceHealthy = computeController.isDeviceHealthy; @override void initState() { super.initState(); _timer = Timer.periodic(const Duration(seconds: 10), (timer) { MLService.instance.triggerML(); - _isDeviceHealthy = machineLearningController.isDeviceHealthy; + _isDeviceHealthy = computeController.isDeviceHealthy; setState(() {}); }); } From 1b62dbbb78831436e1996228c8a04a5ebf39b878 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Tue, 3 Jun 2025 12:08:00 +0200 Subject: [PATCH 040/194] also trim in album search --- mobile/lib/ui/collections/collection_action_sheet.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/ui/collections/collection_action_sheet.dart b/mobile/lib/ui/collections/collection_action_sheet.dart index a827e166bb..9033d16635 100644 --- a/mobile/lib/ui/collections/collection_action_sheet.dart +++ b/mobile/lib/ui/collections/collection_action_sheet.dart @@ -170,7 +170,7 @@ class _CollectionActionSheetState extends State { prefixIcon: Icons.search_rounded, onChange: (value) { setState(() { - _searchQuery = value; + _searchQuery = value.trim(); }); }, isClearable: true, From 48dc3a6b6914ff8737ebca439bcfc3f04353e474 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:38:23 +0530 Subject: [PATCH 041/194] Redirect --- web/packages/base/crypto/ente-impl.ts | 2 +- web/packages/base/crypto/index.ts | 12 +++++++----- web/packages/base/crypto/libsodium.ts | 6 ------ web/packages/base/crypto/worker.ts | 2 +- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index e719a57d32..39bc8eac9a 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -12,7 +12,7 @@ export const _toHex = libsodium.toHex; export const _fromHex = libsodium.fromHex; -export const _generateBoxKey = libsodium.generateBoxKey; +export const _generateKey = libsodium.generateKey; export const _generateBlobOrStreamKey = libsodium.generateBlobOrStreamKey; diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index 214f4d119e..d7abbb7a13 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -137,13 +137,15 @@ export const fromHex = (hexString: string) => : sharedWorker().then((w) => w.fromHex(hexString)); /** - * Return a new randomly generated 256-bit key (as a base64 string) suitable for - * use with the *Box encryption functions. + * Return a new randomly generated 256-bit key (as a base64 string). + * + * The returned key is suitable for use with the *Box encryption functions, and + * as a general encryption key (e.g. as the user's master key or recovery key). */ -export const generateBoxKey = () => +export const generateKey = () => inWorker() - ? ei._generateBoxKey() - : sharedWorker().then((w) => w.generateBoxKey()); + ? ei._generateKey() + : sharedWorker().then((w) => w.generateKey()); /** * Return a new randomly generated 256-bit key (as a base64 string) suitable for diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 89801a6f90..0c3d3b9779 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -157,12 +157,6 @@ export const generateKey = async () => { return toB64(sodium.crypto_secretbox_keygen()); }; -/** Deprecated, use generateKey */ -export const generateBoxKey = async () => { - await sodium.ready; - return toB64(sodium.crypto_secretbox_keygen()); -}; - /** * Generate a new key for use with the *Blob or *Stream encryption functions, * and return its base64 string representation. diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 8e6db10341..95924655c2 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -18,7 +18,7 @@ export class CryptoWorker { fromB64 = ei._fromB64; toHex = ei._toHex; fromHex = ei._fromHex; - generateBoxKey = ei._generateBoxKey; + generateKey = ei._generateKey; generateBlobOrStreamKey = ei._generateBlobOrStreamKey; encryptBoxB64 = ei._encryptBoxB64; encryptThumbnail = ei._encryptThumbnail; From f4ff63ec0a31095d18fee1e2bcf59b3af87d5cae Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:41:47 +0530 Subject: [PATCH 042/194] Swap --- web/apps/photos/src/services/collectionService.ts | 2 +- web/packages/accounts/services/passkey.ts | 8 +++----- web/packages/accounts/services/srp.ts | 4 ++-- web/packages/base/crypto/libsodium.ts | 5 ----- web/packages/base/crypto/worker.ts | 4 ---- web/packages/shared/crypto/helpers.ts | 2 +- 6 files changed, 7 insertions(+), 18 deletions(-) diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index b002c42b0e..94e60e4613 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -70,7 +70,7 @@ const createCollection = async ( const cryptoWorker = await sharedCryptoWorker(); const encryptionKey = await getActualKey(); const token = getToken(); - const collectionKey = await cryptoWorker.generateEncryptionKey(); + const collectionKey = await cryptoWorker.generateKey(); const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = await cryptoWorker.encryptToB64(collectionKey, encryptionKey); const { encryptedData: encryptedName, nonce: nameDecryptionNonce } = diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index e7175c316a..6223ea7497 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -1,9 +1,7 @@ import { TwoFactorAuthorizationResponse } from "ente-accounts/services/user"; import { clientPackageName, isDesktop } from "ente-base/app"; -import { - encryptToB64, - generateEncryptionKey, -} from "ente-base/crypto/libsodium"; +import { generateKey } from "ente-base/crypto"; +import { encryptToB64 } from "ente-base/crypto/libsodium"; import { authenticatedRequestHeaders, ensureOk, @@ -111,7 +109,7 @@ export const openAccountsManagePasskeysPage = async () => { // If not, enable it for them by creating the necessary recovery // information to prevent them from getting locked out. const recoveryKeyB64 = await getRecoveryKey(); - const resetSecret = await generateEncryptionKey(); + const resetSecret = await generateKey(); const encryptionResult = await encryptToB64( resetSecret, recoveryKeyB64, diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index fad07bb1a4..30face4208 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -171,8 +171,8 @@ export async function generateKeyAndSRPAttributes( srpSetupAttributes: SRPSetupAttributes; }> { const cryptoWorker = await sharedCryptoWorker(); - const masterKey = await cryptoWorker.generateEncryptionKey(); - const recoveryKey = await cryptoWorker.generateEncryptionKey(); + const masterKey = await cryptoWorker.generateKey(); + const recoveryKey = await cryptoWorker.generateKey(); const kekSalt = await cryptoWorker.generateSaltToDeriveKey(); const kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt); diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 0c3d3b9779..1f58fccbab 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -879,11 +879,6 @@ export const deriveInteractiveKey = async ( return { key, opsLimit, memLimit }; }; -/** Deprecated, use generateKey */ -export async function generateEncryptionKey() { - return generateKey(); -} - export async function generateSaltToDeriveKey() { await sodium.ready; return await toB64(sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES)); diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 95924655c2..720b993d1d 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -70,10 +70,6 @@ export class CryptoWorker { return libsodium.encryptUTF8(data, key); } - async generateEncryptionKey() { - return libsodium.generateEncryptionKey(); - } - async generateSaltToDeriveKey() { return libsodium.generateSaltToDeriveKey(); } diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index e57ae944d7..21ab4357d2 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -155,7 +155,7 @@ const createNewRecoveryKey = async () => { const cryptoWorker = await sharedCryptoWorker(); - const recoveryKey = await cryptoWorker.generateEncryptionKey(); + const recoveryKey = await cryptoWorker.generateKey(); const encryptedMasterKey = await cryptoWorker.encryptToB64( masterKey, recoveryKey, From 7112e96c75b9816cf7b3796db59381ba1b529bfe Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:46:15 +0530 Subject: [PATCH 043/194] Swap --- web/packages/shared/crypto/helpers.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index 21ab4357d2..db434c0920 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -150,17 +150,16 @@ export const getRecoveryKey = async () => { * @returns a new base64 encoded recovery key. */ const createNewRecoveryKey = async () => { - const masterKey = await getActualKey(); + const masterKey = await masterKeyFromSession(); const existingAttributes = getData("keyAttributes"); const cryptoWorker = await sharedCryptoWorker(); - const recoveryKey = await cryptoWorker.generateKey(); - const encryptedMasterKey = await cryptoWorker.encryptToB64( + const encryptedMasterKey = await cryptoWorker.encryptBoxB64( masterKey, recoveryKey, ); - const encryptedRecoveryKey = await cryptoWorker.encryptToB64( + const encryptedRecoveryKey = await cryptoWorker.encryptBoxB64( recoveryKey, masterKey, ); From f0aac696caa8849a120ce7ebfff3a97425ecb41a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:50:43 +0530 Subject: [PATCH 044/194] Conv --- web/packages/shared/crypto/helpers.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index db434c0920..646f9dff6b 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -4,7 +4,6 @@ import log from "ente-base/log"; import { masterKeyFromSession } from "ente-base/session"; import { getData, setData, setLSUser } from "ente-shared/storage/localStorage"; import { type SessionKey, setKey } from "ente-shared/storage/sessionStorage"; -import { getActualKey } from "ente-shared/user"; import type { KeyAttributes } from "ente-shared/user/types"; const LOGIN_SUB_KEY_LENGTH = 32; @@ -122,16 +121,19 @@ export const getRecoveryKey = async () => { recoveryKeyEncryptedWithMasterKey, recoveryKeyDecryptionNonce, } = keyAttributes; - const masterKey = await getActualKey(); + const masterKey = await masterKeyFromSession(); + let recoveryKey: string; if (recoveryKeyEncryptedWithMasterKey) { - recoveryKey = await cryptoWorker.decryptB64( - recoveryKeyEncryptedWithMasterKey!, - recoveryKeyDecryptionNonce!, + recoveryKey = await cryptoWorker.decryptBoxB64( + { + encryptedData: recoveryKeyEncryptedWithMasterKey!, + nonce: recoveryKeyDecryptionNonce!, + }, masterKey, ); } else { - recoveryKey = await createNewRecoveryKey(); + recoveryKey = await createNewRecoveryKey(masterKey); } return recoveryKey; } catch (e) { @@ -149,8 +151,7 @@ export const getRecoveryKey = async () => { * * @returns a new base64 encoded recovery key. */ -const createNewRecoveryKey = async () => { - const masterKey = await masterKeyFromSession(); +const createNewRecoveryKey = async (masterKey: Uint8Array) => { const existingAttributes = getData("keyAttributes"); const cryptoWorker = await sharedCryptoWorker(); From 2d7689a6dabad5e02624955631ce697b39b720d6 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:56:27 +0530 Subject: [PATCH 045/194] Consolidate and move --- .../accounts/components/RecoveryKey.tsx | 8 +- web/packages/accounts/services/passkey.ts | 10 +- .../accounts/services/recovery-key.ts | 122 ++++++++++++++++++ web/packages/accounts/utils/recovery-key.ts | 48 ------- .../new/photos/components/gallery/helpers.ts | 4 +- web/packages/shared/crypto/helpers.ts | 75 +---------- 6 files changed, 135 insertions(+), 132 deletions(-) create mode 100644 web/packages/accounts/services/recovery-key.ts delete mode 100644 web/packages/accounts/utils/recovery-key.ts diff --git a/web/packages/accounts/components/RecoveryKey.tsx b/web/packages/accounts/components/RecoveryKey.tsx index 5d9f25c7b6..9e1b5da3a0 100644 --- a/web/packages/accounts/components/RecoveryKey.tsx +++ b/web/packages/accounts/components/RecoveryKey.tsx @@ -15,10 +15,12 @@ import { useIsSmallWidth } from "ente-base/components/utils/hooks"; import type { ModalVisibilityProps } from "ente-base/components/utils/modal"; import log from "ente-base/log"; import { downloadString } from "ente-base/utils/web"; -import { getRecoveryKey } from "ente-shared/crypto/helpers"; import { t } from "i18next"; import { useCallback, useEffect, useState } from "react"; -import { recoveryKeyB64ToMnemonic } from "../utils/recovery-key"; +import { + getUserRecoveryKeyB64, + recoveryKeyB64ToMnemonic, +} from "../services/recovery-key"; import { CodeBlock } from "./CodeBlock"; type RecoveryKeyProps = ModalVisibilityProps & { @@ -113,7 +115,7 @@ export const RecoveryKey: React.FC = ({ }; const getRecoveryKeyMnemonic = async () => - recoveryKeyB64ToMnemonic(await getRecoveryKey()); + recoveryKeyB64ToMnemonic(await getUserRecoveryKeyB64()); const downloadRecoveryKeyMnemonic = (key: string) => downloadString(key, "ente-recovery-key.txt"); diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 6223ea7497..34f3ef769c 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -1,7 +1,6 @@ import { TwoFactorAuthorizationResponse } from "ente-accounts/services/user"; import { clientPackageName, isDesktop } from "ente-base/app"; -import { generateKey } from "ente-base/crypto"; -import { encryptToB64 } from "ente-base/crypto/libsodium"; +import { encryptBoxB64, generateKey } from "ente-base/crypto"; import { authenticatedRequestHeaders, ensureOk, @@ -10,11 +9,11 @@ import { } from "ente-base/http"; import log from "ente-base/log"; import { apiURL } from "ente-base/origins"; -import { getRecoveryKey } from "ente-shared/crypto/helpers"; import HTTPService from "ente-shared/network/HTTPService"; import { getData, setData, setLSUser } from "ente-shared/storage/localStorage"; import { getToken } from "ente-shared/storage/localStorage/helpers"; import { z } from "zod"; +import { getUserRecoveryKeyB64 } from "./recovery-key"; import { unstashRedirect } from "./redirect"; /** @@ -108,11 +107,10 @@ export const openAccountsManagePasskeysPage = async () => { if (!recoveryEnabled) { // If not, enable it for them by creating the necessary recovery // information to prevent them from getting locked out. - const recoveryKeyB64 = await getRecoveryKey(); const resetSecret = await generateKey(); - const encryptionResult = await encryptToB64( + const encryptionResult = await encryptBoxB64( resetSecret, - recoveryKeyB64, + await getUserRecoveryKeyB64(), ); await configurePasskeyRecovery( resetSecret, diff --git a/web/packages/accounts/services/recovery-key.ts b/web/packages/accounts/services/recovery-key.ts new file mode 100644 index 0000000000..dcb1011337 --- /dev/null +++ b/web/packages/accounts/services/recovery-key.ts @@ -0,0 +1,122 @@ +import * as bip39 from "bip39"; +import { + decryptBoxB64, + fromHex, + sharedCryptoWorker, + toHex, +} from "ente-base/crypto"; +import { masterKeyFromSession } from "ente-base/session"; +import { getData, setData } from "ente-shared/storage/localStorage"; +import type { KeyAttributes } from "ente-shared/user/types"; +import { putUserRecoveryKeyAttributes } from "./user"; + +// Mobile client library only supports English. +bip39.setDefaultWordlist("english"); + +/** + * Convert the provided BIP-39 mnemonic string into its base64 representation. + * + * @param recoveryKeyMnemonicOrHex The BIP-39 mnemonic (24 word) string + * representing the recovery key. For legacy compatibility, the function also + * works if provided the hex representation of the recovery key. + * + * @returns A base64 string representing the underlying bytes of the recovery key. + */ +export const recoveryKeyB64FromMnemonic = ( + recoveryKeyMnemonicOrHex: string, +) => { + const trimmedInput = recoveryKeyMnemonicOrHex + .trim() + .split(" ") + .map((part) => part.trim()) + .filter((part) => !!part) + .join(" "); + + let recoveryKeyHex: string; + // Check if user is entering mnemonic recovery key. + if (trimmedInput.indexOf(" ") > 0) { + if (trimmedInput.split(" ").length != 24) { + throw new Error("recovery code should have 24 words"); + } + recoveryKeyHex = bip39.mnemonicToEntropy(trimmedInput); + } else { + recoveryKeyHex = trimmedInput; + } + + return fromHex(recoveryKeyHex); +}; + +/** + * Convert the provided base64 encoded recovery key into its BIP-39 mnemonic. + * + * @param recoveryKeyB64 The base64 encoded recovery key to mnemonize. + * + * @returns A 24-word mnemonic that serves as the user visible recovery key. + */ +export const recoveryKeyB64ToMnemonic = async (recoveryKeyB64: string) => + bip39.entropyToMnemonic(await toHex(recoveryKeyB64)); + +/** + * Return the (decrypted) recovery key of the logged in user. + * + * @returns The user's base64 encoded recovery key. + */ +export const getUserRecoveryKeyB64 = async () => { + const masterKey = await masterKeyFromSession(); + + const keyAttributes: KeyAttributes = getData("keyAttributes"); + const { recoveryKeyEncryptedWithMasterKey, recoveryKeyDecryptionNonce } = + keyAttributes; + + if (recoveryKeyEncryptedWithMasterKey && recoveryKeyDecryptionNonce) { + return decryptBoxB64( + { + encryptedData: recoveryKeyEncryptedWithMasterKey, + nonce: recoveryKeyDecryptionNonce, + }, + masterKey, + ); + } else { + return createNewRecoveryKey(masterKey); + } +}; + +/** + * Generate a new recovery key, tell remote about it, update our local state, + * and then return it. + * + * This function will be used only for legacy users for whom we did not generate + * recovery keys during sign up. + * + * @returns a new base64 encoded recovery key. + */ +const createNewRecoveryKey = async (masterKey: Uint8Array) => { + const existingAttributes = getData("keyAttributes"); + + const cryptoWorker = await sharedCryptoWorker(); + const recoveryKey = await cryptoWorker.generateKey(); + const encryptedMasterKey = await cryptoWorker.encryptBoxB64( + masterKey, + recoveryKey, + ); + const encryptedRecoveryKey = await cryptoWorker.encryptBoxB64( + recoveryKey, + masterKey, + ); + + const recoveryKeyAttributes = { + masterKeyEncryptedWithRecoveryKey: encryptedMasterKey.encryptedData, + masterKeyDecryptionNonce: encryptedMasterKey.nonce, + recoveryKeyEncryptedWithMasterKey: encryptedRecoveryKey.encryptedData, + recoveryKeyDecryptionNonce: encryptedRecoveryKey.nonce, + }; + + await putUserRecoveryKeyAttributes(recoveryKeyAttributes); + + setData("keyAttributes", { + ...existingAttributes, + ...recoveryKeyAttributes, + }); + + return recoveryKey; +}; diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts deleted file mode 100644 index 59738da782..0000000000 --- a/web/packages/accounts/utils/recovery-key.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as bip39 from "bip39"; -import { fromHex, toHex } from "ente-base/crypto"; - -// Mobile client library only supports English. -bip39.setDefaultWordlist("english"); - -/** - * Convert the provided BIP-39 mnemonic string into its base64 representation. - * - * @param recoveryKeyMnemonicOrHex The BIP-39 mnemonic (24 word) string - * representing the recovery key. For legacy compatibility, the function also - * works if provided the hex representation of the recovery key. - * - * @returns A base64 string representing the underlying bytes of the recovery key. - */ -export const recoveryKeyB64FromMnemonic = ( - recoveryKeyMnemonicOrHex: string, -) => { - const trimmedInput = recoveryKeyMnemonicOrHex - .trim() - .split(" ") - .map((part) => part.trim()) - .filter((part) => !!part) - .join(" "); - - let recoveryKeyHex: string; - // Check if user is entering mnemonic recovery key. - if (trimmedInput.indexOf(" ") > 0) { - if (trimmedInput.split(" ").length != 24) { - throw new Error("recovery code should have 24 words"); - } - recoveryKeyHex = bip39.mnemonicToEntropy(trimmedInput); - } else { - recoveryKeyHex = trimmedInput; - } - - return fromHex(recoveryKeyHex); -}; - -/** - * Convert the provided base64 encoded recovery key into its BIP-39 mnemonic. - * - * @param recoveryKeyB64 The base64 encoded recovery key to mnemonize. - * - * @returns A 24-word mnemonic that serves as the user visible recovery key. - */ -export const recoveryKeyB64ToMnemonic = async (recoveryKeyB64: string) => - bip39.entropyToMnemonic(await toHex(recoveryKeyB64)); diff --git a/web/packages/new/photos/components/gallery/helpers.ts b/web/packages/new/photos/components/gallery/helpers.ts index afce9688d9..81596bf6ef 100644 --- a/web/packages/new/photos/components/gallery/helpers.ts +++ b/web/packages/new/photos/components/gallery/helpers.ts @@ -10,10 +10,10 @@ * is a needed for fast refresh to work. */ +import { getUserRecoveryKeyB64 } from "ente-accounts/services/recovery-key"; import log from "ente-base/log"; import type { Collection } from "ente-media/collection"; import type { FamilyData } from "ente-new/photos/services/user-details"; -import { getRecoveryKey } from "ente-shared/crypto/helpers"; import type { User } from "ente-shared/user/types"; /** @@ -28,7 +28,7 @@ import type { User } from "ente-shared/user/types"; */ export const validateKey = async () => { try { - await getRecoveryKey(); + await getUserRecoveryKeyB64(); return true; } catch (e) { log.warn("Failed to validate key" /*, caller will logout */, e); diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index 646f9dff6b..0235f739e0 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -1,6 +1,5 @@ -import { putUserRecoveryKeyAttributes } from "ente-accounts/services/user"; +import { getUserRecoveryKeyB64 } from "ente-accounts/services/recovery-key"; import { sharedCryptoWorker } from "ente-base/crypto"; -import log from "ente-base/log"; import { masterKeyFromSession } from "ente-base/session"; import { getData, setData, setLSUser } from "ente-shared/storage/localStorage"; import { type SessionKey, setKey } from "ente-shared/storage/sessionStorage"; @@ -108,80 +107,10 @@ export const saveKeyInSessionStore = async ( export const encryptWithRecoveryKey = async (data: string) => { const cryptoWorker = await sharedCryptoWorker(); - const recoveryKeyB64 = await getRecoveryKey(); + const recoveryKeyB64 = await getUserRecoveryKeyB64(); return cryptoWorker.encryptBoxB64(data, recoveryKeyB64); }; -export const getRecoveryKey = async () => { - try { - const cryptoWorker = await sharedCryptoWorker(); - - const keyAttributes: KeyAttributes = getData("keyAttributes"); - const { - recoveryKeyEncryptedWithMasterKey, - recoveryKeyDecryptionNonce, - } = keyAttributes; - const masterKey = await masterKeyFromSession(); - - let recoveryKey: string; - if (recoveryKeyEncryptedWithMasterKey) { - recoveryKey = await cryptoWorker.decryptBoxB64( - { - encryptedData: recoveryKeyEncryptedWithMasterKey!, - nonce: recoveryKeyDecryptionNonce!, - }, - masterKey, - ); - } else { - recoveryKey = await createNewRecoveryKey(masterKey); - } - return recoveryKey; - } catch (e) { - log.error("getRecoveryKey failed", e); - throw e; - } -}; - -/** - * Generate a new recovery key, tell remote about it, update our local state, - * and then return it. - * - * This function will be used only for legacy users for whom we did not generate - * recovery keys during sign up. - * - * @returns a new base64 encoded recovery key. - */ -const createNewRecoveryKey = async (masterKey: Uint8Array) => { - const existingAttributes = getData("keyAttributes"); - - const cryptoWorker = await sharedCryptoWorker(); - const recoveryKey = await cryptoWorker.generateKey(); - const encryptedMasterKey = await cryptoWorker.encryptBoxB64( - masterKey, - recoveryKey, - ); - const encryptedRecoveryKey = await cryptoWorker.encryptBoxB64( - recoveryKey, - masterKey, - ); - - const recoveryKeyAttributes = { - masterKeyEncryptedWithRecoveryKey: encryptedMasterKey.encryptedData, - masterKeyDecryptionNonce: encryptedMasterKey.nonce, - recoveryKeyEncryptedWithMasterKey: encryptedRecoveryKey.encryptedData, - recoveryKeyDecryptionNonce: encryptedRecoveryKey.nonce, - }; - - await putUserRecoveryKeyAttributes(recoveryKeyAttributes); - - setData("keyAttributes", { - ...existingAttributes, - ...recoveryKeyAttributes, - }); - - return recoveryKey; -}; - /** * Decrypt the {@link encryptedChallenge} sent by remote during the delete * account flow ({@link getAccountDeleteChallenge}), returning a value that can From 6ce7921a16a3f641cbc53329172ff75a648a768d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 16:08:42 +0530 Subject: [PATCH 046/194] Inline --- web/packages/accounts/pages/recover.tsx | 2 +- .../accounts/pages/two-factor/recover.tsx | 2 +- web/packages/accounts/pages/two-factor/setup.tsx | 15 ++++++++------- web/packages/shared/crypto/helpers.ts | 7 ------- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index e0ed5eced5..7d4569795d 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -3,9 +3,9 @@ import { AccountsPageFooter, AccountsPageTitle, } from "ente-accounts/components/layouts/centered-paper"; +import { recoveryKeyB64FromMnemonic } from "ente-accounts/services/recovery-key"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import { sendOTT } from "ente-accounts/services/user"; -import { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; import { decryptBoxB64 } from "ente-base/crypto"; diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index 90d93ab23c..df55ea2682 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -5,12 +5,12 @@ import { AccountsPageFooter, AccountsPageTitle, } from "ente-accounts/components/layouts/centered-paper"; +import { recoveryKeyB64FromMnemonic } from "ente-accounts/services/recovery-key"; import { recoverTwoFactor, removeTwoFactor, type TwoFactorType, } from "ente-accounts/services/user"; -import { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import type { MiniDialogAttributes } from "ente-base/components/MiniDialog"; import { useBaseContext } from "ente-base/context"; diff --git a/web/packages/accounts/pages/two-factor/setup.tsx b/web/packages/accounts/pages/two-factor/setup.tsx index ec61ccacfe..d97f48bf31 100644 --- a/web/packages/accounts/pages/two-factor/setup.tsx +++ b/web/packages/accounts/pages/two-factor/setup.tsx @@ -8,11 +8,12 @@ import { CenteredFill } from "ente-base/components/containers"; import { LinkButton } from "ente-base/components/LinkButton"; import { ActivityIndicator } from "ente-base/components/mui/ActivityIndicator"; import { FocusVisibleButton } from "ente-base/components/mui/FocusVisibleButton"; -import { encryptWithRecoveryKey } from "ente-shared/crypto/helpers"; +import { encryptBoxB64 } from "ente-base/crypto"; import { getData, setLSUser } from "ente-shared/storage/localStorage"; import { t } from "i18next"; import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; +import { getUserRecoveryKeyB64 } from "../../services/recovery-key"; const Page: React.FC = () => { const [twoFactorSecret, setTwoFactorSecret] = useState< @@ -26,14 +27,14 @@ const Page: React.FC = () => { }, []); const handleSubmit = async (otp: string) => { - const { - encryptedData: encryptedTwoFactorSecret, - nonce: twoFactorSecretDecryptionNonce, - } = await encryptWithRecoveryKey(twoFactorSecret!.secretCode); + const encryptedBox = await encryptBoxB64( + twoFactorSecret!.secretCode, + await getUserRecoveryKeyB64(), + ); await enableTwoFactor({ code: otp, - encryptedTwoFactorSecret, - twoFactorSecretDecryptionNonce, + encryptedTwoFactorSecret: encryptedBox.encryptedData, + twoFactorSecretDecryptionNonce: encryptedBox.nonce, }); await setLSUser({ ...getData("user"), isTwoFactorEnabled: true }); await router.push(appHomeRoute); diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index 0235f739e0..7cb9a5a1c8 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -1,4 +1,3 @@ -import { getUserRecoveryKeyB64 } from "ente-accounts/services/recovery-key"; import { sharedCryptoWorker } from "ente-base/crypto"; import { masterKeyFromSession } from "ente-base/session"; import { getData, setData, setLSUser } from "ente-shared/storage/localStorage"; @@ -105,12 +104,6 @@ export const saveKeyInSessionStore = async ( } }; -export const encryptWithRecoveryKey = async (data: string) => { - const cryptoWorker = await sharedCryptoWorker(); - const recoveryKeyB64 = await getUserRecoveryKeyB64(); - return cryptoWorker.encryptBoxB64(data, recoveryKeyB64); -}; - /** * Decrypt the {@link encryptedChallenge} sent by remote during the delete * account flow ({@link getAccountDeleteChallenge}), returning a value that can From 0202cd144796869fb634e65d69235119c317c562 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 3 Jun 2025 16:23:08 +0530 Subject: [PATCH 047/194] Switch to exif_reader --- .../ui/viewer/file/file_details_widget.dart | 2 +- .../file_details/exif_item_widgets.dart | 2 +- mobile/lib/utils/exif_util.dart | 2 +- mobile/lib/utils/file_uploader_util.dart | 2 +- mobile/pubspec.lock | 37 ++++++++++++++++--- mobile/pubspec.yaml | 5 ++- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/mobile/lib/ui/viewer/file/file_details_widget.dart b/mobile/lib/ui/viewer/file/file_details_widget.dart index 58528159ae..1fa5a5ba70 100644 --- a/mobile/lib/ui/viewer/file/file_details_widget.dart +++ b/mobile/lib/ui/viewer/file/file_details_widget.dart @@ -2,7 +2,7 @@ import "dart:async"; import "dart:developer"; import "dart:io"; -import "package:exif/exif.dart"; +import "package:exif_reader/exif_reader.dart"; import "package:flutter/foundation.dart"; import "package:flutter/material.dart"; import "package:logging/logging.dart"; diff --git a/mobile/lib/ui/viewer/file_details/exif_item_widgets.dart b/mobile/lib/ui/viewer/file_details/exif_item_widgets.dart index dead77781b..d77a44988e 100644 --- a/mobile/lib/ui/viewer/file_details/exif_item_widgets.dart +++ b/mobile/lib/ui/viewer/file_details/exif_item_widgets.dart @@ -1,4 +1,4 @@ -import "package:exif/exif.dart"; +import "package:exif_reader/exif_reader.dart"; import "package:flutter/material.dart"; import "package:photos/generated/l10n.dart"; import 'package:photos/models/file/file.dart'; diff --git a/mobile/lib/utils/exif_util.dart b/mobile/lib/utils/exif_util.dart index 086a11e28b..f6d3363614 100644 --- a/mobile/lib/utils/exif_util.dart +++ b/mobile/lib/utils/exif_util.dart @@ -3,7 +3,7 @@ import "dart:developer"; import "dart:io"; import "package:computer/computer.dart"; -import 'package:exif/exif.dart'; +import 'package:exif_reader/exif_reader.dart'; import "package:ffmpeg_kit_flutter/ffprobe_kit.dart"; import "package:ffmpeg_kit_flutter/media_information.dart"; import "package:ffmpeg_kit_flutter/media_information_session.dart"; diff --git a/mobile/lib/utils/file_uploader_util.dart b/mobile/lib/utils/file_uploader_util.dart index 5ca93defe0..b49120ce7b 100644 --- a/mobile/lib/utils/file_uploader_util.dart +++ b/mobile/lib/utils/file_uploader_util.dart @@ -7,7 +7,7 @@ import 'dart:ui' as ui; import "package:archive/archive_io.dart"; import "package:computer/computer.dart"; import 'package:ente_crypto/ente_crypto.dart'; -import "package:exif/exif.dart"; +import "package:exif_reader/exif_reader.dart"; import 'package:logging/logging.dart'; import "package:motion_photos/motion_photos.dart"; import 'package:motionphoto/motionphoto.dart'; diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 14ad1f5347..7109e08165 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -167,6 +167,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + brotli: + dependency: transitive + description: + name: brotli + sha256: "7f891558ed779aab2bed874f0a36b8123f9ff3f19cf6efbee89e18ed294945ae" + url: "https://pub.dev" + source: hosted + version: "0.6.0" build: dependency: transitive description: @@ -566,14 +574,15 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" - exif: + exif_reader: dependency: "direct main" description: - name: exif - sha256: a7980fdb3b7ffcd0b035e5b8a5e1eef7cadfe90ea6a4e85ebb62f87b96c7a172 - url: "https://pub.dev" - source: hosted - version: "3.3.0" + path: "." + ref: "476f701c084861a8e9de5f4f4e5e067fc85fda96" + resolved-ref: "476f701c084861a8e9de5f4f4e5e067fc85fda96" + url: "https://github.com/mgenware/exif_reader.git" + source: git + version: "3.16.1" expandable: dependency: "direct main" description: @@ -1347,6 +1356,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + iso_base_media: + dependency: transitive + description: + name: iso_base_media + sha256: "0f5594feef1fba98179a2df95d1afbdda952de0c7a2e35e6815093f7c00aaf06" + url: "https://pub.dev" + source: hosted + version: "4.5.2" jni: dependency: transitive description: @@ -2103,6 +2120,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.2" + random_access_source: + dependency: transitive + description: + name: random_access_source + sha256: dc86934da2cc4777334f43916234410f232032738c519c0c3452147c5d4fec89 + url: "https://pub.dev" + source: hosted + version: "2.1.0" receive_sharing_intent: dependency: "direct main" description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 25701c6c44..2bc68c240e 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -61,7 +61,10 @@ dependencies: path: plugins/ente_feature_flag equatable: ^2.0.5 event_bus: ^2.0.0 - exif: ^3.0.0 + exif_reader: + git: + url: https://github.com/mgenware/exif_reader.git + ref: 476f701c084861a8e9de5f4f4e5e067fc85fda96 expandable: ^5.0.1 expansion_tile_card: ^3.0.0 extended_image: ^8.1.1 From c17a8d4c382a42d796be8ff197101fcda17cd26b Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 3 Jun 2025 16:23:16 +0530 Subject: [PATCH 048/194] Fix fr arb --- mobile/lib/l10n/intl_fr.arb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mobile/lib/l10n/intl_fr.arb b/mobile/lib/l10n/intl_fr.arb index 3f4dc5004c..811d3f1f28 100644 --- a/mobile/lib/l10n/intl_fr.arb +++ b/mobile/lib/l10n/intl_fr.arb @@ -891,7 +891,7 @@ "authToViewYourMemories": "Authentifiez-vous pour voir vos souvenirs", "unlock": "Déverrouiller", "freeUpSpace": "Libérer de l'espace", - "freeUpSpaceSaving": "{count, plural, one {}=1 {Il peut être supprimé de l'appareil pour libérer {formattedSize}} other {Ils peuvent être supprimés de l'appareil pour libérer {formattedSize}}}", + "freeUpSpaceSaving": "{count, plural, =1 {Il peut être supprimé de l'appareil pour libérer {formattedSize}} other {Ils peuvent être supprimés de l'appareil pour libérer {formattedSize}}}", "filesBackedUpInAlbum": "{count, plural, one {1 fichier dans cet album a été sauvegardé en toute sécurité} other {{formattedNumber} fichiers dans cet album ont été sauvegardés en toute sécurité}}", "@filesBackedUpInAlbum": { "description": "Text to tell user how many files have been backed up in the album", @@ -925,7 +925,7 @@ "@freeUpSpaceSaving": { "description": "Text to tell user how much space they can free up by deleting items from the device" }, - "freeUpAccessPostDelete": "Vous pouvez toujours {count, plural, one {}=1 {l'} other {les}} accéder sur Ente tant que vous avez un abonnement actif", + "freeUpAccessPostDelete": "Vous pouvez toujours {count, plural, =1 {l'} other {les}} accéder sur Ente tant que vous avez un abonnement actif", "@freeUpAccessPostDelete": { "placeholders": { "count": { @@ -1261,8 +1261,8 @@ "description": "Subtitle to indicate that the user can find people quickly by name" }, "findPeopleByName": "Trouver des personnes rapidement par leur nom", - "addViewers": "{count, plural, one {}=0 {Ajouter un spectateur} =1 {Ajouter une spectateur} other {Ajouter des spectateurs}}", - "addCollaborators": "{count, plural, one {}=0 {Ajouter un collaborateur} =1 {Ajouter un collaborateur} other {Ajouter des collaborateurs}}", + "addViewers": "{count, plural, =0 {Ajouter un spectateur} =1 {Ajouter une spectateur} other {Ajouter des spectateurs}}", + "addCollaborators": "{count, plural, =0 {Ajouter un collaborateur} =1 {Ajouter un collaborateur} other {Ajouter des collaborateurs}}", "longPressAnEmailToVerifyEndToEndEncryption": "Appuyez longuement sur un email pour vérifier le chiffrement de bout en bout.", "developerSettingsWarning": "Êtes-vous sûr de vouloir modifier les paramètres du développeur ?", "developerSettings": "Paramètres du développeur", @@ -1396,7 +1396,7 @@ "enableMachineLearningBanner": "Activer l'apprentissage automatique pour la reconnaissance des visages et la recherche magique", "searchDiscoverEmptySection": "Les images seront affichées ici une fois le traitement terminé", "searchPersonsEmptySection": "Les personnes seront affichées ici une fois le traitement terminé", - "viewersSuccessfullyAdded": "{count, plural, one {}=0 {0 spectateur ajouté} =1 {Un spectateur ajouté} other {{count} spectateurs ajoutés}}", + "viewersSuccessfullyAdded": "{count, plural, =0 {0 spectateur ajouté} =1 {Un spectateur ajouté} other {{count} spectateurs ajoutés}}", "@viewersSuccessfullyAdded": { "placeholders": { "count": { From 113b82045186e7ca3d063b8786f7e1211b766517 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 16:54:17 +0530 Subject: [PATCH 049/194] Same name --- web/packages/accounts/pages/two-factor/setup.tsx | 6 +++--- web/packages/accounts/services/passkey.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/packages/accounts/pages/two-factor/setup.tsx b/web/packages/accounts/pages/two-factor/setup.tsx index d97f48bf31..2f128d35da 100644 --- a/web/packages/accounts/pages/two-factor/setup.tsx +++ b/web/packages/accounts/pages/two-factor/setup.tsx @@ -27,14 +27,14 @@ const Page: React.FC = () => { }, []); const handleSubmit = async (otp: string) => { - const encryptedBox = await encryptBoxB64( + const box = await encryptBoxB64( twoFactorSecret!.secretCode, await getUserRecoveryKeyB64(), ); await enableTwoFactor({ code: otp, - encryptedTwoFactorSecret: encryptedBox.encryptedData, - twoFactorSecretDecryptionNonce: encryptedBox.nonce, + encryptedTwoFactorSecret: box.encryptedData, + twoFactorSecretDecryptionNonce: box.nonce, }); await setLSUser({ ...getData("user"), isTwoFactorEnabled: true }); await router.push(appHomeRoute); diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 34f3ef769c..e2a749a93e 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -108,14 +108,14 @@ export const openAccountsManagePasskeysPage = async () => { // If not, enable it for them by creating the necessary recovery // information to prevent them from getting locked out. const resetSecret = await generateKey(); - const encryptionResult = await encryptBoxB64( + const box = await encryptBoxB64( resetSecret, await getUserRecoveryKeyB64(), ); await configurePasskeyRecovery( resetSecret, - encryptionResult.encryptedData, - encryptionResult.nonce, + box.encryptedData, + box.nonce, ); } From c0d57c72bfa6c73ce4b1bc3a3be9c4a6d2198884 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 19:33:03 +0530 Subject: [PATCH 050/194] LP tweaks --- .../gallery/components/viewer/photoswipe.ts | 104 +++++++++++++++++- 1 file changed, 98 insertions(+), 6 deletions(-) diff --git a/web/packages/gallery/components/viewer/photoswipe.ts b/web/packages/gallery/components/viewer/photoswipe.ts index f26d566f29..7cd0da86b8 100644 --- a/web/packages/gallery/components/viewer/photoswipe.ts +++ b/web/packages/gallery/components/viewer/photoswipe.ts @@ -353,6 +353,24 @@ export class FileViewerPhotoSwipe { return asItemData(content.data).isContentZoomable ?? isZoomable; }); + /** + * Last state of the live photo playback on initial display. + */ + let livePhotoPlayInitial = true; + + /** + * Set to the event listener that will be called at the end of the + * initial playback of the initial playback of a live photo on the + * currently displayed slide. + * + * This will be present only during the initial playback (it will be + * cleared when initial playback completes), and can also thus be used + * as a pseudo `isPlayingLivePhotoInitial`. + */ + let livePhotoPlayInitialEndedEvent: + | { listener: () => void; video: HTMLVideoElement } + | undefined; + /** * Last state of the live photo playback toggle. */ @@ -375,17 +393,91 @@ export class FileViewerPhotoSwipe { /** * Update the state of the given {@link videoElement} and the - * {@link livePhotoPlayButtonElement} to reflect {@link livePhotoPlay}. + * {@link livePhotoPlayButtonElement} to reflect + * {@link livePhotoPlayInitial}. + * + * [Note: Live photo playback] + * + * 1. When opening a live photo, play it once unless + * {@link livePhotoPlayInitial} is disabled. This is the behaviour + * controlled by the {@link livePhotoUpdatePlayInitial} function. + * + * 2. If the user toggles playback of during the initial video playback, + * then remember their choice for the current session in the using + * the {@link livePhotoPlayInitial} variable. + * + * 3. The user can play the video again in a loop by activating the + * {@link livePhotoPlayButtonElement}, which triggers the + * {@link livePhotoUpdatePlayToggle} function. This playback state is + * maintained in the {@link livePhotoPlay} variable. */ - const livePhotoUpdatePlay = (video: HTMLVideoElement) => { + const livePhotoUpdatePlayInitial = (video: HTMLVideoElement) => { + const button = livePhotoPlayButtonElement; + if (button) showIf(button, true); + + // Remove any loop attributes we might've inherited from a + // previously displayed live photos elements on the current slide. + video.removeAttribute("loop"); + + // And same for the transient playback state. + if (livePhotoPlayInitialEndedEvent) { + const { video, listener } = livePhotoPlayInitialEndedEvent; + video.removeEventListener("ended", listener); + livePhotoPlayInitialEndedEvent = undefined; + } + + if (livePhotoPlayInitial) { + // Play it once + button?.classList.remove("pswp-ente-off"); + void abortablePlayVideo(video); + video.style.display = "initial"; + const listener = () => { + button?.classList.add("pswp-ente-off"); + video.style.display = "none"; + livePhotoPlayInitialEndedEvent = undefined; + }; + livePhotoPlayInitialEndedEvent = { video, listener }; + video.addEventListener("ended", listener, { once: true }); + } else { + button?.classList.add("pswp-ente-off"); + video.pause(); + video.style.display = "none"; + } + }; + + /** + * See: [Note: Live photo playback] + * + * This function handles the playback toggled via an explicit user + * action (button activation or keyboard shortcut). + */ + const livePhotoUpdatePlayToggle = (video: HTMLVideoElement) => { const button = livePhotoPlayButtonElement; if (button) showIf(button, true); if (livePhotoPlay) { + // Add the loop attribute. + video.setAttribute("loop", ""); + button?.classList.remove("pswp-ente-off"); void abortablePlayVideo(video); video.style.display = "initial"; } else { + // Remove the loop attributes to clean up after ourselves (not + // necessarily needed because we remove it on slide change too). + video.removeAttribute("loop"); + + // If we're in the middle of the initial playback, remember the + // user's choice to disable autoplay. + if (livePhotoPlayInitialEndedEvent) { + livePhotoPlayInitial = false; + + // And reset the event handler. + const { video, listener } = livePhotoPlayInitialEndedEvent; + video.removeEventListener("ended", listener); + livePhotoPlayInitialEndedEvent = undefined; + } + button?.classList.add("pswp-ente-off"); video.pause(); video.style.display = "none"; @@ -444,7 +536,7 @@ export class FileViewerPhotoSwipe { if (!buttonElement || !video) return; livePhotoPlay = !livePhotoPlay; - livePhotoUpdatePlay(video); + livePhotoUpdatePlayToggle(video); }; /** @@ -634,7 +726,7 @@ export class FileViewerPhotoSwipe { // already been called, but now "contentAppend" is happening. if (currSlideData().fileID == fileID) { - livePhotoUpdatePlay(video); + livePhotoUpdatePlayInitial(video); livePhotoUpdateMute(video); } }); @@ -909,7 +1001,7 @@ export class FileViewerPhotoSwipe { pswp.on("change", () => { const video = livePhotoVideoOnSlide(pswp.currSlide); if (video) { - livePhotoUpdatePlay(video); + livePhotoUpdatePlayInitial(video); } else { // Not a live photo, or its video hasn't loaded yet. showIf(buttonElement, false); @@ -1529,7 +1621,7 @@ const hlsVideoControlsHTML = () => ` // playsinline will play the video inline on mobile browsers (where the default // is to open a full screen player). const livePhotoVideoHTML = (videoURL: string) => ` -