diff --git a/.github/workflows/auth-lint.yml b/.github/workflows/auth-lint.yml index 575a5e6dc3..2deaa285f7 100644 --- a/.github/workflows/auth-lint.yml +++ b/.github/workflows/auth-lint.yml @@ -30,6 +30,18 @@ jobs: exit 1 fi done + + - name: Verify all icons are less than 20KB + run: | + find assets/custom-icons -type f -name "*.svg" | while read -r file; do + if [[ "$file" == "assets/custom-icons/icons/bbs_nga.svg" ]]; then + continue + fi + if [[ "$(stat --printf="%s" "$file")" -gt 20480 ]]; then + echo "File size is greater than 20KB: $file ($file_size bytes)" + exit 1 + fi + done - name: Verify custom icon JSON run: cat assets/custom-icons/_data/custom-icons.json | jq empty diff --git a/README.md b/README.md index ad9c4970c0..9fd8fe8daa 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,10 @@ Learn more at [ente.io](https://ente.io). ![Screenshots of Ente Photos](.github/assets/photos.png) -Our flagship product. 3x data replication. On device machine learning. Cross -platform. Private sharing. Collaborative albums. Family plans. Easy import, -easier export. Background uploads. The list goes on. And of course, all of this, -while being fully end-to-end encrypted. +Our flagship product. 3x data replication. Face detection. Semantic search. +Private sharing. Collaborative albums. Family plans. Easy import, easier export. +Background uploads. The list goes on. And of course, all of this, while being +fully end-to-end encrypted across platforms. Ente Photos is a paid service, but we offer 5GB of free storage. You can also clone this repository and choose to self-host. diff --git a/auth/assets/custom-icons/_data/custom-icons.json b/auth/assets/custom-icons/_data/custom-icons.json index be561e4a4c..f79149f99c 100644 --- a/auth/assets/custom-icons/_data/custom-icons.json +++ b/auth/assets/custom-icons/_data/custom-icons.json @@ -85,6 +85,17 @@ "币安" ] }, + { + "title": "Binance TR", + "slug": "binance_tr" + }, + { + "title": "BinanceUS", + "slug": "binance_us", + "altNames": [ + "Binance US" + ] + }, { "title": "Bitfinex" }, @@ -258,6 +269,10 @@ { "title": "Dropbox" }, + { + "title": "DreamHost Panel", + "slug": "dreamhost_panel" + }, { "title": "dus.net", "slug": "dusnet" @@ -433,6 +448,9 @@ { "title": "Kite" }, + { + "title": "Kotas" + }, { "title": "KnownHost", "altNames": [ @@ -515,7 +533,9 @@ }, { "title": "matlab", - "altNames": ["mathworks"] + "altNames": [ + "mathworks" + ] }, { "title": "Mercado Livre", @@ -527,7 +547,7 @@ ] }, { - "title": "Microsoft" + "title": "microsoft" }, { "title": "Microsoft 365", @@ -602,6 +622,9 @@ "title": "ngrok", "hex": "858585" }, + { + "title": "Nelnet" + }, { "title": "nintendo", "altNames": [ @@ -611,6 +634,14 @@ { "title": "Njalla" }, + { + "title": "nordvpn", + "slug": "nordaccount", + "hex": "#4687FF", + "altNames": [ + "Nord Account" + ] + }, { "title": "Notesnook" }, @@ -715,7 +746,8 @@ ] }, { - "title": "randstad" + "title": "randstad", + "hex": "#2175D9" }, { "title": "Real-Debrid", @@ -947,6 +979,10 @@ { "title": "Upstox" }, + { + "title": "US Mobile", + "slug": "us_mobile" + }, { "title": "Vikunja" }, @@ -957,7 +993,8 @@ ] }, { - "title": "WARGAMING.NET" + "title": "WARGAMING.NET", + "slug": "wargamingnet" }, { "title": "Wealthfront" @@ -1009,4 +1046,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/bbs_nga.svg b/auth/assets/custom-icons/icons/bbs_nga.svg index 4735844e74..d0e582e5b1 100644 --- a/auth/assets/custom-icons/icons/bbs_nga.svg +++ b/auth/assets/custom-icons/icons/bbs_nga.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/bloom_host.svg b/auth/assets/custom-icons/icons/bloom_host.svg index 6f624a3265..9555afcb9e 100644 --- a/auth/assets/custom-icons/icons/bloom_host.svg +++ b/auth/assets/custom-icons/icons/bloom_host.svg @@ -1,7 +1 @@ - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/bybit.svg b/auth/assets/custom-icons/icons/bybit.svg index 3413a18878..c792ecfc20 100644 --- a/auth/assets/custom-icons/icons/bybit.svg +++ b/auth/assets/custom-icons/icons/bybit.svg @@ -1 +1,38 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/auth/assets/custom-icons/icons/cloudns.svg b/auth/assets/custom-icons/icons/cloudns.svg index 9096df8c3c..cca37bd105 100644 --- a/auth/assets/custom-icons/icons/cloudns.svg +++ b/auth/assets/custom-icons/icons/cloudns.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/digifinex.svg b/auth/assets/custom-icons/icons/digifinex.svg index 111b5ca533..3aa656462e 100644 --- a/auth/assets/custom-icons/icons/digifinex.svg +++ b/auth/assets/custom-icons/icons/digifinex.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/dreamhost_panel.svg b/auth/assets/custom-icons/icons/dreamhost_panel.svg new file mode 100644 index 0000000000..ab64b80877 --- /dev/null +++ b/auth/assets/custom-icons/icons/dreamhost_panel.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/auth/assets/custom-icons/icons/jianguoyun.svg b/auth/assets/custom-icons/icons/jianguoyun.svg index c2c9060389..dd662af6e5 100644 --- a/auth/assets/custom-icons/icons/jianguoyun.svg +++ b/auth/assets/custom-icons/icons/jianguoyun.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/kotas.svg b/auth/assets/custom-icons/icons/kotas.svg new file mode 100644 index 0000000000..d81e5e99c1 --- /dev/null +++ b/auth/assets/custom-icons/icons/kotas.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/mozilla.svg b/auth/assets/custom-icons/icons/mozilla.svg index ef061c6fb6..956cdfe2dc 100644 --- a/auth/assets/custom-icons/icons/mozilla.svg +++ b/auth/assets/custom-icons/icons/mozilla.svg @@ -1,112 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/nelnet.svg b/auth/assets/custom-icons/icons/nelnet.svg new file mode 100644 index 0000000000..017fab82d5 --- /dev/null +++ b/auth/assets/custom-icons/icons/nelnet.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/auth/assets/custom-icons/icons/nordaccount.svg b/auth/assets/custom-icons/icons/nordaccount.svg new file mode 100644 index 0000000000..49f135c458 --- /dev/null +++ b/auth/assets/custom-icons/icons/nordaccount.svg @@ -0,0 +1 @@ +NordVPN \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/randstad.svg b/auth/assets/custom-icons/icons/randstad.svg index 64d071e340..9f82ed9b03 100644 --- a/auth/assets/custom-icons/icons/randstad.svg +++ b/auth/assets/custom-icons/icons/randstad.svg @@ -1,19 +1,20 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/tcpshield.svg b/auth/assets/custom-icons/icons/tcpshield.svg index 6e6914700f..101486f813 100644 --- a/auth/assets/custom-icons/icons/tcpshield.svg +++ b/auth/assets/custom-icons/icons/tcpshield.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/us_mobile.svg b/auth/assets/custom-icons/icons/us_mobile.svg new file mode 100644 index 0000000000..b2535053a3 --- /dev/null +++ b/auth/assets/custom-icons/icons/us_mobile.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/auth/assets/custom-icons/icons/wargaming.svg b/auth/assets/custom-icons/icons/wargaming.svg deleted file mode 100644 index cceb66ccf6..0000000000 --- a/auth/assets/custom-icons/icons/wargaming.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/wargamingnet.svg b/auth/assets/custom-icons/icons/wargamingnet.svg new file mode 100644 index 0000000000..4ed8043c12 --- /dev/null +++ b/auth/assets/custom-icons/icons/wargamingnet.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/auth/lib/l10n/arb/app_ar.arb b/auth/lib/l10n/arb/app_ar.arb index 7de520f13c..5af7df38bd 100644 --- a/auth/lib/l10n/arb/app_ar.arb +++ b/auth/lib/l10n/arb/app_ar.arb @@ -435,8 +435,6 @@ "customEndpoint": "متصل بـ{endpoint}", "pinText": "ثبت", "unpinText": "ألغِ التثبيت", - "pinnedCodeMessage": "ثُبِّت {code}", - "unpinnedCodeMessage": "أُلغِي تثبيت {code}", "tags": "الأوسمة", "createNewTag": "أنشيء وسم جديد", "tag": "وسم", diff --git a/auth/lib/l10n/arb/app_bg.arb b/auth/lib/l10n/arb/app_bg.arb index e0dd805583..b5b9ac0019 100644 --- a/auth/lib/l10n/arb/app_bg.arb +++ b/auth/lib/l10n/arb/app_bg.arb @@ -148,7 +148,7 @@ "hintForMobile": "Натиснете продължително код, за да го редактирате или премахнете.", "hintForDesktop": "Натиснете десен бутон върху код, за да го редактирате или премахнете.", "scan": "Сканиране", - "scanACode": "Скениране на код", + "scanACode": "Сканиране на код", "verify": "Потвърждаване", "verifyEmail": "Потвърдете имейла", "enterCodeHint": "Въведете 6-цифрения код от\nВашето приложение за удостоверяване", @@ -156,6 +156,7 @@ "twoFactorAuthTitle": "Двуфакторно удостоверяване", "passkeyAuthTitle": "Удостоверяване с ключ за парола", "verifyPasskey": "Потвърдете ключ за парола", + "loginWithTOTP": "Влизане с еднократен код", "recoverAccount": "Възстановяване на акаунт", "enterRecoveryKeyHint": "Въведете Вашия ключ за възстановяване", "recover": "Възстановяване", @@ -199,7 +200,7 @@ "sorryUnableToGenCode": "За съжаление не може да се генерира код за {issuerName}", "noResult": "Няма резултати", "addCode": "Добавяне на код", - "scanAQrCode": "Скениране на QR код", + "scanAQrCode": "Сканиране на QR код", "enterDetailsManually": "Въведете подробности ръчно", "edit": "Редактиране", "share": "Споделяне", @@ -327,6 +328,10 @@ } } }, + "manualSort": "Персонализирано", + "editOrder": "Промяна на подредбата", + "mostFrequentlyUsed": "Често използвани", + "mostRecentlyUsed": "Последно използвани", "activeSessions": "Активни сесии", "somethingWentWrongPleaseTryAgain": "Нещо се обърка, моля опитайте отново", "thisWillLogYouOutOfThisDevice": "Това ще Ви изкара от профила на това устройство!", @@ -444,10 +449,11 @@ "invalidEndpointMessage": "За съжаление въведената от Вас крайна точка е невалидна. Моля, въведете валидна крайна точка и опитайте отново.", "endpointUpdatedMessage": "Крайната точка е актуализирана успешно", "customEndpoint": "Свързан към {endpoint}", - "pinText": "ПИН код", + "pinText": "Закачане", "unpinText": "Откачане", "pinnedCodeMessage": "{code} е закачен", "unpinnedCodeMessage": "{code} е откачен", + "pinned": "Закачен", "tags": "Етикети", "createNewTag": "Създаване на етикет", "tag": "Етикет", diff --git a/auth/lib/l10n/arb/app_ca.arb b/auth/lib/l10n/arb/app_ca.arb index 649d0fdeaf..cf7d61349e 100644 --- a/auth/lib/l10n/arb/app_ca.arb +++ b/auth/lib/l10n/arb/app_ca.arb @@ -446,8 +446,6 @@ "customEndpoint": "Connectat a {endpoint}", "pinText": "Fixa", "unpinText": "Desfixa", - "pinnedCodeMessage": "{code} fixat", - "unpinnedCodeMessage": "{code} deixat de fixar", "tags": "Etiquetes", "createNewTag": "Crea una nova etiqueta", "tag": "Etiqueta", diff --git a/auth/lib/l10n/arb/app_da.arb b/auth/lib/l10n/arb/app_da.arb index f766cdbf40..770e70d89d 100644 --- a/auth/lib/l10n/arb/app_da.arb +++ b/auth/lib/l10n/arb/app_da.arb @@ -446,8 +446,6 @@ "customEndpoint": "Forbindelse oprettet til {endpoint}", "pinText": "Fastgør", "unpinText": "Frigør", - "pinnedCodeMessage": "{code} er blevet fastgjort", - "unpinnedCodeMessage": "{code} er blevet frigjort", "tags": "Tags", "createNewTag": "Opret nyt tag", "tag": "Tag", diff --git a/auth/lib/l10n/arb/app_de.arb b/auth/lib/l10n/arb/app_de.arb index d1bbcd5eb5..8bce3046c8 100644 --- a/auth/lib/l10n/arb/app_de.arb +++ b/auth/lib/l10n/arb/app_de.arb @@ -444,8 +444,6 @@ "customEndpoint": "Mit {endpoint} verbunden", "pinText": "Anpinnen", "unpinText": "Lösen", - "pinnedCodeMessage": "{code} wurde angepinnt", - "unpinnedCodeMessage": "{code} wurde Losgelöst", "tags": "Tags", "createNewTag": "Neuen Tag erstellen", "tag": "Tag", diff --git a/auth/lib/l10n/arb/app_el.arb b/auth/lib/l10n/arb/app_el.arb index 7a9b0aa5ff..8027a2fbc2 100644 --- a/auth/lib/l10n/arb/app_el.arb +++ b/auth/lib/l10n/arb/app_el.arb @@ -156,6 +156,7 @@ "twoFactorAuthTitle": "Αυθεντικοποίηση δύο παραγόντων", "passkeyAuthTitle": "Επιβεβαίωση κλειδιού πρόσβασης", "verifyPasskey": "Επιβεβαίωση κλειδιού πρόσβασης", + "loginWithTOTP": "Είσοδος με TOTP", "recoverAccount": "Ανάκτηση λογαριασμού", "enterRecoveryKeyHint": "Εισάγετε το κλειδί ανάκτησης σας", "recover": "Ανάκτηση", @@ -327,6 +328,10 @@ } } }, + "manualSort": "Προσαρμοσμένο", + "editOrder": "Επεξεργασία σειράς", + "mostFrequentlyUsed": "Συχνά χρησιμοποιούμενο", + "mostRecentlyUsed": "Πρόσφατα χρησιμοποιούμενο", "activeSessions": "Ενεργές συνεδρίες", "somethingWentWrongPleaseTryAgain": "Κάτι πήγε στραβά, παρακαλώ προσπαθήστε ξανά", "thisWillLogYouOutOfThisDevice": "Αυτό θα σας αποσυνδέσει από αυτή τη συσκευή!", @@ -446,8 +451,9 @@ "customEndpoint": "Συνδεδεμένο στο {endpoint}", "pinText": "Καρφίτσωμα", "unpinText": "Ξεκαρφίτσωμα", - "pinnedCodeMessage": "Το {code} καρφιτσώθηκε", - "unpinnedCodeMessage": "Το {code} ξεκαρφιτσώθηκε", + "pinnedCodeMessage": "{code} έχει καρφιτσωθεί", + "unpinnedCodeMessage": "Το {code} έχει ξεκαρφιτσωθεί", + "pinned": "Καρφιτσωμένο", "tags": "Ετικέτες", "createNewTag": "Δημιουργία Νέας Ετικέτας", "tag": "Ετικέτα", diff --git a/auth/lib/l10n/arb/app_en.arb b/auth/lib/l10n/arb/app_en.arb index b50a464108..c891ddebac 100644 --- a/auth/lib/l10n/arb/app_en.arb +++ b/auth/lib/l10n/arb/app_en.arb @@ -258,6 +258,10 @@ "areYouSureYouWantToLogout": "Are you sure you want to logout?", "yesLogout": "Yes, logout", "exit": "Exit", + "theme": "Theme", + "lightTheme": "Light", + "darkTheme": "Dark", + "systemTheme": "System", "verifyingRecoveryKey": "Verifying recovery key...", "recoveryKeyVerified": "Recovery key verified", "recoveryKeySuccessBody": "Great! Your recovery key is valid. Thank you for verifying.\n\nPlease remember to keep your recovery key safely backed up.", @@ -491,5 +495,12 @@ "appLockNotEnabled": "App lock not enabled", "appLockNotEnabledDescription": "Please enable app lock from Security > App Lock", "authToViewPasskey": "Please authenticate to view passkey", - "appLockOfflineModeWarning": "You have chosen to proceed without backups. If you forget your applock, you will be locked out from accessing your data." + "appLockOfflineModeWarning": "You have chosen to proceed without backups. If you forget your applock, you will be locked out from accessing your data.", + "duplicateCodes": "Duplicate codes", + "noDuplicates": "✨ No duplicates", + "youveNoDuplicateCodesThatCanBeCleared": "You've no duplicate codes that can be cleared", + "deduplicateCodes": "Deduplicate codes", + "deselectAll": "Deselect all", + "selectAll": "Select all", + "deleteDuplicates": "Delete duplicates" } \ No newline at end of file diff --git a/auth/lib/l10n/arb/app_es.arb b/auth/lib/l10n/arb/app_es.arb index 27453078f9..93a8c61069 100644 --- a/auth/lib/l10n/arb/app_es.arb +++ b/auth/lib/l10n/arb/app_es.arb @@ -328,6 +328,10 @@ } } }, + "manualSort": "Personalizado", + "editOrder": "Editar orden", + "mostFrequentlyUsed": "Usados frecuentemente", + "mostRecentlyUsed": "Usados recientemente", "activeSessions": "Sesiones activas", "somethingWentWrongPleaseTryAgain": "Algo ha ido mal, por favor, inténtelo de nuevo", "thisWillLogYouOutOfThisDevice": "¡Esto cerrará la sesión de este dispositivo!", @@ -449,6 +453,7 @@ "unpinText": "Desanclar", "pinnedCodeMessage": "{code} ha sido anclado", "unpinnedCodeMessage": "{code} ha sido desanclado", + "pinned": "Anclado", "tags": "Etiquetas", "createNewTag": "Crear Nueva Etiqueta", "tag": "Etiqueta", @@ -485,5 +490,12 @@ "appLockNotEnabled": "Bloqueo de aplicación no activado", "appLockNotEnabledDescription": "Por favor, activa el bloqueo de aplicación desde Seguridad > Bloqueo de aplicación", "authToViewPasskey": "Por favor, autentícate para ver tu clave de acceso", - "appLockOfflineModeWarning": "Has elegido proceder sin copia de seguridad. Si olvidas el código de desbloqueo de la aplicación, se bloqueará el acceso a sus datos." + "appLockOfflineModeWarning": "Has elegido proceder sin copia de seguridad. Si olvidas el código de desbloqueo de la aplicación, se bloqueará el acceso a sus datos.", + "duplicateCodes": "Duplicar códigos", + "noDuplicates": "✨ No hay duplicados", + "youveNoDuplicateCodesThatCanBeCleared": "No tienes códigos duplicados que se puedan borrar", + "deduplicateCodes": "Desduplicar códigos", + "deselectAll": "Deseleccionar todo", + "selectAll": "Seleccionar todo", + "deleteDuplicates": "Eliminar duplicados" } \ No newline at end of file diff --git a/auth/lib/l10n/arb/app_fa.arb b/auth/lib/l10n/arb/app_fa.arb index 98822602fc..e7691aa915 100644 --- a/auth/lib/l10n/arb/app_fa.arb +++ b/auth/lib/l10n/arb/app_fa.arb @@ -401,8 +401,6 @@ "customEndpoint": "متصل شده به {endpoint}", "pinText": "پین", "unpinText": "حذف پین", - "pinnedCodeMessage": "{code} پین شد", - "unpinnedCodeMessage": "{code} از پین حذف شد", "tags": "برچسب‌ها", "createNewTag": "ایجاد برچسب جدید", "tag": "برچسب", diff --git a/auth/lib/l10n/arb/app_fr.arb b/auth/lib/l10n/arb/app_fr.arb index 9b6e02a8ee..6148d7e5df 100644 --- a/auth/lib/l10n/arb/app_fr.arb +++ b/auth/lib/l10n/arb/app_fr.arb @@ -445,8 +445,6 @@ "customEndpoint": "Connecté à {endpoint}", "pinText": "Épingler", "unpinText": "Désépingler", - "pinnedCodeMessage": "{code} a été épinglé", - "unpinnedCodeMessage": "{code} a été désépinglé", "tags": "Tags", "createNewTag": "Créer un nouveau tag", "tag": "Tag", diff --git a/auth/lib/l10n/arb/app_id.arb b/auth/lib/l10n/arb/app_id.arb index d7b53e0fd7..f6166796df 100644 --- a/auth/lib/l10n/arb/app_id.arb +++ b/auth/lib/l10n/arb/app_id.arb @@ -156,6 +156,7 @@ "twoFactorAuthTitle": "Autentikasi dua langkah", "passkeyAuthTitle": "Verifikasi passkey", "verifyPasskey": "Verifikasi passkey", + "loginWithTOTP": "Login menggunakan TOTP", "recoverAccount": "Pulihkan akun", "enterRecoveryKeyHint": "Masukkan kunci pemulihanmu", "recover": "Pulihkan", @@ -327,6 +328,10 @@ } } }, + "manualSort": "Kustom", + "editOrder": "Ubah pesanan", + "mostFrequentlyUsed": "Sering digunakan", + "mostRecentlyUsed": "Baru digunakan", "activeSessions": "Sesi aktif", "somethingWentWrongPleaseTryAgain": "Ada yang salah. Mohon coba kembali", "thisWillLogYouOutOfThisDevice": "Langkah ini akan mengeluarkan Anda dari gawai ini!", @@ -446,8 +451,6 @@ "customEndpoint": "Terkoneksi ke {endpoint}", "pinText": "Sematkan", "unpinText": "Awasematkan", - "pinnedCodeMessage": "{code} telah disematkan", - "unpinnedCodeMessage": "{code} telah diawasematkan", "tags": "Tanda", "createNewTag": "Buat Tanda Baru", "tag": "Tanda", diff --git a/auth/lib/l10n/arb/app_it.arb b/auth/lib/l10n/arb/app_it.arb index 4547e967f7..2c7b27235d 100644 --- a/auth/lib/l10n/arb/app_it.arb +++ b/auth/lib/l10n/arb/app_it.arb @@ -156,6 +156,7 @@ "twoFactorAuthTitle": "Autenticazione a due fattori", "passkeyAuthTitle": "Verifica della passkey", "verifyPasskey": "Verifica passkey", + "loginWithTOTP": "Login con TOTP", "recoverAccount": "Recupera account", "enterRecoveryKeyHint": "Inserisci la tua chiave di recupero", "recover": "Recupera", @@ -327,6 +328,10 @@ } } }, + "manualSort": "Personalizzato", + "editOrder": "Modifica ordine", + "mostFrequentlyUsed": "Utilizzato di frequente", + "mostRecentlyUsed": "Utilizzato di recente", "activeSessions": "Sessioni attive", "somethingWentWrongPleaseTryAgain": "Qualcosa è andato storto, per favore riprova", "thisWillLogYouOutOfThisDevice": "Questo ti disconnetterà da questo dispositivo!", @@ -448,6 +453,7 @@ "unpinText": "Sgancia", "pinnedCodeMessage": "{code} è stato fissato", "unpinnedCodeMessage": "{code} è stato sganciato", + "pinned": "Fissato", "tags": "Tag", "createNewTag": "Crea un nuovo tag", "tag": "Tag", @@ -484,5 +490,12 @@ "appLockNotEnabled": "Blocco app non abilitato", "appLockNotEnabledDescription": "Si prega di abilitare il blocco dell'app da Sicurezza > Blocco App", "authToViewPasskey": "Autenticati per visualizzare le tue passkey", - "appLockOfflineModeWarning": "Hai scelto di procedere senza backup. Se dimentichi il tuo codice di blocco dell'app, non potrai più accedere ai tuoi dati." + "appLockOfflineModeWarning": "Hai scelto di procedere senza backup. Se dimentichi il tuo codice di blocco dell'app, non potrai più accedere ai tuoi dati.", + "duplicateCodes": "Codici duplicati", + "noDuplicates": "✨ Nessun doppione", + "youveNoDuplicateCodesThatCanBeCleared": "Non ci sono codici duplicati che possono essere cancellati", + "deduplicateCodes": "Codici deduplicati", + "deselectAll": "Deselezionare tutti", + "selectAll": "Seleziona tutti", + "deleteDuplicates": "Elimina i duplicati" } \ No newline at end of file diff --git a/auth/lib/l10n/arb/app_ja.arb b/auth/lib/l10n/arb/app_ja.arb index 5b95f42313..8e90a8dbbf 100644 --- a/auth/lib/l10n/arb/app_ja.arb +++ b/auth/lib/l10n/arb/app_ja.arb @@ -156,6 +156,7 @@ "twoFactorAuthTitle": "2 要素認証", "passkeyAuthTitle": "パスキー認証", "verifyPasskey": "パスキーの認証", + "loginWithTOTP": "TOTPでログイン", "recoverAccount": "アカウントを回復", "enterRecoveryKeyHint": "回復キーを入力", "recover": "回復", @@ -327,6 +328,10 @@ } } }, + "manualSort": "カスタム", + "editOrder": "並べ替え", + "mostFrequentlyUsed": "よく使う", + "mostRecentlyUsed": "最近使った", "activeSessions": "アクティブセッション", "somethingWentWrongPleaseTryAgain": "問題が発生しました、再試行してください", "thisWillLogYouOutOfThisDevice": "このデバイスからログアウトします!", @@ -446,8 +451,9 @@ "customEndpoint": "{endpoint} に接続しました", "pinText": "固定", "unpinText": "固定を解除", - "pinnedCodeMessage": "{code} を固定しました", - "unpinnedCodeMessage": "{code} の固定が解除されました", + "pinnedCodeMessage": "{code}がピン留めされました", + "unpinnedCodeMessage": "{code}のピン留めが解除されました", + "pinned": "ピン留め", "tags": "タグ", "createNewTag": "新しいタグの作成", "tag": "タグ", diff --git a/auth/lib/l10n/arb/app_ko.arb b/auth/lib/l10n/arb/app_ko.arb index 936d4572f9..2322ce5962 100644 --- a/auth/lib/l10n/arb/app_ko.arb +++ b/auth/lib/l10n/arb/app_ko.arb @@ -328,6 +328,10 @@ } } }, + "manualSort": "사용자 정의", + "editOrder": "순서 변경", + "mostFrequentlyUsed": "자주 사용됨", + "mostRecentlyUsed": "최근에 사용됨", "activeSessions": "활성화된 세션", "somethingWentWrongPleaseTryAgain": "뭔가 잘못됐습니다, 다시 시도해주세요", "thisWillLogYouOutOfThisDevice": "이 작업을 하시면 기기에서 로그아웃하게 됩니다!", @@ -449,6 +453,7 @@ "unpinText": "핀 해제", "pinnedCodeMessage": "{code}가 핀 되었습니다.", "unpinnedCodeMessage": "{code}의 핀이 해제되었습니다.", + "pinned": "고정됨", "tags": "태그", "createNewTag": "새 태그 만들기", "tag": "태그", diff --git a/auth/lib/l10n/arb/app_lt.arb b/auth/lib/l10n/arb/app_lt.arb index bf9343443d..5e8c1143d3 100644 --- a/auth/lib/l10n/arb/app_lt.arb +++ b/auth/lib/l10n/arb/app_lt.arb @@ -328,6 +328,10 @@ } } }, + "manualSort": "Pasirinktinis", + "editOrder": "Redaguoti tvarką", + "mostFrequentlyUsed": "Dažniausiai naudojamą", + "mostRecentlyUsed": "Neseniai naudotą", "activeSessions": "Aktyvūs seansai", "somethingWentWrongPleaseTryAgain": "Kažkas nutiko ne taip. Bandykite dar kartą.", "thisWillLogYouOutOfThisDevice": "Tai jus atjungs nuo šio įrenginio.", @@ -449,6 +453,7 @@ "unpinText": "Atsegti", "pinnedCodeMessage": "{code} buvo prisegtas", "unpinnedCodeMessage": "{code} buvo atsegtas", + "pinned": "Prisegta", "tags": "Žymės", "createNewTag": "Kurti naują žymę", "tag": "Žymė", diff --git a/auth/lib/l10n/arb/app_ml.arb b/auth/lib/l10n/arb/app_ml.arb new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/auth/lib/l10n/arb/app_ml.arb @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/auth/lib/l10n/arb/app_nl.arb b/auth/lib/l10n/arb/app_nl.arb index d8859b4df9..a99b4f305b 100644 --- a/auth/lib/l10n/arb/app_nl.arb +++ b/auth/lib/l10n/arb/app_nl.arb @@ -156,6 +156,7 @@ "twoFactorAuthTitle": "Tweestapsverificatie", "passkeyAuthTitle": "Passkey verificatie", "verifyPasskey": "Bevestig passkey", + "loginWithTOTP": "Inloggen met TOTP", "recoverAccount": "Account herstellen", "enterRecoveryKeyHint": "Voer je herstelsleutel in", "recover": "Herstellen", @@ -327,6 +328,10 @@ } } }, + "manualSort": "Aangepast", + "editOrder": "Volgorde wijzigen", + "mostFrequentlyUsed": "Vaak gebruikt", + "mostRecentlyUsed": "Recent gebruikt", "activeSessions": "Actieve sessies", "somethingWentWrongPleaseTryAgain": "Er is iets fout gegaan, probeer het opnieuw", "thisWillLogYouOutOfThisDevice": "Dit zal je uitloggen van dit apparaat!", @@ -448,6 +453,7 @@ "unpinText": "Losmaken", "pinnedCodeMessage": "{code} is vastgezet", "unpinnedCodeMessage": "{code} is losgemaakt", + "pinned": "Vastgezet", "tags": "Labels", "createNewTag": "Nieuw label maken", "tag": "Label", diff --git a/auth/lib/l10n/arb/app_pl.arb b/auth/lib/l10n/arb/app_pl.arb index d96bdb7c00..baa5e5de48 100644 --- a/auth/lib/l10n/arb/app_pl.arb +++ b/auth/lib/l10n/arb/app_pl.arb @@ -328,6 +328,10 @@ } } }, + "manualSort": "Niestandardowe", + "editOrder": "Zmień kolejność", + "mostFrequentlyUsed": "Często używane", + "mostRecentlyUsed": "Ostatnio używane", "activeSessions": "Aktywne sesje", "somethingWentWrongPleaseTryAgain": "Coś poszło nie tak, spróbuj ponownie", "thisWillLogYouOutOfThisDevice": "To wyloguje Cię z tego urządzenia!", @@ -449,6 +453,7 @@ "unpinText": "Odepnij", "pinnedCodeMessage": "Przypięto {code}", "unpinnedCodeMessage": "Odpięto {code}", + "pinned": "Przypięte", "tags": "Etykiety", "createNewTag": "Utwórz nową etykietę", "tag": "Etykieta", @@ -485,5 +490,12 @@ "appLockNotEnabled": "Blokada aplikacji nie jest włączona", "appLockNotEnabledDescription": "Prosimy włączyć blokadę aplikacji z Zabezpieczenia > Blokada aplikacji", "authToViewPasskey": "Prosimy uwierzytelnić się, aby wyświetlić klucz dostępu", - "appLockOfflineModeWarning": "Wybrano kontynuowanie bez kopii zapasowych. Jeśli zapomnisz blokady aplikacji, utracisz dostęp do swoich danych." + "appLockOfflineModeWarning": "Wybrano kontynuowanie bez kopii zapasowych. Jeśli zapomnisz blokady aplikacji, utracisz dostęp do swoich danych.", + "duplicateCodes": "Duplikuj kody", + "noDuplicates": "✨ Brak duplikatów", + "youveNoDuplicateCodesThatCanBeCleared": "Nie masz duplikatów kodów, które mogą być wyczyszczone", + "deduplicateCodes": "Deduplikuj kody", + "deselectAll": "Odznacz wszystko", + "selectAll": "Zaznacz wszystko", + "deleteDuplicates": "Usuń duplikaty" } \ No newline at end of file diff --git a/auth/lib/l10n/arb/app_pt.arb b/auth/lib/l10n/arb/app_pt.arb index cb01abb4cf..79b7e69936 100644 --- a/auth/lib/l10n/arb/app_pt.arb +++ b/auth/lib/l10n/arb/app_pt.arb @@ -132,7 +132,7 @@ "general": "Geral", "settings": "Ajustes", "copied": "Copiado", - "pleaseTryAgain": "Tente de novo", + "pleaseTryAgain": "Tente novamente", "existingUser": "Usuário existente", "newUser": "Novo no Ente", "delete": "Excluir", @@ -142,7 +142,7 @@ "suggestFeatures": "Sugerir recursos", "faq": "Perguntas frequentes", "somethingWentWrongMessage": "Algo deu errado. Tente outra vez", - "leaveFamily": "Sair da família", + "leaveFamily": "Sair do plano familiar", "leaveFamilyMessage": "Deseja mesmo sair do plano familiar?", "inFamilyPlanMessage": "Você está em um plano familiar!", "hintForMobile": "Pressione em um código para editar ou excluir.", @@ -271,7 +271,7 @@ "recoveryKeyVerifyReason": "Sua chave de recuperação é a única maneira de recuperar suas fotos se você esqueceu sua senha. Você pode encontrar sua chave de recuperação em Opções > Conta.\n\nInsira sua chave de recuperação aqui para verificar se você a salvou corretamente.", "confirmYourRecoveryKey": "Confirme sua chave de recuperação", "confirm": "Confirmar", - "emailYourLogs": "Enviar logs por e-mail", + "emailYourLogs": "Enviar registros por e-mail", "pleaseSendTheLogsTo": "Envie os logs para \n{toEmail}", "copyEmailAddress": "Copiar endereço de e-mail", "exportLogs": "Exportar logs", @@ -328,6 +328,10 @@ } } }, + "manualSort": "Personalizado", + "editOrder": "Editar ordem", + "mostFrequentlyUsed": "Usado com frequência", + "mostRecentlyUsed": "Usado recentemente", "activeSessions": "Sessões ativas", "somethingWentWrongPleaseTryAgain": "Algo deu errado. Tente outra vez", "thisWillLogYouOutOfThisDevice": "Isso fará com que você saia deste dispositivo!", @@ -337,7 +341,7 @@ "thisDevice": "Esse dispositivo", "toResetVerifyEmail": "Para redefinir sua senha, verifique seu e-mail primeiramente.", "thisEmailIsAlreadyInUse": "Este e-mail já está em uso", - "verificationFailedPleaseTryAgain": "Falha na verificação. Tente novamente", + "verificationFailedPleaseTryAgain": "Falhou na verificação. Tente novamente", "yourVerificationCodeHasExpired": "Seu código de verificação expirou", "incorrectCode": "Código incorreto", "sorryTheCodeYouveEnteredIsIncorrect": "O código inserido está incorreto", @@ -354,7 +358,7 @@ "plainText": "Texto simples", "passwordToEncryptExport": "Senha para criptografar a exportação", "export": "Exportar", - "useOffline": "Usar sem backups", + "useOffline": "Usar sem cópia de segurança", "signInToBackup": "Entre para fazer backup de seus códigos", "singIn": "Entrar", "sigInBackupReminder": "Exporte seus códigos para garantir que você tenha uma cópia para restaurar.", @@ -449,6 +453,7 @@ "unpinText": "Desafixar", "pinnedCodeMessage": "{code} foi fixado", "unpinnedCodeMessage": "{code} foi desafixado", + "pinned": "Fixado", "tags": "Etiquetas", "createNewTag": "Criar nova etiqueta", "tag": "Etiqueta", @@ -485,5 +490,12 @@ "appLockNotEnabled": "Bloqueio de aplicativo não ativado", "appLockNotEnabledDescription": "Ative o bloqueio de aplicativo em Segurança > Bloqueio de aplicativo", "authToViewPasskey": "Autentique para ver a sua chave de acesso", - "appLockOfflineModeWarning": "Você prosseguiu sem cópias de segurança. Caso, se esqueça de seu aplicativo de bloqueio, você não poderá mais acessar seus dados." + "appLockOfflineModeWarning": "Você prosseguiu sem cópias de segurança. Caso, se esqueça de seu aplicativo de bloqueio, você não poderá mais acessar seus dados.", + "duplicateCodes": "Duplicar códigos", + "noDuplicates": "✨ Sem duplicados", + "youveNoDuplicateCodesThatCanBeCleared": "Você não possui códigos duplicados para limpar", + "deduplicateCodes": "Desduplicar códigos", + "deselectAll": "Deselecionar tudo", + "selectAll": "Selecionar tudo", + "deleteDuplicates": "Excluir duplicados" } \ No newline at end of file diff --git a/auth/lib/l10n/arb/app_ru.arb b/auth/lib/l10n/arb/app_ru.arb index 6c2e737db6..1fcb98334f 100644 --- a/auth/lib/l10n/arb/app_ru.arb +++ b/auth/lib/l10n/arb/app_ru.arb @@ -446,8 +446,6 @@ "customEndpoint": "Подключено к {endpoint}", "pinText": "Прикрепить", "unpinText": "Открепить", - "pinnedCodeMessage": "{code} прикреплен", - "unpinnedCodeMessage": "{code} откреплен", "tags": "Метки", "createNewTag": "Создать новую метку", "tag": "Метка", diff --git a/auth/lib/l10n/arb/app_sk.arb b/auth/lib/l10n/arb/app_sk.arb index 96756ab629..4b319f3f2a 100644 --- a/auth/lib/l10n/arb/app_sk.arb +++ b/auth/lib/l10n/arb/app_sk.arb @@ -45,7 +45,7 @@ "timeBasedKeyType": "Na základe času (TOTP)", "counterBasedKeyType": "Na základe počítadla (HOTP)", "saveAction": "Uložiť", - "nextTotpTitle": "ďalej", + "nextTotpTitle": "ďalší", "deleteCodeTitle": "Odstrániť položku?", "deleteCodeMessage": "Naozaj chcete odstrániť položku? Táto akcia je nezvratná.", "trashCode": "Odstrániť kód?", @@ -156,6 +156,7 @@ "twoFactorAuthTitle": "Dvojfaktorové overovanie", "passkeyAuthTitle": "Overenie pomocou passkey", "verifyPasskey": "Overiť passkey", + "loginWithTOTP": "Prihlásenie pomocou TOTP", "recoverAccount": "Obnoviť účet", "enterRecoveryKeyHint": "Vložte váš kód pre obnovenie", "recover": "Obnoviť", @@ -446,8 +447,6 @@ "customEndpoint": "Pripojený k endpointu {endpoint}", "pinText": "Pripnúť", "unpinText": "Odopnúť", - "pinnedCodeMessage": "{code} bol pripnutý", - "unpinnedCodeMessage": "{code} bol odopnutý", "tags": "Tagy", "createNewTag": "Vytvoriť nový tag", "tag": "Tag", diff --git a/auth/lib/l10n/arb/app_sl.arb b/auth/lib/l10n/arb/app_sl.arb index 6123ba22b0..0be8735800 100644 --- a/auth/lib/l10n/arb/app_sl.arb +++ b/auth/lib/l10n/arb/app_sl.arb @@ -327,6 +327,8 @@ } } }, + "mostFrequentlyUsed": "Pogosto uporabljeni", + "mostRecentlyUsed": "Nedavno uporabljeno", "activeSessions": "Aktivne seje", "somethingWentWrongPleaseTryAgain": "Nekaj je šlo narobe, prosimo poizkusite znova.", "thisWillLogYouOutOfThisDevice": "To vas bo odjavilo iz te naprave!", @@ -446,8 +448,7 @@ "customEndpoint": "Povezano na {endpoint}", "pinText": "Pripni", "unpinText": "Odpni", - "pinnedCodeMessage": "{code} je bila pripeta", - "unpinnedCodeMessage": "{code} je bila odpeta", + "pinned": "Pripeto", "tags": "Oznake", "createNewTag": "Ustvari novo oznako", "tag": "Oznaka", diff --git a/auth/lib/l10n/arb/app_tr.arb b/auth/lib/l10n/arb/app_tr.arb index ccb63e3994..c4d87cde5e 100644 --- a/auth/lib/l10n/arb/app_tr.arb +++ b/auth/lib/l10n/arb/app_tr.arb @@ -446,8 +446,6 @@ "customEndpoint": "Bağlandı: {endpoint}", "pinText": "Sabitle", "unpinText": "Sabitlemeyi kaldır", - "pinnedCodeMessage": "{code} sabitlendi", - "unpinnedCodeMessage": "{code} sabitlemesi kaldırıldı", "tags": "Etiketler", "createNewTag": "Yeni etiket oluştur", "tag": "Etiket", diff --git a/auth/lib/l10n/arb/app_uk.arb b/auth/lib/l10n/arb/app_uk.arb index 9b9b560da5..9706d8fe7d 100644 --- a/auth/lib/l10n/arb/app_uk.arb +++ b/auth/lib/l10n/arb/app_uk.arb @@ -115,14 +115,14 @@ "importCodeDelimiterInfo": "Коди можуть бути розділені комою або новим рядком", "selectFile": "Вибрати файл", "emailVerificationToggle": "Підтвердження адреси електронної пошти", - "emailVerificationEnableWarning": "Щоб уникнути блокування доступу до свого облікового запису, обов’язково збережіть копію двофакторної аутентифікації до своєї електронної пошти за межами Ente Auth, перш ніж увімкнути перевірку електронної пошти.", - "authToChangeEmailVerificationSetting": "Будь ласка, пройдіть аутентифікацію, щоб змінити перевірку адреси електронної пошти", + "emailVerificationEnableWarning": "Щоб уникнути блокування доступу до свого облікового запису, обов’язково збережіть копію двоетапної автентифікації до своєї електронної пошти за межами Ente Auth, перш ніж увімкнути перевірку електронної пошти.", + "authToChangeEmailVerificationSetting": "Будь ласка, пройдіть автентифікацію, щоб змінити перевірку адреси електронної пошти", "authenticateGeneric": "Будь ласка, авторизуйтеся", - "authToViewYourRecoveryKey": "Будь ласка, пройдіть аутентифікацію, щоб переглянути ваш ключ відновлення", - "authToChangeYourEmail": "Будь ласка, пройдіть аутентифікацію, щоб змінити адресу електронної пошти", - "authToChangeYourPassword": "Будь ласка, пройдіть аутентифікацію, щоб змінити ваш пароль", - "authToViewSecrets": "Будь ласка, пройдіть аутентифікацію, щоб переглянути ваші секретні коди", - "authToInitiateSignIn": "Будь ласка, пройдіть аутентифікацію, щоб розпочати вхід для резервного копіювання.", + "authToViewYourRecoveryKey": "Будь ласка, пройдіть автентифікацію, щоб переглянути ваш ключ відновлення", + "authToChangeYourEmail": "Будь ласка, пройдіть автентифікацію, щоб змінити адресу електронної пошти", + "authToChangeYourPassword": "Будь ласка, пройдіть автентифікацію, щоб змінити ваш пароль", + "authToViewSecrets": "Будь ласка, пройдіть автентифікацію, щоб переглянути ваші секретні коди", + "authToInitiateSignIn": "Будь ласка, пройдіть автентифікацію, щоб розпочати вхід для резервного копіювання.", "ok": "Ок", "cancel": "Скасувати", "yes": "Так", @@ -153,7 +153,7 @@ "verifyEmail": "Підтвердити електронну адресу", "enterCodeHint": "Введіть нижче шестизначний код із застосунку для автентифікації", "lostDeviceTitle": "Загубили пристрій?", - "twoFactorAuthTitle": "Двофакторна аутентифікація", + "twoFactorAuthTitle": "Двоетапна автентифікація", "passkeyAuthTitle": "Перевірка секретного ключа", "verifyPasskey": "Підтвердження секретного ключа", "loginWithTOTP": "Увійти за допомогою TOTP", @@ -194,7 +194,7 @@ "authToChangeLockscreenSetting": "Будь ласка, авторизуйтесь для зміни налаштувань екрану блокування", "deviceLockEnablePreSteps": "Для увімкнення блокування програми, будь ласка, налаштуйте пароль пристрою або блокування екрана в системних налаштуваннях.", "viewActiveSessions": "Показати активні сеанси", - "authToViewYourActiveSessions": "Будь ласка, пройдіть аутентифікацію, щоб переглянути ваші активні сеанси", + "authToViewYourActiveSessions": "Будь ласка, пройдіть автентифікацію, щоб переглянути ваші активні сеанси", "searchHint": "Пошук...", "search": "Пошук", "sorryUnableToGenCode": "Вибачте, не вдалося створити код для {issuerName}", @@ -328,6 +328,10 @@ } } }, + "manualSort": "Власні", + "editOrder": "Змінити порядок", + "mostFrequentlyUsed": "Часто використовувані", + "mostRecentlyUsed": "Нещодавно використані", "activeSessions": "Активні сеанси", "somethingWentWrongPleaseTryAgain": "Щось пішло не так, спробуйте, будь ласка, знову", "thisWillLogYouOutOfThisDevice": "Це призведе до виходу на цьому пристрої!", @@ -342,9 +346,9 @@ "incorrectCode": "Невірний код", "sorryTheCodeYouveEnteredIsIncorrect": "Вибачте, але введений вами код є невірним", "emailChangedTo": "Адресу електронної пошти змінено на {newEmail}", - "authenticationFailedPleaseTryAgain": "Аутентифікація не пройдена. Будь ласка, спробуйте ще раз", + "authenticationFailedPleaseTryAgain": "Автентифікація не пройдена. Будь ласка, спробуйте ще раз", "authenticationSuccessful": "Автентифікацію виконано!", - "twofactorAuthenticationSuccessfullyReset": "Двофакторна аутентифікація успішно скинута", + "twofactorAuthenticationSuccessfullyReset": "Двоетапна автентифікація успішно скинута", "incorrectRecoveryKey": "Неправильний ключ відновлення", "theRecoveryKeyYouEnteredIsIncorrect": "Ви ввели неправильний ключ відновлення", "enterPassword": "Введіть пароль", @@ -366,9 +370,9 @@ "focusOnSearchBar": "Сфокусуватися на пошуку після запуску програми", "confirmUpdatingkey": "Ви впевнені у тому, що бажаєте змінити секретний ключ?", "minimizeAppOnCopy": "Згорнути програму після копіювання", - "editCodeAuthMessage": "Аутентифікуйтесь, щоб змінити код", - "deleteCodeAuthMessage": "Аутентифікуйтесь, щоб видалити код", - "showQRAuthMessage": "Аутентифікуйтесь, щоб показати QR-код", + "editCodeAuthMessage": "Авторизуйтесь, щоб змінити код", + "deleteCodeAuthMessage": "Авторизуйтесь, щоб видалити код", + "showQRAuthMessage": "Авторизуйтесь, щоб показати QR-код", "confirmAccountDeleteTitle": "Підтвердіть видалення облікового запису", "confirmAccountDeleteMessage": "Цей обліковий запис є зв'язаним з іншими програмами Ente, якщо ви використовуєте якісь з них.\n\nВаші завантажені дані у всіх програмах Ente будуть заплановані до видалення, а обліковий запис буде видалено назавжди.", "androidBiometricHint": "Підтвердити ідентифікацію", @@ -387,11 +391,11 @@ "@androidCancelButton": { "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." }, - "androidSignInTitle": "Необхідна аутентифікація", + "androidSignInTitle": "Необхідна автентифікація", "@androidSignInTitle": { "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." }, - "androidBiometricRequiredTitle": "Потрібна біометрична аутентифікація", + "androidBiometricRequiredTitle": "Потрібна біометрична автентифікація", "@androidBiometricRequiredTitle": { "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." }, @@ -407,7 +411,7 @@ "@goToSettings": { "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." }, - "androidGoToSettingsDescription": "Біометрична аутентифікація не налаштована на вашому пристрої. Перейдіть в 'Налаштування > Безпека', щоб додати біометричну аутентифікацію.", + "androidGoToSettingsDescription": "Біометрична автентифікація не налаштована на вашому пристрої. Перейдіть в «Налаштування > Безпека», щоб додати біометричну автентифікацію.", "@androidGoToSettingsDescription": { "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." }, @@ -415,7 +419,7 @@ "@iOSLockOut": { "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." }, - "iOSGoToSettingsDescription": "Біометрична аутентифікація не налаштована на вашому пристрої. Увімкніть TouchID або FaceID на вашому телефоні.", + "iOSGoToSettingsDescription": "Біометрична автентифікація не налаштована на вашому пристрої. Увімкніть TouchID або FaceID на вашому телефоні.", "@iOSGoToSettingsDescription": { "description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side." }, @@ -449,6 +453,7 @@ "unpinText": "Відкріпити", "pinnedCodeMessage": "{code} закріплено", "unpinnedCodeMessage": "{code} відкріплено", + "pinned": "Закріплено", "tags": "Мітки", "createNewTag": "Створити нову мітку", "tag": "Мітка", diff --git a/auth/lib/l10n/arb/app_vi.arb b/auth/lib/l10n/arb/app_vi.arb index f62d6ade58..ed1131ac2c 100644 --- a/auth/lib/l10n/arb/app_vi.arb +++ b/auth/lib/l10n/arb/app_vi.arb @@ -328,6 +328,10 @@ } } }, + "manualSort": "Tùy chỉnh", + "editOrder": "Chỉnh sửa đơn hàng", + "mostFrequentlyUsed": "Thường dùng", + "mostRecentlyUsed": "Dùng gần đây", "activeSessions": "Các phiên làm việc hiện tại", "somethingWentWrongPleaseTryAgain": "Phát hiện có lỗi, xin thử lại", "thisWillLogYouOutOfThisDevice": "Thao tác này sẽ đăng xuất bạn khỏi thiết bị này!", @@ -449,6 +453,7 @@ "unpinText": "Bỏ ghim", "pinnedCodeMessage": "{code} đã được ghim", "unpinnedCodeMessage": "{code} đã được bỏ ghim", + "pinned": "Đã ghim", "tags": "Thẻ", "createNewTag": "Tạo thẻ mới", "tag": "Thẻ", diff --git a/auth/lib/l10n/arb/app_zh.arb b/auth/lib/l10n/arb/app_zh.arb index 83e83f3b48..55520cfe55 100644 --- a/auth/lib/l10n/arb/app_zh.arb +++ b/auth/lib/l10n/arb/app_zh.arb @@ -328,6 +328,10 @@ } } }, + "manualSort": "自定义", + "editOrder": "编辑顺序", + "mostFrequentlyUsed": "经常使用", + "mostRecentlyUsed": "最近使用", "activeSessions": "已登录的设备", "somethingWentWrongPleaseTryAgain": "出了点问题,请重试", "thisWillLogYouOutOfThisDevice": "这将使您登出该设备!", @@ -449,6 +453,7 @@ "unpinText": "取消置顶", "pinnedCodeMessage": "{code} 已被置顶", "unpinnedCodeMessage": "{code} 已被取消置顶", + "pinned": "已置顶", "tags": "标签", "createNewTag": "创建新标签", "tag": "标签", diff --git a/auth/lib/models/all_icon_data.dart b/auth/lib/models/all_icon_data.dart new file mode 100644 index 0000000000..732667a262 --- /dev/null +++ b/auth/lib/models/all_icon_data.dart @@ -0,0 +1,15 @@ +enum IconType { simpleIcon, customIcon } + +class AllIconData { + final String title; + final IconType type; + final String? color; + final String? slug; + + AllIconData({ + required this.title, + required this.type, + required this.color, + this.slug, + }); +} diff --git a/auth/lib/models/code_display.dart b/auth/lib/models/code_display.dart index 71b74c68f5..6b3d6bb1df 100644 --- a/auth/lib/models/code_display.dart +++ b/auth/lib/models/code_display.dart @@ -12,6 +12,8 @@ class CodeDisplay { String note; final List tags; int position; + String iconSrc; + String iconID; CodeDisplay({ this.pinned = false, @@ -21,8 +23,12 @@ class CodeDisplay { this.tags = const [], this.note = '', this.position = 0, + this.iconSrc = '', + this.iconID = '', }); + bool get isCustomIcon => (iconSrc != '' && iconID != ''); + // copyWith CodeDisplay copyWith({ bool? pinned, @@ -32,6 +38,8 @@ class CodeDisplay { List? tags, String? note, int? position, + String? iconSrc, + String? iconID, }) { final bool updatedPinned = pinned ?? this.pinned; final bool updatedTrashed = trashed ?? this.trashed; @@ -40,6 +48,8 @@ class CodeDisplay { final List updatedTags = tags ?? this.tags; final String updatedNote = note ?? this.note; final int updatedPosition = position ?? this.position; + final String updatedIconSrc = iconSrc ?? this.iconSrc; + final String updatedIconID = iconID ?? this.iconID; return CodeDisplay( pinned: updatedPinned, @@ -49,6 +59,8 @@ class CodeDisplay { tags: updatedTags, note: updatedNote, position: updatedPosition, + iconSrc: updatedIconSrc, + iconID: updatedIconID, ); } @@ -64,6 +76,8 @@ class CodeDisplay { tags: List.from(json['tags'] ?? []), note: json['note'] ?? '', position: json['position'] ?? 0, + iconSrc: json['iconSrc'] ?? 'ente', + iconID: json['iconID'] ?? '', ); } @@ -106,6 +120,8 @@ class CodeDisplay { 'tags': tags, 'note': note, 'position': position, + 'iconSrc': iconSrc, + 'iconID': iconID, }; } diff --git a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart index 0491f48bdc..4857221638 100644 --- a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart +++ b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/events/codes_updated_event.dart'; import "package:ente_auth/l10n/l10n.dart"; +import 'package:ente_auth/models/all_icon_data.dart'; import 'package:ente_auth/models/code.dart'; import 'package:ente_auth/models/code_display.dart'; import 'package:ente_auth/onboarding/model/tag_enums.dart'; @@ -13,7 +14,10 @@ import 'package:ente_auth/onboarding/view/common/tag_chip.dart'; import 'package:ente_auth/store/code_display_store.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; +import 'package:ente_auth/ui/components/custom_icon_widget.dart'; import 'package:ente_auth/ui/components/models/button_result.dart'; +import 'package:ente_auth/ui/custom_icon_page.dart'; +import 'package:ente_auth/ui/utils/icon_utils.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:ente_auth/utils/totp_util.dart'; @@ -42,6 +46,9 @@ class _SetupEnterSecretKeyPageState extends State { late List selectedTags = [...?widget.code?.display.tags]; List allTags = []; StreamSubscription? _streamSubscription; + bool isCustomIcon = false; + String _customIconID = ""; + late IconType _iconSrc; @override void initState() { @@ -81,6 +88,19 @@ class _SetupEnterSecretKeyPageState extends State { _limitTextLength(_accountController, _otherTextLimit); _limitTextLength(_secretController, _otherTextLimit); } + + isCustomIcon = widget.code?.display.isCustomIcon ?? false; + if (isCustomIcon) { + _customIconID = widget.code?.display.iconID ?? "ente"; + } else { + if (widget.code != null) { + _customIconID = widget.code!.issuer; + } + } + _iconSrc = widget.code?.display.iconSrc == "simpleIcon" + ? IconType.simpleIcon + : IconType.customIcon; + super.initState(); } @@ -280,9 +300,21 @@ class _SetupEnterSecretKeyPageState extends State { ), ], ), - const SizedBox( - height: 40, - ), + const SizedBox(height: 32), + if (widget.code != null) + CustomIconWidget(iconData: _customIconID), + const SizedBox(height: 24), + if (widget.code != null) + GestureDetector( + onTap: () async { + await navigateToCustomIconPage(); + }, + child: Text( + "Change Icon", + style: getEnteTextTheme(context).small, + ), + ), + const SizedBox(height: 40), SizedBox( width: 400, child: OutlinedButton( @@ -324,6 +356,11 @@ class _SetupEnterSecretKeyPageState extends State { widget.code?.display.copyWith(tags: selectedTags) ?? CodeDisplay(tags: selectedTags); display.note = notes; + + display.iconID = _customIconID.toLowerCase(); + display.iconSrc = + _iconSrc == IconType.simpleIcon ? 'simpleIcon' : 'customIcon'; + if (widget.code != null && widget.code!.secret != secret) { ButtonResult? result = await showChoiceActionSheet( context, @@ -373,4 +410,28 @@ class _SetupEnterSecretKeyPageState extends State { message ?? context.l10n.pleaseVerifyDetails, ); } + + Future navigateToCustomIconPage() async { + final allIcons = IconUtils.instance.getAllIcons(); + String currentIcon; + if (widget.code!.display.isCustomIcon) { + currentIcon = widget.code!.display.iconID; + } else { + currentIcon = widget.code!.issuer; + } + final AllIconData newCustomIcon = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return CustomIconPage( + currentIcon: currentIcon, + allIcons: allIcons, + ); + }, + ), + ); + setState(() { + _customIconID = newCustomIcon.title; + _iconSrc = newCustomIcon.type; + }); + } } diff --git a/auth/lib/services/deduplication_service.dart b/auth/lib/services/deduplication_service.dart new file mode 100644 index 0000000000..5ca4b044d7 --- /dev/null +++ b/auth/lib/services/deduplication_service.dart @@ -0,0 +1,56 @@ +import 'package:ente_auth/models/code.dart'; +import 'package:ente_auth/store/code_store.dart'; +import 'package:logging/logging.dart'; + +class DeduplicationService { + final _logger = Logger("DeduplicationService"); + + DeduplicationService._privateConstructor(); + + static final DeduplicationService instance = + DeduplicationService._privateConstructor(); + + Future> getDuplicateCodes() async { + try { + final List result = await _getDuplicateCodes(); + return result; + } catch (e, s) { + _logger.severe("failed to get dedupeCode", e, s); + rethrow; + } + } + + Future> _getDuplicateCodes() async { + final codes = await CodeStore.instance.getAllCodes(); + final List duplicateCodes = []; + Map> uniqueCodes = {}; + + for (final code in codes) { + if (code.hasError || code.isTrashed) continue; + + final uniqueKey = "${code.secret}_${code.issuer}_${code.account}"; + + if (uniqueCodes.containsKey(uniqueKey)) { + uniqueCodes[uniqueKey]!.add(code); + } else { + uniqueCodes[uniqueKey] = [code]; + } + } + for (final key in uniqueCodes.keys) { + if (uniqueCodes[key]!.length > 1) { + duplicateCodes.add(DuplicateCodes(key, uniqueCodes[key]!)); + } + } + return duplicateCodes; + } +} + +class DuplicateCodes { + String hash; + final List codes; + + DuplicateCodes( + this.hash, + this.codes, + ); +} diff --git a/auth/lib/ui/code_widget.dart b/auth/lib/ui/code_widget.dart index 16c82c848b..c04feddfb0 100644 --- a/auth/lib/ui/code_widget.dart +++ b/auth/lib/ui/code_widget.dart @@ -445,13 +445,19 @@ class _CodeWidgetState extends State { } Widget _getIcon() { + final String iconData; + if (widget.code.display.isCustomIcon) { + iconData = widget.code.display.iconID; + } else { + iconData = widget.code.issuer; + } return Padding( padding: _shouldShowLargeIcon ? EdgeInsets.only(left: widget.isCompactMode ? 12 : 16) : const EdgeInsets.all(0), child: IconUtils.instance.getIcon( context, - safeDecode(widget.code.issuer).trim(), + safeDecode(iconData).trim(), width: widget.isCompactMode ? (_shouldShowLargeIcon ? 32 : 24) : (_shouldShowLargeIcon ? 42 : 24), diff --git a/auth/lib/ui/components/custom_icon_widget.dart b/auth/lib/ui/components/custom_icon_widget.dart new file mode 100644 index 0000000000..4825ddff60 --- /dev/null +++ b/auth/lib/ui/components/custom_icon_widget.dart @@ -0,0 +1,37 @@ +import 'package:ente_auth/theme/ente_theme.dart'; +import 'package:ente_auth/ui/utils/icon_utils.dart'; +import 'package:ente_auth/utils/totp_util.dart'; +import 'package:flutter/material.dart'; + +class CustomIconWidget extends StatelessWidget { + final String iconData; + + CustomIconWidget({ + super.key, + required this.iconData, + }); + + @override + Widget build(BuildContext context) { + return Container( + width: 70, + height: 70, + decoration: BoxDecoration( + border: Border.all( + width: 1.5, + color: getEnteColorScheme(context).tagChipSelectedColor, + ), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + ), + padding: const EdgeInsets.all(8), + child: FittedBox( + fit: BoxFit.contain, + child: IconUtils.instance.getIcon( + context, + safeDecode(iconData).trim(), + width: 50, + ), + ), + ); + } +} diff --git a/auth/lib/ui/custom_icon_page.dart b/auth/lib/ui/custom_icon_page.dart new file mode 100644 index 0000000000..01edbea9be --- /dev/null +++ b/auth/lib/ui/custom_icon_page.dart @@ -0,0 +1,216 @@ +import 'package:ente_auth/l10n/l10n.dart'; +import 'package:ente_auth/models/all_icon_data.dart'; +import 'package:ente_auth/services/preference_service.dart'; +import 'package:ente_auth/theme/ente_theme.dart'; +import 'package:ente_auth/ui/utils/icon_utils.dart'; +import 'package:flutter/material.dart'; + +class CustomIconPage extends StatefulWidget { + final Map allIcons; + final String currentIcon; + + const CustomIconPage({ + super.key, + required this.allIcons, + required this.currentIcon, + }); + + @override + State createState() => _CustomIconPageState(); +} + +class _CustomIconPageState extends State { + Map _filteredIcons = {}; + bool _showSearchBox = false; + final bool _autoFocusSearch = + PreferenceService.instance.shouldAutoFocusOnSearchBar(); + final TextEditingController _textController = TextEditingController(); + String _searchText = ""; + + // Used to request focus on the search box when clicked the search icon + late FocusNode searchBoxFocusNode; + + @override + void initState() { + _filteredIcons = widget.allIcons; + _showSearchBox = _autoFocusSearch; + searchBoxFocusNode = FocusNode(); + super.initState(); + } + + @override + void dispose() { + _textController.dispose(); + searchBoxFocusNode.dispose(); + super.dispose(); + } + + void _applyFilteringAndRefresh() { + if (_searchText.isEmpty) { + setState(() { + _filteredIcons = widget.allIcons; + }); + return; + } + + final filteredIcons = {}; + widget.allIcons.forEach((title, iconData) { + if (title.toLowerCase().contains(_searchText.toLowerCase())) { + filteredIcons[title] = iconData; + } + }); + + setState(() { + _filteredIcons = filteredIcons; + }); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return Scaffold( + appBar: AppBar( + title: !_showSearchBox + ? const Text('Custom Branding') + : TextField( + autocorrect: false, + enableSuggestions: false, + autofocus: _autoFocusSearch, + controller: _textController, + onChanged: (value) { + _searchText = value; + _applyFilteringAndRefresh(); + }, + decoration: InputDecoration( + hintText: l10n.searchHint, + border: InputBorder.none, + focusedBorder: InputBorder.none, + ), + focusNode: searchBoxFocusNode, + ), + actions: [ + IconButton( + icon: _showSearchBox + ? const Icon(Icons.clear) + : const Icon(Icons.search), + tooltip: "Search", + onPressed: () { + setState( + () { + _showSearchBox = !_showSearchBox; + if (!_showSearchBox) { + _textController.clear(); + _searchText = ""; + } else { + _searchText = _textController.text; + + // Request focus on the search box + searchBoxFocusNode.requestFocus(); + } + _applyFilteringAndRefresh(); + }, + ); + }, + ), + ], + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Expanded( + child: GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: (MediaQuery.sizeOf(context).width ~/ 90) + .clamp(1, double.infinity) + .toInt(), + crossAxisSpacing: 14, + mainAxisSpacing: 14, + childAspectRatio: 1, + ), + itemCount: _filteredIcons.length, + itemBuilder: (context, index) { + final title = _filteredIcons.keys.elementAt(index); + final iconData = _filteredIcons[title]!; + IconType iconType = iconData.type; + String? color = iconData.color; + String? slug = iconData.slug; + + Widget iconWidget; + if (iconType == IconType.simpleIcon) { + iconWidget = IconUtils.instance.getSVGIcon( + "assets/simple-icons/icons/$title.svg", + title, + color, + 40, + context, + ); + } else { + iconWidget = IconUtils.instance.getSVGIcon( + "assets/custom-icons/icons/${slug ?? title}.svg", + title, + color, + 40, + context, + ); + } + + return GestureDetector( + onTap: () { + final newIcon = AllIconData( + title: title, + type: iconType, + color: color, + slug: slug, + ); + Navigator.of(context).pop(newIcon); + }, + child: Container( + decoration: BoxDecoration( + border: Border.all( + width: 1.5, + color: title.toLowerCase() == + widget.currentIcon.toLowerCase() + ? getEnteColorScheme(context) + .tagChipSelectedColor + : Colors.transparent, + ), + borderRadius: const BorderRadius.all( + Radius.circular(12.0), + ), + ), + child: Column( + children: [ + const SizedBox(height: 8), + Expanded( + child: iconWidget, + ), + const SizedBox(height: 12), + Padding( + padding: title.toLowerCase() == + widget.currentIcon.toLowerCase() + ? const EdgeInsets.only(left: 2, right: 2) + : const EdgeInsets.all(0.0), + child: Text( + '${title[0].toUpperCase()}${title.substring(1)}', + style: getEnteTextTheme(context).mini, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + const SizedBox(height: 4), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/auth/lib/ui/home_page.dart b/auth/lib/ui/home_page.dart index e18cfa45ae..1a2d0a83a7 100644 --- a/auth/lib/ui/home_page.dart +++ b/auth/lib/ui/home_page.dart @@ -217,10 +217,11 @@ class _HomePageState extends State { void sortFilteredCodes(List codes, CodeSortKey sortKey) { switch (sortKey) { case CodeSortKey.issuerName: - codes.sort((a, b) => a.issuer.compareTo(b.issuer)); + codes.sort((a, b) => compareAsciiLowerCaseNatural(a.issuer, b.issuer)); break; case CodeSortKey.accountName: - codes.sort((a, b) => a.account.compareTo(b.account)); + codes + .sort((a, b) => compareAsciiLowerCaseNatural(a.account, b.account)); break; case CodeSortKey.mostFrequentlyUsed: codes.sort((a, b) => b.display.tapCount.compareTo(a.display.tapCount)); diff --git a/auth/lib/ui/reorder_codes_page.dart b/auth/lib/ui/reorder_codes_page.dart index 7d79949b0f..4dd693e8c3 100644 --- a/auth/lib/ui/reorder_codes_page.dart +++ b/auth/lib/ui/reorder_codes_page.dart @@ -3,6 +3,7 @@ import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/models/code.dart'; import 'package:ente_auth/services/preference_service.dart'; import 'package:ente_auth/store/code_store.dart'; +import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/code_widget.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; @@ -16,71 +17,71 @@ class ReorderCodesPage extends StatefulWidget { } class _ReorderCodesPageState extends State { - int selectedSortOption = 2; + bool hasChanged = false; final logger = Logger('ReorderCodesPage'); @override Widget build(BuildContext context) { final bool isCompactMode = PreferenceService.instance.isCompactMode(); - return PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, result) async { - if (!didPop) { - final hasSaved = await saveUpadedIndexes(); - if (hasSaved) { + return Scaffold( + appBar: AppBar( + title: const Text("Custom order"), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () async { Navigator.of(context).pop(); - } - } - }, - child: Scaffold( - appBar: AppBar( - title: Text(context.l10n.editOrder), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () async { + }, + ), + actions: [ + GestureDetector( + onTap: () async { final hasSaved = await saveUpadedIndexes(); if (hasSaved) { Navigator.of(context).pop(); } }, + child: Padding( + padding: const EdgeInsets.only(right: 20), + child: Text( + context.l10n.save, + style: TextStyle( + color: hasChanged + ? getEnteColorScheme(context).textBase + : getEnteColorScheme(context).strokeMuted, + ), + ), + ), ), - ), - body: ReorderableListView( - buildDefaultDragHandles: false, - proxyDecorator: - (Widget child, int index, Animation animation) { - return AnimatedBuilder( - animation: animation, - builder: (BuildContext context, _) { - final animValue = Curves.easeInOut.transform(animation.value); - final scale = lerpDouble(1, 1.05, animValue)!; - return Transform.scale(scale: scale, child: child); - }, - ); - }, - children: [ - for (final code in widget.codes) - selectedSortOption == 2 - ? ReorderableDragStartListener( - key: ValueKey('${code.hashCode}_${code.generatedID}'), - index: widget.codes.indexOf(code), - child: CodeWidget( - key: ValueKey(code.generatedID), - code, - isCompactMode: isCompactMode, - ), - ) - : CodeWidget( - key: ValueKey('${code.hashCode}_${code.generatedID}'), - code, - isCompactMode: isCompactMode, - ), - ], - onReorder: (oldIndex, newIndex) { - if (selectedSortOption == 2) updateCodeIndex(oldIndex, newIndex); - }, - ), + ], + ), + body: ReorderableListView( + buildDefaultDragHandles: false, + proxyDecorator: (Widget child, int index, Animation animation) { + return AnimatedBuilder( + animation: animation, + builder: (BuildContext context, _) { + final animValue = Curves.easeInOut.transform(animation.value); + final scale = lerpDouble(1, 1.05, animValue)!; + return Transform.scale(scale: scale, child: child); + }, + ); + }, + children: [ + for (final code in widget.codes) + ReorderableDragStartListener( + key: ValueKey('${code.hashCode}_${code.generatedID}'), + index: widget.codes.indexOf(code), + child: CodeWidget( + key: ValueKey(code.generatedID), + code, + isCompactMode: isCompactMode, + ), + ), + ], + onReorder: (oldIndex, newIndex) { + updateCodeIndex(oldIndex, newIndex); + }, ), ); } @@ -97,6 +98,7 @@ class _ReorderCodesPageState extends State { if (oldIndex < newIndex) newIndex -= 1; final Code code = widget.codes.removeAt(oldIndex); widget.codes.insert(newIndex, code); + hasChanged = true; }); } } diff --git a/auth/lib/ui/settings/data/data_section_widget.dart b/auth/lib/ui/settings/data/data_section_widget.dart index f32739d239..5665d58532 100644 --- a/auth/lib/ui/settings/data/data_section_widget.dart +++ b/auth/lib/ui/settings/data/data_section_widget.dart @@ -1,11 +1,16 @@ +import 'dart:async'; + import 'package:ente_auth/l10n/l10n.dart'; +import 'package:ente_auth/services/deduplication_service.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/components/captioned_text_widget.dart'; import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart'; import 'package:ente_auth/ui/components/menu_item_widget.dart'; import 'package:ente_auth/ui/settings/common_settings.dart'; +import 'package:ente_auth/ui/settings/data/duplicate_code_page.dart'; import 'package:ente_auth/ui/settings/data/export_widget.dart'; import 'package:ente_auth/ui/settings/data/import_page.dart'; +import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; import 'package:flutter/material.dart'; @@ -53,6 +58,33 @@ class DataSectionWidget extends StatelessWidget { }, ), sectionOptionSpacing, + MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: l10n.duplicateCodes, + ), + pressedColor: getEnteColorScheme(context).fillFaint, + trailingIcon: Icons.chevron_right_outlined, + trailingIconIsMuted: true, + onTap: () async { + final List duplicateCodes = + await DeduplicationService.instance.getDuplicateCodes(); + if (duplicateCodes.isEmpty) { + unawaited( + showErrorDialog( + context, + l10n.noDuplicates, + l10n.youveNoDuplicateCodesThatCanBeCleared, + ), + ); + return; + } + await routeToPage( + context, + DuplicateCodePage(duplicateCodes: duplicateCodes), + ); + }, + ), + sectionOptionSpacing, ]); return Column( children: children, diff --git a/auth/lib/ui/settings/data/duplicate_code_page.dart b/auth/lib/ui/settings/data/duplicate_code_page.dart new file mode 100644 index 0000000000..9de8695a52 --- /dev/null +++ b/auth/lib/ui/settings/data/duplicate_code_page.dart @@ -0,0 +1,259 @@ +import 'package:ente_auth/l10n/l10n.dart'; +import 'package:ente_auth/models/code.dart'; +import 'package:ente_auth/services/deduplication_service.dart'; +import 'package:ente_auth/services/local_authentication_service.dart'; +import 'package:ente_auth/store/code_store.dart'; +import 'package:ente_auth/theme/ente_theme.dart'; +import 'package:ente_auth/ui/code_widget.dart'; +import 'package:ente_auth/utils/dialog_util.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:logging/logging.dart'; + +class DuplicateCodePage extends StatefulWidget { + final List duplicateCodes; + const DuplicateCodePage({ + super.key, + required this.duplicateCodes, + }); + + @override + State createState() => _DuplicateCodePageState(); +} + +class _DuplicateCodePageState extends State { + final Logger _logger = Logger("DuplicateCodePage"); + late List _duplicateCodes; + final Set selectedGrids = {}; + + @override + void initState() { + _duplicateCodes = widget.duplicateCodes; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.l10n.deduplicateCodes), + elevation: 0, + ), + body: _getBody(), + ); + } + + Widget _getBody() { + final l10n = context.l10n; + return SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 4, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + GestureDetector( + onTap: () { + if (selectedGrids.length == _duplicateCodes.length) { + _removeAllGrids(); + } else { + _selectAllGrids(); + } + setState(() {}); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + selectedGrids.length == _duplicateCodes.length + ? l10n.deselectAll + : l10n.selectAll, + style: + Theme.of(context).textTheme.titleMedium!.copyWith( + fontSize: 14, + color: Theme.of(context) + .iconTheme + .color! + .withOpacity(0.7), + ), + ), + const Padding(padding: EdgeInsets.only(left: 4)), + selectedGrids.length == _duplicateCodes.length + ? const Icon( + Icons.check_circle, + size: 24, + ) + : Icon( + Icons.check_circle_outlined, + color: getEnteColorScheme(context).strokeMuted, + size: 24, + ), + ], + ), + ), + ], + ), + ), + ), + const SizedBox(height: 8), + Expanded( + child: ListView.builder( + itemCount: _duplicateCodes.length, + shrinkWrap: true, + itemBuilder: (context, index) { + final List codes = _duplicateCodes[index].codes; + return _getGridView( + codes, + index, + ); + }, + ), + ), + selectedGrids.isEmpty ? const SizedBox.shrink() : _getDeleteButton(), + ], + ), + ); + } + + Widget _getGridView(List code, int itemIndex) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 4, + ), + child: GestureDetector( + onTap: () { + if (selectedGrids.contains(itemIndex)) { + selectedGrids.remove(itemIndex); + } else { + selectedGrids.add(itemIndex); + } + setState(() {}); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "${code[0].issuer}, ${code.length} items", + ), + !selectedGrids.contains(itemIndex) + ? Icon( + Icons.check_circle_outlined, + color: getEnteColorScheme(context).strokeMuted, + size: 24, + ) + : const Icon( + Icons.check_circle, + size: 24, + ), + ], + ), + ), + ), + AlignedGridView.count( + crossAxisCount: (MediaQuery.sizeOf(context).width ~/ 400) + .clamp(1, double.infinity) + .toInt(), + padding: const EdgeInsets.only(bottom: 40), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return CodeWidget( + key: ValueKey('${code.hashCode}_$index'), + code[index], + isCompactMode: false, + ); + }, + itemCount: code.length, + ), + ], + ); + } + + Widget _getDeleteButton() { + int selectedItemsCount = 0; + for (int idx = 0; idx < _duplicateCodes.length; idx++) { + if (selectedGrids.contains(idx)) { + selectedItemsCount += _duplicateCodes[idx].codes.length - 1; + } + } + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 20), + child: SizedBox( + width: 400, + child: OutlinedButton( + onPressed: () async { + await deleteDuplicates(selectedItemsCount); + }, + child: Text( + "Delete $selectedItemsCount items", + ), + ), + ), + ); + } + + void _selectAllGrids() { + selectedGrids.clear(); + for (int idx = 0; idx < _duplicateCodes.length; idx++) { + selectedGrids.add(idx); + } + } + + void _removeAllGrids() { + selectedGrids.clear(); + } + + Future deleteDuplicates(int itemCount) async { + bool isAuthSuccessful = + await LocalAuthenticationService.instance.requestLocalAuthentication( + context, + context.l10n.deleteCodeAuthMessage, + ); + if (!isAuthSuccessful) { + return; + } + FocusScope.of(context).requestFocus(); + final l10n = context.l10n; + final String message = "Are you sure you want to trash $itemCount items?"; + await showChoiceActionSheet( + context, + title: l10n.deleteDuplicates, + body: message, + firstButtonLabel: l10n.trash, + isCritical: true, + firstButtonOnTap: () async { + try { + for (int idx = 0; idx < _duplicateCodes.length; idx++) { + if (selectedGrids.contains(idx)) { + final List codes = _duplicateCodes[idx].codes; + for (int i = 1; i < codes.length; i++) { + final display = codes[i].display; + final Code code = codes[i].copyWith( + display: display.copyWith(trashed: true), + ); + await CodeStore.instance.addCode(code); + } + } + } + Navigator.of(context).pop(); + } catch (e) { + _logger.severe('Failed to trash duplicate codes: ${e.toString()}'); + showGenericErrorDialog(context: context, error: e).ignore(); + } + }, + ); + } +} diff --git a/auth/lib/ui/settings/data/export_widget.dart b/auth/lib/ui/settings/data/export_widget.dart index 0df7482898..6d32424d3f 100644 --- a/auth/lib/ui/settings/data/export_widget.dart +++ b/auth/lib/ui/settings/data/export_widget.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'dart:io'; - import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/models/export/ente.dart'; @@ -9,16 +8,15 @@ import 'package:ente_auth/store/code_store.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/components/dialog_widget.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; +import 'package:ente_auth/ui/settings/data/html_export.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/share_utils.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:file_saver/file_saver.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:logging/logging.dart'; import 'package:share_plus/share_plus.dart'; Future handleExportClick(BuildContext context) async { @@ -41,13 +39,22 @@ Future handleExportClick(BuildContext context) async { isInAlert: true, buttonAction: ButtonAction.second, ), + const ButtonWidget( + buttonType: ButtonType.secondary, + labelText: "HTML", + buttonSize: ButtonSize.large, + isInAlert: true, + buttonAction: ButtonAction.third, + ), ], ); if (result?.action != null && result!.action != ButtonAction.cancel) { if (result.action == ButtonAction.first) { await _requestForEncryptionPassword(context); - } else { - await _showExportWarningDialog(context); + } else if (result.action == ButtonAction.second) { + await _showExportWarningDialog(context, "txt"); + } else if (result.action == ButtonAction.third) { + await _showExportWarningDialog(context, "html"); } } } @@ -98,9 +105,8 @@ Future _requestForEncryptionPassword( ), ); // get json value of data - await _exportCodes(context, jsonEncode(data.toJson())); - } catch (e, s) { - Logger("ExportWidget").severe(e, s); + await _exportCodes(context, jsonEncode(data.toJson()), "txt"); + } catch (e) { showToast(context, "Error while exporting codes."); } } @@ -108,26 +114,34 @@ Future _requestForEncryptionPassword( ); } -Future _showExportWarningDialog(BuildContext context) async { +Future _showExportWarningDialog(BuildContext context, String type) async { await showChoiceActionSheet( context, title: context.l10n.warning, body: context.l10n.exportWarningDesc, isCritical: true, firstButtonOnTap: () async { - final data = await _getAuthDataForExport(); - await _exportCodes(context, data); + if (type == "html") { + final data = await generateHtml(context); + await _exportCodes(context, data, type); + } else { + final data = await _getAuthDataForExport(); + await _exportCodes(context, data, type); + } }, secondButtonLabel: context.l10n.cancel, firstButtonLabel: context.l10n.iUnderStand, ); } -Future _exportCodes(BuildContext context, String fileContent) async { +Future _exportCodes( + BuildContext context, + String fileContent, + String extension, +) async { DateTime now = DateTime.now().toUtc(); String formattedDate = DateFormat('yyyy-MM-dd').format(now); String exportFileName = 'ente-auth-codes-$formattedDate'; - String exportFileExtension = 'txt'; final hasAuthenticated = await LocalAuthenticationService.instance .requestLocalAuthentication(context, context.l10n.authToExportCodes); await PlatformUtil.refocusWindows(); @@ -142,14 +156,14 @@ Future _exportCodes(BuildContext context, String fileContent) async { saveAction: () async { await PlatformUtil.shareFile( exportFileName, - exportFileExtension, + extension, CryptoUtil.strToBin(fileContent), MimeType.text, ); }, sendAction: () async { final codeFile = File( - "${Configuration.instance.getTempDirectory()}$exportFileName.$exportFileExtension", + "${Configuration.instance.getTempDirectory()}$exportFileName.$extension", ); if (codeFile.existsSync()) { await codeFile.delete(); diff --git a/auth/lib/ui/settings/data/html_export.dart b/auth/lib/ui/settings/data/html_export.dart new file mode 100644 index 0000000000..705bd8507c --- /dev/null +++ b/auth/lib/ui/settings/data/html_export.dart @@ -0,0 +1,236 @@ +import 'dart:convert'; +import 'dart:ui' as ui; + +import 'package:ente_auth/models/code.dart'; +import 'package:ente_auth/store/code_store.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:qr_flutter/qr_flutter.dart'; + +Future generateQRImageBase64(String data) async { + final qrPainter = QrPainter( + data: data, + version: QrVersions.auto, + eyeStyle: const QrEyeStyle( + eyeShape: QrEyeShape.square, + color: Colors.black, + ), + dataModuleStyle: const QrDataModuleStyle( + dataModuleShape: QrDataModuleShape.square, + color: Colors.black, + ), + ); + + const size = 250.0; + final recorder = ui.PictureRecorder(); + final canvas = Canvas(recorder); + qrPainter.paint(canvas, const Size(size, size)); + final picture = recorder.endRecording(); + final img = await picture.toImage(size.toInt(), size.toInt()); + final byteData = await img.toByteData(format: ui.ImageByteFormat.png); + final pngBytes = byteData!.buffer.asUint8List(); + + return base64Encode(pngBytes); +} + +Future generateOTPEntryHtml( + Code code, + BuildContext context, +) async { + final qrBase64 = await generateQRImageBase64(code.rawData); + String notes = code.display.note; + if (notes.isNotEmpty) { + notes = '

Note: $notes

'; + } + return ''' +
+
+

${code.issuer}

+

${code.account}

+
+

Type: ${code.type.name}

+

Algorithm: ${code.algorithm.name}

+

Digits: ${code.digits}

+

Recovery Code: ${code.secret}

+ $notes +
+ QR Code +
+
+
+
+ '''; +} + +Future generateHtml(BuildContext context) async { + DateTime now = DateTime.now().toUtc(); + String formattedDate = DateFormat('d MMMM, yyyy').format(now); + final allCodes = await CodeStore.instance.getAllCodes(); + final List enteries = []; + + for (final code in allCodes) { + if (code.hasError) continue; + final entry = await generateOTPEntryHtml(code, context); + enteries.add(entry); + } + + return ''' + + + + + + + +

Ente Auth

+

OTP Data Export

+

$formattedDate

+
 
+
+
+

+ ${enteries.join('')} +

+
+
+
+ + + + + + '''; +} diff --git a/auth/lib/ui/settings/theme_switch_widget.dart b/auth/lib/ui/settings/theme_switch_widget.dart index c0495b65b6..0220bf795a 100644 --- a/auth/lib/ui/settings/theme_switch_widget.dart +++ b/auth/lib/ui/settings/theme_switch_widget.dart @@ -1,14 +1,12 @@ - - import 'package:adaptive_theme/adaptive_theme.dart'; import 'package:ente_auth/ente_theme_data.dart'; +import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/components/captioned_text_widget.dart'; import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart'; import 'package:ente_auth/ui/components/menu_item_widget.dart'; import 'package:ente_auth/ui/settings/common_settings.dart'; import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; class ThemeSwitchWidget extends StatefulWidget { const ThemeSwitchWidget({super.key}); @@ -42,7 +40,7 @@ class _ThemeSwitchWidgetState extends State { @override Widget build(BuildContext context) { return ExpandableMenuItemWidget( - title: "Theme", + title: context.l10n.theme, selectionOptionsWidget: _getSectionOptions(context), leadingIcon: Theme.of(context).brightness == Brightness.light ? Icons.light_mode_outlined @@ -64,10 +62,21 @@ class _ThemeSwitchWidgetState extends State { ); } + String _name(BuildContext ctx, AdaptiveThemeMode mode) { + switch (mode) { + case AdaptiveThemeMode.light: + return ctx.l10n.lightTheme; + case AdaptiveThemeMode.dark: + return ctx.l10n.darkTheme; + case AdaptiveThemeMode.system: + return ctx.l10n.systemTheme; + } + } + Widget _menuItem(BuildContext context, AdaptiveThemeMode themeMode) { return MenuItemWidget( captionedTextWidget: CaptionedTextWidget( - title: toBeginningOfSentenceCase(themeMode.name)!, + title: _name(context, themeMode), textStyle: Theme.of(context).colorScheme.enteTheme.textTheme.body, ), pressedColor: getEnteColorScheme(context).fillFaint, diff --git a/auth/lib/ui/utils/icon_utils.dart b/auth/lib/ui/utils/icon_utils.dart index cc05787401..76896a7b36 100644 --- a/auth/lib/ui/utils/icon_utils.dart +++ b/auth/lib/ui/utils/icon_utils.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:ente_auth/ente_theme_data.dart'; +import 'package:ente_auth/models/all_icon_data.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -24,6 +25,80 @@ class IconUtils { await _loadJson(); } + Map getAllIcons() { + Set processedIconPaths = {}; + final allIcons = {}; + + final simpleIterator = _simpleIcons.entries.iterator; + final customIterator = _customIcons.entries.iterator; + + var simpleEntry = simpleIterator.moveNext() ? simpleIterator.current : null; + var customEntry = customIterator.moveNext() ? customIterator.current : null; + + String simpleIconPath, customIconPath; + + while (simpleEntry != null && customEntry != null) { + if (simpleEntry.key.compareTo(customEntry.key) <= 0) { + simpleIconPath = "assets/simple-icons/icons/${simpleEntry.key}.svg"; + if (!processedIconPaths.contains(simpleIconPath)) { + allIcons[simpleEntry.key] = AllIconData( + title: simpleEntry.key, + type: IconType.simpleIcon, + color: simpleEntry.value, + ); + processedIconPaths.add(simpleIconPath); + } + simpleEntry = simpleIterator.moveNext() ? simpleIterator.current : null; + } else { + customIconPath = + "assets/custom-icons/icons/${customEntry.value.slug ?? customEntry.key}.svg"; + + if (!processedIconPaths.contains(customIconPath)) { + allIcons[customEntry.key] = AllIconData( + title: customEntry.key, + type: IconType.customIcon, + color: customEntry.value.color, + slug: customEntry.value.slug, + ); + processedIconPaths.add(customIconPath); + } + customEntry = customIterator.moveNext() ? customIterator.current : null; + } + } + + while (simpleEntry != null) { + simpleIconPath = "assets/simple-icons/icons/${simpleEntry.key}.svg"; + + if (!processedIconPaths.contains(simpleIconPath)) { + allIcons[simpleEntry.key] = AllIconData( + title: simpleEntry.key, + type: IconType.simpleIcon, + color: simpleEntry.value, + ); + processedIconPaths.add(simpleIconPath); + } + simpleEntry = simpleIterator.moveNext() ? simpleIterator.current : null; + } + + while (customEntry != null) { + customIconPath = + "assets/custom-icons/icons/${customEntry.value.slug ?? customEntry.key}.svg"; + + if (!processedIconPaths.contains(customIconPath)) { + allIcons[customEntry.key] = AllIconData( + title: customEntry.key, + type: IconType.customIcon, + color: customEntry.value.color, + slug: customEntry.value.slug, + ); + processedIconPaths.add(customIconPath); + } + customEntry = customIterator.moveNext() ? customIterator.current : null; + } + + return allIcons; + } + Widget getIcon( BuildContext context, String provider, { @@ -38,7 +113,7 @@ class IconUtils { ); for (final title in titlesList) { if (_customIcons.containsKey(title)) { - return _getSVGIcon( + return getSVGIcon( "assets/custom-icons/icons/${_customIcons[title]!.slug ?? title}.svg", title, _customIcons[title]!.color, @@ -46,7 +121,7 @@ class IconUtils { context, ); } else if (_simpleIcons.containsKey(title)) { - return _getSVGIcon( + return getSVGIcon( "assets/simple-icons/icons/$title.svg", title, _simpleIcons[title], @@ -75,7 +150,7 @@ class IconUtils { } } - Widget _getSVGIcon( + Widget getSVGIcon( String path, String title, String? color, diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index 4ff2b66abe..67e66c8ee9 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -1,6 +1,6 @@ name: ente_auth description: ente two-factor authenticator -version: 4.1.5+415 +version: 4.1.7+417 publish_to: none environment: diff --git a/auth/windows/runner/main.cpp b/auth/windows/runner/main.cpp index 11751936ca..4ba3a7dc1e 100644 --- a/auth/windows/runner/main.cpp +++ b/auth/windows/runner/main.cpp @@ -72,7 +72,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); - Win32Window::Point origin(10, 10); + Win32Window::Point origin(70, 70); Win32Window::Size size(1280, 720); if (!window.Create(L"Ente Auth", origin, size)) { diff --git a/desktop/src/main.ts b/desktop/src/main.ts index d187b1f795..68b9e45e06 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -11,7 +11,14 @@ import { nativeImage, shell } from "electron/common"; import type { WebContents } from "electron/main"; -import { BrowserWindow, Menu, Tray, app, protocol } from "electron/main"; +import { + BrowserWindow, + Menu, + Tray, + app, + dialog, + protocol, +} from "electron/main"; import serveNextAt from "next-electron-server"; import { existsSync } from "node:fs"; import fs from "node:fs/promises"; @@ -131,6 +138,7 @@ const main = () => { const webContents = mainWindow.webContents; setDownloadPath(webContents); allowExternalLinks(webContents); + handleBackOnStripeCheckout(mainWindow); allowAllCORSOrigins(webContents); // Start loading the renderer. @@ -502,6 +510,45 @@ const allowExternalLinks = (webContents: WebContents) => } }); +/** + * Handle back button presses on the Stripe checkout page. + * + * For payments, we show the Stripe checkout page to the user in the app's + * window. On this page there is a back button that allows the user to get back + * to the app's contents. Since we're not showing the browser controls, this is + * the only way to get back to the app. + * + * If the user enters something in the text fields on this page (e.g. if they + * start entering their credit card number), and then press back, then the + * browser shows the user a dialog asking them to confirm if they want to + * discard their unsaved changes. However, when running in the context of an + * Electron app, this dialog is not shown, and instead the app just gets stuck + * (the back button stops working, and quitting the app also doesn't work since + * there is an invisible modal dialog). + * + * So we instead intercept these back button presses, and show the same dialog + * that the browser would've shown. + */ +const handleBackOnStripeCheckout = (window: BrowserWindow) => + window.webContents.on("will-prevent-unload", (event) => { + const url = new URL(window.webContents.getURL()); + // Only intercept on Stripe checkout pages. + if (url.host != "checkout.stripe.com") return; + + // The dialog copy is similar to what Chrome would've shown. + // https://www.electronjs.org/docs/latest/api/web-contents#event-will-prevent-unload + const choice = dialog.showMessageBoxSync(window, { + type: "question", + buttons: ["Leave", "Stay"], + title: "Leave site?", + message: "Changes that you made may not be saved.", + defaultId: 0, + cancelId: 1, + }); + const leave = choice === 0; + if (leave) event.preventDefault(); + }); + /** * Allow uploads to arbitrary S3 buckets. * diff --git a/docs/docs/.vitepress/sidebar.ts b/docs/docs/.vitepress/sidebar.ts index 8d5df06965..fe95c35d53 100644 --- a/docs/docs/.vitepress/sidebar.ts +++ b/docs/docs/.vitepress/sidebar.ts @@ -39,6 +39,10 @@ export const sidebar = [ link: "/photos/features/free-up-space/", }, { text: "Hidden photos", link: "/photos/features/hide" }, + { + text: "Legacy", + link: "/photos/features/legacy/", + }, { text: "Location tags", link: "/photos/features/location-tags", diff --git a/docs/docs/photos/features/legacy/accept_trusted_contact_invite.png b/docs/docs/photos/features/legacy/accept_trusted_contact_invite.png new file mode 100644 index 0000000000..e52479be89 Binary files /dev/null and b/docs/docs/photos/features/legacy/accept_trusted_contact_invite.png differ diff --git a/docs/docs/photos/features/legacy/add_trusted_contact.png b/docs/docs/photos/features/legacy/add_trusted_contact.png new file mode 100644 index 0000000000..0d9a84e206 Binary files /dev/null and b/docs/docs/photos/features/legacy/add_trusted_contact.png differ diff --git a/docs/docs/photos/features/legacy/index.md b/docs/docs/photos/features/legacy/index.md new file mode 100644 index 0000000000..1afa987768 --- /dev/null +++ b/docs/docs/photos/features/legacy/index.md @@ -0,0 +1,50 @@ +--- +title: Legacy +description: Using Legacy to pass on your memories to loved ones +--- + +# Legacy + +Legacy allows trusted contacts to recover your account in your absence. The main usecase here is to pass on your memories after your death. It can also be useful for other cases - for e.g., when you forget your password and recovery key. + +Trusted Contacts can initiate a recovery, and if not blocked in 30 days, would be able to change the password to your account and thereby access your memories. + +## Adding a trusted contact + +You can add a trusted contact for your account using the mobile app for Ente Photos. Go to Settings -> Account -> Legacy, and click on "Add Trusted Contact". + +You would be asked to enter the email address of the trusted contact you want to add or choose from a list of contacts on Ente. Please note that the trusted contact must be an Ente user. + +
+ +![Add Trusted Contact](add_trusted_contact.png){width=300px} + +
+ +The trusted contact must accept your request. They can do so by going to Settings -> Account -> Legacy in the Ente Photos mobile app, and clicking on your email address within the Legacy accounts sections. Ente would also send an email notification to the trusted contact to nudge them to accept the invite. + +
+ +![Accept Trusted Contact Invite](accept_trusted_contact_invite.png){width=300px} + +
+ +## Recovering an account as a trusted contact + +As a trusted contact, you can recover an account by going to Settings -> Account -> Legacy in the Ente photos app, tapping on the email address of the account within the Legacy account sections. + +
+ +![Initiate Account Recovery](initiate_account_recovery.png){width=300px} + +
+ +Once the recovery is initiated, the account owner would get 30 days to block the recovery. After 30 days, you can go the same page in the app, where you will be prompted to change the password of the account. Once you change the password, you would be able to access the recovered account with the new password. + +## Blocking account recovery by a trusted contact + +After a trusted contact initiates a recover, you, as the account owner, would get 30 days to block the recovery. To do this, you must go to Settings -> Account -> Legacy, where you will see a message that recovery of the account has been initiated. Tapping on that will allow you to block the recovery. + +## Removing a trusted contact + +You can remove a trusted contact by going to Settings -> Account -> Legacy, tapping on the trusted contact you want to remove, and choosing "Remove" in the popup. \ No newline at end of file diff --git a/docs/docs/photos/features/legacy/initiate_account_recovery.png b/docs/docs/photos/features/legacy/initiate_account_recovery.png new file mode 100644 index 0000000000..4547217e5b Binary files /dev/null and b/docs/docs/photos/features/legacy/initiate_account_recovery.png differ diff --git a/docs/docs/photos/features/machine-learning.md b/docs/docs/photos/features/machine-learning.md index 38c06fd329..25063fcece 100644 --- a/docs/docs/photos/features/machine-learning.md +++ b/docs/docs/photos/features/machine-learning.md @@ -39,6 +39,10 @@ device. > desktop app first, because it can index your existing photos faster. Once your > existing photos have been indexed, then you can use either. The mobile app is > fast enough to index new photos as they are being backed up. +> +> Also, it is beneficial to enable machine learning before importing your +> photos, as this allows the Ente app to index your files as they are getting +> uploaded instead of needing to download them again. The indexes are synced across all your devices automatically using the same end-to-end encrypted security that we use for syncing your photos. diff --git a/docs/docs/photos/migration/from-google-photos/index.md b/docs/docs/photos/migration/from-google-photos/index.md index 0623f157d2..5351ccffc8 100644 --- a/docs/docs/photos/migration/from-google-photos/index.md +++ b/docs/docs/photos/migration/from-google-photos/index.md @@ -68,3 +68,10 @@ will ignore already backed up files and upload just the rest. If you run into any issues during this migration, please reach out to [support@ente.io](mailto:support@ente.io) and we will be happy to help you! + +> [!TIP] +> +> In case you wish to use face recognition and other advanced search features +> provided by Ente, we recommend that you enable [machine +> learning](/photos/features/machine-learning) before importing your photos so +> that the Ente app can directly index files as they are getting uploaded. diff --git a/mobile/assets/icons/legacy-dark.svg b/mobile/assets/icons/legacy-dark.svg new file mode 100644 index 0000000000..4c81fb7d16 --- /dev/null +++ b/mobile/assets/icons/legacy-dark.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/assets/icons/legacy-light.svg b/mobile/assets/icons/legacy-light.svg new file mode 100644 index 0000000000..ca4b5cba94 --- /dev/null +++ b/mobile/assets/icons/legacy-light.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/devtools_options.yaml b/mobile/devtools_options.yaml new file mode 100644 index 0000000000..fa0b357c4f --- /dev/null +++ b/mobile/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/mobile/fastlane/metadata/android/ml/short_description.txt b/mobile/fastlane/metadata/android/ml/short_description.txt new file mode 100644 index 0000000000..85ec7b2977 --- /dev/null +++ b/mobile/fastlane/metadata/android/ml/short_description.txt @@ -0,0 +1 @@ +എന്റേ - പൂർണമായും എൻക്രിപ്റ്റ് ചെയ്ത ചിത്രസംഭരണി \ No newline at end of file diff --git a/mobile/fastlane/metadata/android/ml/title.txt b/mobile/fastlane/metadata/android/ml/title.txt new file mode 100644 index 0000000000..489b6f2de9 --- /dev/null +++ b/mobile/fastlane/metadata/android/ml/title.txt @@ -0,0 +1 @@ +എന്റേ - എൻക്രിപ്റ്റ്ട് ചിത്രസംഭരണി \ No newline at end of file diff --git a/mobile/fastlane/metadata/ios/de/keywords.txt b/mobile/fastlane/metadata/ios/de/keywords.txt index 81f5497b83..71fa009192 100644 --- a/mobile/fastlane/metadata/ios/de/keywords.txt +++ b/mobile/fastlane/metadata/ios/de/keywords.txt @@ -1 +1 @@ -Fotografie,Familie,Privatsphäre,Cloud,Backup,Video,Foto,Verschlüsselung,Speicher,Album,Alternative +Fotos,Fotografie,Familie,Privatsphäre,Cloud,Backup,Videos,Foto,Verschlüsselung,Speicher,Album,Alternative diff --git a/mobile/fastlane/metadata/ios/ml/keywords.txt b/mobile/fastlane/metadata/ios/ml/keywords.txt new file mode 100644 index 0000000000..8ada08e67b --- /dev/null +++ b/mobile/fastlane/metadata/ios/ml/keywords.txt @@ -0,0 +1 @@ +ഫോട്ടോകൾ, ചിത്രങ്ങൾ, പടങ്ങൾ, ഫോട്ടോഗ്രാഫി, കുടുംബം, ഛായാഗ്രഹണം, സ്വകാര്യത, ക്ലൌഡ്, ബാക്കപ്പ്, വീഡിയോകൾ, ഫോട്ടോ, ചിത്രം, പടം, എൻക്രിപ്ഷൻ, സ്റ്റോറേജ്, ആൽബം, ബദൽ diff --git a/mobile/fastlane/metadata/ios/ml/name.txt b/mobile/fastlane/metadata/ios/ml/name.txt new file mode 100644 index 0000000000..7e158ee5c8 --- /dev/null +++ b/mobile/fastlane/metadata/ios/ml/name.txt @@ -0,0 +1 @@ +എന്റേ ചിത്രസംഭരണി diff --git a/mobile/fastlane/metadata/ios/ml/subtitle.txt b/mobile/fastlane/metadata/ios/ml/subtitle.txt new file mode 100644 index 0000000000..c7e81edb30 --- /dev/null +++ b/mobile/fastlane/metadata/ios/ml/subtitle.txt @@ -0,0 +1 @@ +എൻക്രിപ്റ്റ്ട് ചിത്രസംഭരണം diff --git a/mobile/fastlane/metadata/playstore/ml/title.txt b/mobile/fastlane/metadata/playstore/ml/title.txt new file mode 100644 index 0000000000..c87abe18ee --- /dev/null +++ b/mobile/fastlane/metadata/playstore/ml/title.txt @@ -0,0 +1 @@ +എന്റേ ചിത്രസംഭരണി \ No newline at end of file diff --git a/mobile/lib/core/error-reporting/super_logging.dart b/mobile/lib/core/error-reporting/super_logging.dart index 4f5e21cfc9..7c0bef58be 100644 --- a/mobile/lib/core/error-reporting/super_logging.dart +++ b/mobile/lib/core/error-reporting/super_logging.dart @@ -17,6 +17,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:photos/core/error-reporting/tunneled_transport.dart'; import "package:photos/core/errors.dart"; import 'package:photos/models/typedefs.dart'; +import "package:photos/utils/device_info.dart"; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -198,6 +199,12 @@ class SuperLogging { $.info("sentry uploader started"); } + unawaited( + getDeviceName().then((name) { + $.info("Device name: $name"); + }), + ); + if (appConfig.body == null) return; if (enable && sentryIsEnabled) { diff --git a/mobile/lib/emergency/emergency_page.dart b/mobile/lib/emergency/emergency_page.dart new file mode 100644 index 0000000000..04c60b7923 --- /dev/null +++ b/mobile/lib/emergency/emergency_page.dart @@ -0,0 +1,557 @@ +import "dart:async"; + +import "package:flutter/foundation.dart"; +import 'package:flutter/material.dart'; +import "package:flutter_svg/flutter_svg.dart"; +import 'package:photos/core/configuration.dart'; +import "package:photos/emergency/emergency_service.dart"; +import "package:photos/emergency/model.dart"; +import "package:photos/emergency/other_contact_page.dart"; +import "package:photos/emergency/select_contact_page.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/l10n/l10n.dart"; +import "package:photos/theme/colors.dart"; +import 'package:photos/theme/ente_theme.dart'; +import "package:photos/ui/common/loading_widget.dart"; +import "package:photos/ui/components/action_sheet_widget.dart"; +import "package:photos/ui/components/buttons/button_widget.dart"; +import 'package:photos/ui/components/captioned_text_widget.dart'; +import 'package:photos/ui/components/divider_widget.dart'; +import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart'; +import 'package:photos/ui/components/menu_section_title.dart'; +import "package:photos/ui/components/models/button_type.dart"; +import "package:photos/ui/components/notification_widget.dart"; +import 'package:photos/ui/components/title_bar_title_widget.dart'; +import 'package:photos/ui/components/title_bar_widget.dart'; +import "package:photos/ui/sharing/user_avator_widget.dart"; +import "package:photos/utils/navigation_util.dart"; +import "package:photos/utils/toast_util.dart"; + +class EmergencyPage extends StatefulWidget { + const EmergencyPage({ + super.key, + }); + + @override + State createState() => _EmergencyPageState(); +} + +class _EmergencyPageState extends State { + late int currentUserID; + EmergencyInfo? info; + + bool hasTrustedContact = false; + + @override + void initState() { + super.initState(); + currentUserID = Configuration.instance.getUserID()!; + // set info to null after 5 second + Future.delayed( + const Duration(seconds: 0), + () async { + unawaited(_fetchData()); + }, + ); + } + + Future _fetchData() async { + try { + final result = await EmergencyContactService.instance.getInfo(); + if (mounted) { + setState(() { + info = result; + if (info != null) { + hasTrustedContact = info!.contacts.isNotEmpty; + } + }); + } + } catch (e) { + showShortToast( + context, + S.of(context).somethingWentWrong, + ); + } + } + + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + final currentUserID = Configuration.instance.getUserID()!; + final List othersTrustedContacts = + info?.othersEmergencyContact ?? []; + final List trustedContacts = info?.contacts ?? []; + + return Scaffold( + body: CustomScrollView( + primary: false, + slivers: [ + TitleBarWidget( + flexibleSpaceTitle: TitleBarTitleWidget( + title: S.of(context).legacy, + ), + ), + if (info == null) + const SliverFillRemaining( + hasScrollBody: false, + child: Center( + child: EnteLoadingWidget(), + ), + ), + if (info != null) + if (info!.recoverSessions.isNotEmpty) + SliverPadding( + padding: const EdgeInsets.only( + top: 20, + left: 16, + right: 16, + ), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == 0) { + return Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: NotificationWidget( + startIcon: Icons.warning_amber_rounded, + text: context.l10n.recoveryWarning, + actionIcon: null, + onTap: () {}, + ), + ); + } + final RecoverySessions recoverSession = + info!.recoverSessions[index - 1]; + return MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: recoverSession.emergencyContact.email, + makeTextBold: recoverSession.status.isNotEmpty, + textColor: colorScheme.warning500, + ), + leadingIconWidget: UserAvatarWidget( + recoverSession.emergencyContact, + currentUserID: currentUserID, + ), + leadingIconSize: 24, + menuItemColor: colorScheme.fillFaint, + singleBorderRadius: 8, + trailingIcon: Icons.chevron_right, + onTap: () async { + await showRejectRecoveryDialog(recoverSession); + }, + ); + }, + childCount: 1 + info!.recoverSessions.length, + ), + ), + ), + if (info != null) + SliverPadding( + padding: const EdgeInsets.only( + top: 16, + left: 16, + right: 16, + bottom: 8, + ), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == 0 && trustedContacts.isNotEmpty) { + return MenuSectionTitle( + title: S.of(context).trustedContacts, + ); + } else if (index > 0 && index <= trustedContacts.length) { + final listIndex = index - 1; + final contact = trustedContacts[listIndex]; + return Column( + children: [ + MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: contact.emergencyContact.email, + subTitle: contact.isPendingInvite() ? "⚠" : null, + makeTextBold: contact.isPendingInvite(), + ), + leadingIconSize: 24.0, + surfaceExecutionStates: false, + alwaysShowSuccessState: false, + leadingIconWidget: UserAvatarWidget( + contact.emergencyContact, + type: AvatarType.mini, + currentUserID: currentUserID, + ), + menuItemColor: + getEnteColorScheme(context).fillFaint, + trailingIcon: Icons.chevron_right, + trailingIconIsMuted: true, + onTap: () async { + await showRevokeOrRemoveDialog(context, contact); + }, + isTopBorderRadiusRemoved: listIndex > 0, + isBottomBorderRadiusRemoved: true, + singleBorderRadius: 8, + ), + DividerWidget( + dividerType: DividerType.menu, + bgColor: getEnteColorScheme(context).fillFaint, + ), + ], + ); + } else if (index == (1 + trustedContacts.length)) { + if (trustedContacts.isEmpty) { + return Column( + children: [ + const SizedBox(height: 20), + Text( + context.l10n.legacyPageDesc, + style: getEnteTextTheme(context).body, + ), + SizedBox( + height: 200, + width: 200, + child: SvgPicture.asset( + getEnteColorScheme(context).backdropBase == + backgroundBaseDark + ? "assets/icons/legacy-light.svg" + : "assets/icons/legacy-dark.svg", + width: 156, + height: 152, + ), + ), + Text( + context.l10n.legacyPageDesc2, + style: getEnteTextTheme(context).smallMuted, + ), + const SizedBox(height: 16), + ButtonWidget( + buttonType: ButtonType.primary, + labelText: S.of(context).addTrustedContact, + shouldSurfaceExecutionStates: false, + onTap: () async { + await routeToPage( + context, + AddContactPage(info!), + forceCustomPageRoute: true, + ); + unawaited(_fetchData()); + }, + ), + ], + ); + } + return MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: trustedContacts.isNotEmpty + ? S.of(context).addMore + : S.of(context).addTrustedContact, + makeTextBold: true, + ), + leadingIcon: Icons.add_outlined, + surfaceExecutionStates: false, + menuItemColor: getEnteColorScheme(context).fillFaint, + onTap: () async { + await routeToPage( + context, + AddContactPage(info!), + forceCustomPageRoute: true, + ); + unawaited(_fetchData()); + }, + isTopBorderRadiusRemoved: trustedContacts.isNotEmpty, + singleBorderRadius: 8, + ); + } + return const SizedBox.shrink(); + }, + childCount: 1 + trustedContacts.length + 1, + ), + ), + ), + if (info != null && info!.othersEmergencyContact.isNotEmpty) + SliverPadding( + padding: const EdgeInsets.only(top: 0, left: 16, right: 16), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == 0 && (othersTrustedContacts.isNotEmpty)) { + return Column( + children: [ + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: DividerWidget( + dividerType: DividerType.solid, + ), + ), + MenuSectionTitle( + title: context.l10n.legacyAccounts, + ), + ], + ); + } else if (index > 0 && + index <= othersTrustedContacts.length) { + final listIndex = index - 1; + final currentUser = othersTrustedContacts[listIndex]; + final isLastItem = index == othersTrustedContacts.length; + return Column( + children: [ + MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: currentUser.user.email, + makeTextBold: currentUser.isPendingInvite(), + subTitle: + currentUser.isPendingInvite() ? "⚠" : null, + ), + leadingIconSize: 24.0, + leadingIconWidget: UserAvatarWidget( + currentUser.user, + type: AvatarType.mini, + currentUserID: currentUserID, + ), + menuItemColor: + getEnteColorScheme(context).fillFaint, + trailingIcon: Icons.chevron_right, + trailingIconIsMuted: true, + onTap: () async { + if (currentUser.isPendingInvite()) { + await showAcceptOrDeclineDialog( + context, + currentUser, + ); + } else { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return OtherContactPage( + contact: currentUser, + emergencyInfo: info!, + ); + }, + ), + ); + + // await routeToPage( + // context, + // OtherContactPage( + // contact: currentUser, + // emergencyInfo: info!, + // ), + // ); + if (mounted) { + unawaited(_fetchData()); + } + } + }, + isTopBorderRadiusRemoved: listIndex > 0, + isBottomBorderRadiusRemoved: !isLastItem, + singleBorderRadius: 8, + surfaceExecutionStates: false, + ), + isLastItem + ? const SizedBox.shrink() + : DividerWidget( + dividerType: DividerType.menu, + bgColor: + getEnteColorScheme(context).fillFaint, + ), + ], + ); + } + return const SizedBox.shrink(); + }, + childCount: 1 + othersTrustedContacts.length + 1, + ), + ), + ), + ], + ), + ); + } + + Future showRevokeOrRemoveDialog( + BuildContext context, + EmergencyContact contact, + ) async { + if (contact.isPendingInvite()) { + await showActionSheet( + context: context, + body: + "You have invited ${contact.emergencyContact.email} to be a trusted contact", + bodyHighlight: "They are yet to accept your invite", + buttons: [ + ButtonWidget( + labelText: S.of(context).removeInvite, + buttonType: ButtonType.critical, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.first, + shouldStickToDarkTheme: true, + shouldSurfaceExecutionStates: true, + shouldShowSuccessConfirmation: false, + onTap: () async { + await EmergencyContactService.instance + .updateContact(contact, ContactState.userRevokedContact); + info?.contacts.remove(contact); + if (mounted) { + setState(() {}); + unawaited(_fetchData()); + } + }, + isInAlert: true, + ), + ButtonWidget( + labelText: S.of(context).cancel, + buttonType: ButtonType.tertiary, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.second, + shouldStickToDarkTheme: true, + isInAlert: true, + ), + ], + ); + } else { + await showActionSheet( + context: context, + body: + "You have added ${contact.emergencyContact.email} as a trusted contact", + bodyHighlight: "They have accepted your invite", + buttons: [ + ButtonWidget( + labelText: S.of(context).remove, + buttonType: ButtonType.critical, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.second, + shouldStickToDarkTheme: true, + shouldSurfaceExecutionStates: true, + shouldShowSuccessConfirmation: false, + onTap: () async { + await EmergencyContactService.instance + .updateContact(contact, ContactState.userRevokedContact); + info?.contacts.remove(contact); + if (mounted) { + setState(() {}); + unawaited(_fetchData()); + } + }, + isInAlert: true, + ), + ButtonWidget( + labelText: S.of(context).cancel, + buttonType: ButtonType.tertiary, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.third, + shouldStickToDarkTheme: true, + isInAlert: true, + ), + ], + ); + } + } + + Future showAcceptOrDeclineDialog( + BuildContext context, + EmergencyContact contact, + ) async { + await showActionSheet( + context: context, + buttons: [ + ButtonWidget( + labelText: S.of(context).acceptTrustInvite, + buttonType: ButtonType.primary, + buttonSize: ButtonSize.large, + shouldStickToDarkTheme: true, + buttonAction: ButtonAction.first, + onTap: () async { + await EmergencyContactService.instance + .updateContact(contact, ContactState.contactAccepted); + final updatedContact = + contact.copyWith(state: ContactState.contactAccepted); + info?.othersEmergencyContact.remove(contact); + info?.othersEmergencyContact.add(updatedContact); + if (mounted) { + setState(() {}); + } + }, + isInAlert: true, + ), + ButtonWidget( + labelText: S.of(context).declineTrustInvite, + buttonType: ButtonType.critical, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.second, + shouldStickToDarkTheme: true, + onTap: () async { + await EmergencyContactService.instance + .updateContact(contact, ContactState.contactDenied); + info?.othersEmergencyContact.remove(contact); + if (mounted) { + setState(() {}); + } + }, + isInAlert: true, + ), + ButtonWidget( + labelText: S.of(context).cancel, + buttonType: ButtonType.tertiary, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.third, + shouldStickToDarkTheme: true, + isInAlert: true, + ), + ], + body: S.of(context).legacyInvite(contact.user.email), + actionSheetType: ActionSheetType.defaultActionSheet, + ); + return; + } + + Future showRejectRecoveryDialog(RecoverySessions session) async { + final String emergencyContactEmail = session.emergencyContact.email; + await showActionSheet( + context: context, + buttons: [ + ButtonWidget( + labelText: context.l10n.rejectRecovery, + buttonSize: ButtonSize.large, + shouldStickToDarkTheme: true, + buttonType: ButtonType.critical, + buttonAction: ButtonAction.first, + onTap: () async { + await EmergencyContactService.instance.rejectRecovery(session); + info?.recoverSessions + .removeWhere((element) => element.id == session.id); + if (mounted) { + setState(() {}); + } + unawaited(_fetchData()); + }, + isInAlert: true, + ), + if (kDebugMode) + ButtonWidget( + labelText: "Approve recovery (to be removed)", + buttonType: ButtonType.primary, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.second, + shouldStickToDarkTheme: true, + onTap: () async { + await EmergencyContactService.instance.approveRecovery(session); + if (mounted) { + setState(() {}); + } + unawaited(_fetchData()); + }, + isInAlert: true, + ), + ButtonWidget( + labelText: S.of(context).cancel, + buttonType: ButtonType.tertiary, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.third, + shouldStickToDarkTheme: true, + isInAlert: true, + ), + ], + body: context.l10n.recoveryWarningBody(emergencyContactEmail), + actionSheetType: ActionSheetType.defaultActionSheet, + ); + return; + } +} diff --git a/mobile/lib/emergency/emergency_service.dart b/mobile/lib/emergency/emergency_service.dart new file mode 100644 index 0000000000..10d6ad7b0f --- /dev/null +++ b/mobile/lib/emergency/emergency_service.dart @@ -0,0 +1,275 @@ +import "dart:convert"; +import "dart:math"; +import "dart:typed_data"; + +import "package:dio/dio.dart"; +import "package:flutter/cupertino.dart"; +import "package:logging/logging.dart"; +import "package:photos/core/configuration.dart"; +import "package:photos/core/network/network.dart"; +import "package:photos/emergency/model.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/models/api/user/srp.dart"; +import "package:photos/models/key_attributes.dart"; +import "package:photos/models/set_keys_request.dart"; +import "package:photos/services/user_service.dart"; +import "package:photos/ui/common/user_dialogs.dart"; +import "package:photos/utils/crypto_util.dart"; +import "package:photos/utils/dialog_util.dart"; +import "package:photos/utils/email_util.dart"; +import "package:pointycastle/pointycastle.dart"; +import "package:pointycastle/random/fortuna_random.dart"; +import "package:pointycastle/srp/srp6_client.dart"; +import "package:pointycastle/srp/srp6_standard_groups.dart"; +import "package:pointycastle/srp/srp6_util.dart"; +import "package:pointycastle/srp/srp6_verifier_generator.dart"; +import "package:uuid/uuid.dart"; + +class EmergencyContactService { + late Dio _enteDio; + late UserService _userService; + late Configuration _config; + late final Logger _logger = Logger("EmergencyContactService"); + + EmergencyContactService._privateConstructor() { + _enteDio = NetworkClient.instance.enteDio; + _userService = UserService.instance; + _config = Configuration.instance; + } + + static final EmergencyContactService instance = + EmergencyContactService._privateConstructor(); + + Future addContact(BuildContext context, String email) async { + if (!isValidEmail(email)) { + await showErrorDialog( + context, + S.of(context).invalidEmailAddress, + S.of(context).enterValidEmail, + ); + return false; + } else if (email.trim() == Configuration.instance.getEmail()) { + await showErrorDialog( + context, + S.of(context).oops, + S.of(context).youCannotShareWithYourself, + ); + return false; + } + final String? publicKey = await _userService.getPublicKey(email); + if (publicKey == null) { + await showInviteDialog(context, email); + return false; + } + final Uint8List recoveryKey = Configuration.instance.getRecoveryKey(); + final encryptedKey = CryptoUtil.sealSync( + recoveryKey, + CryptoUtil.base642bin(publicKey), + ); + await _enteDio.post( + "/emergency-contacts/add", + data: { + "email": email.trim(), + "encryptedKey": CryptoUtil.bin2base64(encryptedKey), + }, + ); + return true; + } + + Future getInfo() async { + try { + final response = await _enteDio.get("/emergency-contacts/info"); + return EmergencyInfo.fromJson(response.data); + } catch (e, s) { + Logger("EmergencyContact").severe('failed to get info', e, s); + rethrow; + } + } + + Future updateContact( + EmergencyContact contact, + ContactState state, + ) async { + try { + await _enteDio.post( + "/emergency-contacts/update", + data: { + "userID": contact.user.id, + "emergencyContactID": contact.emergencyContact.id, + "state": state.stringValue, + }, + ); + } catch (e, s) { + Logger("EmergencyContact").severe('failed to update contact', e, s); + rethrow; + } + } + + Future startRecovery(EmergencyContact contact) async { + try { + await _enteDio.post( + "/emergency-contacts/start-recovery", + data: { + "userID": contact.user.id, + "emergencyContactID": contact.emergencyContact.id, + }, + ); + } catch (e, s) { + Logger("EmergencyContact").severe('failed to start recovery', e, s); + rethrow; + } + } + + Future stopRecovery(RecoverySessions session) async { + try { + await _enteDio.post( + "/emergency-contacts/stop-recovery", + data: { + "userID": session.user.id, + "emergencyContactID": session.emergencyContact.id, + "id": session.id, + }, + ); + } catch (e, s) { + Logger("EmergencyContact").severe('failed to stop recovery', e, s); + rethrow; + } + } + + Future rejectRecovery(RecoverySessions session) async { + try { + await _enteDio.post( + "/emergency-contacts/reject-recovery", + data: { + "userID": session.user.id, + "emergencyContactID": session.emergencyContact.id, + "id": session.id, + }, + ); + } catch (e, s) { + Logger("EmergencyContact").severe('failed to stop recovery', e, s); + rethrow; + } + } + + Future approveRecovery(RecoverySessions session) async { + try { + await _enteDio.post( + "/emergency-contacts/approve-recovery", + data: { + "userID": session.user.id, + "emergencyContactID": session.emergencyContact.id, + "id": session.id, + }, + ); + } catch (e, s) { + Logger("EmergencyContact").severe('failed to approve recovery', e, s); + rethrow; + } + } + + Future<(String, KeyAttributes)> getRecoveryInfo( + RecoverySessions sessions, + ) async { + try { + final resp = await _enteDio.get( + "/emergency-contacts/recovery-info/${sessions.id}", + ); + final String encryptedKey = resp.data["encryptedKey"]!; + final decryptedKey = CryptoUtil.openSealSync( + CryptoUtil.base642bin(encryptedKey), + CryptoUtil.base642bin(_config.getKeyAttributes()!.publicKey), + _config.getSecretKey()!, + ); + final String hexRecoveryKey = CryptoUtil.bin2hex(decryptedKey); + final KeyAttributes keyAttributes = + KeyAttributes.fromMap(resp.data['userKeyAttr']); + return (hexRecoveryKey, keyAttributes); + } catch (e, s) { + Logger("EmergencyContact").severe('failed to stop recovery', e, s); + rethrow; + } + } + + Future changePasswordForOther( + Uint8List loginKey, + SetKeysRequest setKeysRequest, + RecoverySessions recoverySessions, + ) async { + try { + final SRP6GroupParameters kDefaultSrpGroup = + SRP6StandardGroups.rfc5054_4096; + final String username = const Uuid().v4().toString(); + final SecureRandom random = _getSecureRandom(); + final Uint8List identity = Uint8List.fromList(utf8.encode(username)); + final Uint8List password = loginKey; + final Uint8List salt = random.nextBytes(16); + final gen = SRP6VerifierGenerator( + group: kDefaultSrpGroup, + digest: Digest('SHA-256'), + ); + final v = gen.generateVerifier(salt, identity, password); + + final client = SRP6Client( + group: kDefaultSrpGroup, + digest: Digest('SHA-256'), + random: random, + ); + + final A = client.generateClientCredentials(salt, identity, password); + final request = SetupSRPRequest( + srpUserID: username, + srpSalt: base64Encode(salt), + srpVerifier: base64Encode(SRP6Util.encodeBigInt(v)), + srpA: base64Encode(SRP6Util.encodeBigInt(A!)), + isUpdate: false, + ); + final response = await _enteDio.post( + "/emergency-contacts/init-change-password", + data: { + "recoveryID": recoverySessions.id, + "setupSRPRequest": request.toMap(), + }, + ); + if (response.statusCode == 200) { + final SetupSRPResponse setupSRPResponse = + SetupSRPResponse.fromJson(response.data); + final serverB = + SRP6Util.decodeBigInt(base64Decode(setupSRPResponse.srpB)); + + // ignore: unused_local_variable + final clientS = client.calculateSecret(serverB); + final clientM = client.calculateClientEvidenceMessage(); + // ignore: unused_local_variable + late Response srpCompleteResponse; + srpCompleteResponse = await _enteDio.post( + "/emergency-contacts/change-password", + data: { + "recoveryID": recoverySessions.id, + 'updateSrpAndKeysRequest': { + 'setupID': setupSRPResponse.setupID, + 'srpM1': base64Encode(SRP6Util.encodeBigInt(clientM!)), + 'updatedKeyAttr': setKeysRequest.toMap(), + }, + }, + ); + } else { + throw Exception("register-srp action failed"); + } + } catch (e, s) { + _logger.severe("failed to change password for other", e, s); + rethrow; + } + } + + SecureRandom _getSecureRandom() { + final List seeds = []; + final random = Random.secure(); + for (int i = 0; i < 32; i++) { + seeds.add(random.nextInt(255)); + } + final secureRandom = FortunaRandom(); + secureRandom.seed(KeyParameter(Uint8List.fromList(seeds))); + return secureRandom; + } +} diff --git a/mobile/lib/emergency/model.dart b/mobile/lib/emergency/model.dart new file mode 100644 index 0000000000..e85c7456ca --- /dev/null +++ b/mobile/lib/emergency/model.dart @@ -0,0 +1,153 @@ +import "package:photos/models/api/collection/user.dart"; + +enum ContactState { + userInvitedContact, + userRevokedContact, + contactAccepted, + contactLeft, + contactDenied, + unknown, +} + +extension ContactStateExtension on ContactState { + String get stringValue { + switch (this) { + case ContactState.userInvitedContact: + return "INVITED"; + case ContactState.userRevokedContact: + return "REVOKED"; + case ContactState.contactAccepted: + return "ACCEPTED"; + case ContactState.contactLeft: + return "CONTACT_LEFT"; + case ContactState.contactDenied: + return "CONTACT_DENIED"; + default: + return "UNKNOWN"; + } + } + + static ContactState fromString(String value) { + switch (value) { + case "INVITED": + return ContactState.userInvitedContact; + case "REVOKED": + return ContactState.userRevokedContact; + case "ACCEPTED": + return ContactState.contactAccepted; + case "CONTACT_LEFT": + return ContactState.contactLeft; + case "CONTACT_DENIED": + return ContactState.contactDenied; + default: + return ContactState.unknown; + } + } +} + +class EmergencyContact { + final User user; + final User emergencyContact; + final ContactState state; + final int recoveryNoticeInDays; + + EmergencyContact( + this.user, + this.emergencyContact, + this.state, + this.recoveryNoticeInDays, + ); + + // copyWith + EmergencyContact copyWith({ + User? user, + User? emergencyContact, + ContactState? state, + int? recoveryNoticeInDays, + }) { + return EmergencyContact( + user ?? this.user, + emergencyContact ?? this.emergencyContact, + state ?? this.state, + recoveryNoticeInDays ?? this.recoveryNoticeInDays, + ); + } + + // fromJson + EmergencyContact.fromJson(Map json) + : user = User.fromMap(json['user']), + emergencyContact = User.fromMap(json['emergencyContact']), + state = ContactStateExtension.fromString(json['state'] as String), + recoveryNoticeInDays = json['recoveryNoticeInDays']; + + bool isCurrentUserContact(int userID) { + return user.id == userID; + } + + bool isPendingInvite() { + return state == ContactState.userInvitedContact; + } +} + +class EmergencyInfo { + // List of emergency contacts added by the user + final List contacts; + + // List of recovery sessions that are created to recover current user account + final List recoverSessions; + + // List of emergency contacts that have added current user as their emergency contact + final List othersEmergencyContact; + + // List of recovery sessions that are created to recover grantor's account + final List othersRecoverySession; + + EmergencyInfo( + this.contacts, + this.recoverSessions, + this.othersEmergencyContact, + this.othersRecoverySession, + ); + + // from json + EmergencyInfo.fromJson(Map json) + : contacts = (json['contacts'] as List) + .map((contact) => EmergencyContact.fromJson(contact)) + .toList(), + recoverSessions = (json['recoverSessions'] as List) + .map((session) => RecoverySessions.fromJson(session)) + .toList(), + othersEmergencyContact = (json['othersEmergencyContact'] as List) + .map((grantor) => EmergencyContact.fromJson(grantor)) + .toList(), + othersRecoverySession = (json['othersRecoverySession'] as List) + .map((session) => RecoverySessions.fromJson(session)) + .toList(); +} + +class RecoverySessions { + final String id; + final User user; + final User emergencyContact; + final String status; + final int waitTill; + final int createdAt; + + RecoverySessions( + this.id, + this.user, + this.emergencyContact, + this.status, + this.waitTill, + this.createdAt, + ); + + // fromJson + RecoverySessions.fromJson(Map json) + : id = json['id'], + user = User.fromMap(json['user']), + emergencyContact = User.fromMap(json['emergencyContact']), + status = json['status'], + waitTill = json['waitTill'], + createdAt = json['createdAt']; +} diff --git a/mobile/lib/emergency/other_contact_page.dart b/mobile/lib/emergency/other_contact_page.dart new file mode 100644 index 0000000000..f6f6787911 --- /dev/null +++ b/mobile/lib/emergency/other_contact_page.dart @@ -0,0 +1,279 @@ +import "package:collection/collection.dart"; +import "package:flutter/material.dart"; +import "package:logging/logging.dart"; +import "package:photos/core/configuration.dart"; +import "package:photos/emergency/emergency_service.dart"; +import "package:photos/emergency/model.dart"; +import "package:photos/emergency/recover_others_account.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/l10n/l10n.dart"; +import "package:photos/models/key_attributes.dart"; +import "package:photos/theme/colors.dart"; +import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/components/action_sheet_widget.dart"; +import "package:photos/ui/components/buttons/button_widget.dart"; +import "package:photos/ui/components/captioned_text_widget.dart"; +import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart"; +import "package:photos/ui/components/menu_section_title.dart"; +import "package:photos/ui/components/models/button_type.dart"; +import "package:photos/ui/components/title_bar_title_widget.dart"; +import "package:photos/utils/date_time_util.dart"; +import "package:photos/utils/dialog_util.dart"; +import "package:photos/utils/navigation_util.dart"; + +// OtherContactPage is used to start recovery process for other user's account +// Based on the state of the contact & recovery session, it will show +// different UI +class OtherContactPage extends StatefulWidget { + final EmergencyContact contact; + final EmergencyInfo emergencyInfo; + + const OtherContactPage({ + required this.contact, + required this.emergencyInfo, + super.key, + }); + + @override + State createState() => _OtherContactPageState(); +} + +class _OtherContactPageState extends State { + late String accountEmail = widget.contact.user.email; + RecoverySessions? recoverySession; + String? waitTill; + final Logger _logger = Logger("_OtherContactPageState"); + late EmergencyInfo emergencyInfo = widget.emergencyInfo; + + @override + void initState() { + super.initState(); + recoverySession = widget.emergencyInfo.othersRecoverySession + .firstWhereOrNull((session) => session.user.email == accountEmail); + _fetchData(); + } + + Future _fetchData() async { + try { + final result = await EmergencyContactService.instance.getInfo(); + if (mounted) { + setState(() { + recoverySession = result.othersRecoverySession.firstWhereOrNull( + (session) => session.user.email == accountEmail, + ); + }); + } + } catch (e) { + _logger.severe("Error fetching data", e); + } + } + + @override + Widget build(BuildContext context) { + _logger.info('session ${widget.emergencyInfo}'); + if (recoverySession != null) { + final dateTime = DateTime.now().add( + Duration( + microseconds: recoverySession!.waitTill, + ), + ); + waitTill = getFormattedTime(context, dateTime); + } + final colorScheme = getEnteColorScheme(context); + final textTheme = getEnteTextTheme(context); + return Scaffold( + appBar: AppBar(), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 12, + ), + TitleBarTitleWidget( + title: context.l10n.recoverAccount, + ), + Text( + accountEmail, + textAlign: TextAlign.left, + style: + textTheme.small.copyWith(color: colorScheme.textMuted), + ), + ], + ), + ), + const SizedBox(height: 12), + recoverySession == null + ? Text( + "You can recover $accountEmail's account in ${widget.contact.recoveryNoticeInDays} days" + " after starting the recovery process.", + style: textTheme.body, + ) + : (recoverySession!.status == "READY" + ? Text( + context.l10n.recoveryReady(accountEmail), + style: textTheme.body, + ) + : Text( + "You can recover $accountEmail's" + " account after $waitTill.", + style: textTheme.bodyBold, + )), + const SizedBox(height: 24), + if (recoverySession == null) + ButtonWidget( + // icon: Icons.start_outlined, + buttonType: ButtonType.trailingIconPrimary, + icon: Icons.start_outlined, + labelText: S.of(context).startAccountRecoveryTitle, + onTap: widget.contact.isPendingInvite() + ? null + : () async { + final actionResult = await showChoiceActionSheet( + context, + title: S.of(context).startAccountRecoveryTitle, + firstButtonLabel: S.of(context).yes, + body: "Are you sure you want to initiate recovery?", + isCritical: true, + ); + if (actionResult?.action != null) { + if (actionResult!.action == ButtonAction.first) { + try { + await EmergencyContactService.instance + .startRecovery(widget.contact); + if (mounted) { + _fetchData().ignore(); + await showErrorDialog( + context, + context.l10n.recoveryInitiated, + context.l10n.recoveryInitiatedDesc( + widget.contact.recoveryNoticeInDays, + Configuration.instance.getEmail()!, + ), + ); + } + } catch (e) { + showGenericErrorDialog(context: context, error: e) + .ignore(); + } + } + } + }, + // isTopBorderRadiusRemoved: true, + ), + if (recoverySession != null && recoverySession!.status == "READY") + Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: ButtonWidget( + buttonType: ButtonType.primary, + labelText: context.l10n.recoverAccount, + onTap: () async { + try { + final (String key, KeyAttributes attributes) = + await EmergencyContactService.instance + .getRecoveryInfo(recoverySession!); + routeToPage( + context, + RecoverOthersAccount(key, attributes, recoverySession!), + ).ignore(); + } catch (e) { + showGenericErrorDialog(context: context, error: e) + .ignore(); + } + }, + ), + ), + if (recoverySession != null && + (recoverySession!.status == "WAITING" || + recoverySession!.status == "READY")) + ButtonWidget( + buttonType: ButtonType.neutral, + labelText: S.of(context).cancelAccountRecovery, + shouldSurfaceExecutionStates: false, + onTap: () async { + final actionResult = await showChoiceActionSheet( + context, + title: S.of(context).cancelAccountRecovery, + firstButtonLabel: S.of(context).yes, + body: S.of(context).cancelAccountRecoveryBody, + isCritical: true, + firstButtonOnTap: () async { + await EmergencyContactService.instance + .stopRecovery(recoverySession!); + }, + ); + if (actionResult?.action == ButtonAction.first) { + _fetchData().ignore(); + } + }, + ), + SizedBox(height: recoverySession == null ? 48 : 24), + MenuSectionTitle( + title: S.of(context).removeYourselfAsTrustedContact, + ), + MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: S.of(context).remove, + textColor: warning500, + makeTextBold: true, + ), + leadingIcon: Icons.not_interested_outlined, + leadingIconColor: warning500, + menuItemColor: getEnteColorScheme(context).fillFaint, + surfaceExecutionStates: false, + onTap: () async { + await showRemoveSheet(); + }, + ), + ], + ), + ), + ); + } + + Future showRemoveSheet() async { + await showActionSheet( + context: context, + buttons: [ + ButtonWidget( + labelText: context.l10n.remove, + buttonSize: ButtonSize.large, + shouldStickToDarkTheme: true, + buttonType: ButtonType.critical, + buttonAction: ButtonAction.first, + onTap: () async { + try { + await EmergencyContactService.instance.updateContact( + widget.contact, + ContactState.contactLeft, + ); + Navigator.of(context).pop(); + } catch (e) { + showGenericErrorDialog(context: context, error: e).ignore(); + } + }, + isInAlert: true, + ), + ButtonWidget( + labelText: S.of(context).cancel, + buttonType: ButtonType.tertiary, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.third, + shouldStickToDarkTheme: true, + isInAlert: true, + ), + ], + body: "Are you sure your want to stop being a trusted " + "contact for $accountEmail?", + title: context.l10n.remove, + actionSheetType: ActionSheetType.defaultActionSheet, + ); + return; + } +} diff --git a/mobile/lib/emergency/recover_others_account.dart b/mobile/lib/emergency/recover_others_account.dart new file mode 100644 index 0000000000..d29683ecfd --- /dev/null +++ b/mobile/lib/emergency/recover_others_account.dart @@ -0,0 +1,364 @@ +import "dart:convert"; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:logging/logging.dart'; +import 'package:password_strength/password_strength.dart'; +import "package:photos/emergency/emergency_service.dart"; +import "package:photos/emergency/model.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/models/key_attributes.dart"; +import "package:photos/models/set_keys_request.dart"; +import 'package:photos/ui/common/dynamic_fab.dart'; +import "package:photos/utils/crypto_util.dart"; +import 'package:photos/utils/dialog_util.dart'; +import 'package:photos/utils/toast_util.dart'; + +class RecoverOthersAccount extends StatefulWidget { + final String recoveryKey; + final KeyAttributes attributes; + final RecoverySessions sessions; + + const RecoverOthersAccount( + this.recoveryKey, + this.attributes, + this.sessions, { + super.key, + }); + + @override + State createState() => _RecoverOthersAccountState(); +} + +class _RecoverOthersAccountState extends State { + static const kMildPasswordStrengthThreshold = 0.4; + static const kStrongPasswordStrengthThreshold = 0.7; + + final _logger = Logger((_RecoverOthersAccountState).toString()); + final _passwordController1 = TextEditingController(), + _passwordController2 = TextEditingController(); + final Color _validFieldValueColor = const Color.fromRGBO(45, 194, 98, 0.2); + String _passwordInInputBox = ''; + String _passwordInInputConfirmationBox = ''; + double _passwordStrength = 0.0; + bool _password1Visible = false; + bool _password2Visible = false; + final _password1FocusNode = FocusNode(); + final _password2FocusNode = FocusNode(); + bool _password1InFocus = false; + bool _password2InFocus = false; + + bool _passwordsMatch = false; + bool _isPasswordValid = false; + + @override + void initState() { + super.initState(); + _password1FocusNode.addListener(() { + setState(() { + _password1InFocus = _password1FocusNode.hasFocus; + }); + }); + _password2FocusNode.addListener(() { + setState(() { + _password2InFocus = _password2FocusNode.hasFocus; + }); + }); + } + + @override + Widget build(BuildContext context) { + final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100; + + FloatingActionButtonLocation? fabLocation() { + if (isKeypadOpen) { + return null; + } else { + return FloatingActionButtonLocation.centerFloat; + } + } + + String title = S.of(context).setPasswordTitle; + title = S.of(context).resetPasswordTitle; + return Scaffold( + resizeToAvoidBottomInset: isKeypadOpen, + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.arrow_back), + color: Theme.of(context).iconTheme.color, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + elevation: 0, + ), + body: _getBody(title), + floatingActionButton: DynamicFAB( + isKeypadOpen: isKeypadOpen, + isFormValid: _passwordsMatch && _isPasswordValid, + buttonText: title, + onPressedFunction: () { + _updatePassword(); + FocusScope.of(context).unfocus(); + }, + ), + floatingActionButtonLocation: fabLocation(), + floatingActionButtonAnimator: NoScalingAnimation(), + ); + } + + Widget _getBody(String buttonTextAndHeading) { + final email = widget.sessions.user.email; + var passwordStrengthText = S.of(context).weakStrength; + var passwordStrengthColor = Colors.redAccent; + if (_passwordStrength > kStrongPasswordStrengthThreshold) { + passwordStrengthText = S.of(context).strongStrength; + passwordStrengthColor = Colors.greenAccent; + } else if (_passwordStrength > kMildPasswordStrengthThreshold) { + passwordStrengthText = S.of(context).moderateStrength; + passwordStrengthColor = Colors.orangeAccent; + } + return Column( + children: [ + Expanded( + child: AutofillGroup( + child: ListView( + children: [ + Padding( + padding: + const EdgeInsets.symmetric(vertical: 30, horizontal: 20), + child: Text( + buttonTextAndHeading, + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text( + "Enter new password for $email account. You will be able " + "to use this password to login into $email account.", + textAlign: TextAlign.start, + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(fontSize: 14), + ), + ), + const Padding(padding: EdgeInsets.all(12)), + Visibility( + // hidden textForm for suggesting auto-fill service for saving + // password + visible: false, + child: TextFormField( + autofillHints: const [ + AutofillHints.email, + ], + autocorrect: false, + keyboardType: TextInputType.emailAddress, + initialValue: email, + textInputAction: TextInputAction.next, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), + child: TextFormField( + autofillHints: const [AutofillHints.newPassword], + decoration: InputDecoration( + fillColor: + _isPasswordValid ? _validFieldValueColor : null, + filled: true, + hintText: S.of(context).password, + contentPadding: const EdgeInsets.all(20), + border: UnderlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(6), + ), + suffixIcon: _password1InFocus + ? IconButton( + icon: Icon( + _password1Visible + ? Icons.visibility + : Icons.visibility_off, + color: Theme.of(context).iconTheme.color, + size: 20, + ), + onPressed: () { + setState(() { + _password1Visible = !_password1Visible; + }); + }, + ) + : _isPasswordValid + ? Icon( + Icons.check, + color: Theme.of(context) + .inputDecorationTheme + .focusedBorder! + .borderSide + .color, + ) + : null, + ), + obscureText: !_password1Visible, + controller: _passwordController1, + autofocus: false, + autocorrect: false, + keyboardType: TextInputType.visiblePassword, + onChanged: (password) { + setState(() { + _passwordInInputBox = password; + _passwordStrength = estimatePasswordStrength(password); + _isPasswordValid = + _passwordStrength >= kMildPasswordStrengthThreshold; + _passwordsMatch = _passwordInInputBox == + _passwordInInputConfirmationBox; + }); + }, + textInputAction: TextInputAction.next, + focusNode: _password1FocusNode, + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), + child: TextFormField( + keyboardType: TextInputType.visiblePassword, + controller: _passwordController2, + obscureText: !_password2Visible, + autofillHints: const [AutofillHints.newPassword], + onEditingComplete: () => TextInput.finishAutofillContext(), + decoration: InputDecoration( + fillColor: _passwordsMatch ? _validFieldValueColor : null, + filled: true, + hintText: S.of(context).confirmPassword, + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 20, + ), + suffixIcon: _password2InFocus + ? IconButton( + icon: Icon( + _password2Visible + ? Icons.visibility + : Icons.visibility_off, + color: Theme.of(context).iconTheme.color, + size: 20, + ), + onPressed: () { + setState(() { + _password2Visible = !_password2Visible; + }); + }, + ) + : _passwordsMatch + ? Icon( + Icons.check, + color: Theme.of(context) + .inputDecorationTheme + .focusedBorder! + .borderSide + .color, + ) + : null, + border: UnderlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(6), + ), + ), + focusNode: _password2FocusNode, + onChanged: (cnfPassword) { + setState(() { + _passwordInInputConfirmationBox = cnfPassword; + if (_passwordInInputBox != '') { + _passwordsMatch = _passwordInInputBox == + _passwordInInputConfirmationBox; + } + }); + }, + ), + ), + Opacity( + opacity: + (_passwordInInputBox != '') && _password1InFocus ? 1 : 0, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + child: Text( + S.of(context).passwordStrength(passwordStrengthText), + style: TextStyle( + color: passwordStrengthColor, + ), + ), + ), + ), + const SizedBox(height: 8), + const Padding(padding: EdgeInsets.all(20)), + ], + ), + ), + ), + ], + ); + } + + void _updatePassword() async { + final dialog = + createProgressDialog(context, S.of(context).generatingEncryptionKeys); + await dialog.show(); + try { + final String password = _passwordController1.text; + final KeyAttributes attributes = widget.attributes; + Uint8List? masterKey; + try { + // Decrypt the master key that was earlier encrypted with the recovery key + masterKey = await CryptoUtil.decrypt( + CryptoUtil.base642bin(attributes.masterKeyEncryptedWithRecoveryKey!), + CryptoUtil.hex2bin(widget.recoveryKey), + CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce!), + ); + } catch (e) { + _logger.severe(e, "Failed to get master key using recoveryKey"); + rethrow; + } + + // Derive a key from the password that will be used to encrypt and + // decrypt the master key + final kekSalt = CryptoUtil.getSaltToDeriveKey(); + final derivedKeyResult = await CryptoUtil.deriveSensitiveKey( + utf8.encode(password) as Uint8List, + kekSalt, + ); + final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key); + // Encrypt the key with this derived key + final encryptedKeyData = + CryptoUtil.encryptSync(masterKey, derivedKeyResult.key); + + final updatedAttributes = attributes.copyWith( + kekSalt: CryptoUtil.bin2base64(kekSalt), + encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), + keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!), + memLimit: derivedKeyResult.memLimit, + opsLimit: derivedKeyResult.opsLimit, + ); + final setKeyRequest = SetKeysRequest( + kekSalt: updatedAttributes.kekSalt, + encryptedKey: updatedAttributes.encryptedKey, + keyDecryptionNonce: updatedAttributes.keyDecryptionNonce, + memLimit: updatedAttributes.memLimit!, + opsLimit: updatedAttributes.opsLimit!, + ); + await EmergencyContactService.instance.changePasswordForOther( + loginKey, + setKeyRequest, + widget.sessions, + ); + await dialog.hide(); + showShortToast(context, S.of(context).passwordChangedSuccessfully); + Navigator.of(context).pop(); + } catch (e, s) { + _logger.severe(e, s); + await dialog.hide(); + showGenericErrorDialog(context: context, error: e).ignore(); + } + } +} diff --git a/mobile/lib/emergency/select_contact_page.dart b/mobile/lib/emergency/select_contact_page.dart new file mode 100644 index 0000000000..c12be89bbd --- /dev/null +++ b/mobile/lib/emergency/select_contact_page.dart @@ -0,0 +1,364 @@ +import 'package:email_validator/email_validator.dart'; +import 'package:flutter/material.dart'; +import "package:logging/logging.dart"; +import 'package:photos/core/configuration.dart'; +import "package:photos/emergency/emergency_service.dart"; +import "package:photos/emergency/model.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/models/api/collection/user.dart"; +import 'package:photos/services/collections_service.dart'; +import "package:photos/services/user_service.dart"; +import 'package:photos/theme/ente_theme.dart'; +import 'package:photos/ui/actions/collection/collection_sharing_actions.dart'; +import 'package:photos/ui/components/buttons/button_widget.dart'; +import 'package:photos/ui/components/captioned_text_widget.dart'; +import 'package:photos/ui/components/divider_widget.dart'; +import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart'; +import 'package:photos/ui/components/menu_section_description_widget.dart'; +import 'package:photos/ui/components/menu_section_title.dart'; +import 'package:photos/ui/components/models/button_type.dart'; +import 'package:photos/ui/sharing/user_avator_widget.dart'; +import "package:photos/ui/sharing/verify_identity_dialog.dart"; +import "package:photos/utils/dialog_util.dart"; + +class AddContactPage extends StatefulWidget { + final EmergencyInfo emergencyInfo; + + const AddContactPage(this.emergencyInfo, {super.key}); + + @override + State createState() => _AddContactPage(); +} + +class _AddContactPage extends State { + String selectedEmail = ''; + String _email = ''; + bool isEmailListEmpty = false; + bool _emailIsValid = false; + bool isKeypadOpen = false; + late CollectionActions collectionActions; + late final Logger _logger = Logger('AddContactPage'); + + // Focus nodes are necessary + final textFieldFocusNode = FocusNode(); + final _textController = TextEditingController(); + + @override + void initState() { + collectionActions = CollectionActions(CollectionsService.instance); + super.initState(); + } + + @override + void dispose() { + _textController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100; + final enteTextTheme = getEnteTextTheme(context); + final enteColorScheme = getEnteColorScheme(context); + final List suggestedUsers = _getSuggestedUser(); + isEmailListEmpty = suggestedUsers.isEmpty; + return Scaffold( + resizeToAvoidBottomInset: isKeypadOpen, + appBar: AppBar( + title: Text( + S.of(context).addTrustedContact, + ), + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + S.of(context).addANewEmail, + style: enteTextTheme.small + .copyWith(color: enteColorScheme.textMuted), + ), + ), + const SizedBox(height: 4), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: _getEmailField(), + ), + if (isEmailListEmpty) + const Expanded(child: SizedBox.shrink()) + else + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + children: [ + !isEmailListEmpty + ? MenuSectionTitle( + title: S.of(context).orPickAnExistingOne, + ) + : const SizedBox.shrink(), + Expanded( + child: ListView.builder( + itemBuilder: (context, index) { + if (index >= suggestedUsers.length) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + ), + child: MenuSectionDescriptionWidget( + content: S.of(context).whyAddTrustContact, + ), + ); + } + final currentUser = suggestedUsers[index]; + return Column( + children: [ + MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: currentUser.email, + ), + leadingIconSize: 24.0, + leadingIconWidget: UserAvatarWidget( + currentUser, + type: AvatarType.mini, + ), + menuItemColor: + getEnteColorScheme(context).fillFaint, + pressedColor: + getEnteColorScheme(context).fillFaint, + trailingIcon: + (selectedEmail == currentUser.email) + ? Icons.check + : null, + onTap: () async { + textFieldFocusNode.unfocus(); + if (selectedEmail == currentUser.email) { + selectedEmail = ''; + } else { + selectedEmail = currentUser.email; + } + setState(() => {}); + }, + isTopBorderRadiusRemoved: index > 0, + isBottomBorderRadiusRemoved: + index < (suggestedUsers.length - 1), + ), + (index == (suggestedUsers.length - 1)) + ? const SizedBox.shrink() + : DividerWidget( + dividerType: DividerType.menu, + bgColor: + getEnteColorScheme(context).fillFaint, + ), + ], + ); + }, + itemCount: suggestedUsers.length + 1, + // physics: const ClampingScrollPhysics(), + ), + ), + ], + ), + ), + ), + SafeArea( + child: Padding( + padding: const EdgeInsets.only( + top: 8, + bottom: 8, + left: 16, + right: 16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 8), + ButtonWidget( + buttonType: ButtonType.primary, + buttonSize: ButtonSize.large, + labelText: "Add", + isDisabled: (selectedEmail == '' && !_emailIsValid), + onTap: (selectedEmail == '' && !_emailIsValid) + ? null + : () async { + final emailToAdd = + selectedEmail == '' ? _email : selectedEmail; + final choiceResult = await showChoiceActionSheet( + context, + title: S.of(context).warning, + body: S.of(context).confirmAddingTrustedContact( + emailToAdd, + 30, + ), + firstButtonLabel: S.of(context).proceed, + isCritical: true, + ); + if (choiceResult != null && + choiceResult.action == ButtonAction.first) { + try { + final r = await EmergencyContactService.instance + .addContact(context, emailToAdd); + if (r && mounted) { + Navigator.of(context).pop(true); + } + } catch (e) { + _logger.severe('Failed to add contact', e); + await showErrorDialog( + context, + S.of(context).error, + S.of(context).somethingWentWrong, + ); + } + } + }, + ), + const SizedBox(height: 12), + GestureDetector( + onTap: () async { + if ((selectedEmail == '' && !_emailIsValid)) { + await showErrorDialog( + context, + S.of(context).invalidEmailAddress, + S.of(context).enterValidEmail, + ); + return; + } + final emailToAdd = + selectedEmail == '' ? _email : selectedEmail; + await showDialog( + context: context, + builder: (BuildContext context) { + return VerifyIdentifyDialog( + self: false, + email: emailToAdd, + ); + }, + ); + }, + child: Text( + S.of(context).verifyIDLabel, + textAlign: TextAlign.center, + style: enteTextTheme.smallMuted.copyWith( + decoration: TextDecoration.underline, + ), + ), + ), + const SizedBox(height: 12), + ], + ), + ), + ), + ], + ), + ); + } + + void clearFocus() { + _textController.clear(); + _email = _textController.text; + _emailIsValid = false; + textFieldFocusNode.unfocus(); + setState(() => {}); + } + + Widget _getEmailField() { + return TextFormField( + controller: _textController, + focusNode: textFieldFocusNode, + style: getEnteTextTheme(context).body, + autofillHints: const [AutofillHints.email], + decoration: InputDecoration( + focusedBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + borderSide: + BorderSide(color: getEnteColorScheme(context).strokeMuted), + ), + fillColor: getEnteColorScheme(context).fillFaint, + filled: true, + hintText: S.of(context).enterEmail, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + border: UnderlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(4), + ), + prefixIcon: Icon( + Icons.email_outlined, + color: getEnteColorScheme(context).strokeMuted, + ), + suffixIcon: _email == '' + ? null + : IconButton( + onPressed: clearFocus, + icon: Icon( + Icons.cancel, + color: getEnteColorScheme(context).strokeMuted, + ), + ), + ), + onChanged: (value) { + if (selectedEmail != '') { + selectedEmail = ''; + } + _email = value.trim(); + _emailIsValid = EmailValidator.validate(_email); + setState(() {}); + }, + autocorrect: false, + keyboardType: TextInputType.emailAddress, + //initialValue: _email, + textInputAction: TextInputAction.next, + ); + } + + List _getSuggestedUser() { + final List suggestedUsers = []; + final Set existingEmails = {}; + final int ownerID = Configuration.instance.getUserID()!; + existingEmails.add(Configuration.instance.getEmail()!); + for (final c in CollectionsService.instance.getActiveCollections()) { + if (c.owner?.id == ownerID) { + for (final User? u in c.sharees ?? []) { + if (u != null && + u.id != null && + u.email.isNotEmpty && + !existingEmails.contains(u.email)) { + existingEmails.add(u.email); + suggestedUsers.add(u); + } + } + } else if (c.owner != null && + c.owner!.id != null && + c.owner!.email.isNotEmpty && + !existingEmails.contains(c.owner!.email)) { + existingEmails.add(c.owner!.email); + suggestedUsers.add(c.owner!); + } + } + final cachedUserDetails = UserService.instance.getCachedUserDetails(); + if (cachedUserDetails != null && + (cachedUserDetails.familyData?.members?.isNotEmpty ?? false)) { + for (final member in cachedUserDetails.familyData!.members!) { + if (!existingEmails.contains(member.email)) { + existingEmails.add(member.email); + suggestedUsers.add(User(email: member.email)); + } + } + } + if (_textController.text.trim().isNotEmpty) { + suggestedUsers.removeWhere( + (element) => !element.email + .toLowerCase() + .contains(_textController.text.trim().toLowerCase()), + ); + } + suggestedUsers.sort((a, b) => a.email.compareTo(b.email)); + + return suggestedUsers; + } +} diff --git a/mobile/lib/generated/intl/messages_ar.dart b/mobile/lib/generated/intl/messages_ar.dart index 53ade7100e..1ce7ba622c 100644 --- a/mobile/lib/generated/intl/messages_ar.dart +++ b/mobile/lib/generated/intl/messages_ar.dart @@ -26,10 +26,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("مرحبًا مجددًا!"), "ackPasswordLostWarning": MessageLookupByLibrary.simpleMessage( "أُدركُ أنّني فقدتُ كلمة مروري، فقد أفقد بياناتي لأن بياناتي مشفرة تشفيرًا تامًّا من النهاية إلى النهاية."), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "cancel": MessageLookupByLibrary.simpleMessage("إلغاء"), "decrypting": MessageLookupByLibrary.simpleMessage("فك التشفير..."), "email": MessageLookupByLibrary.simpleMessage("البريد الإلكتروني"), @@ -37,8 +33,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("أدخل عنوان بريدك الإلكتروني"), "enterYourRecoveryKey": MessageLookupByLibrary.simpleMessage("أدخل رمز الاسترداد"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "forgotPassword": MessageLookupByLibrary.simpleMessage("نسيت كلمة المرور"), "incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage( @@ -51,23 +45,13 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("ما من مفتاح استرداد؟"), "noRecoveryKeyNoDecryption": MessageLookupByLibrary.simpleMessage( "لا يمكن فك تشفير بياناتك دون كلمة المرور أو مفتاح الاسترداد بسبب طبيعة بروتوكول التشفير الخاص بنا من النهاية إلى النهاية"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "recoverButton": MessageLookupByLibrary.simpleMessage("استرداد"), "recoverySuccessful": MessageLookupByLibrary.simpleMessage("نجح الاسترداد!"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "sorry": MessageLookupByLibrary.simpleMessage("المعذرة"), "terminate": MessageLookupByLibrary.simpleMessage("إنهاء"), "terminateSession": MessageLookupByLibrary.simpleMessage("إنهاء الجلسة؟"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "thisDevice": MessageLookupByLibrary.simpleMessage("هذا الجهاز"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( diff --git a/mobile/lib/generated/intl/messages_be.dart b/mobile/lib/generated/intl/messages_be.dart index 14a690a0d1..a4a49d3b64 100644 --- a/mobile/lib/generated/intl/messages_be.dart +++ b/mobile/lib/generated/intl/messages_be.dart @@ -44,14 +44,10 @@ class MessageLookup extends MessageLookupByLibrary { "after1Week": MessageLookupByLibrary.simpleMessage("Праз 1 тыдзень"), "after1Year": MessageLookupByLibrary.simpleMessage("Праз 1 год"), "albumOwner": MessageLookupByLibrary.simpleMessage("Уладальнік"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "apply": MessageLookupByLibrary.simpleMessage("Ужыць"), "askDeleteReason": MessageLookupByLibrary.simpleMessage( "Якая асноўная прычына выдалення вашага ўліковага запісу?"), "backup": MessageLookupByLibrary.simpleMessage("Рэзервовая копія"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "cancel": MessageLookupByLibrary.simpleMessage("Скасаваць"), "change": MessageLookupByLibrary.simpleMessage("Змяніць"), "changeEmail": MessageLookupByLibrary.simpleMessage( @@ -132,8 +128,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Увядзіце свой пароль"), "enterYourRecoveryKey": MessageLookupByLibrary.simpleMessage( "Увядзіце свой ключ аднаўлення"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "familyPlans": MessageLookupByLibrary.simpleMessage("Сямейныя тарыфныя планы"), "faqs": MessageLookupByLibrary.simpleMessage("Частыя пытанні"), @@ -181,11 +175,6 @@ class MessageLookup extends MessageLookupByLibrary { "notifications": MessageLookupByLibrary.simpleMessage("Апавяшчэнні"), "ok": MessageLookupByLibrary.simpleMessage("Добра"), "oops": MessageLookupByLibrary.simpleMessage("Вой"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "password": MessageLookupByLibrary.simpleMessage("Пароль"), "passwordChangedSuccessfully": MessageLookupByLibrary.simpleMessage("Пароль паспяхова зменены"), @@ -231,8 +220,6 @@ class MessageLookup extends MessageLookupByLibrary { "saveKey": MessageLookupByLibrary.simpleMessage("Захаваць ключ"), "scanCode": MessageLookupByLibrary.simpleMessage("Сканіраваць код"), "security": MessageLookupByLibrary.simpleMessage("Бяспека"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "selectAll": MessageLookupByLibrary.simpleMessage("Абраць усё"), "selectReason": MessageLookupByLibrary.simpleMessage("Выберыце прычыну"), @@ -263,9 +250,6 @@ class MessageLookup extends MessageLookupByLibrary { "terminateSession": MessageLookupByLibrary.simpleMessage("Перарваць сеанс?"), "termsOfServicesTitle": MessageLookupByLibrary.simpleMessage("Умовы"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "theme": MessageLookupByLibrary.simpleMessage("Тема"), "thisDevice": MessageLookupByLibrary.simpleMessage("Гэта прылада"), "thisWillLogYouOutOfTheFollowingDevice": diff --git a/mobile/lib/generated/intl/messages_bg.dart b/mobile/lib/generated/intl/messages_bg.dart index 27537a5c4b..e887127f40 100644 --- a/mobile/lib/generated/intl/messages_bg.dart +++ b/mobile/lib/generated/intl/messages_bg.dart @@ -21,22 +21,5 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'bg'; final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired.") - }; + static Map _notInlinedMessages(_) => {}; } diff --git a/mobile/lib/generated/intl/messages_ca.dart b/mobile/lib/generated/intl/messages_ca.dart index 5d8a1d9b5d..84dea987b0 100644 --- a/mobile/lib/generated/intl/messages_ca.dart +++ b/mobile/lib/generated/intl/messages_ca.dart @@ -21,22 +21,5 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'ca'; final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired.") - }; + static Map _notInlinedMessages(_) => {}; } diff --git a/mobile/lib/generated/intl/messages_cs.dart b/mobile/lib/generated/intl/messages_cs.dart index 76fe7e756e..226e365e9c 100644 --- a/mobile/lib/generated/intl/messages_cs.dart +++ b/mobile/lib/generated/intl/messages_cs.dart @@ -22,26 +22,10 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "askDeleteReason": MessageLookupByLibrary.simpleMessage( "Jaký je váš hlavní důvod, proč mažete svůj účet?"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( "Zkontrolujte prosím svou doručenou poštu (a spam) pro dokončení ověření"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), - "incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage(""), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired.") + "incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage("") }; } diff --git a/mobile/lib/generated/intl/messages_da.dart b/mobile/lib/generated/intl/messages_da.dart index 75d753f23b..c47bf7b6c6 100644 --- a/mobile/lib/generated/intl/messages_da.dart +++ b/mobile/lib/generated/intl/messages_da.dart @@ -36,14 +36,10 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Aktive sessioner"), "addOnPageSubtitle": MessageLookupByLibrary.simpleMessage("Oplysninger om tilføjelser"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "askDeleteReason": MessageLookupByLibrary.simpleMessage( "Hvad er hovedårsagen til, at du sletter din konto?"), "backedUpFolders": MessageLookupByLibrary.simpleMessage("Sikkerhedskopierede mapper"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "cancel": MessageLookupByLibrary.simpleMessage("Annuller"), "confirmAccountDeletion": MessageLookupByLibrary.simpleMessage("Bekræft Sletning Af Konto"), @@ -80,8 +76,6 @@ class MessageLookup extends MessageLookupByLibrary { "Indtast venligst en gyldig email adresse."), "enterYourEmailAddress": MessageLookupByLibrary.simpleMessage("Indtast din email adresse"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "familyPlanPortalTitle": MessageLookupByLibrary.simpleMessage("Familie"), "feedback": MessageLookupByLibrary.simpleMessage("Feedback"), @@ -112,11 +106,6 @@ class MessageLookup extends MessageLookupByLibrary { "next": MessageLookupByLibrary.simpleMessage("Næste"), "ok": MessageLookupByLibrary.simpleMessage("Ok"), "oops": MessageLookupByLibrary.simpleMessage("Ups"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "password": MessageLookupByLibrary.simpleMessage("Adgangskode"), "pleaseContactSupportAndWeWillBeHappyToHelp": MessageLookupByLibrary.simpleMessage( @@ -127,8 +116,6 @@ class MessageLookup extends MessageLookupByLibrary { "Skan denne QR-kode med godkendelses-appen"), "searchHint1": MessageLookupByLibrary.simpleMessage("Hurtig, søgning på enheden"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "selectReason": MessageLookupByLibrary.simpleMessage("Vælg årsag"), "selectedPhotos": m4, "sendEmail": MessageLookupByLibrary.simpleMessage("Send email"), @@ -139,9 +126,6 @@ class MessageLookup extends MessageLookupByLibrary { "subscribe": MessageLookupByLibrary.simpleMessage("Abonner"), "terminateSession": MessageLookupByLibrary.simpleMessage("Afslut session?"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Dette vil logge dig ud af følgende enhed:"), diff --git a/mobile/lib/generated/intl/messages_de.dart b/mobile/lib/generated/intl/messages_de.dart index d04dac53cc..c2dd01d0fe 100644 --- a/mobile/lib/generated/intl/messages_de.dart +++ b/mobile/lib/generated/intl/messages_de.dart @@ -142,105 +142,108 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(providerName) => "Bitte kontaktiere den Support von ${providerName}, falls etwas abgebucht wurde"; - static String m47(endDate) => + static String m47(count) => + "${Intl.plural(count, zero: '0 Fotos', one: '1 Foto', other: '${count} Fotos')}"; + + static String m48(endDate) => "Kostenlose Testversion gültig bis ${endDate}.\nDu kannst anschließend ein bezahltes Paket auswählen."; - static String m48(toEmail) => "Bitte sende uns eine E-Mail an ${toEmail}"; + static String m49(toEmail) => "Bitte sende uns eine E-Mail an ${toEmail}"; - static String m49(toEmail) => "Bitte sende die Protokolle an ${toEmail}"; + static String m50(toEmail) => "Bitte sende die Protokolle an ${toEmail}"; - static String m50(folderName) => "Verarbeite ${folderName}..."; + static String m51(folderName) => "Verarbeite ${folderName}..."; - static String m51(storeName) => "Bewerte uns auf ${storeName}"; + static String m52(storeName) => "Bewerte uns auf ${storeName}"; - static String m52(storageInGB) => + static String m53(storageInGB) => "3. Ihr beide erhaltet ${storageInGB} GB* kostenlos"; - static String m53(userEmail) => + static String m54(userEmail) => "${userEmail} wird aus diesem geteilten Album entfernt\n\nAlle von ihnen hinzugefügte Fotos werden ebenfalls aus dem Album entfernt"; - static String m54(endDate) => "Erneuert am ${endDate}"; + static String m55(endDate) => "Erneuert am ${endDate}"; - static String m55(count) => + static String m56(count) => "${Intl.plural(count, one: '${count} Ergebnis gefunden', other: '${count} Ergebnisse gefunden')}"; - static String m56(snapshotLenght, searchLenght) => + static String m57(snapshotLenght, searchLenght) => "Abschnittslänge stimmt nicht überein: ${snapshotLenght} != ${searchLenght}"; static String m4(count) => "${count} ausgewählt"; - static String m57(count, yourCount) => + static String m58(count, yourCount) => "${count} ausgewählt (${yourCount} von Ihnen)"; - static String m58(verificationID) => + static String m59(verificationID) => "Hier ist meine Verifizierungs-ID: ${verificationID} für ente.io."; static String m5(verificationID) => "Hey, kannst du bestätigen, dass dies deine ente.io Verifizierungs-ID ist: ${verificationID}"; - static String m59(referralCode, referralStorageInGB) => + static String m60(referralCode, referralStorageInGB) => "Ente Weiterempfehlungs-Code: ${referralCode} \n\nEinlösen unter Einstellungen → Allgemein → Weiterempfehlungen, um ${referralStorageInGB} GB kostenlos zu erhalten, sobald Sie einen kostenpflichtigen Tarif abgeschlossen haben\n\nhttps://ente.io"; - static String m60(numberOfPeople) => + static String m61(numberOfPeople) => "${Intl.plural(numberOfPeople, zero: 'Teile mit bestimmten Personen', one: 'Teilen mit 1 Person', other: 'Teilen mit ${numberOfPeople} Personen')}"; - static String m61(emailIDs) => "Geteilt mit ${emailIDs}"; - - static String m62(fileType) => - "Dieses ${fileType} wird von deinem Gerät gelöscht."; + static String m62(emailIDs) => "Geteilt mit ${emailIDs}"; static String m63(fileType) => + "Dieses ${fileType} wird von deinem Gerät gelöscht."; + + static String m64(fileType) => "Diese Datei ist sowohl in Ente als auch auf deinem Gerät."; - static String m64(fileType) => "Diese Datei wird von Ente gelöscht."; + static String m65(fileType) => "Diese Datei wird von Ente gelöscht."; static String m1(storageAmountInGB) => "${storageAmountInGB} GB"; - static String m65( + static String m66( usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => "${usedAmount} ${usedStorageUnit} von ${totalAmount} ${totalStorageUnit} verwendet"; - static String m66(id) => + static String m67(id) => "Dein ${id} ist bereits mit einem anderen Ente-Konto verknüpft.\nWenn du deine ${id} mit diesem Konto verwenden möchtest, kontaktiere bitte unseren Support"; - static String m67(endDate) => "Dein Abo endet am ${endDate}"; + static String m68(endDate) => "Dein Abo endet am ${endDate}"; - static String m68(completed, total) => + static String m69(completed, total) => "${completed}/${total} Erinnerungsstücke gesichert"; - static String m69(ignoreReason) => + static String m70(ignoreReason) => "Zum Hochladen tippen, Hochladen wird derzeit ignoriert, da ${ignoreReason}"; - static String m70(storageAmountInGB) => + static String m71(storageAmountInGB) => "Diese erhalten auch ${storageAmountInGB} GB"; - static String m71(email) => "Dies ist ${email}s Verifizierungs-ID"; + static String m72(email) => "Dies ist ${email}s Verifizierungs-ID"; - static String m72(count) => - "${Intl.plural(count, zero: '', one: '1 Tag', other: '${count} Tage')}"; + static String m73(count) => + "${Intl.plural(count, zero: 'Demnächst', one: '1 Tag', other: '${count} Tage')}"; - static String m73(galleryType) => + static String m74(galleryType) => "Der Galerie-Typ ${galleryType} unterstützt kein Umbenennen"; - static String m74(ignoreReason) => + static String m75(ignoreReason) => "Upload wird aufgrund von ${ignoreReason} ignoriert"; - static String m75(count) => "Sichere ${count} Erinnerungsstücke..."; + static String m76(count) => "Sichere ${count} Erinnerungsstücke..."; - static String m76(endDate) => "Gültig bis ${endDate}"; + static String m77(endDate) => "Gültig bis ${endDate}"; - static String m77(email) => "Verifiziere ${email}"; + static String m78(email) => "Verifiziere ${email}"; - static String m78(count) => + static String m79(count) => "${Intl.plural(count, zero: '0 Betrachter hinzugefügt', one: '1 Betrachter hinzugefügt', other: '${count} Betrachter hinzugefügt')}"; static String m2(email) => "Wir haben eine E-Mail an ${email} gesendet"; - static String m79(count) => + static String m80(count) => "${Intl.plural(count, one: 'vor einem Jahr', other: 'vor ${count} Jahren')}"; - static String m80(storageSaved) => + static String m81(storageSaved) => "Du hast ${storageSaved} erfolgreich freigegeben!"; final messages = _notInlinedMessages(_notInlinedMessages); @@ -318,13 +321,13 @@ class MessageLookup extends MessageLookupByLibrary { "Alle Erinnerungsstücke gesichert"), "allPersonGroupingWillReset": MessageLookupByLibrary.simpleMessage( "Alle Gruppierungen für diese Person werden zurückgesetzt und du wirst alle Vorschläge für diese Person verlieren"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), + "allow": MessageLookupByLibrary.simpleMessage("Erlauben"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "Erlaube Nutzern, mit diesem Link ebenfalls Fotos zu diesem geteilten Album hinzuzufügen."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage( "Hinzufügen von Fotos erlauben"), "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), + "Erlaube der App, geteilte Album-Links zu öffnen"), "allowDownloads": MessageLookupByLibrary.simpleMessage("Downloads erlauben"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( @@ -440,7 +443,7 @@ class MessageLookup extends MessageLookupByLibrary { "backup": MessageLookupByLibrary.simpleMessage("Backup"), "backupFailed": MessageLookupByLibrary.simpleMessage("Sicherung fehlgeschlagen"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), + "backupFile": MessageLookupByLibrary.simpleMessage("Datei sichern"), "backupOverMobileData": MessageLookupByLibrary.simpleMessage("Über mobile Daten sichern"), "backupSettings": @@ -571,7 +574,7 @@ class MessageLookup extends MessageLookupByLibrary { "confirmAccountDeletion": MessageLookupByLibrary.simpleMessage("Kontolöschung bestätigen"), "confirmDeletePrompt": MessageLookupByLibrary.simpleMessage( - "Ja, ich möchte dieses Konto und alle enthaltenen Daten über alle Apps endgültig und unwiderruflich löschen."), + "Ja, ich möchte dieses Konto und alle enthaltenen Daten über alle Apps hinweg endgültig löschen."), "confirmPassword": MessageLookupByLibrary.simpleMessage("Passwort wiederholen"), "confirmPlanChange": @@ -658,7 +661,7 @@ class MessageLookup extends MessageLookupByLibrary { "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage( "Dieses Konto ist mit anderen Ente-Apps verknüpft, falls du welche verwendest. Deine hochgeladenen Daten werden in allen Ente-Apps zur Löschung vorgemerkt und dein Konto wird endgültig gelöscht."), "deleteEmailRequest": MessageLookupByLibrary.simpleMessage( - "Bitte sende eine E-Mail an account-deletion@ente.io von Deiner bei uns hinterlegten E-Mail-Adresse."), + "Bitte sende eine E-Mail an account-deletion@ente.io von Ihrer bei uns hinterlegten E-Mail-Adresse."), "deleteEmptyAlbums": MessageLookupByLibrary.simpleMessage("Leere Alben löschen"), "deleteEmptyAlbumsWithQuestionMark": @@ -800,7 +803,8 @@ class MessageLookup extends MessageLookupByLibrary { "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": MessageLookupByLibrary.simpleMessage( "Ente kann Dateien nur verschlüsseln und sichern, wenn du den Zugriff darauf gewährst"), - "entePhotosPerm": MessageLookupByLibrary.simpleMessage(""), + "entePhotosPerm": MessageLookupByLibrary.simpleMessage( + "Ente benötigt Berechtigung, um Ihre Fotos zu sichern"), "enteSubscriptionPitch": MessageLookupByLibrary.simpleMessage( "Ente sichert deine Erinnerungen, sodass sie dir nie verloren gehen, selbst wenn du dein Gerät verlierst."), "enteSubscriptionShareWithFamily": MessageLookupByLibrary.simpleMessage( @@ -852,6 +856,8 @@ class MessageLookup extends MessageLookupByLibrary { "extraPhotosFound": MessageLookupByLibrary.simpleMessage("Zusätzliche Fotos gefunden"), "extraPhotosFoundFor": m30, + "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( + "Gesicht ist noch nicht gruppiert, bitte komm später zurück"), "faceRecognition": MessageLookupByLibrary.simpleMessage("Gesichtserkennung"), "faces": MessageLookupByLibrary.simpleMessage("Gesichter"), @@ -1106,6 +1112,8 @@ class MessageLookup extends MessageLookupByLibrary { "Deine Sitzung ist abgelaufen. Bitte melde Dich erneut an."), "loginTerms": MessageLookupByLibrary.simpleMessage( "Mit dem Klick auf \"Anmelden\" stimme ich den Nutzungsbedingungen und der Datenschutzerklärung zu"), + "loginWithTOTP": + MessageLookupByLibrary.simpleMessage("Mit TOTP anmelden"), "logout": MessageLookupByLibrary.simpleMessage("Ausloggen"), "logsDialogBody": MessageLookupByLibrary.simpleMessage( "Dies wird über Logs gesendet, um uns zu helfen, Ihr Problem zu beheben. Bitte beachten Sie, dass Dateinamen aufgenommen werden, um Probleme mit bestimmten Dateien zu beheben."), @@ -1125,7 +1133,9 @@ class MessageLookup extends MessageLookupByLibrary { "Die magische Suche erlaubt das Durchsuchen von Fotos nach ihrem Inhalt, z.B. \'Blumen\', \'rotes Auto\', \'Ausweisdokumente\'"), "manage": MessageLookupByLibrary.simpleMessage("Verwalten"), "manageDeviceStorage": - MessageLookupByLibrary.simpleMessage("Gerätespeicher verwalten"), + MessageLookupByLibrary.simpleMessage("Geräte-Cache verwalten"), + "manageDeviceStorageDesc": MessageLookupByLibrary.simpleMessage( + "Lokalen Cache-Speicher überprüfen und löschen."), "manageFamily": MessageLookupByLibrary.simpleMessage("Familiengruppe verwalten"), "manageLink": MessageLookupByLibrary.simpleMessage("Link verwalten"), @@ -1246,10 +1256,10 @@ class MessageLookup extends MessageLookupByLibrary { "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage( "Ups. Leider ist ein Fehler aufgetreten"), "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), + MessageLookupByLibrary.simpleMessage("Album im Browser öffnen"), "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), + "Bitte nutze die Web-App, um Fotos zu diesem Album hinzuzufügen"), + "openFile": MessageLookupByLibrary.simpleMessage("Datei öffnen"), "openSettings": MessageLookupByLibrary.simpleMessage("Öffne Einstellungen"), "openTheItem": MessageLookupByLibrary.simpleMessage("• Element öffnen"), @@ -1310,13 +1320,14 @@ class MessageLookup extends MessageLookupByLibrary { "photosAddedByYouWillBeRemovedFromTheAlbum": MessageLookupByLibrary.simpleMessage( "Von dir hinzugefügte Fotos werden vom Album entfernt"), + "photosCount": m47, "pickCenterPoint": MessageLookupByLibrary.simpleMessage("Mittelpunkt auswählen"), "pinAlbum": MessageLookupByLibrary.simpleMessage("Album anheften"), "pinLock": MessageLookupByLibrary.simpleMessage("PIN-Sperre"), "playOnTv": MessageLookupByLibrary.simpleMessage( "Album auf dem Fernseher wiedergeben"), - "playStoreFreeTrialValidTill": m47, + "playStoreFreeTrialValidTill": m48, "playstoreSubscription": MessageLookupByLibrary.simpleMessage("PlayStore Abo"), "pleaseCheckYourInternetConnectionAndTryAgain": @@ -1328,14 +1339,14 @@ class MessageLookup extends MessageLookupByLibrary { "pleaseContactSupportIfTheProblemPersists": MessageLookupByLibrary.simpleMessage( "Bitte wenden Sie sich an den Support, falls das Problem weiterhin besteht"), - "pleaseEmailUsAt": m48, + "pleaseEmailUsAt": m49, "pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage( "Bitte erteile die nötigen Berechtigungen"), "pleaseLoginAgain": MessageLookupByLibrary.simpleMessage("Bitte logge dich erneut ein"), "pleaseSelectQuickLinksToRemove": MessageLookupByLibrary.simpleMessage( "Bitte wähle die zu entfernenden schnellen Links"), - "pleaseSendTheLogsTo": m49, + "pleaseSendTheLogsTo": m50, "pleaseTryAgain": MessageLookupByLibrary.simpleMessage("Bitte versuche es erneut"), "pleaseVerifyTheCodeYouHaveEntered": @@ -1362,7 +1373,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Private Sicherungen"), "privateSharing": MessageLookupByLibrary.simpleMessage("Privates Teilen"), - "processingImport": m50, + "processingImport": m51, "publicLinkCreated": MessageLookupByLibrary.simpleMessage("Öffentlicher Link erstellt"), "publicLinkEnabled": @@ -1372,7 +1383,7 @@ class MessageLookup extends MessageLookupByLibrary { "raiseTicket": MessageLookupByLibrary.simpleMessage("Ticket erstellen"), "rateTheApp": MessageLookupByLibrary.simpleMessage("App bewerten"), "rateUs": MessageLookupByLibrary.simpleMessage("Bewerte uns"), - "rateUsOnStore": m51, + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("Wiederherstellen"), "recoverAccount": MessageLookupByLibrary.simpleMessage("Konto wiederherstellen"), @@ -1409,7 +1420,7 @@ class MessageLookup extends MessageLookupByLibrary { "1. Gib diesen Code an deine Freunde"), "referralStep2": MessageLookupByLibrary.simpleMessage( "2. Sie schließen ein bezahltes Abo ab"), - "referralStep3": m52, + "referralStep3": m53, "referrals": MessageLookupByLibrary.simpleMessage("Weiterempfehlungen"), "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage( "Einlösungen sind derzeit pausiert"), @@ -1437,7 +1448,7 @@ class MessageLookup extends MessageLookupByLibrary { "removeLink": MessageLookupByLibrary.simpleMessage("Link entfernen"), "removeParticipant": MessageLookupByLibrary.simpleMessage("Teilnehmer entfernen"), - "removeParticipantBody": m53, + "removeParticipantBody": m54, "removePersonLabel": MessageLookupByLibrary.simpleMessage("Personenetikett entfernen"), "removePublicLink": @@ -1455,7 +1466,7 @@ class MessageLookup extends MessageLookupByLibrary { "renameFile": MessageLookupByLibrary.simpleMessage("Datei umbenennen"), "renewSubscription": MessageLookupByLibrary.simpleMessage("Abonnement erneuern"), - "renewsOn": m54, + "renewsOn": m55, "reportABug": MessageLookupByLibrary.simpleMessage("Fehler melden"), "reportBug": MessageLookupByLibrary.simpleMessage("Fehler melden"), "resendEmail": @@ -1534,11 +1545,11 @@ class MessageLookup extends MessageLookupByLibrary { "Laden Sie Personen ein, damit Sie geteilte Fotos hier einsehen können"), "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( "Personen werden hier angezeigt, sobald die Verarbeitung abgeschlossen ist"), - "searchResultCount": m55, - "searchSectionsLengthMismatch": m56, + "searchResultCount": m56, + "searchSectionsLengthMismatch": m57, "security": MessageLookupByLibrary.simpleMessage("Sicherheit"), "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), + "Öffentliche Album-Links in der App ansehen"), "selectALocation": MessageLookupByLibrary.simpleMessage("Standort auswählen"), "selectALocationFirst": @@ -1570,7 +1581,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Ausgewählte Elemente werden aus allen Alben gelöscht und in den Papierkorb verschoben."), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "send": MessageLookupByLibrary.simpleMessage("Absenden"), "sendEmail": MessageLookupByLibrary.simpleMessage("E-Mail senden"), "sendInvite": MessageLookupByLibrary.simpleMessage("Einladung senden"), @@ -1600,16 +1611,16 @@ class MessageLookup extends MessageLookupByLibrary { "shareAnAlbumNow": MessageLookupByLibrary.simpleMessage("Teile jetzt ein Album"), "shareLink": MessageLookupByLibrary.simpleMessage("Link teilen"), - "shareMyVerificationID": m58, + "shareMyVerificationID": m59, "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage( "Teile mit ausgewählten Personen"), "shareTextConfirmOthersVerificationID": m5, "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage( "Hol dir Ente, damit wir ganz einfach Fotos und Videos in Originalqualität teilen können\n\nhttps://ente.io"), - "shareTextReferralCode": m59, + "shareTextReferralCode": m60, "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage( "Mit Nicht-Ente-Benutzern teilen"), - "shareWithPeopleSectionTitle": m60, + "shareWithPeopleSectionTitle": m61, "shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage("Teile dein erstes Album"), "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( @@ -1620,7 +1631,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Neue geteilte Fotos"), "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( "Erhalte Benachrichtigungen, wenn jemand ein Foto zu einem gemeinsam genutzten Album hinzufügt, dem du angehörst"), - "sharedWith": m61, + "sharedWith": m62, "sharedWithMe": MessageLookupByLibrary.simpleMessage("Mit mir geteilt"), "sharedWithYou": MessageLookupByLibrary.simpleMessage("Mit dir geteilt"), @@ -1636,11 +1647,11 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Andere Geräte abmelden"), "signUpTerms": MessageLookupByLibrary.simpleMessage( "Ich stimme den Nutzungsbedingungen und der Datenschutzerklärung zu"), - "singleFileDeleteFromDevice": m62, + "singleFileDeleteFromDevice": m63, "singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage( "Es wird aus allen Alben gelöscht."), - "singleFileInBothLocalAndRemote": m63, - "singleFileInRemoteOnly": m64, + "singleFileInBothLocalAndRemote": m64, + "singleFileInRemoteOnly": m65, "skip": MessageLookupByLibrary.simpleMessage("Überspringen"), "social": MessageLookupByLibrary.simpleMessage("Social Media"), "someItemsAreInBothEnteAndYourDevice": @@ -1690,10 +1701,10 @@ class MessageLookup extends MessageLookupByLibrary { "storageInGB": m1, "storageLimitExceeded": MessageLookupByLibrary.simpleMessage( "Speichergrenze überschritten"), - "storageUsageInfo": m65, + "storageUsageInfo": m66, "strongStrength": MessageLookupByLibrary.simpleMessage("Stark"), - "subAlreadyLinkedErrMessage": m66, - "subWillBeCancelledOn": m67, + "subAlreadyLinkedErrMessage": m67, + "subWillBeCancelledOn": m68, "subscribe": MessageLookupByLibrary.simpleMessage("Abonnieren"), "subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage( "Du benötigst ein aktives, bezahltes Abonnement, um das Teilen zu aktivieren."), @@ -1710,7 +1721,7 @@ class MessageLookup extends MessageLookupByLibrary { "suggestFeatures": MessageLookupByLibrary.simpleMessage("Verbesserung vorschlagen"), "support": MessageLookupByLibrary.simpleMessage("Support"), - "syncProgress": m68, + "syncProgress": m69, "syncStopped": MessageLookupByLibrary.simpleMessage("Synchronisierung angehalten"), "syncing": MessageLookupByLibrary.simpleMessage("Synchronisiere …"), @@ -1723,7 +1734,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Zum Entsperren antippen"), "tapToUpload": MessageLookupByLibrary.simpleMessage("Zum Hochladen antippen"), - "tapToUploadIsIgnoredDue": m69, + "tapToUploadIsIgnoredDue": m70, "tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage( "Etwas ist schiefgelaufen. Bitte versuche es später noch einmal. Sollte der Fehler weiter bestehen, kontaktiere unser Supportteam."), "terminate": MessageLookupByLibrary.simpleMessage("Beenden"), @@ -1739,7 +1750,7 @@ class MessageLookup extends MessageLookupByLibrary { "Der Download konnte nicht abgeschlossen werden"), "theLinkYouAreTryingToAccessHasExpired": MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), + "Der Link, den du aufrufen möchtest, ist abgelaufen."), "theRecoveryKeyYouEnteredIsIncorrect": MessageLookupByLibrary.simpleMessage( "Der eingegebene Schlüssel ist ungültig"), @@ -1747,7 +1758,7 @@ class MessageLookup extends MessageLookupByLibrary { "theseItemsWillBeDeletedFromYourDevice": MessageLookupByLibrary.simpleMessage( "Diese Elemente werden von deinem Gerät gelöscht."), - "theyAlsoGetXGb": m70, + "theyAlsoGetXGb": m71, "theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage( "Sie werden aus allen Alben gelöscht."), "thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage( @@ -1763,7 +1774,7 @@ class MessageLookup extends MessageLookupByLibrary { "Diese E-Mail-Adresse wird bereits verwendet"), "thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage( "Dieses Bild hat keine Exif-Daten"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Dies ist deine Verifizierungs-ID"), "thisWillLogYouOutOfTheFollowingDevice": @@ -1788,7 +1799,7 @@ class MessageLookup extends MessageLookupByLibrary { "total": MessageLookupByLibrary.simpleMessage("Gesamt"), "totalSize": MessageLookupByLibrary.simpleMessage("Gesamtgröße"), "trash": MessageLookupByLibrary.simpleMessage("Papierkorb"), - "trashDaysLeft": m72, + "trashDaysLeft": m73, "trim": MessageLookupByLibrary.simpleMessage("Schneiden"), "tryAgain": MessageLookupByLibrary.simpleMessage("Erneut versuchen"), "turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage( @@ -1808,7 +1819,7 @@ class MessageLookup extends MessageLookupByLibrary { "Zwei-Faktor-Authentifizierung (2FA) erfolgreich zurückgesetzt"), "twofactorSetup": MessageLookupByLibrary.simpleMessage( "Zweiten Faktor (2FA) einrichten"), - "typeOfGallerGallerytypeIsNotSupportedForRename": m73, + "typeOfGallerGallerytypeIsNotSupportedForRename": m74, "unarchive": MessageLookupByLibrary.simpleMessage("Dearchivieren"), "unarchiveAlbum": MessageLookupByLibrary.simpleMessage("Album dearchivieren"), @@ -1832,10 +1843,10 @@ class MessageLookup extends MessageLookupByLibrary { "updatingFolderSelection": MessageLookupByLibrary.simpleMessage( "Ordnerauswahl wird aktualisiert..."), "upgrade": MessageLookupByLibrary.simpleMessage("Upgrade"), - "uploadIsIgnoredDueToIgnorereason": m74, + "uploadIsIgnoredDueToIgnorereason": m75, "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage( "Dateien werden ins Album hochgeladen..."), - "uploadingMultipleMemories": m75, + "uploadingMultipleMemories": m76, "uploadingSingleMemory": MessageLookupByLibrary.simpleMessage( "Sichere ein Erinnerungsstück..."), "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( @@ -1852,7 +1863,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Ausgewähltes Foto verwenden"), "usedSpace": MessageLookupByLibrary.simpleMessage("Belegter Speicherplatz"), - "validTill": m76, + "validTill": m77, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "Verifizierung fehlgeschlagen, bitte versuchen Sie es erneut"), @@ -1861,7 +1872,7 @@ class MessageLookup extends MessageLookupByLibrary { "verify": MessageLookupByLibrary.simpleMessage("Überprüfen"), "verifyEmail": MessageLookupByLibrary.simpleMessage("E-Mail-Adresse verifizieren"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("Überprüfen"), "verifyPasskey": MessageLookupByLibrary.simpleMessage("Passkey verifizieren"), @@ -1888,7 +1899,7 @@ class MessageLookup extends MessageLookupByLibrary { "viewRecoveryKey": MessageLookupByLibrary.simpleMessage( "Wiederherstellungsschlüssel anzeigen"), "viewer": MessageLookupByLibrary.simpleMessage("Zuschauer"), - "viewersSuccessfullyAdded": m78, + "viewersSuccessfullyAdded": m79, "visitWebToManage": MessageLookupByLibrary.simpleMessage( "Bitte rufe \"web.ente.io\" auf, um dein Abo zu verwalten"), "waitingForVerification": @@ -1907,7 +1918,7 @@ class MessageLookup extends MessageLookupByLibrary { "whatsNew": MessageLookupByLibrary.simpleMessage("Neue Funktionen"), "yearShort": MessageLookupByLibrary.simpleMessage("Jahr"), "yearly": MessageLookupByLibrary.simpleMessage("Jährlich"), - "yearsAgo": m79, + "yearsAgo": m80, "yes": MessageLookupByLibrary.simpleMessage("Ja"), "yesCancel": MessageLookupByLibrary.simpleMessage("Ja, kündigen"), "yesConvertToViewer": MessageLookupByLibrary.simpleMessage( @@ -1939,7 +1950,7 @@ class MessageLookup extends MessageLookupByLibrary { "Du kannst nicht mit dir selbst teilen"), "youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage( "Du hast keine archivierten Elemente."), - "youHaveSuccessfullyFreedUp": m80, + "youHaveSuccessfullyFreedUp": m81, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage( "Dein Benutzerkonto wurde gelöscht"), "yourMap": MessageLookupByLibrary.simpleMessage("Deine Karte"), diff --git a/mobile/lib/generated/intl/messages_el.dart b/mobile/lib/generated/intl/messages_el.dart index d813963065..79c0433b27 100644 --- a/mobile/lib/generated/intl/messages_el.dart +++ b/mobile/lib/generated/intl/messages_el.dart @@ -22,23 +22,7 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "enterYourEmailAddress": MessageLookupByLibrary.simpleMessage( - "Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired.") + "Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας") }; } diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index 6c85cba8b3..60afccfc47 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -59,9 +59,12 @@ class MessageLookup extends MessageLookupByLibrary { static String m18(albumName) => "Collaborative link created for ${albumName}"; - static String m81(count) => + static String m82(count) => "${Intl.plural(count, zero: 'Added 0 collaborator', one: 'Added 1 collaborator', other: 'Added ${count} collaborators')}"; + static String m83(email, numOfDays) => + "You are about to add ${email} as a trusted contact. They will be able to recover your account if you are absent for ${numOfDays} days."; + static String m19(familyAdminEmail) => "Please contact ${familyAdminEmail} to manage your subscription"; @@ -120,6 +123,9 @@ class MessageLookup extends MessageLookupByLibrary { static String m39(count) => "${Intl.plural(count, one: '${count} item', other: '${count} items')}"; + static String m84(email) => + "${email} has invited you to be a trusted contact"; + static String m40(expiryTime) => "Link will expire on ${expiryTime}"; static String m3(count, formattedCount) => @@ -143,107 +149,118 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(providerName) => "Please talk to ${providerName} support if you were charged"; - static String m82(count) => + static String m47(count) => "${Intl.plural(count, zero: '0 photo', one: '1 photo', other: '${count} photos')}"; - static String m47(endDate) => + static String m48(endDate) => "Free trial valid till ${endDate}.\nYou can choose a paid plan afterwards."; - static String m48(toEmail) => "Please email us at ${toEmail}"; + static String m49(toEmail) => "Please email us at ${toEmail}"; - static String m49(toEmail) => "Please send the logs to \n${toEmail}"; + static String m50(toEmail) => "Please send the logs to \n${toEmail}"; - static String m50(folderName) => "Processing ${folderName}..."; + static String m51(folderName) => "Processing ${folderName}..."; - static String m51(storeName) => "Rate us on ${storeName}"; + static String m52(storeName) => "Rate us on ${storeName}"; - static String m52(storageInGB) => + static String m85(days, email) => + "You can access the account after ${days} days. A notification will be sent to ${email}."; + + static String m86(email) => + "You can now recover ${email}\'s account by setting a new password."; + + static String m87(email) => "${email} is trying to recover your account."; + + static String m53(storageInGB) => "3. Both of you get ${storageInGB} GB* free"; - static String m53(userEmail) => + static String m54(userEmail) => "${userEmail} will be removed from this shared album\n\nAny photos added by them will also be removed from the album"; - static String m54(endDate) => "Subscription renews on ${endDate}"; + static String m55(endDate) => "Subscription renews on ${endDate}"; - static String m55(count) => + static String m56(count) => "${Intl.plural(count, one: '${count} result found', other: '${count} results found')}"; - static String m56(snapshotLenght, searchLenght) => + static String m57(snapshotLenght, searchLenght) => "Sections length mismatch: ${snapshotLenght} != ${searchLenght}"; static String m4(count) => "${count} selected"; - static String m57(count, yourCount) => + static String m58(count, yourCount) => "${count} selected (${yourCount} yours)"; - static String m58(verificationID) => + static String m59(verificationID) => "Here\'s my verification ID: ${verificationID} for ente.io."; static String m5(verificationID) => "Hey, can you confirm that this is your ente.io verification ID: ${verificationID}"; - static String m59(referralCode, referralStorageInGB) => + static String m60(referralCode, referralStorageInGB) => "Ente referral code: ${referralCode} \n\nApply it in Settings → General → Referrals to get ${referralStorageInGB} GB free after you signup for a paid plan\n\nhttps://ente.io"; - static String m60(numberOfPeople) => + static String m61(numberOfPeople) => "${Intl.plural(numberOfPeople, zero: 'Share with specific people', one: 'Shared with 1 person', other: 'Shared with ${numberOfPeople} people')}"; - static String m61(emailIDs) => "Shared with ${emailIDs}"; - - static String m62(fileType) => - "This ${fileType} will be deleted from your device."; + static String m62(emailIDs) => "Shared with ${emailIDs}"; static String m63(fileType) => + "This ${fileType} will be deleted from your device."; + + static String m64(fileType) => "This ${fileType} is in both Ente and your device."; - static String m64(fileType) => "This ${fileType} will be deleted from Ente."; + static String m65(fileType) => "This ${fileType} will be deleted from Ente."; static String m1(storageAmountInGB) => "${storageAmountInGB} GB"; - static String m65( + static String m66( usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => "${usedAmount} ${usedStorageUnit} of ${totalAmount} ${totalStorageUnit} used"; - static String m66(id) => + static String m67(id) => "Your ${id} is already linked to another Ente account.\nIf you would like to use your ${id} with this account, please contact our support\'\'"; - static String m67(endDate) => + static String m68(endDate) => "Your subscription will be cancelled on ${endDate}"; - static String m68(completed, total) => + static String m69(completed, total) => "${completed}/${total} memories preserved"; - static String m69(ignoreReason) => + static String m70(ignoreReason) => "Tap to upload, upload is currently ignored due to ${ignoreReason}"; - static String m70(storageAmountInGB) => + static String m71(storageAmountInGB) => "They also get ${storageAmountInGB} GB"; - static String m71(email) => "This is ${email}\'s Verification ID"; + static String m72(email) => "This is ${email}\'s Verification ID"; - static String m72(count) => + static String m73(count) => "${Intl.plural(count, zero: 'Soon', one: '1 day', other: '${count} days')}"; - static String m73(galleryType) => + static String m88(email) => + "You have been invited to be a legacy contact by ${email}."; + + static String m74(galleryType) => "Type of gallery ${galleryType} is not supported for rename"; - static String m74(ignoreReason) => "Upload is ignored due to ${ignoreReason}"; + static String m75(ignoreReason) => "Upload is ignored due to ${ignoreReason}"; - static String m75(count) => "Preserving ${count} memories..."; + static String m76(count) => "Preserving ${count} memories..."; - static String m76(endDate) => "Valid till ${endDate}"; + static String m77(endDate) => "Valid till ${endDate}"; - static String m77(email) => "Verify ${email}"; + static String m78(email) => "Verify ${email}"; - static String m78(count) => + static String m79(count) => "${Intl.plural(count, zero: 'Added 0 viewer', one: 'Added 1 viewer', other: 'Added ${count} viewers')}"; static String m2(email) => "We have sent a mail to ${email}"; - static String m79(count) => + static String m80(count) => "${Intl.plural(count, one: '${count} year ago', other: '${count} years ago')}"; - static String m80(storageSaved) => + static String m81(storageSaved) => "You have successfully freed up ${storageSaved}!"; final messages = _notInlinedMessages(_notInlinedMessages); @@ -251,6 +268,8 @@ class MessageLookup extends MessageLookupByLibrary { "aNewVersionOfEnteIsAvailable": MessageLookupByLibrary.simpleMessage( "A new version of Ente is available."), "about": MessageLookupByLibrary.simpleMessage("About"), + "acceptTrustInvite": + MessageLookupByLibrary.simpleMessage("Accept Invite"), "account": MessageLookupByLibrary.simpleMessage("Account"), "accountIsAlreadyConfigured": MessageLookupByLibrary.simpleMessage( "Account is already configured."), @@ -288,6 +307,8 @@ class MessageLookup extends MessageLookupByLibrary { "addToEnte": MessageLookupByLibrary.simpleMessage("Add to Ente"), "addToHiddenAlbum": MessageLookupByLibrary.simpleMessage("Add to hidden album"), + "addTrustedContact": + MessageLookupByLibrary.simpleMessage("Add Trusted Contact"), "addViewer": MessageLookupByLibrary.simpleMessage("Add viewer"), "addViewers": m9, "addYourPhotosNow": @@ -395,6 +416,8 @@ class MessageLookup extends MessageLookupByLibrary { "Please authenticate to configure two-factor authentication"), "authToInitiateAccountDeletion": MessageLookupByLibrary.simpleMessage( "Please authenticate to initiate account deletion"), + "authToManageLegacy": MessageLookupByLibrary.simpleMessage( + "Please authenticate to manage your trusted contacts"), "authToViewPasskey": MessageLookupByLibrary.simpleMessage( "Please authenticate to view your passkey"), "authToViewYourActiveSessions": MessageLookupByLibrary.simpleMessage( @@ -454,6 +477,10 @@ class MessageLookup extends MessageLookupByLibrary { "canOnlyRemoveFilesOwnedByYou": MessageLookupByLibrary.simpleMessage( "Can only remove files owned by you"), "cancel": MessageLookupByLibrary.simpleMessage("Cancel"), + "cancelAccountRecovery": + MessageLookupByLibrary.simpleMessage("Cancel recovery"), + "cancelAccountRecoveryBody": MessageLookupByLibrary.simpleMessage( + "Are you sure you want to cancel recovery?"), "cancelOtherSubscription": m15, "cancelSubscription": MessageLookupByLibrary.simpleMessage("Cancel subscription"), @@ -539,7 +566,7 @@ class MessageLookup extends MessageLookupByLibrary { "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": MessageLookupByLibrary.simpleMessage( "Collaborators can add photos and videos to the shared album."), - "collaboratorsSuccessfullyAdded": m81, + "collaboratorsSuccessfullyAdded": m82, "collageLayout": MessageLookupByLibrary.simpleMessage("Layout"), "collageSaved": MessageLookupByLibrary.simpleMessage("Collage saved to gallery"), @@ -556,6 +583,7 @@ class MessageLookup extends MessageLookupByLibrary { "Are you sure you want to disable two-factor authentication?"), "confirmAccountDeletion": MessageLookupByLibrary.simpleMessage("Confirm Account Deletion"), + "confirmAddingTrustedContact": m83, "confirmDeletePrompt": MessageLookupByLibrary.simpleMessage( "Yes, I want to permanently delete this account and its data across all apps."), "confirmPassword": @@ -621,6 +649,8 @@ class MessageLookup extends MessageLookupByLibrary { "darkTheme": MessageLookupByLibrary.simpleMessage("Dark"), "dayToday": MessageLookupByLibrary.simpleMessage("Today"), "dayYesterday": MessageLookupByLibrary.simpleMessage("Yesterday"), + "declineTrustInvite": + MessageLookupByLibrary.simpleMessage("Decline Invite"), "decrypting": MessageLookupByLibrary.simpleMessage("Decrypting..."), "decryptingVideo": MessageLookupByLibrary.simpleMessage("Decrypting video..."), @@ -756,6 +786,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Email verification"), "emailYourLogs": MessageLookupByLibrary.simpleMessage("Email your logs"), + "emergencyContacts": + MessageLookupByLibrary.simpleMessage("Emergency Contacts"), "empty": MessageLookupByLibrary.simpleMessage("Empty"), "emptyTrash": MessageLookupByLibrary.simpleMessage("Empty trash?"), "enable": MessageLookupByLibrary.simpleMessage("Enable"), @@ -1009,6 +1041,14 @@ class MessageLookup extends MessageLookupByLibrary { "leaveSharedAlbum": MessageLookupByLibrary.simpleMessage("Leave shared album?"), "left": MessageLookupByLibrary.simpleMessage("Left"), + "legacy": MessageLookupByLibrary.simpleMessage("Legacy"), + "legacyAccounts": + MessageLookupByLibrary.simpleMessage("Legacy accounts"), + "legacyInvite": m84, + "legacyPageDesc": MessageLookupByLibrary.simpleMessage( + "Legacy allows trusted contacts to access your account in your absence."), + "legacyPageDesc2": MessageLookupByLibrary.simpleMessage( + "Trusted contacts can initiate account recovery, and if not blocked within 30 days, reset your password and access your account."), "light": MessageLookupByLibrary.simpleMessage("Light"), "lightTheme": MessageLookupByLibrary.simpleMessage("Light"), "linkCopiedToClipboard": @@ -1266,13 +1306,13 @@ class MessageLookup extends MessageLookupByLibrary { "photosAddedByYouWillBeRemovedFromTheAlbum": MessageLookupByLibrary.simpleMessage( "Photos added by you will be removed from the album"), - "photosCount": m82, + "photosCount": m47, "pickCenterPoint": MessageLookupByLibrary.simpleMessage("Pick center point"), "pinAlbum": MessageLookupByLibrary.simpleMessage("Pin album"), "pinLock": MessageLookupByLibrary.simpleMessage("PIN lock"), "playOnTv": MessageLookupByLibrary.simpleMessage("Play album on TV"), - "playStoreFreeTrialValidTill": m47, + "playStoreFreeTrialValidTill": m48, "playstoreSubscription": MessageLookupByLibrary.simpleMessage("PlayStore subscription"), "pleaseCheckYourInternetConnectionAndTryAgain": @@ -1284,14 +1324,14 @@ class MessageLookup extends MessageLookupByLibrary { "pleaseContactSupportIfTheProblemPersists": MessageLookupByLibrary.simpleMessage( "Please contact support if the problem persists"), - "pleaseEmailUsAt": m48, + "pleaseEmailUsAt": m49, "pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage("Please grant permissions"), "pleaseLoginAgain": MessageLookupByLibrary.simpleMessage("Please login again"), "pleaseSelectQuickLinksToRemove": MessageLookupByLibrary.simpleMessage( "Please select quick links to remove"), - "pleaseSendTheLogsTo": m49, + "pleaseSendTheLogsTo": m50, "pleaseTryAgain": MessageLookupByLibrary.simpleMessage("Please try again"), "pleaseVerifyTheCodeYouHaveEntered": @@ -1317,7 +1357,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Private backups"), "privateSharing": MessageLookupByLibrary.simpleMessage("Private sharing"), - "processingImport": m50, + "proceed": MessageLookupByLibrary.simpleMessage("Proceed"), + "processingImport": m51, "publicLinkCreated": MessageLookupByLibrary.simpleMessage("Public link created"), "publicLinkEnabled": @@ -1327,11 +1368,16 @@ class MessageLookup extends MessageLookupByLibrary { "raiseTicket": MessageLookupByLibrary.simpleMessage("Raise ticket"), "rateTheApp": MessageLookupByLibrary.simpleMessage("Rate the app"), "rateUs": MessageLookupByLibrary.simpleMessage("Rate us"), - "rateUsOnStore": m51, + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("Recover"), "recoverAccount": MessageLookupByLibrary.simpleMessage("Recover account"), "recoverButton": MessageLookupByLibrary.simpleMessage("Recover"), + "recoveryAccount": + MessageLookupByLibrary.simpleMessage("Recover account"), + "recoveryInitiated": + MessageLookupByLibrary.simpleMessage("Recovery initiated"), + "recoveryInitiatedDesc": m85, "recoveryKey": MessageLookupByLibrary.simpleMessage("Recovery key"), "recoveryKeyCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "Recovery key copied to clipboard"), @@ -1345,8 +1391,12 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Recovery key verified"), "recoveryKeyVerifyReason": MessageLookupByLibrary.simpleMessage( "Your recovery key is the only way to recover your photos if you forget your password. You can find your recovery key in Settings > Account.\n\nPlease enter your recovery key here to verify that you have saved it correctly."), + "recoveryReady": m86, "recoverySuccessful": MessageLookupByLibrary.simpleMessage("Recovery successful!"), + "recoveryWarning": MessageLookupByLibrary.simpleMessage( + "A trusted contact is trying to access your account"), + "recoveryWarningBody": m87, "recreatePasswordBody": MessageLookupByLibrary.simpleMessage( "The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish)."), "recreatePasswordTitle": @@ -1361,10 +1411,12 @@ class MessageLookup extends MessageLookupByLibrary { "1. Give this code to your friends"), "referralStep2": MessageLookupByLibrary.simpleMessage( "2. They sign up for a paid plan"), - "referralStep3": m52, + "referralStep3": m53, "referrals": MessageLookupByLibrary.simpleMessage("Referrals"), "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage( "Referrals are currently paused"), + "rejectRecovery": + MessageLookupByLibrary.simpleMessage("Reject recovery"), "remindToEmptyDeviceTrash": MessageLookupByLibrary.simpleMessage( "Also empty \"Recently Deleted\" from \"Settings\" -> \"Storage\" to claim the freed space"), "remindToEmptyEnteTrash": MessageLookupByLibrary.simpleMessage( @@ -1384,10 +1436,11 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Remove from album?"), "removeFromFavorite": MessageLookupByLibrary.simpleMessage("Remove from favorites"), + "removeInvite": MessageLookupByLibrary.simpleMessage("Remove invite"), "removeLink": MessageLookupByLibrary.simpleMessage("Remove link"), "removeParticipant": MessageLookupByLibrary.simpleMessage("Remove participant"), - "removeParticipantBody": m53, + "removeParticipantBody": m54, "removePersonLabel": MessageLookupByLibrary.simpleMessage("Remove person label"), "removePublicLink": @@ -1398,6 +1451,8 @@ class MessageLookup extends MessageLookupByLibrary { "Some of the items you are removing were added by other people, and you will lose access to them"), "removeWithQuestionMark": MessageLookupByLibrary.simpleMessage("Remove?"), + "removeYourselfAsTrustedContact": MessageLookupByLibrary.simpleMessage( + "Remove yourself as trusted contact"), "removingFromFavorites": MessageLookupByLibrary.simpleMessage("Removing from favorites..."), "rename": MessageLookupByLibrary.simpleMessage("Rename"), @@ -1405,7 +1460,7 @@ class MessageLookup extends MessageLookupByLibrary { "renameFile": MessageLookupByLibrary.simpleMessage("Rename file"), "renewSubscription": MessageLookupByLibrary.simpleMessage("Renew subscription"), - "renewsOn": m54, + "renewsOn": m55, "reportABug": MessageLookupByLibrary.simpleMessage("Report a bug"), "reportBug": MessageLookupByLibrary.simpleMessage("Report bug"), "resendEmail": MessageLookupByLibrary.simpleMessage("Resend email"), @@ -1480,8 +1535,8 @@ class MessageLookup extends MessageLookupByLibrary { "Invite people, and you\'ll see all photos shared by them here"), "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( "People will be shown here once processing is complete"), - "searchResultCount": m55, - "searchSectionsLengthMismatch": m56, + "searchResultCount": m56, + "searchSectionsLengthMismatch": m57, "security": MessageLookupByLibrary.simpleMessage("Security"), "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( "See public album links in app"), @@ -1516,7 +1571,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Selected items will be deleted from all albums and moved to trash."), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "send": MessageLookupByLibrary.simpleMessage("Send"), "sendEmail": MessageLookupByLibrary.simpleMessage("Send email"), "sendInvite": MessageLookupByLibrary.simpleMessage("Send invite"), @@ -1545,16 +1600,16 @@ class MessageLookup extends MessageLookupByLibrary { "shareAnAlbumNow": MessageLookupByLibrary.simpleMessage("Share an album now"), "shareLink": MessageLookupByLibrary.simpleMessage("Share link"), - "shareMyVerificationID": m58, + "shareMyVerificationID": m59, "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage( "Share only with the people you want"), "shareTextConfirmOthersVerificationID": m5, "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage( "Download Ente so we can easily share original quality photos and videos\n\nhttps://ente.io"), - "shareTextReferralCode": m59, + "shareTextReferralCode": m60, "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage("Share with non-Ente users"), - "shareWithPeopleSectionTitle": m60, + "shareWithPeopleSectionTitle": m61, "shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage("Share your first album"), "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( @@ -1565,7 +1620,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("New shared photos"), "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( "Receive notifications when someone adds a photo to a shared album that you\'re a part of"), - "sharedWith": m61, + "sharedWith": m62, "sharedWithMe": MessageLookupByLibrary.simpleMessage("Shared with me"), "sharedWithYou": MessageLookupByLibrary.simpleMessage("Shared with you"), @@ -1580,11 +1635,11 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Sign out other devices"), "signUpTerms": MessageLookupByLibrary.simpleMessage( "I agree to the terms of service and privacy policy"), - "singleFileDeleteFromDevice": m62, + "singleFileDeleteFromDevice": m63, "singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage( "It will be deleted from all albums."), - "singleFileInBothLocalAndRemote": m63, - "singleFileInRemoteOnly": m64, + "singleFileInBothLocalAndRemote": m64, + "singleFileInRemoteOnly": m65, "skip": MessageLookupByLibrary.simpleMessage("Skip"), "social": MessageLookupByLibrary.simpleMessage("Social"), "someItemsAreInBothEnteAndYourDevice": @@ -1618,6 +1673,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortNewestFirst": MessageLookupByLibrary.simpleMessage("Newest first"), "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Oldest first"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Success"), + "startAccountRecoveryTitle": + MessageLookupByLibrary.simpleMessage("Start recovery"), "startBackup": MessageLookupByLibrary.simpleMessage("Start backup"), "status": MessageLookupByLibrary.simpleMessage("Status"), "stopCastingBody": MessageLookupByLibrary.simpleMessage( @@ -1630,10 +1687,10 @@ class MessageLookup extends MessageLookupByLibrary { "storageInGB": m1, "storageLimitExceeded": MessageLookupByLibrary.simpleMessage("Storage limit exceeded"), - "storageUsageInfo": m65, + "storageUsageInfo": m66, "strongStrength": MessageLookupByLibrary.simpleMessage("Strong"), - "subAlreadyLinkedErrMessage": m66, - "subWillBeCancelledOn": m67, + "subAlreadyLinkedErrMessage": m67, + "subWillBeCancelledOn": m68, "subscribe": MessageLookupByLibrary.simpleMessage("Subscribe"), "subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage( "You need an active paid subscription to enable sharing."), @@ -1650,7 +1707,7 @@ class MessageLookup extends MessageLookupByLibrary { "suggestFeatures": MessageLookupByLibrary.simpleMessage("Suggest features"), "support": MessageLookupByLibrary.simpleMessage("Support"), - "syncProgress": m68, + "syncProgress": m69, "syncStopped": MessageLookupByLibrary.simpleMessage("Sync stopped"), "syncing": MessageLookupByLibrary.simpleMessage("Syncing..."), "systemTheme": MessageLookupByLibrary.simpleMessage("System"), @@ -1659,7 +1716,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Tap to enter code"), "tapToUnlock": MessageLookupByLibrary.simpleMessage("Tap to unlock"), "tapToUpload": MessageLookupByLibrary.simpleMessage("Tap to upload"), - "tapToUploadIsIgnoredDue": m69, + "tapToUploadIsIgnoredDue": m70, "tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage( "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team."), "terminate": MessageLookupByLibrary.simpleMessage("Terminate"), @@ -1682,7 +1739,7 @@ class MessageLookup extends MessageLookupByLibrary { "theseItemsWillBeDeletedFromYourDevice": MessageLookupByLibrary.simpleMessage( "These items will be deleted from your device."), - "theyAlsoGetXGb": m70, + "theyAlsoGetXGb": m71, "theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage( "They will be deleted from all albums."), "thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage( @@ -1698,7 +1755,7 @@ class MessageLookup extends MessageLookupByLibrary { "This email is already in use"), "thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage("This image has no exif data"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "This is your Verification ID"), "thisWillLogYouOutOfTheFollowingDevice": @@ -1722,8 +1779,11 @@ class MessageLookup extends MessageLookupByLibrary { "total": MessageLookupByLibrary.simpleMessage("total"), "totalSize": MessageLookupByLibrary.simpleMessage("Total size"), "trash": MessageLookupByLibrary.simpleMessage("Trash"), - "trashDaysLeft": m72, + "trashDaysLeft": m73, "trim": MessageLookupByLibrary.simpleMessage("Trim"), + "trustedContacts": + MessageLookupByLibrary.simpleMessage("Trusted contacts"), + "trustedInviteBody": m88, "tryAgain": MessageLookupByLibrary.simpleMessage("Try again"), "turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage( "Turn on backup to automatically upload files added to this device folder to Ente."), @@ -1741,7 +1801,7 @@ class MessageLookup extends MessageLookupByLibrary { "Two-factor authentication successfully reset"), "twofactorSetup": MessageLookupByLibrary.simpleMessage("Two-factor setup"), - "typeOfGallerGallerytypeIsNotSupportedForRename": m73, + "typeOfGallerGallerytypeIsNotSupportedForRename": m74, "unarchive": MessageLookupByLibrary.simpleMessage("Unarchive"), "unarchiveAlbum": MessageLookupByLibrary.simpleMessage("Unarchive album"), @@ -1764,10 +1824,10 @@ class MessageLookup extends MessageLookupByLibrary { "updatingFolderSelection": MessageLookupByLibrary.simpleMessage( "Updating folder selection..."), "upgrade": MessageLookupByLibrary.simpleMessage("Upgrade"), - "uploadIsIgnoredDueToIgnorereason": m74, + "uploadIsIgnoredDueToIgnorereason": m75, "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage("Uploading files to album..."), - "uploadingMultipleMemories": m75, + "uploadingMultipleMemories": m76, "uploadingSingleMemory": MessageLookupByLibrary.simpleMessage("Preserving 1 memory..."), "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( @@ -1783,7 +1843,7 @@ class MessageLookup extends MessageLookupByLibrary { "useSelectedPhoto": MessageLookupByLibrary.simpleMessage("Use selected photo"), "usedSpace": MessageLookupByLibrary.simpleMessage("Used space"), - "validTill": m76, + "validTill": m77, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "Verification failed, please try again"), @@ -1791,7 +1851,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Verification ID"), "verify": MessageLookupByLibrary.simpleMessage("Verify"), "verifyEmail": MessageLookupByLibrary.simpleMessage("Verify email"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("Verify"), "verifyPasskey": MessageLookupByLibrary.simpleMessage("Verify passkey"), "verifyPassword": @@ -1815,13 +1875,14 @@ class MessageLookup extends MessageLookupByLibrary { "viewRecoveryKey": MessageLookupByLibrary.simpleMessage("View recovery key"), "viewer": MessageLookupByLibrary.simpleMessage("Viewer"), - "viewersSuccessfullyAdded": m78, + "viewersSuccessfullyAdded": m79, "visitWebToManage": MessageLookupByLibrary.simpleMessage( "Please visit web.ente.io to manage your subscription"), "waitingForVerification": MessageLookupByLibrary.simpleMessage("Waiting for verification..."), "waitingForWifi": MessageLookupByLibrary.simpleMessage("Waiting for WiFi..."), + "warning": MessageLookupByLibrary.simpleMessage("Warning"), "weAreOpenSource": MessageLookupByLibrary.simpleMessage("We are open source!"), "weDontSupportEditingPhotosAndAlbumsThatYouDont": @@ -1831,9 +1892,11 @@ class MessageLookup extends MessageLookupByLibrary { "weakStrength": MessageLookupByLibrary.simpleMessage("Weak"), "welcomeBack": MessageLookupByLibrary.simpleMessage("Welcome back!"), "whatsNew": MessageLookupByLibrary.simpleMessage("What\'s new"), + "whyAddTrustContact": MessageLookupByLibrary.simpleMessage( + "Trusted contact can help in recovering your data."), "yearShort": MessageLookupByLibrary.simpleMessage("yr"), "yearly": MessageLookupByLibrary.simpleMessage("Yearly"), - "yearsAgo": m79, + "yearsAgo": m80, "yes": MessageLookupByLibrary.simpleMessage("Yes"), "yesCancel": MessageLookupByLibrary.simpleMessage("Yes, cancel"), "yesConvertToViewer": @@ -1865,7 +1928,7 @@ class MessageLookup extends MessageLookupByLibrary { "You cannot share with yourself"), "youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage( "You don\'t have any archived items."), - "youHaveSuccessfullyFreedUp": m80, + "youHaveSuccessfullyFreedUp": m81, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage( "Your account has been deleted"), "yourMap": MessageLookupByLibrary.simpleMessage("Your map"), diff --git a/mobile/lib/generated/intl/messages_es.dart b/mobile/lib/generated/intl/messages_es.dart index 7d3ba12e84..5ae5b6b16d 100644 --- a/mobile/lib/generated/intl/messages_es.dart +++ b/mobile/lib/generated/intl/messages_es.dart @@ -53,16 +53,18 @@ class MessageLookup extends MessageLookupByLibrary { static String m17(isFamilyMember, storageAmountInGb) => "${Intl.select(isFamilyMember, { 'true': - 'Tu familia ha reclamado ${storageAmountInGb} GB hasta el momento', - 'false': - 'Tú has reclamado ${storageAmountInGb} GB hasta el momento', + 'Tu familia ha obtenido ${storageAmountInGb} GB hasta el momento', + 'false': 'Tú has obtenido ${storageAmountInGb} GB hasta el momento', 'other': - '¡Tú has reclamado ${storageAmountInGb} GB hasta el momento!', + '¡Tú has obtenido ${storageAmountInGb} GB hasta el momento!', })}"; static String m18(albumName) => "Enlace colaborativo creado para ${albumName}"; + static String m82(count) => + "${Intl.plural(count, zero: '0 colaboradores añadidos', one: '1 colaborador añadido', other: '${count} colaboradores añadidos')}"; + static String m19(familyAdminEmail) => "Por favor contacta con ${familyAdminEmail} para administrar tu suscripción"; @@ -94,11 +96,13 @@ class MessageLookup extends MessageLookupByLibrary { static String m29(email) => "${email} no tiene una cuente en Ente.\n\nEnvíale una invitación para compartir fotos."; + static String m30(text) => "Fotos adicionales encontradas para ${text}"; + static String m31(count, formattedNumber) => - "${Intl.plural(count, one: '1 archivo', other: '${formattedNumber} archivos')} en este dispositivo han sido respaldados de forma segura"; + "Se ha realizado la copia de seguridad de ${Intl.plural(count, one: '1 archivo', other: '${formattedNumber} archivos')} de este dispositivo de forma segura"; static String m32(count, formattedNumber) => - "${Intl.plural(count, one: '1 archivo', other: '${formattedNumber} archivos')} en este álbum ha sido respaldado de forma segura"; + "Se ha realizado la copia de seguridad de ${Intl.plural(count, one: '1 archivo', other: '${formattedNumber} archivos')} de este álbum de forma segura"; static String m33(storageAmountInGB) => "${storageAmountInGB} GB cada vez que alguien se registra en un plan de pago y aplica tu código"; @@ -129,6 +133,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m42(albumName) => "Movido exitosamente a ${albumName}"; + static String m43(personName) => "No hay sugerencias para ${personName}"; + static String m44(name) => "¿No es ${name}?"; static String m45(familyAdminEmail) => @@ -140,91 +146,109 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(providerName) => "Por favor, habla con el soporte de ${providerName} si se te cobró"; - static String m47(endDate) => + static String m47(count) => + "${Intl.plural(count, zero: '0 fotos', one: '1 foto', other: '${count} fotos')}"; + + static String m48(endDate) => "Prueba gratuita válida hasta ${endDate}.\nPuedes elegir un plan de pago después."; - static String m48(toEmail) => + static String m49(toEmail) => "Por favor, envíanos un correo electrónico a ${toEmail}"; - static String m49(toEmail) => "Por favor, envía los registros a ${toEmail}"; + static String m50(toEmail) => "Por favor, envía los registros a ${toEmail}"; - static String m50(folderName) => "Procesando ${folderName}..."; + static String m51(folderName) => "Procesando ${folderName}..."; - static String m51(storeName) => "Califícanos en ${storeName}"; + static String m52(storeName) => "Puntúanos en ${storeName}"; - static String m52(storageInGB) => + static String m53(storageInGB) => "3. Ambos obtienen ${storageInGB} GB* gratis"; - static String m53(userEmail) => + static String m54(userEmail) => "${userEmail} será eliminado de este álbum compartido\n\nCualquier foto añadida por ellos también será eliminada del álbum"; - static String m54(endDate) => "La suscripción se renueva el ${endDate}"; + static String m55(endDate) => "La suscripción se renueva el ${endDate}"; - static String m55(count) => + static String m56(count) => "${Intl.plural(count, one: '${count} resultado encontrado', other: '${count} resultados encontrados')}"; + static String m57(snapshotLenght, searchLenght) => + "La longitud de las secciones no coincide: ${snapshotLenght} != ${searchLenght}"; + static String m4(count) => "${count} seleccionados"; - static String m57(count, yourCount) => + static String m58(count, yourCount) => "${count} seleccionados (${yourCount} tuyos)"; - static String m58(verificationID) => + static String m59(verificationID) => "Aquí está mi ID de verificación: ${verificationID} para ente.io."; static String m5(verificationID) => "Hola, ¿puedes confirmar que esta es tu ID de verificación ente.io: ${verificationID}?"; - static String m59(referralCode, referralStorageInGB) => + static String m60(referralCode, referralStorageInGB) => "Código de referido de Ente: ${referralCode} \n\nAñádelo en Ajustes → General → Referidos para obtener ${referralStorageInGB} GB gratis tras comprar un plan de pago.\n\nhttps://ente.io"; - static String m60(numberOfPeople) => + static String m61(numberOfPeople) => "${Intl.plural(numberOfPeople, zero: 'Compartir con personas específicas', one: 'Compartido con 1 persona', other: 'Compartido con ${numberOfPeople} personas')}"; - static String m61(emailIDs) => "Compartido con ${emailIDs}"; - - static String m62(fileType) => - "Este ${fileType} se eliminará de tu dispositivo."; + static String m62(emailIDs) => "Compartido con ${emailIDs}"; static String m63(fileType) => + "Este ${fileType} se eliminará de tu dispositivo."; + + static String m64(fileType) => "Este ${fileType} está tanto en Ente como en tu dispositivo."; - static String m64(fileType) => "Este ${fileType} será eliminado de Ente."; + static String m65(fileType) => "Este ${fileType} será eliminado de Ente."; static String m1(storageAmountInGB) => "${storageAmountInGB} GB"; - static String m65( + static String m66( usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => "${usedAmount} ${usedStorageUnit} de ${totalAmount} ${totalStorageUnit} usados"; - static String m66(id) => + static String m67(id) => "Tu ${id} ya está vinculada a otra cuenta de Ente.\nSi deseas utilizar tu ${id} con esta cuenta, ponte en contacto con nuestro servicio de asistencia\'\'"; - static String m67(endDate) => "Tu suscripción se cancelará el ${endDate}"; + static String m68(endDate) => "Tu suscripción se cancelará el ${endDate}"; - static String m68(completed, total) => + static String m69(completed, total) => "${completed}/${total} recuerdos conservados"; - static String m70(storageAmountInGB) => + static String m70(ignoreReason) => + "Toca para subir, la subida se está ignorando debido a ${ignoreReason}"; + + static String m71(storageAmountInGB) => "También obtienen ${storageAmountInGB} GB"; - static String m71(email) => "Este es el ID de verificación de ${email}"; + static String m72(email) => "Este es el ID de verificación de ${email}"; - static String m72(count) => - "${Intl.plural(count, zero: '', one: '1 día', other: '${count} días')}"; + static String m73(count) => + "${Intl.plural(count, zero: 'Pronto', one: '1 día', other: '${count} días')}"; - static String m75(count) => "Preservando ${count} memorias..."; + static String m74(galleryType) => + "El tipo de galería ${galleryType} no es compatible con el renombrado"; - static String m76(endDate) => "Válido hasta ${endDate}"; + static String m75(ignoreReason) => + "La subida se ignoró debido a ${ignoreReason}"; - static String m77(email) => "Verificar ${email}"; + static String m76(count) => "Preservando ${count} memorias..."; + + static String m77(endDate) => "Válido hasta ${endDate}"; + + static String m78(email) => "Verificar ${email}"; + + static String m79(count) => + "${Intl.plural(count, zero: '0 espectadores añadidos', one: '1 espectador añadido', other: '${count} espectadores añadidos')}"; static String m2(email) => "Hemos enviado un correo a ${email}"; - static String m79(count) => - "${Intl.plural(count, one: '${count} año atrás', other: '${count} años atrás')}"; + static String m80(count) => + "${Intl.plural(count, one: 'Hace ${count} año', other: 'Hace ${count} años')}"; - static String m80(storageSaved) => "¡Has liberado ${storageSaved} con éxito!"; + static String m81(storageSaved) => "¡Has liberado ${storageSaved} con éxito!"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -232,18 +256,22 @@ class MessageLookup extends MessageLookupByLibrary { "Hay una nueva versión de Ente disponible."), "about": MessageLookupByLibrary.simpleMessage("Acerca de"), "account": MessageLookupByLibrary.simpleMessage("Cuenta"), + "accountIsAlreadyConfigured": MessageLookupByLibrary.simpleMessage( + "La cuenta ya está configurada."), "accountWelcomeBack": MessageLookupByLibrary.simpleMessage("¡Bienvenido de nuevo!"), "ackPasswordLostWarning": MessageLookupByLibrary.simpleMessage( "Entiendo que si pierdo mi contraseña podría perder mis datos, ya que mis datos están cifrados de extremo a extremo."), "activeSessions": - MessageLookupByLibrary.simpleMessage("Sesiónes activas"), + MessageLookupByLibrary.simpleMessage("Sesiones activas"), + "add": MessageLookupByLibrary.simpleMessage("Añadir"), "addAName": MessageLookupByLibrary.simpleMessage("Añade un nombre"), "addANewEmail": MessageLookupByLibrary.simpleMessage( "Agregar nuevo correo electrónico"), "addCollaborator": MessageLookupByLibrary.simpleMessage("Agregar colaborador"), "addCollaborators": m6, + "addFiles": MessageLookupByLibrary.simpleMessage("Añadir archivos"), "addFromDevice": MessageLookupByLibrary.simpleMessage( "Agregar desde el dispositivo"), "addItem": m7, @@ -251,7 +279,12 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Agregar ubicación"), "addLocationButton": MessageLookupByLibrary.simpleMessage("Añadir"), "addMore": MessageLookupByLibrary.simpleMessage("Añadir más"), + "addName": MessageLookupByLibrary.simpleMessage("Añadir nombre"), + "addNameOrMerge": + MessageLookupByLibrary.simpleMessage("Añadir nombre o combinar"), "addNew": MessageLookupByLibrary.simpleMessage("Añadir nuevo"), + "addNewPerson": + MessageLookupByLibrary.simpleMessage("Añadir nueva persona"), "addOnPageSubtitle": MessageLookupByLibrary.simpleMessage( "Detalles de los complementos"), "addOnValidTill": m8, @@ -286,17 +319,19 @@ class MessageLookup extends MessageLookupByLibrary { "albumTitle": MessageLookupByLibrary.simpleMessage("Título del álbum"), "albumUpdated": MessageLookupByLibrary.simpleMessage("Álbum actualizado"), - "albums": MessageLookupByLibrary.simpleMessage("Álbunes"), + "albums": MessageLookupByLibrary.simpleMessage("Álbumes"), "allClear": MessageLookupByLibrary.simpleMessage("✨ Todo limpio"), "allMemoriesPreserved": MessageLookupByLibrary.simpleMessage( "Todos los recuerdos preservados"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), + "allPersonGroupingWillReset": MessageLookupByLibrary.simpleMessage( + "Se eliminarán todas las agrupaciones para esta persona, y se eliminarán todas sus sugerencias"), + "allow": MessageLookupByLibrary.simpleMessage("Permitir"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "Permitir a las personas con el enlace añadir fotos al álbum compartido."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage("Permitir añadir fotos"), "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), + "Permitir a la aplicación abrir enlaces de álbum compartidos"), "allowDownloads": MessageLookupByLibrary.simpleMessage("Permitir descargas"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( @@ -322,7 +357,8 @@ class MessageLookup extends MessageLookupByLibrary { "Android, iOS, Web, Computadora"), "androidSignInTitle": MessageLookupByLibrary.simpleMessage("Autentificación requerida"), - "appLock": MessageLookupByLibrary.simpleMessage("Aplicación bloqueada"), + "appLock": + MessageLookupByLibrary.simpleMessage("Bloqueo de aplicación"), "appLockDescriptions": MessageLookupByLibrary.simpleMessage( "Escoge entre la pantalla de bloqueo por defecto de tu dispositivo y una pantalla de bloqueo personalizada con un PIN o contraseña."), "appVersion": m13, @@ -348,6 +384,9 @@ class MessageLookup extends MessageLookupByLibrary { "¿Estás seguro de que quieres cerrar la sesión?"), "areYouSureYouWantToRenew": MessageLookupByLibrary.simpleMessage( "¿Estás seguro de que quieres renovar?"), + "areYouSureYouWantToResetThisPerson": + MessageLookupByLibrary.simpleMessage( + "¿Seguro que desea eliminar esta persona?"), "askCancelReason": MessageLookupByLibrary.simpleMessage( "Tu suscripción ha sido cancelada. ¿Quieres compartir el motivo?"), "askDeleteReason": MessageLookupByLibrary.simpleMessage( @@ -402,12 +441,13 @@ class MessageLookup extends MessageLookupByLibrary { "El emparejamiento automático funciona sólo con dispositivos compatibles con Chromecast."), "available": MessageLookupByLibrary.simpleMessage("Disponible"), "availableStorageSpace": m14, - "backedUpFolders": - MessageLookupByLibrary.simpleMessage("Carpetas respaldadas"), - "backup": MessageLookupByLibrary.simpleMessage("Copia de respaldo"), + "backedUpFolders": MessageLookupByLibrary.simpleMessage( + "Carpetas con copia de seguridad"), + "backup": MessageLookupByLibrary.simpleMessage("Copia de seguridad"), "backupFailed": MessageLookupByLibrary.simpleMessage( "La copia de seguridad ha fallado"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), + "backupFile": MessageLookupByLibrary.simpleMessage( + "Archivo de copia de seguridad"), "backupOverMobileData": MessageLookupByLibrary.simpleMessage( "Copia de seguridad usando datos móviles"), "backupSettings": MessageLookupByLibrary.simpleMessage( @@ -415,9 +455,10 @@ class MessageLookup extends MessageLookupByLibrary { "backupStatus": MessageLookupByLibrary.simpleMessage( "Estado de la copia de seguridad"), "backupStatusDescription": MessageLookupByLibrary.simpleMessage( - "Los elementos que han sido respaldados aparecerán aquí"), - "backupVideos": - MessageLookupByLibrary.simpleMessage("Respaldar vídeos"), + "Los elementos con copia seguridad aparecerán aquí"), + "backupVideos": MessageLookupByLibrary.simpleMessage( + "Copia de seguridad de vídeos"), + "birthday": MessageLookupByLibrary.simpleMessage("Cumpleaños"), "blackFridaySale": MessageLookupByLibrary.simpleMessage("Oferta del Black Friday"), "blog": MessageLookupByLibrary.simpleMessage("Blog"), @@ -439,6 +480,7 @@ class MessageLookup extends MessageLookupByLibrary { "cannotAddMorePhotosAfterBecomingViewer": m16, "cannotDeleteSharedFiles": MessageLookupByLibrary.simpleMessage( "No se pueden eliminar los archivos compartidos"), + "castAlbum": MessageLookupByLibrary.simpleMessage("Enviar álbum"), "castIPMismatchBody": MessageLookupByLibrary.simpleMessage( "Por favor, asegúrate de estar en la misma red que el televisor."), "castIPMismatchTitle": @@ -451,6 +493,20 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Cambiar correo electrónico"), "changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage( "¿Cambiar la ubicación de los elementos seleccionados?"), + "changeLogBackupStatusContent": MessageLookupByLibrary.simpleMessage( + "Hemos añadido un registro de todos los archivos que han sido subidos a Ente, incluyendo los fallos y los que están en cola."), + "changeLogBackupStatusTitle": MessageLookupByLibrary.simpleMessage( + "Estado de la copia de seguridad"), + "changeLogDiscoverContent": MessageLookupByLibrary.simpleMessage( + "¿Buscas fotos de tus documentos de identidad, notas o incluso memes? Ve a la pestaña de búsqueda y consulta Descubrir. Según nuestra búsqueda semántica, es un lugar para encontrar fotos que podrían ser importantes para ti.\\n\\nSolo disponible si has activado el aprendizaje automático."), + "changeLogDiscoverTitle": + MessageLookupByLibrary.simpleMessage("Descubrir"), + "changeLogMagicSearchImprovementContent": + MessageLookupByLibrary.simpleMessage( + "Hemos mejorado la búsqueda mágica para que sea mucho más rápida, así no tienes que esperar para encontrar lo que estás buscando."), + "changeLogMagicSearchImprovementTitle": + MessageLookupByLibrary.simpleMessage( + "Mejora de la búsqueda mágica"), "changePassword": MessageLookupByLibrary.simpleMessage("Cambiar contraseña"), "changePasswordTitle": @@ -458,23 +514,25 @@ class MessageLookup extends MessageLookupByLibrary { "changePermissions": MessageLookupByLibrary.simpleMessage("¿Cambiar permisos?"), "changeYourReferralCode": MessageLookupByLibrary.simpleMessage( - "Cambiar tu código de referencia"), + "Cambiar tu código de referido"), "checkForUpdates": MessageLookupByLibrary.simpleMessage("Comprobar actualizaciónes"), "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( "Revisa tu bandeja de entrada (y spam) para completar la verificación"), "checkStatus": MessageLookupByLibrary.simpleMessage("Comprobar estado"), "checking": MessageLookupByLibrary.simpleMessage("Comprobando..."), + "checkingModels": + MessageLookupByLibrary.simpleMessage("Comprobando modelos..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage( - "Reclamar almacenamiento gratis"), - "claimMore": MessageLookupByLibrary.simpleMessage("¡Reclama más!"), - "claimed": MessageLookupByLibrary.simpleMessage("Reclamado"), + "Obtén almacenamiento gratuito"), + "claimMore": MessageLookupByLibrary.simpleMessage("¡Obtén más!"), + "claimed": MessageLookupByLibrary.simpleMessage("Obtenido"), "claimedStorageSoFar": m17, "cleanUncategorized": MessageLookupByLibrary.simpleMessage("Limpiar no categorizado"), "cleanUncategorizedDescription": MessageLookupByLibrary.simpleMessage( "Elimina todos los archivos de Sin categorizar que están presentes en otros álbumes"), - "clearCaches": MessageLookupByLibrary.simpleMessage("Limpiar caché"), + "clearCaches": MessageLookupByLibrary.simpleMessage("Limpiar cachés"), "clearIndexes": MessageLookupByLibrary.simpleMessage("Limpiar índices"), "click": MessageLookupByLibrary.simpleMessage("• Clic"), "clickOnTheOverflowMenu": MessageLookupByLibrary.simpleMessage( @@ -503,6 +561,7 @@ class MessageLookup extends MessageLookupByLibrary { "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": MessageLookupByLibrary.simpleMessage( "Colaboradores pueden añadir fotos y videos al álbum compartido."), + "collaboratorsSuccessfullyAdded": m82, "collageLayout": MessageLookupByLibrary.simpleMessage("Disposición"), "collageSaved": MessageLookupByLibrary.simpleMessage( "Collage guardado en la galería"), @@ -514,6 +573,7 @@ class MessageLookup extends MessageLookupByLibrary { "collectPhotosDescription": MessageLookupByLibrary.simpleMessage( "Crea un enlace donde tus amigos pueden subir fotos en su calidad original."), "color": MessageLookupByLibrary.simpleMessage("Color"), + "configuration": MessageLookupByLibrary.simpleMessage("Configuración"), "confirm": MessageLookupByLibrary.simpleMessage("Confirmar"), "confirm2FADisable": MessageLookupByLibrary.simpleMessage( "¿Estás seguro de que deseas deshabilitar la autenticación de doble factor?"), @@ -556,7 +616,7 @@ class MessageLookup extends MessageLookupByLibrary { "No se pudo actualizar la suscripción"), "count": MessageLookupByLibrary.simpleMessage("Cuenta"), "crashReporting": - MessageLookupByLibrary.simpleMessage("Reporte de fallos"), + MessageLookupByLibrary.simpleMessage("Reporte de errores"), "create": MessageLookupByLibrary.simpleMessage("Crear"), "createAccount": MessageLookupByLibrary.simpleMessage("Crear cuenta"), "createAlbumActionHint": MessageLookupByLibrary.simpleMessage( @@ -575,9 +635,10 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Creando enlace..."), "criticalUpdateAvailable": MessageLookupByLibrary.simpleMessage( "Actualización crítica disponible"), - "crop": MessageLookupByLibrary.simpleMessage("Cortar"), + "crop": MessageLookupByLibrary.simpleMessage("Ajustar encuadre"), "currentUsageIs": MessageLookupByLibrary.simpleMessage("El uso actual es de "), + "currentlyRunning": MessageLookupByLibrary.simpleMessage("ejecutando"), "custom": MessageLookupByLibrary.simpleMessage("Personalizado"), "customEndpoint": m21, "darkTheme": MessageLookupByLibrary.simpleMessage("Oscuro"), @@ -599,16 +660,16 @@ class MessageLookup extends MessageLookupByLibrary { "deleteAlbumDialog": MessageLookupByLibrary.simpleMessage( "¿También eliminar las fotos (y los vídeos) presentes en este álbum de todos los otros álbumes de los que forman parte?"), "deleteAlbumsDialogBody": MessageLookupByLibrary.simpleMessage( - "Esto eliminará todos los álbunes vacíos. Esto es útil cuando quieres reducir el desorden en tu lista de álbumes."), + "Esto eliminará todos los álbumes vacíos. Esto es útil cuando quieres reducir el desorden en tu lista de álbumes."), "deleteAll": MessageLookupByLibrary.simpleMessage("Borrar Todo"), "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage( "Esta cuenta está vinculada a otras aplicaciones de Ente, si utilizas alguna. Se programará la eliminación de los datos cargados en todas las aplicaciones de Ente, y tu cuenta se eliminará permanentemente."), "deleteEmailRequest": MessageLookupByLibrary.simpleMessage( - "Por favor, envía un correo electrónico a account-deletion@ente.io desde tu dirección de correo electrónico registrada."), + "Por favor, envía un correo electrónico a account-deletion@ente.io desde la dirección de correo electrónico que usó para registrarse."), "deleteEmptyAlbums": - MessageLookupByLibrary.simpleMessage("Eliminar álbunes vacíos"), + MessageLookupByLibrary.simpleMessage("Eliminar álbumes vacíos"), "deleteEmptyAlbumsWithQuestionMark": - MessageLookupByLibrary.simpleMessage("¿Eliminar álbunes vacíos?"), + MessageLookupByLibrary.simpleMessage("¿Eliminar álbumes vacíos?"), "deleteFromBoth": MessageLookupByLibrary.simpleMessage("Eliminar de ambos"), "deleteFromDevice": @@ -693,7 +754,7 @@ class MessageLookup extends MessageLookupByLibrary { "doNotSignOut": MessageLookupByLibrary.simpleMessage("No cerrar la sesión"), "doThisLater": - MessageLookupByLibrary.simpleMessage("Hacer esto más tarde"), + MessageLookupByLibrary.simpleMessage("Hacerlo más tarde"), "doYouWantToDiscardTheEditsYouHaveMade": MessageLookupByLibrary.simpleMessage( "¿Quieres descartar las ediciones que has hecho?"), @@ -712,6 +773,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Editar la ubicación"), "editLocationTagTitle": MessageLookupByLibrary.simpleMessage("Editar la ubicación"), + "editPerson": MessageLookupByLibrary.simpleMessage("Editar persona"), "editsSaved": MessageLookupByLibrary.simpleMessage("Ediciones guardadas"), "editsToLocationWillOnlyBeSeenWithinEnte": @@ -731,6 +793,8 @@ class MessageLookup extends MessageLookupByLibrary { "enable": MessageLookupByLibrary.simpleMessage("Habilitar"), "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Ente soporta aprendizaje automático en el dispositivo para la detección de caras, búsqueda mágica y otras características de búsqueda avanzada"), + "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( + "Activar aprendizaje automático para búsqueda mágica y reconocimiento facial"), "enableMaps": MessageLookupByLibrary.simpleMessage("Activar Mapas"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "Esto mostrará tus fotos en el mapa mundial.\n\nEste mapa está gestionado por Open Street Map, y la ubicación exacta de tus fotos nunca se comparte.\n\nPuedes deshabilitar esta función en cualquier momento en Ajustes."), @@ -759,10 +823,13 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Introduce el código"), "enterCodeDescription": MessageLookupByLibrary.simpleMessage( "Introduce el código proporcionado por tu amigo para reclamar almacenamiento gratuito para ambos"), + "enterDateOfBirth": + MessageLookupByLibrary.simpleMessage("Cumpleaños (opcional)"), "enterEmail": MessageLookupByLibrary.simpleMessage( "Ingresar correo electrónico "), "enterFileName": MessageLookupByLibrary.simpleMessage( "Introduce el nombre del archivo"), + "enterName": MessageLookupByLibrary.simpleMessage("Introducir nombre"), "enterNewPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "Introduce una nueva contraseña que podamos usar para cifrar tus datos"), "enterPassword": @@ -774,7 +841,7 @@ class MessageLookup extends MessageLookupByLibrary { "enterPin": MessageLookupByLibrary.simpleMessage("Ingresa tu contraseña"), "enterReferralCode": MessageLookupByLibrary.simpleMessage( - "Ingresar código de referencia"), + "Introduce el código de referido"), "enterThe6digitCodeFromnyourAuthenticatorApp": MessageLookupByLibrary.simpleMessage( "Ingresa el código de seis dígitos de tu aplicación de autenticación"), @@ -797,8 +864,11 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Exportar registros"), "exportYourData": MessageLookupByLibrary.simpleMessage("Exportar tus datos"), + "extraPhotosFound": MessageLookupByLibrary.simpleMessage( + "Fotos adicionales encontradas"), + "extraPhotosFoundFor": m30, "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), + "Cara no agrupada todavía, por favor vuelve más tarde"), "faceRecognition": MessageLookupByLibrary.simpleMessage("Reconocimiento facial"), "faces": MessageLookupByLibrary.simpleMessage("Caras"), @@ -808,12 +878,19 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Error al cancelar"), "failedToDownloadVideo": MessageLookupByLibrary.simpleMessage("Error al descargar el vídeo"), + "failedToFetchActiveSessions": MessageLookupByLibrary.simpleMessage( + "Error al recuperar las sesiones activas"), "failedToFetchOriginalForEdit": MessageLookupByLibrary.simpleMessage( "No se pudo obtener el original para editar"), "failedToFetchReferralDetails": MessageLookupByLibrary.simpleMessage( "No se pueden obtener los detalles de la referencia. Por favor, inténtalo de nuevo más tarde."), "failedToLoadAlbums": MessageLookupByLibrary.simpleMessage("Error al cargar álbumes"), + "failedToPlayVideo": MessageLookupByLibrary.simpleMessage( + "Error al reproducir el video"), + "failedToRefreshStripeSubscription": + MessageLookupByLibrary.simpleMessage( + "Error al actualizar la suscripción"), "failedToRenew": MessageLookupByLibrary.simpleMessage("Renovación fallida"), "failedToVerifyPaymentStatus": MessageLookupByLibrary.simpleMessage( @@ -828,10 +905,13 @@ class MessageLookup extends MessageLookupByLibrary { "faqs": MessageLookupByLibrary.simpleMessage("Preguntas frecuentes"), "favorite": MessageLookupByLibrary.simpleMessage("Favorito"), "feedback": MessageLookupByLibrary.simpleMessage("Sugerencias"), + "file": MessageLookupByLibrary.simpleMessage("Archivo"), "fileFailedToSaveToGallery": MessageLookupByLibrary.simpleMessage( "No se pudo guardar el archivo en la galería"), "fileInfoAddDescHint": - MessageLookupByLibrary.simpleMessage("Añadir una descripción..."), + MessageLookupByLibrary.simpleMessage("Añadir descripción..."), + "fileNotUploadedYet": MessageLookupByLibrary.simpleMessage( + "El archivo aún no se ha subido"), "fileSavedToGallery": MessageLookupByLibrary.simpleMessage( "Archivo guardado en la galería"), "fileTypes": MessageLookupByLibrary.simpleMessage("Tipos de archivos"), @@ -845,6 +925,8 @@ class MessageLookup extends MessageLookupByLibrary { "Archivo guardado en la galería"), "findPeopleByName": MessageLookupByLibrary.simpleMessage( "Encuentra gente rápidamente por su nombre"), + "findThemQuickly": + MessageLookupByLibrary.simpleMessage("Encuéntralos rápidamente"), "flip": MessageLookupByLibrary.simpleMessage("Voltear"), "forYourMemories": MessageLookupByLibrary.simpleMessage("para tus recuerdos"), @@ -852,7 +934,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Olvidé mi contraseña"), "foundFaces": MessageLookupByLibrary.simpleMessage("Caras encontradas"), "freeStorageClaimed": MessageLookupByLibrary.simpleMessage( - "Almacenamiento gratuito reclamado"), + "Almacenamiento gratuito obtenido"), "freeStorageOnReferralSuccess": m33, "freeStorageUsable": MessageLookupByLibrary.simpleMessage( "Almacenamiento libre disponible"), @@ -863,14 +945,14 @@ class MessageLookup extends MessageLookupByLibrary { "freeUpDeviceSpace": MessageLookupByLibrary.simpleMessage( "Liberar espacio del dispositivo"), "freeUpDeviceSpaceDesc": MessageLookupByLibrary.simpleMessage( - "Ahorra espacio en tu dispositivo limpiando archivos que ya han sido respaldados."), + "Ahorra espacio en tu dispositivo limpiando archivos que tienen copia de seguridad."), "freeUpSpace": MessageLookupByLibrary.simpleMessage("Liberar espacio"), "freeUpSpaceSaving": m37, "galleryMemoryLimitInfo": MessageLookupByLibrary.simpleMessage( "Hasta 1000 memorias mostradas en la galería"), "general": MessageLookupByLibrary.simpleMessage("General"), "generatingEncryptionKeys": MessageLookupByLibrary.simpleMessage( - "Generando claves de encriptación..."), + "Generando claves de cifrado..."), "genericProgress": m38, "goToSettings": MessageLookupByLibrary.simpleMessage("Ir a Ajustes"), "googlePlayId": @@ -909,8 +991,11 @@ class MessageLookup extends MessageLookupByLibrary { "La autenticación biométrica está deshabilitada. Por favor, bloquea y desbloquea la pantalla para habilitarla."), "iOSOkButton": MessageLookupByLibrary.simpleMessage("Aceptar"), "ignoreUpdate": MessageLookupByLibrary.simpleMessage("Ignorar"), + "ignored": MessageLookupByLibrary.simpleMessage("ignorado"), "ignoredFolderUploadReason": MessageLookupByLibrary.simpleMessage( "Algunos archivos de este álbum son ignorados de la carga porque previamente habían sido borrados de Ente."), + "imageNotAnalyzed": + MessageLookupByLibrary.simpleMessage("Imagen no analizada"), "immediately": MessageLookupByLibrary.simpleMessage("Inmediatamente"), "importing": MessageLookupByLibrary.simpleMessage("Importando...."), "incorrectCode": @@ -927,6 +1012,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Elementos indexados"), "indexingIsPaused": MessageLookupByLibrary.simpleMessage( "La indexación está pausada. Se reanudará automáticamente cuando el dispositivo esté listo."), + "info": MessageLookupByLibrary.simpleMessage("Info"), "insecureDevice": MessageLookupByLibrary.simpleMessage("Dispositivo inseguro"), "installManually": @@ -971,7 +1057,7 @@ class MessageLookup extends MessageLookupByLibrary { "leaveSharedAlbum": MessageLookupByLibrary.simpleMessage("¿Dejar álbum compartido?"), "left": MessageLookupByLibrary.simpleMessage("Izquierda"), - "light": MessageLookupByLibrary.simpleMessage("Claro"), + "light": MessageLookupByLibrary.simpleMessage("Brillo"), "lightTheme": MessageLookupByLibrary.simpleMessage("Claro"), "linkCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "Enlace copiado al portapapeles"), @@ -1014,6 +1100,7 @@ class MessageLookup extends MessageLookupByLibrary { "loadingYourPhotos": MessageLookupByLibrary.simpleMessage("Cargando tus fotos..."), "localGallery": MessageLookupByLibrary.simpleMessage("Galería local"), + "localIndexing": MessageLookupByLibrary.simpleMessage("Indexado local"), "localSyncErrorMessage": MessageLookupByLibrary.simpleMessage( "Parece que algo salió mal ya que la sincronización de fotos locales está tomando más tiempo del esperado. Por favor contacta con nuestro equipo de soporte"), "location": MessageLookupByLibrary.simpleMessage("Ubicación"), @@ -1034,6 +1121,8 @@ class MessageLookup extends MessageLookupByLibrary { "Tu sesión ha expirado. Por favor, vuelve a iniciar sesión."), "loginTerms": MessageLookupByLibrary.simpleMessage( "Al hacer clic en iniciar sesión, acepto los términos de servicio y la política de privacidad"), + "loginWithTOTP": + MessageLookupByLibrary.simpleMessage("Iniciar sesión con TOTP"), "logout": MessageLookupByLibrary.simpleMessage("Cerrar sesión"), "logsDialogBody": MessageLookupByLibrary.simpleMessage( "Esto enviará registros para ayudarnos a depurar su problema. Ten en cuenta que los nombres de los archivos se incluirán para ayudar a rastrear problemas con archivos específicos."), @@ -1053,10 +1142,12 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Aprendizaje automático"), "magicSearch": MessageLookupByLibrary.simpleMessage("Búsqueda mágica"), "magicSearchHint": MessageLookupByLibrary.simpleMessage( - "La búsqueda mágica permite buscar fotos por su contenido. Por ejemplo, \"flor\", \"carro rojo\", \"documentos de identidad\""), + "La búsqueda mágica permite buscar fotos por su contenido. Por ejemplo, \"flor\", \"coche rojo\", \"documentos de identidad\""), "manage": MessageLookupByLibrary.simpleMessage("Administrar"), "manageDeviceStorage": MessageLookupByLibrary.simpleMessage( - "Administrar almacenamiento del dispositivo"), + "Gestionar almacenamiento caché del dispositivo"), + "manageDeviceStorageDesc": MessageLookupByLibrary.simpleMessage( + "Revisar y borrar almacenamiento caché local."), "manageFamily": MessageLookupByLibrary.simpleMessage("Administrar familia"), "manageLink": @@ -1073,6 +1164,10 @@ class MessageLookup extends MessageLookupByLibrary { "matrix": MessageLookupByLibrary.simpleMessage("Matrix"), "memoryCount": m3, "merchandise": MessageLookupByLibrary.simpleMessage("Mercancías"), + "mergeWithExisting": + MessageLookupByLibrary.simpleMessage("Combinar con existente"), + "mergedPhotos": + MessageLookupByLibrary.simpleMessage("Fotos combinadas"), "mlConsent": MessageLookupByLibrary.simpleMessage( "Habilitar aprendizaje automático"), "mlConsentConfirmation": MessageLookupByLibrary.simpleMessage( @@ -1084,7 +1179,7 @@ class MessageLookup extends MessageLookupByLibrary { "mlConsentTitle": MessageLookupByLibrary.simpleMessage( "¿Habilitar aprendizaje automático?"), "mlIndexingDescription": MessageLookupByLibrary.simpleMessage( - "Por favor ten en cuenta que el aprendizaje automático dará como resultado un mayor ancho de banda y uso de batería hasta que todos los elementos estén indexados. Considera usar la aplicación de escritorio para una indexación más rápida. Todos los resultados se sincronizarán automáticamente."), + "Por favor ten en cuenta que el aprendizaje automático dará como resultado un mayor consumo de ancho de banda y de batería hasta que todos los elementos estén indexados. Considera usar la aplicación de escritorio para una indexación más rápida. Todos los resultados se sincronizarán automáticamente."), "mobileWebDesktop": MessageLookupByLibrary.simpleMessage("Celular, Web, Computadora"), "moderateStrength": MessageLookupByLibrary.simpleMessage("Moderada"), @@ -1092,7 +1187,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Modifica tu consulta o intenta buscar"), "moments": MessageLookupByLibrary.simpleMessage("Momentos"), - "monthly": MessageLookupByLibrary.simpleMessage("Mensual"), + "month": MessageLookupByLibrary.simpleMessage("mes"), + "monthly": MessageLookupByLibrary.simpleMessage("Mensualmente"), "moreDetails": MessageLookupByLibrary.simpleMessage("Más detalles"), "mostRecent": MessageLookupByLibrary.simpleMessage("Más reciente"), "mostRelevant": MessageLookupByLibrary.simpleMessage("Más relevante"), @@ -1113,6 +1209,9 @@ class MessageLookup extends MessageLookupByLibrary { "No se puede conectar a Ente. Por favor, comprueba tu configuración de red y ponte en contacto con el soporte técnico si el error persiste."), "never": MessageLookupByLibrary.simpleMessage("Nunca"), "newAlbum": MessageLookupByLibrary.simpleMessage("Nuevo álbum"), + "newLocation": + MessageLookupByLibrary.simpleMessage("Nueva localización"), + "newPerson": MessageLookupByLibrary.simpleMessage("Nueva persona"), "newToEnte": MessageLookupByLibrary.simpleMessage("Nuevo en Ente"), "newest": MessageLookupByLibrary.simpleMessage("Más reciente"), "next": MessageLookupByLibrary.simpleMessage("Siguiente"), @@ -1127,15 +1226,16 @@ class MessageLookup extends MessageLookupByLibrary { "noDuplicates": MessageLookupByLibrary.simpleMessage("✨ Sin duplicados"), "noExifData": MessageLookupByLibrary.simpleMessage("No hay datos EXIF"), + "noFacesFound": + MessageLookupByLibrary.simpleMessage("No se han encontrado caras"), "noHiddenPhotosOrVideos": MessageLookupByLibrary.simpleMessage( "No hay fotos ni vídeos ocultos"), "noImagesWithLocation": MessageLookupByLibrary.simpleMessage( "No hay imágenes con ubicación"), "noInternetConnection": MessageLookupByLibrary.simpleMessage("No hay conexión al Internet"), - "noPhotosAreBeingBackedUpRightNow": - MessageLookupByLibrary.simpleMessage( - "No se están respaldando fotos ahora mismo"), + "noPhotosAreBeingBackedUpRightNow": MessageLookupByLibrary.simpleMessage( + "No se están realizando copias de seguridad de ninguna foto en este momento"), "noPhotosFoundHere": MessageLookupByLibrary.simpleMessage( "No se encontró ninguna foto aquí"), "noQuickLinksSelected": MessageLookupByLibrary.simpleMessage( @@ -1147,6 +1247,7 @@ class MessageLookup extends MessageLookupByLibrary { "noResults": MessageLookupByLibrary.simpleMessage("Sin resultados"), "noResultsFound": MessageLookupByLibrary.simpleMessage( "No se han encontrado resultados"), + "noSuggestionsForPerson": m43, "noSystemLockFound": MessageLookupByLibrary.simpleMessage( "Bloqueo de sistema no encontrado"), "notPersonLabel": m44, @@ -1160,16 +1261,17 @@ class MessageLookup extends MessageLookupByLibrary { "onEnte": MessageLookupByLibrary.simpleMessage( "En ente"), "onlyFamilyAdminCanChangeCode": m45, + "onlyThem": MessageLookupByLibrary.simpleMessage("Solo ellos"), "oops": MessageLookupByLibrary.simpleMessage("Ups"), "oopsCouldNotSaveEdits": MessageLookupByLibrary.simpleMessage( "Ups, no se pudieron guardar las ediciónes"), "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage("Ups, algo salió mal"), "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), + MessageLookupByLibrary.simpleMessage("Abrir álbum en el navegador"), "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), + "Por favor, utiliza la aplicación web para añadir fotos a este álbum"), + "openFile": MessageLookupByLibrary.simpleMessage("Abrir archivo"), "openSettings": MessageLookupByLibrary.simpleMessage("Abrir Ajustes"), "openTheItem": MessageLookupByLibrary.simpleMessage("• Abrir el elemento"), @@ -1177,6 +1279,8 @@ class MessageLookup extends MessageLookupByLibrary { "Contribuidores de OpenStreetMap"), "optionalAsShortAsYouLike": MessageLookupByLibrary.simpleMessage( "Opcional, tan corto como quieras..."), + "orMergeWithExistingPerson": MessageLookupByLibrary.simpleMessage( + "O combinar con persona existente"), "orPickAnExistingOne": MessageLookupByLibrary.simpleMessage("O elige uno existente"), "pair": MessageLookupByLibrary.simpleMessage("Emparejar"), @@ -1199,7 +1303,7 @@ class MessageLookup extends MessageLookupByLibrary { "passwordStrengthInfo": MessageLookupByLibrary.simpleMessage( "La intensidad de la contraseña se calcula teniendo en cuenta la longitud de la contraseña, los caracteres utilizados, y si la contraseña aparece o no en el top 10,000 de contraseñas más usadas"), "passwordWarning": MessageLookupByLibrary.simpleMessage( - "No almacenamos esta contraseña, así que si la olvidas, no podemos descifrar tus datos"), + "No almacenamos esta contraseña, así que si la olvidas, no podremos descifrar tus datos"), "paymentDetails": MessageLookupByLibrary.simpleMessage("Detalles de pago"), "paymentFailed": MessageLookupByLibrary.simpleMessage("Pago fallido"), @@ -1219,6 +1323,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Borrar permanentemente"), "permanentlyDeleteFromDevice": MessageLookupByLibrary.simpleMessage( "¿Eliminar permanentemente del dispositivo?"), + "personName": + MessageLookupByLibrary.simpleMessage("Nombre de la persona"), "photoDescriptions": MessageLookupByLibrary.simpleMessage("Descripciones de fotos"), "photoGridSize": MessageLookupByLibrary.simpleMessage( @@ -1228,13 +1334,14 @@ class MessageLookup extends MessageLookupByLibrary { "photosAddedByYouWillBeRemovedFromTheAlbum": MessageLookupByLibrary.simpleMessage( "Las fotos añadidas por ti serán removidas del álbum"), + "photosCount": m47, "pickCenterPoint": MessageLookupByLibrary.simpleMessage("Elegir punto central"), "pinAlbum": MessageLookupByLibrary.simpleMessage("Fijar álbum"), "pinLock": MessageLookupByLibrary.simpleMessage("PIN Bloqueado"), "playOnTv": MessageLookupByLibrary.simpleMessage("Reproducir álbum en TV"), - "playStoreFreeTrialValidTill": m47, + "playStoreFreeTrialValidTill": m48, "playstoreSubscription": MessageLookupByLibrary.simpleMessage("Suscripción en la PlayStore"), "pleaseCheckYourInternetConnectionAndTryAgain": @@ -1246,14 +1353,14 @@ class MessageLookup extends MessageLookupByLibrary { "pleaseContactSupportIfTheProblemPersists": MessageLookupByLibrary.simpleMessage( "Por favor, contacta a soporte técnico si el problema persiste"), - "pleaseEmailUsAt": m48, + "pleaseEmailUsAt": m49, "pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage("Por favor, concede permiso"), "pleaseLoginAgain": MessageLookupByLibrary.simpleMessage( "Por favor, vuelve a iniciar sesión"), "pleaseSelectQuickLinksToRemove": MessageLookupByLibrary.simpleMessage( "Por favor, selecciona enlaces rápidos para eliminar"), - "pleaseSendTheLogsTo": m49, + "pleaseSendTheLogsTo": m50, "pleaseTryAgain": MessageLookupByLibrary.simpleMessage( "Por favor, inténtalo nuevamente"), "pleaseVerifyTheCodeYouHaveEntered": @@ -1280,7 +1387,7 @@ class MessageLookup extends MessageLookupByLibrary { "Copias de seguridad privadas"), "privateSharing": MessageLookupByLibrary.simpleMessage("Compartir en privado"), - "processingImport": m50, + "processingImport": m51, "publicLinkCreated": MessageLookupByLibrary.simpleMessage("Enlace público creado"), "publicLinkEnabled": @@ -1291,7 +1398,7 @@ class MessageLookup extends MessageLookupByLibrary { "rateTheApp": MessageLookupByLibrary.simpleMessage("Evalúa la aplicación"), "rateUs": MessageLookupByLibrary.simpleMessage("Califícanos"), - "rateUsOnStore": m51, + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("Recuperar"), "recoverAccount": MessageLookupByLibrary.simpleMessage("Recuperar cuenta"), @@ -1325,8 +1432,8 @@ class MessageLookup extends MessageLookupByLibrary { "referralStep1": MessageLookupByLibrary.simpleMessage( "1. Dale este código a tus amigos"), "referralStep2": MessageLookupByLibrary.simpleMessage( - "2. Se inscriben a un plan pagado"), - "referralStep3": m52, + "2. Se suscriben a un plan de pago"), + "referralStep3": m53, "referrals": MessageLookupByLibrary.simpleMessage("Referidos"), "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage( "Las referencias están actualmente en pausa"), @@ -1353,7 +1460,7 @@ class MessageLookup extends MessageLookupByLibrary { "removeLink": MessageLookupByLibrary.simpleMessage("Eliminar enlace"), "removeParticipant": MessageLookupByLibrary.simpleMessage("Quitar participante"), - "removeParticipantBody": m53, + "removeParticipantBody": m54, "removePersonLabel": MessageLookupByLibrary.simpleMessage( "Eliminar etiqueta de persona"), "removePublicLink": @@ -1371,7 +1478,7 @@ class MessageLookup extends MessageLookupByLibrary { "renameFile": MessageLookupByLibrary.simpleMessage("Renombrar archivo"), "renewSubscription": MessageLookupByLibrary.simpleMessage("Renovar suscripción"), - "renewsOn": m54, + "renewsOn": m55, "reportABug": MessageLookupByLibrary.simpleMessage("Reportar un error"), "reportBug": MessageLookupByLibrary.simpleMessage("Reportar error"), "resendEmail": @@ -1380,6 +1487,7 @@ class MessageLookup extends MessageLookupByLibrary { "Restablecer archivos ignorados"), "resetPasswordTitle": MessageLookupByLibrary.simpleMessage("Restablecer contraseña"), + "resetPerson": MessageLookupByLibrary.simpleMessage("Eliminar"), "resetToDefault": MessageLookupByLibrary.simpleMessage( "Restablecer valores predeterminados"), "restore": MessageLookupByLibrary.simpleMessage("Restaurar"), @@ -1390,6 +1498,7 @@ class MessageLookup extends MessageLookupByLibrary { "resumableUploads": MessageLookupByLibrary.simpleMessage("Subidas reanudables"), "retry": MessageLookupByLibrary.simpleMessage("Reintentar"), + "review": MessageLookupByLibrary.simpleMessage("Revisar"), "reviewDeduplicateItems": MessageLookupByLibrary.simpleMessage( "Por favor, revisa y elimina los elementos que crees que están duplicados."), "reviewSuggestions": @@ -1406,6 +1515,7 @@ class MessageLookup extends MessageLookupByLibrary { "saveCollage": MessageLookupByLibrary.simpleMessage("Guardar collage"), "saveCopy": MessageLookupByLibrary.simpleMessage("Guardar copia"), "saveKey": MessageLookupByLibrary.simpleMessage("Guardar Clave"), + "savePerson": MessageLookupByLibrary.simpleMessage("Guardar persona"), "saveYourRecoveryKeyIfYouHaventAlready": MessageLookupByLibrary.simpleMessage( "Guarda tu clave de recuperación si aún no lo has hecho"), @@ -1427,6 +1537,8 @@ class MessageLookup extends MessageLookupByLibrary { "Agrega descripciones como \"#viaje\" en la información de la foto para encontrarlas aquí rápidamente"), "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage("Buscar por fecha, mes o año"), + "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( + "Las imágenes se mostrarán aquí cuando se complete el procesamiento"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "Las personas se mostrarán aquí una vez que se haya hecho la indexación"), "searchFileTypesAndNamesEmptySection": @@ -1444,10 +1556,13 @@ class MessageLookup extends MessageLookupByLibrary { "Agrupar las fotos que se tomaron cerca de la localización de una foto"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Invita a gente y verás todas las fotos compartidas aquí"), - "searchResultCount": m55, + "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( + "Las personas se mostrarán aquí cuando se complete el procesamiento"), + "searchResultCount": m56, + "searchSectionsLengthMismatch": m57, "security": MessageLookupByLibrary.simpleMessage("Seguridad"), "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), + "Ver enlaces del álbum público en la aplicación"), "selectALocation": MessageLookupByLibrary.simpleMessage("Seleccionar una ubicación"), "selectALocationFirst": MessageLookupByLibrary.simpleMessage( @@ -1455,12 +1570,17 @@ class MessageLookup extends MessageLookupByLibrary { "selectAlbum": MessageLookupByLibrary.simpleMessage("Seleccionar álbum"), "selectAll": MessageLookupByLibrary.simpleMessage("Seleccionar todos"), + "selectAllShort": MessageLookupByLibrary.simpleMessage("Todas"), + "selectCoverPhoto": + MessageLookupByLibrary.simpleMessage("Seleccionar foto de portada"), "selectFoldersForBackup": MessageLookupByLibrary.simpleMessage( - "Seleccionar carpetas para el respaldo"), + "Seleccionar carpetas para la copia de seguridad"), "selectItemsToAdd": MessageLookupByLibrary.simpleMessage( "Selecciona elementos para agregar"), "selectLanguage": MessageLookupByLibrary.simpleMessage("Seleccionar idioma"), + "selectMailApp": + MessageLookupByLibrary.simpleMessage("Seleccionar app de correo"), "selectMorePhotos": MessageLookupByLibrary.simpleMessage("Seleccionar más fotos"), "selectReason": @@ -1471,12 +1591,12 @@ class MessageLookup extends MessageLookupByLibrary { "Los archivos seleccionados no están en Ente"), "selectedFoldersWillBeEncryptedAndBackedUp": MessageLookupByLibrary.simpleMessage( - "Las carpetas seleccionadas se cifrarán y se respaldarán"), + "Las carpetas seleccionadas se cifrarán y se realizará una copia de seguridad"), "selectedItemsWillBeDeletedFromAllAlbumsAndMoved": MessageLookupByLibrary.simpleMessage( "Los archivos seleccionados serán eliminados de todos los álbumes y movidos a la papelera."), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "send": MessageLookupByLibrary.simpleMessage("Enviar"), "sendEmail": MessageLookupByLibrary.simpleMessage("Enviar correo electrónico"), @@ -1486,6 +1606,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Punto final del servidor"), "sessionExpired": MessageLookupByLibrary.simpleMessage("La sesión ha expirado"), + "sessionIdMismatch": + MessageLookupByLibrary.simpleMessage("El ID de sesión no coincide"), "setAPassword": MessageLookupByLibrary.simpleMessage("Establecer una contraseña"), "setAs": MessageLookupByLibrary.simpleMessage("Establecer como"), @@ -1508,16 +1630,16 @@ class MessageLookup extends MessageLookupByLibrary { "shareAnAlbumNow": MessageLookupByLibrary.simpleMessage("Compartir un álbum ahora"), "shareLink": MessageLookupByLibrary.simpleMessage("Compartir enlace"), - "shareMyVerificationID": m58, + "shareMyVerificationID": m59, "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage( "Comparte sólo con la gente que quieres"), "shareTextConfirmOthersVerificationID": m5, "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage( "Descarga Ente para que podamos compartir fácilmente fotos y videos en calidad original.\n\nhttps://ente.io"), - "shareTextReferralCode": m59, + "shareTextReferralCode": m60, "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage( "Compartir con usuarios fuera de Ente"), - "shareWithPeopleSectionTitle": m60, + "shareWithPeopleSectionTitle": m61, "shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage("Comparte tu primer álbum"), "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( @@ -1529,7 +1651,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Nuevas fotos compartidas"), "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( "Recibir notificaciones cuando alguien agrega una foto a un álbum compartido contigo"), - "sharedWith": m61, + "sharedWith": m62, "sharedWithMe": MessageLookupByLibrary.simpleMessage("Compartido conmigo"), "sharedWithYou": @@ -1546,11 +1668,11 @@ class MessageLookup extends MessageLookupByLibrary { "Cerrar la sesión de otros dispositivos"), "signUpTerms": MessageLookupByLibrary.simpleMessage( "Estoy de acuerdo con los términos del servicio y la política de privacidad"), - "singleFileDeleteFromDevice": m62, + "singleFileDeleteFromDevice": m63, "singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage( "Se borrará de todos los álbumes."), - "singleFileInBothLocalAndRemote": m63, - "singleFileInRemoteOnly": m64, + "singleFileInBothLocalAndRemote": m64, + "singleFileInRemoteOnly": m65, "skip": MessageLookupByLibrary.simpleMessage("Omitir"), "social": MessageLookupByLibrary.simpleMessage("Social"), "someItemsAreInBothEnteAndYourDevice": @@ -1578,7 +1700,7 @@ class MessageLookup extends MessageLookupByLibrary { "Lo sentimos, el código que has introducido es incorrecto"), "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": MessageLookupByLibrary.simpleMessage( - "Lo sentimos, no hemos podido generar claves seguras en este dispositivo.\n\nRegístrate desde un dispositivo diferente."), + "Lo sentimos, no hemos podido generar claves seguras en este dispositivo.\n\nPor favor, regístrate desde un dispositivo diferente."), "sort": MessageLookupByLibrary.simpleMessage("Ordenar"), "sortAlbumsBy": MessageLookupByLibrary.simpleMessage("Ordenar por"), "sortNewestFirst": @@ -1599,10 +1721,10 @@ class MessageLookup extends MessageLookupByLibrary { "storageInGB": m1, "storageLimitExceeded": MessageLookupByLibrary.simpleMessage("Límite de datos excedido"), - "storageUsageInfo": m65, + "storageUsageInfo": m66, "strongStrength": MessageLookupByLibrary.simpleMessage("Segura"), - "subAlreadyLinkedErrMessage": m66, - "subWillBeCancelledOn": m67, + "subAlreadyLinkedErrMessage": m67, + "subWillBeCancelledOn": m68, "subscribe": MessageLookupByLibrary.simpleMessage("Suscribirse"), "subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage( "Necesitas una suscripción activa de pago para habilitar el compartir."), @@ -1619,7 +1741,7 @@ class MessageLookup extends MessageLookupByLibrary { "suggestFeatures": MessageLookupByLibrary.simpleMessage("Sugerir una característica"), "support": MessageLookupByLibrary.simpleMessage("Soporte"), - "syncProgress": m68, + "syncProgress": m69, "syncStopped": MessageLookupByLibrary.simpleMessage("Sincronización detenida"), "syncing": MessageLookupByLibrary.simpleMessage("Sincronizando..."), @@ -1629,6 +1751,8 @@ class MessageLookup extends MessageLookupByLibrary { "Toca para introducir el código"), "tapToUnlock": MessageLookupByLibrary.simpleMessage("Toca para desbloquear"), + "tapToUpload": MessageLookupByLibrary.simpleMessage("Toca para subir"), + "tapToUploadIsIgnoredDue": m70, "tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage( "Parece que algo salió mal. Por favor, vuelve a intentarlo después de algún tiempo. Si el error persiste, ponte en contacto con nuestro equipo de soporte."), "terminate": MessageLookupByLibrary.simpleMessage("Terminar"), @@ -1644,7 +1768,7 @@ class MessageLookup extends MessageLookupByLibrary { "No se ha podido completar la descarga"), "theLinkYouAreTryingToAccessHasExpired": MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), + "El enlace al que intenta acceder ha caducado."), "theRecoveryKeyYouEnteredIsIncorrect": MessageLookupByLibrary.simpleMessage( "La clave de recuperación introducida es incorrecta"), @@ -1652,7 +1776,7 @@ class MessageLookup extends MessageLookupByLibrary { "theseItemsWillBeDeletedFromYourDevice": MessageLookupByLibrary.simpleMessage( "Estos elementos se eliminarán de tu dispositivo."), - "theyAlsoGetXGb": m70, + "theyAlsoGetXGb": m71, "theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage( "Se borrarán de todos los álbumes."), "thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage( @@ -1668,7 +1792,7 @@ class MessageLookup extends MessageLookupByLibrary { "Este correo electrónico ya está en uso"), "thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage( "Esta imagen no tiene datos exif"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Esta es tu ID de verificación"), "thisWillLogYouOutOfTheFollowingDevice": @@ -1692,8 +1816,8 @@ class MessageLookup extends MessageLookupByLibrary { "total": MessageLookupByLibrary.simpleMessage("total"), "totalSize": MessageLookupByLibrary.simpleMessage("Tamaño total"), "trash": MessageLookupByLibrary.simpleMessage("Papelera"), - "trashDaysLeft": m72, - "trim": MessageLookupByLibrary.simpleMessage("Recortar"), + "trashDaysLeft": m73, + "trim": MessageLookupByLibrary.simpleMessage("Ajustar duración"), "tryAgain": MessageLookupByLibrary.simpleMessage("Inténtalo de nuevo"), "turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage( "Activar la copia de seguridad para subir automáticamente archivos añadidos a la carpeta de este dispositivo a Ente."), @@ -1711,6 +1835,7 @@ class MessageLookup extends MessageLookupByLibrary { "Autenticación de doble factor restablecida con éxito"), "twofactorSetup": MessageLookupByLibrary.simpleMessage("Configuración de dos pasos"), + "typeOfGallerGallerytypeIsNotSupportedForRename": m74, "unarchive": MessageLookupByLibrary.simpleMessage("Desarchivar"), "unarchiveAlbum": MessageLookupByLibrary.simpleMessage("Desarchivar álbum"), @@ -1735,15 +1860,16 @@ class MessageLookup extends MessageLookupByLibrary { "updatingFolderSelection": MessageLookupByLibrary.simpleMessage( "Actualizando la selección de carpeta..."), "upgrade": MessageLookupByLibrary.simpleMessage("Mejorar"), + "uploadIsIgnoredDueToIgnorereason": m75, "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage( "Subiendo archivos al álbum..."), - "uploadingMultipleMemories": m75, + "uploadingMultipleMemories": m76, "uploadingSingleMemory": MessageLookupByLibrary.simpleMessage("Preservando 1 memoria..."), "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( "Hasta el 50% de descuento, hasta el 4 de diciembre."), "usableReferralStorageInfo": MessageLookupByLibrary.simpleMessage( - "El almacenamiento utilizable está limitado por tu plan actual. El exceso de almacenamiento reclamado se volverá automáticamente utilizable cuando actualices tu plan."), + "El almacenamiento utilizable está limitado por tu plan actual. El exceso de almacenamiento que obtengas se volverá automáticamente utilizable cuando actualices tu plan."), "useAsCover": MessageLookupByLibrary.simpleMessage("Usar como cubierta"), "usePublicLinksForPeopleNotOnEnte": @@ -1754,7 +1880,7 @@ class MessageLookup extends MessageLookupByLibrary { "useSelectedPhoto": MessageLookupByLibrary.simpleMessage("Usar foto seleccionada"), "usedSpace": MessageLookupByLibrary.simpleMessage("Espacio usado"), - "validTill": m76, + "validTill": m77, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "Verificación fallida, por favor inténtalo de nuevo"), @@ -1763,7 +1889,7 @@ class MessageLookup extends MessageLookupByLibrary { "verify": MessageLookupByLibrary.simpleMessage("Verificar"), "verifyEmail": MessageLookupByLibrary.simpleMessage( "Verificar correo electrónico"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("Verificar"), "verifyPasskey": MessageLookupByLibrary.simpleMessage("Verificar clave de acceso"), @@ -1791,6 +1917,7 @@ class MessageLookup extends MessageLookupByLibrary { "viewRecoveryKey": MessageLookupByLibrary.simpleMessage("Ver código de recuperación"), "viewer": MessageLookupByLibrary.simpleMessage("Espectador"), + "viewersSuccessfullyAdded": m79, "visitWebToManage": MessageLookupByLibrary.simpleMessage( "Por favor, visita web.ente.io para administrar tu suscripción"), "waitingForVerification": @@ -1807,8 +1934,9 @@ class MessageLookup extends MessageLookupByLibrary { "welcomeBack": MessageLookupByLibrary.simpleMessage("¡Bienvenido de nuevo!"), "whatsNew": MessageLookupByLibrary.simpleMessage("Qué hay de nuevo"), + "yearShort": MessageLookupByLibrary.simpleMessage("año"), "yearly": MessageLookupByLibrary.simpleMessage("Anualmente"), - "yearsAgo": m79, + "yearsAgo": m80, "yes": MessageLookupByLibrary.simpleMessage("Sí"), "yesCancel": MessageLookupByLibrary.simpleMessage("Sí, cancelar"), "yesConvertToViewer": @@ -1819,13 +1947,15 @@ class MessageLookup extends MessageLookupByLibrary { "yesLogout": MessageLookupByLibrary.simpleMessage("Sí, cerrar sesión"), "yesRemove": MessageLookupByLibrary.simpleMessage("Sí, quitar"), "yesRenew": MessageLookupByLibrary.simpleMessage("Sí, renovar"), + "yesResetPerson": + MessageLookupByLibrary.simpleMessage("Si, eliminar persona"), "you": MessageLookupByLibrary.simpleMessage("Tu"), "youAreOnAFamilyPlan": MessageLookupByLibrary.simpleMessage("¡Estás en un plan familiar!"), "youAreOnTheLatestVersion": MessageLookupByLibrary.simpleMessage( "Estás usando la última versión"), "youCanAtMaxDoubleYourStorage": MessageLookupByLibrary.simpleMessage( - "* Puedes como máximo duplicar tu almacenamiento"), + "* Como máximo puedes duplicar tu almacenamiento"), "youCanManageYourLinksInTheShareTab": MessageLookupByLibrary.simpleMessage( "Puedes administrar tus enlaces en la pestaña compartir."), @@ -1838,7 +1968,7 @@ class MessageLookup extends MessageLookupByLibrary { "No puedes compartir contigo mismo"), "youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage( "No tienes ningún elemento archivado."), - "youHaveSuccessfullyFreedUp": m80, + "youHaveSuccessfullyFreedUp": m81, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage("Tu cuenta ha sido eliminada"), "yourMap": MessageLookupByLibrary.simpleMessage("Tu mapa"), diff --git a/mobile/lib/generated/intl/messages_et.dart b/mobile/lib/generated/intl/messages_et.dart index 7987ca82aa..dc3a61a6ff 100644 --- a/mobile/lib/generated/intl/messages_et.dart +++ b/mobile/lib/generated/intl/messages_et.dart @@ -35,14 +35,10 @@ class MessageLookup extends MessageLookupByLibrary { "albumOwner": MessageLookupByLibrary.simpleMessage("Omanik"), "albumUpdated": MessageLookupByLibrary.simpleMessage("Albumit on uuendatud"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "allowDownloads": MessageLookupByLibrary.simpleMessage("Luba allalaadimised"), "appleId": MessageLookupByLibrary.simpleMessage("Apple ID"), "apply": MessageLookupByLibrary.simpleMessage("Rakenda"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "blog": MessageLookupByLibrary.simpleMessage("Blogi"), "cancel": MessageLookupByLibrary.simpleMessage("Loobu"), "changeEmail": MessageLookupByLibrary.simpleMessage("Muuda e-posti"), @@ -114,8 +110,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Sisesta oma parool"), "exportYourData": MessageLookupByLibrary.simpleMessage("Ekspordi oma andmed"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "faq": MessageLookupByLibrary.simpleMessage("KKK"), "faqs": MessageLookupByLibrary.simpleMessage("KKK"), "feedback": MessageLookupByLibrary.simpleMessage("Tagasiside"), @@ -175,11 +169,6 @@ class MessageLookup extends MessageLookupByLibrary { "oops": MessageLookupByLibrary.simpleMessage("Oih"), "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage("Oih, midagi läks valesti"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "password": MessageLookupByLibrary.simpleMessage("Parool"), "photoSmallCase": MessageLookupByLibrary.simpleMessage("foto"), "pleaseTryAgain": @@ -215,8 +204,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Skaneeri seda QR koodi\noma autentimisrakendusega"), "security": MessageLookupByLibrary.simpleMessage("Turvalisus"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "selectAll": MessageLookupByLibrary.simpleMessage("Vali kõik"), "selectLanguage": MessageLookupByLibrary.simpleMessage("Vali keel"), "selectReason": MessageLookupByLibrary.simpleMessage("Vali põhjus"), @@ -253,9 +240,6 @@ class MessageLookup extends MessageLookupByLibrary { "terms": MessageLookupByLibrary.simpleMessage("Tingimused"), "termsOfServicesTitle": MessageLookupByLibrary.simpleMessage("Tingimused"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "theme": MessageLookupByLibrary.simpleMessage("Teema"), "thisDevice": MessageLookupByLibrary.simpleMessage("See seade"), "trash": MessageLookupByLibrary.simpleMessage("Prügikast"), diff --git a/mobile/lib/generated/intl/messages_fa.dart b/mobile/lib/generated/intl/messages_fa.dart index 0e2ae246ee..41be2be189 100644 --- a/mobile/lib/generated/intl/messages_fa.dart +++ b/mobile/lib/generated/intl/messages_fa.dart @@ -31,13 +31,13 @@ class MessageLookup extends MessageLookupByLibrary { static String m0(passwordStrengthValue) => "قدرت رمز عبور: ${passwordStrengthValue}"; - static String m51(storeName) => "به ما در ${storeName} امتیاز دهید"; + static String m52(storeName) => "به ما در ${storeName} امتیاز دهید"; - static String m65( + static String m66( usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => "${usedAmount} ${usedStorageUnit} از ${totalAmount} ${totalStorageUnit} استفاده شده"; - static String m77(email) => "تایید ${email}"; + static String m78(email) => "تایید ${email}"; static String m2(email) => "ما یک ایمیل به ${email} ارسال کرده‌ایم"; @@ -62,13 +62,10 @@ class MessageLookup extends MessageLookupByLibrary { "addedAs": MessageLookupByLibrary.simpleMessage("اضافه شده به عنوان"), "advanced": MessageLookupByLibrary.simpleMessage("پیشرفته"), "albumUpdated": MessageLookupByLibrary.simpleMessage("آلبوم به‌روز شد"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "به افراد که این پیوند را دارند، اجازه دهید عکس‌ها را به آلبوم اشتراک گذاری شده اضافه کنند."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage("اجازه اضافه کردن عکس"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( "به افراد اجازه دهید عکس اضافه کنند"), "androidBiometricHint": @@ -93,7 +90,6 @@ class MessageLookup extends MessageLookupByLibrary { "backedUpFolders": MessageLookupByLibrary.simpleMessage("پوشه‌های پشتیبان گیری شده"), "backup": MessageLookupByLibrary.simpleMessage("پشتیبان گیری"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "blog": MessageLookupByLibrary.simpleMessage("وبلاگ"), "cancel": MessageLookupByLibrary.simpleMessage("لغو"), "cannotDeleteSharedFiles": MessageLookupByLibrary.simpleMessage( @@ -203,8 +199,6 @@ class MessageLookup extends MessageLookupByLibrary { "error": MessageLookupByLibrary.simpleMessage("خطا"), "everywhere": MessageLookupByLibrary.simpleMessage("همه جا"), "existingUser": MessageLookupByLibrary.simpleMessage("کاربر موجود"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "familyPlanPortalTitle": MessageLookupByLibrary.simpleMessage("خانوادگی"), "familyPlans": @@ -275,11 +269,6 @@ class MessageLookup extends MessageLookupByLibrary { "notifications": MessageLookupByLibrary.simpleMessage("آگاه‌سازی‌ها"), "ok": MessageLookupByLibrary.simpleMessage("تایید"), "oops": MessageLookupByLibrary.simpleMessage("اوه"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "password": MessageLookupByLibrary.simpleMessage("رمز عبور"), "passwordChangedSuccessfully": MessageLookupByLibrary.simpleMessage( "رمز عبور با موفقیت تغییر کرد"), @@ -303,7 +292,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("پشتیبان گیری خصوصی"), "privateSharing": MessageLookupByLibrary.simpleMessage("اشتراک گذاری خصوصی"), - "rateUsOnStore": m51, + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("بازیابی"), "recoverAccount": MessageLookupByLibrary.simpleMessage("بازیابی حساب کاربری"), @@ -336,8 +325,6 @@ class MessageLookup extends MessageLookupByLibrary { "saveKey": MessageLookupByLibrary.simpleMessage("ذخیره کلید"), "search": MessageLookupByLibrary.simpleMessage("جستجو"), "security": MessageLookupByLibrary.simpleMessage("امنیت"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "selectAll": MessageLookupByLibrary.simpleMessage("انتخاب همه"), "selectFoldersForBackup": MessageLookupByLibrary.simpleMessage( "پوشه‌ها را برای پشتیبان گیری انتخاب کنید"), @@ -381,7 +368,7 @@ class MessageLookup extends MessageLookupByLibrary { "storageBreakupFamily": MessageLookupByLibrary.simpleMessage("خانوادگی"), "storageBreakupYou": MessageLookupByLibrary.simpleMessage("شما"), - "storageUsageInfo": m65, + "storageUsageInfo": m66, "strongStrength": MessageLookupByLibrary.simpleMessage("قوی"), "support": MessageLookupByLibrary.simpleMessage("پشتیبانی"), "systemTheme": MessageLookupByLibrary.simpleMessage("سیستم"), @@ -397,9 +384,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("شرایط و مقررات"), "theDownloadCouldNotBeCompleted": MessageLookupByLibrary.simpleMessage("دانلود کامل نشد"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "theme": MessageLookupByLibrary.simpleMessage("تم"), "thisDevice": MessageLookupByLibrary.simpleMessage("این دستگاه"), "thisWillLogYouOutOfTheFollowingDevice": @@ -425,7 +409,7 @@ class MessageLookup extends MessageLookupByLibrary { "از کلید بازیابی استفاده کنید"), "verify": MessageLookupByLibrary.simpleMessage("تایید"), "verifyEmail": MessageLookupByLibrary.simpleMessage("تایید ایمیل"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("تایید"), "verifyPassword": MessageLookupByLibrary.simpleMessage("تایید رمز عبور"), diff --git a/mobile/lib/generated/intl/messages_fr.dart b/mobile/lib/generated/intl/messages_fr.dart index 0641a00830..dc42a2d971 100644 --- a/mobile/lib/generated/intl/messages_fr.dart +++ b/mobile/lib/generated/intl/messages_fr.dart @@ -42,7 +42,7 @@ class MessageLookup extends MessageLookupByLibrary { static String m13(versionValue) => "Version : ${versionValue}"; static String m14(freeAmount, storageUnit) => - "${freeAmount} ${storageUnit} gratuit"; + "${freeAmount} ${storageUnit} libre"; static String m15(paymentProvider) => "Veuillez d\'abord annuler votre abonnement existant de ${paymentProvider}"; @@ -62,7 +62,7 @@ class MessageLookup extends MessageLookupByLibrary { static String m18(albumName) => "Lien collaboratif créé pour ${albumName}"; - static String m81(count) => + static String m82(count) => "${Intl.plural(count, zero: '0 collaborateur ajouté', one: '1 collaborateur ajouté', other: '${count} collaborateurs ajoutés')}"; static String m19(familyAdminEmail) => @@ -126,7 +126,7 @@ class MessageLookup extends MessageLookupByLibrary { static String m40(expiryTime) => "Le lien expirera le ${expiryTime}"; static String m3(count, formattedCount) => - "${Intl.plural(count, one: '${formattedCount} mémoire', other: '${formattedCount} souvenirs')}"; + "${Intl.plural(count, one: '${formattedCount} souvenir', other: '${formattedCount} souvenirs')}"; static String m41(count) => "${Intl.plural(count, one: 'Déplacez l\'objet', other: 'Déplacez des objets')}"; @@ -146,108 +146,108 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(providerName) => "Veuillez contacter le support ${providerName} si vous avez été facturé"; - static String m82(count) => + static String m47(count) => "${Intl.plural(count, zero: '0 photo', one: '1 photo', other: '${count} photos')}"; - static String m47(endDate) => + static String m48(endDate) => "Essai gratuit valable jusqu\'à ${endDate}.\nVous pouvez choisir un plan payant par la suite."; - static String m48(toEmail) => "Merci de nous envoyer un e-mail à ${toEmail}"; + static String m49(toEmail) => "Merci de nous envoyer un e-mail à ${toEmail}"; - static String m49(toEmail) => "Envoyez les logs à ${toEmail}"; + static String m50(toEmail) => "Envoyez les logs à ${toEmail}"; - static String m50(folderName) => "Traitement de ${folderName}..."; + static String m51(folderName) => "Traitement de ${folderName}..."; - static String m51(storeName) => "Notez-nous sur ${storeName}"; + static String m52(storeName) => "Notez-nous sur ${storeName}"; - static String m52(storageInGB) => + static String m53(storageInGB) => "3. Vous recevez tous les deux ${storageInGB} GB* gratuits"; - static String m53(userEmail) => + static String m54(userEmail) => "${userEmail} sera retiré de cet album partagé\n\nToutes les photos ajoutées par eux seront également retirées de l\'album"; - static String m54(endDate) => "Renouvellement le ${endDate}"; + static String m55(endDate) => "Renouvellement le ${endDate}"; - static String m55(count) => + static String m56(count) => "${Intl.plural(count, one: '${count} résultat trouvé', other: '${count} résultats trouvés')}"; - static String m56(snapshotLenght, searchLenght) => + static String m57(snapshotLenght, searchLenght) => "Incompatibilité de la longueur des sections: ${snapshotLenght} != ${searchLenght}"; static String m4(count) => "${count} sélectionné(s)"; - static String m57(count, yourCount) => + static String m58(count, yourCount) => "${count} sélectionné(s) (${yourCount} à vous)"; - static String m58(verificationID) => + static String m59(verificationID) => "Voici mon ID de vérification : ${verificationID} pour ente.io."; static String m5(verificationID) => "Hé, pouvez-vous confirmer qu\'il s\'agit de votre ID de vérification ente.io : ${verificationID}"; - static String m59(referralCode, referralStorageInGB) => + static String m60(referralCode, referralStorageInGB) => "Code de parrainage Ente : ${referralCode} \n\nValidez le dans Paramètres → Général → Références pour obtenir ${referralStorageInGB} Go gratuitement après votre inscription à un plan payant\n\nhttps://ente.io"; - static String m60(numberOfPeople) => + static String m61(numberOfPeople) => "${Intl.plural(numberOfPeople, zero: 'Partagez avec des personnes spécifiques', one: 'Partagé avec 1 personne', other: 'Partagé avec ${numberOfPeople} personnes')}"; - static String m61(emailIDs) => "Partagé avec ${emailIDs}"; - - static String m62(fileType) => - "Elle ${fileType} sera supprimée de votre appareil."; + static String m62(emailIDs) => "Partagé avec ${emailIDs}"; static String m63(fileType) => + "Elle ${fileType} sera supprimée de votre appareil."; + + static String m64(fileType) => "Cette ${fileType} est à la fois sur ente et sur votre appareil."; - static String m64(fileType) => "Cette ${fileType} sera supprimée de l\'Ente."; + static String m65(fileType) => "Cette ${fileType} sera supprimée de l\'Ente."; static String m1(storageAmountInGB) => "${storageAmountInGB} Go"; - static String m65( + static String m66( usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => "${usedAmount} ${usedStorageUnit} sur ${totalAmount} ${totalStorageUnit} utilisé"; - static String m66(id) => + static String m67(id) => "Votre ${id} est déjà lié à un autre compte Ente.\nSi vous souhaitez utiliser votre ${id} avec ce compte, veuillez contacter notre support"; - static String m67(endDate) => "Votre abonnement sera annulé le ${endDate}"; + static String m68(endDate) => "Votre abonnement sera annulé le ${endDate}"; - static String m68(completed, total) => + static String m69(completed, total) => "${completed}/${total} souvenirs conservés"; - static String m69(ignoreReason) => + static String m70(ignoreReason) => "Appuyer pour envoyer, l\'envoi est actuellement ignoré en raison de ${ignoreReason}"; - static String m70(storageAmountInGB) => + static String m71(storageAmountInGB) => "Ils obtiennent aussi ${storageAmountInGB} Go"; - static String m71(email) => "Ceci est l\'ID de vérification de ${email}"; + static String m72(email) => "Ceci est l\'ID de vérification de ${email}"; - static String m72(count) => - "${Intl.plural(count, zero: '0 jour', one: '1 jour', other: '${count} jours')}"; + static String m73(count) => + "${Intl.plural(count, zero: 'Bientôt', one: '1 jour', other: '${count} jours')}"; - static String m73(galleryType) => + static String m74(galleryType) => "Les galeries de type \'${galleryType}\' ne peuvent être renommées"; - static String m74(ignoreReason) => + static String m75(ignoreReason) => "L\'envoi est ignoré en raison de ${ignoreReason}"; - static String m75(count) => "Sauvegarde ${count} souvenirs..."; + static String m76(count) => "Sauvegarde ${count} souvenirs..."; - static String m76(endDate) => "Valable jusqu\'au ${endDate}"; + static String m77(endDate) => "Valable jusqu\'au ${endDate}"; - static String m77(email) => "Vérifier ${email}"; + static String m78(email) => "Vérifier ${email}"; - static String m78(count) => + static String m79(count) => "${Intl.plural(count, zero: '0 observateur ajouté', one: '1 observateur ajouté', other: '${count} observateurs ajoutés')}"; static String m2(email) => "Nous avons envoyé un e-mail à ${email}"; - static String m79(count) => + static String m80(count) => "${Intl.plural(count, one: 'il y a ${count} an', other: 'il y a ${count} ans')}"; - static String m80(storageSaved) => + static String m81(storageSaved) => "Vous avez libéré ${storageSaved} avec succès !"; final messages = _notInlinedMessages(_notInlinedMessages); @@ -324,16 +324,16 @@ class MessageLookup extends MessageLookupByLibrary { "albums": MessageLookupByLibrary.simpleMessage("Albums"), "allClear": MessageLookupByLibrary.simpleMessage("✨ Tout est effacé"), "allMemoriesPreserved": MessageLookupByLibrary.simpleMessage( - "Tous les souvenirs conservés"), + "Tous les souvenirs sont conservés"), "allPersonGroupingWillReset": MessageLookupByLibrary.simpleMessage( "Tous les groupements pour cette personne seront réinitialisés, et vous perdrez toutes les suggestions faites pour cette personne"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), + "allow": MessageLookupByLibrary.simpleMessage("Autoriser"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "Autoriser les personnes avec le lien à ajouter des photos à l\'album partagé."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage( "Autoriser l\'ajout de photos"), "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), + "Autoriser l\'application à ouvrir les liens d\'albums partagés"), "allowDownloads": MessageLookupByLibrary.simpleMessage( "Autoriser les téléchargements"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( @@ -450,7 +450,8 @@ class MessageLookup extends MessageLookupByLibrary { "backup": MessageLookupByLibrary.simpleMessage("Sauvegarde"), "backupFailed": MessageLookupByLibrary.simpleMessage("Échec de la sauvegarde"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), + "backupFile": + MessageLookupByLibrary.simpleMessage("Sauvegarder le fichier"), "backupOverMobileData": MessageLookupByLibrary.simpleMessage( "Sauvegarde sur données mobiles"), "backupSettings": @@ -567,7 +568,7 @@ class MessageLookup extends MessageLookupByLibrary { "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": MessageLookupByLibrary.simpleMessage( "Les collaborateurs peuvent ajouter des photos et des vidéos à l\'album partagé."), - "collaboratorsSuccessfullyAdded": m81, + "collaboratorsSuccessfullyAdded": m82, "collageLayout": MessageLookupByLibrary.simpleMessage("Disposition"), "collageSaved": MessageLookupByLibrary.simpleMessage( "Collage sauvegardé dans la galerie"), @@ -686,7 +687,7 @@ class MessageLookup extends MessageLookupByLibrary { "deleteFromDevice": MessageLookupByLibrary.simpleMessage("Supprimer de l\'appareil"), "deleteFromEnte": - MessageLookupByLibrary.simpleMessage("Supprimé de Ente"), + MessageLookupByLibrary.simpleMessage("Supprimer de Ente"), "deleteItemCount": m22, "deleteLocation": MessageLookupByLibrary.simpleMessage("Supprimer la localisation"), @@ -877,7 +878,7 @@ class MessageLookup extends MessageLookupByLibrary { "Photos supplémentaires trouvées"), "extraPhotosFoundFor": m30, "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), + "Ce visage n\'a pas encore été regroupé, veuillez revenir plus tard"), "faceRecognition": MessageLookupByLibrary.simpleMessage("Reconnaissance faciale"), "faces": MessageLookupByLibrary.simpleMessage("Visages"), @@ -1135,6 +1136,8 @@ class MessageLookup extends MessageLookupByLibrary { "Votre session a expiré. Veuillez vous reconnecter."), "loginTerms": MessageLookupByLibrary.simpleMessage( "En cliquant sur connecter, j\'accepte les conditions d\'utilisation et la politique de confidentialité"), + "loginWithTOTP": + MessageLookupByLibrary.simpleMessage("Se connecter avec TOTP"), "logout": MessageLookupByLibrary.simpleMessage("Déconnexion"), "logsDialogBody": MessageLookupByLibrary.simpleMessage( "Cela enverra des logs pour nous aider à déboguer votre problème. Veuillez noter que les noms de fichiers seront inclus pour aider à suivre les problèmes avec des fichiers spécifiques."), @@ -1157,7 +1160,9 @@ class MessageLookup extends MessageLookupByLibrary { "La recherche magique permet de rechercher des photos par leur contenu, par exemple \'fleur\', \'voiture rouge\', \'documents d\'identité\'"), "manage": MessageLookupByLibrary.simpleMessage("Gérer"), "manageDeviceStorage": MessageLookupByLibrary.simpleMessage( - "Gérer le stockage de l\'appareil"), + "Gérer le cache de l\'appareil"), + "manageDeviceStorageDesc": + MessageLookupByLibrary.simpleMessage("Examiner et vider le cache."), "manageFamily": MessageLookupByLibrary.simpleMessage("Gérer la famille"), "manageLink": MessageLookupByLibrary.simpleMessage("Gérer le lien"), @@ -1277,11 +1282,11 @@ class MessageLookup extends MessageLookupByLibrary { "Oups, impossible d\'enregistrer les modifications"), "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage( "Oups, une erreur est arrivée"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), + "openAlbumInBrowser": MessageLookupByLibrary.simpleMessage( + "Ouvrir l\'album dans le navigateur"), "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), + "Veuillez utiliser l\'application web pour ajouter des photos à cet album"), + "openFile": MessageLookupByLibrary.simpleMessage("Ouvrir le fichier"), "openSettings": MessageLookupByLibrary.simpleMessage("Ouvrir les paramètres"), "openTheItem": @@ -1346,7 +1351,7 @@ class MessageLookup extends MessageLookupByLibrary { "photosAddedByYouWillBeRemovedFromTheAlbum": MessageLookupByLibrary.simpleMessage( "Les photos ajoutées par vous seront retirées de l\'album"), - "photosCount": m82, + "photosCount": m47, "pickCenterPoint": MessageLookupByLibrary.simpleMessage( "Sélectionner le point central"), "pinAlbum": MessageLookupByLibrary.simpleMessage("Épingler l\'album"), @@ -1354,7 +1359,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Verrouillage du code PIN"), "playOnTv": MessageLookupByLibrary.simpleMessage("Lire l\'album sur la TV"), - "playStoreFreeTrialValidTill": m47, + "playStoreFreeTrialValidTill": m48, "playstoreSubscription": MessageLookupByLibrary.simpleMessage("Abonnement au PlayStore"), "pleaseCheckYourInternetConnectionAndTryAgain": @@ -1366,14 +1371,14 @@ class MessageLookup extends MessageLookupByLibrary { "pleaseContactSupportIfTheProblemPersists": MessageLookupByLibrary.simpleMessage( "Merci de contacter l\'assistance si cette erreur persiste"), - "pleaseEmailUsAt": m48, + "pleaseEmailUsAt": m49, "pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage( "Veuillez accorder la permission"), "pleaseLoginAgain": MessageLookupByLibrary.simpleMessage("Veuillez vous reconnecter"), "pleaseSelectQuickLinksToRemove": MessageLookupByLibrary.simpleMessage( "Veuillez sélectionner les liens rapides à supprimer"), - "pleaseSendTheLogsTo": m49, + "pleaseSendTheLogsTo": m50, "pleaseTryAgain": MessageLookupByLibrary.simpleMessage("Veuillez réessayer"), "pleaseVerifyTheCodeYouHaveEntered": @@ -1399,7 +1404,7 @@ class MessageLookup extends MessageLookupByLibrary { "privateBackups": MessageLookupByLibrary.simpleMessage("Sauvegardes privées"), "privateSharing": MessageLookupByLibrary.simpleMessage("Partage privé"), - "processingImport": m50, + "processingImport": m51, "publicLinkCreated": MessageLookupByLibrary.simpleMessage("Lien public créé"), "publicLinkEnabled": @@ -1410,7 +1415,7 @@ class MessageLookup extends MessageLookupByLibrary { "rateTheApp": MessageLookupByLibrary.simpleMessage("Évaluer l\'application"), "rateUs": MessageLookupByLibrary.simpleMessage("Évaluez-nous"), - "rateUsOnStore": m51, + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("Récupérer"), "recoverAccount": MessageLookupByLibrary.simpleMessage("Récupérer un compte"), @@ -1440,12 +1445,12 @@ class MessageLookup extends MessageLookupByLibrary { "reenterPin": MessageLookupByLibrary.simpleMessage("Ressaisir le code PIN"), "referFriendsAnd2xYourPlan": MessageLookupByLibrary.simpleMessage( - "Parrainez des amis et 2x votre abonnement"), + "Parrainez des amis et doublez votre abonnement"), "referralStep1": MessageLookupByLibrary.simpleMessage( "1. Donnez ce code à vos amis"), "referralStep2": MessageLookupByLibrary.simpleMessage( "2. Ils s\'inscrivent à une offre payante"), - "referralStep3": m52, + "referralStep3": m53, "referrals": MessageLookupByLibrary.simpleMessage("Parrainages"), "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage( "Les recommandations sont actuellement en pause"), @@ -1473,7 +1478,7 @@ class MessageLookup extends MessageLookupByLibrary { "removeLink": MessageLookupByLibrary.simpleMessage("Supprimer le lien"), "removeParticipant": MessageLookupByLibrary.simpleMessage("Supprimer le participant"), - "removeParticipantBody": m53, + "removeParticipantBody": m54, "removePersonLabel": MessageLookupByLibrary.simpleMessage( "Supprimer le libellé d\'une personne"), "removePublicLink": @@ -1493,7 +1498,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Renommer le fichier"), "renewSubscription": MessageLookupByLibrary.simpleMessage("Renouveler l’abonnement"), - "renewsOn": m54, + "renewsOn": m55, "reportABug": MessageLookupByLibrary.simpleMessage("Signaler un bug"), "reportBug": MessageLookupByLibrary.simpleMessage("Signaler un bug"), "resendEmail": @@ -1575,11 +1580,11 @@ class MessageLookup extends MessageLookupByLibrary { "Invitez des personnes, et vous verrez ici toutes les photos qu\'elles partagent"), "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( "Les personnes seront affichées ici une fois le traitement terminé"), - "searchResultCount": m55, - "searchSectionsLengthMismatch": m56, + "searchResultCount": m56, + "searchSectionsLengthMismatch": m57, "security": MessageLookupByLibrary.simpleMessage("Sécurité"), "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), + "Ouvrir les liens des albums publics dans l\'application"), "selectALocation": MessageLookupByLibrary.simpleMessage("Sélectionnez un emplacement"), "selectALocationFirst": MessageLookupByLibrary.simpleMessage( @@ -1613,7 +1618,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Les éléments sélectionnés seront supprimés de tous les albums et déplacés dans la corbeille."), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "send": MessageLookupByLibrary.simpleMessage("Envoyer"), "sendEmail": MessageLookupByLibrary.simpleMessage("Envoyer un e-mail"), "sendInvite": @@ -1647,16 +1652,16 @@ class MessageLookup extends MessageLookupByLibrary { "shareAnAlbumNow": MessageLookupByLibrary.simpleMessage( "Partagez un album maintenant"), "shareLink": MessageLookupByLibrary.simpleMessage("Partager le lien"), - "shareMyVerificationID": m58, + "shareMyVerificationID": m59, "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage( "Partager uniquement avec les personnes que vous voulez"), "shareTextConfirmOthersVerificationID": m5, "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage( "Téléchargez Ente pour que nous puissions facilement partager des photos et des vidéos de qualité originale\n\nhttps://ente.io"), - "shareTextReferralCode": m59, + "shareTextReferralCode": m60, "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage( "Partager avec des utilisateurs non-Ente"), - "shareWithPeopleSectionTitle": m60, + "shareWithPeopleSectionTitle": m61, "shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage( "Partagez votre premier album"), "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( @@ -1667,7 +1672,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Nouvelles photos partagées"), "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( "Recevoir des notifications quand quelqu\'un ajoute une photo à un album partagé dont vous faites partie"), - "sharedWith": m61, + "sharedWith": m62, "sharedWithMe": MessageLookupByLibrary.simpleMessage("Partagés avec moi"), "sharedWithYou": @@ -1685,11 +1690,11 @@ class MessageLookup extends MessageLookupByLibrary { "Déconnecter les autres appareils"), "signUpTerms": MessageLookupByLibrary.simpleMessage( "J\'accepte les conditions d\'utilisation et la politique de confidentialité"), - "singleFileDeleteFromDevice": m62, + "singleFileDeleteFromDevice": m63, "singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage( "Elle sera supprimée de tous les albums."), - "singleFileInBothLocalAndRemote": m63, - "singleFileInRemoteOnly": m64, + "singleFileInBothLocalAndRemote": m64, + "singleFileInRemoteOnly": m65, "skip": MessageLookupByLibrary.simpleMessage("Ignorer"), "social": MessageLookupByLibrary.simpleMessage("Réseaux Sociaux"), "someItemsAreInBothEnteAndYourDevice": @@ -1738,10 +1743,10 @@ class MessageLookup extends MessageLookupByLibrary { "storageInGB": m1, "storageLimitExceeded": MessageLookupByLibrary.simpleMessage("Limite de stockage atteinte"), - "storageUsageInfo": m65, + "storageUsageInfo": m66, "strongStrength": MessageLookupByLibrary.simpleMessage("Forte"), - "subAlreadyLinkedErrMessage": m66, - "subWillBeCancelledOn": m67, + "subAlreadyLinkedErrMessage": m67, + "subWillBeCancelledOn": m68, "subscribe": MessageLookupByLibrary.simpleMessage("S\'abonner"), "subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage( "Vous avez besoin d\'un abonnement payant actif pour activer le partage."), @@ -1758,7 +1763,7 @@ class MessageLookup extends MessageLookupByLibrary { "suggestFeatures": MessageLookupByLibrary.simpleMessage( "Suggérer des fonctionnalités"), "support": MessageLookupByLibrary.simpleMessage("Support"), - "syncProgress": m68, + "syncProgress": m69, "syncStopped": MessageLookupByLibrary.simpleMessage("Synchronisation arrêtée ?"), "syncing": MessageLookupByLibrary.simpleMessage( @@ -1771,7 +1776,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Appuyer pour déverrouiller"), "tapToUpload": MessageLookupByLibrary.simpleMessage("Appuyer pour envoyer"), - "tapToUploadIsIgnoredDue": m69, + "tapToUploadIsIgnoredDue": m70, "tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage( "Il semble qu\'une erreur s\'est produite. Veuillez réessayer après un certain temps. Si l\'erreur persiste, veuillez contacter notre équipe d\'assistance."), "terminate": MessageLookupByLibrary.simpleMessage("Se déconnecter"), @@ -1787,7 +1792,7 @@ class MessageLookup extends MessageLookupByLibrary { "Le téléchargement n\'a pas pu être terminé"), "theLinkYouAreTryingToAccessHasExpired": MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), + "Le lien que vous essayez d\'accéder a expiré."), "theRecoveryKeyYouEnteredIsIncorrect": MessageLookupByLibrary.simpleMessage( "La clé de récupération que vous avez entrée est incorrecte"), @@ -1795,7 +1800,7 @@ class MessageLookup extends MessageLookupByLibrary { "theseItemsWillBeDeletedFromYourDevice": MessageLookupByLibrary.simpleMessage( "Ces éléments seront supprimés de votre appareil."), - "theyAlsoGetXGb": m70, + "theyAlsoGetXGb": m71, "theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage( "Ils seront supprimés de tous les albums."), "thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage( @@ -1811,7 +1816,7 @@ class MessageLookup extends MessageLookupByLibrary { "Cette adresse mail est déjà utilisé"), "thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage( "Cette image n\'a pas de données exif"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Ceci est votre ID de vérification"), "thisWillLogYouOutOfTheFollowingDevice": @@ -1835,7 +1840,7 @@ class MessageLookup extends MessageLookupByLibrary { "total": MessageLookupByLibrary.simpleMessage("total"), "totalSize": MessageLookupByLibrary.simpleMessage("Taille totale"), "trash": MessageLookupByLibrary.simpleMessage("Corbeille"), - "trashDaysLeft": m72, + "trashDaysLeft": m73, "trim": MessageLookupByLibrary.simpleMessage("Recadrer"), "tryAgain": MessageLookupByLibrary.simpleMessage("Réessayer"), "turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage( @@ -1856,7 +1861,7 @@ class MessageLookup extends MessageLookupByLibrary { "L\'authentification à deux facteurs a été réinitialisée avec succès "), "twofactorSetup": MessageLookupByLibrary.simpleMessage( "Configuration de l\'authentification à deux facteurs"), - "typeOfGallerGallerytypeIsNotSupportedForRename": m73, + "typeOfGallerGallerytypeIsNotSupportedForRename": m74, "unarchive": MessageLookupByLibrary.simpleMessage("Désarchiver"), "unarchiveAlbum": MessageLookupByLibrary.simpleMessage("Désarchiver l\'album"), @@ -1884,10 +1889,10 @@ class MessageLookup extends MessageLookupByLibrary { "updatingFolderSelection": MessageLookupByLibrary.simpleMessage( "Mise à jour de la sélection du dossier..."), "upgrade": MessageLookupByLibrary.simpleMessage("Améliorer"), - "uploadIsIgnoredDueToIgnorereason": m74, + "uploadIsIgnoredDueToIgnorereason": m75, "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage( "Envoi des fichiers vers l\'album..."), - "uploadingMultipleMemories": m75, + "uploadingMultipleMemories": m76, "uploadingSingleMemory": MessageLookupByLibrary.simpleMessage("Sauvegarde 1 souvenir..."), "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( @@ -1902,8 +1907,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Utiliser la clé de secours"), "useSelectedPhoto": MessageLookupByLibrary.simpleMessage( "Utiliser la photo sélectionnée"), - "usedSpace": MessageLookupByLibrary.simpleMessage("Mémoire utilisée"), - "validTill": m76, + "usedSpace": MessageLookupByLibrary.simpleMessage("Stockage utilisé"), + "validTill": m77, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "La vérification a échouée, veuillez réessayer"), @@ -1912,7 +1917,7 @@ class MessageLookup extends MessageLookupByLibrary { "verify": MessageLookupByLibrary.simpleMessage("Vérifier"), "verifyEmail": MessageLookupByLibrary.simpleMessage("Vérifier l\'email"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("Vérifier"), "verifyPasskey": MessageLookupByLibrary.simpleMessage("Vérifier le code d\'accès"), @@ -1941,7 +1946,7 @@ class MessageLookup extends MessageLookupByLibrary { "viewRecoveryKey": MessageLookupByLibrary.simpleMessage("Voir la clé de récupération"), "viewer": MessageLookupByLibrary.simpleMessage("Observateur"), - "viewersSuccessfullyAdded": m78, + "viewersSuccessfullyAdded": m79, "visitWebToManage": MessageLookupByLibrary.simpleMessage( "Veuillez visiter web.ente.io pour gérer votre abonnement"), "waitingForVerification": MessageLookupByLibrary.simpleMessage( @@ -1959,7 +1964,7 @@ class MessageLookup extends MessageLookupByLibrary { "whatsNew": MessageLookupByLibrary.simpleMessage("Nouveautés"), "yearShort": MessageLookupByLibrary.simpleMessage("an"), "yearly": MessageLookupByLibrary.simpleMessage("Annuel"), - "yearsAgo": m79, + "yearsAgo": m80, "yes": MessageLookupByLibrary.simpleMessage("Oui"), "yesCancel": MessageLookupByLibrary.simpleMessage("Oui, annuler"), "yesConvertToViewer": MessageLookupByLibrary.simpleMessage( @@ -1992,7 +1997,7 @@ class MessageLookup extends MessageLookupByLibrary { "Vous ne pouvez pas partager avec vous-même"), "youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage( "Vous n\'avez aucun élément archivé."), - "youHaveSuccessfullyFreedUp": m80, + "youHaveSuccessfullyFreedUp": m81, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage("Votre compte a été supprimé"), "yourMap": MessageLookupByLibrary.simpleMessage("Votre carte"), diff --git a/mobile/lib/generated/intl/messages_gu.dart b/mobile/lib/generated/intl/messages_gu.dart index cb9f73e2e7..6c1d7e4d90 100644 --- a/mobile/lib/generated/intl/messages_gu.dart +++ b/mobile/lib/generated/intl/messages_gu.dart @@ -21,22 +21,5 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'gu'; final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired.") - }; + static Map _notInlinedMessages(_) => {}; } diff --git a/mobile/lib/generated/intl/messages_he.dart b/mobile/lib/generated/intl/messages_he.dart index 9962dce115..6ca198645d 100644 --- a/mobile/lib/generated/intl/messages_he.dart +++ b/mobile/lib/generated/intl/messages_he.dart @@ -85,48 +85,48 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(providerName) => "אנא דבר עם התמיכה של ${providerName} אם אתה חוייבת"; - static String m51(storeName) => "דרג אותנו ב-${storeName}"; + static String m52(storeName) => "דרג אותנו ב-${storeName}"; - static String m52(storageInGB) => "3. שניכים מקבלים ${storageInGB} GB* בחינם"; + static String m53(storageInGB) => "3. שניכים מקבלים ${storageInGB} GB* בחינם"; - static String m53(userEmail) => + static String m54(userEmail) => "${userEmail} יוסר מהאלבום המשותף הזה\n\nגם תמונות שנוספו על ידיהם יוסרו מהאלבום"; static String m4(count) => "${count} נבחרו"; - static String m57(count, yourCount) => "${count} נבחרו (${yourCount} שלך)"; + static String m58(count, yourCount) => "${count} נבחרו (${yourCount} שלך)"; - static String m58(verificationID) => + static String m59(verificationID) => "הנה מזהה האימות שלי: ${verificationID} עבור ente.io."; static String m5(verificationID) => "היי, תוכל לוודא שזה מזהה האימות שלך של ente.io: ${verificationID}"; - static String m60(numberOfPeople) => + static String m61(numberOfPeople) => "${Intl.plural(numberOfPeople, zero: 'שתף עם אנשים ספציפיים', one: 'שותף עם איש 1', two: 'שותף עם 2 אנשים', other: 'שותף עם ${numberOfPeople} אנשים')}"; - static String m61(emailIDs) => "הושתף ע\"י ${emailIDs}"; + static String m62(emailIDs) => "הושתף ע\"י ${emailIDs}"; - static String m62(fileType) => "${fileType} יימחק מהמכשיר שלך."; + static String m63(fileType) => "${fileType} יימחק מהמכשיר שלך."; static String m1(storageAmountInGB) => "${storageAmountInGB} GB"; - static String m67(endDate) => "המנוי שלך יבוטל ב-${endDate}"; + static String m68(endDate) => "המנוי שלך יבוטל ב-${endDate}"; - static String m68(completed, total) => "${completed}/${total} זכרונות נשמרו"; + static String m69(completed, total) => "${completed}/${total} זכרונות נשמרו"; - static String m70(storageAmountInGB) => "הם גם יקבלו ${storageAmountInGB} GB"; + static String m71(storageAmountInGB) => "הם גם יקבלו ${storageAmountInGB} GB"; - static String m71(email) => "זה מזהה האימות של ${email}"; + static String m72(email) => "זה מזהה האימות של ${email}"; - static String m77(email) => "אמת ${email}"; + static String m78(email) => "אמת ${email}"; static String m2(email) => "שלחנו דוא\"ל ל${email}"; - static String m79(count) => + static String m80(count) => "${Intl.plural(count, one: 'לפני ${count} שנה', two: 'לפני ${count} שנים', many: 'לפני ${count} שנים', other: 'לפני ${count} שנים')}"; - static String m80(storageSaved) => "הצלחת לפנות ${storageSaved}!"; + static String m81(storageSaved) => "הצלחת לפנות ${storageSaved}!"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -165,13 +165,10 @@ class MessageLookup extends MessageLookupByLibrary { "allClear": MessageLookupByLibrary.simpleMessage("✨ הכל נוקה"), "allMemoriesPreserved": MessageLookupByLibrary.simpleMessage("כל הזכרונות נשמרו"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "בנוסף אפשר לאנשים עם הלינק להוסיף תמונות לאלבום המשותף."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage("אפשר הוספת תמונות"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "allowDownloads": MessageLookupByLibrary.simpleMessage("אפשר הורדות"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage("תן לאנשים להוסיף תמונות"), @@ -232,7 +229,6 @@ class MessageLookup extends MessageLookupByLibrary { "backedUpFolders": MessageLookupByLibrary.simpleMessage("תיקיות שגובו"), "backup": MessageLookupByLibrary.simpleMessage("גיבוי"), "backupFailed": MessageLookupByLibrary.simpleMessage("הגיבוי נכשל"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "backupOverMobileData": MessageLookupByLibrary.simpleMessage("גבה על רשת סלולרית"), "backupSettings": MessageLookupByLibrary.simpleMessage("הגדרות גיבוי"), @@ -453,8 +449,6 @@ class MessageLookup extends MessageLookupByLibrary { "exportLogs": MessageLookupByLibrary.simpleMessage("ייצוא לוגים"), "exportYourData": MessageLookupByLibrary.simpleMessage("ייצוא הנתונים שלך"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "failedToApplyCode": MessageLookupByLibrary.simpleMessage("נכשל בהחלת הקוד"), "failedToCancel": MessageLookupByLibrary.simpleMessage("הביטול נכשל"), @@ -563,8 +557,6 @@ class MessageLookup extends MessageLookupByLibrary { "לחץ לחיצה ארוכה על פריט על מנת לראות אותו במסך מלא"), "lostDevice": MessageLookupByLibrary.simpleMessage("איבדת את המכשיר?"), "manage": MessageLookupByLibrary.simpleMessage("נהל"), - "manageDeviceStorage": - MessageLookupByLibrary.simpleMessage("נהל את מקום אחסון המכשיר"), "manageFamily": MessageLookupByLibrary.simpleMessage("נהל משפחה"), "manageLink": MessageLookupByLibrary.simpleMessage("ניהול קישור"), "manageParticipants": MessageLookupByLibrary.simpleMessage("נהל"), @@ -609,11 +601,6 @@ class MessageLookup extends MessageLookupByLibrary { "oops": MessageLookupByLibrary.simpleMessage("אופס"), "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage("אופס, משהו השתבש"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "openSettings": MessageLookupByLibrary.simpleMessage("פתח הגדרות"), "optionalAsShortAsYouLike": MessageLookupByLibrary.simpleMessage("אופציונלי, קצר ככל שתרצה..."), @@ -670,7 +657,7 @@ class MessageLookup extends MessageLookupByLibrary { "raiseTicket": MessageLookupByLibrary.simpleMessage("צור ticket"), "rateTheApp": MessageLookupByLibrary.simpleMessage("דרג את האפליקציה"), "rateUs": MessageLookupByLibrary.simpleMessage("דרג אותנו"), - "rateUsOnStore": m51, + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("שחזר"), "recoverAccount": MessageLookupByLibrary.simpleMessage("שחזר חשבון"), "recoverButton": MessageLookupByLibrary.simpleMessage("שחזר"), @@ -696,7 +683,7 @@ class MessageLookup extends MessageLookupByLibrary { "1. תמסור את הקוד הזה לחברייך"), "referralStep2": MessageLookupByLibrary.simpleMessage( "2. הם נרשמים עבור תוכנית בתשלום"), - "referralStep3": m52, + "referralStep3": m53, "referrals": MessageLookupByLibrary.simpleMessage("הפניות"), "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage("הפניות כרגע מושהות"), @@ -712,7 +699,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("הסר מהאלבום?"), "removeLink": MessageLookupByLibrary.simpleMessage("הסרת קישור"), "removeParticipant": MessageLookupByLibrary.simpleMessage("הסר משתתף"), - "removeParticipantBody": m53, + "removeParticipantBody": m54, "removePublicLink": MessageLookupByLibrary.simpleMessage("הסר לינק ציבורי"), "removeShareItemsWarning": MessageLookupByLibrary.simpleMessage( @@ -752,8 +739,6 @@ class MessageLookup extends MessageLookupByLibrary { "searchByAlbumNameHint": MessageLookupByLibrary.simpleMessage("שם האלבום"), "security": MessageLookupByLibrary.simpleMessage("אבטחה"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "selectAlbum": MessageLookupByLibrary.simpleMessage("בחר אלבום"), "selectAll": MessageLookupByLibrary.simpleMessage("בחר הכל"), "selectFoldersForBackup": @@ -766,7 +751,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "התיקיות שנבחרו יוצפנו ויגובו"), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "send": MessageLookupByLibrary.simpleMessage("שלח"), "sendEmail": MessageLookupByLibrary.simpleMessage("שלח דוא\"ל"), "sendInvite": MessageLookupByLibrary.simpleMessage("שלח הזמנה"), @@ -785,7 +770,7 @@ class MessageLookup extends MessageLookupByLibrary { "shareAnAlbumNow": MessageLookupByLibrary.simpleMessage("שתף אלבום עכשיו"), "shareLink": MessageLookupByLibrary.simpleMessage("שתף קישור"), - "shareMyVerificationID": m58, + "shareMyVerificationID": m59, "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage("שתף רק אם אנשים שאתה בוחר"), "shareTextConfirmOthersVerificationID": m5, @@ -793,7 +778,7 @@ class MessageLookup extends MessageLookupByLibrary { "הורד את ente על מנת שנוכל לשתף תמונות וסרטונים באיכות המקור באופן קל\n\nhttps://ente.io"), "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage( "שתף עם משתמשים שהם לא של ente"), - "shareWithPeopleSectionTitle": m60, + "shareWithPeopleSectionTitle": m61, "shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage("שתף את האלבום הראשון שלך"), "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( @@ -804,13 +789,13 @@ class MessageLookup extends MessageLookupByLibrary { "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( "קבל התראות כשמישהו מוסיף תמונה לאלבום משותף שאתה חלק ממנו"), - "sharedWith": m61, + "sharedWith": m62, "sharedWithMe": MessageLookupByLibrary.simpleMessage("שותף איתי"), "sharing": MessageLookupByLibrary.simpleMessage("משתף..."), "showMemories": MessageLookupByLibrary.simpleMessage("הצג זכרונות"), "signUpTerms": MessageLookupByLibrary.simpleMessage( "אני מסכים לתנאי שירות ולמדיניות הפרטיות"), - "singleFileDeleteFromDevice": m62, + "singleFileDeleteFromDevice": m63, "singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage("זה יימחק מכל האלבומים."), "skip": MessageLookupByLibrary.simpleMessage("דלג"), @@ -843,14 +828,14 @@ class MessageLookup extends MessageLookupByLibrary { "storageLimitExceeded": MessageLookupByLibrary.simpleMessage("גבול מקום האחסון נחרג"), "strongStrength": MessageLookupByLibrary.simpleMessage("חזקה"), - "subWillBeCancelledOn": m67, + "subWillBeCancelledOn": m68, "subscribe": MessageLookupByLibrary.simpleMessage("הרשם"), "subscription": MessageLookupByLibrary.simpleMessage("מנוי"), "success": MessageLookupByLibrary.simpleMessage("הצלחה"), "suggestFeatures": MessageLookupByLibrary.simpleMessage("הציעו מאפיינים"), "support": MessageLookupByLibrary.simpleMessage("תמיכה"), - "syncProgress": m68, + "syncProgress": m69, "syncing": MessageLookupByLibrary.simpleMessage("מסנכרן..."), "systemTheme": MessageLookupByLibrary.simpleMessage("מערכת"), "tapToCopy": MessageLookupByLibrary.simpleMessage("הקש כדי להעתיק"), @@ -865,16 +850,13 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("תודה שנרשמת!"), "theDownloadCouldNotBeCompleted": MessageLookupByLibrary.simpleMessage("לא ניתן להשלים את ההורדה"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "theme": MessageLookupByLibrary.simpleMessage("ערכת נושא"), - "theyAlsoGetXGb": m70, + "theyAlsoGetXGb": m71, "thisCanBeUsedToRecoverYourAccountIfYou": MessageLookupByLibrary.simpleMessage( "זה יכול לשמש לשחזור החשבון שלך במקרה ותאבד את הגורם השני"), "thisDevice": MessageLookupByLibrary.simpleMessage("מכשיר זה"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage("זה מזהה האימות שלך"), "thisWillLogYouOutOfTheFollowingDevice": @@ -918,7 +900,7 @@ class MessageLookup extends MessageLookupByLibrary { "verificationId": MessageLookupByLibrary.simpleMessage("מזהה אימות"), "verify": MessageLookupByLibrary.simpleMessage("אמת"), "verifyEmail": MessageLookupByLibrary.simpleMessage("אימות דוא\"ל"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("אמת"), "verifyPassword": MessageLookupByLibrary.simpleMessage("אמת סיסמא"), "verifyingRecoveryKey": @@ -939,7 +921,7 @@ class MessageLookup extends MessageLookupByLibrary { "weakStrength": MessageLookupByLibrary.simpleMessage("חלשה"), "welcomeBack": MessageLookupByLibrary.simpleMessage("ברוך שובך!"), "yearly": MessageLookupByLibrary.simpleMessage("שנתי"), - "yearsAgo": m79, + "yearsAgo": m80, "yes": MessageLookupByLibrary.simpleMessage("כן"), "yesCancel": MessageLookupByLibrary.simpleMessage("כן, בטל"), "yesConvertToViewer": @@ -962,7 +944,7 @@ class MessageLookup extends MessageLookupByLibrary { "אתה לא יכול לשנמך לתוכנית הזו"), "youCannotShareWithYourself": MessageLookupByLibrary.simpleMessage("אתה לא יכול לשתף עם עצמך"), - "youHaveSuccessfullyFreedUp": m80, + "youHaveSuccessfullyFreedUp": m81, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage("החשבון שלך נמחק"), "yourPlanWasSuccessfullyDowngraded": diff --git a/mobile/lib/generated/intl/messages_hi.dart b/mobile/lib/generated/intl/messages_hi.dart index 1d0f451889..ff4756d8d4 100644 --- a/mobile/lib/generated/intl/messages_hi.dart +++ b/mobile/lib/generated/intl/messages_hi.dart @@ -25,12 +25,8 @@ class MessageLookup extends MessageLookupByLibrary { "accountWelcomeBack": MessageLookupByLibrary.simpleMessage("आपका पुनः स्वागत है"), "activeSessions": MessageLookupByLibrary.simpleMessage("एक्टिव सेशन"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "askDeleteReason": MessageLookupByLibrary.simpleMessage( "आपका अकाउंट हटाने का मुख्य कारण क्या है?"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "cancel": MessageLookupByLibrary.simpleMessage("रद्द करें"), "confirmAccountDeletion": MessageLookupByLibrary.simpleMessage( "अकाउंट डिलीट करने की पुष्टि करें"), @@ -68,8 +64,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("अपना ईमेल ऐड्रेस डालें"), "enterYourRecoveryKey": MessageLookupByLibrary.simpleMessage("अपनी रिकवरी कुंजी दर्ज करें"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "feedback": MessageLookupByLibrary.simpleMessage("प्रतिपुष्टि"), "forgotPassword": MessageLookupByLibrary.simpleMessage("पासवर्ड भूल गए"), @@ -87,17 +81,10 @@ class MessageLookup extends MessageLookupByLibrary { "हमारे एंड-टू-एंड एन्क्रिप्शन प्रोटोकॉल की प्रकृति के कारण, आपके डेटा को आपके पासवर्ड या रिकवरी कुंजी के बिना डिक्रिप्ट नहीं किया जा सकता है"), "ok": MessageLookupByLibrary.simpleMessage("ठीक है"), "oops": MessageLookupByLibrary.simpleMessage("ओह!"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "password": MessageLookupByLibrary.simpleMessage("पासवर्ड"), "recoverButton": MessageLookupByLibrary.simpleMessage("पुनः प्राप्त"), "recoverySuccessful": MessageLookupByLibrary.simpleMessage("रिकवरी सफल हुई!"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "selectReason": MessageLookupByLibrary.simpleMessage("कारण चुनें"), "sendEmail": MessageLookupByLibrary.simpleMessage("ईमेल भेजें"), "somethingWentWrongPleaseTryAgain": @@ -107,9 +94,6 @@ class MessageLookup extends MessageLookupByLibrary { "terminate": MessageLookupByLibrary.simpleMessage("रद्द करें"), "terminateSession": MessageLookupByLibrary.simpleMessage("सेशन रद्द करें?"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "thisDevice": MessageLookupByLibrary.simpleMessage("यह डिवाइस"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( diff --git a/mobile/lib/generated/intl/messages_hu.dart b/mobile/lib/generated/intl/messages_hu.dart index 08f1e5e3b3..fbe4a79ba1 100644 --- a/mobile/lib/generated/intl/messages_hu.dart +++ b/mobile/lib/generated/intl/messages_hu.dart @@ -24,12 +24,8 @@ class MessageLookup extends MessageLookupByLibrary { static Map _notInlinedMessages(_) => { "accountWelcomeBack": MessageLookupByLibrary.simpleMessage("Köszöntjük ismét!"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "askDeleteReason": MessageLookupByLibrary.simpleMessage("Miért törli a fiókját?"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "cancel": MessageLookupByLibrary.simpleMessage("Mégse"), "deleteAccount": MessageLookupByLibrary.simpleMessage("Fiók törlése"), "deleteAccountFeedbackPrompt": MessageLookupByLibrary.simpleMessage( @@ -39,21 +35,9 @@ class MessageLookup extends MessageLookupByLibrary { "Kérjük, adjon meg egy érvényes e-mail címet."), "enterYourEmailAddress": MessageLookupByLibrary.simpleMessage("Adja meg az e-mail címét"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "feedback": MessageLookupByLibrary.simpleMessage("Visszajelzés"), "invalidEmailAddress": MessageLookupByLibrary.simpleMessage("Érvénytelen e-mail cím"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "verify": MessageLookupByLibrary.simpleMessage("Hitelesítés") }; } diff --git a/mobile/lib/generated/intl/messages_id.dart b/mobile/lib/generated/intl/messages_id.dart index 1f469c1df0..565044ef23 100644 --- a/mobile/lib/generated/intl/messages_id.dart +++ b/mobile/lib/generated/intl/messages_id.dart @@ -126,84 +126,81 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(providerName) => "Harap hubungi dukungan ${providerName} jika kamu dikenai biaya"; - static String m47(endDate) => + static String m48(endDate) => "Percobaan gratis berlaku hingga ${endDate}.\nKamu dapat memilih paket berbayar setelahnya."; - static String m48(toEmail) => "Silakan kirimi kami email di ${toEmail}"; + static String m49(toEmail) => "Silakan kirimi kami email di ${toEmail}"; - static String m49(toEmail) => "Silakan kirim log-nya ke \n${toEmail}"; + static String m50(toEmail) => "Silakan kirim log-nya ke \n${toEmail}"; - static String m51(storeName) => "Beri nilai di ${storeName}"; + static String m52(storeName) => "Beri nilai di ${storeName}"; - static String m52(storageInGB) => + static String m53(storageInGB) => "3. Kalian berdua mendapat ${storageInGB} GB* gratis"; - static String m53(userEmail) => + static String m54(userEmail) => "${userEmail} akan dikeluarkan dari album berbagi ini\n\nSemua foto yang ia tambahkan juga akan dihapus dari album ini"; - static String m54(endDate) => "Langganan akan diperpanjang pada ${endDate}"; + static String m55(endDate) => "Langganan akan diperpanjang pada ${endDate}"; - static String m55(count) => + static String m56(count) => "${Intl.plural(count, other: '${count} hasil ditemukan')}"; static String m4(count) => "${count} terpilih"; - static String m57(count, yourCount) => + static String m58(count, yourCount) => "${count} dipilih (${yourCount} milikmu)"; - static String m58(verificationID) => + static String m59(verificationID) => "Ini ID Verifikasi saya di ente.io: ${verificationID}."; static String m5(verificationID) => "Halo, bisakah kamu pastikan bahwa ini adalah ID Verifikasi ente.io milikmu: ${verificationID}"; - static String m59(referralCode, referralStorageInGB) => + static String m60(referralCode, referralStorageInGB) => "Kode rujukan Ente: ${referralCode} \n\nTerapkan pada Pengaturan → Umum → Rujukan untuk mendapatkan ${referralStorageInGB} GB gratis setelah kamu mendaftar paket berbayar\n\nhttps://ente.io"; - static String m60(numberOfPeople) => + static String m61(numberOfPeople) => "${Intl.plural(numberOfPeople, zero: 'Bagikan dengan orang tertentu', one: 'Berbagi dengan 1 orang', other: 'Berbagi dengan ${numberOfPeople} orang')}"; - static String m61(emailIDs) => "Dibagikan dengan ${emailIDs}"; - - static String m62(fileType) => - "${fileType} ini akan dihapus dari perangkat ini."; + static String m62(emailIDs) => "Dibagikan dengan ${emailIDs}"; static String m63(fileType) => + "${fileType} ini akan dihapus dari perangkat ini."; + + static String m64(fileType) => "${fileType} ini tersimpan di Ente dan juga di perangkat ini."; - static String m64(fileType) => "${fileType} ini akan dihapus dari Ente."; + static String m65(fileType) => "${fileType} ini akan dihapus dari Ente."; static String m1(storageAmountInGB) => "${storageAmountInGB} GB"; - static String m65( + static String m66( usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => "${usedAmount} ${usedStorageUnit} dari ${totalAmount} ${totalStorageUnit} terpakai"; - static String m66(id) => + static String m67(id) => "${id} kamu telah terhubung dengan akun Ente lain.\nJika kamu ingin menggunakan ${id} kamu untuk akun ini, silahkan hubungi tim bantuan kami"; - static String m67(endDate) => + static String m68(endDate) => "Langganan kamu akan dibatalkan pada ${endDate}"; - static String m70(storageAmountInGB) => + static String m71(storageAmountInGB) => "Ia juga mendapat ${storageAmountInGB} GB"; - static String m71(email) => "Ini adalah ID Verifikasi milik ${email}"; + static String m72(email) => "Ini adalah ID Verifikasi milik ${email}"; - static String m72(count) => - "${Intl.plural(count, zero: '', one: '1 hari', other: '${count} hari')}"; + static String m77(endDate) => "Berlaku hingga ${endDate}"; - static String m76(endDate) => "Berlaku hingga ${endDate}"; - - static String m77(email) => "Verifikasi ${email}"; + static String m78(email) => "Verifikasi ${email}"; static String m2(email) => "Kami telah mengirimkan email ke ${email}"; - static String m79(count) => + static String m80(count) => "${Intl.plural(count, other: '${count} tahun lalu')}"; - static String m80(storageSaved) => + static String m81(storageSaved) => "Kamu telah berhasil membersihkan ${storageSaved}!"; final messages = _notInlinedMessages(_notInlinedMessages); @@ -259,13 +256,10 @@ class MessageLookup extends MessageLookupByLibrary { "allClear": MessageLookupByLibrary.simpleMessage("✨ Sudah bersih"), "allMemoriesPreserved": MessageLookupByLibrary.simpleMessage("Semua kenangan terpelihara"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "Izinkan orang yang memiliki link untuk menambahkan foto ke album berbagi ini."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage("Izinkan menambah foto"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "allowDownloads": MessageLookupByLibrary.simpleMessage("Izinkan pengunduhan"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( @@ -357,7 +351,6 @@ class MessageLookup extends MessageLookupByLibrary { "backup": MessageLookupByLibrary.simpleMessage("Pencadangan"), "backupFailed": MessageLookupByLibrary.simpleMessage("Pencadangan gagal"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "backupOverMobileData": MessageLookupByLibrary.simpleMessage( "Cadangkan dengan data seluler"), "backupSettings": @@ -671,8 +664,6 @@ class MessageLookup extends MessageLookupByLibrary { "exportLogs": MessageLookupByLibrary.simpleMessage("Ekspor log"), "exportYourData": MessageLookupByLibrary.simpleMessage("Ekspor data kamu"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "faceRecognition": MessageLookupByLibrary.simpleMessage("Pengenalan wajah"), "faces": MessageLookupByLibrary.simpleMessage("Wajah"), @@ -877,8 +868,6 @@ class MessageLookup extends MessageLookupByLibrary { "magicSearch": MessageLookupByLibrary.simpleMessage("Penelusuran ajaib"), "manage": MessageLookupByLibrary.simpleMessage("Atur"), - "manageDeviceStorage": - MessageLookupByLibrary.simpleMessage("Atur penyimpanan perangkat"), "manageFamily": MessageLookupByLibrary.simpleMessage("Atur Keluarga"), "manageLink": MessageLookupByLibrary.simpleMessage("Atur link"), "manageParticipants": MessageLookupByLibrary.simpleMessage("Atur"), @@ -969,11 +958,6 @@ class MessageLookup extends MessageLookupByLibrary { "Aduh, tidak dapat menyimpan perubahan"), "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage("Aduh, terjadi kesalahan"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "openSettings": MessageLookupByLibrary.simpleMessage("Buka Pengaturan"), "openTheItem": MessageLookupByLibrary.simpleMessage("• Buka item-nya"), "openstreetmapContributors": @@ -1027,7 +1011,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Foto yang telah kamu tambahkan akan dihapus dari album ini"), "playOnTv": MessageLookupByLibrary.simpleMessage("Putar album di TV"), - "playStoreFreeTrialValidTill": m47, + "playStoreFreeTrialValidTill": m48, "playstoreSubscription": MessageLookupByLibrary.simpleMessage("Langganan PlayStore"), "pleaseCheckYourInternetConnectionAndTryAgain": @@ -1039,12 +1023,12 @@ class MessageLookup extends MessageLookupByLibrary { "pleaseContactSupportIfTheProblemPersists": MessageLookupByLibrary.simpleMessage( "Silakan hubungi tim bantuan jika masalah terus terjadi"), - "pleaseEmailUsAt": m48, + "pleaseEmailUsAt": m49, "pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage("Harap berikan izin"), "pleaseLoginAgain": MessageLookupByLibrary.simpleMessage("Silakan masuk akun lagi"), - "pleaseSendTheLogsTo": m49, + "pleaseSendTheLogsTo": m50, "pleaseTryAgain": MessageLookupByLibrary.simpleMessage("Silakan coba lagi"), "pleaseVerifyTheCodeYouHaveEntered": @@ -1078,7 +1062,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Buat tiket dukungan"), "rateTheApp": MessageLookupByLibrary.simpleMessage("Nilai app ini"), "rateUs": MessageLookupByLibrary.simpleMessage("Beri kami nilai"), - "rateUsOnStore": m51, + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("Pulihkan"), "recoverAccount": MessageLookupByLibrary.simpleMessage("Pulihkan akun"), "recoverButton": MessageLookupByLibrary.simpleMessage("Pulihkan"), @@ -1106,7 +1090,7 @@ class MessageLookup extends MessageLookupByLibrary { "1. Berikan kode ini ke teman kamu"), "referralStep2": MessageLookupByLibrary.simpleMessage( "2. Ia perlu daftar ke paket berbayar"), - "referralStep3": m52, + "referralStep3": m53, "referrals": MessageLookupByLibrary.simpleMessage("Referensi"), "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage("Rujukan sedang dijeda"), @@ -1128,7 +1112,7 @@ class MessageLookup extends MessageLookupByLibrary { "removeLink": MessageLookupByLibrary.simpleMessage("Hapus link"), "removeParticipant": MessageLookupByLibrary.simpleMessage("Hapus peserta"), - "removeParticipantBody": m53, + "removeParticipantBody": m54, "removePersonLabel": MessageLookupByLibrary.simpleMessage("Hapus label orang"), "removePublicLink": @@ -1144,7 +1128,7 @@ class MessageLookup extends MessageLookupByLibrary { "renameFile": MessageLookupByLibrary.simpleMessage("Ubah nama file"), "renewSubscription": MessageLookupByLibrary.simpleMessage("Perpanjang langganan"), - "renewsOn": m54, + "renewsOn": m55, "reportABug": MessageLookupByLibrary.simpleMessage("Laporkan bug"), "reportBug": MessageLookupByLibrary.simpleMessage("Laporkan bug"), "resendEmail": @@ -1195,10 +1179,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Album, nama dan jenis file"), "searchHint5": MessageLookupByLibrary.simpleMessage( "Segera tiba: Penelusuran wajah & ajaib ✨"), - "searchResultCount": m55, + "searchResultCount": m56, "security": MessageLookupByLibrary.simpleMessage("Keamanan"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "selectALocation": MessageLookupByLibrary.simpleMessage("Pilih lokasi"), "selectALocationFirst": MessageLookupByLibrary.simpleMessage( "Pilih lokasi terlebih dahulu"), @@ -1223,7 +1205,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Item terpilih akan dihapus dari semua album dan dipindahkan ke sampah."), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "send": MessageLookupByLibrary.simpleMessage("Kirim"), "sendEmail": MessageLookupByLibrary.simpleMessage("Kirim email"), "sendInvite": MessageLookupByLibrary.simpleMessage("Kirim undangan"), @@ -1244,16 +1226,16 @@ class MessageLookup extends MessageLookupByLibrary { "shareAnAlbumNow": MessageLookupByLibrary.simpleMessage("Bagikan album sekarang"), "shareLink": MessageLookupByLibrary.simpleMessage("Bagikan link"), - "shareMyVerificationID": m58, + "shareMyVerificationID": m59, "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage( "Bagikan hanya dengan orang yang kamu inginkan"), "shareTextConfirmOthersVerificationID": m5, "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage( "Unduh Ente agar kita bisa berbagi foto dan video kualitas asli dengan mudah\n\nhttps://ente.io"), - "shareTextReferralCode": m59, + "shareTextReferralCode": m60, "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage( "Bagikan ke pengguna non-Ente"), - "shareWithPeopleSectionTitle": m60, + "shareWithPeopleSectionTitle": m61, "shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage("Bagikan album pertamamu"), "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( @@ -1266,7 +1248,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Foto terbagi baru"), "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( "Terima notifikasi apabila seseorang menambahkan foto ke album bersama yang kamu ikuti"), - "sharedWith": m61, + "sharedWith": m62, "sharedWithMe": MessageLookupByLibrary.simpleMessage("Dibagikan dengan saya"), "sharedWithYou": @@ -1281,11 +1263,11 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Keluar di perangkat lain"), "signUpTerms": MessageLookupByLibrary.simpleMessage( "Saya menyetujui ketentuan layanan dan kebijakan privasi Ente"), - "singleFileDeleteFromDevice": m62, + "singleFileDeleteFromDevice": m63, "singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage( "Ia akan dihapus dari semua album."), - "singleFileInBothLocalAndRemote": m63, - "singleFileInRemoteOnly": m64, + "singleFileInBothLocalAndRemote": m64, + "singleFileInRemoteOnly": m65, "skip": MessageLookupByLibrary.simpleMessage("Lewati"), "social": MessageLookupByLibrary.simpleMessage("Sosial"), "someItemsAreInBothEnteAndYourDevice": @@ -1330,10 +1312,10 @@ class MessageLookup extends MessageLookupByLibrary { "storageInGB": m1, "storageLimitExceeded": MessageLookupByLibrary.simpleMessage( "Batas penyimpanan terlampaui"), - "storageUsageInfo": m65, + "storageUsageInfo": m66, "strongStrength": MessageLookupByLibrary.simpleMessage("Kuat"), - "subAlreadyLinkedErrMessage": m66, - "subWillBeCancelledOn": m67, + "subAlreadyLinkedErrMessage": m67, + "subWillBeCancelledOn": m68, "subscribe": MessageLookupByLibrary.simpleMessage("Berlangganan"), "subscription": MessageLookupByLibrary.simpleMessage("Langganan"), "success": MessageLookupByLibrary.simpleMessage("Berhasil"), @@ -1366,9 +1348,6 @@ class MessageLookup extends MessageLookupByLibrary { "Terima kasih telah berlangganan!"), "theDownloadCouldNotBeCompleted": MessageLookupByLibrary.simpleMessage( "Unduhan tidak dapat diselesaikan"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "theRecoveryKeyYouEnteredIsIncorrect": MessageLookupByLibrary.simpleMessage( "Kunci pemulihan yang kamu masukkan salah"), @@ -1376,7 +1355,7 @@ class MessageLookup extends MessageLookupByLibrary { "theseItemsWillBeDeletedFromYourDevice": MessageLookupByLibrary.simpleMessage( "Item ini akan dihapus dari perangkat ini."), - "theyAlsoGetXGb": m70, + "theyAlsoGetXGb": m71, "thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage( "Tindakan ini tidak dapat dibatalkan"), "thisAlbumAlreadyHDACollaborativeLink": @@ -1390,7 +1369,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Email ini telah digunakan"), "thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage( "Gambar ini tidak memiliki data exif"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Ini adalah ID Verifikasi kamu"), "thisWillLogYouOutOfTheFollowingDevice": @@ -1405,7 +1384,6 @@ class MessageLookup extends MessageLookupByLibrary { "todaysLogs": MessageLookupByLibrary.simpleMessage("Log hari ini"), "total": MessageLookupByLibrary.simpleMessage("total"), "trash": MessageLookupByLibrary.simpleMessage("Sampah"), - "trashDaysLeft": m72, "trim": MessageLookupByLibrary.simpleMessage("Pangkas"), "tryAgain": MessageLookupByLibrary.simpleMessage("Coba lagi"), "turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage( @@ -1457,14 +1435,14 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Gunakan kunci pemulihan"), "useSelectedPhoto": MessageLookupByLibrary.simpleMessage("Gunakan foto terpilih"), - "validTill": m76, + "validTill": m77, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "Verifikasi gagal, silakan coba lagi"), "verificationId": MessageLookupByLibrary.simpleMessage("ID Verifikasi"), "verify": MessageLookupByLibrary.simpleMessage("Verifikasi"), "verifyEmail": MessageLookupByLibrary.simpleMessage("Verifikasi email"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyPasskey": MessageLookupByLibrary.simpleMessage("Verifikasi passkey"), "verifyPassword": @@ -1500,7 +1478,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Selamat datang kembali!"), "whatsNew": MessageLookupByLibrary.simpleMessage("Hal yang baru"), "yearly": MessageLookupByLibrary.simpleMessage("Tahunan"), - "yearsAgo": m79, + "yearsAgo": m80, "yes": MessageLookupByLibrary.simpleMessage("Ya"), "yesCancel": MessageLookupByLibrary.simpleMessage("Ya, batalkan"), "yesConvertToViewer": @@ -1527,7 +1505,7 @@ class MessageLookup extends MessageLookupByLibrary { "Kamu tidak bisa berbagi dengan dirimu sendiri"), "youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage( "Kamu tidak memiliki item di arsip."), - "youHaveSuccessfullyFreedUp": m80, + "youHaveSuccessfullyFreedUp": m81, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage("Akunmu telah dihapus"), "yourMap": MessageLookupByLibrary.simpleMessage("Peta kamu"), diff --git a/mobile/lib/generated/intl/messages_it.dart b/mobile/lib/generated/intl/messages_it.dart index 6e884f15af..a2c711a065 100644 --- a/mobile/lib/generated/intl/messages_it.dart +++ b/mobile/lib/generated/intl/messages_it.dart @@ -141,87 +141,87 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(providerName) => "Si prega di parlare con il supporto di ${providerName} se ti è stato addebitato qualcosa"; - static String m47(endDate) => + static String m48(endDate) => "Prova gratuita valida fino al ${endDate}.\nIn seguito potrai scegliere un piano a pagamento."; - static String m48(toEmail) => "Per favore invia un\'email a ${toEmail}"; + static String m49(toEmail) => "Per favore invia un\'email a ${toEmail}"; - static String m49(toEmail) => "Invia i log a \n${toEmail}"; + static String m50(toEmail) => "Invia i log a \n${toEmail}"; - static String m50(folderName) => "Elaborando ${folderName}..."; + static String m51(folderName) => "Elaborando ${folderName}..."; - static String m51(storeName) => "Valutaci su ${storeName}"; + static String m52(storeName) => "Valutaci su ${storeName}"; - static String m52(storageInGB) => + static String m53(storageInGB) => "3. Ottenete entrambi ${storageInGB} GB* gratis"; - static String m53(userEmail) => + static String m54(userEmail) => "${userEmail} verrà rimosso da questo album condiviso\n\nQualsiasi foto aggiunta dall\'utente verrà rimossa dall\'album"; - static String m54(endDate) => "Si rinnova il ${endDate}"; + static String m55(endDate) => "Si rinnova il ${endDate}"; - static String m55(count) => + static String m56(count) => "${Intl.plural(count, one: '${count} risultato trovato', other: '${count} risultati trovati')}"; static String m4(count) => "${count} selezionati"; - static String m57(count, yourCount) => + static String m58(count, yourCount) => "${count} selezionato (${yourCount} tuoi)"; - static String m58(verificationID) => + static String m59(verificationID) => "Ecco il mio ID di verifica: ${verificationID} per ente.io."; static String m5(verificationID) => "Hey, puoi confermare che questo è il tuo ID di verifica: ${verificationID} su ente.io"; - static String m59(referralCode, referralStorageInGB) => + static String m60(referralCode, referralStorageInGB) => "Codice invito Ente: ${referralCode} \n\nInseriscilo in Impostazioni → Generali → Inviti per ottenere ${referralStorageInGB} GB gratis dopo la sottoscrizione a un piano a pagamento\n\nhttps://ente.io"; - static String m60(numberOfPeople) => + static String m61(numberOfPeople) => "${Intl.plural(numberOfPeople, zero: 'Condividi con persone specifiche', one: 'Condividi con una persona', other: 'Condividi con ${numberOfPeople} persone')}"; - static String m61(emailIDs) => "Condiviso con ${emailIDs}"; - - static String m62(fileType) => - "Questo ${fileType} verrà eliminato dal tuo dispositivo."; + static String m62(emailIDs) => "Condiviso con ${emailIDs}"; static String m63(fileType) => + "Questo ${fileType} verrà eliminato dal tuo dispositivo."; + + static String m64(fileType) => "Questo ${fileType} è sia su Ente che sul tuo dispositivo."; - static String m64(fileType) => "Questo ${fileType} verrà eliminato da Ente."; + static String m65(fileType) => "Questo ${fileType} verrà eliminato da Ente."; static String m1(storageAmountInGB) => "${storageAmountInGB} GB"; - static String m65( + static String m66( usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => "${usedAmount} ${usedStorageUnit} di ${totalAmount} ${totalStorageUnit} utilizzati"; - static String m66(id) => + static String m67(id) => "Il tuo ${id} è già collegato a un altro account Ente.\nSe desideri utilizzare il tuo ${id} con questo account, per favore contatta il nostro supporto\'\'"; - static String m67(endDate) => "L\'abbonamento verrà cancellato il ${endDate}"; + static String m68(endDate) => "L\'abbonamento verrà cancellato il ${endDate}"; - static String m68(completed, total) => + static String m69(completed, total) => "${completed}/${total} ricordi conservati"; - static String m70(storageAmountInGB) => + static String m71(storageAmountInGB) => "Anche loro riceveranno ${storageAmountInGB} GB"; - static String m71(email) => "Questo è l\'ID di verifica di ${email}"; + static String m72(email) => "Questo è l\'ID di verifica di ${email}"; - static String m75(count) => "Conservando ${count} ricordi..."; + static String m76(count) => "Conservando ${count} ricordi..."; - static String m76(endDate) => "Valido fino al ${endDate}"; + static String m77(endDate) => "Valido fino al ${endDate}"; - static String m77(email) => "Verifica ${email}"; + static String m78(email) => "Verifica ${email}"; static String m2(email) => "Abbiamo inviato una mail a ${email}"; - static String m79(count) => + static String m80(count) => "${Intl.plural(count, one: '${count} anno fa', other: '${count} anni fa')}"; - static String m80(storageSaved) => + static String m81(storageSaved) => "Hai liberato con successo ${storageSaved}!"; final messages = _notInlinedMessages(_notInlinedMessages); @@ -299,13 +299,10 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Tutti i ricordi conservati"), "allPersonGroupingWillReset": MessageLookupByLibrary.simpleMessage( "Tutti i raggruppamenti per questa persona saranno resettati e perderai tutti i suggerimenti fatti per questa persona"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "Permetti anche alle persone con il link di aggiungere foto all\'album condiviso."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage( "Consenti l\'aggiunta di foto"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "allowDownloads": MessageLookupByLibrary.simpleMessage("Consenti download"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( @@ -419,7 +416,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Cartelle salvate"), "backup": MessageLookupByLibrary.simpleMessage("Backup"), "backupFailed": MessageLookupByLibrary.simpleMessage("Backup fallito"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "backupOverMobileData": MessageLookupByLibrary.simpleMessage("Backup su dati mobili"), "backupSettings": @@ -814,8 +810,6 @@ class MessageLookup extends MessageLookupByLibrary { "extraPhotosFound": MessageLookupByLibrary.simpleMessage("Trovate foto aggiuntive"), "extraPhotosFoundFor": m30, - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "faceRecognition": MessageLookupByLibrary.simpleMessage("Riconoscimento facciale"), "faces": MessageLookupByLibrary.simpleMessage("Volti"), @@ -1089,8 +1083,6 @@ class MessageLookup extends MessageLookupByLibrary { "magicSearchHint": MessageLookupByLibrary.simpleMessage( "La ricerca magica ti permette di cercare le foto in base al loro contenuto, ad esempio \'fiore\', \'auto rossa\', \'documenti d\'identità\'"), "manage": MessageLookupByLibrary.simpleMessage("Gestisci"), - "manageDeviceStorage": MessageLookupByLibrary.simpleMessage( - "Gestisci memoria dispositivo"), "manageFamily": MessageLookupByLibrary.simpleMessage("Gestisci Piano famiglia"), "manageLink": MessageLookupByLibrary.simpleMessage("Gestisci link"), @@ -1209,11 +1201,6 @@ class MessageLookup extends MessageLookupByLibrary { "Ops, impossibile salvare le modifiche"), "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage( "Oops! Qualcosa è andato storto"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "openSettings": MessageLookupByLibrary.simpleMessage("Apri Impostazioni"), "openTheItem": @@ -1281,7 +1268,7 @@ class MessageLookup extends MessageLookupByLibrary { "pinLock": MessageLookupByLibrary.simpleMessage("Blocco con PIN"), "playOnTv": MessageLookupByLibrary.simpleMessage("Riproduci album sulla TV"), - "playStoreFreeTrialValidTill": m47, + "playStoreFreeTrialValidTill": m48, "playstoreSubscription": MessageLookupByLibrary.simpleMessage("Abbonamento su PlayStore"), "pleaseCheckYourInternetConnectionAndTryAgain": @@ -1293,14 +1280,14 @@ class MessageLookup extends MessageLookupByLibrary { "pleaseContactSupportIfTheProblemPersists": MessageLookupByLibrary.simpleMessage( "Riprova. Se il problema persiste, ti invitiamo a contattare l\'assistenza"), - "pleaseEmailUsAt": m48, + "pleaseEmailUsAt": m49, "pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage("Concedi i permessi"), "pleaseLoginAgain": MessageLookupByLibrary.simpleMessage( "Effettua nuovamente l\'accesso"), "pleaseSelectQuickLinksToRemove": MessageLookupByLibrary.simpleMessage( "Si prega di selezionare i link rapidi da rimuovere"), - "pleaseSendTheLogsTo": m49, + "pleaseSendTheLogsTo": m50, "pleaseTryAgain": MessageLookupByLibrary.simpleMessage("Riprova"), "pleaseVerifyTheCodeYouHaveEntered": MessageLookupByLibrary.simpleMessage( @@ -1324,7 +1311,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Backup privato"), "privateSharing": MessageLookupByLibrary.simpleMessage("Condivisioni private"), - "processingImport": m50, + "processingImport": m51, "publicLinkCreated": MessageLookupByLibrary.simpleMessage("Link pubblico creato"), "publicLinkEnabled": @@ -1335,7 +1322,7 @@ class MessageLookup extends MessageLookupByLibrary { "raiseTicket": MessageLookupByLibrary.simpleMessage("Invia ticket"), "rateTheApp": MessageLookupByLibrary.simpleMessage("Valuta l\'app"), "rateUs": MessageLookupByLibrary.simpleMessage("Lascia una recensione"), - "rateUsOnStore": m51, + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("Recupera"), "recoverAccount": MessageLookupByLibrary.simpleMessage("Recupera account"), @@ -1371,7 +1358,7 @@ class MessageLookup extends MessageLookupByLibrary { "1. Condividi questo codice con i tuoi amici"), "referralStep2": MessageLookupByLibrary.simpleMessage( "2. Si iscrivono per un piano a pagamento"), - "referralStep3": m52, + "referralStep3": m53, "referrals": MessageLookupByLibrary.simpleMessage("Invita un Amico"), "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage( "I referral code sono attualmente in pausa"), @@ -1397,7 +1384,7 @@ class MessageLookup extends MessageLookupByLibrary { "removeLink": MessageLookupByLibrary.simpleMessage("Elimina link"), "removeParticipant": MessageLookupByLibrary.simpleMessage("Rimuovi partecipante"), - "removeParticipantBody": m53, + "removeParticipantBody": m54, "removePersonLabel": MessageLookupByLibrary.simpleMessage("Rimuovi etichetta persona"), "removePublicLink": @@ -1415,7 +1402,7 @@ class MessageLookup extends MessageLookupByLibrary { "renameFile": MessageLookupByLibrary.simpleMessage("Rinomina file"), "renewSubscription": MessageLookupByLibrary.simpleMessage("Rinnova abbonamento"), - "renewsOn": m54, + "renewsOn": m55, "reportABug": MessageLookupByLibrary.simpleMessage("Segnala un bug"), "reportBug": MessageLookupByLibrary.simpleMessage("Segnala un bug"), "resendEmail": MessageLookupByLibrary.simpleMessage("Rinvia email"), @@ -1488,10 +1475,8 @@ class MessageLookup extends MessageLookupByLibrary { "Raggruppa foto scattate entro un certo raggio da una foto"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Invita persone e vedrai qui tutte le foto condivise da loro"), - "searchResultCount": m55, + "searchResultCount": m56, "security": MessageLookupByLibrary.simpleMessage("Sicurezza"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "selectALocation": MessageLookupByLibrary.simpleMessage("Seleziona un luogo"), "selectALocationFirst": @@ -1521,7 +1506,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Gli elementi selezionati verranno eliminati da tutti gli album e spostati nel cestino."), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "send": MessageLookupByLibrary.simpleMessage("Invia"), "sendEmail": MessageLookupByLibrary.simpleMessage("Invia email"), "sendInvite": MessageLookupByLibrary.simpleMessage("Invita"), @@ -1553,16 +1538,16 @@ class MessageLookup extends MessageLookupByLibrary { "shareAnAlbumNow": MessageLookupByLibrary.simpleMessage("Condividi un album"), "shareLink": MessageLookupByLibrary.simpleMessage("Condividi link"), - "shareMyVerificationID": m58, + "shareMyVerificationID": m59, "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage( "Condividi solo con le persone che vuoi"), "shareTextConfirmOthersVerificationID": m5, "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage( "Scarica Ente in modo da poter facilmente condividere foto e video in qualità originale\n\nhttps://ente.io"), - "shareTextReferralCode": m59, + "shareTextReferralCode": m60, "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage( "Condividi con utenti che non hanno un account Ente"), - "shareWithPeopleSectionTitle": m60, + "shareWithPeopleSectionTitle": m61, "shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage( "Condividi il tuo primo album"), "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( @@ -1573,7 +1558,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Nuove foto condivise"), "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( "Ricevi notifiche quando qualcuno aggiunge una foto a un album condiviso, di cui fai parte"), - "sharedWith": m61, + "sharedWith": m62, "sharedWithMe": MessageLookupByLibrary.simpleMessage("Condivisi con me"), "sharedWithYou": @@ -1590,11 +1575,11 @@ class MessageLookup extends MessageLookupByLibrary { "Esci dagli altri dispositivi"), "signUpTerms": MessageLookupByLibrary.simpleMessage( "Accetto i termini di servizio e la politica sulla privacy"), - "singleFileDeleteFromDevice": m62, + "singleFileDeleteFromDevice": m63, "singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage( "Verrà eliminato da tutti gli album."), - "singleFileInBothLocalAndRemote": m63, - "singleFileInRemoteOnly": m64, + "singleFileInBothLocalAndRemote": m64, + "singleFileInRemoteOnly": m65, "skip": MessageLookupByLibrary.simpleMessage("Salta"), "social": MessageLookupByLibrary.simpleMessage("Social"), "someItemsAreInBothEnteAndYourDevice": @@ -1644,10 +1629,10 @@ class MessageLookup extends MessageLookupByLibrary { "storageInGB": m1, "storageLimitExceeded": MessageLookupByLibrary.simpleMessage( "Limite d\'archiviazione superato"), - "storageUsageInfo": m65, + "storageUsageInfo": m66, "strongStrength": MessageLookupByLibrary.simpleMessage("Forte"), - "subAlreadyLinkedErrMessage": m66, - "subWillBeCancelledOn": m67, + "subAlreadyLinkedErrMessage": m67, + "subWillBeCancelledOn": m68, "subscribe": MessageLookupByLibrary.simpleMessage("Iscriviti"), "subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage( "È necessario un abbonamento a pagamento attivo per abilitare la condivisione."), @@ -1664,7 +1649,7 @@ class MessageLookup extends MessageLookupByLibrary { "suggestFeatures": MessageLookupByLibrary.simpleMessage("Suggerisci una funzionalità"), "support": MessageLookupByLibrary.simpleMessage("Assistenza"), - "syncProgress": m68, + "syncProgress": m69, "syncStopped": MessageLookupByLibrary.simpleMessage("Sincronizzazione interrotta"), "syncing": MessageLookupByLibrary.simpleMessage( @@ -1690,9 +1675,6 @@ class MessageLookup extends MessageLookupByLibrary { "Grazie per esserti iscritto!"), "theDownloadCouldNotBeCompleted": MessageLookupByLibrary.simpleMessage( "Il download non può essere completato"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "theRecoveryKeyYouEnteredIsIncorrect": MessageLookupByLibrary.simpleMessage( "La chiave di recupero inserita non è corretta"), @@ -1700,7 +1682,7 @@ class MessageLookup extends MessageLookupByLibrary { "theseItemsWillBeDeletedFromYourDevice": MessageLookupByLibrary.simpleMessage( "Questi file verranno eliminati dal tuo dispositivo."), - "theyAlsoGetXGb": m70, + "theyAlsoGetXGb": m71, "theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage( "Verranno eliminati da tutti gli album."), "thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage( @@ -1717,7 +1699,7 @@ class MessageLookup extends MessageLookupByLibrary { "Questo indirizzo email è già registrato"), "thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage( "Questa immagine non ha dati EXIF"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Questo è il tuo ID di verifica"), "thisWillLogYouOutOfTheFollowingDevice": @@ -1789,7 +1771,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Acquista altro spazio"), "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage( "Caricamento dei file nell\'album..."), - "uploadingMultipleMemories": m75, + "uploadingMultipleMemories": m76, "uploadingSingleMemory": MessageLookupByLibrary.simpleMessage("Conservando 1 ricordo..."), "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( @@ -1806,7 +1788,7 @@ class MessageLookup extends MessageLookupByLibrary { "useSelectedPhoto": MessageLookupByLibrary.simpleMessage("Usa la foto selezionata"), "usedSpace": MessageLookupByLibrary.simpleMessage("Spazio utilizzato"), - "validTill": m76, + "validTill": m77, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "Verifica fallita, per favore prova di nuovo"), @@ -1814,7 +1796,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("ID di verifica"), "verify": MessageLookupByLibrary.simpleMessage("Verifica"), "verifyEmail": MessageLookupByLibrary.simpleMessage("Verifica email"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("Verifica"), "verifyPasskey": MessageLookupByLibrary.simpleMessage("Verifica passkey"), @@ -1859,7 +1841,7 @@ class MessageLookup extends MessageLookupByLibrary { "whatsNew": MessageLookupByLibrary.simpleMessage("Novità"), "yearShort": MessageLookupByLibrary.simpleMessage("anno"), "yearly": MessageLookupByLibrary.simpleMessage("Annuale"), - "yearsAgo": m79, + "yearsAgo": m80, "yes": MessageLookupByLibrary.simpleMessage("Si"), "yesCancel": MessageLookupByLibrary.simpleMessage("Sì, cancella"), "yesConvertToViewer": MessageLookupByLibrary.simpleMessage( @@ -1891,7 +1873,7 @@ class MessageLookup extends MessageLookupByLibrary { "Non puoi condividere con te stesso"), "youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage( "Non hai nulla di archiviato."), - "youHaveSuccessfullyFreedUp": m80, + "youHaveSuccessfullyFreedUp": m81, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage( "Il tuo account è stato eliminato"), "yourMap": MessageLookupByLibrary.simpleMessage("La tua mappa"), diff --git a/mobile/lib/generated/intl/messages_ja.dart b/mobile/lib/generated/intl/messages_ja.dart index 2f2b429688..7cfd959319 100644 --- a/mobile/lib/generated/intl/messages_ja.dart +++ b/mobile/lib/generated/intl/messages_ja.dart @@ -135,82 +135,79 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(providerName) => "請求された場合は、 ${providerName} のサポートに連絡してください"; - static String m47(endDate) => + static String m48(endDate) => "${endDate} まで無料トライアルが有効です。\nその後、有料プランを選択することができます。"; - static String m48(toEmail) => "${toEmail} にメールでご連絡ください"; + static String m49(toEmail) => "${toEmail} にメールでご連絡ください"; - static String m49(toEmail) => "ログを以下のアドレスに送信してください \n${toEmail}"; + static String m50(toEmail) => "ログを以下のアドレスに送信してください \n${toEmail}"; - static String m50(folderName) => "${folderName} を処理中..."; + static String m51(folderName) => "${folderName} を処理中..."; - static String m51(storeName) => "${storeName} で評価"; + static String m52(storeName) => "${storeName} で評価"; - static String m52(storageInGB) => "3. お二人とも ${storageInGB} GB*を無料で手に入ります。"; + static String m53(storageInGB) => "3. お二人とも ${storageInGB} GB*を無料で手に入ります。"; - static String m53(userEmail) => + static String m54(userEmail) => "${userEmail} はこの共有アルバムから退出します\n\n${userEmail} が追加した写真もアルバムから削除されます"; - static String m54(endDate) => "サブスクリプションは ${endDate} に更新します"; + static String m55(endDate) => "サブスクリプションは ${endDate} に更新します"; - static String m55(count) => + static String m56(count) => "${Intl.plural(count, one: '${count} 個の結果', other: '${count} 個の結果')}"; static String m4(count) => "${count} 個を選択"; - static String m57(count, yourCount) => "${count} 個選択中(${yourCount} あなた)"; + static String m58(count, yourCount) => "${count} 個選択中(${yourCount} あなた)"; - static String m58(verificationID) => "私の確認ID: ente.ioの ${verificationID}"; + static String m59(verificationID) => "私の確認ID: ente.ioの ${verificationID}"; static String m5(verificationID) => "これがあなたのente.io確認用IDであることを確認できますか? ${verificationID}"; - static String m59(referralCode, referralStorageInGB) => + static String m60(referralCode, referralStorageInGB) => "リフェラルコード: ${referralCode}\n\n設定→一般→リフェラルで使うことで${referralStorageInGB}が無料になります(あなたが有料プランに加入したあと)。\n\nhttps://ente.io"; - static String m60(numberOfPeople) => + static String m61(numberOfPeople) => "${Intl.plural(numberOfPeople, zero: '誰かと共有しましょう', one: '1人と共有されています', other: '${numberOfPeople} 人と共有されています')}"; - static String m61(emailIDs) => "${emailIDs} と共有中"; + static String m62(emailIDs) => "${emailIDs} と共有中"; - static String m62(fileType) => "${fileType} はEnteから削除されます。"; + static String m63(fileType) => "${fileType} はEnteから削除されます。"; - static String m63(fileType) => "この ${fileType} はEnteとお使いのデバイスの両方にあります。"; + static String m64(fileType) => "この ${fileType} はEnteとお使いのデバイスの両方にあります。"; - static String m64(fileType) => "${fileType} はEnteから削除されます。"; + static String m65(fileType) => "${fileType} はEnteから削除されます。"; static String m1(storageAmountInGB) => "${storageAmountInGB} GB"; - static String m65( + static String m66( usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => "${usedAmount} ${usedStorageUnit} / ${totalAmount} ${totalStorageUnit} 使用"; - static String m66(id) => + static String m67(id) => "あなたの ${id} はすでに別のEnteアカウントにリンクされています。\nこのアカウントであなたの ${id} を使用したい場合は、サポートにお問い合わせください。"; - static String m67(endDate) => "サブスクリプションは ${endDate} でキャンセルされます"; + static String m68(endDate) => "サブスクリプションは ${endDate} でキャンセルされます"; - static String m68(completed, total) => "${completed}/${total} のメモリが保存されました"; + static String m69(completed, total) => "${completed}/${total} のメモリが保存されました"; - static String m70(storageAmountInGB) => "紹介者も ${storageAmountInGB} GB を得ます"; + static String m71(storageAmountInGB) => "紹介者も ${storageAmountInGB} GB を得ます"; - static String m71(email) => "これは ${email} の確認用ID"; + static String m72(email) => "これは ${email} の確認用ID"; - static String m72(count) => - "${Intl.plural(count, zero: '', one: '1日', other: '${count} 日')}"; + static String m76(count) => "${count} メモリを保存しています..."; - static String m75(count) => "${count} メモリを保存しています..."; + static String m77(endDate) => "${endDate} まで"; - static String m76(endDate) => "${endDate} まで"; - - static String m77(email) => "${email} を確認"; + static String m78(email) => "${email} を確認"; static String m2(email) => "${email}にメールを送りました"; - static String m79(count) => + static String m80(count) => "${Intl.plural(count, one: '${count} 年前', other: '${count} 年前')}"; - static String m80(storageSaved) => "${storageSaved} を解放しました"; + static String m81(storageSaved) => "${storageSaved} を解放しました"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -268,12 +265,9 @@ class MessageLookup extends MessageLookupByLibrary { "allClear": MessageLookupByLibrary.simpleMessage("✨ オールクリア"), "allMemoriesPreserved": MessageLookupByLibrary.simpleMessage("すべての思い出が保存されました"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "リンクを持つ人が共有アルバムに写真を追加できるようにします。"), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage("写真の追加を許可"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "allowDownloads": MessageLookupByLibrary.simpleMessage("ダウンロードを許可"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage("写真の追加をメンバーに許可する"), @@ -369,7 +363,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("バックアップされたフォルダ"), "backup": MessageLookupByLibrary.simpleMessage("バックアップ"), "backupFailed": MessageLookupByLibrary.simpleMessage("バックアップ失敗"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "backupOverMobileData": MessageLookupByLibrary.simpleMessage("モバイルデータを使ってバックアップ"), "backupSettings": MessageLookupByLibrary.simpleMessage("バックアップ設定"), @@ -693,8 +686,6 @@ class MessageLookup extends MessageLookupByLibrary { "extraPhotosFound": MessageLookupByLibrary.simpleMessage("追加の写真が見つかりました"), "extraPhotosFoundFor": m30, - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "faceRecognition": MessageLookupByLibrary.simpleMessage("顔認識"), "faces": MessageLookupByLibrary.simpleMessage("顔"), "failedToApplyCode": @@ -916,8 +907,6 @@ class MessageLookup extends MessageLookupByLibrary { "magicSearchHint": MessageLookupByLibrary.simpleMessage( "マジック検索では、「花」、「赤い車」、「本人確認書類」などの写真に写っているもので検索できます。"), "manage": MessageLookupByLibrary.simpleMessage("管理"), - "manageDeviceStorage": - MessageLookupByLibrary.simpleMessage("デバイスのストレージを管理"), "manageFamily": MessageLookupByLibrary.simpleMessage("ファミリーの管理"), "manageLink": MessageLookupByLibrary.simpleMessage("リンクを管理"), "manageParticipants": MessageLookupByLibrary.simpleMessage("管理"), @@ -1016,11 +1005,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("編集を保存できませんでした"), "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage("問題が発生しました"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "openSettings": MessageLookupByLibrary.simpleMessage("設定を開く"), "openTheItem": MessageLookupByLibrary.simpleMessage("• アイテムを開く"), "openstreetmapContributors": @@ -1074,7 +1058,7 @@ class MessageLookup extends MessageLookupByLibrary { "pinAlbum": MessageLookupByLibrary.simpleMessage("アルバムをピンする"), "pinLock": MessageLookupByLibrary.simpleMessage("PINロック"), "playOnTv": MessageLookupByLibrary.simpleMessage("TVでアルバムを再生"), - "playStoreFreeTrialValidTill": m47, + "playStoreFreeTrialValidTill": m48, "playstoreSubscription": MessageLookupByLibrary.simpleMessage("PlayStoreサブスクリプション"), "pleaseCheckYourInternetConnectionAndTryAgain": @@ -1084,13 +1068,13 @@ class MessageLookup extends MessageLookupByLibrary { "Support@ente.ioにお問い合わせください、お手伝いいたします。"), "pleaseContactSupportIfTheProblemPersists": MessageLookupByLibrary.simpleMessage("問題が解決しない場合はサポートにお問い合わせください"), - "pleaseEmailUsAt": m48, + "pleaseEmailUsAt": m49, "pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage("権限を付与してください"), "pleaseLoginAgain": MessageLookupByLibrary.simpleMessage("もう一度試してください"), "pleaseSelectQuickLinksToRemove": MessageLookupByLibrary.simpleMessage("削除するクイックリンクを選択してください"), - "pleaseSendTheLogsTo": m49, + "pleaseSendTheLogsTo": m50, "pleaseTryAgain": MessageLookupByLibrary.simpleMessage("もう一度試してください"), "pleaseVerifyTheCodeYouHaveEntered": MessageLookupByLibrary.simpleMessage("入力したコードを確認してください"), @@ -1110,7 +1094,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("プライバシーポリシー"), "privateBackups": MessageLookupByLibrary.simpleMessage("プライベートバックアップ"), "privateSharing": MessageLookupByLibrary.simpleMessage("プライベート共有"), - "processingImport": m50, + "processingImport": m51, "publicLinkCreated": MessageLookupByLibrary.simpleMessage("公開リンクが作成されました"), "publicLinkEnabled": @@ -1120,7 +1104,7 @@ class MessageLookup extends MessageLookupByLibrary { "raiseTicket": MessageLookupByLibrary.simpleMessage("サポートを受ける"), "rateTheApp": MessageLookupByLibrary.simpleMessage("アプリを評価"), "rateUs": MessageLookupByLibrary.simpleMessage("評価して下さい"), - "rateUsOnStore": m51, + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("復元"), "recoverAccount": MessageLookupByLibrary.simpleMessage("アカウントを復元"), "recoverButton": MessageLookupByLibrary.simpleMessage("復元"), @@ -1152,7 +1136,7 @@ class MessageLookup extends MessageLookupByLibrary { "referralStep1": MessageLookupByLibrary.simpleMessage("1. このコードを友達に贈りましょう"), "referralStep2": MessageLookupByLibrary.simpleMessage("2. 友達が有料プランに登録"), - "referralStep3": m52, + "referralStep3": m53, "referrals": MessageLookupByLibrary.simpleMessage("リフェラル"), "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage("リフェラルは現在一時停止しています"), @@ -1175,7 +1159,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("お気に入りリストから外す"), "removeLink": MessageLookupByLibrary.simpleMessage("リンクを削除"), "removeParticipant": MessageLookupByLibrary.simpleMessage("参加者を削除"), - "removeParticipantBody": m53, + "removeParticipantBody": m54, "removePersonLabel": MessageLookupByLibrary.simpleMessage("人名を削除"), "removePublicLink": MessageLookupByLibrary.simpleMessage("公開リンクを削除"), "removePublicLinks": MessageLookupByLibrary.simpleMessage("公開リンクを削除"), @@ -1190,7 +1174,7 @@ class MessageLookup extends MessageLookupByLibrary { "renameFile": MessageLookupByLibrary.simpleMessage("ファイル名を変更"), "renewSubscription": MessageLookupByLibrary.simpleMessage("サブスクリプションの更新"), - "renewsOn": m54, + "renewsOn": m55, "reportABug": MessageLookupByLibrary.simpleMessage("バグを報告"), "reportBug": MessageLookupByLibrary.simpleMessage("バグを報告"), "resendEmail": MessageLookupByLibrary.simpleMessage("メールを再送信"), @@ -1249,10 +1233,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("当時の直近で撮影された写真をグループ化"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage("友達を招待すると、共有される写真はここから閲覧できます"), - "searchResultCount": m55, + "searchResultCount": m56, "security": MessageLookupByLibrary.simpleMessage("セキュリティ"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "selectALocation": MessageLookupByLibrary.simpleMessage("場所を選択"), "selectALocationFirst": MessageLookupByLibrary.simpleMessage("先に場所を選択してください"), @@ -1274,7 +1256,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "選択したアイテムはすべてのアルバムから削除され、ゴミ箱に移動されます。"), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "send": MessageLookupByLibrary.simpleMessage("送信"), "sendEmail": MessageLookupByLibrary.simpleMessage("メールを送信する"), "sendInvite": MessageLookupByLibrary.simpleMessage("招待を送る"), @@ -1296,16 +1278,16 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("アルバムを開いて右上のシェアボタンをタップ"), "shareAnAlbumNow": MessageLookupByLibrary.simpleMessage("アルバムを共有"), "shareLink": MessageLookupByLibrary.simpleMessage("リンクの共有"), - "shareMyVerificationID": m58, + "shareMyVerificationID": m59, "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage("選んだ人と共有します"), "shareTextConfirmOthersVerificationID": m5, "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage( "Enteをダウンロードして、写真や動画の共有を簡単に!\n\nhttps://ente.io"), - "shareTextReferralCode": m59, + "shareTextReferralCode": m60, "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage("Enteを使っていない人に共有"), - "shareWithPeopleSectionTitle": m60, + "shareWithPeopleSectionTitle": m61, "shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage("アルバムの共有をしてみましょう"), "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( @@ -1316,7 +1298,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("新しい共有写真"), "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage("誰かが写真を共有アルバムに追加した時に通知を受け取る"), - "sharedWith": m61, + "sharedWith": m62, "sharedWithMe": MessageLookupByLibrary.simpleMessage("あなたと共有されたアルバム"), "sharedWithYou": MessageLookupByLibrary.simpleMessage("あなたと共有されています"), "sharing": MessageLookupByLibrary.simpleMessage("共有中..."), @@ -1330,11 +1312,11 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("他のデバイスからサインアウトする"), "signUpTerms": MessageLookupByLibrary.simpleMessage( "利用規約プライバシーポリシーに同意します"), - "singleFileDeleteFromDevice": m62, + "singleFileDeleteFromDevice": m63, "singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage("全てのアルバムから削除されます。"), - "singleFileInBothLocalAndRemote": m63, - "singleFileInRemoteOnly": m64, + "singleFileInBothLocalAndRemote": m64, + "singleFileInRemoteOnly": m65, "skip": MessageLookupByLibrary.simpleMessage("スキップ"), "social": MessageLookupByLibrary.simpleMessage("SNS"), "someItemsAreInBothEnteAndYourDevice": @@ -1375,10 +1357,10 @@ class MessageLookup extends MessageLookupByLibrary { "storageInGB": m1, "storageLimitExceeded": MessageLookupByLibrary.simpleMessage("ストレージの上限を超えました"), - "storageUsageInfo": m65, + "storageUsageInfo": m66, "strongStrength": MessageLookupByLibrary.simpleMessage("強いパスワード"), - "subAlreadyLinkedErrMessage": m66, - "subWillBeCancelledOn": m67, + "subAlreadyLinkedErrMessage": m67, + "subWillBeCancelledOn": m68, "subscribe": MessageLookupByLibrary.simpleMessage("サブスクライブ"), "subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage( "共有を有効にするには、有料サブスクリプションが必要です。"), @@ -1392,7 +1374,7 @@ class MessageLookup extends MessageLookupByLibrary { "successfullyUnhid": MessageLookupByLibrary.simpleMessage("非表示を解除しました"), "suggestFeatures": MessageLookupByLibrary.simpleMessage("機能を提案"), "support": MessageLookupByLibrary.simpleMessage("サポート"), - "syncProgress": m68, + "syncProgress": m69, "syncStopped": MessageLookupByLibrary.simpleMessage("同期が停止しました"), "syncing": MessageLookupByLibrary.simpleMessage("同期中..."), "systemTheme": MessageLookupByLibrary.simpleMessage("システム"), @@ -1410,15 +1392,12 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("ありがとうございます!"), "theDownloadCouldNotBeCompleted": MessageLookupByLibrary.simpleMessage("ダウンロードを完了できませんでした"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "theRecoveryKeyYouEnteredIsIncorrect": MessageLookupByLibrary.simpleMessage("入力したリカバリーキーが間違っています"), "theme": MessageLookupByLibrary.simpleMessage("テーマ"), "theseItemsWillBeDeletedFromYourDevice": MessageLookupByLibrary.simpleMessage("これらの項目はデバイスから削除されます。"), - "theyAlsoGetXGb": m70, + "theyAlsoGetXGb": m71, "theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage("全てのアルバムから削除されます。"), "thisActionCannotBeUndone": @@ -1434,7 +1413,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("このメールアドレスはすでに使用されています。"), "thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage("この画像にEXIFデータはありません"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage("これはあなたの認証IDです"), "thisWillLogYouOutOfTheFollowingDevice": @@ -1457,7 +1436,6 @@ class MessageLookup extends MessageLookupByLibrary { "total": MessageLookupByLibrary.simpleMessage("合計"), "totalSize": MessageLookupByLibrary.simpleMessage("合計サイズ"), "trash": MessageLookupByLibrary.simpleMessage("ゴミ箱"), - "trashDaysLeft": m72, "trim": MessageLookupByLibrary.simpleMessage("トリミング"), "tryAgain": MessageLookupByLibrary.simpleMessage("もう一度試してください"), "turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage( @@ -1494,7 +1472,7 @@ class MessageLookup extends MessageLookupByLibrary { "upgrade": MessageLookupByLibrary.simpleMessage("アップグレード"), "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage("アルバムにファイルをアップロード中"), - "uploadingMultipleMemories": m75, + "uploadingMultipleMemories": m76, "uploadingSingleMemory": MessageLookupByLibrary.simpleMessage("1メモリを保存しています..."), "upto50OffUntil4thDec": @@ -1508,13 +1486,13 @@ class MessageLookup extends MessageLookupByLibrary { "useRecoveryKey": MessageLookupByLibrary.simpleMessage("リカバリーキーを使用"), "useSelectedPhoto": MessageLookupByLibrary.simpleMessage("選択した写真を使用"), "usedSpace": MessageLookupByLibrary.simpleMessage("使用済み領域"), - "validTill": m76, + "validTill": m77, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage("確認に失敗しました、再試行してください"), "verificationId": MessageLookupByLibrary.simpleMessage("確認用ID"), "verify": MessageLookupByLibrary.simpleMessage("確認"), "verifyEmail": MessageLookupByLibrary.simpleMessage("Eメールの確認"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("確認"), "verifyPasskey": MessageLookupByLibrary.simpleMessage("パスキーを確認"), "verifyPassword": MessageLookupByLibrary.simpleMessage("パスワードの確認"), @@ -1551,7 +1529,7 @@ class MessageLookup extends MessageLookupByLibrary { "welcomeBack": MessageLookupByLibrary.simpleMessage("おかえりなさい!"), "whatsNew": MessageLookupByLibrary.simpleMessage("最新情報"), "yearly": MessageLookupByLibrary.simpleMessage("年額"), - "yearsAgo": m79, + "yearsAgo": m80, "yes": MessageLookupByLibrary.simpleMessage("はい"), "yesCancel": MessageLookupByLibrary.simpleMessage("キャンセル"), "yesConvertToViewer": @@ -1579,7 +1557,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("自分自身と共有することはできません"), "youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage("アーカイブした項目はありません"), - "youHaveSuccessfullyFreedUp": m80, + "youHaveSuccessfullyFreedUp": m81, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage("アカウントは削除されました"), "yourMap": MessageLookupByLibrary.simpleMessage("あなたの地図"), diff --git a/mobile/lib/generated/intl/messages_km.dart b/mobile/lib/generated/intl/messages_km.dart index f0a94b600c..22d4231361 100644 --- a/mobile/lib/generated/intl/messages_km.dart +++ b/mobile/lib/generated/intl/messages_km.dart @@ -21,22 +21,5 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'km'; final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired.") - }; + static Map _notInlinedMessages(_) => {}; } diff --git a/mobile/lib/generated/intl/messages_ko.dart b/mobile/lib/generated/intl/messages_ko.dart index 02db22fa60..e378d62fd9 100644 --- a/mobile/lib/generated/intl/messages_ko.dart +++ b/mobile/lib/generated/intl/messages_ko.dart @@ -24,12 +24,8 @@ class MessageLookup extends MessageLookupByLibrary { static Map _notInlinedMessages(_) => { "accountWelcomeBack": MessageLookupByLibrary.simpleMessage("다시 오신 것을 환영합니다!"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "askDeleteReason": MessageLookupByLibrary.simpleMessage("계정을 삭제하는 가장 큰 이유가 무엇인가요?"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "cancel": MessageLookupByLibrary.simpleMessage("닫기"), "confirmAccountDeletion": MessageLookupByLibrary.simpleMessage("계정 삭제 확인"), @@ -41,21 +37,9 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("올바른 이메일 주소를 입력하세요."), "enterYourEmailAddress": MessageLookupByLibrary.simpleMessage("이메일을 입력하세요"), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "feedback": MessageLookupByLibrary.simpleMessage("피드백"), "invalidEmailAddress": MessageLookupByLibrary.simpleMessage("잘못된 이메일 주소"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "verify": MessageLookupByLibrary.simpleMessage("인증"), "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage("계정이 삭제되었습니다.") diff --git a/mobile/lib/generated/intl/messages_lt.dart b/mobile/lib/generated/intl/messages_lt.dart index 0c00731aaf..12627e0d77 100644 --- a/mobile/lib/generated/intl/messages_lt.dart +++ b/mobile/lib/generated/intl/messages_lt.dart @@ -26,6 +26,9 @@ class MessageLookup extends MessageLookupByLibrary { static String m9(count) => "${Intl.plural(count, one: 'Pridėti žiūrėtoją', few: 'Pridėti žiūrėtojus', many: 'Pridėti žiūrėtojo', other: 'Pridėti žiūrėtojų')}"; + static String m12(count) => + "${Intl.plural(count, zero: '0 dalyvių', one: '1 dalyvis', few: '${count} dalyviai', many: '${count} dalyvio', other: '${count} dalyvių')}"; + static String m13(versionValue) => "Versija: ${versionValue}"; static String m15(paymentProvider) => @@ -34,8 +37,21 @@ class MessageLookup extends MessageLookupByLibrary { static String m16(user) => "${user} negalės pridėti daugiau nuotraukų į šį albumą\n\nJie vis tiek galės pašalinti esamas pridėtas nuotraukas"; + static String m17(isFamilyMember, storageAmountInGb) => + "${Intl.select(isFamilyMember, { + 'true': 'Jūsų šeima gavo ${storageAmountInGb} GB iki šiol', + 'false': 'Jūs gavote ${storageAmountInGb} GB iki šiol', + 'other': 'Jūs gavote ${storageAmountInGb} GB iki šiol.', + })}"; + + static String m82(count) => + "${Intl.plural(count, zero: 'Pridėta 0 bendradarbių', one: 'Pridėtas 1 bendradarbis', few: 'Pridėti ${count} bendradarbiai', many: 'Pridėta ${count} bendradarbio', other: 'Pridėta ${count} bendradarbių')}"; + static String m21(endpoint) => "Prijungta prie ${endpoint}"; + static String m23(currentlyDeleting, totalCount) => + "Ištrinama ${currentlyDeleting} / ${totalCount}"; + static String m25(supportEmail) => "Iš savo registruoto el. pašto adreso atsiųskite el. laišką adresu ${supportEmail}"; @@ -55,9 +71,13 @@ class MessageLookup extends MessageLookupByLibrary { static String m38(currentlyProcessing, totalCount) => "Apdorojama ${currentlyProcessing} / ${totalCount}"; + static String m40(expiryTime) => "Nuoroda nebegalios ${expiryTime}"; + static String m41(count) => "${Intl.plural(count, one: 'Perkelti elementą', few: 'Perkelti elementus', many: 'Perkelti elemento', other: 'Perkelti elementų')}"; + static String m43(personName) => "Nėra pasiūlymų asmeniui ${personName}."; + static String m44(name) => "Ne ${name}?"; static String m0(passwordStrengthValue) => @@ -66,58 +86,78 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(providerName) => "Kreipkitės į ${providerName} palaikymo komandą, jei jums buvo nuskaičiuota."; - static String m50(folderName) => "Apdorojama ${folderName}..."; + static String m47(count) => + "${Intl.plural(count, zero: '0 nuotraukų', one: '1 nuotrauka', few: '${count} nuotraukos', many: '${count} nuotraukos', other: '${count} nuotraukų')}"; - static String m51(storeName) => "Vertinti mus parduotuvėje „${storeName}“"; + static String m51(folderName) => "Apdorojama ${folderName}..."; - static String m53(userEmail) => - "${userEmail} bus pašalintas iš šio bendrinamo albumo\n\nVisos jų pridėtos nuotraukos taip pat bus pašalintos iš albumo"; + static String m52(storeName) => "Vertinti mus parduotuvėje „${storeName}“"; - static String m55(count) => + static String m53(storageInGB) => + "3. Abu gaunate ${storageInGB} GB* nemokamai"; + + static String m54(userEmail) => + "${userEmail} bus pašalintas iš šio bendrinamo albumo.\n\nVisos jų pridėtos nuotraukos taip pat bus pašalintos iš albumo."; + + static String m55(endDate) => "Prenumerata atnaujinama ${endDate}"; + + static String m56(count) => "${Intl.plural(count, one: 'Rastas ${count} rezultatas', few: 'Rasti ${count} rezultatai', many: 'Rasta ${count} rezultato', other: 'Rasta ${count} rezultatų')}"; + static String m57(snapshotLenght, searchLenght) => + "Skyrių ilgio neatitikimas: ${snapshotLenght} != ${searchLenght}"; + static String m4(count) => "${count} pasirinkta"; - static String m57(count, yourCount) => + static String m58(count, yourCount) => "${count} pasirinkta (${yourCount} jūsų)"; - static String m58(verificationID) => + static String m59(verificationID) => "Štai mano patvirtinimo ID: ${verificationID}, skirta ente.io."; static String m5(verificationID) => "Ei, ar galite patvirtinti, kad tai yra jūsų ente.io patvirtinimo ID: ${verificationID}"; - static String m63(fileType) => + static String m61(numberOfPeople) => + "${Intl.plural(numberOfPeople, zero: 'Bendrinti su konkrečiais asmenimis', one: 'Bendrinima su 1 asmeniu', few: 'Bendrinima su ${numberOfPeople} asmenims', many: 'Bendrinima su ${numberOfPeople} asmens', other: 'Bendrinima su ${numberOfPeople} asmenų')}"; + + static String m64(fileType) => "Šis ${fileType} yra ir saugykloje „Ente“ bei įrenginyje."; - static String m64(fileType) => "Šis ${fileType} bus ištrintas iš „Ente“."; + static String m65(fileType) => "Šis ${fileType} bus ištrintas iš „Ente“."; static String m1(storageAmountInGB) => "${storageAmountInGB} GB"; - static String m66(id) => + static String m67(id) => "Jūsų ${id} jau susietas su kita „Ente“ paskyra.\nJei norite naudoti savo ${id} su šia paskyra, susisiekite su mūsų palaikymo komanda."; - static String m68(completed, total) => + static String m69(completed, total) => "${completed} / ${total} išsaugomi prisiminimai"; - static String m69(ignoreReason) => + static String m70(ignoreReason) => "Palieskite, kad įkeltumėte. Įkėlimas šiuo metu ignoruojamas dėl ${ignoreReason}."; - static String m71(email) => "Tai – ${email} patvirtinimo ID"; + static String m72(email) => "Tai – ${email} patvirtinimo ID"; - static String m73(galleryType) => + static String m73(count) => + "${Intl.plural(count, zero: 'Netrukus', one: '1 diena', few: '${count} dienos', many: '${count} dienos', other: '${count} dienų')}"; + + static String m74(galleryType) => "Galerijos tipas ${galleryType} nepalaikomas pervadinimui."; - static String m74(ignoreReason) => + static String m75(ignoreReason) => "Įkėlimas ignoruojamas dėl ${ignoreReason}."; - static String m76(endDate) => "Galioja iki ${endDate}"; + static String m77(endDate) => "Galioja iki ${endDate}"; - static String m77(email) => "Patvirtinti ${email}"; + static String m78(email) => "Patvirtinti ${email}"; + + static String m79(count) => + "${Intl.plural(count, zero: 'Pridėta 0 žiūrėtojų', one: 'Pridėtas 1 žiūrėtojas', few: 'Pridėti ${count} žiūrėtojai', many: 'Pridėta ${count} žiūrėtojo', other: 'Pridėta ${count} žiūrėtojų')}"; static String m2(email) => "Išsiuntėme laišką adresu ${email}"; - static String m79(count) => + static String m80(count) => "${Intl.plural(count, one: 'prieš ${count} metus', few: 'prieš ${count} metus', many: 'prieš ${count} metų', other: 'prieš ${count} metų')}"; final messages = _notInlinedMessages(_notInlinedMessages); @@ -149,11 +189,13 @@ class MessageLookup extends MessageLookupByLibrary { "addNew": MessageLookupByLibrary.simpleMessage("Pridėti naują"), "addNewPerson": MessageLookupByLibrary.simpleMessage("Pridėti naują asmenį"), + "addOns": MessageLookupByLibrary.simpleMessage("Priedai"), "addToAlbum": MessageLookupByLibrary.simpleMessage("Pridėti į albumą"), "addToEnte": MessageLookupByLibrary.simpleMessage("Pridėti į „Ente“"), "addViewer": MessageLookupByLibrary.simpleMessage("Pridėti žiūrėtoją"), "addViewers": m9, "addedAs": MessageLookupByLibrary.simpleMessage("Pridėta kaip"), + "advanced": MessageLookupByLibrary.simpleMessage("Išplėstiniai"), "advancedSettings": MessageLookupByLibrary.simpleMessage("Išplėstiniai"), "after1Day": MessageLookupByLibrary.simpleMessage("Po 1 dienos"), @@ -162,18 +204,21 @@ class MessageLookup extends MessageLookupByLibrary { "after1Week": MessageLookupByLibrary.simpleMessage("Po 1 savaitės"), "after1Year": MessageLookupByLibrary.simpleMessage("Po 1 metų"), "albumOwner": MessageLookupByLibrary.simpleMessage("Savininkas"), + "albumParticipantsCount": m12, "albumUpdated": MessageLookupByLibrary.simpleMessage("Atnaujintas albumas"), "albums": MessageLookupByLibrary.simpleMessage("Albumai"), + "allMemoriesPreserved": + MessageLookupByLibrary.simpleMessage("Išsaugoti visi prisiminimai"), "allPersonGroupingWillReset": MessageLookupByLibrary.simpleMessage( "Visi šio asmens grupavimai bus iš naujo nustatyti, o jūs neteksite visų šiam asmeniui pateiktų pasiūlymų"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), + "allow": MessageLookupByLibrary.simpleMessage("Leisti"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "Leiskite nuorodą turintiems asmenims taip pat pridėti nuotraukų į bendrinamą albumą."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage("Leisti pridėti nuotraukų"), "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), + "Leisti programai atverti bendrinamų albumų nuorodas"), "allowDownloads": MessageLookupByLibrary.simpleMessage("Leisti atsisiuntimus"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( @@ -192,6 +237,9 @@ class MessageLookup extends MessageLookupByLibrary { "appleId": MessageLookupByLibrary.simpleMessage("„Apple ID“"), "apply": MessageLookupByLibrary.simpleMessage("Taikyti"), "applyCodeTitle": MessageLookupByLibrary.simpleMessage("Taikyti kodą"), + "archive": MessageLookupByLibrary.simpleMessage("Archyvas"), + "archiveAlbum": + MessageLookupByLibrary.simpleMessage("Archyvuoti albumą"), "archiving": MessageLookupByLibrary.simpleMessage("Archyvuojama..."), "areYouSureThatYouWantToLeaveTheFamily": MessageLookupByLibrary.simpleMessage( @@ -206,6 +254,8 @@ class MessageLookup extends MessageLookupByLibrary { "areYouSureYouWantToResetThisPerson": MessageLookupByLibrary.simpleMessage( "Ar tikrai norite iš naujo nustatyti šį asmenį?"), + "askCancelReason": MessageLookupByLibrary.simpleMessage( + "Jūsų prenumerata buvo atšaukta. Ar norėtumėte pasidalyti priežastimi?"), "askDeleteReason": MessageLookupByLibrary.simpleMessage( "Kokia yra pagrindinė priežastis, dėl kurios ištrinate savo paskyrą?"), "atAFalloutShelter": @@ -220,9 +270,18 @@ class MessageLookup extends MessageLookupByLibrary { "authToInitiateAccountDeletion": MessageLookupByLibrary.simpleMessage( "Nustatykite tapatybę, kad pradėtumėte paskyros ištrynimą"), "authToViewPasskey": MessageLookupByLibrary.simpleMessage( - "Nustatykite tapatybę, kad peržiūrėtumėte savo slaptaraktą"), + "Nustatykite tapatybę, kad peržiūrėtumėte savo slaptaraktį"), "authToViewYourHiddenFiles": MessageLookupByLibrary.simpleMessage( "Nustatykite tapatybę, kad peržiūrėtumėte paslėptus failus"), + "authToViewYourRecoveryKey": MessageLookupByLibrary.simpleMessage( + "Nustatykite tapatybę, kad peržiūrėtumėte savo atkūrimo raktą"), + "authenticating": + MessageLookupByLibrary.simpleMessage("Nustatoma tapatybė..."), + "authenticationFailedPleaseTryAgain": + MessageLookupByLibrary.simpleMessage( + "Tapatybės nustatymas nepavyko. Bandykite dar kartą."), + "authenticationSuccessful": MessageLookupByLibrary.simpleMessage( + "Tapatybės nustatymas sėkmingas."), "autoCastDialogBody": MessageLookupByLibrary.simpleMessage( "Čia matysite pasiekiamus perdavimo įrenginius."), "autoLock": @@ -236,8 +295,13 @@ class MessageLookup extends MessageLookupByLibrary { "autoPairDesc": MessageLookupByLibrary.simpleMessage( "Automatinis susiejimas veikia tik su įrenginiais, kurie palaiko „Chromecast“."), "available": MessageLookupByLibrary.simpleMessage("Prieinama"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), + "backup": + MessageLookupByLibrary.simpleMessage("Kurti atsarginę kopiją"), + "backupFile": MessageLookupByLibrary.simpleMessage( + "Kurti atsarginę failo kopiją"), "birthday": MessageLookupByLibrary.simpleMessage("Gimtadienis"), + "blackFridaySale": MessageLookupByLibrary.simpleMessage( + "Juodojo penktadienio išpardavimas"), "blog": MessageLookupByLibrary.simpleMessage("Tinklaraštis"), "cachedData": MessageLookupByLibrary.simpleMessage("Podėliuoti duomenis"), @@ -260,6 +324,20 @@ class MessageLookup extends MessageLookupByLibrary { "changeEmail": MessageLookupByLibrary.simpleMessage("Keisti el. paštą"), "changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage( "Keisti pasirinktų elementų vietovę?"), + "changeLogBackupStatusContent": MessageLookupByLibrary.simpleMessage( + "Pridėjome visų į „Ente“ įkeltų failų žurnalą, įskaitant nesėkmingus ir laukiančius eilėje."), + "changeLogBackupStatusTitle": + MessageLookupByLibrary.simpleMessage("Atsarginės kopijos būsena"), + "changeLogDiscoverContent": MessageLookupByLibrary.simpleMessage( + "Ieškote savo tapatybės kortelių, užrašų ar net memų nuotraukų? Eikite į paieškos kortelę ir patikrinkite Atrasti. Remiantis mūsų semantine paieška, joje rasite nuotraukų, kurios gali būti jums svarbios.\\n\\nPasiekiama tik tada, jei įjungėte mašininį mokymąsi."), + "changeLogDiscoverTitle": + MessageLookupByLibrary.simpleMessage("Atraskite"), + "changeLogMagicSearchImprovementContent": + MessageLookupByLibrary.simpleMessage( + "Patobulinome magiškąją paiešką, kad ji taptų daug spartesnė ir jums nereikėtų laukti, kol rasite tai, ko ieškote."), + "changeLogMagicSearchImprovementTitle": + MessageLookupByLibrary.simpleMessage( + "Magiškos paieškos patobulinimas"), "changePassword": MessageLookupByLibrary.simpleMessage("Keisti slaptažodį"), "changePasswordTitle": @@ -274,11 +352,16 @@ class MessageLookup extends MessageLookupByLibrary { "checking": MessageLookupByLibrary.simpleMessage("Tikrinama..."), "checkingModels": MessageLookupByLibrary.simpleMessage("Tikrinami modeliai..."), + "claimMore": MessageLookupByLibrary.simpleMessage("Gaukite daugiau!"), + "claimed": MessageLookupByLibrary.simpleMessage("Gauta"), + "claimedStorageSoFar": m17, "cleanUncategorized": MessageLookupByLibrary.simpleMessage("Valyti nekategorizuotus"), "cleanUncategorizedDescription": MessageLookupByLibrary.simpleMessage( "Pašalinkite iš nekategorizuotus visus failus, esančius kituose albumuose"), "clearCaches": MessageLookupByLibrary.simpleMessage("Valyti podėlius"), + "clearIndexes": + MessageLookupByLibrary.simpleMessage("Valyti indeksavimus"), "close": MessageLookupByLibrary.simpleMessage("Uždaryti"), "clusteringProgress": MessageLookupByLibrary.simpleMessage("Sankaupos vykdymas"), @@ -290,10 +373,13 @@ class MessageLookup extends MessageLookupByLibrary { "Nukopijuotas kodas į iškarpinę"), "collabLinkSectionDescription": MessageLookupByLibrary.simpleMessage( "Sukurkite nuorodą, kad asmenys galėtų pridėti ir peržiūrėti nuotraukas bendrinamame albume, nereikalaujant „Ente“ programos ar paskyros. Puikiai tinka įvykių nuotraukoms rinkti."), + "collaborativeLink": + MessageLookupByLibrary.simpleMessage("Bendradarbiavimo nuoroda"), "collaborator": MessageLookupByLibrary.simpleMessage("Bendradarbis"), "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": MessageLookupByLibrary.simpleMessage( "Bendradarbiai gali pridėti nuotraukų ir vaizdo įrašų į bendrintą albumą."), + "collaboratorsSuccessfullyAdded": m82, "collect": MessageLookupByLibrary.simpleMessage("Rinkti"), "collectEventPhotos": MessageLookupByLibrary.simpleMessage("Rinkti įvykių nuotraukas"), @@ -304,6 +390,8 @@ class MessageLookup extends MessageLookupByLibrary { "color": MessageLookupByLibrary.simpleMessage("Spalva"), "configuration": MessageLookupByLibrary.simpleMessage("Konfiguracija"), "confirm": MessageLookupByLibrary.simpleMessage("Patvirtinti"), + "confirm2FADisable": MessageLookupByLibrary.simpleMessage( + "Ar tikrai norite išjungti dvigubą tapatybės nustatymą?"), "confirmAccountDeletion": MessageLookupByLibrary.simpleMessage( "Patvirtinti paskyros ištrynimą"), "confirmDeletePrompt": MessageLookupByLibrary.simpleMessage( @@ -324,6 +412,8 @@ class MessageLookup extends MessageLookupByLibrary { "continueLabel": MessageLookupByLibrary.simpleMessage("Tęsti"), "continueOnFreeTrial": MessageLookupByLibrary.simpleMessage( "Tęsti nemokame bandomajame laikotarpyje"), + "copyEmailAddress": + MessageLookupByLibrary.simpleMessage("Kopijuoti el. pašto adresą"), "copyLink": MessageLookupByLibrary.simpleMessage("Kopijuoti nuorodą"), "copypasteThisCodentoYourAuthenticatorApp": MessageLookupByLibrary.simpleMessage( @@ -345,6 +435,8 @@ class MessageLookup extends MessageLookupByLibrary { "crop": MessageLookupByLibrary.simpleMessage("Apkirpti"), "currentUsageIs": MessageLookupByLibrary.simpleMessage("Dabartinis naudojimas – "), + "currentlyRunning": + MessageLookupByLibrary.simpleMessage("šiuo metu vykdoma"), "custom": MessageLookupByLibrary.simpleMessage("Pasirinktinis"), "customEndpoint": m21, "darkTheme": MessageLookupByLibrary.simpleMessage("Tamsi"), @@ -363,10 +455,17 @@ class MessageLookup extends MessageLookupByLibrary { "deleteAlbum": MessageLookupByLibrary.simpleMessage("Ištrinti albumą"), "deleteAlbumDialog": MessageLookupByLibrary.simpleMessage( "Taip pat ištrinti šiame albume esančias nuotraukas (ir vaizdo įrašus) iš visų kitų albumų, kuriuose jos yra dalis?"), + "deleteAlbumsDialogBody": MessageLookupByLibrary.simpleMessage( + "Tai ištrins visus tuščius albumus. Tai naudinga, kai norite sumažinti netvarką savo albumų sąraše."), + "deleteAll": MessageLookupByLibrary.simpleMessage("Ištrinti viską"), "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage( "Ši paskyra susieta su kitomis „Ente“ programomis, jei jas naudojate. Jūsų įkelti duomenys per visas „Ente“ programas bus planuojama ištrinti, o jūsų paskyra bus ištrinta negrįžtamai."), "deleteEmailRequest": MessageLookupByLibrary.simpleMessage( "Iš savo registruoto el. pašto adreso siųskite el. laišką adresu account-deletion@ente.io."), + "deleteEmptyAlbums": + MessageLookupByLibrary.simpleMessage("Ištrinti tuščius albumus"), + "deleteEmptyAlbumsWithQuestionMark": + MessageLookupByLibrary.simpleMessage("Ištrinti tuščius albumus?"), "deleteFromBoth": MessageLookupByLibrary.simpleMessage("Ištrinti iš abiejų"), "deleteFromDevice": @@ -377,6 +476,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Ištrinti vietovę"), "deletePhotos": MessageLookupByLibrary.simpleMessage("Ištrinti nuotraukas"), + "deleteProgress": m23, "deleteReason1": MessageLookupByLibrary.simpleMessage( "Trūksta pagrindinės funkcijos, kurios man reikia"), "deleteReason2": MessageLookupByLibrary.simpleMessage( @@ -387,6 +487,10 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Mano priežastis nenurodyta"), "deleteRequestSLAText": MessageLookupByLibrary.simpleMessage( "Jūsų prašymas bus apdorotas per 72 valandas."), + "deleteSharedAlbum": + MessageLookupByLibrary.simpleMessage("Ištrinti bendrinamą albumą?"), + "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage( + "Albumas bus ištrintas visiems.\n\nPrarasite prieigą prie bendrinamų nuotraukų, esančių šiame albume ir priklausančių kitiems."), "designedToOutlive": MessageLookupByLibrary.simpleMessage("Sukurta išgyventi"), "developerSettings": @@ -403,6 +507,8 @@ class MessageLookup extends MessageLookupByLibrary { "Žiūrėtojai vis tiek gali daryti ekrano kopijas arba išsaugoti nuotraukų kopijas naudojant išorinius įrankius"), "disableDownloadWarningTitle": MessageLookupByLibrary.simpleMessage("Atkreipkite dėmesį"), + "disableTwofactor": MessageLookupByLibrary.simpleMessage( + "Išjungti dvigubą tapatybės nustatymą"), "discord": MessageLookupByLibrary.simpleMessage("„Discord“"), "discover": MessageLookupByLibrary.simpleMessage("Atraskite"), "discover_babies": MessageLookupByLibrary.simpleMessage("Kūdikiai"), @@ -429,9 +535,12 @@ class MessageLookup extends MessageLookupByLibrary { "doThisLater": MessageLookupByLibrary.simpleMessage("Daryti tai vėliau"), "done": MessageLookupByLibrary.simpleMessage("Atlikta"), + "doubleYourStorage": + MessageLookupByLibrary.simpleMessage("Padvigubinkite saugyklą"), "download": MessageLookupByLibrary.simpleMessage("Atsisiųsti"), "downloadFailed": MessageLookupByLibrary.simpleMessage("Atsisiuntimas nepavyko."), + "downloading": MessageLookupByLibrary.simpleMessage("Atsisiunčiama..."), "dropSupportEmail": m25, "duplicateItemsGroup": m27, "edit": MessageLookupByLibrary.simpleMessage("Redaguoti"), @@ -471,9 +580,13 @@ class MessageLookup extends MessageLookupByLibrary { "„Ente“ reikia leidimo išsaugoti jūsų nuotraukas"), "enteSubscriptionPitch": MessageLookupByLibrary.simpleMessage( "„Ente“ išsaugo jūsų prisiminimus, todėl jie visada bus pasiekiami, net jei prarasite įrenginį."), + "enteSubscriptionShareWithFamily": MessageLookupByLibrary.simpleMessage( + "Į planą galima pridėti ir savo šeimą."), "enterAlbumName": MessageLookupByLibrary.simpleMessage("Įveskite albumo pavadinimą"), "enterCode": MessageLookupByLibrary.simpleMessage("Įvesti kodą"), + "enterCodeDescription": MessageLookupByLibrary.simpleMessage( + "Įveskite draugo pateiktą kodą, kad gautumėte nemokamą saugyklą abiem."), "enterDateOfBirth": MessageLookupByLibrary.simpleMessage("Gimtadienis (neprivaloma)"), "enterEmail": @@ -512,14 +625,18 @@ class MessageLookup extends MessageLookupByLibrary { "Rastos papildomos nuotraukos"), "extraPhotosFoundFor": m30, "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), + "Veidas dar nesugrupuotas. Grįžkite vėliau."), "faceRecognition": MessageLookupByLibrary.simpleMessage("Veido atpažinimas"), "faces": MessageLookupByLibrary.simpleMessage("Veidai"), + "failedToApplyCode": + MessageLookupByLibrary.simpleMessage("Nepavyko pritaikyti kodo."), "failedToCancel": MessageLookupByLibrary.simpleMessage("Nepavyko atsisakyti"), "failedToFetchActiveSessions": MessageLookupByLibrary.simpleMessage( "Nepavyko gauti aktyvių seansų."), + "failedToFetchOriginalForEdit": MessageLookupByLibrary.simpleMessage( + "Nepavyko gauti originalo redagavimui."), "failedToPlayVideo": MessageLookupByLibrary.simpleMessage( "Nepavyko paleisti vaizdo įrašą. "), "failedToRefreshStripeSubscription": @@ -527,9 +644,11 @@ class MessageLookup extends MessageLookupByLibrary { "Nepavyko atnaujinti prenumeratos."), "failedToVerifyPaymentStatus": MessageLookupByLibrary.simpleMessage( "Nepavyko patvirtinti mokėjimo būsenos"), + "familyPlans": MessageLookupByLibrary.simpleMessage("Šeimos planai"), "faq": MessageLookupByLibrary.simpleMessage("DUK"), "faqs": MessageLookupByLibrary.simpleMessage("DUK"), "feedback": MessageLookupByLibrary.simpleMessage("Atsiliepimai"), + "file": MessageLookupByLibrary.simpleMessage("Failas"), "fileNotUploadedYet": MessageLookupByLibrary.simpleMessage("Failas dar neįkeltas."), "fileTypes": MessageLookupByLibrary.simpleMessage("Failų tipai"), @@ -546,6 +665,7 @@ class MessageLookup extends MessageLookupByLibrary { "Nemokamas bandomasis laikotarpis"), "freeTrialValidTill": m34, "freeUpAmount": m36, + "general": MessageLookupByLibrary.simpleMessage("Bendrieji"), "generatingEncryptionKeys": MessageLookupByLibrary.simpleMessage( "Generuojami šifravimo raktai..."), "genericProgress": m38, @@ -558,6 +678,10 @@ class MessageLookup extends MessageLookupByLibrary { "guestView": MessageLookupByLibrary.simpleMessage("Svečio peržiūra"), "guestViewEnablePreSteps": MessageLookupByLibrary.simpleMessage( "Kad įjungtumėte svečio peržiūrą, sistemos nustatymuose nustatykite įrenginio prieigos kodą arba ekrano užraktą."), + "hearUsExplanation": MessageLookupByLibrary.simpleMessage( + "Mes nesekame programų diegimų. Mums padėtų, jei pasakytumėte, kur mus radote."), + "hearUsWhereTitle": MessageLookupByLibrary.simpleMessage( + "Kaip išgirdote apie „Ente“? (nebūtina)"), "hidden": MessageLookupByLibrary.simpleMessage("Paslėpti"), "hide": MessageLookupByLibrary.simpleMessage("Slėpti"), "hideContent": MessageLookupByLibrary.simpleMessage("Slėpti turinį"), @@ -565,18 +689,25 @@ class MessageLookup extends MessageLookupByLibrary { "Paslepia programų turinį programų perjungiklyje ir išjungia ekrano kopijas"), "hideContentDescriptionIos": MessageLookupByLibrary.simpleMessage( "Paslepia programos turinį programos perjungiklyje"), + "hiding": MessageLookupByLibrary.simpleMessage("Slepiama..."), "howItWorks": MessageLookupByLibrary.simpleMessage("Kaip tai veikia"), "howToViewShareeVerificationID": MessageLookupByLibrary.simpleMessage( - "Paprašykite jų ilgai paspausti savo el. pašto adresą nustatymų ekrane ir patvirtinkite, kad abiejų įrenginių ID sutampa."), + "Paprašykite jų ilgai paspausti savo el. pašto adresą nustatymų ekrane ir patvirtinti, kad abiejų įrenginių ID sutampa."), "iOSGoToSettingsDescription": MessageLookupByLibrary.simpleMessage( "Biometrinis tapatybės nustatymas jūsų įrenginyje nenustatytas. Telefone įjunkite „Touch ID“ arba „Face ID“."), "iOSOkButton": MessageLookupByLibrary.simpleMessage("Gerai"), + "ignoreUpdate": MessageLookupByLibrary.simpleMessage("Ignoruoti"), + "ignored": MessageLookupByLibrary.simpleMessage("ignoruota"), "imageNotAnalyzed": MessageLookupByLibrary.simpleMessage("Vaizdas neanalizuotas."), "immediately": MessageLookupByLibrary.simpleMessage("Iš karto"), "importing": MessageLookupByLibrary.simpleMessage("Importuojama...."), + "incorrectCode": + MessageLookupByLibrary.simpleMessage("Neteisingas kodas."), "incorrectPasswordTitle": MessageLookupByLibrary.simpleMessage("Neteisingas slaptažodis"), + "incorrectRecoveryKey": + MessageLookupByLibrary.simpleMessage("Neteisingas atkūrimo raktas"), "incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage( "Įvestas atkūrimo raktas yra neteisingas."), "incorrectRecoveryKeyTitle": @@ -619,6 +750,8 @@ class MessageLookup extends MessageLookupByLibrary { "leave": MessageLookupByLibrary.simpleMessage("Palikti"), "leaveAlbum": MessageLookupByLibrary.simpleMessage("Palikti albumą"), "leaveFamily": MessageLookupByLibrary.simpleMessage("Palikti šeimą"), + "leaveSharedAlbum": + MessageLookupByLibrary.simpleMessage("Palikti bendrinamą albumą?"), "left": MessageLookupByLibrary.simpleMessage("Kairė"), "light": MessageLookupByLibrary.simpleMessage("Šviesi"), "lightTheme": MessageLookupByLibrary.simpleMessage("Šviesi"), @@ -626,9 +759,13 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Įrenginių riba"), "linkEnabled": MessageLookupByLibrary.simpleMessage("Įjungta"), "linkExpired": MessageLookupByLibrary.simpleMessage("Nebegalioja"), + "linkExpiresOn": m40, "linkExpiry": MessageLookupByLibrary.simpleMessage("Nuorodos galiojimo laikas"), + "linkHasExpired": + MessageLookupByLibrary.simpleMessage("Nuoroda nebegalioja"), "linkNeverExpires": MessageLookupByLibrary.simpleMessage("Niekada"), + "livePhotos": MessageLookupByLibrary.simpleMessage("Gyvos nuotraukos"), "loadMessage1": MessageLookupByLibrary.simpleMessage( "Galite bendrinti savo prenumeratą su šeima."), "loadMessage2": MessageLookupByLibrary.simpleMessage( @@ -667,12 +804,15 @@ class MessageLookup extends MessageLookupByLibrary { "locations": MessageLookupByLibrary.simpleMessage("Vietovės"), "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Užrakinti"), "logInLabel": MessageLookupByLibrary.simpleMessage("Prisijungti"), + "loggingOut": MessageLookupByLibrary.simpleMessage("Atsijungiama..."), "loginSessionExpired": MessageLookupByLibrary.simpleMessage("Seansas baigėsi"), "loginSessionExpiredDetails": MessageLookupByLibrary.simpleMessage( "Jūsų seansas baigėsi. Prisijunkite iš naujo."), "loginTerms": MessageLookupByLibrary.simpleMessage( "Spustelėjus Prisijungti sutinku su paslaugų sąlygomis ir privatumo politika"), + "loginWithTOTP": + MessageLookupByLibrary.simpleMessage("Prisijungti su TOTP"), "logout": MessageLookupByLibrary.simpleMessage("Atsijungti"), "longPressAnEmailToVerifyEndToEndEncryption": MessageLookupByLibrary.simpleMessage( @@ -689,6 +829,10 @@ class MessageLookup extends MessageLookupByLibrary { "magicSearchHint": MessageLookupByLibrary.simpleMessage( "Magiška paieška leidžia ieškoti nuotraukų pagal jų turinį, pvz., „gėlė“, „raudonas automobilis“, „tapatybės dokumentai“"), "manage": MessageLookupByLibrary.simpleMessage("Tvarkyti"), + "manageDeviceStorage": + MessageLookupByLibrary.simpleMessage("Tvarkyti įrenginio podėlį"), + "manageDeviceStorageDesc": MessageLookupByLibrary.simpleMessage( + "Peržiūrėkite ir išvalykite vietinę podėlį."), "manageFamily": MessageLookupByLibrary.simpleMessage("Tvarkyti šeimą"), "manageLink": MessageLookupByLibrary.simpleMessage("Tvarkyti nuorodą"), "manageParticipants": MessageLookupByLibrary.simpleMessage("Tvarkyti"), @@ -719,6 +863,7 @@ class MessageLookup extends MessageLookupByLibrary { "Mobiliuosiuose, internete ir darbalaukyje"), "moderateStrength": MessageLookupByLibrary.simpleMessage("Vidutinė"), "moments": MessageLookupByLibrary.simpleMessage("Akimirkos"), + "month": MessageLookupByLibrary.simpleMessage("mėnesis"), "monthly": MessageLookupByLibrary.simpleMessage("Mėnesinis"), "moreDetails": MessageLookupByLibrary.simpleMessage( "Daugiau išsamios informacijos"), @@ -736,6 +881,7 @@ class MessageLookup extends MessageLookupByLibrary { "Nepavyksta prisijungti prie „Ente“. Patikrinkite tinklo nustatymus ir susisiekite su palaikymo komanda, jei klaida tęsiasi."), "never": MessageLookupByLibrary.simpleMessage("Niekada"), "newAlbum": MessageLookupByLibrary.simpleMessage("Naujas albumas"), + "newLocation": MessageLookupByLibrary.simpleMessage("Nauja vietovė"), "newPerson": MessageLookupByLibrary.simpleMessage("Naujas asmuo"), "newToEnte": MessageLookupByLibrary.simpleMessage("Naujas platformoje „Ente“"), @@ -745,6 +891,8 @@ class MessageLookup extends MessageLookupByLibrary { "noDeviceFound": MessageLookupByLibrary.simpleMessage("Įrenginys nerastas"), "noDeviceLimit": MessageLookupByLibrary.simpleMessage("Jokio"), + "noDuplicates": + MessageLookupByLibrary.simpleMessage("✨ Dublikatų nėra"), "noExifData": MessageLookupByLibrary.simpleMessage("Nėra EXIF duomenų"), "noFacesFound": MessageLookupByLibrary.simpleMessage("Nerasta veidų."), "noImagesWithLocation": @@ -760,9 +908,15 @@ class MessageLookup extends MessageLookupByLibrary { "noResults": MessageLookupByLibrary.simpleMessage("Rezultatų nėra."), "noResultsFound": MessageLookupByLibrary.simpleMessage("Rezultatų nerasta."), + "noSuggestionsForPerson": m43, "noSystemLockFound": MessageLookupByLibrary.simpleMessage("Nerastas sistemos užraktas"), "notPersonLabel": m44, + "nothingSharedWithYouYet": MessageLookupByLibrary.simpleMessage( + "Kol kas su jumis niekuo nesibendrinama."), + "nothingToSeeHere": MessageLookupByLibrary.simpleMessage( + "Čia nėra nieko, ką pamatyti. 👀"), + "notifications": MessageLookupByLibrary.simpleMessage("Pranešimai"), "ok": MessageLookupByLibrary.simpleMessage("Gerai"), "onDevice": MessageLookupByLibrary.simpleMessage("Įrenginyje"), "onEnte": MessageLookupByLibrary.simpleMessage( @@ -770,10 +924,10 @@ class MessageLookup extends MessageLookupByLibrary { "onlyThem": MessageLookupByLibrary.simpleMessage("Tik jiems"), "oops": MessageLookupByLibrary.simpleMessage("Ups"), "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), + MessageLookupByLibrary.simpleMessage("Atverti albumą naršyklėje"), "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), + "Naudokite interneto programą, kad pridėtumėte nuotraukų į šį albumą."), + "openFile": MessageLookupByLibrary.simpleMessage("Atverti failą"), "optionalAsShortAsYouLike": MessageLookupByLibrary.simpleMessage( "Nebūtina, trumpai, kaip jums patinka..."), "orMergeWithExistingPerson": @@ -787,9 +941,9 @@ class MessageLookup extends MessageLookupByLibrary { "panorama": MessageLookupByLibrary.simpleMessage("Panorama"), "passKeyPendingVerification": MessageLookupByLibrary.simpleMessage( "Vis dar laukiama patvirtinimo"), - "passkey": MessageLookupByLibrary.simpleMessage("Slaptaraktas"), + "passkey": MessageLookupByLibrary.simpleMessage("Slaptaraktis"), "passkeyAuthTitle": - MessageLookupByLibrary.simpleMessage("Slaptarakto patvirtinimas"), + MessageLookupByLibrary.simpleMessage("Slaptarakčio patvirtinimas"), "password": MessageLookupByLibrary.simpleMessage("Slaptažodis"), "passwordChangedSuccessfully": MessageLookupByLibrary.simpleMessage( "Slaptažodis sėkmingai pakeistas"), @@ -812,13 +966,17 @@ class MessageLookup extends MessageLookupByLibrary { "pendingSync": MessageLookupByLibrary.simpleMessage("Laukiama sinchronizacija"), "people": MessageLookupByLibrary.simpleMessage("Asmenys"), + "permanentlyDelete": + MessageLookupByLibrary.simpleMessage("Ištrinti negrįžtamai"), "permanentlyDeleteFromDevice": MessageLookupByLibrary.simpleMessage( "Ištrinti negrįžtamai iš įrenginio?"), "personName": MessageLookupByLibrary.simpleMessage("Asmens vardas"), "photoSmallCase": MessageLookupByLibrary.simpleMessage("nuotrauka"), + "photos": MessageLookupByLibrary.simpleMessage("Nuotraukos"), "photosAddedByYouWillBeRemovedFromTheAlbum": MessageLookupByLibrary.simpleMessage( "Jūsų pridėtos nuotraukos bus pašalintos iš albumo"), + "photosCount": m47, "pinAlbum": MessageLookupByLibrary.simpleMessage("Prisegti albumą"), "pinLock": MessageLookupByLibrary.simpleMessage("PIN užrakinimas"), "playOnTv": MessageLookupByLibrary.simpleMessage( @@ -841,6 +999,8 @@ class MessageLookup extends MessageLookupByLibrary { "pleaseWait": MessageLookupByLibrary.simpleMessage("Palaukite..."), "pressAndHoldToPlayVideo": MessageLookupByLibrary.simpleMessage( "Paspauskite ir palaikykite, kad paleistumėte vaizdo įrašą"), + "pressAndHoldToPlayVideoDetailed": MessageLookupByLibrary.simpleMessage( + "Paspauskite ir palaikykite vaizdą, kad paleistumėte vaizdo įrašą"), "privacy": MessageLookupByLibrary.simpleMessage("Privatumas"), "privacyPolicyTitle": MessageLookupByLibrary.simpleMessage("Privatumo politika"), @@ -848,10 +1008,13 @@ class MessageLookup extends MessageLookupByLibrary { "Privačios atsarginės kopijos"), "privateSharing": MessageLookupByLibrary.simpleMessage("Privatus bendrinimas"), - "processingImport": m50, + "processingImport": m51, + "publicLinkEnabled": + MessageLookupByLibrary.simpleMessage("Įjungta viešoji nuoroda"), "raiseTicket": MessageLookupByLibrary.simpleMessage("Sukurti paraišką"), "rateTheApp": MessageLookupByLibrary.simpleMessage("Vertinti programą"), - "rateUsOnStore": m51, + "rateUs": MessageLookupByLibrary.simpleMessage("Vertinti mus"), + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("Atkurti"), "recoverAccount": MessageLookupByLibrary.simpleMessage("Atkurti paskyrą"), @@ -880,6 +1043,15 @@ class MessageLookup extends MessageLookupByLibrary { "Įveskite slaptažodį iš naujo"), "reenterPin": MessageLookupByLibrary.simpleMessage("Įveskite PIN iš naujo"), + "referFriendsAnd2xYourPlan": MessageLookupByLibrary.simpleMessage( + "Rekomenduokite draugams ir 2 kartus padidinkite savo planą"), + "referralStep1": MessageLookupByLibrary.simpleMessage( + "1. Duokite šį kodą savo draugams"), + "referralStep2": MessageLookupByLibrary.simpleMessage( + "2. Jie užsiregistruoja mokamą planą"), + "referralStep3": m53, + "remindToEmptyDeviceTrash": MessageLookupByLibrary.simpleMessage( + "Taip pat ištuštinkite Neseniai ištrinti iš Nustatymai -> Saugykla, kad atlaisvintumėte vietos."), "remoteImages": MessageLookupByLibrary.simpleMessage("Nuotoliniai vaizdai"), "remoteThumbnails": @@ -898,7 +1070,7 @@ class MessageLookup extends MessageLookupByLibrary { "removeLink": MessageLookupByLibrary.simpleMessage("Šalinti nuorodą"), "removeParticipant": MessageLookupByLibrary.simpleMessage("Šalinti dalyvį"), - "removeParticipantBody": m53, + "removeParticipantBody": m54, "removePersonLabel": MessageLookupByLibrary.simpleMessage("Šalinti asmens žymą"), "removePublicLink": @@ -913,6 +1085,7 @@ class MessageLookup extends MessageLookupByLibrary { "renameFile": MessageLookupByLibrary.simpleMessage("Pervadinti failą"), "renewSubscription": MessageLookupByLibrary.simpleMessage("Atnaujinti prenumeratą"), + "renewsOn": m55, "reportABug": MessageLookupByLibrary.simpleMessage("Pranešti apie riktą"), "reportBug": @@ -926,6 +1099,8 @@ class MessageLookup extends MessageLookupByLibrary { "restore": MessageLookupByLibrary.simpleMessage("Atkurti"), "restoreToAlbum": MessageLookupByLibrary.simpleMessage("Atkurti į albumą"), + "restoringFiles": + MessageLookupByLibrary.simpleMessage("Atkuriami failai..."), "reviewDeduplicateItems": MessageLookupByLibrary.simpleMessage( "Peržiūrėkite ir ištrinkite elementus, kurie, jūsų manymu, yra dublikatai."), "reviewSuggestions": @@ -954,35 +1129,52 @@ class MessageLookup extends MessageLookupByLibrary { "searchHint3": MessageLookupByLibrary.simpleMessage( "Albumai, failų pavadinimai ir tipai"), "searchHint4": MessageLookupByLibrary.simpleMessage("Vietovė"), + "searchHint5": MessageLookupByLibrary.simpleMessage( + "Jau netrukus: veidų ir magiškos paieškos ✨"), "searchLocationEmptySection": MessageLookupByLibrary.simpleMessage( "Grupės nuotraukos, kurios padarytos tam tikru spinduliu nuo nuotraukos"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Pakvieskite asmenis ir čia matysite visas jų bendrinamas nuotraukas."), "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( "Asmenys bus rodomi čia, kai bus užbaigtas apdorojimas"), - "searchResultCount": m55, + "searchResultCount": m56, + "searchSectionsLengthMismatch": m57, + "security": MessageLookupByLibrary.simpleMessage("Saugumas"), "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), + "Žiūrėti viešų albumų nuorodas programoje"), "selectALocation": MessageLookupByLibrary.simpleMessage("Pasirinkite vietovę"), "selectALocationFirst": MessageLookupByLibrary.simpleMessage( "Pirmiausia pasirinkite vietovę"), + "selectAll": MessageLookupByLibrary.simpleMessage("Pasirinkti viską"), + "selectAllShort": MessageLookupByLibrary.simpleMessage("Viskas"), + "selectCoverPhoto": MessageLookupByLibrary.simpleMessage( + "Pasirinkite viršelio nuotrauką"), + "selectFoldersForBackup": MessageLookupByLibrary.simpleMessage( + "Pasirinkite aplankus atsarginėms kopijoms kurti"), "selectLanguage": MessageLookupByLibrary.simpleMessage("Pasirinkite kalbą"), + "selectMailApp": + MessageLookupByLibrary.simpleMessage("Pasirinkti pašto programą"), "selectReason": MessageLookupByLibrary.simpleMessage("Pasirinkite priežastį"), "selectYourPlan": MessageLookupByLibrary.simpleMessage("Pasirinkite planą"), "selectedFilesAreNotOnEnte": MessageLookupByLibrary.simpleMessage( "Pasirinkti failai nėra platformoje „Ente“"), + "selectedFoldersWillBeEncryptedAndBackedUp": + MessageLookupByLibrary.simpleMessage( + "Pasirinkti aplankai bus užšifruoti ir sukurtos atsarginės kopijos."), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "send": MessageLookupByLibrary.simpleMessage("Siųsti"), "sendEmail": MessageLookupByLibrary.simpleMessage("Siųsti el. laišką"), "sendInvite": MessageLookupByLibrary.simpleMessage("Siųsti kvietimą"), "sendLink": MessageLookupByLibrary.simpleMessage("Siųsti nuorodą"), "serverEndpoint": MessageLookupByLibrary.simpleMessage("Serverio galutinis taškas"), + "sessionExpired": + MessageLookupByLibrary.simpleMessage("Seansas baigėsi"), "sessionIdMismatch": MessageLookupByLibrary.simpleMessage("Seanso ID nesutampa."), "setAPassword": @@ -998,11 +1190,14 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Nustatyti slaptažodį"), "setupComplete": MessageLookupByLibrary.simpleMessage("Sąranka baigta"), "share": MessageLookupByLibrary.simpleMessage("Bendrinti"), + "shareALink": + MessageLookupByLibrary.simpleMessage("Bendrinkite nuorodą"), "shareAlbumHint": MessageLookupByLibrary.simpleMessage( "Atidarykite albumą ir palieskite bendrinimo mygtuką viršuje dešinėje, kad bendrintumėte."), "shareAnAlbumNow": MessageLookupByLibrary.simpleMessage("Bendrinti albumą dabar"), - "shareMyVerificationID": m58, + "shareLink": MessageLookupByLibrary.simpleMessage("Bendrinti nuorodą"), + "shareMyVerificationID": m59, "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage( "Bendrinkite tik su tais asmenimis, su kuriais norite"), "shareTextConfirmOthersVerificationID": m5, @@ -1010,16 +1205,29 @@ class MessageLookup extends MessageLookupByLibrary { "Atsisiųskite „Ente“, kad galėtume lengvai bendrinti originalios kokybės nuotraukas ir vaizdo įrašus.\n\nhttps://ente.io"), "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage( "Bendrinkite su ne „Ente“ naudotojais."), + "shareWithPeopleSectionTitle": m61, "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( "Sukurkite bendrinamus ir bendradarbiaujamus albumus su kitais „Ente“ naudotojais, įskaitant naudotojus nemokamuose planuose."), + "sharedByYou": + MessageLookupByLibrary.simpleMessage("Bendrinta iš jūsų"), + "sharedPhotoNotifications": MessageLookupByLibrary.simpleMessage( + "Naujos bendrintos nuotraukos"), + "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( + "Gaukite pranešimus, kai kas nors prideda nuotrauką į bendrinamą albumą, kuriame dalyvaujate."), + "sharedWithMe": + MessageLookupByLibrary.simpleMessage("Bendrinta su manimi"), + "sharedWithYou": + MessageLookupByLibrary.simpleMessage("Bendrinta su jumis"), "sharing": MessageLookupByLibrary.simpleMessage("Bendrinima..."), + "showMemories": + MessageLookupByLibrary.simpleMessage("Rodyti prisiminimus"), "showPerson": MessageLookupByLibrary.simpleMessage("Rodyti asmenį"), "signOutOtherBody": MessageLookupByLibrary.simpleMessage( "Jei manote, kad kas nors gali žinoti jūsų slaptažodį, galite priverstinai atsijungti iš visų kitų įrenginių, naudojančių jūsų paskyrą."), "signUpTerms": MessageLookupByLibrary.simpleMessage( "Sutinku su paslaugų sąlygomis ir privatumo politika"), - "singleFileInBothLocalAndRemote": m63, - "singleFileInRemoteOnly": m64, + "singleFileInBothLocalAndRemote": m64, + "singleFileInRemoteOnly": m65, "skip": MessageLookupByLibrary.simpleMessage("Praleisti"), "social": MessageLookupByLibrary.simpleMessage("Socialinės"), "someoneSharingAlbumsWithYouShouldSeeTheSameId": @@ -1056,15 +1264,20 @@ class MessageLookup extends MessageLookupByLibrary { "storageLimitExceeded": MessageLookupByLibrary.simpleMessage("Viršyta saugyklos riba."), "strongStrength": MessageLookupByLibrary.simpleMessage("Stipri"), - "subAlreadyLinkedErrMessage": m66, + "subAlreadyLinkedErrMessage": m67, "subscribe": MessageLookupByLibrary.simpleMessage("Prenumeruoti"), "subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage( "Kad įjungtumėte bendrinimą, reikia aktyvios mokamos prenumeratos."), "subscription": MessageLookupByLibrary.simpleMessage("Prenumerata"), + "success": MessageLookupByLibrary.simpleMessage("Sėkmė"), + "successfullyArchived": + MessageLookupByLibrary.simpleMessage("Sėkmingai suarchyvuota"), + "successfullyUnarchived": + MessageLookupByLibrary.simpleMessage("Sėkmingai išarchyvuota"), "suggestFeatures": MessageLookupByLibrary.simpleMessage("Siūlyti funkcijas"), "support": MessageLookupByLibrary.simpleMessage("Palaikymas"), - "syncProgress": m68, + "syncProgress": m69, "syncStopped": MessageLookupByLibrary.simpleMessage( "Sinchronizavimas sustabdytas"), "syncing": MessageLookupByLibrary.simpleMessage("Sinchronizuojama..."), @@ -1077,7 +1290,7 @@ class MessageLookup extends MessageLookupByLibrary { "Palieskite, kad atrakintumėte"), "tapToUpload": MessageLookupByLibrary.simpleMessage("Palieskite, kad įkeltumėte"), - "tapToUploadIsIgnoredDue": m69, + "tapToUploadIsIgnoredDue": m70, "tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage( "Atrodo, kad kažkas nutiko ne taip. Bandykite dar kartą po kurio laiko. Jei klaida tęsiasi, susisiekite su mūsų palaikymo komanda."), "terminate": MessageLookupByLibrary.simpleMessage("Baigti"), @@ -1088,15 +1301,20 @@ class MessageLookup extends MessageLookupByLibrary { "thankYou": MessageLookupByLibrary.simpleMessage("Dėkojame"), "theLinkYouAreTryingToAccessHasExpired": MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), + "Nuoroda, kurią bandote pasiekti, nebegalioja."), + "theRecoveryKeyYouEnteredIsIncorrect": + MessageLookupByLibrary.simpleMessage( + "Įvestas atkūrimo raktas yra neteisingas."), "theme": MessageLookupByLibrary.simpleMessage("Tema"), "thisCanBeUsedToRecoverYourAccountIfYou": MessageLookupByLibrary.simpleMessage( "Tai gali būti naudojama paskyrai atkurti, jei prarandate dvigubo tapatybės nustatymą"), "thisDevice": MessageLookupByLibrary.simpleMessage("Šis įrenginys"), + "thisEmailIsAlreadyInUse": MessageLookupByLibrary.simpleMessage( + "Šis el. paštas jau naudojamas."), "thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage( "Šis vaizdas neturi Exif duomenų"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage("Tai – jūsų patvirtinimo ID"), "thisWillLogYouOutOfTheFollowingDevice": @@ -1118,25 +1336,38 @@ class MessageLookup extends MessageLookupByLibrary { "Per daug neteisingų bandymų."), "total": MessageLookupByLibrary.simpleMessage("iš viso"), "trash": MessageLookupByLibrary.simpleMessage("Šiukšlinė"), + "trashDaysLeft": m73, "trim": MessageLookupByLibrary.simpleMessage("Trumpinti"), "tryAgain": MessageLookupByLibrary.simpleMessage("Bandyti dar kartą"), "twitter": MessageLookupByLibrary.simpleMessage("„Twitter“"), "twoMonthsFreeOnYearlyPlans": MessageLookupByLibrary.simpleMessage( "2 mėnesiai nemokamai metiniuose planuose"), + "twofactor": MessageLookupByLibrary.simpleMessage( + "Dvigubas tapatybės nustatymas"), "twofactorAuthenticationPageTitle": MessageLookupByLibrary.simpleMessage( "Dvigubas tapatybės nustatymas"), "twofactorSetup": MessageLookupByLibrary.simpleMessage( "Dvigubo tapatybės nustatymo sąranka"), - "typeOfGallerGallerytypeIsNotSupportedForRename": m73, + "typeOfGallerGallerytypeIsNotSupportedForRename": m74, + "unarchive": MessageLookupByLibrary.simpleMessage("Išarchyvuoti"), + "unarchiveAlbum": + MessageLookupByLibrary.simpleMessage("Išarchyvuoti albumą"), + "unarchiving": + MessageLookupByLibrary.simpleMessage("Išarchyvuojama..."), "unavailableReferralCode": MessageLookupByLibrary.simpleMessage( "Atsiprašome, šis kodas nepasiekiamas."), "uncategorized": MessageLookupByLibrary.simpleMessage("Nekategorizuoti"), "unlock": MessageLookupByLibrary.simpleMessage("Atrakinti"), "unpinAlbum": MessageLookupByLibrary.simpleMessage("Atsegti albumą"), + "unselectAll": MessageLookupByLibrary.simpleMessage("Nesirinkti visų"), + "updatingFolderSelection": MessageLookupByLibrary.simpleMessage( + "Atnaujinamas aplankų pasirinkimas..."), "upgrade": MessageLookupByLibrary.simpleMessage("Keisti planą"), - "uploadIsIgnoredDueToIgnorereason": m74, + "uploadIsIgnoredDueToIgnorereason": m75, + "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( + "Iki 50% nuolaida, gruodžio 4 d."), "useAsCover": MessageLookupByLibrary.simpleMessage("Naudoti kaip viršelį"), "usePublicLinksForPeopleNotOnEnte": MessageLookupByLibrary.simpleMessage( @@ -1144,7 +1375,7 @@ class MessageLookup extends MessageLookupByLibrary { "useRecoveryKey": MessageLookupByLibrary.simpleMessage("Naudoti atkūrimo raktą"), "usedSpace": MessageLookupByLibrary.simpleMessage("Naudojama vieta"), - "validTill": m76, + "validTill": m77, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "Patvirtinimas nepavyko. Bandykite dar kartą."), @@ -1153,10 +1384,10 @@ class MessageLookup extends MessageLookupByLibrary { "verify": MessageLookupByLibrary.simpleMessage("Patvirtinti"), "verifyEmail": MessageLookupByLibrary.simpleMessage("Patvirtinti el. paštą"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("Patvirtinti"), "verifyPasskey": - MessageLookupByLibrary.simpleMessage("Patvirtinti slaptaraktą"), + MessageLookupByLibrary.simpleMessage("Patvirtinti slaptaraktį"), "verifyPassword": MessageLookupByLibrary.simpleMessage("Patvirtinkite slaptažodį"), "verifying": MessageLookupByLibrary.simpleMessage("Patvirtinama..."), @@ -1165,6 +1396,7 @@ class MessageLookup extends MessageLookupByLibrary { "videoInfo": MessageLookupByLibrary.simpleMessage("Vaizdo įrašo informacija"), "videoSmallCase": MessageLookupByLibrary.simpleMessage("vaizdo įrašas"), + "videos": MessageLookupByLibrary.simpleMessage("Vaizdo įrašai"), "viewAddOnButton": MessageLookupByLibrary.simpleMessage("Peržiūrėti priedus"), "viewAll": MessageLookupByLibrary.simpleMessage("Peržiūrėti viską"), @@ -1172,18 +1404,23 @@ class MessageLookup extends MessageLookupByLibrary { "viewRecoveryKey": MessageLookupByLibrary.simpleMessage("Peržiūrėti atkūrimo raktą"), "viewer": MessageLookupByLibrary.simpleMessage("Žiūrėtojas"), + "viewersSuccessfullyAdded": m79, "visitWebToManage": MessageLookupByLibrary.simpleMessage( "Aplankykite web.ente.io, kad tvarkytumėte savo prenumeratą"), "waitingForVerification": MessageLookupByLibrary.simpleMessage("Laukiama patvirtinimo..."), "weAreOpenSource": MessageLookupByLibrary.simpleMessage("Esame atviro kodo!"), + "weDontSupportEditingPhotosAndAlbumsThatYouDont": + MessageLookupByLibrary.simpleMessage( + "Nepalaikome nuotraukų ir albumų redagavimo, kurių dar neturite."), "weHaveSendEmailTo": m2, "weakStrength": MessageLookupByLibrary.simpleMessage("Silpna"), "welcomeBack": MessageLookupByLibrary.simpleMessage("Sveiki sugrįžę!"), "whatsNew": MessageLookupByLibrary.simpleMessage("Kas naujo"), + "yearShort": MessageLookupByLibrary.simpleMessage("m."), "yearly": MessageLookupByLibrary.simpleMessage("Metinis"), - "yearsAgo": m79, + "yearsAgo": m80, "yes": MessageLookupByLibrary.simpleMessage("Taip"), "yesCancel": MessageLookupByLibrary.simpleMessage("Taip, atsisakyti"), "yesConvertToViewer": @@ -1196,8 +1433,14 @@ class MessageLookup extends MessageLookupByLibrary { "you": MessageLookupByLibrary.simpleMessage("Jūs"), "youAreOnTheLatestVersion": MessageLookupByLibrary.simpleMessage("Esate naujausioje versijoje"), + "youCanAtMaxDoubleYourStorage": MessageLookupByLibrary.simpleMessage( + "* Galite daugiausiai padvigubinti savo saugyklą."), "youCannotDowngradeToThisPlan": MessageLookupByLibrary.simpleMessage( "Negalite pakeisti į šį planą"), + "youCannotShareWithYourself": MessageLookupByLibrary.simpleMessage( + "Negalite bendrinti su savimi."), + "youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage( + "Neturite jokių archyvuotų elementų."), "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage("Jūsų paskyra ištrinta"), "yourMap": MessageLookupByLibrary.simpleMessage("Jūsų žemėlapis"), @@ -1207,7 +1450,7 @@ class MessageLookup extends MessageLookupByLibrary { "yourSubscriptionHasExpired": MessageLookupByLibrary.simpleMessage("Jūsų prenumerata baigėsi."), "yourVerificationCodeHasExpired": MessageLookupByLibrary.simpleMessage( - "Jūsų patvirtinimo kodo laikas nebegaliojantis."), + "Jūsų patvirtinimo kodas nebegaliojantis."), "youveNoDuplicateFilesThatCanBeCleared": MessageLookupByLibrary.simpleMessage( "Neturite dubliuotų failų, kuriuos būtų galima išvalyti") diff --git a/mobile/lib/generated/intl/messages_ml.dart b/mobile/lib/generated/intl/messages_ml.dart new file mode 100644 index 0000000000..67826830c1 --- /dev/null +++ b/mobile/lib/generated/intl/messages_ml.dart @@ -0,0 +1,25 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a ml locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'ml'; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => {}; +} diff --git a/mobile/lib/generated/intl/messages_nl.dart b/mobile/lib/generated/intl/messages_nl.dart index 60521812b3..d23b55d91a 100644 --- a/mobile/lib/generated/intl/messages_nl.dart +++ b/mobile/lib/generated/intl/messages_nl.dart @@ -61,7 +61,7 @@ class MessageLookup extends MessageLookupByLibrary { static String m18(albumName) => "Gezamenlijke link aangemaakt voor ${albumName}"; - static String m81(count) => + static String m82(count) => "${Intl.plural(count, zero: '0 samenwerkers toegevoegd', one: '1 samenwerker toegevoegd', other: '${count} samenwerkers toegevoegd')}"; static String m19(familyAdminEmail) => @@ -145,110 +145,110 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(providerName) => "Praat met ${providerName} klantenservice als u in rekening bent gebracht"; - static String m82(count) => + static String m47(count) => "${Intl.plural(count, zero: '0 foto\'s', one: '1 foto', other: '${count} foto\'s')}"; - static String m47(endDate) => + static String m48(endDate) => "Gratis proefperiode geldig tot ${endDate}.\nU kunt naderhand een betaald abonnement kiezen."; - static String m48(toEmail) => "Stuur ons een e-mail op ${toEmail}"; + static String m49(toEmail) => "Stuur ons een e-mail op ${toEmail}"; - static String m49(toEmail) => + static String m50(toEmail) => "Verstuur de logboeken alstublieft naar ${toEmail}"; - static String m50(folderName) => "Verwerken van ${folderName}..."; + static String m51(folderName) => "Verwerken van ${folderName}..."; - static String m51(storeName) => "Beoordeel ons op ${storeName}"; + static String m52(storeName) => "Beoordeel ons op ${storeName}"; - static String m52(storageInGB) => + static String m53(storageInGB) => "Jullie krijgen allebei ${storageInGB} GB* gratis"; - static String m53(userEmail) => + static String m54(userEmail) => "${userEmail} zal worden verwijderd uit dit gedeelde album\n\nAlle door hen toegevoegde foto\'s worden ook uit het album verwijderd"; - static String m54(endDate) => "Wordt verlengd op ${endDate}"; + static String m55(endDate) => "Wordt verlengd op ${endDate}"; - static String m55(count) => + static String m56(count) => "${Intl.plural(count, one: '${count} resultaat gevonden', other: '${count} resultaten gevonden')}"; - static String m56(snapshotLenght, searchLenght) => + static String m57(snapshotLenght, searchLenght) => "Lengte van secties komt niet overeen: ${snapshotLenght} != ${searchLenght}"; static String m4(count) => "${count} geselecteerd"; - static String m57(count, yourCount) => + static String m58(count, yourCount) => "${count} geselecteerd (${yourCount} van jou)"; - static String m58(verificationID) => + static String m59(verificationID) => "Hier is mijn verificatie-ID: ${verificationID} voor ente.io."; static String m5(verificationID) => "Hey, kunt u bevestigen dat dit uw ente.io verificatie-ID is: ${verificationID}"; - static String m59(referralCode, referralStorageInGB) => + static String m60(referralCode, referralStorageInGB) => "Ente verwijzingscode: ${referralCode} \n\nPas het toe bij Instellingen → Algemeen → Verwijzingen om ${referralStorageInGB} GB gratis te krijgen nadat je je hebt aangemeld voor een betaald abonnement\n\nhttps://ente.io"; - static String m60(numberOfPeople) => + static String m61(numberOfPeople) => "${Intl.plural(numberOfPeople, zero: 'Deel met specifieke mensen', one: 'Gedeeld met 1 persoon', other: 'Gedeeld met ${numberOfPeople} mensen')}"; - static String m61(emailIDs) => "Gedeeld met ${emailIDs}"; - - static String m62(fileType) => - "Deze ${fileType} zal worden verwijderd van jouw apparaat."; + static String m62(emailIDs) => "Gedeeld met ${emailIDs}"; static String m63(fileType) => - "Deze ${fileType} staat zowel in Ente als op jouw apparaat."; + "Deze ${fileType} zal worden verwijderd van jouw apparaat."; static String m64(fileType) => + "Deze ${fileType} staat zowel in Ente als op jouw apparaat."; + + static String m65(fileType) => "Deze ${fileType} zal worden verwijderd uit Ente."; static String m1(storageAmountInGB) => "${storageAmountInGB} GB"; - static String m65( + static String m66( usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => "${usedAmount} ${usedStorageUnit} van ${totalAmount} ${totalStorageUnit} gebruikt"; - static String m66(id) => + static String m67(id) => "Jouw ${id} is al aan een ander Ente account gekoppeld.\nAls je jouw ${id} wilt gebruiken met dit account, neem dan contact op met onze klantenservice"; - static String m67(endDate) => "Uw abonnement loopt af op ${endDate}"; + static String m68(endDate) => "Uw abonnement loopt af op ${endDate}"; - static String m68(completed, total) => + static String m69(completed, total) => "${completed}/${total} herinneringen bewaard"; - static String m69(ignoreReason) => + static String m70(ignoreReason) => "Tik om te uploaden, upload wordt momenteel genegeerd vanwege ${ignoreReason}"; - static String m70(storageAmountInGB) => + static String m71(storageAmountInGB) => "Zij krijgen ook ${storageAmountInGB} GB"; - static String m71(email) => "Dit is de verificatie-ID van ${email}"; + static String m72(email) => "Dit is de verificatie-ID van ${email}"; - static String m72(count) => - "${Intl.plural(count, zero: '', one: '1 dag', other: '${count} dagen')}"; + static String m73(count) => + "${Intl.plural(count, zero: 'Binnenkort', one: '1 dag', other: '${count} dagen')}"; - static String m73(galleryType) => + static String m74(galleryType) => "Galerijtype ${galleryType} wordt niet ondersteund voor hernoemen"; - static String m74(ignoreReason) => + static String m75(ignoreReason) => "Upload wordt genegeerd omdat ${ignoreReason}"; - static String m75(count) => "${count} herinneringen veiligstellen..."; + static String m76(count) => "${count} herinneringen veiligstellen..."; - static String m76(endDate) => "Geldig tot ${endDate}"; + static String m77(endDate) => "Geldig tot ${endDate}"; - static String m77(email) => "Verifieer ${email}"; + static String m78(email) => "Verifieer ${email}"; - static String m78(count) => + static String m79(count) => "${Intl.plural(count, zero: '0 kijkers toegevoegd', one: '1 kijker toegevoegd', other: '${count} kijkers toegevoegd')}"; static String m2(email) => "We hebben een e-mail gestuurd naar ${email}"; - static String m79(count) => + static String m80(count) => "${Intl.plural(count, one: '${count} jaar geleden', other: '${count} jaar geleden')}"; - static String m80(storageSaved) => + static String m81(storageSaved) => "Je hebt ${storageSaved} succesvol vrijgemaakt!"; final messages = _notInlinedMessages(_notInlinedMessages); @@ -325,13 +325,10 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Alle herinneringen bewaard"), "allPersonGroupingWillReset": MessageLookupByLibrary.simpleMessage( "Alle groepen voor deze persoon worden gereset, en je verliest alle suggesties die voor deze persoon zijn gedaan"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "Sta toe dat mensen met de link ook foto\'s kunnen toevoegen aan het gedeelde album."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage("Foto\'s toevoegen toestaan"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "allowDownloads": MessageLookupByLibrary.simpleMessage("Downloads toestaan"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( @@ -445,7 +442,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Back-up mappen"), "backup": MessageLookupByLibrary.simpleMessage("Back-up"), "backupFailed": MessageLookupByLibrary.simpleMessage("Back-up mislukt"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "backupOverMobileData": MessageLookupByLibrary.simpleMessage( "Back-up maken via mobiele data"), "backupSettings": @@ -556,7 +552,7 @@ class MessageLookup extends MessageLookupByLibrary { "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": MessageLookupByLibrary.simpleMessage( "Samenwerkers kunnen foto\'s en video\'s toevoegen aan het gedeelde album."), - "collaboratorsSuccessfullyAdded": m81, + "collaboratorsSuccessfullyAdded": m82, "collageLayout": MessageLookupByLibrary.simpleMessage("Layout"), "collageSaved": MessageLookupByLibrary.simpleMessage( "Collage opgeslagen in gallerij"), @@ -861,8 +857,6 @@ class MessageLookup extends MessageLookupByLibrary { "extraPhotosFound": MessageLookupByLibrary.simpleMessage("Extra foto\'s gevonden"), "extraPhotosFoundFor": m30, - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "faceRecognition": MessageLookupByLibrary.simpleMessage("Gezichtsherkenning"), "faces": MessageLookupByLibrary.simpleMessage("Gezichten"), @@ -1132,8 +1126,6 @@ class MessageLookup extends MessageLookupByLibrary { "magicSearchHint": MessageLookupByLibrary.simpleMessage( "Magisch zoeken maakt het mogelijk om foto\'s op hun inhoud worden gezocht, bijvoorbeeld \"bloem\", \"rode auto\", \"identiteitsdocumenten\""), "manage": MessageLookupByLibrary.simpleMessage("Beheren"), - "manageDeviceStorage": - MessageLookupByLibrary.simpleMessage("Apparaatopslag beheren"), "manageFamily": MessageLookupByLibrary.simpleMessage("Familie abonnement beheren"), "manageLink": MessageLookupByLibrary.simpleMessage("Beheer link"), @@ -1253,11 +1245,6 @@ class MessageLookup extends MessageLookupByLibrary { "Oeps, kon bewerkingen niet opslaan"), "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage("Oeps, er is iets misgegaan"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "openSettings": MessageLookupByLibrary.simpleMessage("Instellingen openen"), "openTheItem": MessageLookupByLibrary.simpleMessage("• Open het item"), @@ -1318,7 +1305,7 @@ class MessageLookup extends MessageLookupByLibrary { "photosAddedByYouWillBeRemovedFromTheAlbum": MessageLookupByLibrary.simpleMessage( "Foto\'s toegevoegd door u zullen worden verwijderd uit het album"), - "photosCount": m82, + "photosCount": m47, "pickCenterPoint": MessageLookupByLibrary.simpleMessage("Kies middelpunt"), "pinAlbum": @@ -1326,7 +1313,7 @@ class MessageLookup extends MessageLookupByLibrary { "pinLock": MessageLookupByLibrary.simpleMessage("PIN vergrendeling"), "playOnTv": MessageLookupByLibrary.simpleMessage("Album afspelen op TV"), - "playStoreFreeTrialValidTill": m47, + "playStoreFreeTrialValidTill": m48, "playstoreSubscription": MessageLookupByLibrary.simpleMessage("PlayStore abonnement"), "pleaseCheckYourInternetConnectionAndTryAgain": @@ -1338,14 +1325,14 @@ class MessageLookup extends MessageLookupByLibrary { "pleaseContactSupportIfTheProblemPersists": MessageLookupByLibrary.simpleMessage( "Neem contact op met klantenservice als het probleem aanhoudt"), - "pleaseEmailUsAt": m48, + "pleaseEmailUsAt": m49, "pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage( "Geef alstublieft toestemming"), "pleaseLoginAgain": MessageLookupByLibrary.simpleMessage("Log opnieuw in"), "pleaseSelectQuickLinksToRemove": MessageLookupByLibrary.simpleMessage( "Selecteer snelle links om te verwijderen"), - "pleaseSendTheLogsTo": m49, + "pleaseSendTheLogsTo": m50, "pleaseTryAgain": MessageLookupByLibrary.simpleMessage("Probeer het nog eens"), "pleaseVerifyTheCodeYouHaveEntered": @@ -1371,7 +1358,7 @@ class MessageLookup extends MessageLookupByLibrary { "privateBackups": MessageLookupByLibrary.simpleMessage("Privé back-ups"), "privateSharing": MessageLookupByLibrary.simpleMessage("Privé delen"), - "processingImport": m50, + "processingImport": m51, "publicLinkCreated": MessageLookupByLibrary.simpleMessage("Publieke link aangemaakt"), "publicLinkEnabled": @@ -1381,7 +1368,7 @@ class MessageLookup extends MessageLookupByLibrary { "raiseTicket": MessageLookupByLibrary.simpleMessage("Meld probleem"), "rateTheApp": MessageLookupByLibrary.simpleMessage("Beoordeel de app"), "rateUs": MessageLookupByLibrary.simpleMessage("Beoordeel ons"), - "rateUsOnStore": m51, + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("Herstellen"), "recoverAccount": MessageLookupByLibrary.simpleMessage("Account herstellen"), @@ -1416,7 +1403,7 @@ class MessageLookup extends MessageLookupByLibrary { "1. Geef deze code aan je vrienden"), "referralStep2": MessageLookupByLibrary.simpleMessage( "2. Ze registreren voor een betaald plan"), - "referralStep3": m52, + "referralStep3": m53, "referrals": MessageLookupByLibrary.simpleMessage("Referenties"), "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage( "Verwijzingen zijn momenteel gepauzeerd"), @@ -1444,7 +1431,7 @@ class MessageLookup extends MessageLookupByLibrary { "removeLink": MessageLookupByLibrary.simpleMessage("Verwijder link"), "removeParticipant": MessageLookupByLibrary.simpleMessage("Deelnemer verwijderen"), - "removeParticipantBody": m53, + "removeParticipantBody": m54, "removePersonLabel": MessageLookupByLibrary.simpleMessage("Verwijder persoonslabel"), "removePublicLink": @@ -1464,7 +1451,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Bestandsnaam wijzigen"), "renewSubscription": MessageLookupByLibrary.simpleMessage("Abonnement verlengen"), - "renewsOn": m54, + "renewsOn": m55, "reportABug": MessageLookupByLibrary.simpleMessage("Een fout melden"), "reportBug": MessageLookupByLibrary.simpleMessage("Fout melden"), "resendEmail": @@ -1542,11 +1529,9 @@ class MessageLookup extends MessageLookupByLibrary { "Nodig mensen uit, en je ziet alle foto\'s die door hen worden gedeeld hier"), "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( "Mensen worden hier getoond zodra de verwerking voltooid is"), - "searchResultCount": m55, - "searchSectionsLengthMismatch": m56, + "searchResultCount": m56, + "searchSectionsLengthMismatch": m57, "security": MessageLookupByLibrary.simpleMessage("Beveiliging"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "selectALocation": MessageLookupByLibrary.simpleMessage("Selecteer een locatie"), "selectALocationFirst": @@ -1578,7 +1563,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Geselecteerde bestanden worden verwijderd uit alle albums en verplaatst naar de prullenbak."), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "send": MessageLookupByLibrary.simpleMessage("Verzenden"), "sendEmail": MessageLookupByLibrary.simpleMessage("E-mail versturen"), "sendInvite": @@ -1610,16 +1595,16 @@ class MessageLookup extends MessageLookupByLibrary { "shareAnAlbumNow": MessageLookupByLibrary.simpleMessage("Deel nu een album"), "shareLink": MessageLookupByLibrary.simpleMessage("Link delen"), - "shareMyVerificationID": m58, + "shareMyVerificationID": m59, "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage( "Deel alleen met de mensen die u wilt"), "shareTextConfirmOthersVerificationID": m5, "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage( "Download Ente zodat we gemakkelijk foto\'s en video\'s in originele kwaliteit kunnen delen\n\nhttps://ente.io"), - "shareTextReferralCode": m59, + "shareTextReferralCode": m60, "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage( "Delen met niet-Ente gebruikers"), - "shareWithPeopleSectionTitle": m60, + "shareWithPeopleSectionTitle": m61, "shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage("Deel jouw eerste album"), "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( @@ -1630,7 +1615,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Nieuwe gedeelde foto\'s"), "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( "Ontvang meldingen wanneer iemand een foto toevoegt aan een gedeeld album waar je deel van uitmaakt"), - "sharedWith": m61, + "sharedWith": m62, "sharedWithMe": MessageLookupByLibrary.simpleMessage("Gedeeld met mij"), "sharedWithYou": MessageLookupByLibrary.simpleMessage("Gedeeld met jou"), @@ -1646,11 +1631,11 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Log uit op andere apparaten"), "signUpTerms": MessageLookupByLibrary.simpleMessage( "Ik ga akkoord met de gebruiksvoorwaarden en privacybeleid"), - "singleFileDeleteFromDevice": m62, + "singleFileDeleteFromDevice": m63, "singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage( "Het wordt uit alle albums verwijderd."), - "singleFileInBothLocalAndRemote": m63, - "singleFileInRemoteOnly": m64, + "singleFileInBothLocalAndRemote": m64, + "singleFileInRemoteOnly": m65, "skip": MessageLookupByLibrary.simpleMessage("Overslaan"), "social": MessageLookupByLibrary.simpleMessage("Sociale media"), "someItemsAreInBothEnteAndYourDevice": MessageLookupByLibrary.simpleMessage( @@ -1696,10 +1681,10 @@ class MessageLookup extends MessageLookupByLibrary { "storageInGB": m1, "storageLimitExceeded": MessageLookupByLibrary.simpleMessage("Opslaglimiet overschreden"), - "storageUsageInfo": m65, + "storageUsageInfo": m66, "strongStrength": MessageLookupByLibrary.simpleMessage("Sterk"), - "subAlreadyLinkedErrMessage": m66, - "subWillBeCancelledOn": m67, + "subAlreadyLinkedErrMessage": m67, + "subWillBeCancelledOn": m68, "subscribe": MessageLookupByLibrary.simpleMessage("Abonneer"), "subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage( "Je hebt een actief betaald abonnement nodig om delen mogelijk te maken."), @@ -1716,7 +1701,7 @@ class MessageLookup extends MessageLookupByLibrary { "suggestFeatures": MessageLookupByLibrary.simpleMessage("Features voorstellen"), "support": MessageLookupByLibrary.simpleMessage("Ondersteuning"), - "syncProgress": m68, + "syncProgress": m69, "syncStopped": MessageLookupByLibrary.simpleMessage("Synchronisatie gestopt"), "syncing": MessageLookupByLibrary.simpleMessage("Synchroniseren..."), @@ -1728,7 +1713,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Tik om te ontgrendelen"), "tapToUpload": MessageLookupByLibrary.simpleMessage("Tik om te uploaden"), - "tapToUploadIsIgnoredDue": m69, + "tapToUploadIsIgnoredDue": m70, "tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage( "Het lijkt erop dat er iets fout is gegaan. Probeer het later opnieuw. Als de fout zich blijft voordoen, neem dan contact op met ons supportteam."), "terminate": MessageLookupByLibrary.simpleMessage("Beëindigen"), @@ -1742,9 +1727,6 @@ class MessageLookup extends MessageLookupByLibrary { "Dank je wel voor het abonneren!"), "theDownloadCouldNotBeCompleted": MessageLookupByLibrary.simpleMessage( "De download kon niet worden voltooid"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "theRecoveryKeyYouEnteredIsIncorrect": MessageLookupByLibrary.simpleMessage( "De ingevoerde herstelsleutel is onjuist"), @@ -1752,7 +1734,7 @@ class MessageLookup extends MessageLookupByLibrary { "theseItemsWillBeDeletedFromYourDevice": MessageLookupByLibrary.simpleMessage( "Deze bestanden zullen worden verwijderd van uw apparaat."), - "theyAlsoGetXGb": m70, + "theyAlsoGetXGb": m71, "theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage( "Ze zullen uit alle albums worden verwijderd."), "thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage( @@ -1768,7 +1750,7 @@ class MessageLookup extends MessageLookupByLibrary { "Dit e-mailadres is al in gebruik"), "thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage( "Deze foto heeft geen exif gegevens"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage("Dit is uw verificatie-ID"), "thisWillLogYouOutOfTheFollowingDevice": @@ -1793,7 +1775,7 @@ class MessageLookup extends MessageLookupByLibrary { "total": MessageLookupByLibrary.simpleMessage("totaal"), "totalSize": MessageLookupByLibrary.simpleMessage("Totale grootte"), "trash": MessageLookupByLibrary.simpleMessage("Prullenbak"), - "trashDaysLeft": m72, + "trashDaysLeft": m73, "trim": MessageLookupByLibrary.simpleMessage("Knippen"), "tryAgain": MessageLookupByLibrary.simpleMessage("Probeer opnieuw"), "turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage( @@ -1813,7 +1795,7 @@ class MessageLookup extends MessageLookupByLibrary { "Tweestapsverificatie succesvol gereset"), "twofactorSetup": MessageLookupByLibrary.simpleMessage("Tweestapsverificatie"), - "typeOfGallerGallerytypeIsNotSupportedForRename": m73, + "typeOfGallerGallerytypeIsNotSupportedForRename": m74, "unarchive": MessageLookupByLibrary.simpleMessage("Uit archief halen"), "unarchiveAlbum": MessageLookupByLibrary.simpleMessage("Album uit archief halen"), @@ -1839,10 +1821,10 @@ class MessageLookup extends MessageLookupByLibrary { "updatingFolderSelection": MessageLookupByLibrary.simpleMessage("Map selectie bijwerken..."), "upgrade": MessageLookupByLibrary.simpleMessage("Upgraden"), - "uploadIsIgnoredDueToIgnorereason": m74, + "uploadIsIgnoredDueToIgnorereason": m75, "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage( "Bestanden worden geüpload naar album..."), - "uploadingMultipleMemories": m75, + "uploadingMultipleMemories": m76, "uploadingSingleMemory": MessageLookupByLibrary.simpleMessage( "1 herinnering veiligstellen..."), "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( @@ -1858,7 +1840,7 @@ class MessageLookup extends MessageLookupByLibrary { "useSelectedPhoto": MessageLookupByLibrary.simpleMessage("Gebruik geselecteerde foto"), "usedSpace": MessageLookupByLibrary.simpleMessage("Gebruikte ruimte"), - "validTill": m76, + "validTill": m77, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "Verificatie mislukt, probeer het opnieuw"), @@ -1866,7 +1848,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Verificatie ID"), "verify": MessageLookupByLibrary.simpleMessage("Verifiëren"), "verifyEmail": MessageLookupByLibrary.simpleMessage("Bevestig e-mail"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("Verifiëren"), "verifyPasskey": MessageLookupByLibrary.simpleMessage("Bevestig passkey"), @@ -1893,7 +1875,7 @@ class MessageLookup extends MessageLookupByLibrary { "viewRecoveryKey": MessageLookupByLibrary.simpleMessage("Toon herstelsleutel"), "viewer": MessageLookupByLibrary.simpleMessage("Kijker"), - "viewersSuccessfullyAdded": m78, + "viewersSuccessfullyAdded": m79, "visitWebToManage": MessageLookupByLibrary.simpleMessage( "Bezoek alstublieft web.ente.io om uw abonnement te beheren"), "waitingForVerification": @@ -1911,7 +1893,7 @@ class MessageLookup extends MessageLookupByLibrary { "whatsNew": MessageLookupByLibrary.simpleMessage("Nieuw"), "yearShort": MessageLookupByLibrary.simpleMessage("jr"), "yearly": MessageLookupByLibrary.simpleMessage("Jaarlijks"), - "yearsAgo": m79, + "yearsAgo": m80, "yes": MessageLookupByLibrary.simpleMessage("Ja"), "yesCancel": MessageLookupByLibrary.simpleMessage("Ja, opzeggen"), "yesConvertToViewer": @@ -1943,7 +1925,7 @@ class MessageLookup extends MessageLookupByLibrary { "Je kunt niet met jezelf delen"), "youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage( "U heeft geen gearchiveerde bestanden."), - "youHaveSuccessfullyFreedUp": m80, + "youHaveSuccessfullyFreedUp": m81, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage("Je account is verwijderd"), "yourMap": MessageLookupByLibrary.simpleMessage("Jouw kaart"), diff --git a/mobile/lib/generated/intl/messages_no.dart b/mobile/lib/generated/intl/messages_no.dart index 1d79ae102d..4f35bbb6bb 100644 --- a/mobile/lib/generated/intl/messages_no.dart +++ b/mobile/lib/generated/intl/messages_no.dart @@ -51,20 +51,20 @@ class MessageLookup extends MessageLookupByLibrary { static String m4(count) => "${count} valgt"; - static String m57(count, yourCount) => "${count} valgt (${yourCount} dine)"; + static String m58(count, yourCount) => "${count} valgt (${yourCount} dine)"; - static String m58(verificationID) => + static String m59(verificationID) => "Her er min verifiserings-ID: ${verificationID} for ente.io."; static String m5(verificationID) => "Hei, kan du bekrefte at dette er din ente.io verifiserings-ID: ${verificationID}"; - static String m60(numberOfPeople) => + static String m61(numberOfPeople) => "${Intl.plural(numberOfPeople, zero: 'Del med bestemte personer', one: 'Delt med 1 person', other: 'Delt med ${numberOfPeople} personer')}"; - static String m71(email) => "Dette er ${email} sin verifiserings-ID"; + static String m72(email) => "Dette er ${email} sin verifiserings-ID"; - static String m77(email) => "Verifiser ${email}"; + static String m78(email) => "Verifiser ${email}"; static String m2(email) => "Vi har sendt en e-post til ${email}"; @@ -94,13 +94,10 @@ class MessageLookup extends MessageLookupByLibrary { "albumParticipantsCount": m12, "albumUpdated": MessageLookupByLibrary.simpleMessage("Album oppdatert"), "albums": MessageLookupByLibrary.simpleMessage("Album"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "Tillat folk med lenken å også legge til bilder til det delte albumet."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage("Tillat å legge til bilder"), - "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), "allowDownloads": MessageLookupByLibrary.simpleMessage("Tillat nedlastinger"), "apply": MessageLookupByLibrary.simpleMessage("Anvend"), @@ -110,7 +107,6 @@ class MessageLookup extends MessageLookupByLibrary { "Vennligst autentiser deg for å se dine skjulte filer"), "authToViewYourRecoveryKey": MessageLookupByLibrary.simpleMessage( "Vennligst autentiser deg for å se gjennopprettingsnøkkelen din"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), "cancel": MessageLookupByLibrary.simpleMessage("Avbryt"), "cannotAddMorePhotosAfterBecomingViewer": m16, "changeEmail": @@ -220,8 +216,6 @@ class MessageLookup extends MessageLookupByLibrary { "Skriv inn din gjenopprettingsnøkkel"), "expiredLinkInfo": MessageLookupByLibrary.simpleMessage( "Denne lenken er utløpt. Vennligst velg en ny utløpstid eller deaktiver lenkeutløp."), - "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), "failedToLoadAlbums": MessageLookupByLibrary.simpleMessage("Kunne ikke laste inn album"), "familyPlans": @@ -271,8 +265,6 @@ class MessageLookup extends MessageLookupByLibrary { "machineLearning": MessageLookupByLibrary.simpleMessage("Maskinlæring"), "magicSearch": MessageLookupByLibrary.simpleMessage("Magisk søk"), "manage": MessageLookupByLibrary.simpleMessage("Administrer"), - "manageDeviceStorage": - MessageLookupByLibrary.simpleMessage("Behandle enhetslagring"), "manageLink": MessageLookupByLibrary.simpleMessage("Administrer lenke"), "manageParticipants": MessageLookupByLibrary.simpleMessage("Administrer"), @@ -290,11 +282,6 @@ class MessageLookup extends MessageLookupByLibrary { "notifications": MessageLookupByLibrary.simpleMessage("Varslinger"), "ok": MessageLookupByLibrary.simpleMessage("Ok"), "oops": MessageLookupByLibrary.simpleMessage("Oisann"), - "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), - "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), "orPickAnExistingOne": MessageLookupByLibrary.simpleMessage("Eller velg en eksisterende"), "password": MessageLookupByLibrary.simpleMessage("Passord"), @@ -360,8 +347,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Skann denne strekkoden med\nautentiseringsappen din"), "security": MessageLookupByLibrary.simpleMessage("Sikkerhet"), - "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), "selectAll": MessageLookupByLibrary.simpleMessage("Velg alle"), "selectFoldersForBackup": MessageLookupByLibrary.simpleMessage( "Velg mapper for sikkerhetskopiering"), @@ -370,7 +355,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Valgte mapper vil bli kryptert og sikkerhetskopiert"), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "sendEmail": MessageLookupByLibrary.simpleMessage("Send e-post"), "sendInvite": MessageLookupByLibrary.simpleMessage("Send invitasjon"), "sendLink": MessageLookupByLibrary.simpleMessage("Send lenke"), @@ -380,9 +365,9 @@ class MessageLookup extends MessageLookupByLibrary { "setupComplete": MessageLookupByLibrary.simpleMessage("Oppsett fullført"), "shareALink": MessageLookupByLibrary.simpleMessage("Del en lenke"), - "shareMyVerificationID": m58, + "shareMyVerificationID": m59, "shareTextConfirmOthersVerificationID": m5, - "shareWithPeopleSectionTitle": m60, + "shareWithPeopleSectionTitle": m61, "sharedPhotoNotifications": MessageLookupByLibrary.simpleMessage("Nye delte bilder"), "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( @@ -416,14 +401,11 @@ class MessageLookup extends MessageLookupByLibrary { "terminateSession": MessageLookupByLibrary.simpleMessage("Avslutte økten?"), "termsOfServicesTitle": MessageLookupByLibrary.simpleMessage("Vilkår"), - "theLinkYouAreTryingToAccessHasExpired": - MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), "thisCanBeUsedToRecoverYourAccountIfYou": MessageLookupByLibrary.simpleMessage( "Dette kan brukes til å gjenopprette kontoen din hvis du mister din andre faktor"), "thisDevice": MessageLookupByLibrary.simpleMessage("Denne enheten"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Dette er din bekreftelses-ID"), "thisWillLogYouOutOfTheFollowingDevice": @@ -450,7 +432,7 @@ class MessageLookup extends MessageLookupByLibrary { "verify": MessageLookupByLibrary.simpleMessage("Bekreft"), "verifyEmail": MessageLookupByLibrary.simpleMessage("Bekreft e-postadresse"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyPassword": MessageLookupByLibrary.simpleMessage("Bekreft passord"), "verifyingRecoveryKey": MessageLookupByLibrary.simpleMessage( diff --git a/mobile/lib/generated/intl/messages_pl.dart b/mobile/lib/generated/intl/messages_pl.dart index aae60b6e76..084714c38a 100644 --- a/mobile/lib/generated/intl/messages_pl.dart +++ b/mobile/lib/generated/intl/messages_pl.dart @@ -60,6 +60,9 @@ class MessageLookup extends MessageLookupByLibrary { static String m18(albumName) => "Utworzono link współpracy dla ${albumName}"; + static String m82(count) => + "${Intl.plural(count, zero: 'Dodano 0 współuczestników', one: 'Dodano 1 współuczestnika', other: 'Dodano ${count} współuczestników')}"; + static String m19(familyAdminEmail) => "Prosimy skontaktować się z ${familyAdminEmail}, by zarzadząć swoją subskrypcją"; @@ -141,93 +144,111 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(providerName) => "Porozmawiaj ze wsparciem ${providerName} jeśli zostałeś obciążony"; - static String m47(endDate) => + static String m47(count) => + "${Intl.plural(count, zero: '0 zdjęć', one: '1 zdjęcie', few: '${count} zdjęcia', other: '${count} zdjęć')}"; + + static String m48(endDate) => "Bezpłatny okres próbny ważny do ${endDate}.\nNastępnie możesz wybrać płatny plan."; - static String m48(toEmail) => + static String m49(toEmail) => "Prosimy o kontakt mailowy pod adresem ${toEmail}"; - static String m49(toEmail) => "Prosimy wysłać logi do ${toEmail}"; + static String m50(toEmail) => "Prosimy wysłać logi do ${toEmail}"; - static String m50(folderName) => "Przetwarzanie ${folderName}..."; + static String m51(folderName) => "Przetwarzanie ${folderName}..."; - static String m51(storeName) => "Oceń nas na ${storeName}"; + static String m52(storeName) => "Oceń nas na ${storeName}"; - static String m52(storageInGB) => + static String m53(storageInGB) => "3. Oboje otrzymujecie ${storageInGB} GB* za darmo"; - static String m53(userEmail) => + static String m54(userEmail) => "${userEmail} zostanie usunięty z tego udostępnionego albumu\n\nWszelkie dodane przez nich zdjęcia zostaną usunięte z albumu"; - static String m54(endDate) => "Subskrypcja odnowi się ${endDate}"; + static String m55(endDate) => "Subskrypcja odnowi się ${endDate}"; - static String m55(count) => + static String m56(count) => "${Intl.plural(count, one: 'Znaleziono ${count} wynik', few: 'Znaleziono ${count} wyniki', other: 'Znaleziono ${count} wyników')}"; + static String m57(snapshotLenght, searchLenght) => + "Niezgodność długości sekcji: ${snapshotLenght} != ${searchLenght}"; + static String m4(count) => "Wybrano ${count}"; - static String m57(count, yourCount) => + static String m58(count, yourCount) => "Wybrano ${count} (twoich ${yourCount})"; - static String m58(verificationID) => + static String m59(verificationID) => "Oto mój identyfikator weryfikacyjny: ${verificationID} dla ente.io."; static String m5(verificationID) => "Hej, czy możesz potwierdzić, że to jest Twój identyfikator weryfikacyjny ente.io: ${verificationID}"; - static String m59(referralCode, referralStorageInGB) => + static String m60(referralCode, referralStorageInGB) => "Kod polecający: ${referralCode} \n\nZastosuj go w: Ustawienia → Ogólne → Polecanie, aby otrzymać ${referralStorageInGB} GB za darmo po zarejestrowaniu się w płatnym planie\n\nhttps://ente.io"; - static String m60(numberOfPeople) => + static String m61(numberOfPeople) => "${Intl.plural(numberOfPeople, zero: 'Udostępnione określonym osobom', one: 'Udostępnione 1 osobie', other: 'Udostępnione ${numberOfPeople} osobom')}"; - static String m61(emailIDs) => "Udostępnione z ${emailIDs}"; - - static String m62(fileType) => - "Ten ${fileType} zostanie usunięty z Twojego urządzenia."; + static String m62(emailIDs) => "Udostępnione z ${emailIDs}"; static String m63(fileType) => + "Ten ${fileType} zostanie usunięty z Twojego urządzenia."; + + static String m64(fileType) => "Ten ${fileType} jest zarówno w Ente, jak i na twoim urządzeniu."; - static String m64(fileType) => "Ten ${fileType} zostanie usunięty z Ente."; + static String m65(fileType) => "Ten ${fileType} zostanie usunięty z Ente."; static String m1(storageAmountInGB) => "${storageAmountInGB} GB"; - static String m65( + static String m66( usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => "Użyto ${usedAmount} ${usedStorageUnit} z ${totalAmount} ${totalStorageUnit}"; - static String m66(id) => + static String m67(id) => "Twoje ${id} jest już połączony z innym kontem Ente.\nJeśli chcesz użyć swojego ${id} za pomocą tego konta, skontaktuj się z naszym wsparciem technicznym"; - static String m67(endDate) => + static String m68(endDate) => "Twoja subskrypcja zostanie anulowana dnia ${endDate}"; - static String m68(completed, total) => + static String m69(completed, total) => "Zachowano ${completed}/${total} wspomnień"; - static String m70(storageAmountInGB) => + static String m70(ignoreReason) => + "Naciśnij, aby przesłać, przesyłanie jest obecnie ignorowane z powodu ${ignoreReason}"; + + static String m71(storageAmountInGB) => "Oni również otrzymują ${storageAmountInGB} GB"; - static String m71(email) => "To jest identyfikator weryfikacyjny ${email}"; + static String m72(email) => "To jest identyfikator weryfikacyjny ${email}"; - static String m72(count) => - "${Intl.plural(count, zero: '', one: '${count} dzień', few: '${count} dni', other: '${count} dni')}"; + static String m73(count) => + "${Intl.plural(count, zero: 'Wkrótce', one: '1 dzień', few: '${count} dni', other: '${count} dni')}"; - static String m75(count) => + static String m74(galleryType) => + "Typ galerii ${galleryType} nie jest obsługiwany dla zmiany nazwy"; + + static String m75(ignoreReason) => + "Przesyłanie jest ignorowane z powodu ${ignoreReason}"; + + static String m76(count) => "${Intl.plural(count, one: 'Zachowywanie ${count} wspomnienia...', few: 'Zachowywanie ${count} wspomnienia...', many: 'Zachowywanie ${count} wspomnień...', other: 'Zachowywanie ${count} wspomnień...')}"; - static String m76(endDate) => "Ważne do ${endDate}"; + static String m77(endDate) => "Ważne do ${endDate}"; - static String m77(email) => "Zweryfikuj ${email}"; + static String m78(email) => "Zweryfikuj ${email}"; + + static String m79(count) => + "${Intl.plural(count, zero: 'Dodano 0 widzów', one: 'Dodano 1 widza', other: 'Dodano ${count} widzów')}"; static String m2(email) => "Wysłaliśmy wiadomość na adres ${email}"; - static String m79(count) => + static String m80(count) => "${Intl.plural(count, one: '${count} rok temu', few: '${count} lata temu', many: '${count} lat temu', other: '${count} lata temu')}"; - static String m80(storageSaved) => "Pomyślnie zwolniłeś/aś ${storageSaved}!"; + static String m81(storageSaved) => "Pomyślnie zwolniłeś/aś ${storageSaved}!"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -235,6 +256,8 @@ class MessageLookup extends MessageLookupByLibrary { "Dostępna jest nowa wersja Ente."), "about": MessageLookupByLibrary.simpleMessage("O nas"), "account": MessageLookupByLibrary.simpleMessage("Konto"), + "accountIsAlreadyConfigured": MessageLookupByLibrary.simpleMessage( + "Konto jest już skonfigurowane."), "accountWelcomeBack": MessageLookupByLibrary.simpleMessage("Witaj ponownie!"), "ackPasswordLostWarning": MessageLookupByLibrary.simpleMessage( @@ -300,13 +323,13 @@ class MessageLookup extends MessageLookupByLibrary { "Wszystkie wspomnienia zachowane"), "allPersonGroupingWillReset": MessageLookupByLibrary.simpleMessage( "Wszystkie grupy dla tej osoby zostaną zresetowane i stracisz wszystkie sugestie dla tej osoby"), - "allow": MessageLookupByLibrary.simpleMessage("Allow"), + "allow": MessageLookupByLibrary.simpleMessage("Zezwól"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "Pozwól osobom z linkiem na dodawania zdjęć do udostępnionego albumu."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage("Pozwól na dodawanie zdjęć"), "allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage( - "Allow app to open shared album links"), + "Zezwalaj aplikacji na otwieranie udostępnianych linków do albumu"), "allowDownloads": MessageLookupByLibrary.simpleMessage("Zezwól na pobieranie"), "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( @@ -422,7 +445,8 @@ class MessageLookup extends MessageLookupByLibrary { "backup": MessageLookupByLibrary.simpleMessage("Kopia zapasowa"), "backupFailed": MessageLookupByLibrary.simpleMessage( "Tworzenie kopii zapasowej nie powiodło się"), - "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), + "backupFile": + MessageLookupByLibrary.simpleMessage("Zrób kopię zapasową pliku"), "backupOverMobileData": MessageLookupByLibrary.simpleMessage( "Kopia zapasowa przez dane mobilne"), "backupSettings": @@ -433,6 +457,7 @@ class MessageLookup extends MessageLookupByLibrary { "Elementy, których kopia zapasowa została utworzona, zostaną wyświetlone w tym miejscu"), "backupVideos": MessageLookupByLibrary.simpleMessage("Utwórz kopię zapasową wideo"), + "birthday": MessageLookupByLibrary.simpleMessage("Urodziny"), "blackFridaySale": MessageLookupByLibrary.simpleMessage( "Wyprzedaż z okazji Czarnego Piątku"), "blog": MessageLookupByLibrary.simpleMessage("Blog"), @@ -466,10 +491,20 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Zmień adres e-mail"), "changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage( "Zmienić lokalizację wybranych elementów?"), + "changeLogBackupStatusContent": MessageLookupByLibrary.simpleMessage( + "Dodaliśmy dziennik wszystkich plików, które zostały przesłane do Ente, wraz z błędami i kolejką."), "changeLogBackupStatusTitle": MessageLookupByLibrary.simpleMessage("Status Kopii Zapasowej"), + "changeLogDiscoverContent": MessageLookupByLibrary.simpleMessage( + "Szukasz zdjęć Twoich kart identyfikacyjnych, notatek, a nawet memów? Przejdź do zakładki wyszukiwania i sprawdź Odkryj. Na podstawie naszego semantycznego wyszukiwania jest to miejsce, w którym znajdziesz zdjęcia, które mogą być dla Ciebie ważne.\\n\\nDostępne tylko wtedy, gdy jest włączone nauczanie maszynowe."), "changeLogDiscoverTitle": MessageLookupByLibrary.simpleMessage("Odkryj"), + "changeLogMagicSearchImprovementContent": + MessageLookupByLibrary.simpleMessage( + "Ulepszyliśmy magiczne wyszukiwanie, aby stało się o wiele szybsze, więc nie musisz czekać na to, czego szukasz."), + "changeLogMagicSearchImprovementTitle": + MessageLookupByLibrary.simpleMessage( + "Poprawa Magicznego Wyszukiwania"), "changePassword": MessageLookupByLibrary.simpleMessage("Zmień hasło"), "changePasswordTitle": MessageLookupByLibrary.simpleMessage("Zmień hasło"), @@ -524,6 +559,7 @@ class MessageLookup extends MessageLookupByLibrary { "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": MessageLookupByLibrary.simpleMessage( "Współuczestnicy mogą dodawać zdjęcia i wideo do udostępnionego albumu."), + "collaboratorsSuccessfullyAdded": m82, "collageLayout": MessageLookupByLibrary.simpleMessage("Układ"), "collageSaved": MessageLookupByLibrary.simpleMessage("Kolaż zapisano w galerii"), @@ -598,6 +634,8 @@ class MessageLookup extends MessageLookupByLibrary { "crop": MessageLookupByLibrary.simpleMessage("Kadruj"), "currentUsageIs": MessageLookupByLibrary.simpleMessage("Aktualne użycie to "), + "currentlyRunning": + MessageLookupByLibrary.simpleMessage("aktualnie uruchomiony"), "custom": MessageLookupByLibrary.simpleMessage("Niestandardowy"), "customEndpoint": m21, "darkTheme": MessageLookupByLibrary.simpleMessage("Ciemny"), @@ -774,10 +812,13 @@ class MessageLookup extends MessageLookupByLibrary { "enterCode": MessageLookupByLibrary.simpleMessage("Wprowadź kod"), "enterCodeDescription": MessageLookupByLibrary.simpleMessage( "Wprowadź kod dostarczony przez znajomego, aby uzyskać bezpłatne miejsce dla was obojga"), + "enterDateOfBirth": MessageLookupByLibrary.simpleMessage( + "Data urodzenia (nieobowiązkowo)"), "enterEmail": MessageLookupByLibrary.simpleMessage("Wprowadź adres e-mail"), "enterFileName": MessageLookupByLibrary.simpleMessage("Wprowadź nazwę pliku"), + "enterName": MessageLookupByLibrary.simpleMessage("Wprowadź nazwę"), "enterNewPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "Wprowadź nowe hasło, którego możemy użyć do zaszyfrowania Twoich danych"), "enterPassword": MessageLookupByLibrary.simpleMessage("Wprowadź hasło"), @@ -813,7 +854,7 @@ class MessageLookup extends MessageLookupByLibrary { "Znaleziono dodatkowe zdjęcia"), "extraPhotosFoundFor": m30, "faceNotClusteredYet": MessageLookupByLibrary.simpleMessage( - "Face not clustered yet, please come back later"), + "Twarz jeszcze nie zgrupowana, prosimy wrócić później"), "faceRecognition": MessageLookupByLibrary.simpleMessage("Rozpoznawanie twarzy"), "faces": MessageLookupByLibrary.simpleMessage("Twarze"), @@ -823,12 +864,19 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Nie udało się anulować"), "failedToDownloadVideo": MessageLookupByLibrary.simpleMessage("Nie udało się pobrać wideo"), + "failedToFetchActiveSessions": MessageLookupByLibrary.simpleMessage( + "Nie udało się pobrać aktywnych sesji"), "failedToFetchOriginalForEdit": MessageLookupByLibrary.simpleMessage( "Nie udało się pobrać oryginału do edycji"), "failedToFetchReferralDetails": MessageLookupByLibrary.simpleMessage( "Nie można pobrać szczegółów polecenia. Spróbuj ponownie później."), "failedToLoadAlbums": MessageLookupByLibrary.simpleMessage( "Nie udało się załadować albumów"), + "failedToPlayVideo": MessageLookupByLibrary.simpleMessage( + "Nie udało się odtworzyć wideo"), + "failedToRefreshStripeSubscription": + MessageLookupByLibrary.simpleMessage( + "Nie udało się odświeżyć subskrypcji"), "failedToRenew": MessageLookupByLibrary.simpleMessage("Nie udało się odnowić"), "failedToVerifyPaymentStatus": MessageLookupByLibrary.simpleMessage( @@ -930,6 +978,7 @@ class MessageLookup extends MessageLookupByLibrary { "Uwierzytelnianie biometryczne jest wyłączone. Prosimy zablokować i odblokować ekran, aby je włączyć."), "iOSOkButton": MessageLookupByLibrary.simpleMessage("OK"), "ignoreUpdate": MessageLookupByLibrary.simpleMessage("Ignoruj"), + "ignored": MessageLookupByLibrary.simpleMessage("ignorowane"), "ignoredFolderUploadReason": MessageLookupByLibrary.simpleMessage( "Niektóre pliki w tym albumie są ignorowane podczas przesyłania, ponieważ zostały wcześniej usunięte z Ente."), "imageNotAnalyzed": MessageLookupByLibrary.simpleMessage( @@ -1058,6 +1107,8 @@ class MessageLookup extends MessageLookupByLibrary { "Twoja sesja wygasła. Zaloguj się ponownie."), "loginTerms": MessageLookupByLibrary.simpleMessage( "Klikając, zaloguj się, zgadzam się na regulamin i politykę prywatności"), + "loginWithTOTP": + MessageLookupByLibrary.simpleMessage("Zaloguj się za pomocą TOTP"), "logout": MessageLookupByLibrary.simpleMessage("Wyloguj"), "logsDialogBody": MessageLookupByLibrary.simpleMessage( "Spowoduje to wysyłanie logów, aby pomóc nam w debugowaniu twojego problemu. Pamiętaj, że nazwy plików zostaną dołączone, aby pomóc w śledzeniu problemów z określonymi plikami."), @@ -1081,7 +1132,9 @@ class MessageLookup extends MessageLookupByLibrary { "Magiczne wyszukiwanie pozwala na wyszukiwanie zdjęć według ich zawartości, np. \"kwiat\", \"czerwony samochód\", \"dokumenty tożsamości\""), "manage": MessageLookupByLibrary.simpleMessage("Zarządzaj"), "manageDeviceStorage": MessageLookupByLibrary.simpleMessage( - "Zarządzaj pamięcią urządzenia"), + "Zarządzaj pamięcią podręczną urządzenia"), + "manageDeviceStorageDesc": MessageLookupByLibrary.simpleMessage( + "Przejrzyj i wyczyść lokalną pamięć podręczną."), "manageFamily": MessageLookupByLibrary.simpleMessage("Zarządzaj Rodziną"), "manageLink": MessageLookupByLibrary.simpleMessage("Zarządzaj linkiem"), @@ -1202,10 +1255,10 @@ class MessageLookup extends MessageLookupByLibrary { "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage("Ups, coś poszło nie tak"), "openAlbumInBrowser": - MessageLookupByLibrary.simpleMessage("Open album in browser"), + MessageLookupByLibrary.simpleMessage("Otwórz album w przeglądarce"), "openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage( - "Please use the web app to add photos to this album"), - "openFile": MessageLookupByLibrary.simpleMessage("Open file"), + "Prosimy użyć aplikacji internetowej, aby dodać zdjęcia do tego albumu"), + "openFile": MessageLookupByLibrary.simpleMessage("Otwórz plik"), "openSettings": MessageLookupByLibrary.simpleMessage("Otwórz Ustawienia"), "openTheItem": MessageLookupByLibrary.simpleMessage("• Otwórz element"), @@ -1266,13 +1319,14 @@ class MessageLookup extends MessageLookupByLibrary { "photosAddedByYouWillBeRemovedFromTheAlbum": MessageLookupByLibrary.simpleMessage( "Zdjęcia dodane przez Ciebie zostaną usunięte z albumu"), + "photosCount": m47, "pickCenterPoint": MessageLookupByLibrary.simpleMessage("Wybierz punkt środkowy"), "pinAlbum": MessageLookupByLibrary.simpleMessage("Przypnij album"), "pinLock": MessageLookupByLibrary.simpleMessage("Blokada PIN"), "playOnTv": MessageLookupByLibrary.simpleMessage( "Odtwórz album na telewizorze"), - "playStoreFreeTrialValidTill": m47, + "playStoreFreeTrialValidTill": m48, "playstoreSubscription": MessageLookupByLibrary.simpleMessage("Subskrypcja PlayStore"), "pleaseCheckYourInternetConnectionAndTryAgain": @@ -1284,14 +1338,14 @@ class MessageLookup extends MessageLookupByLibrary { "pleaseContactSupportIfTheProblemPersists": MessageLookupByLibrary.simpleMessage( "Skontaktuj się z pomocą techniczną, jeśli problem będzie się powtarzał"), - "pleaseEmailUsAt": m48, + "pleaseEmailUsAt": m49, "pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage( "Prosimy przyznać uprawnienia"), "pleaseLoginAgain": MessageLookupByLibrary.simpleMessage("Zaloguj się ponownie"), "pleaseSelectQuickLinksToRemove": MessageLookupByLibrary.simpleMessage( "Prosimy wybrać szybkie linki do usunięcia"), - "pleaseSendTheLogsTo": m49, + "pleaseSendTheLogsTo": m50, "pleaseTryAgain": MessageLookupByLibrary.simpleMessage("Spróbuj ponownie"), "pleaseVerifyTheCodeYouHaveEntered": @@ -1317,7 +1371,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Prywatne kopie zapasowe"), "privateSharing": MessageLookupByLibrary.simpleMessage("Udostępnianie prywatne"), - "processingImport": m50, + "processingImport": m51, "publicLinkCreated": MessageLookupByLibrary.simpleMessage("Utworzono publiczny link"), "publicLinkEnabled": @@ -1327,7 +1381,7 @@ class MessageLookup extends MessageLookupByLibrary { "raiseTicket": MessageLookupByLibrary.simpleMessage("Zgłoś"), "rateTheApp": MessageLookupByLibrary.simpleMessage("Oceń aplikację"), "rateUs": MessageLookupByLibrary.simpleMessage("Oceń nas"), - "rateUsOnStore": m51, + "rateUsOnStore": m52, "recover": MessageLookupByLibrary.simpleMessage("Odzyskaj"), "recoverAccount": MessageLookupByLibrary.simpleMessage("Odzyskaj konto"), @@ -1363,7 +1417,7 @@ class MessageLookup extends MessageLookupByLibrary { "1. Przekaż ten kod swoim znajomym"), "referralStep2": MessageLookupByLibrary.simpleMessage("2. Wykupują płatny plan"), - "referralStep3": m52, + "referralStep3": m53, "referrals": MessageLookupByLibrary.simpleMessage("Polecenia"), "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage( "Wysyłanie poleceń jest obecnie wstrzymane"), @@ -1389,7 +1443,7 @@ class MessageLookup extends MessageLookupByLibrary { "removeLink": MessageLookupByLibrary.simpleMessage("Usuń link"), "removeParticipant": MessageLookupByLibrary.simpleMessage("Usuń użytkownika"), - "removeParticipantBody": m53, + "removeParticipantBody": m54, "removePersonLabel": MessageLookupByLibrary.simpleMessage("Usuń etykietę osoby"), "removePublicLink": @@ -1408,7 +1462,7 @@ class MessageLookup extends MessageLookupByLibrary { "renameFile": MessageLookupByLibrary.simpleMessage("Zmień nazwę pliku"), "renewSubscription": MessageLookupByLibrary.simpleMessage("Odnów subskrypcję"), - "renewsOn": m54, + "renewsOn": m55, "reportABug": MessageLookupByLibrary.simpleMessage("Zgłoś błąd"), "reportBug": MessageLookupByLibrary.simpleMessage("Zgłoś błąd"), "resendEmail": @@ -1428,6 +1482,7 @@ class MessageLookup extends MessageLookupByLibrary { "resumableUploads": MessageLookupByLibrary.simpleMessage("Przesyłania wznawialne"), "retry": MessageLookupByLibrary.simpleMessage("Spróbuj ponownie"), + "review": MessageLookupByLibrary.simpleMessage("Przejrzyj"), "reviewDeduplicateItems": MessageLookupByLibrary.simpleMessage( "Przejrzyj i usuń elementy, które uważasz, że są duplikatami."), "reviewSuggestions": @@ -1485,10 +1540,11 @@ class MessageLookup extends MessageLookupByLibrary { "Zaproś ludzi, a zobaczysz tutaj wszystkie udostępnione przez nich zdjęcia"), "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( "Osoby będą wyświetlane tutaj po zakończeniu przetwarzania"), - "searchResultCount": m55, + "searchResultCount": m56, + "searchSectionsLengthMismatch": m57, "security": MessageLookupByLibrary.simpleMessage("Bezpieczeństwo"), "seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage( - "See public album links in app"), + "Zobacz publiczne linki do albumów w aplikacji"), "selectALocation": MessageLookupByLibrary.simpleMessage("Wybierz lokalizację"), "selectALocationFirst": MessageLookupByLibrary.simpleMessage( @@ -1496,11 +1552,15 @@ class MessageLookup extends MessageLookupByLibrary { "selectAlbum": MessageLookupByLibrary.simpleMessage("Wybierz album"), "selectAll": MessageLookupByLibrary.simpleMessage("Zaznacz wszystko"), "selectAllShort": MessageLookupByLibrary.simpleMessage("Wszystko"), + "selectCoverPhoto": + MessageLookupByLibrary.simpleMessage("Wybierz zdjęcie na okładkę"), "selectFoldersForBackup": MessageLookupByLibrary.simpleMessage( "Wybierz foldery do stworzenia kopii zapasowej"), "selectItemsToAdd": MessageLookupByLibrary.simpleMessage("Wybierz elementy do dodania"), "selectLanguage": MessageLookupByLibrary.simpleMessage("Wybierz Język"), + "selectMailApp": + MessageLookupByLibrary.simpleMessage("Wybierz aplikację pocztową"), "selectMorePhotos": MessageLookupByLibrary.simpleMessage("Wybierz więcej zdjęć"), "selectReason": MessageLookupByLibrary.simpleMessage("Wybierz powód"), @@ -1515,7 +1575,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Wybrane elementy zostaną usunięte ze wszystkich albumów i przeniesione do kosza."), "selectedPhotos": m4, - "selectedPhotosWithYours": m57, + "selectedPhotosWithYours": m58, "send": MessageLookupByLibrary.simpleMessage("Wyślij"), "sendEmail": MessageLookupByLibrary.simpleMessage("Wyślij e-mail"), "sendInvite": @@ -1524,6 +1584,8 @@ class MessageLookup extends MessageLookupByLibrary { "serverEndpoint": MessageLookupByLibrary.simpleMessage("Punkt końcowy serwera"), "sessionExpired": MessageLookupByLibrary.simpleMessage("Sesja wygasła"), + "sessionIdMismatch": + MessageLookupByLibrary.simpleMessage("Niezgodność ID sesji"), "setAPassword": MessageLookupByLibrary.simpleMessage("Ustaw hasło"), "setAs": MessageLookupByLibrary.simpleMessage("Ustaw jako"), "setCover": MessageLookupByLibrary.simpleMessage("Ustaw okładkę"), @@ -1542,16 +1604,16 @@ class MessageLookup extends MessageLookupByLibrary { "shareAnAlbumNow": MessageLookupByLibrary.simpleMessage("Udostępnij teraz album"), "shareLink": MessageLookupByLibrary.simpleMessage("Udostępnij link"), - "shareMyVerificationID": m58, + "shareMyVerificationID": m59, "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage( "Udostępnij tylko ludziom, którym chcesz"), "shareTextConfirmOthersVerificationID": m5, "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage( "Pobierz Ente, abyśmy mogli łatwo udostępniać zdjęcia i wideo w oryginalnej jakości\n\nhttps://ente.io"), - "shareTextReferralCode": m59, + "shareTextReferralCode": m60, "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage( "Udostępnij użytkownikom bez konta Ente"), - "shareWithPeopleSectionTitle": m60, + "shareWithPeopleSectionTitle": m61, "shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage( "Udostępnij swój pierwszy album"), "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( @@ -1564,7 +1626,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Nowe udostępnione zdjęcia"), "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( "Otrzymuj powiadomienia, gdy ktoś doda zdjęcie do udostępnionego albumu, którego jesteś częścią"), - "sharedWith": m61, + "sharedWith": m62, "sharedWithMe": MessageLookupByLibrary.simpleMessage("Udostępnione ze mną"), "sharedWithYou": @@ -1581,11 +1643,11 @@ class MessageLookup extends MessageLookupByLibrary { "Wyloguj z pozostałych urządzeń"), "signUpTerms": MessageLookupByLibrary.simpleMessage( "Akceptuję warunki korzystania z usługi i politykę prywatności"), - "singleFileDeleteFromDevice": m62, + "singleFileDeleteFromDevice": m63, "singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage( "To zostanie usunięte ze wszystkich albumów."), - "singleFileInBothLocalAndRemote": m63, - "singleFileInRemoteOnly": m64, + "singleFileInBothLocalAndRemote": m64, + "singleFileInRemoteOnly": m65, "skip": MessageLookupByLibrary.simpleMessage("Pomiń"), "social": MessageLookupByLibrary.simpleMessage("Społeczność"), "someItemsAreInBothEnteAndYourDevice": @@ -1634,10 +1696,10 @@ class MessageLookup extends MessageLookupByLibrary { "storageInGB": m1, "storageLimitExceeded": MessageLookupByLibrary.simpleMessage("Przekroczono limit pamięci"), - "storageUsageInfo": m65, + "storageUsageInfo": m66, "strongStrength": MessageLookupByLibrary.simpleMessage("Silne"), - "subAlreadyLinkedErrMessage": m66, - "subWillBeCancelledOn": m67, + "subAlreadyLinkedErrMessage": m67, + "subWillBeCancelledOn": m68, "subscribe": MessageLookupByLibrary.simpleMessage("Subskrybuj"), "subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage( "Potrzebujesz aktywnej płatnej subskrypcji, aby włączyć udostępnianie."), @@ -1654,7 +1716,7 @@ class MessageLookup extends MessageLookupByLibrary { "suggestFeatures": MessageLookupByLibrary.simpleMessage("Zaproponuj funkcje"), "support": MessageLookupByLibrary.simpleMessage("Wsparcie techniczne"), - "syncProgress": m68, + "syncProgress": m69, "syncStopped": MessageLookupByLibrary.simpleMessage("Synchronizacja zatrzymana"), "syncing": MessageLookupByLibrary.simpleMessage("Synchronizowanie..."), @@ -1665,6 +1727,9 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Stuknij, aby wprowadzić kod"), "tapToUnlock": MessageLookupByLibrary.simpleMessage("Naciśnij, aby odblokować"), + "tapToUpload": + MessageLookupByLibrary.simpleMessage("Naciśnij, aby przesłać"), + "tapToUploadIsIgnoredDue": m70, "tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage( "Wygląda na to, że coś poszło nie tak. Spróbuj ponownie po pewnym czasie. Jeśli błąd będzie się powtarzał, skontaktuj się z naszym zespołem pomocy technicznej."), "terminate": MessageLookupByLibrary.simpleMessage("Zakończ"), @@ -1680,7 +1745,7 @@ class MessageLookup extends MessageLookupByLibrary { "Pobieranie nie mogło zostać ukończone"), "theLinkYouAreTryingToAccessHasExpired": MessageLookupByLibrary.simpleMessage( - "The link you are trying to access has expired."), + "Link, do którego próbujesz uzyskać dostęp, wygasł."), "theRecoveryKeyYouEnteredIsIncorrect": MessageLookupByLibrary.simpleMessage( "Wprowadzony klucz odzyskiwania jest nieprawidłowy"), @@ -1688,7 +1753,7 @@ class MessageLookup extends MessageLookupByLibrary { "theseItemsWillBeDeletedFromYourDevice": MessageLookupByLibrary.simpleMessage( "Te elementy zostaną usunięte z Twojego urządzenia."), - "theyAlsoGetXGb": m70, + "theyAlsoGetXGb": m71, "theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage( "Zostaną one usunięte ze wszystkich albumów."), "thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage( @@ -1704,7 +1769,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Ten e-mail jest już używany"), "thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage( "Ten obraz nie posiada danych exif"), - "thisIsPersonVerificationId": m71, + "thisIsPersonVerificationId": m72, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "To jest Twój Identyfikator Weryfikacji"), "thisWillLogYouOutOfTheFollowingDevice": @@ -1728,7 +1793,7 @@ class MessageLookup extends MessageLookupByLibrary { "total": MessageLookupByLibrary.simpleMessage("ogółem"), "totalSize": MessageLookupByLibrary.simpleMessage("Całkowity rozmiar"), "trash": MessageLookupByLibrary.simpleMessage("Kosz"), - "trashDaysLeft": m72, + "trashDaysLeft": m73, "trim": MessageLookupByLibrary.simpleMessage("Przytnij"), "tryAgain": MessageLookupByLibrary.simpleMessage("Spróbuj ponownie"), "turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage( @@ -1749,6 +1814,7 @@ class MessageLookup extends MessageLookupByLibrary { "Pomyślnie zresetowano uwierzytelnianie dwustopniowe"), "twofactorSetup": MessageLookupByLibrary.simpleMessage( "Uwierzytelnianie dwustopniowe"), + "typeOfGallerGallerytypeIsNotSupportedForRename": m74, "unarchive": MessageLookupByLibrary.simpleMessage("Przywróć z archiwum"), "unarchiveAlbum": @@ -1773,9 +1839,10 @@ class MessageLookup extends MessageLookupByLibrary { "updatingFolderSelection": MessageLookupByLibrary.simpleMessage( "Aktualizowanie wyboru folderu..."), "upgrade": MessageLookupByLibrary.simpleMessage("Ulepsz"), + "uploadIsIgnoredDueToIgnorereason": m75, "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage( "Przesyłanie plików do albumu..."), - "uploadingMultipleMemories": m75, + "uploadingMultipleMemories": m76, "uploadingSingleMemory": MessageLookupByLibrary.simpleMessage( "Zachowywanie 1 wspomnienia..."), "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( @@ -1791,7 +1858,7 @@ class MessageLookup extends MessageLookupByLibrary { "useSelectedPhoto": MessageLookupByLibrary.simpleMessage("Użyj zaznaczone zdjęcie"), "usedSpace": MessageLookupByLibrary.simpleMessage("Zajęta przestrzeń"), - "validTill": m76, + "validTill": m77, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "Weryfikacja nie powiodła się, spróbuj ponownie"), @@ -1800,7 +1867,7 @@ class MessageLookup extends MessageLookupByLibrary { "verify": MessageLookupByLibrary.simpleMessage("Zweryfikuj"), "verifyEmail": MessageLookupByLibrary.simpleMessage("Zweryfikuj adres e-mail"), - "verifyEmailID": m77, + "verifyEmailID": m78, "verifyIDLabel": MessageLookupByLibrary.simpleMessage("Zweryfikuj"), "verifyPasskey": MessageLookupByLibrary.simpleMessage("Zweryfikuj klucz dostępu"), @@ -1826,6 +1893,7 @@ class MessageLookup extends MessageLookupByLibrary { "viewRecoveryKey": MessageLookupByLibrary.simpleMessage("Zobacz klucz odzyskiwania"), "viewer": MessageLookupByLibrary.simpleMessage("Widz"), + "viewersSuccessfullyAdded": m79, "visitWebToManage": MessageLookupByLibrary.simpleMessage( "Odwiedź stronę web.ente.io, aby zarządzać subskrypcją"), "waitingForVerification": MessageLookupByLibrary.simpleMessage( @@ -1841,8 +1909,9 @@ class MessageLookup extends MessageLookupByLibrary { "weakStrength": MessageLookupByLibrary.simpleMessage("Słabe"), "welcomeBack": MessageLookupByLibrary.simpleMessage("Witaj ponownie!"), "whatsNew": MessageLookupByLibrary.simpleMessage("Co nowego"), + "yearShort": MessageLookupByLibrary.simpleMessage("r"), "yearly": MessageLookupByLibrary.simpleMessage("Rocznie"), - "yearsAgo": m79, + "yearsAgo": m80, "yes": MessageLookupByLibrary.simpleMessage("Tak"), "yesCancel": MessageLookupByLibrary.simpleMessage("Tak, anuluj"), "yesConvertToViewer": @@ -1874,7 +1943,7 @@ class MessageLookup extends MessageLookupByLibrary { "Nie możesz udostępnić samemu sobie"), "youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage( "Nie masz żadnych zarchiwizowanych elementów."), - "youHaveSuccessfullyFreedUp": m80, + "youHaveSuccessfullyFreedUp": m81, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage( "Twoje konto zostało usunięte"), "yourMap": MessageLookupByLibrary.simpleMessage("Twoja mapa"), diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index 438a1f90fe..6a488eb5b1 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -10498,6 +10498,286 @@ class S { args: [], ); } + + /// `Emergency Contacts` + String get emergencyContacts { + return Intl.message( + 'Emergency Contacts', + name: 'emergencyContacts', + desc: '', + args: [], + ); + } + + /// `Accept Invite` + String get acceptTrustInvite { + return Intl.message( + 'Accept Invite', + name: 'acceptTrustInvite', + desc: '', + args: [], + ); + } + + /// `Decline Invite` + String get declineTrustInvite { + return Intl.message( + 'Decline Invite', + name: 'declineTrustInvite', + desc: '', + args: [], + ); + } + + /// `Remove yourself as trusted contact` + String get removeYourselfAsTrustedContact { + return Intl.message( + 'Remove yourself as trusted contact', + name: 'removeYourselfAsTrustedContact', + desc: '', + args: [], + ); + } + + /// `Legacy` + String get legacy { + return Intl.message( + 'Legacy', + name: 'legacy', + desc: '', + args: [], + ); + } + + /// `Legacy allows trusted contacts to access your account in your absence.` + String get legacyPageDesc { + return Intl.message( + 'Legacy allows trusted contacts to access your account in your absence.', + name: 'legacyPageDesc', + desc: '', + args: [], + ); + } + + /// `Trusted contacts can initiate account recovery, and if not blocked within 30 days, reset your password and access your account.` + String get legacyPageDesc2 { + return Intl.message( + 'Trusted contacts can initiate account recovery, and if not blocked within 30 days, reset your password and access your account.', + name: 'legacyPageDesc2', + desc: '', + args: [], + ); + } + + /// `Legacy accounts` + String get legacyAccounts { + return Intl.message( + 'Legacy accounts', + name: 'legacyAccounts', + desc: '', + args: [], + ); + } + + /// `Trusted contacts` + String get trustedContacts { + return Intl.message( + 'Trusted contacts', + name: 'trustedContacts', + desc: '', + args: [], + ); + } + + /// `Add Trusted Contact` + String get addTrustedContact { + return Intl.message( + 'Add Trusted Contact', + name: 'addTrustedContact', + desc: '', + args: [], + ); + } + + /// `Remove invite` + String get removeInvite { + return Intl.message( + 'Remove invite', + name: 'removeInvite', + desc: '', + args: [], + ); + } + + /// `A trusted contact is trying to access your account` + String get recoveryWarning { + return Intl.message( + 'A trusted contact is trying to access your account', + name: 'recoveryWarning', + desc: '', + args: [], + ); + } + + /// `Reject recovery` + String get rejectRecovery { + return Intl.message( + 'Reject recovery', + name: 'rejectRecovery', + desc: '', + args: [], + ); + } + + /// `Recovery initiated` + String get recoveryInitiated { + return Intl.message( + 'Recovery initiated', + name: 'recoveryInitiated', + desc: '', + args: [], + ); + } + + /// `You can access the account after {days} days. A notification will be sent to {email}.` + String recoveryInitiatedDesc(int days, String email) { + return Intl.message( + 'You can access the account after $days days. A notification will be sent to $email.', + name: 'recoveryInitiatedDesc', + desc: '', + args: [days, email], + ); + } + + /// `Cancel recovery` + String get cancelAccountRecovery { + return Intl.message( + 'Cancel recovery', + name: 'cancelAccountRecovery', + desc: '', + args: [], + ); + } + + /// `Recover account` + String get recoveryAccount { + return Intl.message( + 'Recover account', + name: 'recoveryAccount', + desc: '', + args: [], + ); + } + + /// `Are you sure you want to cancel recovery?` + String get cancelAccountRecoveryBody { + return Intl.message( + 'Are you sure you want to cancel recovery?', + name: 'cancelAccountRecoveryBody', + desc: '', + args: [], + ); + } + + /// `Start recovery` + String get startAccountRecoveryTitle { + return Intl.message( + 'Start recovery', + name: 'startAccountRecoveryTitle', + desc: '', + args: [], + ); + } + + /// `Trusted contact can help in recovering your data.` + String get whyAddTrustContact { + return Intl.message( + 'Trusted contact can help in recovering your data.', + name: 'whyAddTrustContact', + desc: '', + args: [], + ); + } + + /// `You can now recover {email}'s account by setting a new password.` + String recoveryReady(String email) { + return Intl.message( + 'You can now recover $email\'s account by setting a new password.', + name: 'recoveryReady', + desc: '', + args: [email], + ); + } + + /// `{email} is trying to recover your account.` + String recoveryWarningBody(Object email) { + return Intl.message( + '$email is trying to recover your account.', + name: 'recoveryWarningBody', + desc: '', + args: [email], + ); + } + + /// `You have been invited to be a legacy contact by {email}.` + String trustedInviteBody(Object email) { + return Intl.message( + 'You have been invited to be a legacy contact by $email.', + name: 'trustedInviteBody', + desc: '', + args: [email], + ); + } + + /// `Warning` + String get warning { + return Intl.message( + 'Warning', + name: 'warning', + desc: '', + args: [], + ); + } + + /// `Proceed` + String get proceed { + return Intl.message( + 'Proceed', + name: 'proceed', + desc: '', + args: [], + ); + } + + /// `You are about to add {email} as a trusted contact. They will be able to recover your account if you are absent for {numOfDays} days.` + String confirmAddingTrustedContact(String email, int numOfDays) { + return Intl.message( + 'You are about to add $email as a trusted contact. They will be able to recover your account if you are absent for $numOfDays days.', + name: 'confirmAddingTrustedContact', + desc: '', + args: [email, numOfDays], + ); + } + + /// `{email} has invited you to be a trusted contact` + String legacyInvite(Object email) { + return Intl.message( + '$email has invited you to be a trusted contact', + name: 'legacyInvite', + desc: '', + args: [email], + ); + } + + /// `Please authenticate to manage your trusted contacts` + String get authToManageLegacy { + return Intl.message( + 'Please authenticate to manage your trusted contacts', + name: 'authToManageLegacy', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { @@ -10528,6 +10808,7 @@ class AppLocalizationDelegate extends LocalizationsDelegate { Locale.fromSubtags(languageCode: 'km'), Locale.fromSubtags(languageCode: 'ko'), Locale.fromSubtags(languageCode: 'lt'), + Locale.fromSubtags(languageCode: 'ml'), Locale.fromSubtags(languageCode: 'nl'), Locale.fromSubtags(languageCode: 'no'), Locale.fromSubtags(languageCode: 'pl'), diff --git a/mobile/lib/l10n/intl_de.arb b/mobile/lib/l10n/intl_de.arb index 34f5b1a283..e5cf109c05 100644 --- a/mobile/lib/l10n/intl_de.arb +++ b/mobile/lib/l10n/intl_de.arb @@ -12,7 +12,7 @@ "deleteAccountFeedbackPrompt": "Wir bedauern sehr, dass du dein Konto löschen möchtest. Du würdest uns sehr helfen, wenn du uns kurz einige Gründe hierfür nennen könntest.", "feedback": "Rückmeldung", "kindlyHelpUsWithThisInformation": "Bitte gib diese Daten ein", - "confirmDeletePrompt": "Ja, ich möchte dieses Konto und alle enthaltenen Daten über alle Apps endgültig und unwiderruflich löschen.", + "confirmDeletePrompt": "Ja, ich möchte dieses Konto und alle enthaltenen Daten über alle Apps hinweg endgültig löschen.", "confirmAccountDeletion": "Kontolöschung bestätigen", "deleteAccountPermanentlyButton": "Konto unwiderruflich löschen", "yourAccountHasBeenDeleted": "Dein Benutzerkonto wurde gelöscht", @@ -23,8 +23,8 @@ "deleteReason4": "Mein Grund ist nicht aufgeführt", "sendEmail": "E-Mail senden", "deleteRequestSLAText": "Deine Anfrage wird innerhalb von 72 Stunden bearbeitet.", - "deleteEmailRequest": "Bitte sende eine E-Mail an account-deletion@ente.io von Deiner bei uns hinterlegten E-Mail-Adresse.", - "entePhotosPerm": "", + "deleteEmailRequest": "Bitte sende eine E-Mail an account-deletion@ente.io von Ihrer bei uns hinterlegten E-Mail-Adresse.", + "entePhotosPerm": "Ente benötigt Berechtigung, um Ihre Fotos zu sichern", "ok": "Ok", "createAccount": "Konto erstellen", "createNewAccount": "Neues Konto erstellen", @@ -1246,7 +1246,6 @@ "deviceCodeHint": "Code eingeben", "joinDiscord": "Discord beitreten", "locations": "Orte", - "descriptions": "Beschreibungen", "addAName": "Füge einen Namen hinzu", "findThemQuickly": "Finde sie schnell", "@findThemQuickly": { @@ -1515,5 +1514,65 @@ "openAlbumInBrowserTitle": "Bitte nutze die Web-App, um Fotos zu diesem Album hinzuzufügen", "allow": "Erlauben", "allowAppToOpenSharedAlbumLinks": "Erlaube der App, geteilte Album-Links zu öffnen", - "seePublicAlbumLinksInApp": "Öffentliche Album-Links in der App ansehen" + "seePublicAlbumLinksInApp": "Öffentliche Album-Links in der App ansehen", + "emergencyContacts": "Notfallkontakte", + "acceptTrustInvite": "Einladung annehmen", + "declineTrustInvite": "Einladung ablehnen", + "removeYourselfAsTrustedContact": "Entferne dich als vertrauenswürdigen Kontakt", + "legacy": "Digitales Erbe", + "legacyPageDesc": "Das digitale Erbe erlaubt vertrauenswürdigen Kontakten den Zugriff auf dein Konto in deiner Abwesenheit.", + "legacyPageDesc2": "Vertrauenswürdige Kontakte können eine Kontowiederherstellung einleiten und, wenn dies nicht innerhalb von 30 Tagen blockiert wird, dein Passwort und den Kontozugriff zurücksetzen.", + "legacyAccounts": "Digital geerbte Konten", + "trustedContacts": "Vertrauenswürdige Kontakte", + "addTrustedContact": "Vertrauenswürdigen Kontakt hinzufügen", + "removeInvite": "Einladung entfernen", + "recoveryWarning": "Ein vertrauenswürdiger Kontakt versucht, auf dein Konto zuzugreifen", + "rejectRecovery": "Wiederherstellung ablehnen", + "recoveryInitiated": "Wiederherstellung gestartet", + "recoveryInitiatedDesc": "Du kannst nach {days} Tagen auf das Konto zugreifen. Eine Benachrichtigung wird an {email} versendet.", + "@recoveryInitiatedDesc": { + "placeholders": { + "days": { + "type": "int", + "example": "30" + }, + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "cancelAccountRecovery": "Wiederherstellung abbrechen", + "recoveryAccount": "Konto wiederherstellen", + "cancelAccountRecoveryBody": "Bist du sicher, dass du die Wiederherstellung abbrechen möchtest?", + "startAccountRecoveryTitle": "Wiederherstellung starten", + "whyAddTrustContact": "Ein vertrauenswürdiger Kontakt kann helfen, deine Daten wiederherzustellen.", + "recoveryReady": "Du kannst jetzt das Konto von {email} wiederherstellen, indem du ein neues Passwort setzt.", + "@recoveryReady": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "recoveryWarningBody": "{email} versucht, dein Konto wiederherzustellen.", + "trustedInviteBody": "Du wurdest von {email} eingeladen, ein Kontakt für das digitale Erbe zu werden.", + "warning": "Warnung", + "proceed": "Fortfahren", + "confirmAddingTrustedContact": "Du bist dabei, {email} als vertrauenswürdigen Kontakt hinzuzufügen. Die Person wird in der Lage sein, dein Konto wiederherzustellen, wenn du für {numOfDays} Tage abwesend bist.", + "@confirmAddingTrustedContact": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + }, + "numOfDays": { + "type": "int", + "example": "30" + } + } + }, + "legacyInvite": "{email} hat dich eingeladen, ein vertrauenswürdiger Kontakt zu werden", + "authToManageLegacy": "Bitte authentifiziere dich, um deine vertrauenswürdigen Kontakte zu verwalten" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index fe015edcb4..3e56c5edd8 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1514,5 +1514,66 @@ "openAlbumInBrowserTitle": "Please use the web app to add photos to this album", "allow": "Allow", "allowAppToOpenSharedAlbumLinks": "Allow app to open shared album links", - "seePublicAlbumLinksInApp": "See public album links in app" + "seePublicAlbumLinksInApp": "See public album links in app", + "emergencyContacts": "Emergency Contacts", + "acceptTrustInvite": "Accept Invite", + "declineTrustInvite": "Decline Invite", + "removeYourselfAsTrustedContact": "Remove yourself as trusted contact", + + "legacy" : "Legacy", + "legacyPageDesc": "Legacy allows trusted contacts to access your account in your absence.", + "legacyPageDesc2": "Trusted contacts can initiate account recovery, and if not blocked within 30 days, reset your password and access your account.", + "legacyAccounts": "Legacy accounts", + "trustedContacts": "Trusted contacts", + "addTrustedContact": "Add Trusted Contact", + "removeInvite": "Remove invite", + "recoveryWarning": "A trusted contact is trying to access your account", + "rejectRecovery": "Reject recovery", + "recoveryInitiated": "Recovery initiated", + "recoveryInitiatedDesc": "You can access the account after {days} days. A notification will be sent to {email}.", + "@recoveryInitiatedDesc": { + "placeholders": { + "days": { + "type": "int", + "example": "30" + }, + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "cancelAccountRecovery": "Cancel recovery", + "recoveryAccount":"Recover account", + "cancelAccountRecoveryBody": "Are you sure you want to cancel recovery?", + "startAccountRecoveryTitle": "Start recovery", + "whyAddTrustContact": "Trusted contact can help in recovering your data.", + "recoveryReady": "You can now recover {email}'s account by setting a new password.", + "@recoveryReady": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "recoveryWarningBody": "{email} is trying to recover your account.", + "trustedInviteBody": "You have been invited to be a legacy contact by {email}.", + "warning": "Warning", + "proceed": "Proceed", + "confirmAddingTrustedContact": "You are about to add {email} as a trusted contact. They will be able to recover your account if you are absent for {numOfDays} days.", + "@confirmAddingTrustedContact": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + }, + "numOfDays": { + "type": "int", + "example": "30" + } + } + }, + "legacyInvite": "{email} has invited you to be a trusted contact", + "authToManageLegacy": "Please authenticate to manage your trusted contacts" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_es.arb b/mobile/lib/l10n/intl_es.arb index 0245feb26d..089b7e6ac8 100644 --- a/mobile/lib/l10n/intl_es.arb +++ b/mobile/lib/l10n/intl_es.arb @@ -184,7 +184,7 @@ "description": "Switch button to enable uploading photos to a public link" }, "allowAddPhotosDescription": "Permitir a las personas con el enlace añadir fotos al álbum compartido.", - "passwordLock": "Bloqueo por contraseña", + "passwordLock": "Bloqueo con contraseña", "disableDownloadWarningTitle": "Por favor, ten en cuenta", "disableDownloadWarningBody": "Los espectadores todavía pueden tomar capturas de pantalla o guardar una copia de tus fotos usando herramientas externas", "allowDownloads": "Permitir descargas", @@ -477,7 +477,7 @@ "backupStatusDescription": "Los elementos con copia seguridad aparecerán aquí", "backupOverMobileData": "Copia de seguridad usando datos móviles", "backupVideos": "Copia de seguridad de vídeos", - "disableAutoLock": "Desactivar autobloqueo", + "disableAutoLock": "Desactivar bloqueo automático", "deviceLockExplanation": "Deshabilita el bloqueo de pantalla del dispositivo cuando Ente está en primer plano y haya una copia de seguridad en curso. Normalmente esto no es necesario, pero puede ayudar a que las grandes cargas y las importaciones iniciales de grandes bibliotecas se completen más rápido.", "about": "Acerca de", "weAreOpenSource": "¡Somos de código abierto!", @@ -1109,7 +1109,7 @@ "@androidCancelButton": { "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." }, - "androidSignInTitle": "Autentificación requerida", + "androidSignInTitle": "Se necesita autenticación biométrica", "@androidSignInTitle": { "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." }, @@ -1229,7 +1229,7 @@ "selectALocationFirst": "Primero, selecciona una ubicación", "changeLocationOfSelectedItems": "¿Cambiar la ubicación de los elementos seleccionados?", "editsToLocationWillOnlyBeSeenWithinEnte": "Las ediciones a la ubicación sólo se verán dentro de Ente", - "cleanUncategorized": "Limpiar no categorizado", + "cleanUncategorized": "Limpiar sin categorizar", "cleanUncategorizedDescription": "Elimina todos los archivos de Sin categorizar que están presentes en otros álbumes", "waitingForVerification": "Esperando verificación...", "passkey": "Clave de acceso", @@ -1246,7 +1246,6 @@ "deviceCodeHint": "Introduce el código", "joinDiscord": "Únete al Discord", "locations": "Ubicaciones", - "descriptions": "Descripciones", "addAName": "Añade un nombre", "findThemQuickly": "Encuéntralos rápidamente", "@findThemQuickly": { @@ -1319,8 +1318,8 @@ "panorama": "Panorama", "reenterPassword": "Rescribe tu contraseña", "reenterPin": "Rescribe tu PIN", - "deviceLock": "Dispositivo Bloqueado", - "pinLock": "PIN Bloqueado", + "deviceLock": "Bloqueo del dispositivo", + "pinLock": "Bloqueo con Pin", "next": "Siguiente", "setNewPassword": "Ingresa tu nueva contraseña", "enterPin": "Ingresa tu contraseña", @@ -1330,7 +1329,7 @@ "tapToUnlock": "Toca para desbloquear", "tooManyIncorrectAttempts": "Demasiados intentos incorrectos", "videoInfo": "Información de video", - "autoLock": "Autobloqueo", + "autoLock": "Bloqueo automático", "immediately": "Inmediatamente", "autoLockFeatureDescription": "Tiempo después de que la aplicación esté en segundo plano", "hideContent": "Ocultar contenido", @@ -1515,5 +1514,65 @@ "openAlbumInBrowserTitle": "Por favor, utiliza la aplicación web para añadir fotos a este álbum", "allow": "Permitir", "allowAppToOpenSharedAlbumLinks": "Permitir a la aplicación abrir enlaces de álbum compartidos", - "seePublicAlbumLinksInApp": "Ver enlaces del álbum público en la aplicación" + "seePublicAlbumLinksInApp": "Ver enlaces del álbum público en la aplicación", + "emergencyContacts": "Contactos de emergencia", + "acceptTrustInvite": "Aceptar invitación", + "declineTrustInvite": "Rechazar invitación", + "removeYourselfAsTrustedContact": "Quitarse como contacto de confianza", + "legacy": "Legado", + "legacyPageDesc": "Legado permite a los contactos de confianza acceder a su cuenta en su ausencia.", + "legacyPageDesc2": "Los contactos de confianza pueden iniciar la recuperación de la cuenta, y si no están bloqueados en un plazo de 30 días, restablecer su contraseña y acceder a su cuenta.", + "legacyAccounts": "Cuentas legadas", + "trustedContacts": "Contactos de confianza", + "addTrustedContact": "Añadir contacto de confianza", + "removeInvite": "Eliminar invitación", + "recoveryWarning": "Un contacto de confianza está intentando acceder a tu cuenta", + "rejectRecovery": "Rechazar la recuperación", + "recoveryInitiated": "Recuperación iniciada", + "recoveryInitiatedDesc": "Puedes acceder a la cuenta después de {days} días. Se enviará una notificación a {email}.", + "@recoveryInitiatedDesc": { + "placeholders": { + "days": { + "type": "int", + "example": "30" + }, + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "cancelAccountRecovery": "Cancelar la recuperación", + "recoveryAccount": "Recuperar cuenta", + "cancelAccountRecoveryBody": "¿Estás seguro de que quieres cancelar la recuperación?", + "startAccountRecoveryTitle": "Iniciar la recuperación", + "whyAddTrustContact": "Un contacto de confianza puede ayudar a recuperar sus datos.", + "recoveryReady": "Ahora puedes recuperar la cuenta de {email} estableciendo una nueva contraseña.", + "@recoveryReady": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "recoveryWarningBody": "{email} está intentando recuperar tu cuenta.", + "trustedInviteBody": "Has sido invitado a ser un contacto legado por {email}.", + "warning": "Advertencia", + "proceed": "Continuar", + "confirmAddingTrustedContact": "Estás a punto de añadir {email} como un contacto de confianza. Esta persona podrá recuperar tu cuenta si no estás durante {numOfDays} días.", + "@confirmAddingTrustedContact": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + }, + "numOfDays": { + "type": "int", + "example": "30" + } + } + }, + "legacyInvite": "{email} te ha invitado a ser un contacto de confianza", + "authToManageLegacy": "Por favor, autentícate para administrar tus contactos de confianza" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_fr.arb b/mobile/lib/l10n/intl_fr.arb index e5284b778b..6d1c299ff7 100644 --- a/mobile/lib/l10n/intl_fr.arb +++ b/mobile/lib/l10n/intl_fr.arb @@ -359,7 +359,7 @@ "singleFileInBothLocalAndRemote": "Cette {fileType} est à la fois sur ente et sur votre appareil.", "singleFileInRemoteOnly": "Cette {fileType} sera supprimée de l'Ente.", "singleFileDeleteFromDevice": "Elle {fileType} sera supprimée de votre appareil.", - "deleteFromEnte": "Supprimé de Ente", + "deleteFromEnte": "Supprimer de Ente", "yesDelete": "Oui, supprimer", "movedToTrash": "Déplacé dans la corbeille", "deleteFromDevice": "Supprimer de l'appareil", @@ -951,7 +951,7 @@ "emailChangedTo": "L'e-mail a été changé en {newEmail}", "verifying": "Validation en cours...", "disablingTwofactorAuthentication": "Désactiver la double-authentification...", - "allMemoriesPreserved": "Tous les souvenirs conservés", + "allMemoriesPreserved": "Tous les souvenirs sont conservés", "loadingGallery": "Chargement de la galerie...", "syncing": "En cours de synchronisation...", "encryptingBackup": "Chiffrement de la sauvegarde...", @@ -1246,7 +1246,6 @@ "deviceCodeHint": "Saisissez le code", "joinDiscord": "Rejoindre Discord", "locations": "Emplacements", - "descriptions": "Descriptions", "addAName": "Ajouter un nom", "findThemQuickly": "Trouvez-les rapidement", "@findThemQuickly": { @@ -1515,5 +1514,65 @@ "openAlbumInBrowserTitle": "Veuillez utiliser l'application web pour ajouter des photos à cet album", "allow": "Autoriser", "allowAppToOpenSharedAlbumLinks": "Autoriser l'application à ouvrir les liens d'albums partagés", - "seePublicAlbumLinksInApp": "Ouvrir les liens des albums publics dans l'application" + "seePublicAlbumLinksInApp": "Ouvrir les liens des albums publics dans l'application", + "emergencyContacts": "Contacts d'urgence", + "acceptTrustInvite": "Accepter l'invitation", + "declineTrustInvite": "Refuser l’invitation", + "removeYourselfAsTrustedContact": "Retirez-vous comme contact de confiance", + "legacy": "Héritage", + "legacyPageDesc": "Héritage permet aux contacts de confiance d'accéder à votre compte en votre absence.", + "legacyPageDesc2": "Les contacts de confiance peuvent initier la récupération du compte et, s'ils ne sont pas bloqués dans les 30 jours qui suivent, peuvent réinitialiser votre mot de passe et accéder à votre compte.", + "legacyAccounts": "Comptes hérités", + "trustedContacts": "Contacts de confiance", + "addTrustedContact": "Ajouter un contact de confiance", + "removeInvite": "Supprimer l’Invitation", + "recoveryWarning": "Un contact de confiance tente d'accéder à votre compte", + "rejectRecovery": "Rejeter la récupération", + "recoveryInitiated": "Récupération initiée", + "recoveryInitiatedDesc": "Vous pourrez accéder au compte d'ici {days} jours. Une notification sera envoyée à {email}.", + "@recoveryInitiatedDesc": { + "placeholders": { + "days": { + "type": "int", + "example": "30" + }, + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "cancelAccountRecovery": "Annuler la récupération", + "recoveryAccount": "Récupérer un compte", + "cancelAccountRecoveryBody": "Êtes-vous sûr de vouloir annuler la récupération ?", + "startAccountRecoveryTitle": "Démarrer la récupération", + "whyAddTrustContact": "Un contact de confiance peut vous aider à récupérer vos données.", + "recoveryReady": "Vous pouvez maintenant récupérer le compte de {email} en définissant un nouveau mot de passe.", + "@recoveryReady": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "recoveryWarningBody": "{email} tente de récupérer votre compte.", + "trustedInviteBody": "Vous avez été invité(e) à être un(e) héritier(e) par {email}.", + "warning": "Attention", + "proceed": "Procéder", + "confirmAddingTrustedContact": "Vous êtes sur le point d'ajouter {email} en tant que contact sûr. Il pourra récupérer votre compte si vous êtes absent pendant {numOfDays} jours.", + "@confirmAddingTrustedContact": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + }, + "numOfDays": { + "type": "int", + "example": "30" + } + } + }, + "legacyInvite": "{email} vous a invité à être un contact sûr", + "authToManageLegacy": "Veuillez vous authentifier pour gérer vos contacts de confiance" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_id.arb b/mobile/lib/l10n/intl_id.arb index 0c0df6a830..21ff8eed39 100644 --- a/mobile/lib/l10n/intl_id.arb +++ b/mobile/lib/l10n/intl_id.arb @@ -1094,7 +1094,6 @@ "castInstruction": "Buka cast.ente.io pada perangkat yang ingin kamu tautkan.\n\nMasukkan kode yang ditampilkan untuk memutar album di TV.", "deviceCodeHint": "Masukkan kode", "joinDiscord": "Bergabung ke Discord", - "descriptions": "Keterangan", "addAName": "Tambahkan nama", "findPeopleByName": "Telusuri orang dengan mudah menggunakan nama", "addCollaborators": "{count, plural, other {Tambahkan kolaborator}}", diff --git a/mobile/lib/l10n/intl_it.arb b/mobile/lib/l10n/intl_it.arb index 05358e4fad..d9e0b56274 100644 --- a/mobile/lib/l10n/intl_it.arb +++ b/mobile/lib/l10n/intl_it.arb @@ -1233,7 +1233,6 @@ "deviceCodeHint": "Inserisci il codice", "joinDiscord": "Unisciti a Discord", "locations": "Luoghi", - "descriptions": "Descrizioni", "addAName": "Aggiungi un nome", "findThemQuickly": "Trovali rapidamente", "@findThemQuickly": { diff --git a/mobile/lib/l10n/intl_ja.arb b/mobile/lib/l10n/intl_ja.arb index dda6665b03..a6bccc9a86 100644 --- a/mobile/lib/l10n/intl_ja.arb +++ b/mobile/lib/l10n/intl_ja.arb @@ -1233,7 +1233,6 @@ "deviceCodeHint": "コードを入力する", "joinDiscord": "Discordに参加", "locations": "場所", - "descriptions": "説明文", "addAName": "名前を追加", "findThemQuickly": "すばやく見つける", "@findThemQuickly": { diff --git a/mobile/lib/l10n/intl_lt.arb b/mobile/lib/l10n/intl_lt.arb index 6563880854..1664e5a700 100644 --- a/mobile/lib/l10n/intl_lt.arb +++ b/mobile/lib/l10n/intl_lt.arb @@ -203,6 +203,7 @@ "enterPassword": "Įveskite slaptažodį", "removeLink": "Šalinti nuorodą", "manageLink": "Tvarkyti nuorodą", + "linkExpiresOn": "Nuoroda nebegalios {expiryTime}", "albumUpdated": "Atnaujintas albumas", "never": "Niekada", "custom": "Pasirinktinis", @@ -215,16 +216,39 @@ "after1Month": "Po 1 mėnesio", "after1Year": "Po 1 metų", "manageParticipants": "Tvarkyti", + "albumParticipantsCount": "{count, plural, one {{count} dalyvis} few {{count} dalyviai} many {{count} dalyvio}=0 {0 dalyvių} =1 {1 dalyvis} other {{count} dalyvių}}", + "@albumParticipantsCount": { + "placeholders": { + "count": { + "type": "int", + "example": "5" + } + }, + "description": "Number of participants in an album, including the album owner." + }, "collabLinkSectionDescription": "Sukurkite nuorodą, kad asmenys galėtų pridėti ir peržiūrėti nuotraukas bendrinamame albume, nereikalaujant „Ente“ programos ar paskyros. Puikiai tinka įvykių nuotraukoms rinkti.", "collectPhotos": "Rinkti nuotraukas", + "collaborativeLink": "Bendradarbiavimo nuoroda", "shareWithNonenteUsers": "Bendrinkite su ne „Ente“ naudotojais.", "createPublicLink": "Kurti viešą nuorodą", "sendLink": "Siųsti nuorodą", "copyLink": "Kopijuoti nuorodą", + "linkHasExpired": "Nuoroda nebegalioja", + "publicLinkEnabled": "Įjungta viešoji nuoroda", + "shareALink": "Bendrinkite nuorodą", "sharedAlbumSectionDescription": "Sukurkite bendrinamus ir bendradarbiaujamus albumus su kitais „Ente“ naudotojais, įskaitant naudotojus nemokamuose planuose.", + "shareWithPeopleSectionTitle": "{numberOfPeople, plural, one {Bendrinima su {numberOfPeople} asmeniu} few {Bendrinima su {numberOfPeople} asmenims} many {Bendrinima su {numberOfPeople} asmens}=0 {Bendrinti su konkrečiais asmenimis} =1 {Bendrinima su 1 asmeniu} other {Bendrinima su {numberOfPeople} asmenų}}", + "@shareWithPeopleSectionTitle": { + "placeholders": { + "numberOfPeople": { + "type": "int", + "example": "2" + } + } + }, "thisIsYourVerificationId": "Tai – jūsų patvirtinimo ID", "someoneSharingAlbumsWithYouShouldSeeTheSameId": "Asmuo, kuris bendrina albumus su jumis, savo įrenginyje turėtų matyti tą patį ID.", - "howToViewShareeVerificationID": "Paprašykite jų ilgai paspausti savo el. pašto adresą nustatymų ekrane ir patvirtinkite, kad abiejų įrenginių ID sutampa.", + "howToViewShareeVerificationID": "Paprašykite jų ilgai paspausti savo el. pašto adresą nustatymų ekrane ir patvirtinti, kad abiejų įrenginių ID sutampa.", "thisIsPersonVerificationId": "Tai – {email} patvirtinimo ID", "@thisIsPersonVerificationId": { "placeholders": { @@ -247,7 +271,9 @@ "enterCodeDescription": "Įveskite draugo pateiktą kodą, kad gautumėte nemokamą saugyklą abiem.", "apply": "Taikyti", "failedToApplyCode": "Nepavyko pritaikyti kodo.", + "enterReferralCode": "Įveskite rekomendacijos kodą", "codeAppliedPageTitle": "Pritaikytas kodas", + "changeYourReferralCode": "Keisti savo rekomendacijos kodą", "change": "Keisti", "unavailableReferralCode": "Atsiprašome, šis kodas nepasiekiamas.", "codeChangeLimitReached": "Atsiprašome, pasiekėte kodo pakeitimų ribą.", @@ -257,13 +283,31 @@ "description": "Used to indicate storage claimed, like 10GB Claimed" }, "claimMore": "Gaukite daugiau!", + "freeStorageOnReferralSuccess": "{storageAmountInGB} GB kiekvieną kartą, kai kas nors užsiregistruoja mokamam planui ir pritaiko jūsų kodą.", + "shareTextReferralCode": "„Ente“ rekomendacijos kodas: {referralCode} \n\nTaikykite jį per Nustatymai → Bendrieji → Rekomendacijos, kad gautumėte {referralStorageInGB} GB nemokamai po to, kai užsiregistruosite mokamam planui.\n\nhttps://ente.io", "inviteYourFriends": "Kviesti savo draugus", + "failedToFetchReferralDetails": "Nepavyksta gauti rekomendacijos informacijos. Bandykite dar kartą vėliau.", "referralStep1": "1. Duokite šį kodą savo draugams", "referralStep2": "2. Jie užsiregistruoja mokamą planą", "referralStep3": "3. Abu gaunate {storageInGB} GB* nemokamai", + "referralsAreCurrentlyPaused": "Šiuo metu rekomendacijos yra pristabdytos", "youCanAtMaxDoubleYourStorage": "* Galite daugiausiai padvigubinti savo saugyklą.", + "claimedStorageSoFar": "{isFamilyMember, select, true {Jūsų šeima gavo {storageAmountInGb} GB iki šiol} false {Jūs gavote {storageAmountInGb} GB iki šiol} other {Jūs gavote {storageAmountInGb} GB iki šiol.}}", + "@claimedStorageSoFar": { + "placeholders": { + "isFamilyMember": { + "type": "String", + "example": "true" + }, + "storageAmountInGb": { + "type": "int", + "example": "10" + } + } + }, "faq": "DUK", "total": "iš viso", + "usableReferralStorageInfo": "Naudojama saugykla ribojama pagal jūsų dabartinį planą. Perteklinė gauta saugykla automatiškai taps tinkama naudoti, kai pakeisite planą.", "removeFromAlbumTitle": "Pašalinti iš albumo?", "removeFromAlbum": "Šalinti iš albumo", "itemsWillBeRemovedFromAlbum": "Pasirinkti elementai bus pašalinti iš šio albumo", @@ -272,17 +316,20 @@ "subscribeToEnableSharing": "Kad įjungtumėte bendrinimą, reikia aktyvios mokamos prenumeratos.", "subscribe": "Prenumeruoti", "canOnlyRemoveFilesOwnedByYou": "Galima pašalinti tik jums priklausančius failus", + "deleteSharedAlbum": "Ištrinti bendrinamą albumą?", "deleteAlbum": "Ištrinti albumą", "deleteAlbumDialog": "Taip pat ištrinti šiame albume esančias nuotraukas (ir vaizdo įrašus) iš visų kitų albumų, kuriuose jos yra dalis?", + "deleteSharedAlbumDialogBody": "Albumas bus ištrintas visiems.\n\nPrarasite prieigą prie bendrinamų nuotraukų, esančių šiame albume ir priklausančių kitiems.", "yesRemove": "Taip, šalinti", "creatingLink": "Kuriama nuoroda...", "removeWithQuestionMark": "Šalinti?", - "removeParticipantBody": "{userEmail} bus pašalintas iš šio bendrinamo albumo\n\nVisos jų pridėtos nuotraukos taip pat bus pašalintos iš albumo", + "removeParticipantBody": "{userEmail} bus pašalintas iš šio bendrinamo albumo.\n\nVisos jų pridėtos nuotraukos taip pat bus pašalintos iš albumo.", "keepPhotos": "Palikti nuotraukas", "deletePhotos": "Ištrinti nuotraukas", "inviteToEnte": "Kviesti į „Ente“", "removePublicLink": "Šalinti viešą nuorodą", "sharing": "Bendrinima...", + "youCannotShareWithYourself": "Negalite bendrinti su savimi.", "archive": "Archyvas", "importing": "Importuojama....", "hidden": "Paslėpti", @@ -406,9 +453,26 @@ "authToInitiateAccountDeletion": "Nustatykite tapatybę, kad pradėtumėte paskyros ištrynimą", "areYouSureYouWantToLogout": "Ar tikrai norite atsijungti?", "yesLogout": "Taip, atsijungti", + "ignoreUpdate": "Ignoruoti", + "downloading": "Atsisiunčiama...", "backup": "Kurti atsarginę kopiją", "removeDuplicates": "Šalinti dublikatus", + "noDuplicates": "✨ Dublikatų nėra", "youveNoDuplicateFilesThatCanBeCleared": "Neturite dubliuotų failų, kuriuos būtų galima išvalyti", + "success": "Sėkmė", + "rateUs": "Vertinti mus", + "remindToEmptyDeviceTrash": "Taip pat ištuštinkite Neseniai ištrinti iš Nustatymai -> Saugykla, kad atlaisvintumėte vietos.", + "familyPlans": "Šeimos planai", + "notifications": "Pranešimai", + "sharedPhotoNotifications": "Naujos bendrintos nuotraukos", + "sharedPhotoNotificationsExplanation": "Gaukite pranešimus, kai kas nors prideda nuotrauką į bendrinamą albumą, kuriame dalyvaujate.", + "advanced": "Išplėstiniai", + "general": "Bendrieji", + "security": "Saugumas", + "authToViewYourRecoveryKey": "Nustatykite tapatybę, kad peržiūrėtumėte savo atkūrimo raktą", + "twofactor": "Dvigubas tapatybės nustatymas", + "disableTwofactor": "Išjungti dvigubą tapatybės nustatymą", + "confirm2FADisable": "Ar tikrai norite išjungti dvigubą tapatybės nustatymą?", "no": "Ne", "yes": "Taip", "social": "Socialinės", @@ -432,6 +496,7 @@ "freeTrial": "Nemokamas bandomasis laikotarpis", "selectYourPlan": "Pasirinkite planą", "enteSubscriptionPitch": "„Ente“ išsaugo jūsų prisiminimus, todėl jie visada bus pasiekiami, net jei prarasite įrenginį.", + "enteSubscriptionShareWithFamily": "Į planą galima pridėti ir savo šeimą.", "currentUsageIs": "Dabartinis naudojimas – ", "@currentUsageIs": { "description": "This text is followed by storage usage", @@ -441,8 +506,10 @@ "type": "text" }, "faqs": "DUK", + "renewsOn": "Prenumerata atnaujinama {endDate}", "freeTrialValidTill": "Nemokamas bandomasis laikotarpis galioja iki {endDate}", "validTill": "Galioja iki {endDate}", + "playStoreFreeTrialValidTill": "Nemokama bandomoji versija galioja iki {endDate}.\nVėliau galėsite pasirinkti mokamą planą.", "subscription": "Prenumerata", "paymentDetails": "Mokėjimo duomenys", "manageFamily": "Tvarkyti šeimą", @@ -477,6 +544,7 @@ }, "optionalAsShortAsYouLike": "Nebūtina, trumpai, kaip jums patinka...", "send": "Siųsti", + "askCancelReason": "Jūsų prenumerata buvo atšaukta. Ar norėtumėte pasidalyti priežastimi?", "googlePlayId": "„Google Play“ ID", "appleId": "„Apple ID“", "playstoreSubscription": "„PlayStore“ prenumerata", @@ -534,6 +602,8 @@ "allowPeopleToAddPhotos": "Leiskite asmenims pridėti nuotraukų", "shareAnAlbumNow": "Bendrinti albumą dabar", "collectEventPhotos": "Rinkti įvykių nuotraukas", + "sessionExpired": "Seansas baigėsi", + "loggingOut": "Atsijungiama...", "@onDevice": { "description": "The text displayed above folders/albums stored on device", "type": "text" @@ -547,6 +617,10 @@ "name": "Pavadinimą", "newest": "Naujausią", "lastUpdated": "Paskutinį kartą atnaujintą", + "deleteEmptyAlbums": "Ištrinti tuščius albumus", + "deleteEmptyAlbumsWithQuestionMark": "Ištrinti tuščius albumus?", + "deleteAlbumsDialogBody": "Tai ištrins visus tuščius albumus. Tai naudinga, kai norite sumažinti netvarką savo albumų sąraše.", + "deleteProgress": "Ištrinama {currentlyDeleting} / {totalCount}", "genericProgress": "Apdorojama {currentlyProcessing} / {totalCount}", "@genericProgress": { "description": "Generic progress text to display when processing multiple items", @@ -562,6 +636,7 @@ } } }, + "permanentlyDelete": "Ištrinti negrįžtamai", "restore": "Atkurti", "@restore": { "description": "Display text for an action which triggers a restore of item from trash", @@ -569,6 +644,7 @@ }, "unarchive": "Išarchyvuoti", "removeFromFavorite": "Šalinti iš mėgstamų", + "shareLink": "Bendrinti nuorodą", "addToEnte": "Pridėti į „Ente“", "addToAlbum": "Pridėti į albumą", "delete": "Ištrinti", @@ -581,6 +657,8 @@ }, "createOrSelectAlbum": "Kurkite arba pasirinkite albumą", "enterAlbumName": "Įveskite albumo pavadinimą", + "restoringFiles": "Atkuriami failai...", + "sharedWithMe": "Bendrinta su manimi", "doubleYourStorage": "Padvigubinkite saugyklą", "referFriendsAnd2xYourPlan": "Rekomenduokite draugams ir 2 kartus padidinkite savo planą", "shareAlbumHint": "Atidarykite albumą ir palieskite bendrinimo mygtuką viršuje dešinėje, kad bendrintumėte.", @@ -604,6 +682,7 @@ "sortNewestFirst": "Naujausią pirmiausiai", "sortOldestFirst": "Seniausią pirmiausiai", "rename": "Pervadinti", + "leaveSharedAlbum": "Palikti bendrinamą albumą?", "leaveAlbum": "Palikti albumą", "photosAddedByYouWillBeRemovedFromTheAlbum": "Jūsų pridėtos nuotraukos bus pašalintos iš albumo", "youDontHaveAnyArchivedItems": "Neturite jokių archyvuotų elementų.", @@ -616,6 +695,8 @@ "thisImageHasNoExifData": "Šis vaizdas neturi Exif duomenų", "exif": "EXIF", "noResults": "Rezultatų nėra.", + "weDontSupportEditingPhotosAndAlbumsThatYouDont": "Nepalaikome nuotraukų ir albumų redagavimo, kurių dar neturite.", + "failedToFetchOriginalForEdit": "Nepavyko gauti originalo redagavimui.", "close": "Uždaryti", "setAs": "Nustatyti kaip", "download": "Atsisiųsti", @@ -626,9 +707,16 @@ "reviewDeduplicateItems": "Peržiūrėkite ir ištrinkite elementus, kurie, jūsų manymu, yra dublikatai.", "unlock": "Atrakinti", "freeUpAmount": "Atlaisvinti {sizeInMBorGB}", + "thisEmailIsAlreadyInUse": "Šis el. paštas jau naudojamas.", + "incorrectCode": "Neteisingas kodas.", + "authenticationFailedPleaseTryAgain": "Tapatybės nustatymas nepavyko. Bandykite dar kartą.", "verificationFailedPleaseTryAgain": "Patvirtinimas nepavyko. Bandykite dar kartą.", + "authenticating": "Nustatoma tapatybė...", + "authenticationSuccessful": "Tapatybės nustatymas sėkmingas.", + "incorrectRecoveryKey": "Neteisingas atkūrimo raktas", + "theRecoveryKeyYouEnteredIsIncorrect": "Įvestas atkūrimo raktas yra neteisingas.", "pleaseVerifyTheCodeYouHaveEntered": "Patvirtinkite įvestą kodą.", - "yourVerificationCodeHasExpired": "Jūsų patvirtinimo kodo laikas nebegaliojantis.", + "yourVerificationCodeHasExpired": "Jūsų patvirtinimo kodas nebegaliojantis.", "verifying": "Patvirtinama...", "allMemoriesPreserved": "Išsaugoti visi prisiminimai", "loadingGallery": "Įkeliama galerija...", @@ -669,7 +757,12 @@ "localGallery": "Vietinė galerija", "todaysLogs": "Šiandienos žurnalai", "viewLogs": "Peržiūrėti žurnalus", + "logsDialogBody": "Tai nusiųs žurnalus, kurie padės mums išspręsti jūsų problemą. Atkreipkite dėmesį, kad failų pavadinimai bus įtraukti, kad būtų lengviau atsekti problemas su konkrečiais failais.", + "preparingLogs": "Ruošiami žurnalai...", + "emailYourLogs": "Atsiųskite žurnalus el. laišku", + "pleaseSendTheLogsTo": "Siųskite žurnalus adresu\n{toEmail}", "copyEmailAddress": "Kopijuoti el. pašto adresą", + "exportLogs": "Eksportuoti žurnalus", "didYouKnow": "Ar žinojote?", "loadMessage1": "Galite bendrinti savo prenumeratą su šeima.", "loadMessage2": "Iki šiol išsaugojome daugiau kaip 30 milijonų prisiminimų.", @@ -719,6 +812,7 @@ "@setLabel": { "description": "Label of confirm button to add a new custom radius to the radius selector of a location tag" }, + "familyPlanPortalTitle": "Šeima", "androidBiometricHint": "Patvirtinkite tapatybę", "@androidBiometricHint": { "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." @@ -731,10 +825,30 @@ "@androidSignInTitle": { "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." }, + "androidBiometricRequiredTitle": "Privaloma biometrija", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Privalomi įrenginio kredencialai", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Privalomi įrenginio kredencialai", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, "goToSettings": "Eiti į nustatymus", "@goToSettings": { "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." }, + "androidGoToSettingsDescription": "Biometrinis tapatybės nustatymas jūsų įrenginyje nenustatytas. Eikite į Nustatymai > Saugumas ir pridėkite biometrinį tapatybės nustatymą.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biometrinis tapatybės nustatymas išjungtas. Kad jį įjungtumėte, užrakinkite ir atrakinkite ekraną.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, "iOSGoToSettingsDescription": "Biometrinis tapatybės nustatymas jūsų įrenginyje nenustatytas. Telefone įjunkite „Touch ID“ arba „Face ID“.", "@iOSGoToSettingsDescription": { "description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side." @@ -756,12 +870,24 @@ "create": "Kurti", "viewAll": "Peržiūrėti viską", "nothingSharedWithYouYet": "Kol kas su jumis niekuo nesibendrinama.", + "sharedWithYou": "Bendrinta su jumis", + "sharedByYou": "Bendrinta iš jūsų", + "hiding": "Slepiama...", "fileTypes": "Failų tipai", "deleteConfirmDialogBody": "Ši paskyra susieta su kitomis „Ente“ programomis, jei jas naudojate. Jūsų įkelti duomenys per visas „Ente“ programas bus planuojama ištrinti, o jūsų paskyra bus ištrinta negrįžtamai.", + "hearUsWhereTitle": "Kaip išgirdote apie „Ente“? (nebūtina)", + "hearUsExplanation": "Mes nesekame programų diegimų. Mums padėtų, jei pasakytumėte, kur mus radote.", "viewAddOnButton": "Peržiūrėti priedus", + "addOns": "Priedai", "yourMap": "Jūsų žemėlapis", + "blackFridaySale": "Juodojo penktadienio išpardavimas", + "upto50OffUntil4thDec": "Iki 50% nuolaida, gruodžio 4 d.", + "photos": "Nuotraukos", + "videos": "Vaizdo įrašai", + "livePhotos": "Gyvos nuotraukos", "searchHint3": "Albumai, failų pavadinimai ir tipai", "searchHint4": "Vietovė", + "searchHint5": "Jau netrukus: veidų ir magiškos paieškos ✨", "searchResultCount": "{count, plural, one{Rastas {count} rezultatas} few {Rasti {count} rezultatai} many {Rasta {count} rezultato} other{Rasta {count} rezultatų}}", "@searchResultCount": { "description": "Text to tell user how many results were found for their search query", @@ -805,7 +931,6 @@ "deviceCodeHint": "Įveskite kodą", "joinDiscord": "Jungtis prie „Discord“", "locations": "Vietovės", - "descriptions": "Aprašymai", "addAName": "Pridėti vardą", "findThemQuickly": "Raskite juos greitai", "@findThemQuickly": { @@ -1071,5 +1196,65 @@ "openAlbumInBrowserTitle": "Naudokite interneto programą, kad pridėtumėte nuotraukų į šį albumą.", "allow": "Leisti", "allowAppToOpenSharedAlbumLinks": "Leisti programai atverti bendrinamų albumų nuorodas", - "seePublicAlbumLinksInApp": "Žiūrėti viešų albumų nuorodas programoje" + "seePublicAlbumLinksInApp": "Žiūrėti viešų albumų nuorodas programoje", + "emergencyContacts": "Skubios pagalbos kontaktai", + "acceptTrustInvite": "Priimti kvietimą", + "declineTrustInvite": "Atmesti kvietimą", + "removeYourselfAsTrustedContact": "Šalinti save kaip patikimą kontaktą", + "legacy": "Palikimas", + "legacyPageDesc": "Palikimas leidžia patikimiems kontaktams pasiekti jūsų paskyrą jums nesant.", + "legacyPageDesc2": "Patikimi kontaktai gali pradėti paskyros atkūrimą, o jei per 30 dienų paskyra neužblokuojama, iš naujo nustatyti slaptažodį ir pasiekti paskyrą.", + "legacyAccounts": "Palikimo paskyros", + "trustedContacts": "Patikimi kontaktai", + "addTrustedContact": "Pridėti patikimą kontaktą", + "removeInvite": "Šalinti kvietimą", + "recoveryWarning": "Patikimas kontaktas bando pasiekti jūsų paskyrą.", + "rejectRecovery": "Atmesti atkūrimą", + "recoveryInitiated": "Pradėtas atkūrimas", + "recoveryInitiatedDesc": "Paskyrą galėsite pasiekti po {days} dienų. Pranešimas bus išsiųstas į {email}.", + "@recoveryInitiatedDesc": { + "placeholders": { + "days": { + "type": "int", + "example": "30" + }, + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "cancelAccountRecovery": "Atšaukti atkūrimą", + "recoveryAccount": "Atkurti paskyrą", + "cancelAccountRecoveryBody": "Ar tikrai norite atšaukti atkūrimą?", + "startAccountRecoveryTitle": "Pradėti atkūrimą", + "whyAddTrustContact": "Patikimas kontaktas gali padėti atkurti jūsų duomenis.", + "recoveryReady": "Dabar galite atkurti {email} paskyrą nustatydami naują slaptažodį.", + "@recoveryReady": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "recoveryWarningBody": "{email} bando atkurti jūsų paskyrą.", + "trustedInviteBody": "Buvote pakviesti tapti {email} palikimo kontaktu.", + "warning": "Įspėjimas", + "proceed": "Tęsti", + "confirmAddingTrustedContact": "Ketinate pridėti {email} kaip patikimą kontaktą. Jie galės atkurti jūsų paskyrą, jei jūsų nebus {numOfDays} dienų.", + "@confirmAddingTrustedContact": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + }, + "numOfDays": { + "type": "int", + "example": "30" + } + } + }, + "legacyInvite": "{email} pakvietė jus būti patikimu kontaktu", + "authToManageLegacy": "Nustatykite tapatybę, kad tvarkytumėte patikimus kontaktus" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ml.arb b/mobile/lib/l10n/intl_ml.arb new file mode 100644 index 0000000000..7cfd3a2887 --- /dev/null +++ b/mobile/lib/l10n/intl_ml.arb @@ -0,0 +1,103 @@ +{ + "@@locale ": "en", + "enterYourEmailAddress": "നിങ്ങളുടെ ഇമെയിൽ വിലാസം നൽകുക", + "accountWelcomeBack": "വീണ്ടും സ്വാഗതം!", + "email": "ഇമെയിൽ", + "cancel": "റദ്ദാക്കുക", + "verify": "ഉറപ്പിക്കുക", + "invalidEmailAddress": "അസാധുവായ ഇമെയിൽ വിലാസം", + "enterValidEmail": "സാധുവായ ഒരു ഇമെയിൽ നൽകുക.", + "deleteAccount": "അക്കൗണ്ട് ഉപേക്ഷിക്കു", + "askDeleteReason": "അക്കൗണ്ട് ഉപേക്ഷിക്കുവാൻ പ്രധാന കാരണമെന്താണ്?", + "deleteAccountFeedbackPrompt": "സേവനം ഉപേക്ഷിക്കുന്നതിൽ ഖേദിക്കുന്നു. മെച്ചപ്പെടുത്താൻ ഞങ്ങളെ സഹായിക്കുന്നതിനായി ദയവായി നിങ്ങളുടെ അഭിപ്രായം പങ്കിടുക.", + "feedback": "അഭിപ്രായം", + "kindlyHelpUsWithThisInformation": "വിവരങ്ങൾ തന്നു സഹായിക്കുക", + "selectReason": "കാരണം തിരഞ്ഞെടുക്കൂ", + "deleteReason1": "അത്യാവശപെട്ടയൊരു സുവിഷേശത ഇതിൽ ഇല്ല", + "deleteReason3": "ഇതിനേക്കാൾ ഇഷ്ടപ്പെടുന്ന മറ്റൊരു സേവനം കണ്ടെത്തി", + "deleteReason4": "എന്റെ കാരണം ഉൾകൊണ്ടിട്ടില്ല", + "sendEmail": "ഇമെയിൽ അയക്കുക", + "ok": "ശരി", + "createAccount": "അക്കൗണ്ട് തുറക്കുക", + "createNewAccount": "പുതിയ അക്കൗണ്ട് തുറക്കുക", + "password": "സങ്കേതക്കുറി", + "confirmPassword": "സങ്കേതക്കുറി ഉറപ്പിക്കുക", + "oops": "അയ്യോ", + "somethingWentWrongPleaseTryAgain": "എന്തോ കുഴപ്പം സംഭവിച്ചു, ദയവായി വീണ്ടും ശ്രമിക്കുക", + "thisDevice": "ഈ ഉപകരണം", + "recoverButton": "വീണ്ടെടുക്കുക", + "recoverySuccessful": "വീണ്ടെടുക്കൽ വിജയകരം!", + "forgotPassword": "സങ്കേതക്കുറി മറന്നുപോയി", + "sorry": "ക്ഷമിക്കുക", + "verifyEmail": "ഇമെയിൽ ദൃഢീകരിക്കുക", + "weakStrength": "ദുർബലം", + "strongStrength": "ശക്തം", + "moderateStrength": "ഇടത്തരം", + "continueLabel": "തുടരൂ", + "howItWorks": "പ്രവർത്തന രീതി", + "privacyPolicyTitle": "സ്വകാര്യതാനയം", + "termsOfServicesTitle": "നിബന്ധനകൾ", + "changeEmail": "ഇമെയിൽ മാറ്റുക", + "welcomeBack": "വീണ്ടും സ്വാഗതം!", + "incorrectPasswordTitle": "തെറ്റായ സങ്കേതക്കുറി", + "pleaseTryAgain": "ദയവായി വീണ്ടും ശ്രമിക്കുക", + "recreatePasswordTitle": "സങ്കേതക്കുറി പുനസൃഷ്ടിക്കുക", + "verifyPassword": "സങ്കേതക്കുറി ദൃഢീകരിക്കുക", + "doThisLater": "പിന്നീട് ചെയ്യുക", + "confirm": "നിജപ്പെടുത്തുക", + "setupComplete": "സജ്ജീകരണം പൂർത്തിയായി", + "albumOwner": "ഉടമ", + "@albumOwner": { + "description": "Role of the album owner" + }, + "noDeviceLimit": "ഒന്നുമില്ല", + "@noDeviceLimit": { + "description": "Text to indicate that there is limit on number of devices" + }, + "linkExpired": "കാലഹരണപ്പെട്ടു", + "custom": "ഇഷ്‌ടാനുസൃതം", + "@custom": { + "description": "Label for setting custom value for link expiry" + }, + "privacy": "സ്വകാര്യത", + "terms": "നിബന്ധനകൾ", + "emailVerificationToggle": "ഇമെയിൽ ദൃഢീകരണം", + "ignoreUpdate": "അവഗണിക്കുക", + "retry": "പുനശ്രമിക്കുക", + "success": "സഫലം", + "sparkleSuccess": "✨ സഫലം", + "general": "പൊതുവായവ", + "security": "സുരക്ഷ", + "no": "വേണ്ട", + "mastodon": "മാസ്റ്റഡോൺ", + "matrix": "മേട്രിക്സ്", + "reddit": "റെഡ്ഡിറ്റ്", + "support": "പിന്തുണ", + "lightTheme": "തെളിഞ", + "darkTheme": "ഇരുണ്ട", + "faqs": "പതിവുചോദ്യങ്ങൾ", + "monthly": "പ്രതിമാസം", + "@monthly": { + "description": "The text to display for monthly plans", + "type": "text" + }, + "yearly": "പ്രതിവർഷം", + "@yearly": { + "description": "The text to display for yearly plans", + "type": "text" + }, + "send": "അയക്കുക", + "thankYou": "നന്ദി", + "available": "ലഭ്യമാണ്", + "name": "പേര്", + "favorite": "പ്രിയപ്പെട്ടവ", + "hide": "മറയ്ക്കുക", + "share": "പങ്കിടുക", + "sharedWithMe": "എന്നോട് പങ്കിട്ടവ", + "sharedByMe": "ഞാനാൽ പങ്കിട്ടവ", + "sortAlbumsBy": "ഇപ്രകാരം അടുക്കുക", + "nothingToSeeHere": "ഇവിടൊന്നും കാണ്മാനില്ല! 👀", + "calculating": "കണക്കുകൂട്ടുന്നു...", + "close": "അടക്കുക", + "count": "എണ്ണം" +} \ No newline at end of file diff --git a/mobile/lib/l10n/intl_nl.arb b/mobile/lib/l10n/intl_nl.arb index d2b6e4a993..591cfe1894 100644 --- a/mobile/lib/l10n/intl_nl.arb +++ b/mobile/lib/l10n/intl_nl.arb @@ -412,6 +412,8 @@ "description": "The text to display in the advanced settings section" }, "photoGridSize": "Foto raster grootte", + "manageDeviceStorage": "Apparaatcache beheren", + "manageDeviceStorageDesc": "Bekijk en wis lokale cache opslag.", "machineLearning": "Machine Learning", "mlConsent": "Schakel machine learning in", "mlConsentTitle": "Machine learning inschakelen?", @@ -1232,6 +1234,7 @@ "waitingForVerification": "Wachten op verificatie...", "passkey": "Passkey", "passkeyAuthTitle": "Passkey verificatie", + "loginWithTOTP": "Inloggen met TOTP", "passKeyPendingVerification": "Verificatie is nog in behandeling", "loginSessionExpired": "Sessie verlopen", "loginSessionExpiredDetails": "Jouw sessie is verlopen. Log opnieuw in.", @@ -1243,7 +1246,6 @@ "deviceCodeHint": "Voer de code in", "joinDiscord": "Join de Discord", "locations": "Locaties", - "descriptions": "Beschrijvingen", "addAName": "Een naam toevoegen", "findThemQuickly": "Vind ze snel", "@findThemQuickly": { @@ -1503,5 +1505,34 @@ "changeLogDiscoverTitle": "Ontdek", "changeLogDiscoverContent": "Op zoek naar foto's van je identiteitskaarten, notities of zelfs herinneringen? Ga naar het tabblad Zoeken en bekijk ontdekking. Op basis van onze semantische zoekopdracht is het een plek om foto's te vinden die belangrijk voor jou kunnen zijn.\\n\\nAlleen beschikbaar als je Machine Learning hebt ingeschakeld.", "selectCoverPhoto": "Selecteer omslagfoto", - "newLocation": "Nieuwe locatie" + "newLocation": "Nieuwe locatie", + "faceNotClusteredYet": "Gezicht nog niet geclusterd, kom later terug", + "theLinkYouAreTryingToAccessHasExpired": "De link die je probeert te openen is verlopen.", + "openFile": "Bestand openen", + "backupFile": "Back-up bestand", + "openAlbumInBrowser": "Open album in browser", + "openAlbumInBrowserTitle": "Gebruik de webapp om foto's aan dit album toe te voegen", + "allow": "Toestaan", + "allowAppToOpenSharedAlbumLinks": "App toestaan gedeelde album links te openen", + "seePublicAlbumLinksInApp": "Bekijk publieke album links in de app", + "emergencyContacts": "Noodcontacten", + "acceptTrustInvite": "Uitnodiging accepteren", + "declineTrustInvite": "Uitnodiging afwijzen", + "removeYourselfAsTrustedContact": "Verwijder jezelf als vertrouwd contact", + "legacy": "Legacy", + "legacyPageDesc": "Legacy geeft vertrouwde contacten toegang tot je account bij afwezigheid.", + "legacyPageDesc2": "Vertrouwde contacten kunnen accountherstel starten, en indien deze niet binnen 30 dagen wordt geblokkeerd, je wachtwoord resetten en toegang krijgen tot je account.", + "legacyAccounts": "Legacy accounts", + "trustedContacts": "Vertrouwde contacten", + "addTrustedContact": "Vertrouwd contact toevoegen", + "removeInvite": "Verwijder uitnodiging", + "recoveryWarning": "Een vertrouwd contact probeert toegang te krijgen tot je account", + "rejectRecovery": "Herstel weigeren", + "recoveryInitiated": "Herstel gestart", + "cancelAccountRecovery": "Herstel annuleren", + "recoveryAccount": "Account herstellen", + "cancelAccountRecoveryBody": "Weet je zeker dat je het herstel wilt annuleren?", + "startAccountRecoveryTitle": "Herstel starten", + "whyAddTrustContact": "Vertrouwde contacten kunnen helpen bij het herstellen van je data.", + "authToManageLegacy": "Verifieer om je vertrouwde contacten te beheren" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_pl.arb b/mobile/lib/l10n/intl_pl.arb index df53e8fe15..860494fccf 100644 --- a/mobile/lib/l10n/intl_pl.arb +++ b/mobile/lib/l10n/intl_pl.arb @@ -8,7 +8,7 @@ "invalidEmailAddress": "Nieprawidłowy adres e-mail", "enterValidEmail": "Prosimy podać prawidłowy adres e-mail.", "deleteAccount": "Usuń konto", - "askDeleteReason": "Jaka jest przyczyna usunięcia konta?", + "askDeleteReason": "Jaka jest główna przyczyna usunięcia Twojego konta?", "deleteAccountFeedbackPrompt": "Przykro nam, że odchodzisz. Wyjaśnij nam, dlaczego nas opuszczasz, aby pomóc ulepszać nasze usługi.", "feedback": "Opinia", "kindlyHelpUsWithThisInformation": "Pomóż nam z tą informacją", @@ -1246,7 +1246,6 @@ "deviceCodeHint": "Wprowadź kod", "joinDiscord": "Dołącz do serwera Discord", "locations": "Lokalizacje", - "descriptions": "Opisy", "addAName": "Dodaj nazwę", "findThemQuickly": "Znajdź ich szybko", "@findThemQuickly": { @@ -1514,5 +1513,64 @@ "openAlbumInBrowserTitle": "Prosimy użyć aplikacji internetowej, aby dodać zdjęcia do tego albumu", "allow": "Zezwól", "allowAppToOpenSharedAlbumLinks": "Zezwalaj aplikacji na otwieranie udostępnianych linków do albumu", - "seePublicAlbumLinksInApp": "Zobacz publiczne linki do albumów w aplikacji" + "seePublicAlbumLinksInApp": "Zobacz publiczne linki do albumów w aplikacji", + "emergencyContacts": "Kontakty Alarmowe", + "acceptTrustInvite": "Zaakceptuj Zaproszenie", + "declineTrustInvite": "Odrzuć Zaproszenie", + "removeYourselfAsTrustedContact": "Usuń siebie z listy zaufanych kontaktów", + "legacy": "Dziedzictwo", + "legacyPageDesc": "Dziedzictwo pozwala zaufanym kontaktom na dostęp do Twojego konta w razie Twojej nieobecności.", + "legacyPageDesc2": "Zaufane kontakty mogą rozpocząć odzyskiwanie konta, a jeśli nie zostaną zablokowane w ciągu 30 dni, zresetować Twoje hasło i uzyskać dostęp do Twojego konta.", + "trustedContacts": "Zaufane kontakty", + "addTrustedContact": "Dodaj Zaufany Kontakt", + "removeInvite": "Usuń zaproszenie", + "recoveryWarning": "Zaufany kontakt próbuje uzyskać dostęp do Twojego konta", + "rejectRecovery": "Odrzuć odzyskiwanie", + "recoveryInitiated": "Odzyskiwanie rozpoczęte", + "recoveryInitiatedDesc": "Możesz uzyskać dostęp do konta po dniu {days} dni. Powiadomienie zostanie wysłane na {email}.", + "@recoveryInitiatedDesc": { + "placeholders": { + "days": { + "type": "int", + "example": "30" + }, + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "cancelAccountRecovery": "Anuluj odzyskiwanie", + "recoveryAccount": "Odzyskaj konto", + "cancelAccountRecoveryBody": "Czy na pewno chcesz anulować odzyskiwanie?", + "startAccountRecoveryTitle": "Rozpocznij odzyskiwanie", + "whyAddTrustContact": "Zaufany kontakt może pomóc w odzyskaniu Twoich danych.", + "recoveryReady": "Możesz teraz odzyskać konto {email} poprzez ustawienie nowego hasła.", + "@recoveryReady": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "recoveryWarningBody": "{email} próbuje odzyskać Twoje konto.", + "trustedInviteBody": "Zostałeś zaproszony do bycia dziedzicznym kontaktem przez {email}.", + "warning": "Uwaga", + "proceed": "Kontynuuj", + "confirmAddingTrustedContact": "Zamierzasz dodać {email} jako zaufany kontakt. Będą mogli odzyskać Twoje konto, jeśli jesteś nieobecny przez {numOfDays} dni.", + "@confirmAddingTrustedContact": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + }, + "numOfDays": { + "type": "int", + "example": "30" + } + } + }, + "legacyInvite": "{email} zaprosił Cię do zostania zaufanym kontaktem", + "authToManageLegacy": "Prosimy uwierzytelnić się, aby zarządzać zaufanymi kontaktami" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_pt.arb b/mobile/lib/l10n/intl_pt.arb index 8379a83810..0ba064c75e 100644 --- a/mobile/lib/l10n/intl_pt.arb +++ b/mobile/lib/l10n/intl_pt.arb @@ -1246,7 +1246,6 @@ "deviceCodeHint": "Insira o código", "joinDiscord": "Junte-se ao Discord", "locations": "Localizações", - "descriptions": "Descrições", "addAName": "Adicione um nome", "findThemQuickly": "Busque-os rapidamente", "@findThemQuickly": { @@ -1515,5 +1514,65 @@ "openAlbumInBrowserTitle": "Use o aplicativo da web para adicionar fotos a este álbum", "allow": "Permitir", "allowAppToOpenSharedAlbumLinks": "Permitir aplicativo abrir links de álbum compartilhado", - "seePublicAlbumLinksInApp": "Ver links de álbum compartilhado no aplicativo" + "seePublicAlbumLinksInApp": "Ver links de álbum compartilhado no aplicativo", + "emergencyContacts": "Contatos de emergência", + "acceptTrustInvite": "Aceitar convite", + "declineTrustInvite": "Recusar convite", + "removeYourselfAsTrustedContact": "Remover si mesmo dos contatos confiáveis", + "legacy": "Legado", + "legacyPageDesc": "O legado permite que contatos confiáveis acessem sua conta em sua ausência.", + "legacyPageDesc2": "Contatos confiáveis podem iniciar recuperação de conta, e se não for cancelado dentro de 30 dias, redefina sua senha e acesse sua conta.", + "legacyAccounts": "Contas legado", + "trustedContacts": "Contatos confiáveis", + "addTrustedContact": "Adicionar contato confiável", + "removeInvite": "Remover convite", + "recoveryWarning": "Um contato confiável está tentando acessar sua conta", + "rejectRecovery": "Rejeitar recuperação", + "recoveryInitiated": "A recuperação iniciou", + "recoveryInitiatedDesc": "Você poderá acessar a conta após {days} dias. Uma notificação será enviada para {email}.", + "@recoveryInitiatedDesc": { + "placeholders": { + "days": { + "type": "int", + "example": "30" + }, + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "cancelAccountRecovery": "Cancelar recuperação", + "recoveryAccount": "Recuperar conta", + "cancelAccountRecoveryBody": "Deseja mesmo cancelar a recuperação de conta?", + "startAccountRecoveryTitle": "Iniciar recuperação", + "whyAddTrustContact": "Um contato confiável pode ajudá-lo em recuperar seus dados.", + "recoveryReady": "Você pode recuperar a conta com e-mail {email} por definir uma nova senha.", + "@recoveryReady": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "recoveryWarningBody": "{email} está tentando recuperar sua conta.", + "trustedInviteBody": "Você foi convidado para ser um contato legado por {email}.", + "warning": "Aviso", + "proceed": "Continuar", + "confirmAddingTrustedContact": "Você está prestes a adicionar {email} como contato confiável. Eles poderão recuperar sua conta se você estiver ausente por {numOfDays} dias.", + "@confirmAddingTrustedContact": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + }, + "numOfDays": { + "type": "int", + "example": "30" + } + } + }, + "legacyInvite": "{email} convidou você para ser um contato confiável", + "authToManageLegacy": "Autentique-se para gerenciar seus contatos confiáveis" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ru.arb b/mobile/lib/l10n/intl_ru.arb index fe23d02eb0..6cbb7fa922 100644 --- a/mobile/lib/l10n/intl_ru.arb +++ b/mobile/lib/l10n/intl_ru.arb @@ -415,6 +415,17 @@ "mlConsentPrivacy": "Пожалуйста, нажмите здесь, чтобы узнать больше об этой функции в нашей политике конфиденциальности", "mlConsentConfirmation": "Я понимаю и хочу включить машинное обучение", "magicSearch": "Волшебный поиск", + "discover_notes": "Заметки", + "discover_memes": "Мемы", + "discover_babies": "Дети", + "discover_pets": "Питомцы", + "discover_selfies": "Селфи", + "discover_wallpapers": "Обои", + "discover_food": "Еда", + "discover_celebrations": "Поздравления", + "discover_sunset": "Закат", + "discover_hills": "Горы", + "discover_greenery": "Зелень", "mlIndexingDescription": "Обратите внимание, что машинное обучение приведёт к повышенному потреблению трафика и батареи, пока все элементы не будут проиндексированы. Рекомендуем использовать ПК версию для более быстрого индексирования. Полученные результаты будут синхронизированы автоматически между устройствами.", "loadingModel": "Загрузка моделей...", "waitingForWifi": "Ожидание WiFi...", @@ -448,6 +459,7 @@ "showMemories": "Показать воспоминания", "yearsAgo": "{count, plural, one{{count} год назад} other{{count} лет назад}}", "backupSettings": "Настройки резервного копирования", + "backupStatusDescription": "Здесь будут сохранены резервные копии", "backupOverMobileData": "Резервное копирование через мобильную сеть", "backupVideos": "Резервное копирование видео", "disableAutoLock": "Отключить автоблокировку", @@ -1205,7 +1217,6 @@ "deviceCodeHint": "Введите код", "joinDiscord": "Присоединиться в Discord", "locations": "Локации", - "descriptions": "Описания", "addAName": "Добавить имя", "findPeopleByName": "Быстрый поиск людей по имени", "addViewers": "{count, plural, one {Добавьте зрителя} few {Добавьте зрителей} many {Добавьте зрителей} other {Добавьте зрителей}}", @@ -1221,6 +1232,13 @@ "createCollaborativeLink": "Создать совместную ссылку", "search": "Поиск", "enterPersonName": "Введите имя", + "enterName": "Введите имя", + "savePerson": "Сохранить человека", + "editPerson": "Отредактировать человека", + "mergedPhotos": "Объединенные фото", + "orMergeWithExistingPerson": "Или объединить с существующими", + "enterDateOfBirth": "Дата рождения (необязательно)", + "birthday": "День рождения", "removePersonLabel": "Удалить метку человека", "autoPairDesc": "Автоматическое подключение работает только с устройствами, поддерживающими Chromecast.", "manualPairDesc": "Пара с PIN-кодом работает с любым экраном, на котором вы хотите посмотреть ваш альбом.", @@ -1247,6 +1265,7 @@ "right": "Вправо", "whatsNew": "Что нового", "reviewSuggestions": "Посмотреть предложения", + "review": "Отзыв", "useAsCover": "Использовать для обложки", "notPersonLabel": "Не {name}?", "@notPersonLabel": { @@ -1289,5 +1308,175 @@ "removePublicLinks": "Удалить публичные ссылки", "thisWillRemovePublicLinksOfAllSelectedQuickLinks": "Это удалит публичные ссылки на все выбранные быстрые ссылки.", "guestView": "Гостевой вид", - "guestViewEnablePreSteps": "Чтобы включить гостевой вид, настройте пароль устройства или блокировку экрана в настройках системы." + "guestViewEnablePreSteps": "Чтобы включить гостевой вид, настройте пароль устройства или блокировку экрана в настройках системы.", + "nameTheAlbum": "Назовите альбом", + "collectPhotosDescription": "Создайте ссылку, где ваши друзья смогут загружать фото в оригинальном качестве.", + "collect": "Собрать", + "appLockDescriptions": "Выберите между экраном блокировки вашего устройства и пользовательским экраном блокировки с PIN-кодом или паролем.", + "toEnableAppLockPleaseSetupDevicePasscodeOrScreen": "Чтобы включить блокировку, настройте пароль устройства или блокировку экрана в настройках системы.", + "authToViewPasskey": "Пожалуйста, авторизуйтесь, чтобы просмотреть ваш пароль", + "loopVideoOn": "Цикл видео включен", + "loopVideoOff": "Цикл видео выключен", + "localSyncErrorMessage": "Похоже, что-то пошло не так, так как локальная синхронизация фото занимает больше времени, чем ожидалось. Пожалуйста, свяжитесь с нашей службой поддержки", + "showPerson": "Показать человека", + "sort": "Сортировать", + "mostRecent": "Недавние", + "mostRelevant": "Сначала актуальные", + "loadingYourPhotos": "Загрузка фотографий...", + "processingImport": "Обработка {folderName}...", + "personName": "Имя человека", + "addNewPerson": "Добавить новую персону", + "addNameOrMerge": "Добавить имя или объединить", + "mergeWithExisting": "Объединить с существующим", + "newPerson": "Новое лицо", + "addName": "Добавить имя", + "add": "Добавить", + "extraPhotosFoundFor": "Дополнительные фотографии найдены для {text}", + "@extraPhotosFoundFor": { + "placeholders": { + "text": { + "type": "String" + } + } + }, + "extraPhotosFound": "Найдены дополнительные фотографии", + "configuration": "Настройки", + "localIndexing": "Локальное индексирование", + "resetPerson": "Убрать", + "areYouSureYouWantToResetThisPerson": "Вы уверены, что хотите сбросить этого человека?", + "allPersonGroupingWillReset": "Все группы этого человека будут сброшены, и вы потеряете все предложения, сделанные для этого человека", + "yesResetPerson": "Да, сбросить человека", + "onlyThem": "Только их", + "checkingModels": "Проверка моделей...", + "enableMachineLearningBanner": "Включить автоматическое обучение для Магического Поиска и распознавания лица", + "searchDiscoverEmptySection": "Изображения будут показаны здесь после завершения обработки", + "searchPersonsEmptySection": "Люди будут показаны здесь после выполнения обработки", + "viewersSuccessfullyAdded": "{count, plural, =0 {Добавлено 0 зрителей}=1 {Добавлен 1 зритель} other {Добавлено {count} зрителей}}", + "@viewersSuccessfullyAdded": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + }, + "description": "Number of viewers that were successfully added to an album." + }, + "collaboratorsSuccessfullyAdded": "{count, plural, =0 {Добавлено 0 соавторов} =1 {Добавлен 1 соавтор} other {Добавлено {count} соавторов}}", + "@collaboratorsSuccessfullyAdded": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + }, + "description": "Number of collaborators that were successfully added to an album." + }, + "accountIsAlreadyConfigured": "Аккаунт уже настроен.", + "sessionIdMismatch": "Несоответствие ID сеанса", + "@sessionIdMismatch": { + "description": "In passkey page, deeplink is ignored because of session ID mismatch." + }, + "failedToFetchActiveSessions": "Не удалось получить активные сеансы", + "@failedToFetchActiveSessions": { + "description": "In session page, warn user (in toast) that active sessions could not be fetched." + }, + "failedToRefreshStripeSubscription": "Не удалось обновить подписку", + "failedToPlayVideo": "Не удалось воспроизвести видео", + "uploadIsIgnoredDueToIgnorereason": "Загрузка игнорируется из-за {ignoreReason}", + "@uploadIsIgnoredDueToIgnorereason": { + "placeholders": { + "ignoreReason": { + "type": "String", + "example": "no network" + } + } + }, + "typeOfGallerGallerytypeIsNotSupportedForRename": "Тип галереи {galleryType} не поддерживается для переименования", + "@typeOfGallerGallerytypeIsNotSupportedForRename": { + "placeholders": { + "galleryType": { + "type": "String", + "example": "no network" + } + } + }, + "info": "Информация", + "addFiles": "Добавить файлы", + "castAlbum": "Трансляция альбома", + "imageNotAnalyzed": "Изображение не анализировано", + "noFacesFound": "Лица не найдены", + "fileNotUploadedYet": "Файл ещё не загружен", + "noSuggestionsForPerson": "Нет предложений для {personName}", + "@noSuggestionsForPerson": { + "placeholders": { + "personName": { + "type": "String", + "example": "Alice" + } + } + }, + "month": "месяц", + "yearShort": "г.", + "@yearShort": { + "description": "Appears in pricing page (/yr)" + }, + "currentlyRunning": "сейчас запущено", + "ignored": "игнорировать", + "photosCount": "{count, plural, =0 {0 photo} =1 {1 photo} other {{count} photos}}", + "@photosCount": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + } + }, + "file": "Файл", + "searchSectionsLengthMismatch": "Длина разделов не совпадает: {snapshotLenght} != {searchLenght}", + "@searchSectionsLengthMismatch": { + "description": "Appears in search tab page", + "placeholders": { + "snapshotLenght": { + "type": "int", + "example": "1" + }, + "searchLenght": { + "type": "int", + "example": "2" + } + } + }, + "selectMailApp": "Выберите приложение почты", + "selectAllShort": "Все", + "@selectAllShort": { + "description": "Text that appears in bottom right when you start to select multiple photos. When clicked, it selects all photos." + }, + "changeLogMagicSearchImprovementTitle": "Улучшение Магического Поиска", + "changeLogMagicSearchImprovementContent": "Мы улучшили Магический Поиск, чтобы искать гораздо быстрее, поэтому вам не нужно ждать, чтобы найти то, что вы ищете.", + "changeLogBackupStatusTitle": "Состояние резервных копий", + "changeLogBackupStatusContent": "Мы добавили журнал всех файлов, которые были загружены в Ente, включая сбои и очереди.", + "changeLogDiscoverTitle": "Узнать", + "changeLogDiscoverContent": "Ищете фотографии ваших идентификационных карт, заметок или даже мемов? Перейдите во вкладку поиска и проверьте \"Узнать\". Основываясь на нашем поиске, это место для поиска фотографий, которые могут быть важны для вас.\\n\\nдоступно только в том случае, если вы включили Машинное обучение.", + "selectCoverPhoto": "Выберите обложку", + "newLocation": "Новое местоположение", + "faceNotClusteredYet": "Лицо еще не кластеризовано, пожалуйста, вернитесь позже", + "theLinkYouAreTryingToAccessHasExpired": "Ссылка, к которой вы пытаетесь получить доступ, устарела.", + "openFile": "Открыть файл", + "backupFile": "Резервный файл", + "openAlbumInBrowser": "Открыть альбом в браузере", + "openAlbumInBrowserTitle": "Пожалуйста, используйте веб-приложение, чтобы добавить фотографии в этот альбом", + "allow": "Разрешить", + "allowAppToOpenSharedAlbumLinks": "Разрешить приложению открывать общие ссылки на альбомы", + "seePublicAlbumLinksInApp": "Смотреть ссылки публичного альбома в приложении", + "emergencyContacts": "Экстренные контакты", + "acceptTrustInvite": "Принять приглашение", + "declineTrustInvite": "Отклонить приглашение", + "removeYourselfAsTrustedContact": "Удалить себя как доверенный контакт", + "legacyPageDesc2": "Доверенные контакты могут инициировать восстановление учетной записи, если они не будут заблокированы в течение 30 дней, сбросить пароль и получить доступ к вашей учетной записи.", + "trustedContacts": "Доверенные контакты", + "addTrustedContact": "Добавить доверенный контакт", + "removeInvite": "Удалить приглашение", + "recoveryWarning": "Доверенный контакт пытается получить доступ к вашей учетной записи", + "rejectRecovery": "Отклонить восстановление", + "recoveryInitiated": "Восстановление запущено" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_tr.arb b/mobile/lib/l10n/intl_tr.arb index 058f532ec2..6541f31f8d 100644 --- a/mobile/lib/l10n/intl_tr.arb +++ b/mobile/lib/l10n/intl_tr.arb @@ -1148,7 +1148,6 @@ "deviceCodeHint": "Kodu girin", "joinDiscord": "Discord'a Katıl", "locations": "Konum", - "descriptions": "Açıklama", "addViewers": "{count, plural, zero {Görüntüleyen ekle} one {Görüntüleyen ekle} other {Görüntüleyen ekle}}", "addCollaborators": "{count, plural, zero {Ortak çalışan ekle} one {Ortak çalışan ekle} other {Ortak çalışan ekle}}", "longPressAnEmailToVerifyEndToEndEncryption": "Uçtan uca şifrelemeyi doğrulamak için bir e-postaya uzun basın.", diff --git a/mobile/lib/l10n/intl_uk.arb b/mobile/lib/l10n/intl_uk.arb index 582e5c9aea..fea4b16684 100644 --- a/mobile/lib/l10n/intl_uk.arb +++ b/mobile/lib/l10n/intl_uk.arb @@ -318,7 +318,7 @@ "codeUsedByYou": "Код використано вами", "freeStorageClaimed": "Безплатне сховище отримано", "freeStorageUsable": "Безплатне сховище можна використовувати", - "usableReferralStorageInfo": "Доступний обсяг пам'яті обмежений вашим поточним тарифом. Надлишок заявленого обсягу автоматично стане доступним, коли ви покращте тариф.", + "usableReferralStorageInfo": "Доступний обсяг пам'яті обмежений вашим поточним тарифом. Надлишок заявленого обсягу автоматично стане доступним, коли ви покращите тариф.", "removeFromAlbumTitle": "Видалити з альбому?", "removeFromAlbum": "Видалити з альбому", "itemsWillBeRemovedFromAlbum": "Вибрані елементи будуть видалені з цього альбому", @@ -771,7 +771,7 @@ "moveToAlbum": "Перемістити до альбому", "unhide": "Показати", "unarchive": "Розархівувати", - "favorite": "В улюблене", + "favorite": "Додати до улюбленого", "removeFromFavorite": "Вилучити з улюбленого", "shareLink": "Поділитися посиланням", "createCollage": "Створити колаж", @@ -1054,10 +1054,10 @@ "edit": "Редагувати", "deleteLocation": "Видалити розташування", "rotateLeft": "Повернути ліворуч", - "flip": "Перевернути", + "flip": "Відзеркалити", "rotateRight": "Повернути праворуч", "saveCopy": "Зберегти копію", - "light": "Світла", + "light": "Яскравість", "color": "Колір", "yesDiscardChanges": "Так, відхилити зміни", "doYouWantToDiscardTheEditsYouHaveMade": "Ви хочете відхилити внесені зміни?", @@ -1246,7 +1246,6 @@ "deviceCodeHint": "Введіть код", "joinDiscord": "Приєднатися до Discord серверу", "locations": "Розташування", - "descriptions": "Описи", "addAName": "Додати ім'я", "findThemQuickly": "Знайдіть їх швидко", "@findThemQuickly": { @@ -1515,5 +1514,65 @@ "openAlbumInBrowserTitle": "Використовуйте вебзастосунок, щоби додавати фотографії до цього альбому", "allow": "Дозволити", "allowAppToOpenSharedAlbumLinks": "Дозволити застосунку відкривати спільні альбоми", - "seePublicAlbumLinksInApp": "Посилання на публічні альбоми в застосунку" + "seePublicAlbumLinksInApp": "Посилання на публічні альбоми в застосунку", + "emergencyContacts": "Екстрені контакти", + "acceptTrustInvite": "Прийняти запрошення", + "declineTrustInvite": "Відхилити запрошення", + "removeYourselfAsTrustedContact": "Видалити себе як довірений контакт", + "legacy": "Спадок", + "legacyPageDesc": "«Спадок» дозволяє довіреним контактам отримати доступ до вашого облікового запису під час вашої відсутності.", + "legacyPageDesc2": "Довірені контакти можуть ініціювати відновлення облікового запису, і якщо його не буде заблоковано протягом 30 днів, скинути пароль і отримати доступ до нього.", + "legacyAccounts": "Облікові записи «Спадку»", + "trustedContacts": "Довірені контакти", + "addTrustedContact": "Додати довірений контакт", + "removeInvite": "Видалити запрошення", + "recoveryWarning": "Довірений контакт намагається отримати доступ до вашого облікового запису", + "rejectRecovery": "Відхилити відновлення", + "recoveryInitiated": "Почато відновлення", + "recoveryInitiatedDesc": "Ви зможете отримати доступ до облікового запису через {days} днів. Повідомлення буде надіслано на {email}.", + "@recoveryInitiatedDesc": { + "placeholders": { + "days": { + "type": "int", + "example": "30" + }, + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "cancelAccountRecovery": "Скасувати відновлення", + "recoveryAccount": "Відновити обліковий запис", + "cancelAccountRecoveryBody": "Ви впевнені, що хочете скасувати відновлення?", + "startAccountRecoveryTitle": "Почати відновлення", + "whyAddTrustContact": "Довірений контакт може допомогти у відновленні ваших даних.", + "recoveryReady": "Тепер ви можете відновити обліковий запис {email}, встановивши новий пароль.", + "@recoveryReady": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "recoveryWarningBody": "{email} намагається відновити ваш обліковий запис.", + "trustedInviteBody": "Ви отримали запрошення стати спадковим контактом від {email}.", + "warning": "Увага", + "proceed": "Продовжити", + "confirmAddingTrustedContact": "Ви збираєтеся додати {email} як довірений контакт. Вони зможуть відновити ваш обліковий запис, якщо ви будете відсутні протягом {numOfDays} днів.", + "@confirmAddingTrustedContact": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + }, + "numOfDays": { + "type": "int", + "example": "30" + } + } + }, + "legacyInvite": "{email} запросив вас стати довіреною особою", + "authToManageLegacy": "Авторизуйтесь, щоби керувати довіреними контактами" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_vi.arb b/mobile/lib/l10n/intl_vi.arb index 042f36dbdf..55e9bc303c 100644 --- a/mobile/lib/l10n/intl_vi.arb +++ b/mobile/lib/l10n/intl_vi.arb @@ -940,8 +940,8 @@ "verificationFailedPleaseTryAgain": "Xác minh không thành công, vui lòng thử lại", "authenticating": "Đang xác thực...", "authenticationSuccessful": "Xác thực thành công!", - "incorrectRecoveryKey": "Khóa phục hồi không chính xác", - "theRecoveryKeyYouEnteredIsIncorrect": "Khóa phục hồi bạn đã nhập không chính xác", + "incorrectRecoveryKey": "Khóa khôi phục không chính xác", + "theRecoveryKeyYouEnteredIsIncorrect": "Khóa khôi phục bạn đã nhập không chính xác", "twofactorAuthenticationSuccessfullyReset": "Xác thực hai yếu tố đã được đặt lại thành công", "pleaseVerifyTheCodeYouHaveEntered": "Vui lòng xác minh mã bạn đã nhập", "pleaseContactSupportIfTheProblemPersists": "Vui lòng liên hệ với bộ phận hỗ trợ nếu vấn đề vẫn tiếp diễn", @@ -1246,7 +1246,6 @@ "deviceCodeHint": "Nhập mã", "joinDiscord": "Tham gia Discord", "locations": "Vị trí", - "descriptions": "Mô tả", "addAName": "Thêm một tên", "findThemQuickly": "Tìm họ nhanh chóng", "@findThemQuickly": { @@ -1515,5 +1514,65 @@ "openAlbumInBrowserTitle": "Vui lòng sử dụng ứng dụng web để thêm ảnh vào album này", "allow": "Cho phép", "allowAppToOpenSharedAlbumLinks": "Cho phép ứng dụng mở liên kết album chia sẻ", - "seePublicAlbumLinksInApp": "Xem liên kết album công khai trong ứng dụng" + "seePublicAlbumLinksInApp": "Xem liên kết album công khai trong ứng dụng", + "emergencyContacts": "Liên hệ khẩn cấp", + "acceptTrustInvite": "Chấp nhận lời mời", + "declineTrustInvite": "Từ chối lời mời", + "removeYourselfAsTrustedContact": "Gỡ bỏ bạn khỏi liên hệ tin cậy", + "legacy": "Thừa kế", + "legacyPageDesc": "Thừa kế cho phép các liên hệ tin cậy truy cập tài khoản của bạn khi bạn không hoạt động.", + "legacyPageDesc2": "Các liên hệ tin cậy có thể khởi động quá trình khôi phục tài khoản, và nếu không bị chặn trong vòng 30 ngày, có thể đặt lại mật khẩu và truy cập tài khoản của bạn.", + "legacyAccounts": "Tài khoản thừa kế", + "trustedContacts": "Liên hệ tin cậy", + "addTrustedContact": "Thêm liên hệ tin cậy", + "removeInvite": "Gỡ bỏ lời mời", + "recoveryWarning": "Một liên hệ tin cậy đang cố gắng truy cập tài khoản của bạn", + "rejectRecovery": "Từ chối khôi phục", + "recoveryInitiated": "Quá trình khôi phục đã được khởi động", + "recoveryInitiatedDesc": "Bạn có thể truy cập tài khoản sau {days} ngày. Một thông báo sẽ được gửi đến {email}.", + "@recoveryInitiatedDesc": { + "placeholders": { + "days": { + "type": "int", + "example": "30" + }, + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "cancelAccountRecovery": "Hủy khôi phục", + "recoveryAccount": "Khôi phục tài khoản", + "cancelAccountRecoveryBody": "Bạn có chắc chắn muốn hủy khôi phục không?", + "startAccountRecoveryTitle": "Bắt đầu khôi phục", + "whyAddTrustContact": "Liên hệ tin cậy có thể giúp khôi phục dữ liệu của bạn.", + "recoveryReady": "Bạn có thể khôi phục tài khoản của {email} bằng cách đặt lại mật khẩu mới.", + "@recoveryReady": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + } + } + }, + "recoveryWarningBody": "{email} đang cố gắng khôi phục tài khoản của bạn.", + "trustedInviteBody": "Bạn đã được mời làm người liên hệ thừa kế bởi {email}.", + "warning": "Cảnh báo", + "proceed": "Tiếp tục", + "confirmAddingTrustedContact": "Bạn sắp thêm {email} làm liên hệ tin cậy. Họ sẽ có thể khôi phục tài khoản của bạn nếu bạn không hoạt động trong {numOfDays} ngày.", + "@confirmAddingTrustedContact": { + "placeholders": { + "email": { + "type": "String", + "example": "me@example.com" + }, + "numOfDays": { + "type": "int", + "example": "30" + } + } + }, + "legacyInvite": "{email} đã mời bạn trở thành một liên hệ tin cậy", + "authToManageLegacy": "Vui lòng xác thực để quản lý các liên hệ tin cậy của bạn" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_zh.arb b/mobile/lib/l10n/intl_zh.arb index aa49396b23..45b357a467 100644 --- a/mobile/lib/l10n/intl_zh.arb +++ b/mobile/lib/l10n/intl_zh.arb @@ -1233,7 +1233,6 @@ "deviceCodeHint": "输入代码", "joinDiscord": "加入 Discord", "locations": "位置", - "descriptions": "描述", "addAName": "添加一个名称", "findPeopleByName": "按名称快速查找人物", "addViewers": "{count, plural, zero {添加查看者} one {添加查看者} other {添加查看者}}", diff --git a/mobile/lib/services/local_sync_service.dart b/mobile/lib/services/local_sync_service.dart index 1915ac30c2..d8237dd92a 100644 --- a/mobile/lib/services/local_sync_service.dart +++ b/mobile/lib/services/local_sync_service.dart @@ -23,6 +23,7 @@ import "package:photos/utils/debouncer.dart"; import "package:photos/utils/photo_manager_util.dart"; import "package:photos/utils/sqlite_util.dart"; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:synchronized/synchronized.dart'; import 'package:tuple/tuple.dart'; class LocalSyncService { @@ -31,6 +32,7 @@ class LocalSyncService { late SharedPreferences _prefs; Completer? _existingSync; late Debouncer _changeCallbackDebouncer; + final Lock _lock = Lock(); static const kDbUpdationTimeKey = "db_updation_time"; static const kHasCompletedFirstImportKey = "has_completed_firstImport"; @@ -77,50 +79,57 @@ class LocalSyncService { } _existingSync = Completer(); final int ownerID = Configuration.instance.getUserID()!; - final existingLocalFileIDs = await _db.getExistingLocalFileIDs(ownerID); - _logger.info("${existingLocalFileIDs.length} localIDs were discovered"); + + // We use a lock to prevent synchronisation to occur while it is downloading + // as this introduces wrong entry in FilesDB due to race condition + // This is a fix for https://github.com/ente-io/ente/issues/4296 + await _lock.synchronized(() async { + final existingLocalFileIDs = await _db.getExistingLocalFileIDs(ownerID); + _logger.info("${existingLocalFileIDs.length} localIDs were discovered"); - final syncStartTime = DateTime.now().microsecondsSinceEpoch; - final lastDBUpdationTime = _prefs.getInt(kDbUpdationTimeKey) ?? 0; - final startTime = DateTime.now().microsecondsSinceEpoch; - if (lastDBUpdationTime != 0) { - await _loadAndStoreDiff( - existingLocalFileIDs, - fromTime: lastDBUpdationTime, - toTime: syncStartTime, - ); - } else { - // Load from 0 - 01.01.2010 - Bus.instance.fire(SyncStatusUpdate(SyncStatus.startedFirstGalleryImport)); - var startTime = 0; - var toYear = 2010; - var toTime = DateTime(toYear).microsecondsSinceEpoch; - while (toTime < syncStartTime) { + final syncStartTime = DateTime.now().microsecondsSinceEpoch; + final lastDBUpdationTime = _prefs.getInt(kDbUpdationTimeKey) ?? 0; + final startTime = DateTime.now().microsecondsSinceEpoch; + if (lastDBUpdationTime != 0) { + await _loadAndStoreDiff( + existingLocalFileIDs, + fromTime: lastDBUpdationTime, + toTime: syncStartTime, + ); + } else { + // Load from 0 - 01.01.2010 + Bus.instance.fire(SyncStatusUpdate(SyncStatus.startedFirstGalleryImport)); + var startTime = 0; + var toYear = 2010; + var toTime = DateTime(toYear).microsecondsSinceEpoch; + while (toTime < syncStartTime) { + await _loadAndStoreDiff( + existingLocalFileIDs, + fromTime: startTime, + toTime: toTime, + ); + startTime = toTime; + toYear++; + toTime = DateTime(toYear).microsecondsSinceEpoch; + } await _loadAndStoreDiff( existingLocalFileIDs, fromTime: startTime, - toTime: toTime, + toTime: syncStartTime, ); - startTime = toTime; - toYear++; - toTime = DateTime(toYear).microsecondsSinceEpoch; } - await _loadAndStoreDiff( - existingLocalFileIDs, - fromTime: startTime, - toTime: syncStartTime, - ); - } - if (!hasCompletedFirstImport()) { - await _prefs.setBool(kHasCompletedFirstImportKey, true); - await _refreshDeviceFolderCountAndCover(isFirstSync: true); - _logger.fine("first gallery import finished"); - Bus.instance - .fire(SyncStatusUpdate(SyncStatus.completedFirstGalleryImport)); - } - final endTime = DateTime.now().microsecondsSinceEpoch; - final duration = Duration(microseconds: endTime - startTime); - _logger.info("Load took " + duration.inMilliseconds.toString() + "ms"); + if (!hasCompletedFirstImport()) { + await _prefs.setBool(kHasCompletedFirstImportKey, true); + await _refreshDeviceFolderCountAndCover(isFirstSync: true); + _logger.fine("first gallery import finished"); + Bus.instance + .fire(SyncStatusUpdate(SyncStatus.completedFirstGalleryImport)); + } + final endTime = DateTime.now().microsecondsSinceEpoch; + final duration = Duration(microseconds: endTime - startTime); + _logger.info("Load took " + duration.inMilliseconds.toString() + "ms"); + }); + _existingSync?.complete(); _existingSync = null; } @@ -240,6 +249,10 @@ class LocalSyncService { } } + Lock getLock() { + return _lock; + } + bool hasGrantedPermissions() { return _prefs.getBool(kHasGrantedPermissionsKey) ?? false; } diff --git a/mobile/lib/theme/ente_theme.dart b/mobile/lib/theme/ente_theme.dart index f1290c1616..0bcc2261ac 100644 --- a/mobile/lib/theme/ente_theme.dart +++ b/mobile/lib/theme/ente_theme.dart @@ -18,6 +18,10 @@ class EnteTheme { required this.shadowMenu, required this.shadowButton, }); + + bool isDark(BuildContext context) { + return Theme.of(context).brightness == Brightness.dark; + } } EnteTheme lightTheme = EnteTheme( diff --git a/mobile/lib/ui/actions/collection/collection_sharing_actions.dart b/mobile/lib/ui/actions/collection/collection_sharing_actions.dart index 0e427b2532..dc9f58d9f3 100644 --- a/mobile/lib/ui/actions/collection/collection_sharing_actions.dart +++ b/mobile/lib/ui/actions/collection/collection_sharing_actions.dart @@ -19,6 +19,7 @@ import 'package:photos/services/user_service.dart'; import 'package:photos/theme/colors.dart'; import 'package:photos/theme/ente_theme.dart'; import 'package:photos/ui/common/progress_dialog.dart'; +import "package:photos/ui/common/user_dialogs.dart"; import 'package:photos/ui/components/action_sheet_widget.dart'; import 'package:photos/ui/components/buttons/button_widget.dart'; import 'package:photos/ui/components/dialog_widget.dart'; @@ -233,28 +234,7 @@ class CollectionActions { if (publicKey == null || publicKey == '') { // todo: neeraj replace this as per the design where a new screen // is used for error. Do this change along with handling of network errors - await showDialogWidget( - context: context, - title: S.of(context).inviteToEnte, - icon: Icons.info_outline, - body: S.of(context).emailNoEnteAccount(email), - isDismissible: true, - buttons: [ - ButtonWidget( - buttonType: ButtonType.neutral, - icon: Icons.adaptive.share, - labelText: S.of(context).sendInvite, - isInAlert: true, - onTap: () async { - unawaited( - shareText( - S.of(context).shareTextRecommendUsingEnte, - ), - ); - }, - ), - ], - ); + await showInviteDialog(context, email); return false; } else { return true; diff --git a/mobile/lib/ui/common/user_dialogs.dart b/mobile/lib/ui/common/user_dialogs.dart new file mode 100644 index 0000000000..4ddbe85efe --- /dev/null +++ b/mobile/lib/ui/common/user_dialogs.dart @@ -0,0 +1,33 @@ +import "dart:async"; + +import "package:flutter/material.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/ui/components/buttons/button_widget.dart"; +import "package:photos/ui/components/dialog_widget.dart"; +import "package:photos/ui/components/models/button_type.dart"; +import "package:photos/utils/share_util.dart"; + +Future showInviteDialog(BuildContext context, String email) async { + await showDialogWidget( + context: context, + title: S.of(context).inviteToEnte, + icon: Icons.info_outline, + body: S.of(context).emailNoEnteAccount(email), + isDismissible: true, + buttons: [ + ButtonWidget( + buttonType: ButtonType.neutral, + icon: Icons.adaptive.share, + labelText: S.of(context).sendInvite, + isInAlert: true, + onTap: () async { + unawaited( + shareText( + S.of(context).shareTextRecommendUsingEnte, + ), + ); + }, + ), + ], + ); +} diff --git a/mobile/lib/ui/components/notification_widget.dart b/mobile/lib/ui/components/notification_widget.dart index 2a8e18c712..6036b44771 100644 --- a/mobile/lib/ui/components/notification_widget.dart +++ b/mobile/lib/ui/components/notification_widget.dart @@ -17,7 +17,7 @@ enum NotificationType { class NotificationWidget extends StatelessWidget { final IconData startIcon; - final IconData actionIcon; + final IconData? actionIcon; final String text; final String? subText; final GestureTapCallback onTap; @@ -155,14 +155,15 @@ class NotificationWidget extends StatelessWidget { ), ), const SizedBox(width: 12), - IconButtonWidget( - icon: actionIcon, - iconButtonType: IconButtonType.rounded, - iconColor: strokeColorScheme.strokeBase, - defaultColor: strokeColorScheme.fillFaint, - pressedColor: strokeColorScheme.fillMuted, - onTap: onTap, - ), + if (actionIcon != null) + IconButtonWidget( + icon: actionIcon!, + iconButtonType: IconButtonType.rounded, + iconColor: strokeColorScheme.strokeBase, + defaultColor: strokeColorScheme.fillFaint, + pressedColor: strokeColorScheme.fillMuted, + onTap: onTap, + ), ], ), ), diff --git a/mobile/lib/ui/settings/account_section_widget.dart b/mobile/lib/ui/settings/account_section_widget.dart index 48193dc3b9..2c67514268 100644 --- a/mobile/lib/ui/settings/account_section_widget.dart +++ b/mobile/lib/ui/settings/account_section_widget.dart @@ -1,6 +1,8 @@ import 'dart:async'; +import "package:flutter/foundation.dart"; import 'package:flutter/material.dart'; +import "package:photos/emergency/emergency_page.dart"; import "package:photos/generated/l10n.dart"; import 'package:photos/services/local_authentication_service.dart'; import 'package:photos/services/user_service.dart'; @@ -142,6 +144,33 @@ class AccountSectionWidget extends StatelessWidget { }, ), sectionOptionSpacing, + MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: S.of(context).legacy, + ), + pressedColor: getEnteColorScheme(context).fillFaint, + trailingIcon: Icons.chevron_right_outlined, + trailingIconIsMuted: true, + showOnlyLoadingState: true, + onTap: () async { + final hasAuthenticated = kDebugMode || + await LocalAuthenticationService.instance + .requestLocalAuthentication( + context, + S.of(context).authToManageLegacy, + ); + if (hasAuthenticated) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return const EmergencyPage(); + }, + ), + ).ignore(); + } + }, + ), + sectionOptionSpacing, MenuItemWidget( captionedTextWidget: CaptionedTextWidget( title: S.of(context).exportYourData, diff --git a/mobile/lib/ui/tools/editor/video_editor_page.dart b/mobile/lib/ui/tools/editor/video_editor_page.dart index 392d93034c..d96f0008a7 100644 --- a/mobile/lib/ui/tools/editor/video_editor_page.dart +++ b/mobile/lib/ui/tools/editor/video_editor_page.dart @@ -299,6 +299,8 @@ class _VideoEditorPageState extends State { ); } catch (_) { await dialog.hide(); + } finally { + await PhotoManager.startChangeNotify(); } } } diff --git a/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart b/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart index 00c06bb2ef..16f23e3f73 100644 --- a/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart +++ b/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart @@ -822,17 +822,27 @@ class _FileSelectionActionsWidgetState } Future _download(List files) async { + final totalFiles = files.length; + int downloadedFiles = 0; + final dialog = createProgressDialog( context, - S.of(context).downloading, + S.of(context).downloading + " ($downloadedFiles/$totalFiles)", isDismissible: true, ); await dialog.show(); try { + final downloadQueue = DownloadQueue(maxConcurrent: 5); final futures = []; for (final file in files) { if (file.localID == null) { - futures.add(downloadToGallery(file)); + futures.add( + downloadQueue.add(() async { + await downloadToGallery(file); + downloadedFiles++; + dialog.update(message: S.of(context).downloading + " ($downloadedFiles/$totalFiles)"); + }), + ); } } await Future.wait(futures); diff --git a/mobile/lib/utils/device_info.dart b/mobile/lib/utils/device_info.dart index 70aae5d55a..4925a0b144 100644 --- a/mobile/lib/utils/device_info.dart +++ b/mobile/lib/utils/device_info.dart @@ -50,3 +50,21 @@ Future isAndroidSDKVersionLowerThan(int inputSDK) async { return false; } } + +Future getDeviceName() async { + try { + if (Platform.isIOS) { + final IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo; + return iosInfo.utsname.machine; + } else if (Platform.isAndroid) { + final AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo; + + return "${androidInfo.brand} ${androidInfo.model}"; + } else { + return "Not iOS or Android"; + } + } catch (e) { + Logger("device_info").severe("deviceSpec check failed", e); + return null; + } +} diff --git a/mobile/lib/utils/file_download_util.dart b/mobile/lib/utils/file_download_util.dart index 1014005c94..bb5fbeb323 100644 --- a/mobile/lib/utils/file_download_util.dart +++ b/mobile/lib/utils/file_download_util.dart @@ -1,3 +1,5 @@ +import "dart:async"; +import "dart:collection"; import 'dart:io'; import 'package:dio/dio.dart'; @@ -190,44 +192,49 @@ Future downloadToGallery(EnteFile file) async { type == FileType.livePhoto && Platform.isAndroid; AssetEntity? savedAsset; final File? fileToSave = await getFile(file); - //Disabling notifications for assets changing to insert the file into - //files db before triggering a sync. - await PhotoManager.stopChangeNotify(); - if (type == FileType.image) { - savedAsset = await PhotoManager.editor - .saveImageWithPath(fileToSave!.path, title: file.title!); - } else if (type == FileType.video) { - savedAsset = - await PhotoManager.editor.saveVideo(fileToSave!, title: file.title!); - } else if (type == FileType.livePhoto) { - final File? liveVideoFile = - await getFileFromServer(file, liveVideo: true); - if (liveVideoFile == null) { - throw AssertionError("Live video can not be null"); + // We use a lock to prevent synchronisation to occur while it is downloading + // as this introduces wrong entry in FilesDB due to race condition + // This is a fix for https://github.com/ente-io/ente/issues/4296 + await LocalSyncService.instance.getLock().synchronized(() async { + //Disabling notifications for assets changing to insert the file into + //files db before triggering a sync. + await PhotoManager.stopChangeNotify(); + if (type == FileType.image) { + savedAsset = await PhotoManager.editor + .saveImageWithPath(fileToSave!.path, title: file.title!); + } else if (type == FileType.video) { + savedAsset = + await PhotoManager.editor.saveVideo(fileToSave!, title: file.title!); + } else if (type == FileType.livePhoto) { + final File? liveVideoFile = + await getFileFromServer(file, liveVideo: true); + if (liveVideoFile == null) { + throw AssertionError("Live video can not be null"); + } + if (downloadLivePhotoOnDroid) { + await _saveLivePhotoOnDroid(fileToSave!, liveVideoFile, file); + } else { + savedAsset = await PhotoManager.editor.darwin.saveLivePhoto( + imageFile: fileToSave!, + videoFile: liveVideoFile, + title: file.title!, + ); + } } - if (downloadLivePhotoOnDroid) { - await _saveLivePhotoOnDroid(fileToSave!, liveVideoFile, file); - } else { - savedAsset = await PhotoManager.editor.darwin.saveLivePhoto( - imageFile: fileToSave!, - videoFile: liveVideoFile, - title: file.title!, - ); - } - } - if (savedAsset != null) { - file.localID = savedAsset.id; - await FilesDB.instance.insert(file); - Bus.instance.fire( - LocalPhotosUpdatedEvent( - [file], - source: "download", - ), - ); - } else if (!downloadLivePhotoOnDroid && savedAsset == null) { - _logger.severe('Failed to save assert of type $type'); - } + if (savedAsset != null) { + file.localID = savedAsset!.id; + await FilesDB.instance.insert(file); + Bus.instance.fire( + LocalPhotosUpdatedEvent( + [file], + source: "download", + ), + ); + } else if (!downloadLivePhotoOnDroid && savedAsset == null) { + _logger.severe('Failed to save assert of type $type'); + } + }); } catch (e) { _logger.severe("Failed to save file", e); rethrow; @@ -273,3 +280,37 @@ Future _saveLivePhotoOnDroid( ); await IgnoredFilesService.instance.cacheAndInsert([ignoreVideoFile]); } + +class DownloadQueue { + final int maxConcurrent; + final Queue Function()> _queue = Queue(); + int _runningTasks = 0; + + DownloadQueue({this.maxConcurrent = 5}); + + Future add(Future Function() task) async { + final completer = Completer(); + _queue.add(() async { + try { + await task(); + completer.complete(); + } catch (e) { + completer.completeError(e); + } finally { + _runningTasks--; + _processQueue(); + } + return completer.future; + }); + _processQueue(); + return completer.future; + } + + void _processQueue() { + while (_runningTasks < maxConcurrent && _queue.isNotEmpty) { + final task = _queue.removeFirst(); + _runningTasks++; + task(); + } + } +} \ No newline at end of file diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index b475dd03b7..41c3100014 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.9.65+965 +version: 0.9.71+971 publish_to: none environment: diff --git a/server/Dockerfile b/server/Dockerfile index 778d5ed8ee..b29bbc314a 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21-alpine3.17 AS builder +FROM golang:1.23.4-alpine3.21 AS builder RUN apk add --no-cache gcc musl-dev git build-base pkgconfig libsodium-dev ENV GOOS=linux @@ -13,7 +13,7 @@ COPY . . RUN --mount=type=cache,target=/root/.cache/go-build \ go build -o museum cmd/museum/main.go -FROM alpine:3.17 +FROM alpine:3.21 RUN apk add libsodium-dev COPY --from=builder /etc/ente/museum . COPY configurations configurations diff --git a/server/cmd/museum/main.go b/server/cmd/museum/main.go index 50545dc6eb..cb2a8e2d48 100644 --- a/server/cmd/museum/main.go +++ b/server/cmd/museum/main.go @@ -6,8 +6,10 @@ import ( b64 "encoding/base64" "fmt" "github.com/ente-io/museum/ente/base" + "github.com/ente-io/museum/pkg/controller/emergency" "github.com/ente-io/museum/pkg/controller/file_copy" "github.com/ente-io/museum/pkg/controller/filedata" + emergencyRepo "github.com/ente-io/museum/pkg/repo/emergency" "net/http" "os" "os/signal" @@ -460,8 +462,14 @@ func main() { privateAPI.POST("/trash/delete", trashHandler.Delete) privateAPI.POST("/trash/empty", trashHandler.Empty) + emergencyCtrl := &emergency.Controller{ + Repo: &emergencyRepo.Repository{DB: db}, + UserRepo: userRepo, + UserCtrl: userController, + } userHandler := &api.UserHandler{ - UserController: userController, + UserController: userController, + EmergencyController: emergencyCtrl, } publicAPI.POST("/users/ott", userHandler.SendOTT) publicAPI.POST("/users/verify-email", userHandler.VerifyEmail) @@ -605,6 +613,20 @@ func main() { familiesJwtAuthAPI.DELETE("/family/remove-member/:id", familyHandler.RemoveMember) familiesJwtAuthAPI.DELETE("/family/revoke-invite/:id", familyHandler.RevokeInvite) + emergencyHandler := &api.EmergencyHandler{ + Controller: emergencyCtrl, + } + + privateAPI.POST("/emergency-contacts/add", emergencyHandler.AddContact) + privateAPI.GET("/emergency-contacts/info", emergencyHandler.GetInfo) + privateAPI.POST("/emergency-contacts/update", emergencyHandler.UpdateContact) + privateAPI.POST("/emergency-contacts/start-recovery", emergencyHandler.StartRecovery) + privateAPI.POST("/emergency-contacts/stop-recovery", emergencyHandler.StopRecovery) + privateAPI.POST("/emergency-contacts/reject-recovery", emergencyHandler.RejectRecovery) + privateAPI.POST("/emergency-contacts/approve-recovery", emergencyHandler.ApproveRecovery) + privateAPI.GET("/emergency-contacts/recovery-info/:id", emergencyHandler.GetRecoveryInfo) + privateAPI.POST("/emergency-contacts/init-change-password", emergencyHandler.InitChangePassword) + privateAPI.POST("/emergency-contacts/change-password", emergencyHandler.ChangePassword) billingHandler := &api.BillingHandler{ Controller: billingController, AppStoreController: appStoreController, @@ -645,6 +667,7 @@ func main() { UserAuthRepo: userAuthRepo, UserController: userController, FamilyController: familyController, + EmergencyController: emergencyCtrl, RemoteStoreController: remoteStoreController, FileRepo: fileRepo, StorageBonusRepo: storagBonusRepo, diff --git a/server/ente/emergency.go b/server/ente/emergency.go new file mode 100644 index 0000000000..0dcab7ab17 --- /dev/null +++ b/server/ente/emergency.go @@ -0,0 +1,76 @@ +package ente + +import "github.com/google/uuid" + +type AddContact struct { + Email string `json:"email" binding:"required"` + EncryptedKey string `json:"encryptedKey" binding:"required"` + // Indicates after how many days, the emergency contact will be able to recover the account, if the user + // does not deny the recovery request + RecoveryNoticeInDays *int `json:"recoveryNoticeInDays"` +} + +type UpdateContact struct { + UserID int64 `json:"userID" binding:"required"` + EmergencyContactID int64 `json:"emergencyContactID" binding:"required"` + State ContactState `json:"state" binding:"required"` +} + +type ContactIdentifier struct { + UserID int64 `json:"userID" binding:"required"` + EmergencyContactID int64 `json:"emergencyContactID" binding:"required"` +} + +type RecoveryIdentifier struct { + ID uuid.UUID `json:"id" binding:"required"` + UserID int64 `json:"userID" binding:"required"` + EmergencyContactID int64 `json:"emergencyContactID" binding:"required"` +} + +type ContactState string + +const ( + UserInvitedContact ContactState = "INVITED" + UserRevokedContact ContactState = "REVOKED" + ContactAccepted ContactState = "ACCEPTED" + ContactLeft ContactState = "CONTACT_LEFT" + ContactDenied ContactState = "CONTACT_DENIED" +) + +type EmergencyContactEntity struct { + User BasicUser `json:"user"` + EmergencyContact BasicUser `json:"emergencyContact"` + State ContactState `json:"state"` + RecoveryNoticeInDays int32 `json:"recoveryNoticeInDays"` +} + +type RecoveryStatus string + +const ( + RecoveryStatusInitiated RecoveryStatus = "INITIATED" + RecoveryStatusWaiting RecoveryStatus = "WAITING" + RecoveryStatusRejected RecoveryStatus = "REJECTED" + RecoveryStatusRecovered RecoveryStatus = "RECOVERED" + RecoveryStatusStopped RecoveryStatus = "STOPPED" + RecoveryStatusReady RecoveryStatus = "READY" +) + +func (rs RecoveryStatus) Ptr() *RecoveryStatus { + return &rs +} + +type RecoverySession struct { + ID uuid.UUID `json:"id"` + User BasicUser `json:"user"` + EmergencyContact BasicUser `json:"emergencyContact"` + Status RecoveryStatus `json:"status"` + WaitTill int64 `json:"waitTill"` + CreatedAt int64 `json:"createdAt"` +} + +type EmergencyDataResponse struct { + Contacts []*EmergencyContactEntity `json:"contacts"` + RecoverySessions []*RecoverySession `json:"recoverSessions"` + OthersEmergencyContact []*EmergencyContactEntity `json:"othersEmergencyContact"` + OthersRecoverSessions []*RecoverySession `json:"othersRecoverySession"` +} diff --git a/server/ente/srp.go b/server/ente/srp.go index b1d5334433..6a55dd1e86 100644 --- a/server/ente/srp.go +++ b/server/ente/srp.go @@ -26,6 +26,16 @@ type CompleteSRPSetupResponse struct { SRPM2 string `json:"srpM2" binding:"required"` } +type RecoverySrpSetupRequest struct { + RecoveryID uuid.UUID `json:"recoveryID" binding:"required"` + SetUpSRPReq SetupSRPRequest `json:"setupSRPRequest" binding:"required"` +} + +type RecoveryUpdateSRPAndKeysRequest struct { + RecoveryID uuid.UUID `json:"recoveryID" binding:"required"` + UpdateSrp UpdateSRPAndKeysRequest `json:"updateSrpAndKeysRequest" binding:"required"` +} + // UpdateSRPAndKeysRequest is used to update the SRP attributes (e.g. when user updates his password) and also // update the keys attributes type UpdateSRPAndKeysRequest struct { diff --git a/server/ente/user.go b/server/ente/user.go index aa2e3ca142..59925e90c1 100644 --- a/server/ente/user.go +++ b/server/ente/user.go @@ -215,3 +215,8 @@ type Session struct { PrettyUA string `json:"prettyUA"` LastUsedTime int64 `json:"lastUsedTime"` } + +type BasicUser struct { + ID int64 `json:"id"` + Email string `json:"email"` +} diff --git a/server/go.mod b/server/go.mod index ad40901322..ace84d9fb6 100644 --- a/server/go.mod +++ b/server/go.mod @@ -1,6 +1,6 @@ module github.com/ente-io/museum -go 1.21 +go 1.23 require ( firebase.google.com/go v3.13.0+incompatible diff --git a/server/mail-templates/legacy/legacy_base.html b/server/mail-templates/legacy/legacy_base.html new file mode 100644 index 0000000000..5e0629fc04 --- /dev/null +++ b/server/mail-templates/legacy/legacy_base.html @@ -0,0 +1,139 @@ +{{define "legacy_base"}} + + + + + + + +
 
+
+
+ {{block "content" .}} Default Content {{end}} +
+
+
+ + + +{{end}} + diff --git a/server/mail-templates/legacy/legacy_invite.html b/server/mail-templates/legacy/legacy_invite.html new file mode 100644 index 0000000000..6ae2827220 --- /dev/null +++ b/server/mail-templates/legacy/legacy_invite.html @@ -0,0 +1,10 @@ +{{define "content"}} +

Hello,

+ +

{{.LegacyContact}} has invited you to be their trusted contact.

+ +

As a trusted contact, you can recover {{.LegacyContact}}'s account in their absence.

+

To accept the invite, please open the Ente Photos app, and navigate to Settings > Account > Legacy.

+ +

If you need help, please reply to this email.

+{{end}} diff --git a/server/mail-templates/legacy/legacy_invite_accepted.html b/server/mail-templates/legacy/legacy_invite_accepted.html new file mode 100644 index 0000000000..ff5de53fe7 --- /dev/null +++ b/server/mail-templates/legacy/legacy_invite_accepted.html @@ -0,0 +1,9 @@ +{{define "content"}} +

Hello,

+ +

{{.TrustedContact}} has accepted your request to be your trusted contact.

+ +

As a trusted contact, {{.TrustedContact}} can recover your account in your absence.

+ +

If you need help, please reply to this email.

+{{end}} diff --git a/server/mail-templates/legacy/legacy_invite_rejected.html b/server/mail-templates/legacy/legacy_invite_rejected.html new file mode 100644 index 0000000000..15eed9800d --- /dev/null +++ b/server/mail-templates/legacy/legacy_invite_rejected.html @@ -0,0 +1,7 @@ +{{define "content"}} +

Hello,

+ +

{{.TrustedContact}} has rejected your request to be a trusted contact.

+ +

If you need help, please reply to this email

+{{end}} diff --git a/server/mail-templates/legacy/legacy_invite_sent.html b/server/mail-templates/legacy/legacy_invite_sent.html new file mode 100644 index 0000000000..ae32c3bd2f --- /dev/null +++ b/server/mail-templates/legacy/legacy_invite_sent.html @@ -0,0 +1,10 @@ +{{define "content"}} +

Hello,

+ +

You have invited {{.TrustedContact}} to be your trusted contact.

+ +

As a trusted contact, {{.TrustedContact}} can recover your account in your absence.

+

If you want to cancel the invite, please navigate to Settings -> Account -> Legacy in the Ente Photos app.

+ +

If you need help, please reply to this email.

+{{end}} diff --git a/server/mail-templates/legacy/legacy_left.html b/server/mail-templates/legacy/legacy_left.html new file mode 100644 index 0000000000..042225d822 --- /dev/null +++ b/server/mail-templates/legacy/legacy_left.html @@ -0,0 +1,7 @@ +{{define "content"}} +

Hello,

+ +

{{.TrustedContact}} has removed themselves from being your trusted contact.

+ +

If you need help, please reply to this email.

+{{end}} diff --git a/server/mail-templates/legacy/legacy_removed.html b/server/mail-templates/legacy/legacy_removed.html new file mode 100644 index 0000000000..845656be32 --- /dev/null +++ b/server/mail-templates/legacy/legacy_removed.html @@ -0,0 +1,7 @@ +{{define "content"}} +

Hello,

+ +

{{.LegacyContact}} has removed you as their trusted contact.

+ +

If you need help, please reply to this email.

+{{end}} diff --git a/server/mail-templates/legacy/recovery_cancelled.html b/server/mail-templates/legacy/recovery_cancelled.html new file mode 100644 index 0000000000..e15e45bcf8 --- /dev/null +++ b/server/mail-templates/legacy/recovery_cancelled.html @@ -0,0 +1,7 @@ +{{define "content"}} +

Hello,

+ +

{{.TrustedContact}} has cancelled the process of recovering your account.

+ +

If you need help, please reply to this email.

+{{end}} diff --git a/server/mail-templates/legacy/recovery_completed_legacy.html b/server/mail-templates/legacy/recovery_completed_legacy.html new file mode 100644 index 0000000000..31eea89461 --- /dev/null +++ b/server/mail-templates/legacy/recovery_completed_legacy.html @@ -0,0 +1,7 @@ +{{define "content"}} +

Hello,

+ +

{{.TrustedContact}} has changed the password and can now access your account. + +

If you need help, please reply to this email.

+{{end}} diff --git a/server/mail-templates/legacy/recovery_completed_trusted.html b/server/mail-templates/legacy/recovery_completed_trusted.html new file mode 100644 index 0000000000..cfb99a0317 --- /dev/null +++ b/server/mail-templates/legacy/recovery_completed_trusted.html @@ -0,0 +1,7 @@ +{{define "content"}} +

Hello,

+ +

You can now access {{.LegacyContact}}'s account with the new password you setup on Ente.

+ +

If you need any help, please let us know by responding to this email, we'll be around.

+{{end}} diff --git a/server/mail-templates/legacy/recovery_ready_legacy.html b/server/mail-templates/legacy/recovery_ready_legacy.html new file mode 100644 index 0000000000..e90539ff61 --- /dev/null +++ b/server/mail-templates/legacy/recovery_ready_legacy.html @@ -0,0 +1,7 @@ +{{define "content"}} +

Hello,

+ +

{{.TrustedContact}} can now recover your account by changing the password. + +

If you need help, please reply to this email.

+{{end}} diff --git a/server/mail-templates/legacy/recovery_ready_trusted.html b/server/mail-templates/legacy/recovery_ready_trusted.html new file mode 100644 index 0000000000..bfb46db2a4 --- /dev/null +++ b/server/mail-templates/legacy/recovery_ready_trusted.html @@ -0,0 +1,8 @@ +{{define "content"}} +

Hello,

+ +

You can now recover {{.LegacyContact}}'s account.

+

To change the password to {{.LegacyContact}}'s account, please navigate to Settings > Account > Legacy in the Ente Photos app.

+ +

If you need help, please reply to this email.

+{{end}} diff --git a/server/mail-templates/legacy/recovery_rejected.html b/server/mail-templates/legacy/recovery_rejected.html new file mode 100644 index 0000000000..cdf5356dd3 --- /dev/null +++ b/server/mail-templates/legacy/recovery_rejected.html @@ -0,0 +1,7 @@ +{{define "content"}} +

Hello,

+ +

{{.LegacyContact}} has blocked your request to recover their account.

+ +

If you need help, please reply to this email

+{{end}} diff --git a/server/mail-templates/legacy/recovery_reminder.html b/server/mail-templates/legacy/recovery_reminder.html new file mode 100644 index 0000000000..b9196115fe --- /dev/null +++ b/server/mail-templates/legacy/recovery_reminder.html @@ -0,0 +1,9 @@ +{{define "content"}} +

Hello,

+ +

{{.TrustedContact}} has initiated recovery on your account. After 2 days, they will be able to change the password and access your account.

+ +

If you want to block the recovery, please navigate to Settings > Account > Legacy in the Ente Photos app.

+ +

If you need help, please reply to this email.

+{{end}} diff --git a/server/mail-templates/legacy/recovery_started.html b/server/mail-templates/legacy/recovery_started.html new file mode 100644 index 0000000000..50c9c89391 --- /dev/null +++ b/server/mail-templates/legacy/recovery_started.html @@ -0,0 +1,9 @@ +{{define "content"}} +

Hello,

+ +

{{.TrustedContact}} has initiated recovery on your account. After 30 days, they will be able to change the password and access your account.

+ +

If you want to block the recovery, please navigate to Settings > Account > Legacy in the Ente Photos app.

+ +

If you need help, please reply to this email.

+{{end}} diff --git a/server/migrations/93_emergency_contact.down.sql b/server/migrations/93_emergency_contact.down.sql new file mode 100644 index 0000000000..9ecb876451 --- /dev/null +++ b/server/migrations/93_emergency_contact.down.sql @@ -0,0 +1,12 @@ +DROP TRIGGER IF EXISTS update_emergency_recovery_updated_at ON emergency_recovery; +DROP TRIGGER IF EXISTS update_emergency_conctact_updated_at ON emergency_contact; + +DROP INDEX IF EXISTS idx_emergency_recovery_next_reminder_at; +DROP INDEX IF EXISTS idx_emergency_recovery_user_id; +DROP INDEX IF EXISTS idx_emergency_contact_id; +DROP INDEX IF EXISTS idx_emergency_recovery_limit_active_recovery; + +DROP TABLE IF EXISTS emergency_recovery; +DROP TABLE IF EXISTS emergency_contact; + +DROP FUNCTION IF EXISTS trigger_updated_at_microseconds_column; diff --git a/server/migrations/93_emergency_contact.up.sql b/server/migrations/93_emergency_contact.up.sql new file mode 100644 index 0000000000..9ad3d97984 --- /dev/null +++ b/server/migrations/93_emergency_contact.up.sql @@ -0,0 +1,68 @@ +CREATE TABLE IF NOT EXISTS emergency_contact ( + user_id BIGINT NOT NULL, + emergency_contact_id BIGINT NOT NULL, + state TEXT NOT NULL CHECK (state IN ('INVITED', 'ACCEPTED', 'REVOKED', 'DELETED', 'CONTACT_LEFT', 'CONTACT_DENIED')), + created_at BIGINT NOT NULL DEFAULT now_utc_micro_seconds(), + updated_at BIGINT NOT NULL DEFAULT now_utc_micro_seconds(), + notice_period_in_hrs INT NOT NULL, + encrypted_key TEXT, + CONSTRAINT fk_emergency_contact_user_id + FOREIGN KEY (user_id) + REFERENCES users (user_id) + ON DELETE CASCADE, + CONSTRAINT fk_emergency_contact_emergency_contact_id + FOREIGN KEY (emergency_contact_id) + REFERENCES users (user_id) + ON DELETE CASCADE, + CONSTRAINT chk_user_id_not_equal_emergency_contact_id + CHECK (user_id != emergency_contact_id), + CONSTRAINT chk_encrypted_key_null + CHECK ((state IN ('REVOKED', 'DELETED', 'CONTACT_LEFT', 'CONTACT_DENIED') AND encrypted_key IS NULL) OR + (state NOT IN ('REVOKED', 'DELETED', 'CONTACT_LEFT', 'CONTACT_DENIED') AND encrypted_key IS NOT NULL)), + CONSTRAINT unique_user_emergency_contact + UNIQUE (user_id, emergency_contact_id) +); + +CREATE INDEX idx_emergency_contact_id ON emergency_contact(emergency_contact_id); + + +CREATE TRIGGER update_emergency_conctact_updated_at + BEFORE UPDATE + ON families + FOR EACH ROW +EXECUTE PROCEDURE + trigger_updated_at_microseconds_column(); + + +CREATE TABLE IF NOT EXISTS emergency_recovery ( + id uuid PRIMARY KEY NOT NULL, + user_id BIGINT NOT NULL, + emergency_contact_id BIGINT NOT NULL, + status TEXT NOT NULL CHECK (status IN ('WAITING', 'REJECTED', 'RECOVERED', 'STOPPED', 'READY')), + wait_till BIGINT, + next_reminder_at BIGINT, + created_at BIGINT NOT NULL DEFAULT now_utc_micro_seconds(), + updated_at BIGINT NOT NULL DEFAULT now_utc_micro_seconds(), + CONSTRAINT fk_emergency_recovery_user_id + FOREIGN KEY (user_id) + REFERENCES users (user_id) + ON DELETE CASCADE, + CONSTRAINT fk_emergency_recovery_emergency_contact_id + FOREIGN KEY (emergency_contact_id) + REFERENCES users (user_id) + ON DELETE CASCADE +); + +-- unique constraint on user_id, emergency_contact_id and status where status is WAITING or READY +CREATE UNIQUE INDEX idx_emergency_recovery_limit_active_recovery ON emergency_recovery(user_id, emergency_contact_id, status) + WHERE status IN ('WAITING', 'READY'); + +CREATE INDEX idx_emergency_recovery_user_id ON emergency_recovery(user_id); +CREATE INDEX idx_emergency_recovery_next_reminder_at ON emergency_recovery(next_reminder_at); + +CREATE TRIGGER update_emergency_recovery_updated_at + BEFORE UPDATE + ON families + FOR EACH ROW +EXECUTE PROCEDURE + trigger_updated_at_microseconds_column(); \ No newline at end of file diff --git a/server/pkg/api/admin.go b/server/pkg/api/admin.go index 4e91321777..124d21078f 100644 --- a/server/pkg/api/admin.go +++ b/server/pkg/api/admin.go @@ -3,6 +3,7 @@ package api import ( "errors" "fmt" + "github.com/ente-io/museum/pkg/controller/emergency" "github.com/ente-io/museum/pkg/controller/remotestore" "github.com/ente-io/museum/pkg/repo/authenticator" "net/http" @@ -47,6 +48,7 @@ type AdminHandler struct { StorageBonusRepo *storagebonus.Repository BillingController *controller.BillingController UserController *user.UserController + EmergencyController *emergency.Controller FamilyController *family.Controller RemoteStoreController *remotestore.Controller ObjectCleanupController *controller.ObjectCleanupController @@ -182,6 +184,13 @@ func (h *AdminHandler) DeleteUser(c *gin.Context) { "req_id": requestid.Get(c), "req_ctx": "account_deletion", }) + + // todo: (neeraj) refactor this part, currently there's a circular dependency between user and emergency controllers + removeLegacyErr := h.EmergencyController.HandleAccountDeletion(c, user.ID, logger) + if removeLegacyErr != nil { + handler.Error(c, stacktrace.Propagate(removeLegacyErr, "")) + return + } response, err := h.UserController.HandleAccountDeletion(c, user.ID, logger) if err != nil { handler.Error(c, stacktrace.Propagate(err, "")) diff --git a/server/pkg/api/emergency.go b/server/pkg/api/emergency.go new file mode 100644 index 0000000000..7fef6a27ca --- /dev/null +++ b/server/pkg/api/emergency.go @@ -0,0 +1,156 @@ +package api + +import ( + "github.com/ente-io/museum/ente" + "github.com/ente-io/museum/pkg/controller/emergency" + "github.com/ente-io/museum/pkg/utils/auth" + "github.com/ente-io/museum/pkg/utils/handler" + "github.com/ente-io/stacktrace" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "net/http" +) + +// EmergencyHandler contains handlers for managing emergency contacts +type EmergencyHandler struct { + Controller *emergency.Controller +} + +// AddContact adds a new emergency contact for current user +func (h *EmergencyHandler) AddContact(c *gin.Context) { + var request ente.AddContact + if err := c.ShouldBindJSON(&request); err != nil { + handler.Error(c, stacktrace.Propagate(err, "Could not bind request params")) + return + } + err := h.Controller.AddContact(c, auth.GetUserID(c.Request.Header), request) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.Status(http.StatusOK) +} + +func (h *EmergencyHandler) GetInfo(c *gin.Context) { + resp, err := h.Controller.GetInfo(c, auth.GetUserID(c.Request.Header)) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.JSON(http.StatusOK, resp) +} + +func (h *EmergencyHandler) UpdateContact(c *gin.Context) { + var request ente.UpdateContact + if err := c.ShouldBindJSON(&request); err != nil { + handler.Error(c, stacktrace.Propagate(ente.NewBadRequestWithMessage("failed to validate req param"), err.Error())) + return + } + err := h.Controller.UpdateContact(c, auth.GetUserID(c.Request.Header), request) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.JSON(http.StatusOK, gin.H{}) +} + +func (h *EmergencyHandler) StartRecovery(c *gin.Context) { + var request ente.ContactIdentifier + if err := c.ShouldBindJSON(&request); err != nil { + handler.Error(c, stacktrace.Propagate(ente.NewBadRequestWithMessage("failed to validate req param"), err.Error())) + return + } + err := h.Controller.StartRecovery(c, auth.GetUserID(c.Request.Header), request) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.JSON(http.StatusOK, gin.H{}) +} + +func (h *EmergencyHandler) StopRecovery(c *gin.Context) { + var request ente.RecoveryIdentifier + if err := c.ShouldBindJSON(&request); err != nil { + handler.Error(c, stacktrace.Propagate(ente.NewBadRequestWithMessage("failed to validate req param"), err.Error())) + return + } + err := h.Controller.StopRecovery(c, auth.GetUserID(c.Request.Header), request) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.JSON(http.StatusOK, gin.H{}) +} + +func (h *EmergencyHandler) RejectRecovery(c *gin.Context) { + var request ente.RecoveryIdentifier + if err := c.ShouldBindJSON(&request); err != nil { + handler.Error(c, stacktrace.Propagate(ente.NewBadRequestWithMessage("failed to validate req param"), err.Error())) + return + } + err := h.Controller.RejectRecovery(c, auth.GetUserID(c.Request.Header), request) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.JSON(http.StatusOK, gin.H{}) +} + +func (h *EmergencyHandler) ApproveRecovery(c *gin.Context) { + var request ente.RecoveryIdentifier + if err := c.ShouldBindJSON(&request); err != nil { + handler.Error(c, stacktrace.Propagate(ente.NewBadRequestWithMessage("failed to validate req param"), err.Error())) + return + } + err := h.Controller.ApproveRecovery(c, auth.GetUserID(c.Request.Header), request) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.JSON(http.StatusOK, gin.H{}) +} + +func (h *EmergencyHandler) GetRecoveryInfo(c *gin.Context) { + sessionID, err := uuid.Parse(c.Param("id")) + if err != nil { + handler.Error(c, stacktrace.Propagate(ente.NewBadRequestWithMessage("failed to validate req param"), err.Error())) + return + } + encRecovery, keyAttr, err := h.Controller.GetRecoveryInfo(c, auth.GetUserID(c.Request.Header), sessionID) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.JSON(http.StatusOK, gin.H{ + "encryptedKey": encRecovery, + "userKeyAttr": keyAttr, + }) +} + +func (h *EmergencyHandler) InitChangePassword(c *gin.Context) { + var request ente.RecoverySrpSetupRequest + if err := c.ShouldBindJSON(&request); err != nil { + handler.Error(c, stacktrace.Propagate(ente.NewBadRequestWithMessage("failed to validate req param"), err.Error())) + return + } + resp, err := h.Controller.InitChangePassword(c, auth.GetUserID(c.Request.Header), request) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.JSON(http.StatusOK, resp) +} + +func (h *EmergencyHandler) ChangePassword(c *gin.Context) { + var request ente.RecoveryUpdateSRPAndKeysRequest + if err := c.ShouldBindJSON(&request); err != nil { + handler.Error(c, stacktrace.Propagate(ente.NewBadRequestWithMessage("failed to validate req param"), err.Error())) + return + } + resp, err := h.Controller.ChangePassword(c, auth.GetUserID(c.Request.Header), request) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.JSON(http.StatusOK, resp) +} diff --git a/server/pkg/api/offer.go b/server/pkg/api/offer.go index 74e319a96c..0ec9081f12 100644 --- a/server/pkg/api/offer.go +++ b/server/pkg/api/offer.go @@ -4,7 +4,6 @@ import ( "net/http" "github.com/ente-io/museum/pkg/controller/offer" - "github.com/ente-io/museum/pkg/utils/network" "github.com/gin-gonic/gin" ) @@ -16,6 +15,6 @@ type OfferHandler struct { // Deprecated for now func (h *OfferHandler) GetBlackFridayOffers(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ - "offers": h.Controller.GetBlackFridayOffers(network.GetClientCountry(c)), + "offers": []interface{}{}, }) } diff --git a/server/pkg/api/user.go b/server/pkg/api/user.go index f0ede26f2f..930ea6ec8c 100644 --- a/server/pkg/api/user.go +++ b/server/pkg/api/user.go @@ -4,6 +4,7 @@ import ( "database/sql" "errors" "fmt" + "github.com/ente-io/museum/pkg/controller/emergency" "github.com/gin-contrib/requestid" "github.com/sirupsen/logrus" "net/http" @@ -22,7 +23,8 @@ import ( // UserHandler exposes request handlers for all user related requests type UserHandler struct { - UserController *user.UserController + UserController *user.UserController + EmergencyController *emergency.Controller } // SendOTT generates and sends an OTT to the provided email address @@ -529,6 +531,17 @@ func (h *UserHandler) DeleteUser(c *gin.Context) { handler.Error(c, stacktrace.Propagate(err, "Could not bind request params")) return } + // todo: (neeraj) refactor this part, currently there's a circular dependency between user and emergency controllers + removeLegacyErr := h.EmergencyController.HandleAccountDeletion(c, auth.GetUserID(c.Request.Header), + logrus.WithFields(logrus.Fields{ + "user_id": auth.GetUserID(c.Request.Header), + "req_id": requestid.Get(c), + "req_ctx": "self_account_deletion", + })) + if removeLegacyErr != nil { + handler.Error(c, stacktrace.Propagate(removeLegacyErr, "")) + return + } response, err := h.UserController.SelfDeleteAccount(c, request) if err != nil { handler.Error(c, stacktrace.Propagate(err, "")) diff --git a/server/pkg/controller/emergency/account_owner.go b/server/pkg/controller/emergency/account_owner.go new file mode 100644 index 0000000000..27a747d1c5 --- /dev/null +++ b/server/pkg/controller/emergency/account_owner.go @@ -0,0 +1,129 @@ +package emergency + +import ( + "database/sql" + "errors" + "github.com/ente-io/museum/ente" + "github.com/ente-io/museum/pkg/utils/time" + "github.com/ente-io/stacktrace" + "github.com/gin-gonic/gin" +) + +func (c *Controller) AddContact(ctx *gin.Context, userID int64, request ente.AddContact) error { + emergencyContactID, err := c.UserRepo.GetUserIDWithEmail(request.Email) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return stacktrace.Propagate(ente.ErrNotFound, "invited member is not on ente") + } else { + return stacktrace.Propagate(err, "") + } + } + noticeInHrs := 24 * 30 + if request.RecoveryNoticeInDays != nil { + noticeInHrs = *request.RecoveryNoticeInDays * 24 + } + hasUpdated, err := c.Repo.AddEmergencyContact(ctx, userID, emergencyContactID, request.EncryptedKey, noticeInHrs) + if err != nil { + return stacktrace.Propagate(err, "") + } + if hasUpdated { + go c.sendContactNotification(ctx, userID, emergencyContactID, ente.UserInvitedContact) + } + return nil +} + +func (c *Controller) GetInfo(ctx *gin.Context, userID int64) (*ente.EmergencyDataResponse, error) { + contacts, err := c.Repo.GetActiveContactForUser(ctx, userID) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + userIDs := make([]int64, 0, len(contacts)) + for _, contact := range contacts { + userIDs = append(userIDs, contact.EmergencyContactID) + userIDs = append(userIDs, contact.UserID) + } + recoverRows, err := c.Repo.GetActiveRecoverySessions(ctx, userID) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + for _, session := range recoverRows { + userIDs = append(userIDs, session.UserID) + userIDs = append(userIDs, session.EmergencyContactID) + } + userIdToUserMap, err := c.UserRepo.GetActiveUsersForIds(userIDs) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + userEmergencyContacts := make([]*ente.EmergencyContactEntity, 0) + othersEmergencyContact := make([]*ente.EmergencyContactEntity, 0) + for _, contact := range contacts { + user, ok1 := userIdToUserMap[contact.UserID] + emergencyContactUser, ok2 := userIdToUserMap[contact.EmergencyContactID] + if !ok1 || !ok2 { + continue + } + entity := &ente.EmergencyContactEntity{ + User: ente.BasicUser{ + ID: user.ID, + Email: user.Email, + }, + EmergencyContact: ente.BasicUser{ + ID: emergencyContactUser.ID, + Email: emergencyContactUser.Email, + }, + State: contact.State, + RecoveryNoticeInDays: contact.NoticePeriodInHrs / 24, + } + if contact.UserID == userID { + userEmergencyContacts = append(userEmergencyContacts, entity) + } else { + othersEmergencyContact = append(othersEmergencyContact, entity) + } + } + recoverSessions := make([]*ente.RecoverySession, 0) + othersRecoverSessions := make([]*ente.RecoverySession, 0) + nowInMicroseconds := time.Microseconds() + for _, session := range recoverRows { + user, ok1 := userIdToUserMap[session.UserID] + emergencyContactUser, ok2 := userIdToUserMap[session.EmergencyContactID] + if !ok1 || !ok2 { + continue + } + waitTime := session.WaitTill - nowInMicroseconds + status := session.Status + if waitTime < 0 { + if status == ente.RecoveryStatusWaiting { + status = ente.RecoveryStatusReady + } + waitTime = 0 + } + + entity := &ente.RecoverySession{ + ID: session.ID, + User: ente.BasicUser{ + ID: user.ID, + Email: user.Email, + }, + EmergencyContact: ente.BasicUser{ + ID: emergencyContactUser.ID, + Email: emergencyContactUser.Email, + }, + Status: status, + WaitTill: waitTime, + CreatedAt: session.CreatedAt, + } + if session.UserID == userID { + recoverSessions = append(recoverSessions, entity) + } else { + othersRecoverSessions = append(othersRecoverSessions, entity) + } + } + + response := &ente.EmergencyDataResponse{ + Contacts: userEmergencyContacts, + OthersEmergencyContact: othersEmergencyContact, + RecoverySessions: recoverSessions, + OthersRecoverSessions: othersRecoverSessions, + } + return response, nil +} diff --git a/server/pkg/controller/emergency/controller.go b/server/pkg/controller/emergency/controller.go new file mode 100644 index 0000000000..975e8377c4 --- /dev/null +++ b/server/pkg/controller/emergency/controller.go @@ -0,0 +1,125 @@ +package emergency + +import ( + "fmt" + + "github.com/ente-io/museum/ente" + "github.com/ente-io/museum/pkg/controller/user" + "github.com/ente-io/museum/pkg/repo" + "github.com/ente-io/museum/pkg/repo/emergency" + "github.com/ente-io/stacktrace" + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +type Controller struct { + Repo *emergency.Repository + UserRepo *repo.UserRepository + UserCtrl *user.UserController +} + +func (c *Controller) UpdateContact(ctx *gin.Context, + userID int64, + req ente.UpdateContact) error { + if err := validateUpdateReq(userID, req); err != nil { + return stacktrace.Propagate(err, "") + } + if req.State == ente.ContactDenied || req.State == ente.ContactLeft || req.State == ente.UserRevokedContact { + activeSessions, sessionErr := c.Repo.GetActiveSessions(ctx, req.UserID, req.EmergencyContactID) + if sessionErr != nil { + return stacktrace.Propagate(sessionErr, "") + } + for _, session := range activeSessions { + if req.State == ente.UserRevokedContact { + rejErr := c.RejectRecovery(ctx, userID, ente.RecoveryIdentifier{ + ID: session.ID, + UserID: session.UserID, + EmergencyContactID: session.EmergencyContactID, + }) + if rejErr != nil { + return stacktrace.Propagate(rejErr, "failed to reject recovery") + } + } else { + stopErr := c.StopRecovery(ctx, userID, ente.RecoveryIdentifier{ + ID: session.ID, + UserID: session.UserID, + EmergencyContactID: session.EmergencyContactID, + }) + if stopErr != nil { + return stacktrace.Propagate(stopErr, "failed to stop recovery") + } + } + } + } + hasUpdate, err := c.Repo.UpdateState(ctx, req.UserID, req.EmergencyContactID, req.State) + if !hasUpdate { + log.WithField("userID", userID).WithField("req", req). + Warn("No update applied for emergency contact") + } else { + go c.sendContactNotification(ctx, req.UserID, req.EmergencyContactID, req.State) + } + if err != nil { + return stacktrace.Propagate(err, "") + } + return nil +} + +func (c *Controller) HandleAccountDeletion(ctx *gin.Context, userID int64, logger *log.Entry) error { + logger.Info("Clean up emergency contacts on account deletion") + contacts, err := c.Repo.GetActiveContactForUser(ctx, userID) + if err != nil { + return stacktrace.Propagate(err, "") + } + if len(contacts) == 0 { + return nil + } + for _, contact := range contacts { + if contact.UserID == userID { + logger.Info("Removing emergency contact from user side") + removeErr := c.UpdateContact(ctx, userID, ente.UpdateContact{ + UserID: userID, + EmergencyContactID: contact.EmergencyContactID, + State: ente.UserRevokedContact, + }) + if removeErr != nil { + return stacktrace.Propagate(removeErr, "") + } + } else { + logger.Info("Removing user from emergency contact side") + leaveErr := c.UpdateContact(ctx, userID, ente.UpdateContact{ + UserID: contact.UserID, + EmergencyContactID: userID, + State: ente.ContactLeft, + }) + if leaveErr != nil { + return stacktrace.Propagate(leaveErr, "") + } + } + } + return nil +} + +func validateUpdateReq(userID int64, req ente.UpdateContact) error { + if req.EmergencyContactID == req.UserID { + return stacktrace.Propagate(ente.NewBadRequestWithMessage("contact and user can not be same"), "") + } + if req.EmergencyContactID != userID && req.UserID != userID { + return stacktrace.Propagate(ente.ErrPermissionDenied, "user can only update his own state") + } + + isActorContact := userID == req.EmergencyContactID + if isActorContact { + if req.State == ente.ContactAccepted || + req.State == ente.ContactLeft || + req.State == ente.ContactDenied { + return nil + } + return stacktrace.Propagate(ente.NewBadRequestWithMessage(fmt.Sprintf("Can not update state to %s", req.State)), "") + } else { + if req.State == ente.UserInvitedContact || + req.State == ente.UserRevokedContact { + return nil + } + return stacktrace.Propagate(ente.NewBadRequestWithMessage(fmt.Sprintf("Can not update state to %s", req.State)), "") + } + } diff --git a/server/pkg/controller/emergency/email.go b/server/pkg/controller/emergency/email.go new file mode 100644 index 0000000000..ad374197ce --- /dev/null +++ b/server/pkg/controller/emergency/email.go @@ -0,0 +1,243 @@ +package emergency + +import ( + "context" + "fmt" + "github.com/ente-io/museum/ente" + emailUtil "github.com/ente-io/museum/pkg/utils/email" + "github.com/ente-io/stacktrace" + log "github.com/sirupsen/logrus" +) + +const ( + BaseTemplate string = "legacy/legacy_base.html" + InviteTemplate string = "legacy/legacy_invite.html" + AcceptedTemplate string = "legacy/legacy_invite_accepted.html" + RejectedInviteTemplate string = "legacy/legacy_invite_rejected.html" + InviteSentTemplate string = "legacy/legacy_invite_sent.html" + LeftTemplate string = "legacy/legacy_left.html" + RemovedTemplate string = "legacy/legacy_removed.html" + + RecoveryCancelledTemplate string = "legacy/recovery_cancelled.html" + RecoveryCompletedTrustedTemplate string = "legacy/recovery_completed_trusted.html" + RecoveryCompletedLegacyTemplate string = "legacy/recovery_completed_legacy.html" + + RecoveryReadyLegacyTemplate string = "legacy/recovery_ready_legacy.html" + RecoveryReadyTrustedTemplate string = "legacy/recovery_ready_trusted.html" + + RecoveryRejectedTemplate string = "legacy/recovery_rejected.html" + RecoveryReminderTemplate string = "legacy/recovery_reminder.html" + RecoveryStartedTemplate string = "legacy/recovery_started.html" +) + +type emailData struct { + title string + templateName string + emailTo string + templateData map[string]interface{} + inlineImages []map[string]interface{} +} + +func (c *Controller) createEmailData(legacyUser, trustedUser ente.User, newStatus ente.ContactState) ([]emailData, error) { + templateData := map[string]interface{}{ + "LegacyContact": legacyUser.Email, + "TrustedContact": trustedUser.Email, + } + + var emailContent []emailData + switch newStatus { + case ente.UserInvitedContact: + emailContent = append(emailContent, emailData{ + title: "You have been added as a trusted contact", + templateName: InviteTemplate, + emailTo: trustedUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + emailContent = append(emailContent, emailData{ + title: "Trusted contact invited", + templateName: InviteSentTemplate, + emailTo: legacyUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + case ente.UserRevokedContact: + emailContent = append(emailContent, emailData{ + title: "Legacy account access removed", + templateName: RemovedTemplate, + emailTo: trustedUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + case ente.ContactLeft: + emailContent = append(emailContent, emailData{ + title: "Trusted contact removed", + templateName: LeftTemplate, + emailTo: legacyUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + case ente.ContactDenied: + emailContent = append(emailContent, emailData{ + title: "Legacy invite rejected", + templateName: RejectedInviteTemplate, + emailTo: legacyUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + case ente.ContactAccepted: + emailContent = append(emailContent, emailData{ + title: "Trusted contact added", + templateName: AcceptedTemplate, + emailTo: legacyUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + default: + return nil, fmt.Errorf("unsupported status %s", newStatus) + } + return emailContent, nil +} + +func (c *Controller) sendContactNotification(ctx context.Context, legacyUserID int64, trustedUserID int64, newStatus ente.ContactState) error { + legacyUser, err := c.UserRepo.Get(legacyUserID) + if err != nil { + return stacktrace.Propagate(err, "") + } + trustedUser, err := c.UserRepo.Get(trustedUserID) + if err != nil { + return stacktrace.Propagate(err, "") + } + + emailDatas, err := c.createEmailData(legacyUser, trustedUser, newStatus) + if err != nil { + return stacktrace.Propagate(err, "") + } + + for _, data := range emailDatas { + content := data + err = emailUtil.SendTemplatedEmailV2([]string{content.emailTo}, "Ente", "team@ente.io", + content.title, BaseTemplate, content.templateName, content.templateData, content.inlineImages) + if err != nil { + log.WithError(err).WithFields(log.Fields{ + "state": newStatus, + "to": content.emailTo, + "template": content.templateName, + }).Error("failed to send email") + return stacktrace.Propagate(err, "") + } + } + + return nil +} + +func (c *Controller) createRecoveryEmailData(legacyUser, trustedUser ente.User, newStatus ente.RecoveryStatus) ([]emailData, error) { + templateData := map[string]interface{}{ + "LegacyContact": legacyUser.Email, + "TrustedContact": trustedUser.Email, + } + + var emailDatas []emailData + + switch newStatus { + case ente.RecoveryStatusInitiated: + emailDatas = append(emailDatas, emailData{ + title: "Ente account recovery initiated", + templateName: RecoveryStartedTemplate, + emailTo: legacyUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + case ente.RecoveryStatusRecovered: + emailDatas = append(emailDatas, emailData{ + title: "Ente account password reset", + templateName: RecoveryCompletedLegacyTemplate, + emailTo: legacyUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + emailDatas = append(emailDatas, emailData{ + title: "Ente account recovery successful", + templateName: RecoveryCompletedTrustedTemplate, + emailTo: trustedUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + + case ente.RecoveryStatusStopped: + emailDatas = append(emailDatas, emailData{ + title: "Ente account recovery cancelled", + templateName: RecoveryCancelledTemplate, + emailTo: legacyUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + case ente.RecoveryStatusRejected: + emailDatas = append(emailDatas, emailData{ + title: "Ente account recovery blocked", + templateName: RecoveryRejectedTemplate, + emailTo: trustedUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + case ente.RecoveryStatusWaiting: + emailDatas = append(emailDatas, emailData{ + title: "Ente account recovery due", + templateName: RecoveryReminderTemplate, + emailTo: legacyUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + case ente.RecoveryStatusReady: + emailDatas = append(emailDatas, emailData{ + title: "Ente account recoverable", + templateName: RecoveryReadyTrustedTemplate, + emailTo: trustedUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + emailDatas = append(emailDatas, emailData{ + title: "Ente account recoverable", + templateName: RecoveryReadyLegacyTemplate, + emailTo: legacyUser.Email, + templateData: templateData, + inlineImages: []map[string]interface{}{}, + }) + default: + return nil, fmt.Errorf("unsupported status %s", newStatus) + } + + return emailDatas, nil +} + +func (c *Controller) sendRecoveryNotification(ctx context.Context, legacyUserID int64, trustedUserID int64, newStatus ente.RecoveryStatus) error { + legacyUser, err := c.UserRepo.Get(legacyUserID) + if err != nil { + return stacktrace.Propagate(err, "") + } + trustedUser, err := c.UserRepo.Get(trustedUserID) + if err != nil { + return stacktrace.Propagate(err, "") + } + + emailDatas, err := c.createRecoveryEmailData(legacyUser, trustedUser, newStatus) + if err != nil { + return stacktrace.Propagate(err, "") + } + + for _, data := range emailDatas { + content := data + err = emailUtil.SendTemplatedEmailV2([]string{content.emailTo}, "Ente", "team@ente.io", + content.title, BaseTemplate, content.templateName, content.templateData, content.inlineImages) + if err != nil { + log.WithError(err).WithFields(log.Fields{ + "state": newStatus, + "to": content.emailTo, + "template": content.templateName, + }).Error("failed to send email") + return stacktrace.Propagate(err, "") + } + } + + return nil +} diff --git a/server/pkg/controller/emergency/recovery.go b/server/pkg/controller/emergency/recovery.go new file mode 100644 index 0000000000..4b726911d7 --- /dev/null +++ b/server/pkg/controller/emergency/recovery.go @@ -0,0 +1,90 @@ +package emergency + +import ( + "github.com/ente-io/museum/ente" + "github.com/ente-io/museum/pkg/repo/emergency" + "github.com/ente-io/stacktrace" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +func (c *Controller) GetRecoveryInfo(ctx *gin.Context, + userID int64, + sessionID uuid.UUID, +) (*string, *ente.KeyAttributes, error) { + contact, err := c.checkRecoveryAndGetContact(ctx, userID, sessionID) + if err != nil { + return nil, nil, err + } + recoveryTarget, err := c.UserRepo.Get(contact.UserID) + if err != nil { + return nil, nil, err + } + keyAttr, err := c.UserRepo.GetKeyAttributes(recoveryTarget.ID) + if err != nil { + return nil, nil, err + } + return contact.EncryptedKey, &keyAttr, nil +} + +func (c *Controller) InitChangePassword(ctx *gin.Context, userID int64, request ente.RecoverySrpSetupRequest) (*ente.SetupSRPResponse, error) { + sessionID := request.RecoveryID + contact, err := c.checkRecoveryAndGetContact(ctx, userID, sessionID) + if err != nil { + return nil, err + } + resp, err := c.UserCtrl.SetupSRP(ctx, contact.UserID, request.SetUpSRPReq) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + return resp, nil +} + +func (c *Controller) ChangePassword(ctx *gin.Context, userID int64, request ente.RecoveryUpdateSRPAndKeysRequest) (*ente.UpdateSRPSetupResponse, error) { + sessionID := request.RecoveryID + contact, err := c.checkRecoveryAndGetContact(ctx, userID, sessionID) + if err != nil { + return nil, err + } + resp, err := c.UserCtrl.UpdateSrpAndKeyAttributes(ctx, contact.UserID, request.UpdateSrp, false) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + + hasUpdate, err := c.Repo.UpdateRecoveryStatusForID(ctx, sessionID, ente.RecoveryStatusRecovered) + if err != nil { + return nil, stacktrace.Propagate(err, "failed to update recovery status") + } + if !hasUpdate { + log.WithField("userID", userID).WithField("req", request). + Warn("no row updated while rejecting recovery") + } else { + go c.sendRecoveryNotification(ctx, contact.UserID, contact.EmergencyContactID, ente.RecoveryStatusRecovered) + } + + return resp, nil +} + +func (c *Controller) checkRecoveryAndGetContact(ctx *gin.Context, + userID int64, + sessionID uuid.UUID) (*emergency.ContactRow, error) { + recoverRow, err := c.Repo.GetRecoverRowByID(ctx, sessionID) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + if recoverRow.EmergencyContactID != userID { + return nil, stacktrace.Propagate(ente.ErrPermissionDenied, "only the emergency contact can get recovery info") + } + if err = recoverRow.CanRecover(); err != nil { + return nil, stacktrace.Propagate(ente.NewBadRequestWithMessage(err.Error()), "") + } + contact, err := c.Repo.GetActiveEmergencyContact(ctx, recoverRow.UserID, recoverRow.EmergencyContactID) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + if contact.EncryptedKey == nil { + return nil, stacktrace.Propagate(ente.ErrNotFound, "no encrypted key found") + } + return contact, nil +} diff --git a/server/pkg/controller/emergency/recovery_contact.go b/server/pkg/controller/emergency/recovery_contact.go new file mode 100644 index 0000000000..c2d763d061 --- /dev/null +++ b/server/pkg/controller/emergency/recovery_contact.go @@ -0,0 +1,102 @@ +package emergency + +import ( + "github.com/ente-io/museum/ente" + "github.com/ente-io/stacktrace" + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +func (c *Controller) StartRecovery(ctx *gin.Context, + actorUserID int64, + req ente.ContactIdentifier) error { + if req.EmergencyContactID == req.UserID { + return stacktrace.Propagate(ente.NewBadRequestWithMessage("contact and user can not be same"), "") + } + if req.EmergencyContactID != actorUserID { + return stacktrace.Propagate(ente.ErrPermissionDenied, "only the emergency contact can start recovery") + } + + contact, err := c.Repo.GetActiveEmergencyContact(ctx, req.UserID, req.EmergencyContactID) + if err != nil { + return stacktrace.Propagate(err, "") + } + + hasUpdate, err := c.Repo.InsertIntoRecovery(ctx, req, *contact) + if !hasUpdate { + log.WithField("userID", actorUserID).WithField("req", req). + Warn("No need to send email") + } else { + go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusInitiated) + } + if err != nil { + return stacktrace.Propagate(err, "") + } + return nil +} + +func (c *Controller) RejectRecovery(ctx *gin.Context, + userID int64, + req ente.RecoveryIdentifier) error { + if req.EmergencyContactID == req.UserID { + return stacktrace.Propagate(ente.NewBadRequestWithMessage("contact and user can not be same"), "") + } + if req.UserID != userID { + return stacktrace.Propagate(ente.ErrPermissionDenied, "only account owner can reject recovery") + } + hasUpdate, err := c.Repo.UpdateRecoveryStatusForID(ctx, req.ID, ente.RecoveryStatusRejected) + if !hasUpdate { + log.WithField("userID", userID).WithField("req", req). + Warn("no row updated while rejecting recovery") + } else { + go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusRejected) + } + if err != nil { + return stacktrace.Propagate(err, "") + } + return nil +} + +func (c *Controller) ApproveRecovery(ctx *gin.Context, + userID int64, + req ente.RecoveryIdentifier) error { + if req.EmergencyContactID == req.UserID { + return stacktrace.Propagate(ente.NewBadRequestWithMessage("contact and user can not be same"), "") + } + if req.UserID != userID { + return stacktrace.Propagate(ente.ErrPermissionDenied, "only account owner can reject recovery") + } + hasUpdate, err := c.Repo.UpdateRecoveryStatusForID(ctx, req.ID, ente.RecoveryStatusReady) + if !hasUpdate { + log.WithField("userID", userID).WithField("req", req). + Warn("no row updated while rejecting recovery") + } else { + go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusReady) + } + if err != nil { + return stacktrace.Propagate(err, "") + } + return nil +} + +func (c *Controller) StopRecovery(ctx *gin.Context, + userID int64, + req ente.RecoveryIdentifier) error { + if req.EmergencyContactID == req.UserID { + return stacktrace.Propagate(ente.NewBadRequestWithMessage("contact and user can not be same"), "") + } + if req.EmergencyContactID != userID { + return stacktrace.Propagate(ente.ErrPermissionDenied, "only the emergency contact can stop recovery") + } + hasUpdate, err := c.Repo.UpdateRecoveryStatusForID(ctx, req.ID, ente.RecoveryStatusStopped) + if err != nil { + return stacktrace.Propagate(err, "") + } + if !hasUpdate { + log.WithField("userID", userID).WithField("req", req). + Warn("no row updated while stopping recovery") + } else { + go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusStopped) + } + return stacktrace.Propagate(err, "") +} diff --git a/server/pkg/controller/mailing_lists.go b/server/pkg/controller/mailing_lists.go index fbec444951..497d00a69e 100644 --- a/server/pkg/controller/mailing_lists.go +++ b/server/pkg/controller/mailing_lists.go @@ -140,11 +140,7 @@ func (c *MailingListsController) Unsubscribe(email string) error { // shouldSkipZoho() checks if the MailingListsController // should be skipped due to missing credentials. func (c *MailingListsController) shouldSkipZoho() bool { - if c.zohoCredentials.RefreshToken == "" { - // Skip - return true - } - return false + return c.zohoCredentials.RefreshToken == "" } // shouldSkipListmonk() checks if the Listmonk mailing list diff --git a/server/pkg/repo/datacleanup/repository.go b/server/pkg/repo/datacleanup/repository.go index fc1a1c08f6..9f6e884215 100644 --- a/server/pkg/repo/datacleanup/repository.go +++ b/server/pkg/repo/datacleanup/repository.go @@ -64,7 +64,7 @@ func (r *Repository) MoveToNextStage(ctx context.Context, userID int64, stage en } // ScheduleNextAttemptAfterNHours bumps the attempt count by one and schedule next attempt after n hr(s) -func (r *Repository) ScheduleNextAttemptAfterNHours(ctx context.Context, userID int64, n int8) error { +func (r *Repository) ScheduleNextAttemptAfterNHours(ctx context.Context, userID int64, n int32) error { _, err := r.DB.ExecContext(ctx, `UPDATE data_cleanup SET stage_attempt_count = stage_attempt_count +1, stage_schedule_time = $1 WHERE user_id = $2`, time.MicrosecondsAfterHours(n), userID) return stacktrace.Propagate(err, "failed to insert/update") diff --git a/server/pkg/repo/emergency/recovery.go b/server/pkg/repo/emergency/recovery.go new file mode 100644 index 0000000000..dc41c3d5ff --- /dev/null +++ b/server/pkg/repo/emergency/recovery.go @@ -0,0 +1,136 @@ +package emergency + +import ( + "context" + "database/sql" + "fmt" + + "github.com/ente-io/museum/ente" + "github.com/ente-io/museum/pkg/utils/time" + "github.com/ente-io/stacktrace" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/lib/pq" +) + +type RecoverRow struct { + ID uuid.UUID + UserID int64 + EmergencyContactID int64 + Status ente.RecoveryStatus + WaitTill int64 + NextReminderAt int64 + CreatedAt int64 +} + +func (r RecoverRow) CanRecover() error { + if r.Status != ente.RecoveryStatusReady && r.Status != ente.RecoveryStatusWaiting { + return fmt.Errorf("recovery status is not waiting or ready") + } + if r.WaitTill > time.Microseconds() && r.Status == ente.RecoveryStatusWaiting { + return fmt.Errorf("recovery wait time is not over") + } + return nil +} + +func (repo *Repository) InsertIntoRecovery(ctx *gin.Context, req ente.ContactIdentifier, row ContactRow) (bool, error) { + waitTime := time.MicrosecondsAfterHours(row.NoticePeriodInHrs) + nextReminder := time.MicrosecondsAfterHours(row.NoticePeriodInHrs - 24) + if row.NoticePeriodInHrs < 25 { + nextReminder = time.Microseconds() + } + result, err := repo.DB.ExecContext(ctx, `INSERT INTO emergency_recovery (id,user_id, emergency_contact_id, status, wait_till, next_reminder_at) VALUES ($1, $2, $3, $4, $5, $6) on conflict DO NOTHING`, + uuid.New(), req.UserID, req.EmergencyContactID, ente.RecoveryStatusWaiting, waitTime, nextReminder) + if err != nil { + return false, stacktrace.Propagate(err, "") + } + count, _ := result.RowsAffected() + return count > 0, nil +} + +func (repo *Repository) GetActiveRecoverySessions(ctx *gin.Context, userID int64) ([]*RecoverRow, error) { + rows, err := repo.DB.QueryContext(ctx, `SELECT id, user_id, emergency_contact_id, status, wait_till, next_reminder_at, created_at +FROM emergency_recovery WHERE (user_id=$1 OR emergency_contact_id=$1) AND status= ANY($2)`, userID, pq.Array([]ente.RecoveryStatus{ente.RecoveryStatusWaiting, ente.RecoveryStatusReady})) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + defer rows.Close() + var sessions []*RecoverRow + for rows.Next() { + var row RecoverRow + if err := rows.Scan(&row.ID, &row.UserID, &row.EmergencyContactID, &row.Status, &row.WaitTill, &row.NextReminderAt, &row.CreatedAt); err != nil { + return nil, stacktrace.Propagate(err, "") + } + sessions = append(sessions, &row) + } + return sessions, nil +} + +func (repo *Repository) GetActiveSessions(ctx *gin.Context, userID int64, emergencyContactID int64) ([]*RecoverRow, error) { + rows, err := repo.DB.QueryContext(ctx, `SELECT id, user_id, emergency_contact_id, status, wait_till, next_reminder_at, created_at +FROM emergency_recovery WHERE user_id=$1 and emergency_contact_id=$2 AND status= ANY($3)`, userID, emergencyContactID, pq.Array([]ente.RecoveryStatus{ente.RecoveryStatusWaiting, ente.RecoveryStatusReady})) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + defer rows.Close() + var sessions []*RecoverRow + for rows.Next() { + var row RecoverRow + if err := rows.Scan(&row.ID, &row.UserID, &row.EmergencyContactID, &row.Status, &row.WaitTill, &row.NextReminderAt, &row.CreatedAt); err != nil { + return nil, stacktrace.Propagate(err, "") + } + sessions = append(sessions, &row) + } + return sessions, nil +} + +func (repo *Repository) UpdateRecoveryStatusForID(ctx context.Context, sessionID uuid.UUID, status ente.RecoveryStatus) (bool, error) { + validPrevStatus := validPreviousStatus(status) + var result sql.Result + var err error + if status == ente.RecoveryStatusReady { + result, err = repo.DB.ExecContext(ctx, `UPDATE emergency_recovery SET status=$1, wait_till=$2 WHERE id=$3 and status = ANY($4)`, status, time.Microseconds(), sessionID, pq.Array(validPrevStatus)) + } else { + result, err = repo.DB.ExecContext(ctx, `UPDATE emergency_recovery SET status=$1 WHERE id=$2 and status = ANY($3)`, status, sessionID, pq.Array(validPrevStatus)) + } + if err != nil { + return false, stacktrace.Propagate(err, "") + } + rows, _ := result.RowsAffected() + return rows > 0, nil +} +func (repo *Repository) GetRecoverRowByID(ctx context.Context, sessionID uuid.UUID) (*RecoverRow, error) { + var row RecoverRow + err := repo.DB.QueryRowContext(ctx, `SELECT id, user_id, emergency_contact_id, status, wait_till, next_reminder_at, created_at + FROM emergency_recovery WHERE id=$1`, sessionID).Scan(&row.ID, &row.UserID, &row.EmergencyContactID, &row.Status, &row.WaitTill, &row.NextReminderAt, &row.CreatedAt) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + return &row, nil +} + +func (repo *Repository) UpdateRecoveryStatus(ctx context.Context, userID, emergencyContactID int64, status ente.RecoveryStatus) error { + validPrevStatus := validPreviousStatus(status) + _, err := repo.DB.ExecContext(ctx, `UPDATE emergency_recovery SET status=$1 WHERE user_id =$2 and emergency_contact_id =$3 and status = ANY($4)`, status, userID, emergencyContactID, pq.Array(validPrevStatus)) + if err != nil { + return stacktrace.Propagate(err, "") + } + return nil +} + +func validPreviousStatus(newStatus ente.RecoveryStatus) []ente.RecoveryStatus { + result := make([]ente.RecoveryStatus, 0) + switch newStatus { + case ente.RecoveryStatusWaiting: + break + case ente.RecoveryStatusReady: + result = append(result, ente.RecoveryStatusWaiting, ente.RecoveryStatusReady) + case ente.RecoveryStatusStopped: + result = append(result, ente.RecoveryStatusWaiting, ente.RecoveryStatusReady) + case ente.RecoveryStatusRejected: + result = append(result, ente.RecoveryStatusWaiting, ente.RecoveryStatusReady) + case ente.RecoveryStatusRecovered: + result = append(result, ente.RecoveryStatusWaiting, ente.RecoveryStatusReady) + } + return result +} diff --git a/server/pkg/repo/emergency/repository.go b/server/pkg/repo/emergency/repository.go new file mode 100644 index 0000000000..5bf4eba9c3 --- /dev/null +++ b/server/pkg/repo/emergency/repository.go @@ -0,0 +1,124 @@ +package emergency + +import ( + "context" + "database/sql" + "github.com/ente-io/museum/ente" + "github.com/ente-io/stacktrace" + "github.com/lib/pq" +) + +// Repository defines the methods for managing emergency contacts and recovery process. +type Repository struct { + DB *sql.DB +} + +type ContactRow struct { + UserID int64 + EmergencyContactID int64 + State ente.ContactState + NoticePeriodInHrs int32 + EncryptedKey *string +} + +func (r *Repository) AddEmergencyContact(ctx context.Context, userID int64, emergencyContactID int64, encKey string, noticeInHrs int) (bool, error) { + if userID == emergencyContactID { + return false, ente.NewBadRequestWithMessage("user cannot add themself as emergency contact") + } + result, err := r.DB.ExecContext(ctx, ` +INSERT INTO emergency_contact(user_id, emergency_contact_id, state, encrypted_key, notice_period_in_hrs) VALUES ($1,$2,$3,$4,$5) +ON CONFLICT (user_id, emergency_contact_id) DO UPDATE SET state=$3, encrypted_key=$4, notice_period_in_hrs=$5 +WHERE emergency_contact.user_id=$1 AND emergency_contact.emergency_contact_id=$2 AND emergency_contact.state = ANY($6)`, + userID, // $1 user_id + emergencyContactID, + ente.UserInvitedContact, + encKey, + noticeInHrs, + pq.Array([]ente.ContactState{ente.ContactDenied, ente.ContactLeft, ente.UserRevokedContact})) + if err != nil { + return false, stacktrace.Propagate(err, "failed to insert/update") + } + rowAffected, err := result.RowsAffected() + if err != nil { + return false, stacktrace.Propagate(err, "failed to insert/update") + } + return rowAffected > 0, nil +} + +// GetActiveContactForUser returns all the contacts for a user that are in state accepted or invited +// and also returns all the contacts that have added the user as emergency contact +func (r *Repository) GetActiveContactForUser(ctx context.Context, userID int64) ([]*ContactRow, error) { + rows, err := r.DB.QueryContext(ctx, + `SELECT user_id, emergency_contact_id, state, notice_period_in_hrs, encrypted_key + FROM emergency_contact WHERE (user_id=$1 or emergency_contact_id=$1) + and state = ANY($2)`, userID, pq.Array([]ente.ContactState{ente.ContactAccepted, ente.UserInvitedContact})) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + defer rows.Close() + var contacts []*ContactRow + for rows.Next() { + var c ContactRow + err := rows.Scan(&c.UserID, &c.EmergencyContactID, &c.State, &c.NoticePeriodInHrs, &c.EncryptedKey) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + contacts = append(contacts, &c) + } + return contacts, nil +} + +// GetActiveEmergencyContact for a given userID and emergencyContactID in active state +func (r *Repository) GetActiveEmergencyContact(ctx context.Context, userID int64, emergencyContactID int64) (*ContactRow, error) { + row := r.DB.QueryRowContext(ctx, `SELECT user_id, emergency_contact_id, state, notice_period_in_hrs, encrypted_key + FROM emergency_contact WHERE user_id=$1 and emergency_contact_id=$2 and state = $3`, + userID, emergencyContactID, ente.ContactAccepted) + var c ContactRow + err := row.Scan(&c.UserID, &c.EmergencyContactID, &c.State, &c.NoticePeriodInHrs, &c.EncryptedKey) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + return &c, nil +} + +// UpdateState will return true if the state was updated, false if the state was not updated +func (r *Repository) UpdateState(ctx context.Context, + userID int64, + emergencyContactID int64, + newState ente.ContactState) (bool, error) { + allowedPreviousStates := getValidPreviousState(newState) + var res sql.Result + var err error + if newState == ente.ContactAccepted || newState == ente.UserInvitedContact { + res, err = r.DB.ExecContext(ctx, `UPDATE emergency_contact SET state=$1 WHERE user_id=$2 and emergency_contact_id=$3 and state = ANY($4)`, + newState, userID, emergencyContactID, pq.Array(allowedPreviousStates)) + } else { + res, err = r.DB.ExecContext(ctx, `UPDATE emergency_contact SET state=$1, encrypted_key = NULL WHERE user_id=$2 and emergency_contact_id=$3 and state = ANY($4)`, + newState, userID, emergencyContactID, pq.Array(allowedPreviousStates)) + } + if err != nil { + return false, stacktrace.Propagate(err, "") + } + count, err2 := res.RowsAffected() + if count > 1 { + panic("invalid state, only one row should be updated") + } + return count > 0, stacktrace.Propagate(err2, "") +} + +func getValidPreviousState(cs ente.ContactState) []ente.ContactState { + switch cs { + case ente.UserInvitedContact: + return []ente.ContactState{ente.UserRevokedContact, ente.ContactLeft, ente.ContactDenied} + case ente.ContactAccepted: + return []ente.ContactState{ente.UserInvitedContact, ente.ContactAccepted} + case ente.ContactLeft: + return []ente.ContactState{ente.UserInvitedContact, ente.ContactAccepted} + case ente.ContactDenied: + return []ente.ContactState{ente.UserInvitedContact} + case ente.UserRevokedContact: + return []ente.ContactState{ente.UserInvitedContact, ente.ContactAccepted} + + } + panic("invalid state") +} diff --git a/server/pkg/repo/user.go b/server/pkg/repo/user.go index f35a47e1f9..b482a16a87 100644 --- a/server/pkg/repo/user.go +++ b/server/pkg/repo/user.go @@ -396,3 +396,29 @@ func (repo *UserRepository) GetEmailsFromHashes(hashes []string) ([]string, erro } return emails, nil } + +// GetActiveUsersForIds returns a map of users by their IDs, similar to GetUserByID +func (repo *UserRepository) GetActiveUsersForIds(id []int64) (map[int64]*ente.User, error) { + result := make(map[int64]*ente.User) + rows, err := repo.DB.Query(`SELECT user_id, encrypted_email, email_decryption_nonce, email_hash, creation_time FROM users WHERE encrypted_email IS NOT NULL and user_id = ANY($1)`, pq.Array(id)) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + defer rows.Close() + for rows.Next() { + var user ente.User + var encryptedEmail, nonce []byte + err := rows.Scan(&user.ID, &encryptedEmail, &nonce, &user.Hash, &user.CreationTime) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + email, err := crypto.Decrypt(encryptedEmail, repo.SecretEncryptionKey, nonce) + if err != nil { + return nil, stacktrace.Propagate(err, "") + } + user.Email = email + result[user.ID] = &user + } + return result, nil + +} diff --git a/server/pkg/utils/email/email.go b/server/pkg/utils/email/email.go index 9586f5451e..ca90fd48b4 100644 --- a/server/pkg/utils/email/email.go +++ b/server/pkg/utils/email/email.go @@ -12,6 +12,7 @@ import ( "html/template" "net/http" "net/smtp" + "path" "strings" "github.com/ente-io/museum/ente" @@ -167,6 +168,15 @@ func SendTemplatedEmail(to []string, fromName string, fromEmail string, subject return Send(to, fromName, fromEmail, subject, body, inlineImages) } +func SendTemplatedEmailV2(to []string, fromName string, fromEmail string, subject string, baseTemplate, templateName string, templateData map[string]interface{}, inlineImages []map[string]interface{}) error { + body, err := getMailBodyWithBase(baseTemplate, templateName, templateData) + if err != nil { + return stacktrace.Propagate(err, "") + } + + return Send(to, fromName, fromEmail, subject, body, inlineImages) +} + func GetMaskedEmail(email string) string { at := strings.LastIndex(email, "@") if at >= 0 { @@ -192,3 +202,30 @@ func getMailBody(templateName string, templateData map[string]interface{}) (stri } return htmlbody.String(), nil } + +// getMailBody generates the mail HTML body from the provided template and data, supporting inheritance +func getMailBodyWithBase(baseTemplateName, templateName string, templateData map[string]interface{}) (string, error) { + htmlBody := new(bytes.Buffer) + + // Define paths for the base template and the specific template + baseTemplate := "mail-templates/" + baseTemplateName + specificTemplate := "mail-templates/" + templateName + + parts := strings.Split(baseTemplate, "/") + lastPart := parts[len(parts)-1] + baseTemplateID := strings.TrimSuffix(lastPart, path.Ext(lastPart)) + + // Parse the base and specific templates together + t, err := template.ParseFiles(baseTemplate, specificTemplate) + if err != nil { + return "", stacktrace.Propagate(err, "failed to parse templates") + } + + // Execute the base template with the provided data + err = t.ExecuteTemplate(htmlBody, baseTemplateID, templateData) + if err != nil { + return "", stacktrace.Propagate(err, "failed to execute template") + } + + return htmlBody.String(), nil +} diff --git a/server/pkg/utils/time/time.go b/server/pkg/utils/time/time.go index b4fda7f68b..69749a96a8 100644 --- a/server/pkg/utils/time/time.go +++ b/server/pkg/utils/time/time.go @@ -29,7 +29,7 @@ func Nanoseconds() int64 { } // MicrosecondsAfterHours returns the time in micro seconds after noOfHours -func MicrosecondsAfterHours(noOfHours int8) int64 { +func MicrosecondsAfterHours(noOfHours int32) int64 { return Microseconds() + int64(noOfHours)*MicroSecondsInOneHour } diff --git a/web/apps/cast/src/pages/index.tsx b/web/apps/cast/src/pages/index.tsx index 74cb384a16..9e97a227c6 100644 --- a/web/apps/cast/src/pages/index.tsx +++ b/web/apps/cast/src/pages/index.tsx @@ -1,6 +1,6 @@ import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator"; import log from "@/base/log"; -import { styled } from "@mui/material"; +import { styled, Typography } from "@mui/material"; import { PairingCode } from "components/PairingCode"; import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; @@ -64,9 +64,13 @@ export default function Index() { return ( -

+ Enter this code on Ente Photos to pair this screen -

+ {pairingCode ? : }

Visit{" "} @@ -87,10 +91,6 @@ const Container = styled("div")` align-items: center; text-align: center; - h1 { - font-weight: normal; - } - p { font-size: 1.2rem; } diff --git a/web/apps/photos/src/pages/duplicates.tsx b/web/apps/photos/src/pages/duplicates.tsx new file mode 100644 index 0000000000..2a604fc4a3 --- /dev/null +++ b/web/apps/photos/src/pages/duplicates.tsx @@ -0,0 +1 @@ +export { default } from "@/new/photos/pages/duplicates"; diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 56125b68e1..55bc583c6e 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -77,7 +77,7 @@ import { clearKeys, getKey, } from "@ente/shared/storage/sessionStorage"; -import ArrowBack from "@mui/icons-material/ArrowBack"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined"; import MenuIcon from "@mui/icons-material/Menu"; import type { ButtonProps, IconButtonProps } from "@mui/material"; @@ -1181,7 +1181,7 @@ const HiddenSectionNavbarContents: React.FC< }} > - + {t("section_hidden")} diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 8573e1cdd1..ba444dcba7 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -144,11 +144,13 @@ export default function LandingPage() { - {showLogin ? ( - - ) : ( - - )} + + {showLogin ? ( + + ) : ( + + )} + diff --git a/web/apps/photos/src/services/export/index.ts b/web/apps/photos/src/services/export/index.ts index e61f186421..76b0c287c7 100644 --- a/web/apps/photos/src/services/export/index.ts +++ b/web/apps/photos/src/services/export/index.ts @@ -380,7 +380,7 @@ class ExportService { ); log.info( - `files:${files.length} unexported files: ${filesToExport.length}, deleted exported files: ${removedFileUIDs.length}, renamed collections: ${renamedCollections.length}, deleted collections: ${deletedExportedCollections.length}`, + `[export] files: ${files.length}, disk files: ${diskFileRecordIDs?.size ?? ""}, unexported files: ${filesToExport.length}, deleted exported files: ${removedFileUIDs.length}, renamed collections: ${renamedCollections.length}, deleted collections: ${deletedExportedCollections.length}`, ); let success = 0; let failed = 0; @@ -1256,7 +1256,7 @@ const readOnDiskFileExportRecordIDs = async ( // // - `exportDir` traces its origin to `electron.selectDirectory()`, which // returns POSIX paths. Down below we use it as the base directory when - // construction paths for the items to export. + // constructing paths for the items to export. // // - `findFiles` is also guaranteed to return POSIX paths. // diff --git a/web/packages/accounts/components/Login.tsx b/web/packages/accounts/components/Login.tsx index 8dd8fad6e3..82784844bd 100644 --- a/web/packages/accounts/components/Login.tsx +++ b/web/packages/accounts/components/Login.tsx @@ -1,4 +1,5 @@ import { FormPaperFooter, FormPaperTitle } from "@/base/components/FormPaper"; +import { isMuseumHTTPError } from "@/base/http"; import log from "@/base/log"; import LinkButton from "@ente/shared/components/LinkButton"; import SingleInputForm, { @@ -10,7 +11,7 @@ import { t } from "i18next"; import { useRouter } from "next/router"; import { PAGES } from "../constants/pages"; import { getSRPAttributes } from "../services/srp-remote"; -import { sendOtt } from "../services/user"; +import { sendOTT } from "../services/user"; interface LoginProps { signUp: () => void; @@ -26,26 +27,30 @@ export const Login: React.FC = ({ signUp, host }) => { setFieldError, ) => { try { - await setLSUser({ email }); const srpAttributes = await getSRPAttributes(email); log.debug(() => ["srpAttributes", JSON.stringify(srpAttributes)]); if (!srpAttributes || srpAttributes.isEmailMFAEnabled) { - await sendOtt(email); + try { + await sendOTT(email, "login"); + } catch (e) { + if ( + await isMuseumHTTPError(e, 404, "USER_NOT_REGISTERED") + ) { + setFieldError(t("email_not_registered")); + return; + } + throw e; + } + await setLSUser({ email }); void router.push(PAGES.VERIFY); } else { + await setLSUser({ email }); setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes); void router.push(PAGES.CREDENTIALS); } } catch (e) { - if (e instanceof Error) { - setFieldError( - `${t("generic_error_retry")} (reason:${e.message})`, - ); - } else { - setFieldError( - `${t("generic_error_retry")} (reason:${JSON.stringify(e)})`, - ); - } + log.error("Login failed", e); + setFieldError(t("generic_error")); } }; diff --git a/web/packages/accounts/components/SignUp.tsx b/web/packages/accounts/components/SignUp.tsx index ea91b5fdc6..97bdd98dc2 100644 --- a/web/packages/accounts/components/SignUp.tsx +++ b/web/packages/accounts/components/SignUp.tsx @@ -1,5 +1,6 @@ import { FormPaperFooter, FormPaperTitle } from "@/base/components/FormPaper"; import { LoadingButton } from "@/base/components/mui/LoadingButton"; +import { isMuseumHTTPError } from "@/base/http"; import log from "@/base/log"; import { LS_KEYS, setLSUser } from "@ente/shared//storage/localStorage"; import { VerticallyCentered } from "@ente/shared/components/Container"; @@ -37,7 +38,7 @@ import { Trans } from "react-i18next"; import * as Yup from "yup"; import { PAGES } from "../constants/pages"; import { generateKeyAndSRPAttributes } from "../services/srp"; -import { sendOtt } from "../services/user"; +import { sendOTT } from "../services/user"; import { isWeakPassword } from "../utils/password"; import { PasswordStrengthHint } from "./PasswordStrength"; @@ -81,15 +82,18 @@ export const SignUp: React.FC = ({ router, login, host }) => { } setLoading(true); try { - await setLSUser({ email }); setLocalReferralSource(referral); - await sendOtt(email); + await sendOTT(email, "signup"); + await setLSUser({ email }); } catch (e) { - const message = e instanceof Error ? e.message : ""; - setFieldError( - "confirm", - `${t("generic_error_retry")} ${message}`, - ); + log.error("Signup failed", e); + if ( + await isMuseumHTTPError(e, 409, "USER_ALREADY_REGISTERED") + ) { + setFieldError("email", t("email_already_registered")); + } else { + setFieldError("email", t("generic_error")); + } throw e; } try { diff --git a/web/packages/accounts/pages/change-email.tsx b/web/packages/accounts/pages/change-email.tsx index db07369fbd..c1262a622d 100644 --- a/web/packages/accounts/pages/change-email.tsx +++ b/web/packages/accounts/pages/change-email.tsx @@ -4,6 +4,7 @@ import { FormPaperTitle, } from "@/base/components/FormPaper"; import { LoadingButton } from "@/base/components/mui/LoadingButton"; +import { isHTTPErrorWithStatus } from "@/base/http"; import log from "@/base/log"; import { VerticallyCentered } from "@ente/shared/components/Container"; import LinkButton from "@ente/shared/components/LinkButton"; @@ -16,7 +17,7 @@ import { useEffect, useState } from "react"; import { Trans } from "react-i18next"; import * as Yup from "yup"; import { appHomeRoute } from "../services/redirect"; -import { changeEmail, sendOTTForEmailChange } from "../services/user"; +import { changeEmail, sendOTT } from "../services/user"; import type { PageProps } from "../types/page"; const Page: React.FC = () => { @@ -60,7 +61,7 @@ const ChangeEmailForm: React.FC = () => { ) => { try { setLoading(true); - await sendOTTForEmailChange(email); + await sendOTT(email, "change"); setEmail(email); setShowOttInputVisibility(true); setShowMessage(true); @@ -71,7 +72,12 @@ const ChangeEmailForm: React.FC = () => { // }, 250); } catch (e) { log.error(e); - setFieldError("email", t("email_already_taken")); + setFieldError( + "email", + isHTTPErrorWithStatus(e, 403) + ? t("email_already_taken") + : t("generic_error"), + ); } setLoading(false); }; diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index aff92ecee5..cb1a368fa2 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -1,5 +1,5 @@ import { PAGES } from "@/accounts/constants/pages"; -import { sendOtt } from "@/accounts/services/user"; +import { sendOTT } from "@/accounts/services/user"; import { FormPaper, FormPaperFooter, @@ -48,7 +48,7 @@ const Page: React.FC = ({ appContext }) => { return; } if (!user?.encryptedToken && !user?.token) { - void sendOtt(user.email); + void sendOTT(user.email, undefined); stashRedirect(PAGES.RECOVER); void router.push(PAGES.VERIFY); return; diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index dfbeb387d4..c9bcb1e283 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -42,7 +42,7 @@ import { configureSRP } from "../services/srp"; import type { SRPAttributes, SRPSetupAttributes } from "../services/srp-remote"; import { getSRPAttributes } from "../services/srp-remote"; import type { UserVerificationResponse } from "../services/user"; -import { putAttributes, sendOtt, verifyOtt } from "../services/user"; +import { putAttributes, sendOTT, verifyOtt } from "../services/user"; import type { PageProps } from "../types/page"; const Page: React.FC = ({ appContext }) => { @@ -170,7 +170,7 @@ const Page: React.FC = ({ appContext }) => { const resendEmail = async () => { setResend(1); - await sendOtt(email); + await sendOTT(email, undefined); setResend(2); setTimeout(() => setResend(0), 3000); }; diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index 3ed5e7c257..a6f4c2672f 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -1,6 +1,9 @@ -import { appName } from "@/base/app"; import type { B64EncryptionResult } from "@/base/crypto/libsodium"; -import { authenticatedRequestHeaders, ensureOk } from "@/base/http"; +import { + authenticatedRequestHeaders, + ensureOk, + publicRequestHeaders, +} from "@/base/http"; import { apiURL } from "@/base/origins"; import HTTPService from "@ente/shared/network/HTTPService"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; @@ -54,12 +57,31 @@ export interface RecoveryKey { recoveryKeyDecryptionNonce: string; } -export const sendOtt = async (email: string) => { - return HTTPService.post(await apiURL("/users/ott"), { - email, - client: appName == "auth" ? "totp" : "web", - }); -}; +/** + * Ask remote to send a OTP / OTT to the given email to verify that the user has + * access to it. Subsequent the app will pass this OTT back via the + * {@link verifyOTT} method. + * + * @param email The email to verify. + * + * @param purpose In which context is the email being verified. Remote applies + * additional business rules depending on this. For example, passing the purpose + * "login" ensures that the OTT is only sent to an already registered email. + * + * In cases where the purpose is ambiguous (e.g. we're not sure if it is an + * existing login or a new signup), the purpose can be set to `undefined`. + */ +export const sendOTT = async ( + email: string, + purpose: "change" | "signup" | "login" | undefined, +) => + ensureOk( + await fetch(await apiURL("/users/ott"), { + method: "POST", + headers: publicRequestHeaders(), + body: JSON.stringify({ email, purpose }), + }), + ); export const verifyOtt = async ( email: string, @@ -171,14 +193,6 @@ export const changeEmail = async (email: string, ott: string) => { ); }; -export const sendOTTForEmailChange = async (email: string) => { - await HTTPService.post(await apiURL("/users/ott"), { - email, - client: "web", - purpose: "change", - }); -}; - export const setupTwoFactor = async () => { const resp = await HTTPService.post( await apiURL("/users/two-factor/setup"), diff --git a/web/packages/base/http.ts b/web/packages/base/http.ts index f1f11d56fb..0b9850ce4e 100644 --- a/web/packages/base/http.ts +++ b/web/packages/base/http.ts @@ -1,6 +1,8 @@ import { retryAsyncOperation } from "@/utils/promise"; +import { z } from "zod"; import { clientPackageName } from "./app"; import { ensureAuthToken } from "./local-user"; +import log from "./log"; /** * Return headers that should be passed alongwith (almost) all authenticated @@ -101,6 +103,12 @@ export const ensureOk = (res: Response) => { if (!res.ok) throw new HTTPError(res); }; +/** + * Return true if this is a HTTP error with the given {@link httpStatus}. + */ +export const isHTTPErrorWithStatus = (e: unknown, httpStatus: number) => + e instanceof HTTPError && e.res.status == httpStatus; + /** * Return true if this is a HTTP "client" error. * @@ -120,6 +128,36 @@ export const isHTTP4xxError = (e: unknown) => export const isHTTP401Error = (e: unknown) => e instanceof HTTPError && e.res.status == 401; +/** + * Return `true` if this is an error because of a HTTP failure response returned + * by museum with the given "code" and HTTP status. + * + * For some known set of errors, museum returns a payload of the form + * + * {"code":"USER_NOT_REGISTERED","message":"User is not registered"} + * + * where the code can be used to match a specific reason for the HTTP request + * failing. This function can be used as a predicate to check both the HTTP + * status code and the "code" within the payload. + */ +export const isMuseumHTTPError = async ( + e: unknown, + httpStatus: number, + code: string, +) => { + if (e instanceof HTTPError && e.res.status == httpStatus) { + try { + const payload = z + .object({ code: z.string() }) + .parse(await e.res.json()); + return payload.code == code; + } catch (e) { + log.warn("Ignoring error when parsing error payload", e); + return false; + } + } + return false; +}; /** * A helper function to adapt {@link retryAsyncOperation} for HTTP fetches. * diff --git a/web/packages/base/locales/ar-SA/translation.json b/web/packages/base/locales/ar-SA/translation.json index 13d1104397..8715a9ed72 100644 --- a/web/packages/base/locales/ar-SA/translation.json +++ b/web/packages/base/locales/ar-SA/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "أدخل عنوان البريد الإلكتروني", "EMAIL_ERROR": "أدخل بريد إلكتروني صالح", "required": "مطلوب", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "تم إرسال رمز التحقق إلى {{email}}", "CHECK_INBOX": "الرجاء التحقق من صندوق الوارد (والرسائل غير المرغوب فيها) لإكمال التحقق", "ENTER_OTT": "رمز التحقق", diff --git a/web/packages/base/locales/be-BY/translation.json b/web/packages/base/locales/be-BY/translation.json index d6a9df3e25..1fd42d4419 100644 --- a/web/packages/base/locales/be-BY/translation.json +++ b/web/packages/base/locales/be-BY/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Увядзіце адрас электроннай пошты", "EMAIL_ERROR": "Увядзіце сапраўдны адрас электроннай пошты", "required": "патрабуецца", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "Праверце свае ўваходныя лісты (і спам) для завяршэння праверкі", "ENTER_OTT": "Код пацвярджэння", diff --git a/web/packages/base/locales/bg-BG/translation.json b/web/packages/base/locales/bg-BG/translation.json index 93dc3f0fe5..1445254cb8 100644 --- a/web/packages/base/locales/bg-BG/translation.json +++ b/web/packages/base/locales/bg-BG/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/ca-ES/translation.json b/web/packages/base/locales/ca-ES/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/ca-ES/translation.json +++ b/web/packages/base/locales/ca-ES/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/da-DK/translation.json b/web/packages/base/locales/da-DK/translation.json index 1f61add7e0..44886649b9 100644 --- a/web/packages/base/locales/da-DK/translation.json +++ b/web/packages/base/locales/da-DK/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/de-DE/translation.json b/web/packages/base/locales/de-DE/translation.json index 09fd9e5214..fe5b318faa 100644 --- a/web/packages/base/locales/de-DE/translation.json +++ b/web/packages/base/locales/de-DE/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "E-Mail-Adresse eingeben", "EMAIL_ERROR": "Geben Sie eine gültige E-Mail-Adresse ein", "required": "Erforderlich", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Bestätigungscode an {{email}} gesendet", "CHECK_INBOX": "Bitte überprüfe deinen E-Mail-Posteingang (und Spam), um die Verifizierung abzuschließen", "ENTER_OTT": "Bestätigungscode", diff --git a/web/packages/base/locales/el-GR/translation.json b/web/packages/base/locales/el-GR/translation.json index e3762cd159..fad28265c7 100644 --- a/web/packages/base/locales/el-GR/translation.json +++ b/web/packages/base/locales/el-GR/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Εισάγετε διεύθυνση ηλ. ταχυδρομείου", "EMAIL_ERROR": "Εισάγετε μία έγκυρη διεύθυνση ηλ. ταχυδρομείου", "required": "Υποχρεωτικό", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Ο κωδικός επαλήθευσης στάλθηκε στο {{email}}", "CHECK_INBOX": "Παρακαλώ ελέγξτε τα εισερχόμενά σας (και τα ανεπιθύμητα) για να ολοκληρώσετε την επαλήθευση", "ENTER_OTT": "Κωδικός επαλήθευσης", diff --git a/web/packages/base/locales/en-US/translation.json b/web/packages/base/locales/en-US/translation.json index 0608e4a78b..256aaab9bd 100644 --- a/web/packages/base/locales/en-US/translation.json +++ b/web/packages/base/locales/en-US/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Enter email address", "EMAIL_ERROR": "Enter a valid email", "required": "required", + "email_not_registered": "Email not registered", + "email_already_registered": "Email already registered", "EMAIL_SENT": "Verification code sent to {{email}}", "CHECK_INBOX": "Please check your inbox (and spam) to complete verification", "ENTER_OTT": "Verification code", diff --git a/web/packages/base/locales/es-ES/translation.json b/web/packages/base/locales/es-ES/translation.json index ac43c16e8f..12dc00c24e 100644 --- a/web/packages/base/locales/es-ES/translation.json +++ b/web/packages/base/locales/es-ES/translation.json @@ -1,5 +1,5 @@ { - "intro_slide_1_title": "

Copias de seguridad privadas
para su recuerdos
", + "intro_slide_1_title": "
Copias de seguridad privadas
para sus recuerdos
", "intro_slide_1": "Encriptado de extremo a extremo por defecto", "intro_slide_2_title": "
Almacenado de forma segura
en un refugio de llenos
", "intro_slide_2": "Diseñado para superar", @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Introducir email", "EMAIL_ERROR": "Introduce un email válido", "required": "Requerido", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Código de verificación enviado al {{email}}", "CHECK_INBOX": "Revisa tu bandeja de entrada (y spam) para completar la verificación", "ENTER_OTT": "Código de verificación", @@ -319,7 +321,7 @@ "unhide_to_album": "Hacer visible al álbum", "restore_to_album": "Restaurar al álbum", "section_all": "Todo", - "section_uncategorized": "No clasificado", + "section_uncategorized": "Sin categorizar", "section_archive": "Archivo", "section_hidden": "Oculto", "section_trash": "Papelera", @@ -348,7 +350,7 @@ "leave_shared_album_message": "Dejará el álbum, y dejará de ser visible para usted.", "leave_shared_album": "Sí, dejar", "not_file_owner_delete_error": "No puedes eliminar archivos de un álbum compartido", - "confirm_remove_message": "Los elementos seleccionados serán eliminados de este álbum. Los elementos que estén sólo en este álbum serán movidos a Sin categorizar.", + "confirm_remove_message": "Los elementos seleccionados serán eliminados de este álbum. Los elementos que estén solo en este álbum serán movidos a Sin categorizar.", "confirm_remove_incl_others_message": "Algunos de los elementos que estás eliminando fueron añadidos por otras personas, y perderás el acceso a ellos.", "oldest": "Antiguo", "last_updated": "Última actualización", @@ -532,8 +534,8 @@ "EXPORT_PROGRESS": "{{progress.success}} / {{progress.total}} archivos exportados", "MIGRATING_EXPORT": "Preparando...", "RENAMING_COLLECTION_FOLDERS": "Renombrando carpetas del álbum...", - "TRASHING_DELETED_FILES": "", - "TRASHING_DELETED_COLLECTIONS": "", + "TRASHING_DELETED_FILES": "Eliminando ficheros borrados...", + "TRASHING_DELETED_COLLECTIONS": "Eliminando álbumes borrados...", "CONTINUOUS_EXPORT": "Sincronizar continuamente", "PENDING_ITEMS": "Elementos pendientes", "EXPORT_STARTING": "Exportar iniciando...", diff --git a/web/packages/base/locales/et-EE/translation.json b/web/packages/base/locales/et-EE/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/et-EE/translation.json +++ b/web/packages/base/locales/et-EE/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/fa-IR/translation.json b/web/packages/base/locales/fa-IR/translation.json index 572a3fcf6d..009681a82e 100644 --- a/web/packages/base/locales/fa-IR/translation.json +++ b/web/packages/base/locales/fa-IR/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/fi-FI/translation.json b/web/packages/base/locales/fi-FI/translation.json index 44725e7c69..63aecf6e39 100644 --- a/web/packages/base/locales/fi-FI/translation.json +++ b/web/packages/base/locales/fi-FI/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Syötä sähköpostiosoite", "EMAIL_ERROR": "Syötä voimassa oleva sähköpostiosoite", "required": "Pakollinen", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Vahvistuskoodi lähetetty osoitteeseen {{email}}", "CHECK_INBOX": "Tarkista saapuneet-kansiosi (ja roskaposti) suorittaaksesi vahvistuksen", "ENTER_OTT": "Vahvistuskoodi", diff --git a/web/packages/base/locales/fr-FR/translation.json b/web/packages/base/locales/fr-FR/translation.json index 2cfd66c291..fa1f074a4d 100644 --- a/web/packages/base/locales/fr-FR/translation.json +++ b/web/packages/base/locales/fr-FR/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Saisir l'adresse e-mail", "EMAIL_ERROR": "Saisir un e-mail valide", "required": "Nécessaire", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Code de vérification envoyé à {{email}}", "CHECK_INBOX": "Veuillez consulter votre boite de réception (et indésirables) pour poursuivre la vérification", "ENTER_OTT": "Code de vérification", diff --git a/web/packages/base/locales/gu-IN/translation.json b/web/packages/base/locales/gu-IN/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/gu-IN/translation.json +++ b/web/packages/base/locales/gu-IN/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/hi-IN/translation.json b/web/packages/base/locales/hi-IN/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/hi-IN/translation.json +++ b/web/packages/base/locales/hi-IN/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/hu-HU/translation.json b/web/packages/base/locales/hu-HU/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/hu-HU/translation.json +++ b/web/packages/base/locales/hu-HU/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/id-ID/translation.json b/web/packages/base/locales/id-ID/translation.json index cbe105ed01..1f25e8ac74 100644 --- a/web/packages/base/locales/id-ID/translation.json +++ b/web/packages/base/locales/id-ID/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Masukkan alamat email", "EMAIL_ERROR": "Masukkan email yang sah", "required": "Wajib", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Kode verifikasi dikirim ke {{email}}", "CHECK_INBOX": "Silakan periksa kotak masuk (serta kotak spam) untuk menyelesaikan verifikasi", "ENTER_OTT": "Kode verifikasi", diff --git a/web/packages/base/locales/is-IS/translation.json b/web/packages/base/locales/is-IS/translation.json index 656452b6a1..e1977a533b 100644 --- a/web/packages/base/locales/is-IS/translation.json +++ b/web/packages/base/locales/is-IS/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/it-IT/translation.json b/web/packages/base/locales/it-IT/translation.json index e51814436b..651d505a0b 100644 --- a/web/packages/base/locales/it-IT/translation.json +++ b/web/packages/base/locales/it-IT/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Inserisci l'indirizzo email", "EMAIL_ERROR": "Inserisci un indirizzo email valido", "required": "Campo obbligatorio", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Codice di verifica inviato a {{email}}", "CHECK_INBOX": "Controlla la tua casella di posta (e lo spam) per completare la verifica", "ENTER_OTT": "Codice di verifica", diff --git a/web/packages/base/locales/ja-JP/translation.json b/web/packages/base/locales/ja-JP/translation.json index a80bfdaa79..0cc82a65ff 100644 --- a/web/packages/base/locales/ja-JP/translation.json +++ b/web/packages/base/locales/ja-JP/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "メールアドレスを入力してください", "EMAIL_ERROR": "有効なメールアドレスを入力してください", "required": "必須", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "{{email}} に認証コードが送信されました", "CHECK_INBOX": "認証を完了するために、受信トレイ(および迷惑メール)を確認してください。", "ENTER_OTT": "認証コード", diff --git a/web/packages/base/locales/km-KH/translation.json b/web/packages/base/locales/km-KH/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/km-KH/translation.json +++ b/web/packages/base/locales/km-KH/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/ko-KR/translation.json b/web/packages/base/locales/ko-KR/translation.json index cb78dc73a4..67dc34a912 100644 --- a/web/packages/base/locales/ko-KR/translation.json +++ b/web/packages/base/locales/ko-KR/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "이메일 주소를 입력하세요", "EMAIL_ERROR": "올바른 이메일을 입력하세요", "required": "필수", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "{{email}} 로 인증 코드가 전송되었습니다", "CHECK_INBOX": "인증을 완료하기 위해 당신의 메일 수신함(그리고 스팸 수신함)을 확인하세요.", "ENTER_OTT": "인증 코드", diff --git a/web/packages/base/locales/lt-LT/translation.json b/web/packages/base/locales/lt-LT/translation.json index 69598d961b..f1892a9ca9 100644 --- a/web/packages/base/locales/lt-LT/translation.json +++ b/web/packages/base/locales/lt-LT/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Įveskite el. pašto adresą", "EMAIL_ERROR": "Įveskite tinkamą el. paštą.", "required": "privaloma.", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Patvirtinimo kodas išsiųstas adresu {{email}}", "CHECK_INBOX": "Patikrinkite savo gautiejus (ir šlamštą), kad užbaigtumėte patvirtinimą.", "ENTER_OTT": "Patvirtinimo kodas", diff --git a/web/packages/base/locales/ml-IN/translation.json b/web/packages/base/locales/ml-IN/translation.json new file mode 100644 index 0000000000..6ad2bf3c1a --- /dev/null +++ b/web/packages/base/locales/ml-IN/translation.json @@ -0,0 +1,657 @@ +{ + "intro_slide_1_title": "", + "intro_slide_1": "", + "intro_slide_2_title": "", + "intro_slide_2": "", + "intro_slide_3_title": "", + "intro_slide_3": "", + "login": "", + "sign_up": "", + "NEW_USER": "", + "EXISTING_USER": "", + "ENTER_EMAIL": "", + "EMAIL_ERROR": "", + "required": "", + "email_not_registered": "", + "email_already_registered": "", + "EMAIL_SENT": "", + "CHECK_INBOX": "", + "ENTER_OTT": "", + "RESEND_MAIL": "", + "VERIFY": "", + "generic_error": "", + "generic_error_retry": "", + "INVALID_CODE": "", + "EXPIRED_CODE": "", + "SENDING": "", + "SENT": "", + "password": "", + "link_password_description": "", + "unlock": "", + "SET_PASSPHRASE": "", + "VERIFY_PASSPHRASE": "", + "INCORRECT_PASSPHRASE": "", + "ENTER_ENC_PASSPHRASE": "", + "PASSPHRASE_DISCLAIMER": "", + "key_generation_in_progress": "", + "PASSPHRASE_HINT": "", + "CONFIRM_PASSPHRASE": "", + "REFERRAL_CODE_HINT": "", + "REFERRAL_INFO": "", + "PASSPHRASE_MATCH_ERROR": "", + "welcome_to_ente_title": "", + "welcome_to_ente_subtitle": "", + "new_album": "", + "create_albums": "", + "enter_album_name": "", + "close_key": "", + "enter_file_name": "", + "close": "", + "yes": "", + "no": "", + "nothing_here": "", + "upload": "", + "import": "", + "add_photos": "", + "add_more_photos": "", + "add_photos_count_one": "", + "add_photos_count": "", + "select_photos": "", + "FILE_UPLOAD": "", + "UPLOAD_STAGE_MESSAGE": { + "0": "", + "1": "", + "3": "", + "4": "", + "5": "" + }, + "FILE_NOT_UPLOADED_LIST": "", + "INITIAL_LOAD_DELAY_WARNING": "", + "USER_DOES_NOT_EXIST": "", + "NO_ACCOUNT": "", + "ACCOUNT_EXISTS": "", + "CREATE": "", + "download": "", + "download_album": "", + "download_favorites": "", + "download_uncategorized": "", + "download_hidden_items": "", + "download_key": "", + "copy_key": "", + "toggle_fullscreen_key": "", + "zoom_in_out_key": "", + "previous_key": "", + "next_key": "", + "title_photos": "", + "title_auth": "", + "title_accounts": "", + "UPLOAD_FIRST_PHOTO": "", + "IMPORT_YOUR_FOLDERS": "", + "upload_dropzone_hint": "", + "watch_folder_dropzone_hint": "", + "trash_files_title": "", + "trash_file_title": "", + "delete_files_title": "", + "delete_files_message": "", + "selected_count": "", + "selected_and_yours_count": "", + "delete": "", + "delete_key": "", + "favorite": "", + "favorite_key": "", + "unfavorite_key": "", + "convert": "", + "multi_folder_upload": "", + "upload_to_choice": "", + "upload_to_single_album": "", + "upload_to_album_per_folder": "", + "session_expired": "", + "session_expired_message": "", + "PASSWORD_GENERATION_FAILED": "", + "CHANGE_PASSWORD": "", + "password_changed_elsewhere": "", + "password_changed_elsewhere_message": "", + "go_back": "", + "recovery_key": "", + "do_this_later": "", + "save_key": "", + "recovery_key_description": "", + "key_not_stored_note": "", + "recovery_key_generation_failed": "", + "FORGOT_PASSWORD": "", + "RECOVER_ACCOUNT": "", + "RECOVERY_KEY_HINT": "", + "RECOVER": "", + "NO_RECOVERY_KEY": "", + "INCORRECT_RECOVERY_KEY": "", + "sorry": "", + "no_recovery_key_message": "", + "no_two_factor_recovery_key_message": "", + "contact_support": "", + "request_feature": "", + "support": "", + "cancel": "", + "logout": "", + "logout_message": "", + "delete_account": "", + "delete_account_manually_message": "", + "CHANGE_EMAIL": "", + "ok": "", + "success": "", + "error": "", + "OFFLINE_MSG": "", + "install": "", + "install_mobile_app": "", + "download_app": "", + "download_app_message": "", + "EXPORT": "", + "subscription": "", + "manage_payment_method": "", + "manage_family": "", + "family_plan": "", + "leave_family_plan": "", + "leave": "", + "leave_family_plan_confirm": "", + "choose_plan": "", + "manage_plan": "", + "current_usage": "", + "two_months_free": "", + "POPULAR": "", + "free_plan_option": "", + "free_plan_description": "", + "active": "", + "subscription_info_free": "", + "subscription_info_family": "", + "subscription_info_expired": "", + "subscription_info_renewal_cancelled": "", + "subscription_info_storage_quota_exceeded": "", + "subscription_status_renewal_active": "", + "subscription_status_renewal_cancelled": "", + "add_on_valid_till": "", + "subscription_expired": "", + "storage_quota_exceeded": "", + "subscription_purchase_success": "", + "subscription_purchase_cancelled": "", + "subscription_purchase_failed": "", + "subscription_verification_error": "", + "update_payment_method_message": "", + "payment_method_authentication_failed": "", + "update_payment_method": "", + "monthly": "", + "yearly": "", + "month_short": "", + "year": "", + "update_subscription": "", + "update_subscription_title": "", + "update_subscription_message": "", + "cancel_subscription": "", + "cancel_subscription_message": "", + "cancel_subscription_with_addon_message": "", + "subscription_cancel_success": "", + "reactivate_subscription": "", + "reactivate_subscription_message": "", + "subscription_activate_success": "", + "thank_you": "", + "cancel_subscription_on_mobile": "", + "cancel_subscription_on_mobile_message": "", + "mail_to_manage_subscription": "", + "rename": "", + "rename_file": "", + "rename_album": "", + "delete_album": "", + "delete_album_title": "", + "delete_album_message": "", + "delete_photos": "", + "keep_photos": "", + "share_album": "", + "SHARE_WITH_SELF": "", + "ALREADY_SHARED": "", + "SHARING_BAD_REQUEST_ERROR": "", + "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "", + "search": "", + "search_results": "", + "no_results": "", + "search_hint": "", + "album": "", + "date": "", + "description": "", + "file_type": "", + "magic": "", + "photos_count_zero": "", + "photos_count_one": "", + "photos_count": "", + "terms_and_conditions": "", + "people": "", + "indexing_scheduled": "", + "indexing_photos": "", + "indexing_fetching": "", + "indexing_people": "", + "indexing_done": "", + "syncing_wait": "", + "people_empty_too_few": "", + "unnamed_person": "", + "add_a_name": "", + "new_person": "", + "add_name": "", + "rename_person": "", + "reset_person_confirm": "", + "reset_person_confirm_message": "", + "ignore": "", + "ignore_person_confirm": "", + "ignore_person_confirm_message": "", + "ignored": "", + "show_person": "", + "review_suggestions": "", + "saved_choices": "", + "discard_changes": "", + "discard_changes_confirm_message": "", + "people_suggestions_finding": "", + "people_suggestions_empty": "", + "info": "", + "info_key": "", + "file_name": "", + "caption_placeholder": "", + "location": "", + "view_on_map": "", + "map": "", + "enable_map": "", + "enable_maps_confirm": "", + "enable_maps_confirm_message": "", + "disable_map": "", + "disable_maps_confirm": "", + "disable_maps_confirm_message": "", + "details": "", + "view_exif": "", + "no_exif": "", + "exif": "", + "ISO": "", + "two_factor": "", + "two_factor_authentication": "", + "TWO_FACTOR_QR_INSTRUCTION": "", + "ENTER_CODE_MANUALLY": "", + "TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "", + "SCAN_QR_CODE": "", + "enable_two_factor": "", + "enable": "", + "enabled": "", + "LOST_DEVICE": "", + "INCORRECT_CODE": "", + "two_factor_info": "", + "disable": "", + "reconfigure": "", + "reconfigure_two_factor_hint": "", + "update_two_factor": "", + "update_two_factor_message": "", + "update": "", + "disable_two_factor": "", + "disable_two_factor_message": "", + "export_data": "", + "select_folder": "", + "select_zips": "", + "faq": "", + "takeout_hint": "", + "destination": "", + "start": "", + "last_export_time": "", + "export_again": "", + "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "", + "SEND_OTT": "", + "email_already_taken": "", + "ETAGS_BLOCKED": "", + "LIVE_PHOTOS_DETECTED": "", + "RETRY_FAILED": "", + "FAILED_UPLOADS": "", + "failed_uploads_hint": "", + "SKIPPED_FILES": "", + "THUMBNAIL_GENERATION_FAILED_UPLOADS": "", + "UNSUPPORTED_FILES": "", + "SUCCESSFUL_UPLOADS": "", + "SKIPPED_INFO": "", + "UNSUPPORTED_INFO": "", + "BLOCKED_UPLOADS": "", + "INPROGRESS_UPLOADS": "", + "TOO_LARGE_UPLOADS": "", + "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "", + "LARGER_THAN_AVAILABLE_STORAGE_INFO": "", + "TOO_LARGE_INFO": "", + "THUMBNAIL_GENERATION_FAILED_INFO": "", + "upload_to_album": "", + "add_to_album": "", + "move_to_album": "", + "unhide_to_album": "", + "restore_to_album": "", + "section_all": "", + "section_uncategorized": "", + "section_archive": "", + "section_hidden": "", + "section_trash": "", + "favorites": "", + "archive": "", + "archive_album": "", + "unarchive": "", + "unarchive_album": "", + "hide_collection": "", + "unhide_collection": "", + "move": "", + "add": "", + "remove": "", + "yes_remove": "", + "remove_from_album": "", + "move_to_trash": "", + "trash_files_message": "", + "trash_file_message": "", + "delete_permanently": "", + "restore": "", + "empty_trash": "", + "empty_trash_title": "", + "empty_trash_message": "", + "leave_album": "", + "leave_shared_album_title": "", + "leave_shared_album_message": "", + "leave_shared_album": "", + "not_file_owner_delete_error": "", + "confirm_remove_message": "", + "confirm_remove_incl_others_message": "", + "oldest": "", + "last_updated": "", + "name": "", + "fix_creation_time": "", + "fix_creation_time_in_progress": "", + "fix_creation_time_file_updated": "", + "fix_creation_time_completed": "", + "fix_creation_time_completed_with_errors": "", + "fix_creation_time_options": "", + "exif_date_time_original": "", + "exif_date_time_digitized": "", + "exif_metadata_date": "", + "custom_time": "", + "caption_character_limit": "", + "sharing_details": "", + "modify_sharing": "", + "ADD_COLLABORATORS": "", + "ADD_NEW_EMAIL": "", + "shared_with_people_count_zero": "", + "shared_with_people_count_one": "", + "shared_with_people_count": "", + "participants_count_zero": "", + "participants_count_one": "", + "participants_count": "", + "ADD_VIEWERS": "", + "CHANGE_PERMISSIONS_TO_VIEWER": "", + "CHANGE_PERMISSIONS_TO_COLLABORATOR": "", + "CONVERT_TO_VIEWER": "", + "CONVERT_TO_COLLABORATOR": "", + "CHANGE_PERMISSION": "", + "REMOVE_PARTICIPANT": "", + "CONFIRM_REMOVE": "", + "MANAGE": "", + "ADDED_AS": "", + "COLLABORATOR_RIGHTS": "", + "REMOVE_PARTICIPANT_HEAD": "", + "OWNER": "", + "COLLABORATORS": "", + "ADD_MORE": "", + "VIEWERS": "", + "OR_ADD_EXISTING": "", + "REMOVE_PARTICIPANT_MESSAGE": "", + "NOT_FOUND": "", + "link_expired": "", + "link_expired_message": "", + "MANAGE_LINK": "", + "LINK_TOO_MANY_REQUESTS": "", + "FILE_DOWNLOAD": "", + "PUBLIC_COLLECT": "", + "LINK_DEVICE_LIMIT": "", + "NO_DEVICE_LIMIT": "", + "LINK_EXPIRY": "", + "never": "", + "disable_file_download": "", + "disable_file_download_message": "", + "SHARED_USING": "", + "SHARING_REFERRAL_CODE": "", + "live_photo_indicator": "", + "disable_password": "", + "disable_password_message": "", + "password_lock": "", + "lock": "", + "file": "", + "folder": "", + "google_takeout": "", + "DEDUPLICATE_FILES": "", + "NO_DUPLICATES_FOUND": "", + "FILES": "", + "EACH": "", + "DEDUPLICATE_BASED_ON_SIZE": "", + "stop_uploads_title": "", + "stop_uploads_message": "", + "yes_stop_uploads": "", + "stop_downloads_title": "", + "stop_downloads_message": "", + "yes_stop_downloads": "", + "albums": "", + "albums_count_one": "", + "albums_count": "", + "all_albums": "", + "all_hidden_albums": "", + "hidden_albums": "", + "hidden_items": "", + "ENTER_TWO_FACTOR_OTP": "", + "create_account": "", + "COPIED": "", + "upgrade_now": "", + "renew_now": "", + "storage": "", + "used": "", + "you": "", + "family": "", + "free": "", + "of": "", + "watch_folders": "", + "watched_folders": "", + "no_folders_added": "", + "watch_folders_hint_1": "", + "watch_folders_hint_2": "", + "watch_folders_hint_3": "", + "ADD_FOLDER": "", + "stop_watching": "", + "stop_watching_folder_title": "", + "stop_watching_folder_message": "", + "YES_STOP": "", + "CHANGE_FOLDER": "", + "debug_logs": "", + "download_logs": "", + "download_logs_message": "", + "WEAK_DEVICE": "", + "drag_and_drop_hint": "", + "AUTHENTICATE": "", + "UPLOADED_TO_SINGLE_COLLECTION": "", + "UPLOADED_TO_SEPARATE_COLLECTIONS": "", + "nevermind": "", + "update_available": "", + "update_installable_message": "", + "install_now": "", + "install_on_next_launch": "", + "update_available_message": "", + "download_and_install": "", + "ignore_this_version": "", + "TODAY": "", + "YESTERDAY": "", + "enter_name": "", + "uploader_name_hint": "", + "name_placeholder": "", + "root_level_file_with_folder_not_allowed": "", + "root_level_file_with_folder_not_allowed_message": "", + "CHOSE_THEME": "", + "more_details": "", + "ml_search": "", + "ml_search_description": "", + "ml_search_footnote": "", + "indexing": "", + "processed": "", + "indexing_status_running": "", + "indexing_status_fetching": "", + "indexing_status_scheduled": "", + "indexing_status_done": "", + "ml_search_disable": "", + "ml_search_disable_confirm": "", + "ml_consent": "", + "ml_consent_title": "", + "ml_consent_description": "", + "ml_consent_confirmation": "", + "labs": "", + "passphrase_strength_weak": "", + "passphrase_strength_moderate": "", + "passphrase_strength_strong": "", + "preferences": "", + "language": "", + "advanced": "", + "export_directory_does_not_exist": "", + "export_directory_does_not_exist_message": "", + "storage_unit": { + "b": "", + "kb": "", + "mb": "", + "gb": "", + "tb": "" + }, + "after_time": { + "hour": "", + "day": "", + "week": "", + "month": "", + "year": "" + }, + "copy_link": "", + "done": "", + "LINK_SHARE_TITLE": "", + "REMOVE_LINK": "", + "CREATE_PUBLIC_SHARING": "", + "public_link_created": "", + "PUBLIC_LINK_ENABLED": "", + "COLLECT_PHOTOS": "", + "PUBLIC_COLLECT_SUBTEXT": "", + "STOP_EXPORT": "", + "EXPORT_PROGRESS": "", + "MIGRATING_EXPORT": "", + "RENAMING_COLLECTION_FOLDERS": "", + "TRASHING_DELETED_FILES": "", + "TRASHING_DELETED_COLLECTIONS": "", + "CONTINUOUS_EXPORT": "", + "PENDING_ITEMS": "", + "EXPORT_STARTING": "", + "delete_account_reason_label": "", + "delete_account_reason_placeholder": "", + "delete_reason": { + "missing_feature": "", + "behaviour": "", + "found_another_service": "", + "not_listed": "" + }, + "delete_account_feedback_label": "", + "delete_account_feedback_placeholder": "", + "delete_account_confirm_checkbox_label": "", + "delete_account_confirm": "", + "delete_account_confirm_message": "", + "feedback_required": "", + "feedback_required_found_another_service": "", + "RECOVER_TWO_FACTOR": "", + "at": "", + "auth_next": "", + "auth_download_mobile_app": "", + "no_codes_added_yet": "", + "HIDE": "", + "UNHIDE": "", + "sort_by": "", + "newest_first": "", + "oldest_first": "", + "pin_album": "", + "unpin_album": "", + "unpreviewable_file_notification": "", + "DOWNLOAD_COMPLETE": "", + "DOWNLOADING_COLLECTION": "", + "DOWNLOAD_FAILED": "", + "DOWNLOAD_PROGRESS": "", + "CHRISTMAS": "", + "CHRISTMAS_EVE": "", + "NEW_YEAR": "", + "NEW_YEAR_EVE": "", + "image": "", + "video": "", + "live_photo": "", + "photo_editor": "", + "confirm_editor_close": "", + "confirm_editor_close_message": "", + "brightness": "", + "contrast": "", + "saturation": "", + "blur": "", + "transform": "", + "crop": "", + "aspect_ratio": "", + "square": "", + "freehand": "", + "apply_crop": "", + "rotation": "", + "rotate_left": "", + "rotate_right": "", + "flip": "", + "flip_vertically": "", + "flip_horizontally": "", + "download_edited": "", + "save_a_copy_to_ente": "", + "restore_original": "", + "photo_edit_required_to_save": "", + "colors": "", + "invert_colors": "", + "reset": "", + "faster_upload": "", + "faster_upload_description": "", + "cast_album_to_tv": "", + "enter_cast_pin_code": "", + "code": "", + "pair_device_to_tv": "", + "tv_not_found": "", + "cast_auto_pair": "", + "cast_auto_pair_description": "", + "choose_device_from_browser": "", + "cast_auto_pair_failed": "", + "pair_with_pin": "", + "pair_with_pin_description": "", + "visit_cast_url": "", + "passkeys": "", + "passkey_fetch_failed": "", + "manage_passkey": "", + "delete_passkey": "", + "delete_passkey_confirmation": "", + "rename_passkey": "", + "add_passkey": "", + "enter_passkey_name": "", + "passkeys_description": "", + "created_at": "", + "passkey_add_failed": "", + "passkey_login_failed": "", + "passkey_login_invalid_url": "", + "passkey_login_already_claimed_session": "", + "passkey_login_generic_error": "", + "passkey_login_credential_hint": "", + "passkeys_not_supported": "", + "try_again": "", + "check_status": "", + "passkey_login_instructions": "", + "passkey_login": "", + "totp_login": "", + "passkey": "", + "passkey_verify_description": "", + "waiting_for_verification": "", + "verification_still_pending": "", + "passkey_verified": "", + "redirecting_back_to_app": "", + "redirect_close_instructions": "", + "redirect_again": "", + "autogenerated_first_album_name": "", + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" +} diff --git a/web/packages/base/locales/nl-NL/translation.json b/web/packages/base/locales/nl-NL/translation.json index f904c54e93..0c5ddfd49c 100644 --- a/web/packages/base/locales/nl-NL/translation.json +++ b/web/packages/base/locales/nl-NL/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Vul e-mailadres in", "EMAIL_ERROR": "Vul een geldig e-mailadres in", "required": "Vereist", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Verificatiecode verzonden naar {{email}}", "CHECK_INBOX": "Controleer je inbox (en spam) om verificatie te voltooien", "ENTER_OTT": "Verificatiecode", diff --git a/web/packages/base/locales/pl-PL/translation.json b/web/packages/base/locales/pl-PL/translation.json index c5c4a1fc5a..d5cf8fed00 100644 --- a/web/packages/base/locales/pl-PL/translation.json +++ b/web/packages/base/locales/pl-PL/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Wprowadź adres e-mail", "EMAIL_ERROR": "Wprowadź prawidłowy adres e-mail", "required": "Wymagane", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Kod weryfikacyjny wysłany do {{email}}", "CHECK_INBOX": "Sprawdź swoją skrzynkę odbiorczą (i spam), aby zakończyć weryfikację", "ENTER_OTT": "Kod weryfikacyjny", diff --git a/web/packages/base/locales/pt-BR/translation.json b/web/packages/base/locales/pt-BR/translation.json index 435b55085a..eb240a47de 100644 --- a/web/packages/base/locales/pt-BR/translation.json +++ b/web/packages/base/locales/pt-BR/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Insira o endereço de e-mail", "EMAIL_ERROR": "Inserir um endereço de e-mail válido", "required": "requerido", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Código de verificação enviado para {{email}}", "CHECK_INBOX": "Verifique a sua caixa de entrada (e spam) para concluir a verificação", "ENTER_OTT": "Código de verificação", diff --git a/web/packages/base/locales/pt-PT/translation.json b/web/packages/base/locales/pt-PT/translation.json index 7818840a6e..2d136c0657 100644 --- a/web/packages/base/locales/pt-PT/translation.json +++ b/web/packages/base/locales/pt-PT/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Introduza o endereço de email", "EMAIL_ERROR": "Introduza um endereço de email válido", "required": "Obrigatório", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Código de verificação enviado para {{email}}", "CHECK_INBOX": "Verifique a sua caixa de entrada (e spam) para concluir a verificação", "ENTER_OTT": "Código de verificação", diff --git a/web/packages/base/locales/ro-RO/translation.json b/web/packages/base/locales/ro-RO/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/ro-RO/translation.json +++ b/web/packages/base/locales/ro-RO/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/ru-RU/translation.json b/web/packages/base/locales/ru-RU/translation.json index f3991f2baf..70611f521a 100644 --- a/web/packages/base/locales/ru-RU/translation.json +++ b/web/packages/base/locales/ru-RU/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Введите адрес электронной почты", "EMAIL_ERROR": "Введите действительный адрес электронной почты", "required": "Требуется", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Проверочный код отправлен на {{email}}", "CHECK_INBOX": "Пожалуйста, проверьте свой почтовый ящик (и спам) для завершения проверки", "ENTER_OTT": "Проверочный код", diff --git a/web/packages/base/locales/sl-SI/translation.json b/web/packages/base/locales/sl-SI/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/sl-SI/translation.json +++ b/web/packages/base/locales/sl-SI/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/sv-SE/translation.json b/web/packages/base/locales/sv-SE/translation.json index d019202977..f135254641 100644 --- a/web/packages/base/locales/sv-SE/translation.json +++ b/web/packages/base/locales/sv-SE/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Ange e-postadress", "EMAIL_ERROR": "Ange en giltig e-postadress", "required": "Obligatoriskt", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Verifieringskod skickas till {{email}}", "CHECK_INBOX": "Kontrollera din inkorg (och skräppost) för att slutföra verifieringen", "ENTER_OTT": "Verifieringskod", diff --git a/web/packages/base/locales/ta-IN/translation.json b/web/packages/base/locales/ta-IN/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/ta-IN/translation.json +++ b/web/packages/base/locales/ta-IN/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/te-IN/translation.json b/web/packages/base/locales/te-IN/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/te-IN/translation.json +++ b/web/packages/base/locales/te-IN/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/th-TH/translation.json b/web/packages/base/locales/th-TH/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/th-TH/translation.json +++ b/web/packages/base/locales/th-TH/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/ti-ER/translation.json b/web/packages/base/locales/ti-ER/translation.json index b4200225e4..6ad2bf3c1a 100644 --- a/web/packages/base/locales/ti-ER/translation.json +++ b/web/packages/base/locales/ti-ER/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "", "EMAIL_ERROR": "", "required": "", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "", "CHECK_INBOX": "", "ENTER_OTT": "", diff --git a/web/packages/base/locales/tr-TR/translation.json b/web/packages/base/locales/tr-TR/translation.json index 4b91bc0fa6..9cf7f32ac4 100644 --- a/web/packages/base/locales/tr-TR/translation.json +++ b/web/packages/base/locales/tr-TR/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "E-posta adresini girin", "EMAIL_ERROR": "Geçerli bir e-posta gir", "required": "Gerekli", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Doğrulama kodu {{email}} adresine gönderildi", "CHECK_INBOX": "Lütfen doğrulama işlemini tamamlamak için gelen kutusunu (veya spam) kontrol et", "ENTER_OTT": "Doğrulama kodu", diff --git a/web/packages/base/locales/uk-UA/translation.json b/web/packages/base/locales/uk-UA/translation.json index 0e840f09fe..50339b9ae0 100644 --- a/web/packages/base/locales/uk-UA/translation.json +++ b/web/packages/base/locales/uk-UA/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Введіть поштову адресу", "EMAIL_ERROR": "Введіть дійсну поштову адресу", "required": "обов'язково", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Код підтвердження надіслано на {{email}}", "CHECK_INBOX": "Перевірте вашу поштову скриньку (та спам), щоб завершити перевірку", "ENTER_OTT": "Код перевірки", @@ -96,7 +98,7 @@ "delete": "Видалити", "delete_key": "Видалити (DEL)", "favorite": "Улюблене", - "favorite_key": "Улюблене (L)", + "favorite_key": "Додати до улюбленого (L)", "unfavorite_key": "Вилучити з улюбленого (L)", "convert": "Перетворити", "multi_folder_upload": "Виявлено кілька тек", diff --git a/web/packages/base/locales/vi-VN/translation.json b/web/packages/base/locales/vi-VN/translation.json index a77faab3a8..1603a4c92d 100644 --- a/web/packages/base/locales/vi-VN/translation.json +++ b/web/packages/base/locales/vi-VN/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "Nhập địa chỉ email", "EMAIL_ERROR": "Nhập một email hợp lệ", "required": "bắt buộc", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "Mã xác minh đã được gửi đến {{email}}", "CHECK_INBOX": "Vui lòng kiểm tra hộp thư đến (và thư rác) để hoàn tất xác minh", "ENTER_OTT": "Mã xác minh", diff --git a/web/packages/base/locales/zh-CN/translation.json b/web/packages/base/locales/zh-CN/translation.json index 8f189b9c25..85352d44fa 100644 --- a/web/packages/base/locales/zh-CN/translation.json +++ b/web/packages/base/locales/zh-CN/translation.json @@ -12,6 +12,8 @@ "ENTER_EMAIL": "请输入电子邮件地址", "EMAIL_ERROR": "请输入有效的电子邮件", "required": "必需的", + "email_not_registered": "", + "email_already_registered": "", "EMAIL_SENT": "验证码已发送至 {{email}}", "CHECK_INBOX": "请检查您的收件箱 (或者是在您的“垃圾邮件”列表内) 以完成验证", "ENTER_OTT": "验证码", diff --git a/web/packages/new/photos/components/PlanSelector.tsx b/web/packages/new/photos/components/PlanSelector.tsx index eea71dd2b4..a0c1ed49d1 100644 --- a/web/packages/new/photos/components/PlanSelector.tsx +++ b/web/packages/new/photos/components/PlanSelector.tsx @@ -541,9 +541,7 @@ const PlanRow: React.FC = ({ return ( - - {bytesInGB(plan.storage)} - + {bytesInGB(plan.storage)} {t("storage_unit.gb")} diff --git a/web/packages/new/photos/pages/duplicates.tsx b/web/packages/new/photos/pages/duplicates.tsx new file mode 100644 index 0000000000..fb6791b21a --- /dev/null +++ b/web/packages/new/photos/pages/duplicates.tsx @@ -0,0 +1,243 @@ +import { ActivityErrorIndicator } from "@/base/components/ErrorIndicator"; +import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator"; +import { CenteredFill } from "@/base/components/mui/Container"; +import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton"; +import { pt } from "@/base/i18n"; +import log from "@/base/log"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; +import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; +import SortIcon from "@mui/icons-material/Sort"; +import { Box, IconButton, Stack, Typography } from "@mui/material"; +import { useRouter } from "next/router"; +import React, { useEffect, useReducer } from "react"; +import Autosizer from "react-virtualized-auto-sizer"; +import { deduceDuplicates, type DuplicateGroup } from "../services/dedup"; +import { useAppContext } from "../types/context"; + +const Page: React.FC = () => { + const { showNavBar } = useAppContext(); + + const [state, dispatch] = useReducer(dedupReducer, initialDedupState); + + useEffect(() => { + // TODO: Remove me + showNavBar(false); + + dispatch({ type: "analyze" }); + void deduceDuplicates() + .then((duplicateGroups) => + dispatch({ type: "analysisCompleted", duplicateGroups }), + ) + .catch((e: unknown) => { + log.error("Failed to detect duplicates", e); + dispatch({ type: "analysisFailed" }); + }); + }, [showNavBar]); + + const contents = (() => { + switch (state.status) { + case undefined: + case "analyzing": + return ; + case "analysisFailed": + return ; + case "analysisCompleted": + if (state.duplicateGroups.length == 0) { + return ; + } else { + return ; + } + default: + return ; + } + })(); + + return ( + + + {contents} + + ); +}; + +export default Page; + +interface DedupState { + status: + | undefined + | "analyzing" + | "analysisFailed" + | "analysisCompleted" + | "dedupe" + | "dedupeFailed"; + /** + * Groups of duplicates. + * + * These are groups of files that our algorithm has detected as exact + * duplicates, augmented with UI state and various cached properties to make + * them more amenable to be directly used by the UI component. + * + * These are sorted in order of display, reflecting the {@link sortType} + * user preference. + */ + duplicateGroups: DuplicateGroup[]; + /** + * The attribute to use for sorting {@link duplicateGroups}. + */ + sortOrder: "prunableCount" | "prunableSize"; + /** + * The number of files that will be pruned if the user decides to dedup the + * current selection. + */ + prunableCount: number; + /** + * The size (in bytes) that can be saved if the user decides to dedup the + * current selection. + */ + prunableSize: number; +} + +type DedupAction = + | { type: "analyze" } + | { type: "analysisFailed" } + | { type: "analysisCompleted"; duplicateGroups: DuplicateGroup[] } + | { type: "changeSortOrder"; sortOrder: DedupState["sortOrder"] } + | { type: "select"; index: number } + | { type: "deselect"; index: number } + | { type: "deselectAll" } + | { type: "dedupe" } + | { type: "dedupeCompleted" } + | { type: "dedupeFailed" }; + +const initialDedupState: DedupState = { + status: undefined, + duplicateGroups: [], + sortOrder: "prunableSize", + prunableCount: 0, + prunableSize: 0, +}; + +const dedupReducer: React.Reducer = ( + state, + action, +) => { + switch (action.type) { + case "analyze": + return { ...state, status: "analyzing" }; + case "analysisFailed": + return { ...state, status: "analysisFailed" }; + case "analysisCompleted": { + const duplicateGroups = action.duplicateGroups; + const prunableCount = duplicateGroups.reduce( + (sum, { prunableCount }) => sum + prunableCount, + 0, + ); + const prunableSize = duplicateGroups.reduce( + (sum, { prunableSize }) => sum + prunableSize, + 0, + ); + return { + ...state, + status: "analysisCompleted", + duplicateGroups, + prunableCount, + prunableSize, + }; + } + + default: + return state; + } +}; + +const Navbar: React.FC = () => { + const router = useRouter(); + + return ( + ({ + alignItems: "center", + justifyContent: "space-between", + padding: "8px 4px", + borderBottom: `1px solid ${theme.palette.divider}`, + })} + > + + + + + + {pt("Remove duplicates")} + + + + + + + + + + ); +}; + +const Loading: React.FC = () => ( + + + +); + +const LoadFailed: React.FC = () => ( + + + +); + +const NoDuplicatesFound: React.FC = () => ( + + + {pt("No duplicates")} + + +); + +const Duplicates: React.FC = () => { + return ( + + + + {({ height, width }) => ( + +
1
+
1
+
1
+
1
+
1
+
1
+
+ )} +
+
+ + + Test + Test + + +
+ ); +}; diff --git a/web/packages/new/photos/services/dedup.ts b/web/packages/new/photos/services/dedup.ts new file mode 100644 index 0000000000..163fb71520 --- /dev/null +++ b/web/packages/new/photos/services/dedup.ts @@ -0,0 +1,78 @@ +import type { EnteFile } from "@/media/file"; +import { wait } from "@/utils/promise"; + +/** + * A group of duplicates as shown in the UI. + */ +export interface DuplicateGroup { + /** + * Files which our algorithm has determined to be duplicates of each other. + * + * These are sorted by the collectionName. + */ + items: { + /** The underlying collection file. */ + file: EnteFile; + /** The name of the collection to which this file belongs. */ + collectionName: string; + }[]; + /** + * The size (in bytes) of each item in the group. + */ + itemSize: number; + /** + * The number of files that will be pruned if the user decides to dedup this + * group. + */ + prunableCount: number; + /** + * The size (in bytes) that can be saved if the user decides to dedup this + * group. + */ + prunableSize: number; + /** + * `true` if the user has marked this group for deduping. + */ + isSelected: boolean; +} + +/** + * Find exact duplicates in the user's library, and return them in groups that + * can then be deduped keeping only one entry in each group. + * + * [Note: Deduplication logic] + * + * Detecting duplicates: + * + * 1. Identify and divide files into multiple groups based on (size + hash). + * + * 2. By default select all group, with option to unselect individual groups or + * all groups. + * + * Pruning duplicates: + * + * When user presses the dedup button with some selected groups, + * + * 1. Identify and select the file which we don't want to delete (preferring + * file with caption or edited time). + * + * 2. For the remaining files identify the collection owned by the user in which + * the remaining files are present. + * + * 3. Add the file that we don't plan to delete to such collections as a + * symlink. + * + * 4. Delete the remaining files. + */ +export const deduceDuplicates = async () => { + await wait(1000); + return [ + { + items: [], + itemSize: 0, + prunableCount: 0, + prunableSize: 0, + isSelected: true, + }, + ]; +}; diff --git a/web/packages/shared/themes/typography.ts b/web/packages/shared/themes/typography.ts index 7279742823..4d18800230 100644 --- a/web/packages/shared/themes/typography.ts +++ b/web/packages/shared/themes/typography.ts @@ -4,6 +4,11 @@ export const typography: TypographyOptions = { h1: { fontSize: "48px", lineHeight: "58px", + // [Note: Bold headings] + // + // Browser default is bold, but MUI resets it to 500 which is too light + // for our chosen font. + fontWeight: "bold", }, h2: { fontSize: "32px", @@ -20,8 +25,7 @@ export const typography: TypographyOptions = { h5: { fontSize: "20px", lineHeight: "25px", - // Browser default is bold, but MUI resets it to 500 which is too light - // for our chosen font. + // See: [Note: Bold headings] fontWeight: "bold", }, // h6 is the default variant used by MUI's DialogTitle. @@ -29,8 +33,7 @@ export const typography: TypographyOptions = { // The font size and line height belows is the same as large. fontSize: "18px", lineHeight: "22px", - // Browser default is bold, but MUI resets it to 500 which is too light - // for our chosen font. + // See: [Note: Bold headings] fontWeight: "bold", }, large: {