diff --git a/auth/assets/custom-icons/_data/custom-icons.json b/auth/assets/custom-icons/_data/custom-icons.json index e61f302c33..29c52254e7 100644 --- a/auth/assets/custom-icons/_data/custom-icons.json +++ b/auth/assets/custom-icons/_data/custom-icons.json @@ -1429,6 +1429,15 @@ "Toshl" ] }, + { + "title": "Tebex", + "slug": "tebex", + "altNames": [ + "tebex", + "tebex.io", + "buycraft" + ] + }, { "title": "xAI", "slug": "xai" diff --git a/auth/assets/custom-icons/icons/tebex.svg b/auth/assets/custom-icons/icons/tebex.svg new file mode 100644 index 0000000000..b7ec26fae5 --- /dev/null +++ b/auth/assets/custom-icons/icons/tebex.svg @@ -0,0 +1,19 @@ + + vikunja + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/docs/.vitepress/sidebar.ts b/docs/docs/.vitepress/sidebar.ts index 7845572bf2..a90caad19b 100644 --- a/docs/docs/.vitepress/sidebar.ts +++ b/docs/docs/.vitepress/sidebar.ts @@ -52,6 +52,10 @@ export const sidebar = [ link: "/photos/features/machine-learning", }, { text: "Map", link: "/photos/features/map" }, + { + text: "Notifications", + link: "/photos/features/notifications", + }, { text: "Passkeys", link: "/photos/features/passkeys", diff --git a/docs/docs/photos/features/machine-learning.md b/docs/docs/photos/features/machine-learning.md index 215c1d98a8..e43edd5b43 100644 --- a/docs/docs/photos/features/machine-learning.md +++ b/docs/docs/photos/features/machine-learning.md @@ -47,8 +47,20 @@ device. The indexes are synced across all your devices automatically using the same end-to-end encrypted security that we use for syncing your photos. -Note that the desktop app does not currently support modifying the face -groupings, that is only supported by the mobile app. +--- + +#### Local indexing on mobile + +In general the machine learning is optimized to work well on most mobile device. +However, devices with low RAM (4-6GB) and large photo libraries might struggle +to complete the indexing without affecting performance of the app. In such case, +you might want to disable local indexing and let the desktop run it instead. + +You can disable local indexing from the settings, under +`General > Advanced > Machine learning > Configuration`. This way, you can +continue to use the ML features without your phone performance taking any hit. + +--- For more information on how to use Machine Learning for face recognition please check out [the FAQ](../faq/face-recognition). diff --git a/docs/docs/photos/features/notifications.md b/docs/docs/photos/features/notifications.md new file mode 100644 index 0000000000..0cf33a6e0e --- /dev/null +++ b/docs/docs/photos/features/notifications.md @@ -0,0 +1,33 @@ +--- +title: Notifications +description: Details about notifications in Ente +--- + +# Notifications + +The Ente app can send notifications to notify you of an update, or just to +remind you of some sweet or helpful memory at the right time. + +## New shared photos + +Receive notifications when someone adds a photo to a shared album that you're a +part of. + +## "On this day" memories + +Receive reminders about memories from this day in previous years. These +reminders will only be shown if there are enough photos taken across previous +years of the specific day. + +## Birthday notifications + +Receive reminders when it's someone's birthday. Tapping on the notification will +take you to photos of the birthday person. This requires you to first add a +birthday to a person, and will only be shown if there are enough photos of that +person. + +## Notification permission + +By default all notification categories are enabled if you give notification +permission. You can disable all of the above notification categories from +`Settings > Notifications`. Notifications currently only work on mobile. diff --git a/mobile/fastlane/metadata/android/pt_PT/full_description.txt b/mobile/fastlane/metadata/android/pt_PT/full_description.txt index 38a7c1b83f..9cd11069c4 100644 --- a/mobile/fastlane/metadata/android/pt_PT/full_description.txt +++ b/mobile/fastlane/metadata/android/pt_PT/full_description.txt @@ -4,33 +4,33 @@ Se busca por uma alternativa do Google Fotos baseada em privacidade, chegaste ao Temos aplicações de fonte aberta para Android, iOS, sítio web e desktop, e as fotos serão perfeitamente sincronizadas entre todas elas numa maneira de encriptação de ponta a ponta (e2ee). -Ente também simplifica a partilha dos seus álbuns com os seus entes queridos, mesmo que estes não estejam no ente. Pode partilhar ligações visíveis publicamente, onde podem ver o seu álbum e colaborar adicionando fotografias ao mesmo, mesmo sem uma conta ou aplicação. +Ente facilita o partilhamento dos seus álbuns com entes queridos, mesmo se não estiverem no ente. Pode partilhar ligações visíveis a público, onde eles podem ver o seu álbum e colaborar a adicionar fotos, mesmo sem uma conta ou a aplicação. -Os seus dados encriptados são replicados em 3 locais diferentes, incluindo um abrigo de emergência em Paris. Levamos a posteridade a sério e facilitamos a tarefa de garantir que as suas memórias perdurem para além de si. +Os dados são replicados em 3 localizações diferentes, incluindo um posto em Paris. Levamos a nossa postura a sério e facilitamos para certificarmos que as suas memórias revivam-no. -Estamos aqui para criar a aplicação de fotografias mais segura de sempre, junte-se à nossa viagem! +Estamos aqui para fazer a aplicação mais segura do mundo, venha e adere a nossa jornada! -RECURSOS -- Cópias de segurança de qualidade original, porque cada pixel é importante -- Planos familiares, para que possa partilhar o armazenamento com a sua família -- Álbuns colaborativos, para que possa reunir fotos depois de uma viagem -- Pastas partilhadas, caso queira que o seu parceiro desfrute dos seus cliques na “Câmara” -- Links para álbuns, que podem ser protegidas com uma palavra-passe -- Capacidade de libertar espaço, removendo ficheiros dos quais foi feita uma cópia de segurança segura -- Apoio humano, porque vale a pena -- Descrições, para que possa legendar as suas memórias e encontrá-las facilmente -- Editor de imagens, para dar os retoques finais -- Favoritar, ocultar e reviver suas memórias, pois elas são preciosas -- Importação com um clique do Google, da Apple, do seu disco rígido e muito mais -- Tema escuro, porque as suas fotos ficam bem com ele +FUNCIONALIDADES +- Backups com qualidade original, por cada píxel valer a pena +- Planos em família, para poder partilhar armazenamento com familiares +- Álbuns de colaboração, para unir fotos depois de uma caminhada +- Pastas partilhadas, se quiser que o seu parceiro desfrute dos seus cliques na "Câmara" +- Ligações para álbuns, que podem ser protegidos com uma palavra-passe +- Possibilidade de liberar espaço, removendo ficheiros que já foram feitos backup +- Suporte físico, por valer a pena +- Descrições, para entender as suas memórias e encontrá-las facilmente +- Editor de imagens, para dar retoques finais +- Adicionar aos favoritos, obscurecer e reviver as suas memórias, para aqueles tão preciosos +- Importar num só clique do Google, Apple, e o seu disco rígido e mais +- Tema escuro, para as suas fotos encaixarem melhor - 2FA, 3FA, autenticação biométrica -- e MUITO mais! +- e MAIS além! PERMISSÕES -ente solicita determinadas permissões para servir o objetivo de um fornecedor de armazenamento de fotografias, que pode ser consultado aqui: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md +Ente pede por certas permissões para servir o propósito dum provedor de armazenamento de foto, onde pode ser revisto aqui: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md PREÇO -Não oferecemos planos gratuitos para sempre, porque é importante para nós mantermo-nos sustentáveis e resistirmos ao teste do tempo. Em vez disso, oferecemos planos acessíveis que pode partilhar livremente com a sua família. Pode encontrar mais informações em ente.io. +Não garantimos planos gratuitos para sempre, já que é importante a nós mantermo-nos sustentáveis e conseguirmos superar o desafio do tempo. Ao invés, garantimos planos acessíveis para poder partilhar livremente com os seus familiares. Para mais informações, consulte "ente.io" -SUPPORT -Orgulhamo-nos de oferecer um apoio humano Se for nosso cliente pago, pode contactar team@ente.io e esperar uma resposta da nossa equipa no prazo de 24 horas. +SUPORTE +Estamos orgulhosos ao oferecer suporte físico. Se for um cliente pago, pode contactar a nossa equipa através de "team@ente.io" e esperar uma resposta nossa dentro de um dia. diff --git a/mobile/lib/l10n/intl_ar.arb b/mobile/lib/l10n/intl_ar.arb index 7a1238ffda..c37adfd0c3 100644 --- a/mobile/lib/l10n/intl_ar.arb +++ b/mobile/lib/l10n/intl_ar.arb @@ -1730,25 +1730,15 @@ "onTheRoad": "على الطريق مرة أخرى", "food": "متعة الطهي", "pets": "رفاق فروي", - "cLIcon": "أيقونة جديدة", - "cLIconDesc": "أخيرًا، أيقونة تطبيق جديدة، نعتقد أنها تمثل عملنا على أفضل وجه. أضفنا أيضًا مبدل أيقونات حتى تتمكن من الاستمرار في استخدام الأيقونة القديمة.", - "cLMemories": "الذكريات", - "cLMemoriesDesc": "أعد اكتشاف لحظاتك الخاصة - تسليط الضوء على الأشخاص المفضلين لديك، رحلاتك وعطلاتك، أفضل لقطاتك، وأكثر من ذلك بكثير. قم بتشغيل تعلم الآلة، ضع علامة على نفسك وقم بتسمية أصدقائك للحصول على أفضل تجربة.", - "cLWidgets": "الأدوات المصغرة (Widgets)", - "cLWidgetsDesc": "الأدوات المصغرة للشاشة الرئيسية المدمجة مع الذكريات متاحة الآن. ستعرض لحظاتك الخاصة دون فتح التطبيق.", - "cLFamilyPlan": "حدود الخطة العائلية", - "cLFamilyPlanDesc": "يمكنك الآن تعيين حدود لمقدار التخزين الذي يمكن لأفراد عائلتك استخدامه.", - "cLBulkEdit": "تعديل التواريخ بشكل جماعي", - "cLBulkEditDesc": "يمكنك الآن تحديد صور متعددة، وتعديل التاريخ/الوقت لجميعها بإجراء سريع واحد. تغيير التواريخ مدعوم أيضًا.", "curatedMemories": "ذكريات منسقة", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "memories": "ذكريات", + "deleteMultipleAlbumDialog": "هل تريد أيضًا حذف الصور (والمقاطع) الموجودة في هذه الألبومات {count} من كافة الألبومات الأخرى التي تشترك فيها؟", + "addParticipants": "إضافة مشاركين", + "selectedAlbums": "{count} تم تحديد", + "actionNotSupportedOnFavouritesAlbum": "الإجراء غير مدعوم في ألبوم المفضلة", + "onThisDay": "في هذا اليوم", + "newPhotosEmoji": " جديد 📸", + "happyBirthday": "عيد ميلاد سعيد! 🥳", + "happyBirthdayToPerson": "عيد ميلاد سعيد إلى {name}! 🎉", + "birthdays": "أعياد الميلاد" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_be.arb b/mobile/lib/l10n/intl_be.arb index 570e7f9e71..96cf8a2814 100644 --- a/mobile/lib/l10n/intl_be.arb +++ b/mobile/lib/l10n/intl_be.arb @@ -208,15 +208,5 @@ "darkTheme": "Цёмная", "systemTheme": "Сістэма", "freeTrial": "Бясплатная пробная версія", - "faqs": "Частыя пытанні", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "faqs": "Частыя пытанні" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_bg.arb b/mobile/lib/l10n/intl_bg.arb index abc21027fd..c8494661c6 100644 --- a/mobile/lib/l10n/intl_bg.arb +++ b/mobile/lib/l10n/intl_bg.arb @@ -1,13 +1,3 @@ { - "@@locale ": "en", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "@@locale ": "en" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ca.arb b/mobile/lib/l10n/intl_ca.arb index abc21027fd..c8494661c6 100644 --- a/mobile/lib/l10n/intl_ca.arb +++ b/mobile/lib/l10n/intl_ca.arb @@ -1,13 +1,3 @@ { - "@@locale ": "en", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "@@locale ": "en" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_cs.arb b/mobile/lib/l10n/intl_cs.arb index ef9c1d94e6..fa046acab2 100644 --- a/mobile/lib/l10n/intl_cs.arb +++ b/mobile/lib/l10n/intl_cs.arb @@ -436,18 +436,5 @@ "previous": "Předchozí", "newRange": "Nový rozsah", "youAndThem": "Vy a {name}", - "selfiesWithThem": "Selfie s {name}", - "cLIcon": "Nová ikona", - "cLMemories": "Vzpomínky", - "cLWidgets": "Widgety", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "selfiesWithThem": "Selfie s {name}" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_da.arb b/mobile/lib/l10n/intl_da.arb index c3d869d43b..dfd9dee37f 100644 --- a/mobile/lib/l10n/intl_da.arb +++ b/mobile/lib/l10n/intl_da.arb @@ -322,15 +322,5 @@ "longPressAnEmailToVerifyEndToEndEncryption": "Langt tryk på en e-mail for at bekræfte slutningen af krypteringen.", "developerSettingsWarning": "Er du sikker på, at du vil ændre udviklerindstillingerne?", "next": "Næste", - "enterPin": "Indtast PIN", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "enterPin": "Indtast PIN" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_de.arb b/mobile/lib/l10n/intl_de.arb index 38f0408969..9f74523ca4 100644 --- a/mobile/lib/l10n/intl_de.arb +++ b/mobile/lib/l10n/intl_de.arb @@ -1731,14 +1731,30 @@ "food": "Kulinarische Genüsse", "pets": "Pelzige Begleiter", "curatedMemories": "Ausgewählte Erinnerungen", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "widgets": "Widgets", + "memories": "Erinnerungen", + "peopleWidgetDesc": "Wähle die Personen, die du auf der Startseite sehen möchtest.", + "albumsWidgetDesc": "Wähle die Alben, die du auf der Startseite sehen möchtest.", + "memoriesWidgetDesc": "Wähle die Arten von Erinnerungen, die du auf der Startseite sehen möchtest.", + "smartMemories": "Smarte Erinnerungen", + "pastYearsMemories": "Erinnerungen der letzten Jahre", + "deleteMultipleAlbumDialog": "Sollen die Fotos (und Videos) aus diesen {count} Alben auch aus allen anderen Alben gelöscht werden, in denen sie enthalten sind?", + "addParticipants": "Teilnehmer hinzufügen", + "selectedAlbums": "{count} ausgewählt", + "actionNotSupportedOnFavouritesAlbum": "Aktion für das Favoritenalbum nicht unterstützt", + "onThisDayMemories": "Erinnerungen an diesem Tag", + "onThisDay": "An diesem Tag", + "lookBackOnYourMemories": "Schau zurück auf deine Erinnerungen 🌄", + "newPhotosEmoji": " neue 📸", + "sorryWeHadToPauseYourBackups": "Entschuldigung, wir mussten deine Sicherungen pausieren", + "clickToInstallOurBestVersionYet": "Klicke, um unsere bisher beste Version zu installieren", + "onThisDayNotificationExplanation": "Erhalte Erinnerungen von diesem Tag in den vergangenen Jahren.", + "addMemoriesWidgetPrompt": "Füge ein Erinnerungs-Widget zu deiner Startseite hinzu und komm hierher zurück, um es anzupassen.", + "addAlbumWidgetPrompt": "Füge ein Alben-Widget zu deiner Startseite hinzu und komm hierher zurück, um es anzupassen.", + "addPeopleWidgetPrompt": "Füge ein Personen-Widget zu deiner Startseite hinzu und komm hierher zurück, um es anzupassen.", + "birthdayNotifications": "Geburtstagsbenachrichtigungen", + "receiveRemindersOnBirthdays": "Erhalte Erinnerungen, wenn jemand Geburtstag hat. Ein Klick auf die Benachrichtigung bringt dich zu den Fotos der Person, die Geburtstag hat.", + "happyBirthday": "Herzlichen Glückwunsch zum Geburtstag! 🥳", + "happyBirthdayToPerson": "Alles Gutes zum Geburtstag an {name}! 🎉", + "birthdays": "Geburtstage" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_el.arb b/mobile/lib/l10n/intl_el.arb index a0578b6ccd..ce8b1a1a54 100644 --- a/mobile/lib/l10n/intl_el.arb +++ b/mobile/lib/l10n/intl_el.arb @@ -1,14 +1,4 @@ { "@@locale ": "en", - "enterYourEmailAddress": "Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "enterYourEmailAddress": "Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_es.arb b/mobile/lib/l10n/intl_es.arb index d8c567f6f5..665724372d 100644 --- a/mobile/lib/l10n/intl_es.arb +++ b/mobile/lib/l10n/intl_es.arb @@ -1724,25 +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", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "curatedMemories": "Memorias revisadas" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_et.arb b/mobile/lib/l10n/intl_et.arb index a263b070b3..dfe1fba1a4 100644 --- a/mobile/lib/l10n/intl_et.arb +++ b/mobile/lib/l10n/intl_et.arb @@ -218,15 +218,5 @@ "storageBreakupYou": "Sina", "@storageBreakupYou": { "description": "Label to indicate how much storage you are using when you are part of a family plan" - }, - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + } } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_eu.arb b/mobile/lib/l10n/intl_eu.arb index cdddc4a675..b08aaee4ef 100644 --- a/mobile/lib/l10n/intl_eu.arb +++ b/mobile/lib/l10n/intl_eu.arb @@ -458,15 +458,5 @@ "iOSLockOut": "Autentifikazio biometrikoa deuseztatuta dago. Mesedez, blokeatu eta desblokeatu zure pantaila indarrean jartzeko.", "@iOSLockOut": { "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." - }, - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + } } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_fa.arb b/mobile/lib/l10n/intl_fa.arb index 53738d922f..edabff8132 100644 --- a/mobile/lib/l10n/intl_fa.arb +++ b/mobile/lib/l10n/intl_fa.arb @@ -307,15 +307,5 @@ "developerSettings": "تنظیمات توسعه‌دهنده", "search": "جستجو", "whatsNew": "تغییرات جدید", - "reviewSuggestions": "مرور پیشنهادها", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "reviewSuggestions": "مرور پیشنهادها" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_fr.arb b/mobile/lib/l10n/intl_fr.arb index 08ca5989ff..a9d7a8aa5e 100644 --- a/mobile/lib/l10n/intl_fr.arb +++ b/mobile/lib/l10n/intl_fr.arb @@ -899,7 +899,7 @@ "authToViewYourMemories": "Authentifiez-vous pour voir vos souvenirs", "unlock": "Déverrouiller", "freeUpSpace": "Libérer de l'espace", - "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}}}", + "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}}}", "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", @@ -933,7 +933,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, =1 {l'} other {les}} accéder sur Ente tant que vous avez un abonnement actif", + "freeUpAccessPostDelete": "Vous pouvez toujours {count, plural, one {}=1 {l'} other {les}} accéder sur Ente tant que vous avez un abonnement actif", "@freeUpAccessPostDelete": { "placeholders": { "count": { @@ -1269,8 +1269,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, =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}}", + "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}}", "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", @@ -1404,7 +1404,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, =0 {0 spectateur ajouté} =1 {Un spectateur ajouté} other {{count} spectateurs ajoutés}}", + "viewersSuccessfullyAdded": "{count, plural, one {}=0 {0 spectateur ajouté} =1 {Un spectateur ajouté} other {{count} spectateurs ajoutés}}", "@viewersSuccessfullyAdded": { "placeholders": { "count": { @@ -1731,14 +1731,25 @@ "food": "Plaisir culinaire", "pets": "Compagnons à quatre pattes", "curatedMemories": "Souvenirs conservés", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "widgets": "Gadgets", + "memories": "Souvenirs", + "peopleWidgetDesc": "Sélectionnez les personnes que vous souhaitez voir sur votre écran d'accueil.", + "albumsWidgetDesc": "Sélectionnez les personnes que vous souhaitez voir sur votre écran d'accueil.", + "memoriesWidgetDesc": "Sélectionnez le type de souvenirs que vous souhaitez voir sur votre écran d'accueil.", + "smartMemories": "Souvenirs intelligents", + "pastYearsMemories": "Souvenirs de ces dernières années", + "deleteMultipleAlbumDialog": "Supprimer également les photos (et les vidéos) présentes dans ces {count} albums de tous les autres albums dont ils font partie ?", + "addParticipants": "Ajouter des participants", + "selectedAlbums": "{count} sélectionné(s)", + "actionNotSupportedOnFavouritesAlbum": "Action non prise en charge sur l'album des Favoris", + "onThisDayMemories": "Souvenirs du jour", + "onThisDay": "Ce jour-ci", + "lookBackOnYourMemories": "Regarde tes souvenirs passés 🌄", + "newPhotosEmoji": " nouveau 📸", + "sorryWeHadToPauseYourBackups": "Désolé, nous avons dû mettre en pause vos sauvegardes", + "clickToInstallOurBestVersionYet": "Cliquez pour installer notre meilleure version", + "onThisDayNotificationExplanation": "Recevoir des rappels sur les souvenirs de cette journée des années précédentes.", + "addMemoriesWidgetPrompt": "Ajoutez un gadget des souvenirs à votre écran d'accueil et revenez ici pour le personnaliser.", + "addAlbumWidgetPrompt": "Ajoutez un gadget d'album à votre écran d'accueil et revenez ici pour le personnaliser.", + "addPeopleWidgetPrompt": "Ajoutez un gadget des personnes à votre écran d'accueil et revenez ici pour le personnaliser." } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_gu.arb b/mobile/lib/l10n/intl_gu.arb index abc21027fd..c8494661c6 100644 --- a/mobile/lib/l10n/intl_gu.arb +++ b/mobile/lib/l10n/intl_gu.arb @@ -1,13 +1,3 @@ { - "@@locale ": "en", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "@@locale ": "en" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_he.arb b/mobile/lib/l10n/intl_he.arb index bbff73ef16..c4232070a8 100644 --- a/mobile/lib/l10n/intl_he.arb +++ b/mobile/lib/l10n/intl_he.arb @@ -791,15 +791,5 @@ "addPhotos": "הוסף תמונות", "create": "צור", "viewAll": "הצג הכל", - "hiding": "מחביא...", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "hiding": "מחביא..." } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_hi.arb b/mobile/lib/l10n/intl_hi.arb index c40173480b..b79d9682f2 100644 --- a/mobile/lib/l10n/intl_hi.arb +++ b/mobile/lib/l10n/intl_hi.arb @@ -48,15 +48,5 @@ "sorry": "क्षमा करें!", "noRecoveryKeyNoDecryption": "हमारे एंड-टू-एंड एन्क्रिप्शन प्रोटोकॉल की प्रकृति के कारण, आपके डेटा को आपके पासवर्ड या रिकवरी कुंजी के बिना डिक्रिप्ट नहीं किया जा सकता है", "verifyEmail": "ईमेल सत्यापित करें", - "toResetVerifyEmail": "अपना पासवर्ड रीसेट करने के लिए, कृपया पहले अपना ईमेल सत्यापित करें।", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "toResetVerifyEmail": "अपना पासवर्ड रीसेट करने के लिए, कृपया पहले अपना ईमेल सत्यापित करें।" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_hu.arb b/mobile/lib/l10n/intl_hu.arb index e560efa38d..92bdab9095 100644 --- a/mobile/lib/l10n/intl_hu.arb +++ b/mobile/lib/l10n/intl_hu.arb @@ -10,15 +10,5 @@ "deleteAccount": "Fiók törlése", "askDeleteReason": "Miért törli a fiókját?", "deleteAccountFeedbackPrompt": "Sajnáljuk, hogy távozik. Kérjük, ossza meg velünk visszajelzéseit, hogy segítsen nekünk a fejlődésben.", - "feedback": "Visszajelzés", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "feedback": "Visszajelzés" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_id.arb b/mobile/lib/l10n/intl_id.arb index fc51c89709..a3a4e66d10 100644 --- a/mobile/lib/l10n/intl_id.arb +++ b/mobile/lib/l10n/intl_id.arb @@ -4,6 +4,7 @@ "enterYourNewEmailAddress": "Masukkan alamat email baru anda", "accountWelcomeBack": "Selamat datang kembali!", "emailAlreadyRegistered": "Email sudah terdaftar.", + "emailNotRegistered": "Email belum terdaftar.", "email": "Email", "cancel": "Batal", "verify": "Verifikasi", @@ -330,6 +331,7 @@ "removingFromFavorites": "Menghapus dari favorit...", "sorryCouldNotAddToFavorites": "Maaf, tidak dapat menambahkan ke favorit!", "sorryCouldNotRemoveFromFavorites": "Maaf, tidak dapat menghapus dari favorit!", + "subscribeToEnableSharing": "Anda memerlukan langganan berbayar yang aktif untuk bisa berbagi.", "subscribe": "Berlangganan", "canOnlyRemoveFilesOwnedByYou": "Hanya dapat menghapus berkas yang dimiliki oleh mu", "deleteSharedAlbum": "Hapus album bersama?", @@ -399,6 +401,8 @@ "description": "The text to display in the advanced settings section" }, "photoGridSize": "Ukuran kotak foto", + "manageDeviceStorage": "Mengelola cache perangkat", + "manageDeviceStorageDesc": "Tinjau dan hapus penyimpanan cache lokal.", "machineLearning": "Pemelajaran mesin", "mlConsent": "Aktifkan pemelajaran mesin", "mlConsentTitle": "Aktifkan pemelajaran mesin?", @@ -406,8 +410,13 @@ "mlConsentPrivacy": "Klik di sini untuk detail lebih lanjut tentang fitur ini pada kebijakan privasi kami", "mlConsentConfirmation": "Saya memahami, dan bersedia mengaktifkan pemelajaran mesin", "magicSearch": "Penelusuran ajaib", + "discover": "Temukan", + "@discover": { + "description": "The text to display for the discover section under which we show receipts, screenshots, sunsets, greenery, etc." + }, "discover_identity": "Identitas", "discover_screenshots": "Tangkapan layar", + "discover_receipts": "Tanda Terima", "discover_notes": "Catatan", "discover_memes": "Meme", "discover_babies": "Bayi", @@ -415,6 +424,7 @@ "discover_selfies": "Swafoto", "discover_wallpapers": "Gambar latar", "discover_food": "Makanan", + "discover_celebrations": "Perayaan", "discover_sunset": "Senja", "discover_hills": "Bukit", "mlIndexingDescription": "Perlu diperhatikan bahwa pemelajaran mesin dapat meningkatkan penggunaan data dan baterai perangkat hingga seluruh item selesai terindeks. Gunakan aplikasi desktop untuk pengindeksan lebih cepat, seluruh hasil akan tersinkronkan secara otomatis.", @@ -649,6 +659,8 @@ "startBackup": "Mulai pencadangan", "noPhotosAreBeingBackedUpRightNow": "Tidak ada foto yang sedang dicadangkan sekarang", "grantFullAccessPrompt": "Harap berikan akses ke semua foto di app Pengaturan", + "allowPermTitle": "Izinkan akses ke foto", + "allowPermBody": "Ijinkan akses ke foto Anda dari Pengaturan agar Ente dapat menampilkan dan mencadangkan pustaka Anda.", "openSettings": "Buka Pengaturan", "selectMorePhotos": "Pilih lebih banyak foto", "existingUser": "Masuk", @@ -674,6 +686,7 @@ "type": "text" }, "backupFailed": "Pencadangan gagal", + "sorryBackupFailedDesc": "Maaf, kami tidak dapat mencadangkan berkas ini sekarang, kami akan mencobanya kembali nanti.", "couldNotBackUpTryLater": "Kami tidak dapat mencadangkan data kamu.\nKami akan coba lagi nanti.", "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "Ente hanya dapat mengenkripsi dan menyimpan file jika kamu berikan izin", "pleaseGrantPermissions": "Harap berikan izin", @@ -1098,15 +1111,5 @@ "rotate": "Putar", "left": "Kiri", "right": "Kanan", - "whatsNew": "Hal yang baru", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "whatsNew": "Hal yang baru" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_it.arb b/mobile/lib/l10n/intl_it.arb index eba50f8f04..fbd4675357 100644 --- a/mobile/lib/l10n/intl_it.arb +++ b/mobile/lib/l10n/intl_it.arb @@ -1,6 +1,7 @@ { "@@locale ": "en", "enterYourEmailAddress": "Inserisci il tuo indirizzo email", + "enterYourNewEmailAddress": "Inserisci il tuo nuovo indirizzo email", "accountWelcomeBack": "Bentornato!", "emailAlreadyRegistered": "Email già registrata.", "emailNotRegistered": "Email non registrata.", @@ -371,6 +372,21 @@ "deleteFromBoth": "Elimina da entrambi", "newAlbum": "Nuovo album", "albums": "Album", + "memoryCount": "{count, plural, =0{nessun ricordo} one{{formattedCount} ricordo} other{{formattedCount} ricordi}}", + "@memoryCount": { + "description": "The text to display the number of memories", + "type": "text", + "placeholders": { + "count": { + "example": "1", + "type": "int" + }, + "formattedCount": { + "type": "String", + "example": "11.513, 11,511" + } + } + }, "selectedPhotos": "{count} selezionati", "@selectedPhotos": { "description": "Display the number of selected photos", @@ -510,6 +526,7 @@ "viewLargeFiles": "File di grandi dimensioni", "viewLargeFilesDesc": "Visualizza i file che stanno occupando la maggior parte dello spazio di archiviazione.", "noDuplicates": "✨ Nessun doppione", + "youveNoDuplicateFilesThatCanBeCleared": "Non ci sono file duplicati che possono essere eliminati", "success": "Operazione riuscita", "rateUs": "Lascia una recensione", "remindToEmptyDeviceTrash": "Vuota anche \"Cancellati di recente\" da \"Impostazioni\" -> \"Storage\" per avere più spazio libero", @@ -705,6 +722,7 @@ "type": "text" }, "backupFailed": "Backup fallito", + "sorryBackupFailedDesc": "Purtroppo non è stato possibile eseguire il backup del file in questo momento, riproveremo più tardi.", "couldNotBackUpTryLater": "Impossibile eseguire il backup dei tuoi dati.\nRiproveremo più tardi.", "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "Ente può criptare e conservare i file solo se gliene concedi l'accesso", "pleaseGrantPermissions": "Concedi i permessi", @@ -776,6 +794,14 @@ "share": "Condividi", "unhideToAlbum": "Non nascondere l'album", "restoreToAlbum": "Ripristina l'album", + "moveItem": "{count, plural, one {}=1 {Sposta elemento} other {Sposta elementi}}", + "@moveItem": { + "description": "Page title while moving one or more items to an album" + }, + "addItem": "{count, plural, one {}=1 {Aggiungi elemento} other {Aggiungi elementi}}", + "@addItem": { + "description": "Page title while adding one or more items to album" + }, "createOrSelectAlbum": "Crea o seleziona album", "selectAlbum": "Seleziona album", "searchByAlbumNameHint": "Nome album", @@ -873,6 +899,7 @@ "authToViewYourMemories": "Autenticati per visualizzare le tue foto", "unlock": "Sblocca", "freeUpSpace": "Libera spazio", + "freeUpSpaceSaving": "{count, plural, one {}=1 {Può essere cancellato per liberare {formattedSize}} other {Possono essere cancellati per liberare {formattedSize}}}", "filesBackedUpInAlbum": "{count, plural, one {1 file} other {{formattedNumber} file}} di quest'album sono stati salvati in modo sicuro", "@filesBackedUpInAlbum": { "description": "Text to tell user how many files have been backed up in the album", @@ -903,6 +930,9 @@ } } }, + "@freeUpSpaceSaving": { + "description": "Text to tell user how much space they can free up by deleting items from the device" + }, "freeUpAmount": "Libera {sizeInMBorGB}", "thisEmailIsAlreadyInUse": "Questo indirizzo email è già registrato", "incorrectCode": "Codice sbagliato", @@ -1230,6 +1260,8 @@ "description": "Subtitle to indicate that the user can find people quickly by name" }, "findPeopleByName": "Trova rapidamente le persone per nome", + "addViewers": "{count, plural, one {}=0 {Aggiungi visualizzatore} =1 {Add viewer} other {Aggiungi visualizzatori}}", + "addCollaborators": "{count, plural, one {}=0 {Aggiungi collaboratore} =1 {Aggiungi collaboratore} other {Aggiungi collaboratori}}", "longPressAnEmailToVerifyEndToEndEncryption": "Premi a lungo un'email per verificare la crittografia end to end.", "developerSettingsWarning": "Sei sicuro di voler modificare le Impostazioni sviluppatore?", "developerSettings": "Impostazioni sviluppatore", @@ -1241,9 +1273,12 @@ "createCollaborativeLink": "Crea link collaborativo", "search": "Cerca", "enterPersonName": "Inserisci il nome della persona", + "editEmailAlreadyLinked": "Questa email è già collegata a {name}.", + "viewPersonToUnlink": "Visualizza {name} per scollegare", "enterName": "Aggiungi nome", "savePerson": "Salva persona", "editPerson": "Modifica persona", + "mergedPhotos": "Fotografie unite", "orMergeWithExistingPerson": "O unisci con esistente", "enterDateOfBirth": "Compleanno (Opzionale)", "birthday": "Compleanno", @@ -1350,6 +1385,7 @@ "extraPhotosFound": "Trovate foto aggiuntive", "configuration": "Configurazione", "localIndexing": "Indicizzazione locale", + "processed": "Processato", "resetPerson": "Rimuovi", "areYouSureYouWantToResetThisPerson": "Sei sicuro di voler resettare questa persona?", "allPersonGroupingWillReset": "Tutti i raggruppamenti per questa persona saranno resettati e perderai tutti i suggerimenti fatti per questa persona", @@ -1359,6 +1395,16 @@ "enableMachineLearningBanner": "Abilita l'apprendimento automatico per la ricerca magica e il riconoscimento facciale", "searchDiscoverEmptySection": "Le immagini saranno mostrate qui una volta che l'elaborazione e la sincronizzazione saranno completate", "searchPersonsEmptySection": "Le persone saranno mostrate qui una volta che l'elaborazione e la sincronizzazione saranno completate", + "viewersSuccessfullyAdded": "{count, plural, one {}=0 {Added 0 visualizzatori} =1 {Added 1 visualizzatore} other {Added {count} visualizzatori}}", + "@viewersSuccessfullyAdded": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + }, + "description": "Number of viewers that were successfully added to an album." + }, "collaboratorsSuccessfullyAdded": "{count, plural, =0 {Aggiunti 0 collaboratori} =1 {Aggiunto 1 collaboratore} other {Aggiunti {count} collaboratori}}", "@collaboratorsSuccessfullyAdded": { "placeholders": { @@ -1389,6 +1435,15 @@ } } }, + "typeOfGallerGallerytypeIsNotSupportedForRename": "Il tipo di galleria {galleryType} non è supportato per la rinomina", + "@typeOfGallerGallerytypeIsNotSupportedForRename": { + "placeholders": { + "galleryType": { + "type": "String", + "example": "no network" + } + } + }, "tapToUploadIsIgnoredDue": "Tocca per caricare, il caricamento è attualmente ignorato a causa di {ignoreReason}", "@tapToUploadIsIgnoredDue": { "description": "Shown in upload icon widet, inside a tooltip.", @@ -1425,16 +1480,46 @@ }, "currentlyRunning": "attualmente in esecuzione", "ignored": "ignorato", + "photosCount": "{count, plural, one {}=0 {0 foto} =1 {1 foto} other {{count} foto}}", + "@photosCount": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + } + }, "file": "File", + "searchSectionsLengthMismatch": "Lunghezza sezioni non corrisponde: {snapshotLength} != {searchLength}", + "@searchSectionsLengthMismatch": { + "description": "Appears in search tab page", + "placeholders": { + "snapshotLength": { + "type": "int", + "example": "1" + }, + "searchLength": { + "type": "int", + "example": "2" + } + } + }, + "selectMailApp": "Seleziona app email", + "selectAllShort": "Tutte", + "@selectAllShort": { + "description": "Text that appears in bottom right when you start to select multiple photos. When clicked, it selects all photos." + }, "selectCoverPhoto": "Seleziona foto di copertina", "newLocation": "Nuova posizione", "faceNotClusteredYet": "Faccia non ancora raggruppata, per favore torna più tardi", + "theLinkYouAreTryingToAccessHasExpired": "Il link a cui stai cercando di accedere è scaduto.", "openFile": "Apri file", "backupFile": "File di backup", "openAlbumInBrowser": "Apri album nel browser", "openAlbumInBrowserTitle": "Utilizza l'app web per aggiungere foto a questo album", "allow": "Consenti", "allowAppToOpenSharedAlbumLinks": "Consenti all'app di aprire link all'album condiviso", + "seePublicAlbumLinksInApp": "Vedi link album pubblici nell'app", "emergencyContacts": "Contatti di emergenza", "acceptTrustInvite": "Accetta l'invito", "declineTrustInvite": "Rifiuta l'invito", @@ -1498,6 +1583,14 @@ "useDifferentPlayerInfo": "Hai problemi a riprodurre questo video? Premi a lungo qui per provare un altro lettore.", "hideSharedItemsFromHomeGallery": "Nascondi gli elementi condivisi dalla galleria principale", "gallery": "Galleria", + "joinAlbum": "Unisciti all'album", + "joinAlbumSubtext": "per visualizzare e aggiungere le tue foto", + "joinAlbumSubtextViewer": "per aggiungerla agli album condivisi", + "join": "Unisciti", + "linkEmail": "Link Email", + "link": "Link", + "noEnteAccountExclamation": "Nessun account Ente!", + "orPickFromYourContacts": "o scegli tra i tuoi contatti", "emailDoesNotHaveEnteAccount": "{email} non ha un account Ente.", "@emailDoesNotHaveEnteAccount": { "description": "Shown when email doesn't have an Ente account", @@ -1531,6 +1624,19 @@ } } }, + "linkPersonToEmailConfirmation": "Questo collegherà {personName} a {email}", + "@linkPersonToEmailConfirmation": { + "description": "Confirmation message when linking a person to an email", + "placeholders": { + "personName": { + "type": "String" + }, + "email": { + "type": "String" + } + } + }, + "selectYourFace": "Seleziona il tuo volto", "reassigningLoading": "Riassegnando...", "reassignedToName": "Riassegnato a {name}", "@reassignedToName": { @@ -1540,6 +1646,7 @@ } } }, + "saveChangesBeforeLeavingQuestion": "Salvare le modifiche prima di uscire?", "dontSave": "Non salvare", "thisIsMeExclamation": "Questo sono io!", "linkPerson": "Collega persona", @@ -1547,16 +1654,92 @@ "@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." }, + "videoStreaming": "Video in streaming", "processingVideos": "Elaborando video", - "onThisDay": "On this day", + "streamDetails": "Dettagli dello streaming", + "processing": "In elaborazione", + "queued": "In coda", + "ineligible": "Non idoneo", + "failed": "Non riuscito", + "playStream": "Riproduci lo streaming", + "playOriginal": "Riproduci originale", "joinAlbumConfirmationDialogBody": "Unirsi a un album renderà visibile la tua email ai suoi partecipanti.", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "pleaseWaitThisWillTakeAWhile": "Attendere, potrebbe volerci un po' di tempo.", + "editTime": "Modifica orario", + "selectTime": "Imposta ora", + "selectDate": "Imposta data", + "previous": "Precedente", + "selectOneDateAndTimeForAll": "Seleziona una data e un'ora per tutti", + "selectStartOfRange": "Seleziona inizio dell'intervallo", + "thisWillMakeTheDateAndTimeOfAllSelected": "In questo modo la data e l'ora di tutte le foto selezionate saranno uguali.", + "allWillShiftRangeBasedOnFirst": "Questo è il primo nel gruppo. Altre foto selezionate si sposteranno automaticamente in base a questa nuova data", + "newRange": "Nuovo intervallo", + "selectOneDateAndTime": "Seleziona data e orario", + "moveSelectedPhotosToOneDate": "Sposta foto selezionate in una data specifica", + "shiftDatesAndTime": "Sposta date e orari", + "photosKeepRelativeTimeDifference": "Le foto mantengono una differenza di tempo relativa", + "photocountPhotos": "{count, plural, one {}=0 {Nessuna foto} =1 {1 foto} other {{count} foto}}", + "@photocountPhotos": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + } + }, + "appIcon": "Icona dell'app", + "notThisPerson": "Non è questa persona?", + "selectedItemsWillBeRemovedFromThisPerson": "Gli elementi selezionati verranno rimossi da questa persona, ma non eliminati dalla tua libreria.", + "throughTheYears": "{dateFormat} negli anni", + "thisWeekThroughTheYears": "Questa settimana negli anni", + "thisWeekXYearsAgo": "{count, plural, one {}=1 {Questa settimana, {count} anno fa} other {Questa settimana, {count} anni fa}}", + "youAndThem": "Tu e {name}", + "embracingThem": "Abbracciando {name}", + "partyWithThem": "Festa con {name}", + "hikingWithThem": "Escursioni con {name}", + "feastingWithThem": "Festeggiando con {name}", + "selfiesWithThem": "Selfie con {name}", + "posingWithThem": "In posa con {name}", + "backgroundWithThem": "Bellissimi panorami con {name}", + "sportsWithThem": "Sport con {name}", + "roadtripWithThem": "Viaggio con {name}", + "spotlightOnYourself": "Tu in primo piano", + "spotlightOnThem": "Riflettori su {name}", + "personIsAge": "{name} ha {age}!", + "personTurningAge": "{name} sta per compiere {age} anni", + "lastTimeWithThem": "Ultima volta con {name}", + "tripToLocation": "Viaggio a {location}", + "tripInYear": "Viaggio nel {year}", + "lastYearsTrip": "Viaggio dello scorso anno", + "sunrise": "All'orizzonte", + "mountains": "Oltre le colline", + "greenery": "In mezzo al verde", + "beach": "Sabbia e mare", + "city": "In città", + "moon": "Al chiaro di luna", + "onTheRoad": "Un altro viaggio su strada", + "food": "Delizia culinaria", + "pets": "Compagni pelosetti", + "curatedMemories": "Ricordi importanti", + "widgets": "Widget", + "memories": "Ricordi", + "peopleWidgetDesc": "Seleziona le persone che desideri vedere nella schermata principale.", + "albumsWidgetDesc": "Seleziona gli album che desideri vedere nella schermata principale.", + "memoriesWidgetDesc": "Seleziona il tipo di ricordi che desideri vedere nella schermata principale.", + "smartMemories": "Ricordi intelligenti", + "pastYearsMemories": "Ricordi degli ultimi anni", + "deleteMultipleAlbumDialog": "Eliminare anche le foto (e i video) presenti su {count} album da tutti gli altri album di cui fanno parte?", + "addParticipants": "Aggiungi Partecipanti", + "selectedAlbums": "{count} selezionati", + "actionNotSupportedOnFavouritesAlbum": "Questa azione non è supportata nei Preferiti", + "onThisDayMemories": "Ricordi di questo giorno", + "onThisDay": "In questo giorno", + "lookBackOnYourMemories": "Rivivi i tuoi ricordi 🌄", + "newPhotosEmoji": " nuova 📸", + "sorryWeHadToPauseYourBackups": "Spiacenti, abbiamo dovuto mettere in pausa i backup", + "clickToInstallOurBestVersionYet": "Clicca per installare l'ultima versione dell'app", + "onThisDayNotificationExplanation": "Ricevi promemoria sui ricordi da questo giorno negli anni precedenti.", + "addMemoriesWidgetPrompt": "Aggiungi un widget dei ricordi nella schermata iniziale e torna qui per personalizzarlo.", + "addAlbumWidgetPrompt": "Aggiungi un widget per gli album nella schermata iniziale e torna qui per personalizzarlo.", + "addPeopleWidgetPrompt": "Aggiungi un widget delle persone nella schermata iniziale e torna qui per personalizzarlo." } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ja.arb b/mobile/lib/l10n/intl_ja.arb index c82cba8ec8..63a175ed0f 100644 --- a/mobile/lib/l10n/intl_ja.arb +++ b/mobile/lib/l10n/intl_ja.arb @@ -1666,25 +1666,5 @@ "moon": "月明かりの中", "onTheRoad": "再び道で", "food": "料理を楽しむ", - "pets": "毛むくじゃらな仲間たち", - "cLIcon": "新しいアイコン", - "cLIconDesc": "新しいアプリのアイコンが登場です!ちなみに、古いアイコンが好きだった人のためにアイコンの切り替え機能も用意がございます。", - "cLMemories": "思い出", - "cLMemoriesDesc": "あなたにとって特別な瞬間を再発見しましょう。 - あなたの好きな人々、あなたの旅行や休日。 機械学習をオンにすると、自分自身にタグを付けたり、友達の顔に名前をつけたりすることができます。", - "cLWidgets": "ウィジェット", - "cLWidgetsDesc": "Enteの「思い出」機能と統合されたホーム画面ウィジェットが利用可能になりました!", - "cLFamilyPlan": "ファミリープランの制限", - "cLFamilyPlanDesc": "ファミリーメンバーが使用できるストレージ容量の制限を設定できるようになりました。", - "cLBulkEdit": "日付を一括編集", - "cLBulkEditDesc": "複数の写真を、1回の操作で日付/時刻を編集することもできるようになりました。", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "pets": "毛むくじゃらな仲間たち" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_km.arb b/mobile/lib/l10n/intl_km.arb index abc21027fd..c8494661c6 100644 --- a/mobile/lib/l10n/intl_km.arb +++ b/mobile/lib/l10n/intl_km.arb @@ -1,13 +1,3 @@ { - "@@locale ": "en", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "@@locale ": "en" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ko.arb b/mobile/lib/l10n/intl_ko.arb index 34883210eb..06c81195f7 100644 --- a/mobile/lib/l10n/intl_ko.arb +++ b/mobile/lib/l10n/intl_ko.arb @@ -12,15 +12,5 @@ "feedback": "피드백", "confirmAccountDeletion": "계정 삭제 확인", "deleteAccountPermanentlyButton": "계정을 영구적으로 삭제", - "yourAccountHasBeenDeleted": "계정이 삭제되었습니다.", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "yourAccountHasBeenDeleted": "계정이 삭제되었습니다." } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_lt.arb b/mobile/lib/l10n/intl_lt.arb index ff887415b4..38faa492c0 100644 --- a/mobile/lib/l10n/intl_lt.arb +++ b/mobile/lib/l10n/intl_lt.arb @@ -1731,14 +1731,25 @@ "food": "Kulinarinis malonumas", "pets": "Furio draugai", "curatedMemories": "Kuruoti prisiminimai", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "widgets": "Valdikliai", + "memories": "Prisiminimai", + "peopleWidgetDesc": "Pasirinkite asmenis, kuriuos norite matyti savo pradžios ekrane.", + "albumsWidgetDesc": "Pasirinkite albumus, kuriuos norite matyti savo pradžios ekrane.", + "memoriesWidgetDesc": "Pasirinkite, kokius prisiminimus norite matyti savo pradžios ekrane.", + "smartMemories": "Išmanieji prisiminimai", + "pastYearsMemories": "Praėjusių metų prisiminimai", + "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.", + "onThisDayMemories": "Šios dienos prisiminimai", + "onThisDay": "Šią dieną", + "lookBackOnYourMemories": "Pažvelkite atgal į savo prisiminimus 🌄", + "newPhotosEmoji": " naujas 📸", + "sorryWeHadToPauseYourBackups": "Atsiprašome, turėjome pristabdyti jūsų atsarginių kopijų kūrimą.", + "clickToInstallOurBestVersionYet": "Spustelėkite, kad įdiegtumėte geriausią mūsų versiją iki šiol", + "onThisDayNotificationExplanation": "Gaukite priminimus apie praėjusių metų šios dienos prisiminimus.", + "addMemoriesWidgetPrompt": "Pridėkite prisiminimų valdiklį prie savo pradžios ekrano ir grįžkite čia, kad tinkintumėte.", + "addAlbumWidgetPrompt": "Pridėkite albumo valdiklį prie savo pradžios ekrano ir grįžkite čia, kad tinkintumėte.", + "addPeopleWidgetPrompt": "Pridėkite asmenų valdiklį prie savo pradžios ekrano ir grįžkite čia, kad tinkintumėte." } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_lv.arb b/mobile/lib/l10n/intl_lv.arb index abc21027fd..c8494661c6 100644 --- a/mobile/lib/l10n/intl_lv.arb +++ b/mobile/lib/l10n/intl_lv.arb @@ -1,13 +1,3 @@ { - "@@locale ": "en", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "@@locale ": "en" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ml.arb b/mobile/lib/l10n/intl_ml.arb index 7ca8666be6..7cfd3a2887 100644 --- a/mobile/lib/l10n/intl_ml.arb +++ b/mobile/lib/l10n/intl_ml.arb @@ -99,15 +99,5 @@ "nothingToSeeHere": "ഇവിടൊന്നും കാണ്മാനില്ല! 👀", "calculating": "കണക്കുകൂട്ടുന്നു...", "close": "അടക്കുക", - "count": "എണ്ണം", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "count": "എണ്ണം" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_nl.arb b/mobile/lib/l10n/intl_nl.arb index 3e804f487d..5665bbdc6d 100644 --- a/mobile/lib/l10n/intl_nl.arb +++ b/mobile/lib/l10n/intl_nl.arb @@ -1730,24 +1730,26 @@ "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.", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "curatedMemories": "Samengestelde herinneringen", + "widgets": "Widgets", + "memories": "Herinneringen", + "peopleWidgetDesc": "Selecteer de mensen die je wilt zien op je beginscherm.", + "albumsWidgetDesc": "Selecteer de albums die je wilt zien op je beginscherm.", + "memoriesWidgetDesc": "Selecteer het soort herinneringen dat je wilt zien op je beginscherm.", + "smartMemories": "Slimme herinneringen", + "pastYearsMemories": "Afgelopen jaren", + "deleteMultipleAlbumDialog": "Verwijder de foto's (en video's) van deze {count} albums ook uit alle andere albums waar deze deel van uitmaken?", + "addParticipants": "Voeg deelnemers toe", + "selectedAlbums": "{count} geselecteerd", + "actionNotSupportedOnFavouritesAlbum": "Actie niet ondersteund op Favorieten album", + "onThisDayMemories": "Op deze dag herinneringen", + "onThisDay": "Op deze dag", + "lookBackOnYourMemories": "Kijk terug op je herinneringen 🌄", + "newPhotosEmoji": " nieuwe 📸", + "sorryWeHadToPauseYourBackups": "Sorry, we moesten je back-ups pauzeren", + "clickToInstallOurBestVersionYet": "Klik om onze beste versie tot nu toe te installeren", + "onThisDayNotificationExplanation": "Ontvang meldingen over herinneringen op deze dag door de jaren heen.", + "addMemoriesWidgetPrompt": "Voeg een widget toe aan je beginscherm en kom hier terug om aan te passen.", + "addAlbumWidgetPrompt": "Voeg een widget toe aan je beginscherm en kom hier terug om aan te passen.", + "addPeopleWidgetPrompt": "Voeg een widget toe aan je beginscherm en kom hier terug om aan te passen." } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_no.arb b/mobile/lib/l10n/intl_no.arb index be3c47794b..b5c93dab92 100644 --- a/mobile/lib/l10n/intl_no.arb +++ b/mobile/lib/l10n/intl_no.arb @@ -1689,25 +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å.", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "pets": "Pelsvenner" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_or.arb b/mobile/lib/l10n/intl_or.arb index abc21027fd..c8494661c6 100644 --- a/mobile/lib/l10n/intl_or.arb +++ b/mobile/lib/l10n/intl_or.arb @@ -1,13 +1,3 @@ { - "@@locale ": "en", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "@@locale ": "en" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_pl.arb b/mobile/lib/l10n/intl_pl.arb index 03dba7b1c6..6181cdb0a8 100644 --- a/mobile/lib/l10n/intl_pl.arb +++ b/mobile/lib/l10n/intl_pl.arb @@ -1533,15 +1533,5 @@ "joinAlbumSubtextViewer": "aby dodać to do udostępnionych albumów", "join": "Dołącz", "linkEmail": "Połącz adres e-mail", - "noEnteAccountExclamation": "Brak konta Ente!", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "noEnteAccountExclamation": "Brak konta Ente!" } \ 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 5b04c3a1c0..3d6eff615a 100644 --- a/mobile/lib/l10n/intl_pt_BR.arb +++ b/mobile/lib/l10n/intl_pt_BR.arb @@ -1,6 +1,6 @@ { "@@locale ": "en", - "enterYourEmailAddress": "Insira seu endereço de e-mail", + "enterYourEmailAddress": "Insira seu e-mail", "enterYourNewEmailAddress": "Insira seu novo e-mail", "accountWelcomeBack": "Bem-vindo(a) de volta!", "emailAlreadyRegistered": "E-mail já registrado.", @@ -8,8 +8,8 @@ "email": "E-mail", "cancel": "Cancelar", "verify": "Verificar", - "invalidEmailAddress": "Endereço de e-mail inválido", - "enterValidEmail": "Insira um endereço de e-mail válido.", + "invalidEmailAddress": "E-mail inválido", + "enterValidEmail": "Insira um e-mail válido.", "deleteAccount": "Excluir conta", "askDeleteReason": "Por que você quer excluir sua conta?", "deleteAccountFeedbackPrompt": "Lamentamos você ir. Compartilhe seu feedback para ajudar-nos a melhorar.", @@ -132,13 +132,13 @@ } }, "twofactorSetup": "Configuração de dois fatores", - "enterCode": "Insira o código", + "enterCode": "Inserir código", "scanCode": "Escanear código", "codeCopiedToClipboard": "Código copiado para a área de transferência", - "copypasteThisCodentoYourAuthenticatorApp": "Copie e cole este código\npara o aplicativo autenticador", + "copypasteThisCodentoYourAuthenticatorApp": "Copie e cole o código no aplicativo autenticador", "tapToCopy": "toque para copiar", "scanThisBarcodeWithnyourAuthenticatorApp": "Escaneie este código de barras com\no aplicativo autenticador", - "enterThe6digitCodeFromnyourAuthenticatorApp": "Digite o código de 6 dígitos do\naplicativo de autenticador", + "enterThe6digitCodeFromnyourAuthenticatorApp": "Digite o código de 6 dígitos do\naplicativo autenticador", "confirm": "Confirmar", "setupComplete": "Configuração concluída", "saveYourRecoveryKeyIfYouHaventAlready": "Salve sua chave de recuperação, se você ainda não fez", @@ -273,7 +273,7 @@ "shareTextRecommendUsingEnte": "Baixe o Ente para que nós possamos compartilhar com facilidade fotos e vídeos de qualidade original\n\nhttps://ente.io", "done": "Concluído", "applyCodeTitle": "Aplicar código", - "enterCodeDescription": "Insira o código fornecido pelo seu amigo para reivindicar o armazenamento grátis para os dois", + "enterCodeDescription": "Insira o código fornecido por um amigo para reivindicar armazenamento grátis para ambos", "apply": "Aplicar", "failedToApplyCode": "Falhou ao aplicar código", "enterReferralCode": "Inserir código de referência", @@ -291,14 +291,14 @@ "details": "Detalhes", "claimMore": "Reivindique mais!", "theyAlsoGetXGb": "Eles também recebem {storageAmountInGB} GB", - "freeStorageOnReferralSuccess": "{storageAmountInGB} GB cada vez que alguém se inscrever a um plano pago e aplicar seu código", - "shareTextReferralCode": "Código de referência do Ente: {referralCode} \n\nAplique-o em Configurações → Geral → Referências para obter {referralStorageInGB} GB grátis após a sua inscrição num plano pago\n\nhttps://ente.io", + "freeStorageOnReferralSuccess": "{storageAmountInGB} GB toda vez que alguém utilizar seu código num plano pago", + "shareTextReferralCode": "Código de referência Ente: {referralCode} \n\nAplique-o em Opções → Geral → Referências para obter {referralStorageInGB} GB grátis após a inscrição num plano pago\n\nhttps://ente.io", "claimFreeStorage": "Reivindique armaz. grátis", "inviteYourFriends": "Convide seus amigos", "failedToFetchReferralDetails": "Não foi possível buscar os detalhes de referência. Tente novamente mais tarde.", "referralStep1": "1. Envie este código aos seus amigos", "referralStep2": "2. Eles então se inscrevem num plano pago", - "referralStep3": "3. Ambos os dois ganham {storageInGB} GB* grátis", + "referralStep3": "3. Ambos ganham {storageInGB} GB* grátis", "referralsAreCurrentlyPaused": "As referências estão atualmente pausadas", "youCanAtMaxDoubleYourStorage": "* Você pode duplicar seu armazenamento ao máximo", "claimedStorageSoFar": "{isFamilyMember, select,true {Sua família reinvidicou {storageAmountInGb} GB até então}false {Você reinvindicou {storageAmountInGb} GB até então}other {Você reinvindicou {storageAmountInGb} GB até então!}}", @@ -317,13 +317,13 @@ "faq": "Perguntas frequentes", "help": "Ajuda", "oopsSomethingWentWrong": "Ops, algo deu errado", - "peopleUsingYourCode": "Pessoas que usam seu código", + "peopleUsingYourCode": "Pessoas que usou o código", "eligible": "elegível", "total": "total", "codeUsedByYou": "Código usado por você", "freeStorageClaimed": "Armaz. grátis reivindicado", "freeStorageUsable": "Armazenamento disponível", - "usableReferralStorageInfo": "O armazenamento disponível é limitado pelo seu plano atual. O excesso de armazenamento reivindicado tornará automaticamente útil quando você atualizar seu plano.", + "usableReferralStorageInfo": "O armazenamento disponível é limitado devido ao seu plano atual. O armazenamento adicional será aplicado quando você atualizar seu plano.", "removeFromAlbumTitle": "Remover do álbum?", "removeFromAlbum": "Remover do álbum", "itemsWillBeRemovedFromAlbum": "Os itens selecionados serão removidos deste álbum", @@ -453,8 +453,8 @@ "indexedItems": "Itens indexados", "pendingItems": "Itens pendentes", "clearIndexes": "Limpar índices", - "selectFoldersForBackup": "Selecionar pastas para copiar com segurança", - "selectedFoldersWillBeEncryptedAndBackedUp": "As pastas selecionadas serão criptografadas e armazenadas em copiadas com segurança", + "selectFoldersForBackup": "Selecione as pastas para salvá-las", + "selectedFoldersWillBeEncryptedAndBackedUp": "As pastas selecionadas serão criptografadas e salvas em segurança", "unselectAll": "Desmarcar tudo", "selectAll": "Selecionar tudo", "skip": "Pular", @@ -478,13 +478,13 @@ }, "showMemories": "Mostrar memórias", "yearsAgo": "{count, plural, one{{count} ano atrás} other{{count} anos atrás}}", - "backupSettings": "Opções de cópia de segurança", - "backupStatus": "Status da cópia de segurança", - "backupStatusDescription": "Os itens que foram salvos com segurança aparecerão aqui", - "backupOverMobileData": "Salvamento com segurança usando dados móveis", - "backupVideos": "Cópia de segurança de vídeos", + "backupSettings": "Ajustes de salvar em segurança", + "backupStatus": "Estado das mídias salvas", + "backupStatusDescription": "Os itens salvos em segurança aparecerão aqui", + "backupOverMobileData": "Salvar em segurança com dados móveis", + "backupVideos": "Salvar vídeos em segurança", "disableAutoLock": "Desativar bloqueio automático", - "deviceLockExplanation": "Desativa o bloqueio de tela quando o Ente está de fundo e têm uma cópia de segurança sendo feita. Isso normalmente não é necessário, no entanto, ajuda a envios grandes e importações iniciais de bibliotecas maiores concluírem mais rápido.", + "deviceLockExplanation": "Desativa o bloqueio de tela se o Ente estiver de fundo e uma cópia de segurança ainda estar em andamento. Às vezes, isso não é necessário, mas ajuda a agilizar envios grandes e importações iniciais de bibliotecas maiores.", "about": "Sobre", "weAreOpenSource": "Nós somos de código aberto!", "privacy": "Privacidade", @@ -515,8 +515,8 @@ "cannotDeleteSharedFiles": "Não é possível excluir arquivos compartilhados", "theDownloadCouldNotBeCompleted": "A instalação não pôde ser concluída", "retry": "Tentar novamente", - "backedUpFolders": "Pastas copiadas com segurança", - "backup": "Cópia de segurança", + "backedUpFolders": "Pastas salvas em segurança", + "backup": "Salvar em segurança", "freeUpDeviceSpace": "Liberar espaço no dispositivo", "freeUpDeviceSpaceDesc": "Economize espaço em seu dispositivo por limpar arquivos já salvos com segurança.", "allClear": "✨ Tudo limpo", @@ -529,7 +529,7 @@ "youveNoDuplicateFilesThatCanBeCleared": "Você não possui nenhum arquivo duplicado que possa ser excluído", "success": "Sucesso", "rateUs": "Avaliar", - "remindToEmptyDeviceTrash": "Também vazio \"Excluído recentemente\" de \"Opções\" -> \"Armazenamento\" para reivindicar espaço liberado", + "remindToEmptyDeviceTrash": "Também esvazie o \"Excluído Recentemente\" das \"Opções\" -> \"Armazenamento\" para liberar espaço", "youHaveSuccessfullyFreedUp": "Você liberou {storageSaved} com sucesso!", "@youHaveSuccessfullyFreedUp": { "description": "The text to display when the user has successfully freed up storage", @@ -1731,14 +1731,30 @@ "food": "Amor por culinária", "pets": "Companhias peludas", "curatedMemories": "Memórias restauradas", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "widgets": "Widgets", + "memories": "Memórias", + "peopleWidgetDesc": "Selecione as pessoas que deseje vê-las na sua tela inicial.", + "albumsWidgetDesc": "Selecione os álbuns que deseje vê-los na sua tela inicial.", + "memoriesWidgetDesc": "Selecione os tipos de memórias que deseje vê-las na sua tela inicial.", + "smartMemories": "Memórias inteligentes", + "pastYearsMemories": "Memórias dos anos passados", + "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?", + "addParticipants": "Adicionar participante", + "selectedAlbums": "{count} selecionado(s)", + "actionNotSupportedOnFavouritesAlbum": "Ação não suportada em álbum favorito", + "onThisDayMemories": "Memórias deste dia", + "onThisDay": "Neste dia", + "lookBackOnYourMemories": "Revisão em suas memórias 🌄", + "newPhotosEmoji": " novo 📸", + "sorryWeHadToPauseYourBackups": "Desculpe, tivemos que pausar suas cópias de segurança", + "clickToInstallOurBestVersionYet": "Clique para instalar a nossa melhor versão até então", + "onThisDayNotificationExplanation": "Receba lembretes de memórias deste dia em anos passados.", + "addMemoriesWidgetPrompt": "Adicione um widget de memória a sua tela inicial e volte aqui para personalizar.", + "addAlbumWidgetPrompt": "Adicione um widget de álbum a sua tela inicial e volte aqui para personalizar.", + "addPeopleWidgetPrompt": "Adicione um widget de pessoas a sua tela inicial e volte aqui para personalizar.", + "birthdayNotifications": "Notificações de aniversário", + "receiveRemindersOnBirthdays": "Receba notificações quando alguém fizer um aniversário. Tocar na notificação o levará às fotos do aniversariante.", + "happyBirthday": "Feliz aniversário! 🥳", + "happyBirthdayToPerson": "Feliz aniversário a {name}! 🎉", + "birthdays": "Aniversários" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_pt_PT.arb b/mobile/lib/l10n/intl_pt_PT.arb index 54140bbc89..b1bd6e5ac2 100644 --- a/mobile/lib/l10n/intl_pt_PT.arb +++ b/mobile/lib/l10n/intl_pt_PT.arb @@ -1387,15 +1387,5 @@ "example": "no network" } } - }, - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + } } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ro.arb b/mobile/lib/l10n/intl_ro.arb index f1524ba7b8..6cc353ab2b 100644 --- a/mobile/lib/l10n/intl_ro.arb +++ b/mobile/lib/l10n/intl_ro.arb @@ -1522,15 +1522,5 @@ "joinAlbum": "Alăturați-vă albumului", "joinAlbumSubtext": "pentru a vedea și a adăuga fotografii", "joinAlbumSubtextViewer": "pentru a adăuga la albumele distribuite", - "join": "Alăturare", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "join": "Alăturare" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ru.arb b/mobile/lib/l10n/intl_ru.arb index ee596e6815..98bcdac583 100644 --- a/mobile/lib/l10n/intl_ru.arb +++ b/mobile/lib/l10n/intl_ru.arb @@ -1724,25 +1724,5 @@ "onTheRoad": "Снова в пути", "food": "Кулинарное наслаждение", "pets": "Пушистые спутники", - "cLIcon": "Новая иконка", - "cLIconDesc": "Наконец-то новая иконка приложения, которая, как мы считаем, лучше всего отражает нашу работу. Мы также добавили переключатель иконок, чтобы вы могли продолжать использовать старую иконку.", - "cLMemories": "Воспоминания", - "cLMemoriesDesc": "Откройте заново свои особенные моменты — в центре внимания ваши любимые люди, поездки и праздники, лучшие снимки и многое другое. Для наилучших впечатлений включите машинное обучение и отметьте себя и своих друзей.", - "cLWidgets": "Виджеты", - "cLWidgetsDesc": "Теперь доступны виджеты домашнего экрана, интегрированные с воспоминаниями. Они покажут ваши особенные моменты, не открывая приложения.", - "cLFamilyPlan": "Ограничения семейного тарифа", - "cLFamilyPlanDesc": "Теперь вы можете установить ограничения на объём хранилища, которое могут использовать члены вашей семьи.", - "cLBulkEdit": "Массовое редактирование дат", - "cLBulkEditDesc": "Теперь вы можете выбрать несколько фото и отредактировать дату/время быстро и сразу для всех. Также поддерживается смещение дат.", - "curatedMemories": "Отобранные воспоминания", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "curatedMemories": "Отобранные воспоминания" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_sl.arb b/mobile/lib/l10n/intl_sl.arb index abc21027fd..c8494661c6 100644 --- a/mobile/lib/l10n/intl_sl.arb +++ b/mobile/lib/l10n/intl_sl.arb @@ -1,13 +1,3 @@ { - "@@locale ": "en", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "@@locale ": "en" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_sr.arb b/mobile/lib/l10n/intl_sr.arb index 769371850f..c8494661c6 100644 --- a/mobile/lib/l10n/intl_sr.arb +++ b/mobile/lib/l10n/intl_sr.arb @@ -1,7 +1,3 @@ { - "@@locale ": "en", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "@@locale ": "en" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_sv.arb b/mobile/lib/l10n/intl_sv.arb index edd5f3a326..ccc0d6f882 100644 --- a/mobile/lib/l10n/intl_sv.arb +++ b/mobile/lib/l10n/intl_sv.arb @@ -531,15 +531,5 @@ "sort": "Sortera", "newPerson": "Ny person", "addName": "Lägg till namn", - "add": "Lägg till", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "add": "Lägg till" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ta.arb b/mobile/lib/l10n/intl_ta.arb index d999bafbeb..1b9d1e8190 100644 --- a/mobile/lib/l10n/intl_ta.arb +++ b/mobile/lib/l10n/intl_ta.arb @@ -19,15 +19,5 @@ "deleteAccountPermanentlyButton": "கணக்கை நிரந்தரமாக நீக்கவும்", "yourAccountHasBeenDeleted": "உங்கள் கணக்கு நீக்கப்பட்டது", "selectReason": "காரணத்தைத் தேர்ந்தெடுக்கவும்", - "deleteReason1": "எனக்கு தேவையான ஒரு முக்கிய அம்சம் இதில் இல்லை", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "deleteReason1": "எனக்கு தேவையான ஒரு முக்கிய அம்சம் இதில் இல்லை" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_te.arb b/mobile/lib/l10n/intl_te.arb index abc21027fd..c8494661c6 100644 --- a/mobile/lib/l10n/intl_te.arb +++ b/mobile/lib/l10n/intl_te.arb @@ -1,13 +1,3 @@ { - "@@locale ": "en", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "@@locale ": "en" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_th.arb b/mobile/lib/l10n/intl_th.arb index 49942f031c..a7c4c4ccb2 100644 --- a/mobile/lib/l10n/intl_th.arb +++ b/mobile/lib/l10n/intl_th.arb @@ -287,15 +287,5 @@ "description": "Label for the map view" }, "maps": "แผนที่", - "enableMaps": "เปิดใช้งานแผนที่", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "enableMaps": "เปิดใช้งานแผนที่" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ti.arb b/mobile/lib/l10n/intl_ti.arb index abc21027fd..c8494661c6 100644 --- a/mobile/lib/l10n/intl_ti.arb +++ b/mobile/lib/l10n/intl_ti.arb @@ -1,13 +1,3 @@ { - "@@locale ": "en", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "@@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 b31fb70fba..37294ba1e4 100644 --- a/mobile/lib/l10n/intl_tr.arb +++ b/mobile/lib/l10n/intl_tr.arb @@ -1730,25 +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", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "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", + "onThisDay": "Bu günde" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_uk.arb b/mobile/lib/l10n/intl_uk.arb index a7b2c55f93..c13e19312c 100644 --- a/mobile/lib/l10n/intl_uk.arb +++ b/mobile/lib/l10n/intl_uk.arb @@ -1510,15 +1510,5 @@ }, "legacyInvite": "{email} запросив вас стати довіреною особою", "authToManageLegacy": "Авторизуйтесь, щоби керувати довіреними контактами", - "useDifferentPlayerInfo": "Виникли проблеми з відтворенням цього відео? Натисніть і утримуйте тут, щоб спробувати інший плеєр.", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "useDifferentPlayerInfo": "Виникли проблеми з відтворенням цього відео? Натисніть і утримуйте тут, щоб спробувати інший плеєр." } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_vi.arb b/mobile/lib/l10n/intl_vi.arb index 6d88de6b46..9943afc24a 100644 --- a/mobile/lib/l10n/intl_vi.arb +++ b/mobile/lib/l10n/intl_vi.arb @@ -1532,15 +1532,5 @@ "joinAlbum": "Tham gia album", "joinAlbumSubtext": "để xem và thêm ảnh của bạn", "joinAlbumSubtextViewer": "thêm vào album được chia sẻ", - "join": "Tham gia", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "join": "Tham gia" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_zh.arb b/mobile/lib/l10n/intl_zh.arb index 7a1961bfb2..650f26d333 100644 --- a/mobile/lib/l10n/intl_zh.arb +++ b/mobile/lib/l10n/intl_zh.arb @@ -1723,25 +1723,5 @@ "moon": "月光之下", "onTheRoad": "再次踏上旅途", "food": "美食盛宴", - "pets": "毛茸茸的伙伴", - "cLIcon": "新图标", - "cLIconDesc": "终于迎来了一个全新的应用图标,我们认为它最能代表我们的作品。同时,我们还添加了图标切换功能,所以您可以继续使用旧图标。", - "cLMemories": "回忆", - "cLMemoriesDesc": "重新发现你的珍贵时刻——聚焦你最爱的亲友、旅行与假期、美妙瞬间等精彩回忆。启用机器学习,标记自己并为朋友命名,享受最佳体验。", - "cLWidgets": "小组件", - "cLWidgetsDesc": "全新首页小组件,与回忆深度集成。无需打开应用,即可在主屏幕上查看你的特别时刻。", - "cLFamilyPlan": "家庭计划存储限制", - "cLFamilyPlanDesc": "你现在可以为家庭成员设置存储空间使用上限。", - "cLBulkEdit": "批量编辑日期", - "cLBulkEditDesc": "你现在可以选择多张照片,一键批量修改日期/时间,并支持日期顺移。", - "onThisDay": "On this day", - "lookBackOnYourMemories": "Look back on your memories 🌄", - "newPhotosEmoji": " new 📸", - "sorryWeHadToPauseYourBackups": "Sorry, we had to pause your backups", - "clickToInstallOurBestVersionYet": "Click to install our best version yet", - "onThisDayNotificationExplanation": "Receive reminders about memories from this day in previous years.", - "birhtdayNotifications": "Birhtday notifications", - "receiveRemindersOnBirthdays": "Receive reminders when it's someone's birthday. Tapping on the notification will take you to photos of the birthday person.", - "happyBirthday": "Happy birthday! 🥳", - "birthdays": "Birthdays" + "pets": "毛茸茸的伙伴" } \ No newline at end of file diff --git a/mobile/lib/services/smart_memories_service.dart b/mobile/lib/services/smart_memories_service.dart index 7e5f94ba23..f523ecc48b 100644 --- a/mobile/lib/services/smart_memories_service.dart +++ b/mobile/lib/services/smart_memories_service.dart @@ -1699,14 +1699,7 @@ class SmartMemoriesService { if (memories == null) continue; if (memories.length < 5) continue; final years = daysToYears[day]!; - if (years.toSet().length < 3) continue; - final yearCounts = {}; - for (final year in years) { - yearCounts[year] = (yearCounts[year] ?? 0) + 1; - } - final bool hasThreeInAtLeastThreeYears = - yearCounts.values.where((count) => count >= 3).length >= 3; - if (!hasThreeInAtLeastThreeYears) continue; + if (years.toSet().length < 2) continue; final filteredMemories = []; if (memories.length > 20) { diff --git a/web/packages/accounts/components/SignUpContents.tsx b/web/packages/accounts/components/SignUpContents.tsx index 605ca29a28..cfa44dab7b 100644 --- a/web/packages/accounts/components/SignUpContents.tsx +++ b/web/packages/accounts/components/SignUpContents.tsx @@ -13,8 +13,14 @@ import { Tooltip, Typography, } from "@mui/material"; -import { generateKeyAndSRPAttributes } from "ente-accounts/services/srp"; -import { sendOTT } from "ente-accounts/services/user"; +import { + deriveSRPPassword, + generateSRPSetupAttributes, +} from "ente-accounts/services/srp"; +import { + generateKeysAndAttributes, + sendOTT, +} from "ente-accounts/services/user"; import { generateAndSaveIntermediateKeyAttributes } from "ente-accounts/utils/helpers"; import { isWeakPassword } from "ente-accounts/utils/password"; import { LinkButton } from "ente-base/components/LinkButton"; @@ -96,8 +102,12 @@ export const SignUpContents: React.FC = ({ throw e; } try { - const { keyAttributes, masterKey, srpSetupAttributes } = - await generateKeyAndSRPAttributes(passphrase); + const { masterKey, kek, keyAttributes } = + await generateKeysAndAttributes(passphrase); + + const srpSetupAttributes = await generateSRPSetupAttributes( + await deriveSRPPassword(kek), + ); setData("originalKeyAttributes", keyAttributes); setData("srpSetupAttributes", srpSetupAttributes); diff --git a/web/packages/accounts/pages/change-password.tsx b/web/packages/accounts/pages/change-password.tsx index b0e3c564f0..8c1c00ca68 100644 --- a/web/packages/accounts/pages/change-password.tsx +++ b/web/packages/accounts/pages/change-password.tsx @@ -8,24 +8,24 @@ import SetPasswordForm, { } from "ente-accounts/components/SetPasswordForm"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import { - convertBase64ToBuffer, - convertBufferToBase64, deriveSRPPassword, - generateSRPClient, generateSRPSetupAttributes, getSRPAttributes, - startSRPSetup, + srpSetupOrReconfigure, updateSRPAndKeys, + type UpdatedKeyAttr, } from "ente-accounts/services/srp"; -import type { - KeyAttributes, - UpdatedKey, - User, +import { + ensureSavedKeyAttributes, + localUser, + type LocalUser, } from "ente-accounts/services/user"; import { generateAndSaveIntermediateKeyAttributes } from "ente-accounts/utils/helpers"; import { LinkButton } from "ente-base/components/LinkButton"; +import { LoadingIndicator } from "ente-base/components/loaders"; import { sharedCryptoWorker } from "ente-base/crypto"; -import type { DerivedKey } from "ente-base/crypto/types"; +import { deriveKeyInsufficientMemoryErrorMessage } from "ente-base/crypto/types"; +import log from "ente-base/log"; import { ensureMasterKeyFromSession, saveMasterKeyInSessionAndSafeStore, @@ -33,91 +33,98 @@ import { import { getData, setData } from "ente-shared/storage/localStorage"; import { t } from "i18next"; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; +/** + * A page that allows a user to reset or change their password. + */ const Page: React.FC = () => { - const [token, setToken] = useState(); - const [user, setUser] = useState(); + const [user, setUser] = useState(); const router = useRouter(); useEffect(() => { - const user = getData("user"); - setUser(user); - if (!user?.token) { + const user = localUser(); + if (user) { + setUser(user); + } else { stashRedirect("/change-password"); void router.push("/"); - } else { - setToken(user.token); } }, [router]); + return user ? : ; +}; + +export default Page; + +interface PageContentsProps { + user: LocalUser; +} + +const PageContents: React.FC = ({ user }) => { + const token = user.token; + + const router = useRouter(); + const onSubmit: SetPasswordFormProps["callback"] = async ( passphrase, setFieldError, ) => { + try { + await onSubmit2(passphrase); + } catch (e) { + log.error("Could not change password", e); + setFieldError( + "confirm", + e instanceof Error && + e.message == deriveKeyInsufficientMemoryErrorMessage + ? t("password_generation_failed") + : t("generic_error"), + ); + } + }; + + const onSubmit2 = async (passphrase: string) => { const cryptoWorker = await sharedCryptoWorker(); const masterKey = await ensureMasterKeyFromSession(); - const keyAttributes: KeyAttributes = getData("keyAttributes"); - let kek: DerivedKey; - try { - kek = await cryptoWorker.deriveSensitiveKey(passphrase); - } catch { - setFieldError("confirm", t("password_generation_failed")); - return; - } + const keyAttributes = ensureSavedKeyAttributes(); + const { + key: kek, + salt: kekSalt, + opsLimit, + memLimit, + } = await cryptoWorker.deriveSensitiveKey(passphrase); const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = - await cryptoWorker.encryptBox(masterKey, kek.key); - const updatedKey: UpdatedKey = { + await cryptoWorker.encryptBox(masterKey, kek); + const updatedKeyAttr: UpdatedKeyAttr = { encryptedKey, keyDecryptionNonce, - kekSalt: kek.salt, - opsLimit: kek.opsLimit, - memLimit: kek.memLimit, + kekSalt, + opsLimit, + memLimit, }; - const loginSubKey = await deriveSRPPassword(kek.key); + const loginSubKey = await deriveSRPPassword(kek); const { srpUserID, srpSalt, srpVerifier } = await generateSRPSetupAttributes(loginSubKey); - const srpClient = await generateSRPClient( - srpSalt, - srpUserID, - loginSubKey, + await srpSetupOrReconfigure( + { srpSalt, srpUserID, srpVerifier, loginSubKey }, + ({ setupID, srpM1 }) => + updateSRPAndKeys(token, { setupID, srpM1, updatedKeyAttr }), ); - const srpA = convertBufferToBase64(srpClient.computeA()); - - const { setupID, srpB } = await startSRPSetup(token!, { - srpUserID, - srpSalt, - srpVerifier, - srpA, - }); - - srpClient.setB(convertBase64ToBuffer(srpB)); - - const srpM1 = convertBufferToBase64(srpClient.computeM1()); - - await updateSRPAndKeys(token!, { - setupID, - srpM1, - updatedKeyAttr: updatedKey, - }); - // Update the SRP attributes that are stored locally. - if (user?.email) { - const srpAttributes = await getSRPAttributes(user.email); - if (srpAttributes) { - setData("srpAttributes", srpAttributes); - } + const srpAttributes = await getSRPAttributes(user.email); + if (srpAttributes) { + setData("srpAttributes", srpAttributes); } - const updatedKeyAttributes = Object.assign(keyAttributes, updatedKey); await generateAndSaveIntermediateKeyAttributes( passphrase, - updatedKeyAttributes, + { ...keyAttributes, ...updatedKeyAttr }, masterKey, ); @@ -131,12 +138,11 @@ const Page: React.FC = () => { void router.push(appHomeRoute); }; - // TODO: Handle the case where user is not loaded yet. return ( {t("change_password")} @@ -150,5 +156,3 @@ const Page: React.FC = () => { ); }; - -export default Page; diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 78b9780628..e2a527ef40 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -21,13 +21,13 @@ import { unstashRedirect, } from "ente-accounts/services/redirect"; import { checkSessionValidity } from "ente-accounts/services/session"; -import type { SRPAttributes } from "ente-accounts/services/srp"; import { configureSRP, deriveSRPPassword, generateSRPSetupAttributes, getSRPAttributes, loginViaSRP, + type SRPAttributes, } from "ente-accounts/services/srp"; import type { KeyAttributes, User } from "ente-accounts/services/user"; import { diff --git a/web/packages/accounts/pages/generate.tsx b/web/packages/accounts/pages/generate.tsx index 41010d6bab..c5993eb2cb 100644 --- a/web/packages/accounts/pages/generate.tsx +++ b/web/packages/accounts/pages/generate.tsx @@ -10,14 +10,19 @@ import SetPasswordForm, { import { appHomeRoute } from "ente-accounts/services/redirect"; import { configureSRP, - generateKeyAndSRPAttributes, + deriveSRPPassword, + generateSRPSetupAttributes, } from "ente-accounts/services/srp"; import type { KeyAttributes, User } from "ente-accounts/services/user"; -import { putUserKeyAttributes } from "ente-accounts/services/user"; +import { + generateKeysAndAttributes, + putUserKeyAttributes, +} from "ente-accounts/services/user"; import { generateAndSaveIntermediateKeyAttributes } from "ente-accounts/utils/helpers"; import { LinkButton } from "ente-base/components/LinkButton"; import { LoadingIndicator } from "ente-base/components/loaders"; import { useBaseContext } from "ente-base/context"; +import { deriveKeyInsufficientMemoryErrorMessage } from "ente-base/crypto/types"; import log from "ente-base/log"; import { haveCredentialsInSession, @@ -66,8 +71,12 @@ const Page: React.FC = () => { setFieldError, ) => { try { - const { keyAttributes, masterKey, srpSetupAttributes } = - await generateKeyAndSRPAttributes(passphrase); + const { masterKey, kek, keyAttributes } = + await generateKeysAndAttributes(passphrase); + + const srpSetupAttributes = await generateSRPSetupAttributes( + await deriveSRPPassword(kek), + ); await putUserKeyAttributes(keyAttributes); await configureSRP(srpSetupAttributes); @@ -81,7 +90,13 @@ const Page: React.FC = () => { setOpenRecoveryKey(true); } catch (e) { log.error("failed to generate password", e); - setFieldError("passphrase", t("password_generation_failed")); + setFieldError( + "passphrase", + e instanceof Error && + e.message == deriveKeyInsufficientMemoryErrorMessage + ? t("password_generation_failed") + : t("generic_error"), + ); } }; diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index e09548ee0c..24cd649a1d 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -16,11 +16,12 @@ import { stashedRedirect, unstashRedirect, } from "ente-accounts/services/redirect"; -import type { - SRPAttributes, - SRPSetupAttributes, +import { + configureSRP, + getSRPAttributes, + type SRPAttributes, + type SRPSetupAttributes, } from "ente-accounts/services/srp"; -import { configureSRP, getSRPAttributes } from "ente-accounts/services/srp"; import type { KeyAttributes, User } from "ente-accounts/services/user"; import { putUserKeyAttributes, diff --git a/web/packages/accounts/services/session.ts b/web/packages/accounts/services/session.ts index 09c88435b6..05e4c64d97 100644 --- a/web/packages/accounts/services/session.ts +++ b/web/packages/accounts/services/session.ts @@ -6,8 +6,7 @@ import { getAuthToken } from "ente-base/token"; import { getData } from "ente-shared/storage/localStorage"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; -import type { SRPAttributes } from "./srp"; -import { getSRPAttributes } from "./srp"; +import { getSRPAttributes, type SRPAttributes } from "./srp"; import { ensureLocalUser, putUserKeyAttributes, diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index a8fce7b9d6..0ba5d326dc 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -1,5 +1,4 @@ import { HttpStatusCode } from "axios"; -import type { KeyAttributes } from "ente-accounts/services/user"; import { deriveSubKeyBytes, sharedCryptoWorker, toB64 } from "ente-base/crypto"; import { ensureOk, publicRequestHeaders } from "ente-base/http"; import log from "ente-base/log"; @@ -9,7 +8,92 @@ import HTTPService from "ente-shared/network/HTTPService"; import { getToken } from "ente-shared/storage/localStorage/helpers"; import { SRP, SrpClient } from "fast-srp-hap"; import { v4 as uuidv4 } from "uuid"; -import type { UpdatedKey, UserVerificationResponse } from "./user"; +import type { UserVerificationResponse } from "./user"; + +/** + * The SRP attributes for a user. + * + * [Note: SRP] + * + * The SRP (Secure Remote Password) protocol is a modified Diffie-Hellman key + * exchange that allows the remote to verify the user's possession of a + * passphrase, and the user to ensure that remote is not being impersonated, + * without the passphrase ever leaving the device. + * + * It is used as an alternative to email verification flows, though the user + * also has an option to enable it in addition to SRP. + * + * For more about the what and why, see the announcement blog post + * https://ente.io/blog/ente-adopts-secure-remote-passwords/ + * + * Here we do not focus on the math (the above blog post links to reference + * material, and there is also an RFC), but instead of the various bits of + * information that get exchanged. + * + * Broadly, there are two scenarios: SRP setup, and SRP verification. + * + * [Note: SRP setup] + * + * During SRP setup, client generates + * + * 01. A SRP user ID (a new UUID-v4) + * 02. A SRP password (deterministically derived from their regular KEK) + * 03. A SRP salt (randomly generated) + * + * These 3 things are enough to create a SRP verifier and client + * + * 04. verifier = computeSRPVerifier({ userID, password, salt }) + * 05. client = new SRPClient({ userID, password, salt }) + * + * The SRP client can just be thought of an ephemeral stateful mechanism to + * avoid passing all the state accrued so far to each operation. + * + * The client (app) then starts the setup ceremony with remote: + * + * 06. Use SRP client to conjure secret `a` and use that to compute a public A + * 07. Send { userID, salt, verifier, A } to remote ("/users/srp/setup") + * + * Remote then: + * + * 08. Generates a SRP serverKey (random) + * 09. Saves { userID, serverKey, A } into SRP sessions table + * 10. Creates server = new SRPServer({ verifier, serverKey }) + * 11. Uses SRP server to conjure secret `b` and use that to compute a public B + * 12. Stashes { sessionID, userID, salt, verifier } into SRP setups table + * 13. Returns { setupID, B } to client + * + * Client then + * + * 14. Tells its SRP client about B + * 15. Computes SRP M1 (evidence message) using the SRP client + * 16. Sends { setupID, M1 } to remote ("/users/srp/complete") + * + * Remote then + * + * 17. Uses setupID to read the stashed { sessionID, userID, salt, verifier } + * 18. Uses sessionID to read { serverKey, A } + * 19. Recreates server = new SRPServer({ verifier, serverKey }), sets server.A + * 20. Verifies M1 using the SRP server, obtaining a SRP M2 (evidence message) + * 21. Returns M2 + * + * Client then + * + * 22. Verifies M2 + * + * SRP setup is now complete. + * + * A similar flow is used when the user changes their passphrase. On passphrase + * change, a new KEK is generated, thus the SRP password also changes, and so a + * subset of the steps above are done to update both client and remote. + */ +export interface SRPAttributes { + srpUserID: string; + srpSalt: string; + memLimit: number; + opsLimit: number; + kekSalt: string; + isEmailMFAEnabled: boolean; +} /** * Derive a "password" (which is really an arbitrary binary value, not human @@ -27,16 +111,7 @@ export const deriveSRPPassword = async (kek: string) => { return toB64(kekSubKeyBytes.slice(0, 16)); }; -export interface SRPAttributes { - srpUserID: string; - srpSalt: string; - memLimit: number; - opsLimit: number; - kekSalt: string; - isEmailMFAEnabled: boolean; -} - -export interface GetSRPAttributesResponse { +interface GetSRPAttributesResponse { attributes: SRPAttributes; } @@ -47,29 +122,29 @@ export interface SRPSetupAttributes { loginSubKey: string; } -export interface SetupSRPRequest { +interface SetupSRPRequest { srpUserID: string; srpSalt: string; srpVerifier: string; srpA: string; } -export interface SetupSRPResponse { +interface SetupSRPResponse { setupID: string; srpB: string; } -export interface CompleteSRPSetupRequest { +interface CompleteSRPSetupRequest { setupID: string; srpM1: string; } -export interface CompleteSRPSetupResponse { +interface CompleteSRPSetupResponse { setupID: string; srpM2: string; } -export interface CreateSRPSessionResponse { +interface CreateSRPSessionResponse { sessionID: string; srpB: string; } @@ -78,10 +153,18 @@ export interface SRPVerificationResponse extends UserVerificationResponse { srpM2: string; } +export interface UpdatedKeyAttr { + kekSalt: string; + encryptedKey: string; + keyDecryptionNonce: string; + opsLimit: number; + memLimit: number; +} + export interface UpdateSRPAndKeysRequest { srpM1: string; setupID: string; - updatedKeyAttr: UpdatedKey; + updatedKeyAttr: UpdatedKeyAttr; /** * If true (default), then all existing sessions for the user will be * invalidated. @@ -128,7 +211,7 @@ export const startSRPSetup = async ( } }; -export const completeSRPSetup = async ( +const completeSRPSetup = async ( token: string, completeSRPSetupRequest: CompleteSRPSetupRequest, ) => { @@ -146,46 +229,6 @@ export const completeSRPSetup = async ( } }; -export const createSRPSession = async (srpUserID: string, srpA: string) => { - const res = await fetch(await apiURL("/users/srp/create-session"), { - method: "POST", - headers: publicRequestHeaders(), - body: JSON.stringify({ srpUserID, srpA }), - }); - ensureOk(res); - const data = await res.json(); - // TODO: Use zod - return data as CreateSRPSessionResponse; -}; - -export const verifySRPSession = async ( - sessionID: string, - srpUserID: string, - srpM1: string, -) => { - try { - const resp = await HTTPService.post( - await apiURL("/users/srp/verify-session"), - { sessionID, srpUserID, srpM1 }, - undefined, - ); - return resp.data as SRPVerificationResponse; - } catch (e) { - log.error("verifySRPSession failed", e); - if ( - e instanceof ApiError && - // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison - e.httpStatusCode === HttpStatusCode.Unauthorized - ) { - // The API contract allows for a SRP verification 401 both because - // of incorrect credentials or a non existent account. - throw Error(CustomError.INCORRECT_PASSWORD_OR_NO_ACCOUNT); - } else { - throw e; - } - } -}; - export const updateSRPAndKeys = async ( token: string, updateSRPAndKeyRequest: UpdateSRPAndKeysRequest, @@ -204,43 +247,89 @@ export const updateSRPAndKeys = async ( } }; -export const configureSRP = async ({ - srpSalt, - srpUserID, - srpVerifier, - loginSubKey, -}: SRPSetupAttributes) => { - try { - const srpClient = await generateSRPClient( - srpSalt, - srpUserID, - loginSubKey, - ); +export const configureSRP = async (attr: SRPSetupAttributes) => + srpSetupOrReconfigure(attr, (cbAttr) => + completeSRPSetup(getToken(), cbAttr), + ); - const srpA = convertBufferToBase64(srpClient.computeA()); +export const srpSetupOrReconfigure = async ( + { srpSalt, srpUserID, srpVerifier, loginSubKey }: SRPSetupAttributes, + cb: ({ + setupID, + srpM1, + }: { + setupID: string; + srpM1: string; + }) => Promise<{ srpM2: string }>, +) => { + const srpClient = await generateSRPClient(srpSalt, srpUserID, loginSubKey); - log.debug(() => `srp a: ${srpA}`); - const token = getToken(); - const { setupID, srpB } = await startSRPSetup(token, { - srpA, - srpUserID, - srpSalt, - srpVerifier, - }); + const srpA = convertBufferToBase64(srpClient.computeA()); - srpClient.setB(convertBase64ToBuffer(srpB)); + const token = getToken(); + const { setupID, srpB } = await startSRPSetup(token, { + srpA, + srpUserID, + srpSalt, + srpVerifier, + }); - const srpM1 = convertBufferToBase64(srpClient.computeM1()); + srpClient.setB(convertBase64ToBuffer(srpB)); - const { srpM2 } = await completeSRPSetup(token, { srpM1, setupID }); + const srpM1 = convertBufferToBase64(srpClient.computeM1()); - srpClient.checkM2(convertBase64ToBuffer(srpM2)); - } catch (e) { - log.error("Failed to configure SRP", e); - throw e; - } + const { srpM2 } = await cb({ srpM1, setupID }); + + srpClient.checkM2(convertBase64ToBuffer(srpM2)); }; +export const generateSRPClient = async ( + srpSalt: string, + srpUserID: string, + loginSubKey: string, +) => { + return new Promise((resolve, reject) => { + SRP.genKey(function (err, secret1) { + try { + if (err) { + reject(err); + } + if (!secret1) { + throw Error("secret1 gen failed"); + } + const srpClient = new SrpClient( + SRP.params["4096"], + convertBase64ToBuffer(srpSalt), + Buffer.from(srpUserID), + convertBase64ToBuffer(loginSubKey), + secret1, + false, + ); + + resolve(srpClient); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors + reject(e); + } + }); + }); +}; + +export const convertBufferToBase64 = (buffer: Buffer) => { + return buffer.toString("base64"); +}; + +export const convertBase64ToBuffer = (base64: string) => { + return Buffer.from(base64, "base64"); +}; + +/** + * + * @param loginSubKey The user's SRP password (autogenerated, derived + * deterministically from their kek by {@link deriveSRPPassword}). + * + * @returns + */ export const generateSRPSetupAttributes = async ( loginSubKey: string, ): Promise => { @@ -306,97 +395,42 @@ export const loginViaSRP = async ( } }; -// ==================== -// HELPERS -// ==================== - -export const generateSRPClient = async ( - srpSalt: string, - srpUserID: string, - loginSubKey: string, -) => { - return new Promise((resolve, reject) => { - SRP.genKey(function (err, secret1) { - try { - if (err) { - reject(err); - } - if (!secret1) { - throw Error("secret1 gen failed"); - } - const srpClient = new SrpClient( - SRP.params["4096"], - convertBase64ToBuffer(srpSalt), - Buffer.from(srpUserID), - convertBase64ToBuffer(loginSubKey), - secret1, - false, - ); - - resolve(srpClient); - } catch (e) { - // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors - reject(e); - } - }); +export const createSRPSession = async (srpUserID: string, srpA: string) => { + const res = await fetch(await apiURL("/users/srp/create-session"), { + method: "POST", + headers: publicRequestHeaders(), + body: JSON.stringify({ srpUserID, srpA }), }); + ensureOk(res); + const data = await res.json(); + // TODO: Use zod + return data as CreateSRPSessionResponse; }; -export const convertBufferToBase64 = (buffer: Buffer) => { - return buffer.toString("base64"); +export const verifySRPSession = async ( + sessionID: string, + srpUserID: string, + srpM1: string, +) => { + try { + const resp = await HTTPService.post( + await apiURL("/users/srp/verify-session"), + { sessionID, srpUserID, srpM1 }, + undefined, + ); + return resp.data as SRPVerificationResponse; + } catch (e) { + log.error("verifySRPSession failed", e); + if ( + e instanceof ApiError && + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison + e.httpStatusCode === HttpStatusCode.Unauthorized + ) { + // The API contract allows for a SRP verification 401 both because + // of incorrect credentials or a non existent account. + throw Error(CustomError.INCORRECT_PASSWORD_OR_NO_ACCOUNT); + } else { + throw e; + } + } }; - -export const convertBase64ToBuffer = (base64: string) => { - return Buffer.from(base64, "base64"); -}; - -export async function generateKeyAndSRPAttributes( - passphrase: string, -): Promise<{ - keyAttributes: KeyAttributes; - masterKey: string; - srpSetupAttributes: SRPSetupAttributes; -}> { - const cryptoWorker = await sharedCryptoWorker(); - const masterKey = await cryptoWorker.generateKey(); - const recoveryKey = await cryptoWorker.generateKey(); - const kek = await cryptoWorker.deriveSensitiveKey(passphrase); - - const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = - await cryptoWorker.encryptBox(masterKey, kek.key); - const { - encryptedData: masterKeyEncryptedWithRecoveryKey, - nonce: masterKeyDecryptionNonce, - } = await cryptoWorker.encryptBox(masterKey, recoveryKey); - const { - encryptedData: recoveryKeyEncryptedWithMasterKey, - nonce: recoveryKeyDecryptionNonce, - } = await cryptoWorker.encryptBox(recoveryKey, masterKey); - - const keyPair = await cryptoWorker.generateKeyPair(); - const { - encryptedData: encryptedSecretKey, - nonce: secretKeyDecryptionNonce, - } = await cryptoWorker.encryptBox(keyPair.privateKey, masterKey); - - const loginSubKey = await deriveSRPPassword(kek.key); - - const srpSetupAttributes = await generateSRPSetupAttributes(loginSubKey); - - const keyAttributes: KeyAttributes = { - encryptedKey, - keyDecryptionNonce, - kekSalt: kek.salt, - opsLimit: kek.opsLimit, - memLimit: kek.memLimit, - publicKey: keyPair.publicKey, - encryptedSecretKey, - secretKeyDecryptionNonce, - masterKeyEncryptedWithRecoveryKey, - masterKeyDecryptionNonce, - recoveryKeyEncryptedWithMasterKey, - recoveryKeyDecryptionNonce, - }; - - return { keyAttributes, masterKey, srpSetupAttributes }; -} diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index ce3dd2b183..3848e245bd 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -1,4 +1,10 @@ -import { decryptBox, encryptBox } from "ente-base/crypto"; +import { + decryptBox, + deriveSensitiveKey, + encryptBox, + generateKey, + generateKeyPair, +} from "ente-base/crypto"; import { authenticatedRequestHeaders, ensureOk, @@ -43,12 +49,16 @@ const LocalUser = z.object({ token: z.string(), }); -/** Locally available data for the logged in user */ +/** + * The local storage data about the user after they've logged in. + */ export type LocalUser = z.infer; /** * The local storage data about the user before login or signup is complete. * + * [Note: Partial local user] + * * During login or signup, the user object exists in various partial states in * local storage. * @@ -57,19 +67,41 @@ export type LocalUser = z.infer; * - When the user enters their email, the email property of the stored object * is set, but nothing else. * - * - If they have second factor verification set, then after entering their - * password {@link isTwoFactorEnabled} and {@link twoFactorSessionID} will - * also get filled in. + * - After they verify their password, we have two cases: if second factor + * verification is not set, and when it is set. * - * - Once they verify their TOTP based second factor, their {@link id} and - * {@link encryptedToken} will also get filled in. + * - If second factor verification is not set, then after verifying their + * password their {@link id} and {@link encryptedToken} will get filled in, + * and {@link isTwoFactorEnabled} will be set to false. + * + * - If they have second factor verification set, then after verifying their + * password {@link isTwoFactorEnabled} and {@link twoFactorSessionID} will + * also get filled in. Once they verify their TOTP based second factor, their + * {@link id} and {@link encryptedToken} will also get filled in. + * + * So while the underlying storage is the same, we offer two APIs for code to + * obtain the user: + * + * - Before login is complete, or when it is unknown if login is complete or + * not, then {@link partialLocalUser} can be used to obtain a + * {@link LocalUser} with all of its properties set to be optional. + * + * - When we know that the login has completed, we can use either + * {@link localUser} (which returns `undefined` if our presumption is false) + * or {@link ensureLocalUser} (which throws if our presumption is false) to + * obtain an object with all the properties expected to be present for a + * locally persisted user set to be required. */ -// TODO: Start using me. -export const PreLoginLocalUser = LocalUser.partial(); +export const partialLocalUser = (): Partial | undefined => { + // TODO: duplicate of getData("user") + const s = localStorage.getItem("user"); + if (!s) return undefined; + return LocalUser.partial().parse(JSON.parse(s)); +}; /** * Return the logged-in user, if someone is indeed logged in. Otherwise return - * `undefined` (TODO: That's not what it is doing...). + * `undefined`. * * The user's data is stored in the browser's localStorage. Thus, this function * only works from the main thread, not from web workers (local storage is not @@ -79,7 +111,8 @@ export const localUser = (): LocalUser | undefined => { // TODO: duplicate of getData("user") const s = localStorage.getItem("user"); if (!s) return undefined; - return LocalUser.parse(JSON.parse(s)); + const { success, data } = LocalUser.safeParse(JSON.parse(s)); + return success ? data : undefined; }; /** @@ -288,6 +321,61 @@ export const ensureSavedKeyAttributes = (): KeyAttributes => export const saveKeyAttributes = (keyAttributes: KeyAttributes) => localStorage.setItem("keyAttributes", JSON.stringify(keyAttributes)); +/** + * Generate a new set of key attributes. + * + * @param passphrase The passphrase to use for deriving the key encryption key. + * + * @returns a newly generated master key (base64 string), kek (base64 string) + * and the key attributes associated with the combination. + */ +export async function generateKeysAndAttributes( + passphrase: string, +): Promise<{ masterKey: string; kek: string; keyAttributes: KeyAttributes }> { + const masterKey = await generateKey(); + const recoveryKey = await generateKey(); + const { + key: kek, + salt: kekSalt, + opsLimit, + memLimit, + } = await deriveSensitiveKey(passphrase); + + const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = + await encryptBox(masterKey, kek); + const { + encryptedData: masterKeyEncryptedWithRecoveryKey, + nonce: masterKeyDecryptionNonce, + } = await encryptBox(masterKey, recoveryKey); + const { + encryptedData: recoveryKeyEncryptedWithMasterKey, + nonce: recoveryKeyDecryptionNonce, + } = await encryptBox(recoveryKey, masterKey); + + const keyPair = await generateKeyPair(); + const { + encryptedData: encryptedSecretKey, + nonce: secretKeyDecryptionNonce, + } = await encryptBox(keyPair.privateKey, masterKey); + + const keyAttributes: KeyAttributes = { + encryptedKey, + keyDecryptionNonce, + kekSalt, + opsLimit, + memLimit, + publicKey: keyPair.publicKey, + encryptedSecretKey, + secretKeyDecryptionNonce, + masterKeyEncryptedWithRecoveryKey, + masterKeyDecryptionNonce, + recoveryKeyEncryptedWithMasterKey, + recoveryKeyDecryptionNonce, + }; + + return { masterKey, kek, keyAttributes }; +} + /** * Update or set the user's {@link KeyAttributes} on remote. */ @@ -459,14 +547,6 @@ export const remoteLogoutIfNeeded = async () => { ensureOk(res); }; -export interface UpdatedKey { - kekSalt: string; - encryptedKey: string; - keyDecryptionNonce: string; - memLimit: number; - opsLimit: number; -} - /** * Change the email associated with the user's account on remote. * diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 26f561f34e..a5151f384a 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -11,6 +11,7 @@ import { mergeUint8Arrays } from "ente-utils/array"; import sodium from "libsodium-wrappers-sumo"; import { + deriveKeyInsufficientMemoryErrorMessage, streamEncryptionChunkSize, type BytesOrB64, type DerivedKey, @@ -837,7 +838,7 @@ export const deriveSensitiveKey = async ( memLimit /= 2; } } - throw new Error("Failed to derive key: memory limit exceeded"); + throw new Error(deriveKeyInsufficientMemoryErrorMessage); }; /** diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index 2e20f5978b..ac33b0bcd9 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -1,3 +1,7 @@ +/** + * @file types shared between the public API (main thread) and implementation + * (worker thread). Also some constants, but no code. + */ import { type StateAddress } from "libsodium-wrappers-sumo"; /** @@ -19,6 +23,18 @@ export type SodiumStateAddress = StateAddress; */ export const streamEncryptionChunkSize = 4 * 1024 * 1024; +/** + * The {@link message} of {@link Error} that is thrown by + * {@link deriveSensitiveKey} if we could not find acceptable ops and mem limit + * combinations without exceeded the maximum mem limit. + * + * Generally, this indicates that the current device is not powerful enough to + * perform the key derivation. This is rare for computers, but can happen with + * older mobile devices with too little RAM. + */ +export const deriveKeyInsufficientMemoryErrorMessage = + "Failed to derive key (insufficient memory)"; + /** * Data provided either as bytes ({@link Uint8Array}) or their base64 string * representation. diff --git a/web/packages/base/http.ts b/web/packages/base/http.ts index 2b8e189b74..8dd9b76d09 100644 --- a/web/packages/base/http.ts +++ b/web/packages/base/http.ts @@ -141,6 +141,8 @@ export const isHTTP401Error = (e: unknown) => * Return `true` if this is an error because of a HTTP failure response returned * by museum with the given "code" and HTTP status. * + * > The function is async because it needs to parse the payload. + * * For some known set of errors, museum returns a payload of the form * * {"code":"USER_NOT_REGISTERED","message":"User is not registered"} diff --git a/web/packages/base/session.ts b/web/packages/base/session.ts index 9cfecd1e20..410d1f1a5e 100644 --- a/web/packages/base/session.ts +++ b/web/packages/base/session.ts @@ -82,7 +82,20 @@ export const masterKeyFromSession = async () => { */ export const saveMasterKeyInSessionAndSafeStore = async (masterKey: string) => { await saveKeyInSessionStore("encryptionKey", masterKey); - await globalThis.electron?.saveMasterKeyInSafeStorage(masterKey); + try { + await globalThis.electron?.saveMasterKeyInSafeStorage(masterKey); + } catch (e) { + // [Note: Safe storage is best effort] + // + // The user might be running on an OS which does not provide secure + // storage. Practically this is rare, but it can happen, especially on + // Linux if the app is run in a desktop environment without libsecret. + // + // So intercept failures to read and write to safe storage, and + // gracefully degrade to how the web app behaves (ask the user for their + // password in each session). + log.warn("Failed to save master key in safe storage", e); + } }; /** @@ -135,7 +148,8 @@ export const updateSessionFromElectronSafeStorageIfNeeded = async () => { try { masterKey = await electron.masterKeyFromSafeStorage(); } catch (e) { - log.error("Failed to read master key from safe storage", e); + // See: [Note: Safe storage is best effort] + log.warn("Failed to read master key from safe storage", e); } if (masterKey) { await saveKeyInSessionStore("encryptionKey", masterKey); diff --git a/web/packages/new/photos/services/settings.ts b/web/packages/new/photos/services/settings.ts index 3d3649d211..fadea19a42 100644 --- a/web/packages/new/photos/services/settings.ts +++ b/web/packages/new/photos/services/settings.ts @@ -2,7 +2,7 @@ * @file Storage (in-memory, local, remote) and update of various settings. */ -import { localUser } from "ente-accounts/services/user"; +import { partialLocalUser } from "ente-accounts/services/user"; import { isDevBuild } from "ente-base/env"; import log from "ente-base/log"; import { updateShouldDisableCFUploadProxy } from "ente-gallery/services/upload"; @@ -188,16 +188,16 @@ const setSettingsSnapshot = (snapshot: Settings) => { }; /** - * Return `true` if this is a development build, and the current user is marked - * as an "development" user. + * Return `true` if this is a development build, and the current user (if any) + * is marked as an "development" user. * * Emails that end in "@ente.io" are considered as dev users. */ export const isDevBuildAndUser = () => isDevBuild && isDevUserViaEmail(); const isDevUserViaEmail = () => { - const user = localUser(); - return !!user?.email.endsWith("@ente.io"); + const user = partialLocalUser(); + return !!user?.email?.endsWith("@ente.io"); }; /**