Browse Source

Merge branch 'main' into mobile_face

Neeraj Gupta 1 year ago
parent
commit
c224e38ddf
100 changed files with 1372 additions and 6605 deletions
  1. 2 1
      .github/workflows/auth-release.yml
  2. 1 1
      .github/workflows/web-crowdin.yml
  3. 4 1
      CONTRIBUTING.md
  4. 1 13
      auth/ios/Podfile.lock
  5. 5 5
      auth/ios/Runner.xcodeproj/project.pbxproj
  6. 7 7
      auth/lib/l10n/arb/app_en.arb
  7. 16 6
      auth/lib/ui/home_page.dart
  8. 23 29
      auth/lib/ui/two_factor_authentication_page.dart
  9. 0 4
      auth/linux/flutter/generated_plugin_registrant.cc
  10. 0 1
      auth/linux/flutter/generated_plugins.cmake
  11. 1 0
      auth/linux/packaging/appimage/make_config.yaml
  12. 1 1
      auth/linux/packaging/rpm/make_config.yaml
  13. 0 2
      auth/macos/Flutter/GeneratedPluginRegistrant.swift
  14. 2 18
      auth/pubspec.lock
  15. 2 2
      auth/pubspec.yaml
  16. 0 3
      auth/windows/flutter/generated_plugin_registrant.cc
  17. 0 1
      auth/windows/flutter/generated_plugins.cmake
  18. 0 5
      desktop/.eslintrc.js
  19. BIN
      desktop/build/ggmlclip-linux
  20. BIN
      desktop/build/ggmlclip-mac
  21. BIN
      desktop/build/ggmlclip-windows.exe
  22. BIN
      desktop/build/msvcp140d.dll
  23. BIN
      desktop/build/ucrtbased.dll
  24. BIN
      desktop/build/vcruntime140_1d.dll
  25. BIN
      desktop/build/vcruntime140d.dll
  26. 17 17
      desktop/docs/dependencies.md
  27. 0 1
      desktop/electron-builder.yml
  28. 0 17
      desktop/src/api/electronStore.ts
  29. 0 28
      desktop/src/api/safeStorage.ts
  30. 0 41
      desktop/src/api/upload.ts
  31. 0 26
      desktop/src/constants/errors.ts
  32. 25 10
      desktop/src/main.ts
  33. 0 13
      desktop/src/main/init.ts
  34. 33 37
      desktop/src/main/ipc.ts
  35. 15 29
      desktop/src/main/log.ts
  36. 16 24
      desktop/src/preload.ts
  37. 19 32
      desktop/src/services/appUpdater.ts
  38. 2 2
      desktop/src/services/chokidar.ts
  39. 288 0
      desktop/src/services/clip-service.ts
  40. 0 506
      desktop/src/services/clipService.ts
  41. 1 2
      desktop/src/services/ffmpeg.ts
  42. 4 2
      desktop/src/services/fs.ts
  43. 16 11
      desktop/src/services/imageProcessor.ts
  44. 26 0
      desktop/src/services/store.ts
  45. 29 0
      desktop/src/services/upload.ts
  46. 1 0
      desktop/src/types/any-shell-escape.d.ts
  47. 27 5
      desktop/src/types/ipc.ts
  48. 1 0
      desktop/src/types/main.ts
  49. 4 77
      docs/docs/.vitepress/sidebar.ts
  50. 10 8
      docs/docs/photos/faq/export.md
  51. 15 6
      docs/docs/photos/faq/general.md
  52. 3 1
      docs/docs/photos/faq/subscription.md
  53. 0 1
      docs/docs/photos/migration/export/index.md
  54. 22 8
      docs/docs/self-hosting/guides/admin.md
  55. 0 5
      docs/docs/self-hosting/guides/custom-server/index.md
  56. 2 2
      docs/docs/self-hosting/index.md
  57. 13 0
      docs/docs/self-hosting/troubleshooting/uploads.md
  58. 1 1
      infra/services/listmonk/listmonk.service
  59. 1 0
      infra/services/nginx/nginx.service
  60. 36 0
      mobile/fastlane/metadata/android/tr/full_description.txt
  61. 1 0
      mobile/fastlane/metadata/android/tr/short_description.txt
  62. 1 0
      mobile/fastlane/metadata/android/tr/title.txt
  63. 1 1
      mobile/fastlane/metadata/ios/ru/name.txt
  64. 1 1
      mobile/fastlane/metadata/ios/ru/subtitle.txt
  65. 33 0
      mobile/fastlane/metadata/ios/tr/description.txt
  66. 1 0
      mobile/fastlane/metadata/ios/tr/keywords.txt
  67. 1 0
      mobile/fastlane/metadata/ios/tr/name.txt
  68. 1 0
      mobile/fastlane/metadata/ios/tr/subtitle.txt
  69. 30 0
      mobile/fastlane/metadata/playstore/tr/full_description.txt
  70. 1 0
      mobile/fastlane/metadata/playstore/tr/short_description.txt
  71. 130 0
      mobile/integration_test/app_init_test.dart
  72. 32 4
      mobile/lib/db/files_db.dart
  73. 29 29
      mobile/lib/generated/intl/messages_en.dart
  74. 60 60
      mobile/lib/generated/l10n.dart
  75. 31 31
      mobile/lib/l10n/intl_en.arb
  76. 39 32
      mobile/lib/l10n/intl_pt.arb
  77. 53 46
      mobile/lib/l10n/intl_zh.arb
  78. 27 1
      mobile/lib/services/remote_sync_service.dart
  79. 1 1
      mobile/lib/ui/payment/stripe_subscription_page.dart
  80. 123 122
      mobile/lib/ui/viewer/file/file_app_bar.dart
  81. 2 0
      mobile/lib/ui/viewer/file_details/upload_icon_widget.dart
  82. 10 2
      mobile/pubspec.lock
  83. 3 2
      mobile/pubspec.yaml
  84. 23 0
      mobile/scripts/app_init_perf_test.sh
  85. 2 1
      mobile/scripts/gallery_scroll_perf_test.sh
  86. 3 2
      mobile/test_driver/perf_driver.dart
  87. 9 0
      server/migrations/83_fix_ott_index.down.sql
  88. 9 0
      server/migrations/83_fix_ott_index.up.sql
  89. 5 12
      server/pkg/controller/mailing_lists.go
  90. 7 6
      server/pkg/controller/user/userauth.go
  91. 8 8
      server/pkg/repo/userauth.go
  92. 3 0
      web/.gitignore
  93. 0 654
      web/apps/accounts/public/locales/bg-BG/translation.json
  94. 0 654
      web/apps/accounts/public/locales/de-DE/translation.json
  95. 0 654
      web/apps/accounts/public/locales/en-US/translation.json
  96. 0 654
      web/apps/accounts/public/locales/es-ES/translation.json
  97. 0 654
      web/apps/accounts/public/locales/fa-IR/translation.json
  98. 0 654
      web/apps/accounts/public/locales/fr-FR/translation.json
  99. 0 654
      web/apps/accounts/public/locales/it-IT/translation.json
  100. 0 654
      web/apps/accounts/public/locales/ko-KR/translation.json

+ 2 - 1
.github/workflows/auth-release.yml

@@ -85,7 +85,8 @@ jobs:
             - name: Install dependencies for desktop build
               run: |
                   sudo apt-get update -y
-                  sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate appindicator3-0.1 libappindicator3-dev libffi7
+                  sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate appindicator3-0.1 libappindicator3-dev libffi-dev libtiff5
+                  sudo updatedb --localpaths='/usr/lib/x86_64-linux-gnu'
 
             - name: Install appimagetool
               run: |

+ 1 - 1
.github/workflows/web-crowdin.yml

@@ -5,7 +5,7 @@ on:
         branches: [main]
         paths:
             # Run workflow when web's en-US/translation.json is changed
-            - "web/apps/photos/public/locales/en-US/translation.json"
+            - "web/packages/next/locales/en-US/translation.json"
             # Or the workflow itself is changed
             - ".github/workflows/web-crowdin.yml"
     schedule:

+ 4 - 1
CONTRIBUTING.md

@@ -59,7 +59,10 @@ See [docs/](docs/README.md) for how to edit these documents.
 
 ## Code contributions
 
-If you'd like to contribute code, it is best to start small.
+Code is a small aspect of community, and the ways mentioned above are more
+important in helping us. But if you'd _really_ like to contribute code, it is
+best to start small. Consider some well-scoped changes, say like adding more
+[custom icons to auth](auth/docs/adding-icons.md).
 
 Each of the individual product/platform specific directories in this repository
 have instructions on setting up a dev environment and making changes. The issues

+ 1 - 13
auth/ios/Podfile.lock

@@ -67,8 +67,6 @@ PODS:
     - Toast
   - local_auth_darwin (0.0.1):
     - Flutter
-  - local_auth_ios (0.0.1):
-    - Flutter
   - move_to_background (0.0.1):
     - Flutter
   - MTBBarcodeScanner (5.0.11)
@@ -99,8 +97,6 @@ PODS:
   - shared_preferences_foundation (0.0.1):
     - Flutter
     - FlutterMacOS
-  - smart_auth (0.0.1):
-    - Flutter
   - sodium_libs (2.2.1):
     - Flutter
   - sqflite (0.0.3):
@@ -142,7 +138,6 @@ DEPENDENCIES:
   - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
   - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
   - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
-  - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
   - move_to_background (from `.symlinks/plugins/move_to_background/ios`)
   - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
   - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
@@ -151,7 +146,6 @@ DEPENDENCIES:
   - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
   - share_plus (from `.symlinks/plugins/share_plus/ios`)
   - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
-  - smart_auth (from `.symlinks/plugins/smart_auth/ios`)
   - sodium_libs (from `.symlinks/plugins/sodium_libs/ios`)
   - sqflite (from `.symlinks/plugins/sqflite/darwin`)
   - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
@@ -202,8 +196,6 @@ EXTERNAL SOURCES:
     :path: ".symlinks/plugins/fluttertoast/ios"
   local_auth_darwin:
     :path: ".symlinks/plugins/local_auth_darwin/darwin"
-  local_auth_ios:
-    :path: ".symlinks/plugins/local_auth_ios/ios"
   move_to_background:
     :path: ".symlinks/plugins/move_to_background/ios"
   package_info_plus:
@@ -220,8 +212,6 @@ EXTERNAL SOURCES:
     :path: ".symlinks/plugins/share_plus/ios"
   shared_preferences_foundation:
     :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
-  smart_auth:
-    :path: ".symlinks/plugins/smart_auth/ios"
   sodium_libs:
     :path: ".symlinks/plugins/sodium_libs/ios"
   sqflite:
@@ -245,11 +235,10 @@ SPEC CHECKSUMS:
   flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
   flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
   flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
-  flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
+  flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
   flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
   fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
   local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98
-  local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
   move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
   MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
   OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
@@ -264,7 +253,6 @@ SPEC CHECKSUMS:
   SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
   share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
   shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
-  smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2
   sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318
   sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
   sqlite3: 73b7fc691fdc43277614250e04d183740cb15078

+ 5 - 5
auth/ios/Runner.xcodeproj/project.pbxproj

@@ -365,7 +365,7 @@
 				DEVELOPMENT_TEAM = 6Z68YJY9Q2;
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = Runner/Info.plist;
-				INFOPLIST_KEY_CFBundleDisplayName = auth;
+				INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
@@ -439,7 +439,7 @@
 				DEVELOPMENT_TEAM = 6Z68YJY9Q2;
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = Runner/Info.plist;
-				INFOPLIST_KEY_CFBundleDisplayName = auth;
+				INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
@@ -513,7 +513,7 @@
 				DEVELOPMENT_TEAM = 6Z68YJY9Q2;
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = Runner/Info.plist;
-				INFOPLIST_KEY_CFBundleDisplayName = auth;
+				INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
@@ -587,7 +587,7 @@
 				DEVELOPMENT_TEAM = 6Z68YJY9Q2;
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = Runner/Info.plist;
-				INFOPLIST_KEY_CFBundleDisplayName = auth;
+				INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",
@@ -661,7 +661,7 @@
 				DEVELOPMENT_TEAM = 6Z68YJY9Q2;
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = Runner/Info.plist;
-				INFOPLIST_KEY_CFBundleDisplayName = auth;
+				INFOPLIST_KEY_CFBundleDisplayName = "Ente Auth";
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/Frameworks",

+ 7 - 7
auth/lib/l10n/arb/app_en.arb

@@ -78,14 +78,14 @@
   "data": "Data",
   "importCodes": "Import codes",
   "importTypePlainText": "Plain text",
-  "importTypeEnteEncrypted": "ente Encrypted export",
+  "importTypeEnteEncrypted": "Ente Encrypted export",
   "passwordForDecryptingExport": "Password to decrypt export",
   "passwordEmptyError": "Password can not be empty",
   "importFromApp": "Import codes from {appName}",
   "importGoogleAuthGuide": "Export your accounts from Google Authenticator to a QR code using the \"Transfer Accounts\" option. Then using another device, scan the QR code.\n\nTip: You can use your laptop's webcam to take a picture of the QR code.",
   "importSelectJsonFile": "Select JSON file",
   "importSelectAppExport": "Select {appName} export file",
-  "importEnteEncGuide": "Select the encrypted JSON file exported from ente",
+  "importEnteEncGuide": "Select the encrypted JSON file exported from Ente",
   "importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.",
   "importBitwardenGuide": "Use the \"Export vault\" option within Bitwarden Tools and import the unencrypted JSON file.",
   "importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.",
@@ -115,22 +115,22 @@
   "copied": "Copied",
   "pleaseTryAgain": "Please try again",
   "existingUser": "Existing User",
-  "newUser": "New to ente",
+  "newUser": "New to Ente",
   "delete": "Delete",
   "enterYourPasswordHint": "Enter your password",
   "forgotPassword": "Forgot password",
   "oops": "Oops",
   "suggestFeatures": "Suggest features",
   "faq": "FAQ",
-  "faq_q_1": "How secure is ente Auth?",
-  "faq_a_1": "All codes you backup via ente is stored end-to-end encrypted. This means only you can access your codes. Our apps are open source and our cryptography has been externally audited.",
+  "faq_q_1": "How secure is Auth?",
+  "faq_a_1": "All codes you backup via Auth is stored end-to-end encrypted. This means only you can access your codes. Our apps are open source and our cryptography has been externally audited.",
   "faq_q_2": "Can I access my codes on desktop?",
   "faq_a_2": "You can access your codes on the web @ auth.ente.io.",
   "faq_q_3": "How can I delete codes?",
   "faq_a_3": "You can delete a code by swiping left on that item.",
   "faq_q_4": "How can I support this project?",
   "faq_a_4": "You can support the development of this project by subscribing to our Photos app @ ente.io.",
-  "faq_q_5": "How can I enable FaceID lock in ente Auth",
+  "faq_q_5": "How can I enable FaceID lock in Auth",
   "faq_a_5": "You can enable FaceID lock under Settings → Security → Lockscreen.",
   "somethingWentWrongMessage": "Something went wrong, please try again",
   "leaveFamily": "Leave family",
@@ -350,7 +350,7 @@
   "deleteCodeAuthMessage": "Authenticate to delete code",
   "showQRAuthMessage": "Authenticate to show QR code",
   "confirmAccountDeleteTitle": "Confirm account deletion",
-  "confirmAccountDeleteMessage": "This account is linked to other ente apps, if you use any.\n\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
+  "confirmAccountDeleteMessage": "This account is linked to other Ente apps, if you use any.\n\nYour uploaded data, across all Ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
   "androidBiometricHint": "Verify identity",
   "@androidBiometricHint": {
     "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."

+ 16 - 6
auth/lib/ui/home_page.dart

@@ -93,12 +93,22 @@ class _HomePageState extends State<HomePage> {
   void _applyFilteringAndRefresh() {
     if (_searchText.isNotEmpty && _showSearchBox) {
       final String val = _searchText.toLowerCase();
-      _filteredCodes = _codes
-          .where(
-            (element) => (element.account.toLowerCase().contains(val) ||
-                element.issuer.toLowerCase().contains(val)),
-          )
-          .toList();
+      // Prioritize issuer match above account for better UX while searching
+      // for a specific TOTP for email providers. Searching for "emailProvider" like (gmail, proton) should
+      // show the email provider first instead of other accounts where protonmail
+      // is the account name.
+      final List<Code> issuerMatch = [];
+      final List<Code> accountMatch = [];
+
+      for (final Code code in _codes) {
+        if (code.issuer.toLowerCase().contains(val)) {
+          issuerMatch.add(code);
+        } else if (code.account.toLowerCase().contains(val)) {
+          accountMatch.add(code);
+        }
+      }
+      _filteredCodes = issuerMatch;
+      _filteredCodes.addAll(accountMatch);
     } else {
       _filteredCodes = _codes;
     }

+ 23 - 29
auth/lib/ui/two_factor_authentication_page.dart

@@ -4,8 +4,7 @@ import 'package:ente_auth/services/user_service.dart';
 import 'package:ente_auth/ui/lifecycle_event_handler.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
-
-import 'package:pinput/pinput.dart';
+import 'package:pinput/pin_put/pin_put.dart';
 
 class TwoFactorAuthenticationPage extends StatefulWidget {
   final String sessionID;
@@ -20,6 +19,10 @@ class TwoFactorAuthenticationPage extends StatefulWidget {
 class _TwoFactorAuthenticationPageState
     extends State<TwoFactorAuthenticationPage> {
   final _pinController = TextEditingController();
+  final _pinPutDecoration = BoxDecoration(
+    border: Border.all(color: const Color.fromRGBO(45, 194, 98, 1.0)),
+    borderRadius: BorderRadius.circular(15.0),
+  );
   String _code = "";
   late LifecycleEventHandler _lifecycleEventHandler;
 
@@ -60,16 +63,6 @@ class _TwoFactorAuthenticationPageState
 
   Widget _getBody() {
     final l10n = context.l10n;
-    final pinPutDecoration = BoxDecoration(
-      border: Border.all(
-        color: Theme.of(context)
-            .inputDecorationTheme
-            .focusedBorder!
-            .borderSide
-            .color,
-      ),
-      borderRadius: BorderRadius.circular(15.0),
-    );
     return Column(
       crossAxisAlignment: CrossAxisAlignment.stretch,
       mainAxisAlignment: MainAxisAlignment.center,
@@ -86,31 +79,32 @@ class _TwoFactorAuthenticationPageState
         const Padding(padding: EdgeInsets.all(32)),
         Padding(
           padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
-          child: Pinput(
-            onSubmitted: (String code) {
+          child: PinPut(
+            fieldsCount: 6,
+            onSubmit: (String code) {
               _verifyTwoFactorCode(code);
             },
-            length: 6,
-            defaultPinTheme: const PinTheme(),
-            submittedPinTheme: PinTheme(
-              decoration: pinPutDecoration.copyWith(
-                borderRadius: BorderRadius.circular(20.0),
-              ),
-            ),
-            focusedPinTheme: PinTheme(
-              decoration: pinPutDecoration,
-            ),
-            followingPinTheme: PinTheme(
-              decoration: pinPutDecoration.copyWith(
-                borderRadius: BorderRadius.circular(5.0),
-              ),
-            ),
             onChanged: (String pin) {
               setState(() {
                 _code = pin;
               });
             },
             controller: _pinController,
+            submittedFieldDecoration: _pinPutDecoration.copyWith(
+              borderRadius: BorderRadius.circular(20.0),
+            ),
+            selectedFieldDecoration: _pinPutDecoration,
+            followingFieldDecoration: _pinPutDecoration.copyWith(
+              borderRadius: BorderRadius.circular(5.0),
+              border: Border.all(
+                color: const Color.fromRGBO(45, 194, 98, 0.5),
+              ),
+            ),
+            inputDecoration: const InputDecoration(
+              focusedBorder: InputBorder.none,
+              border: InputBorder.none,
+              counterText: '',
+            ),
             autofocus: true,
           ),
         ),

+ 0 - 4
auth/linux/flutter/generated_plugin_registrant.cc

@@ -13,7 +13,6 @@
 #include <gtk/gtk_plugin.h>
 #include <screen_retriever/screen_retriever_plugin.h>
 #include <sentry_flutter/sentry_flutter_plugin.h>
-#include <smart_auth/smart_auth_plugin.h>
 #include <sodium_libs/sodium_libs_plugin.h>
 #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
 #include <tray_manager/tray_manager_plugin.h>
@@ -42,9 +41,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
   g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
       fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
   sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
-  g_autoptr(FlPluginRegistrar) smart_auth_registrar =
-      fl_plugin_registry_get_registrar_for_plugin(registry, "SmartAuthPlugin");
-  smart_auth_plugin_register_with_registrar(smart_auth_registrar);
   g_autoptr(FlPluginRegistrar) sodium_libs_registrar =
       fl_plugin_registry_get_registrar_for_plugin(registry, "SodiumLibsPlugin");
   sodium_libs_plugin_register_with_registrar(sodium_libs_registrar);

+ 0 - 1
auth/linux/flutter/generated_plugins.cmake

@@ -10,7 +10,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
   gtk
   screen_retriever
   sentry_flutter
-  smart_auth
   sodium_libs
   sqlite3_flutter_libs
   tray_manager

+ 1 - 0
auth/linux/packaging/appimage/make_config.yaml

@@ -25,3 +25,4 @@ startup_notify: false
 #   - libcurl.so.4
 include:
     - libffi.so.7
+    - libtiff.so.5

+ 1 - 1
auth/linux/packaging/rpm/make_config.yaml

@@ -9,7 +9,7 @@ url: https://github.com/ente-io/ente
 
 display_name: Auth
 
-dependencies:
+requires:
   - libsqlite3x
   - webkit2gtk-4.0
   - libsodium

+ 0 - 2
auth/macos/Flutter/GeneratedPluginRegistrant.swift

@@ -20,7 +20,6 @@ import screen_retriever
 import sentry_flutter
 import share_plus
 import shared_preferences_foundation
-import smart_auth
 import sodium_libs
 import sqflite
 import sqlite3_flutter_libs
@@ -44,7 +43,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
   SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
   SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
   SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
-  SmartAuthPlugin.register(with: registry.registrar(forPlugin: "SmartAuthPlugin"))
   SodiumLibsPlugin.register(with: registry.registrar(forPlugin: "SodiumLibsPlugin"))
   SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
   Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))

+ 2 - 18
auth/pubspec.lock

@@ -1109,10 +1109,10 @@ packages:
     dependency: "direct main"
     description:
       name: pinput
-      sha256: a92b55ecf9c25d1b9e100af45905385d5bc34fc9b6b04177a9e82cb88fe4d805
+      sha256: "27eb69042f75755bdb6544f6e79a50a6ed09d6e97e2d75c8421744df1e392949"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.1"
+    version: "1.2.2"
   platform:
     dependency: transitive
     description:
@@ -1334,14 +1334,6 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.99"
-  smart_auth:
-    dependency: transitive
-    description:
-      name: smart_auth
-      sha256: a25229b38c02f733d0a4e98d941b42bed91a976cb589e934895e60ccfa674cf6
-      url: "https://pub.dev"
-    source: hosted
-    version: "1.1.1"
   sodium:
     dependency: transitive
     description:
@@ -1551,14 +1543,6 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.2.2"
-  universal_platform:
-    dependency: transitive
-    description:
-      name: universal_platform
-      sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc
-      url: "https://pub.dev"
-    source: hosted
-    version: "1.0.0+1"
   url_launcher:
     dependency: "direct main"
     description:

+ 2 - 2
auth/pubspec.yaml

@@ -1,6 +1,6 @@
 name: ente_auth
 description: ente two-factor authenticator
-version: 2.0.50+250
+version: 2.0.55+255
 publish_to: none
 
 environment:
@@ -75,7 +75,7 @@ dependencies:
   password_strength: ^0.2.0
   path: ^1.8.3
   path_provider: ^2.0.11
-  pinput: ^3.0.1
+  pinput: ^1.2.2
   pointycastle: ^3.7.3
   privacy_screen: ^0.0.6
   protobuf: ^3.0.0

+ 0 - 3
auth/windows/flutter/generated_plugin_registrant.cc

@@ -16,7 +16,6 @@
 #include <screen_retriever/screen_retriever_plugin.h>
 #include <sentry_flutter/sentry_flutter_plugin.h>
 #include <share_plus/share_plus_windows_plugin_c_api.h>
-#include <smart_auth/smart_auth_plugin.h>
 #include <sodium_libs/sodium_libs_plugin_c_api.h>
 #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
 #include <tray_manager/tray_manager_plugin.h>
@@ -44,8 +43,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
       registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
   SharePlusWindowsPluginCApiRegisterWithRegistrar(
       registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
-  SmartAuthPluginRegisterWithRegistrar(
-      registry->GetRegistrarForPlugin("SmartAuthPlugin"));
   SodiumLibsPluginCApiRegisterWithRegistrar(
       registry->GetRegistrarForPlugin("SodiumLibsPluginCApi"));
   Sqlite3FlutterLibsPluginRegisterWithRegistrar(

+ 0 - 1
auth/windows/flutter/generated_plugins.cmake

@@ -13,7 +13,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
   screen_retriever
   sentry_flutter
   share_plus
-  smart_auth
   sodium_libs
   sqlite3_flutter_libs
   tray_manager

+ 0 - 5
desktop/.eslintrc.js

@@ -7,11 +7,6 @@ module.exports = {
         // "plugin:@typescript-eslint/strict-type-checked",
         // "plugin:@typescript-eslint/stylistic-type-checked",
     ],
-    /* Temporarily disable some rules
-       Enhancement: Remove me */
-    rules: {
-        "no-unused-vars": "off",
-    },
     /* Temporarily add a global
        Enhancement: Remove me */
     globals: {

BIN
desktop/build/ggmlclip-linux


BIN
desktop/build/ggmlclip-mac


BIN
desktop/build/ggmlclip-windows.exe


BIN
desktop/build/msvcp140d.dll


BIN
desktop/build/ucrtbased.dll


BIN
desktop/build/vcruntime140_1d.dll


BIN
desktop/build/vcruntime140d.dll


+ 17 - 17
desktop/docs/dependencies.md

@@ -61,15 +61,15 @@ Electron process. This allows us to directly use the output produced by
 
 ### Others
 
-* [any-shell-escape](https://github.com/boazy/any-shell-escape) is for
-  escaping shell commands before we execute them (e.g. say when invoking the
-  embedded ffmpeg CLI).
+-   [any-shell-escape](https://github.com/boazy/any-shell-escape) is for
+    escaping shell commands before we execute them (e.g. say when invoking the
+    embedded ffmpeg CLI).
 
-* [auto-launch](https://github.com/Teamwork/node-auto-launch) is for
-  automatically starting our app on login, if the user so wishes.
+-   [auto-launch](https://github.com/Teamwork/node-auto-launch) is for
+    automatically starting our app on login, if the user so wishes.
 
-* [electron-store](https://github.com/sindresorhus/electron-store) is used for
-  persisting user preferences and other arbitrary data.
+-   [electron-store](https://github.com/sindresorhus/electron-store) is used for
+    persisting user preferences and other arbitrary data.
 
 ## Dev
 
@@ -79,12 +79,12 @@ are similar to that in the web code.
 
 Some extra ones specific to the code here are:
 
-* [concurrently](https://github.com/open-cli-tools/concurrently) for spawning
-  parallel tasks when we do `yarn dev`.
+-   [concurrently](https://github.com/open-cli-tools/concurrently) for spawning
+    parallel tasks when we do `yarn dev`.
 
-* [shx](https://github.com/shelljs/shx) for providing a portable way to use Unix
-  commands in our `package.json` scripts. This allows us to use the same
-  commands (like `ln`) across different platforms like Linux and Windows.
+-   [shx](https://github.com/shelljs/shx) for providing a portable way to use
+    Unix commands in our `package.json` scripts. This allows us to use the same
+    commands (like `ln`) across different platforms like Linux and Windows.
 
 ## Functionality
 
@@ -111,11 +111,11 @@ watcher for the watch folders functionality.
 
 ### AI/ML
 
-* [onnxruntime-node](https://github.com/Microsoft/onnxruntime)
-* html-entities is used by the bundled clip-bpe-ts.
-* GGML binaries are bundled
-* We also use [jpeg-js](https://github.com/jpeg-js/jpeg-js#readme) for
-  conversion of all images to JPEG before processing.
+-   [onnxruntime-node](https://github.com/Microsoft/onnxruntime) is used for
+    natural language searches based on CLIP.
+-   html-entities is used by the bundled clip-bpe-ts tokenizer.
+-   [jpeg-js](https://github.com/jpeg-js/jpeg-js#readme) is used for decoding
+    JPEG data into raw RGB bytes before passing it to ONNX.
 
 ## ZIP
 

+ 0 - 1
desktop/electron-builder.yml

@@ -19,7 +19,6 @@ mac:
         arch: [universal]
     category: public.app-category.photography
     hardenedRuntime: true
-    x64ArchFiles: Contents/Resources/ggmlclip-mac
 afterSign: electron-builder-notarize
 extraFiles:
     - from: build

+ 0 - 17
desktop/src/api/electronStore.ts

@@ -1,17 +0,0 @@
-import { logError } from "../main/log";
-import { keysStore } from "../stores/keys.store";
-import { safeStorageStore } from "../stores/safeStorage.store";
-import { uploadStatusStore } from "../stores/upload.store";
-import { watchStore } from "../stores/watch.store";
-
-export const clearElectronStore = () => {
-    try {
-        uploadStatusStore.clear();
-        keysStore.clear();
-        safeStorageStore.clear();
-        watchStore.clear();
-    } catch (e) {
-        logError(e, "error while clearing electron store");
-        throw e;
-    }
-};

+ 0 - 28
desktop/src/api/safeStorage.ts

@@ -1,28 +0,0 @@
-import { safeStorage } from "electron/main";
-import { logError } from "../main/log";
-import { safeStorageStore } from "../stores/safeStorage.store";
-
-export async function setEncryptionKey(encryptionKey: string) {
-    try {
-        const encryptedKey: Buffer =
-            await safeStorage.encryptString(encryptionKey);
-        const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64");
-        safeStorageStore.set("encryptionKey", b64EncryptedKey);
-    } catch (e) {
-        logError(e, "setEncryptionKey failed");
-        throw e;
-    }
-}
-
-export async function getEncryptionKey(): Promise<string> {
-    try {
-        const b64EncryptedKey = safeStorageStore.get("encryptionKey");
-        if (b64EncryptedKey) {
-            const keyBuffer = Buffer.from(b64EncryptedKey, "base64");
-            return await safeStorage.decryptString(keyBuffer);
-        }
-    } catch (e) {
-        logError(e, "getEncryptionKey failed");
-        throw e;
-    }
-}

+ 0 - 41
desktop/src/api/upload.ts

@@ -1,41 +0,0 @@
-import { getElectronFile } from "../services/fs";
-import {
-    getElectronFilesFromGoogleZip,
-    getSavedFilePaths,
-} from "../services/upload";
-import { uploadStatusStore } from "../stores/upload.store";
-import { ElectronFile, FILE_PATH_TYPE } from "../types/ipc";
-
-export const getPendingUploads = async () => {
-    const filePaths = getSavedFilePaths(FILE_PATH_TYPE.FILES);
-    const zipPaths = getSavedFilePaths(FILE_PATH_TYPE.ZIPS);
-    const collectionName = uploadStatusStore.get("collectionName");
-
-    let files: ElectronFile[] = [];
-    let type: FILE_PATH_TYPE;
-    if (zipPaths.length) {
-        type = FILE_PATH_TYPE.ZIPS;
-        for (const zipPath of zipPaths) {
-            files = [
-                ...files,
-                ...(await getElectronFilesFromGoogleZip(zipPath)),
-            ];
-        }
-        const pendingFilePaths = new Set(filePaths);
-        files = files.filter((file) => pendingFilePaths.has(file.path));
-    } else if (filePaths.length) {
-        type = FILE_PATH_TYPE.FILES;
-        files = await Promise.all(filePaths.map(getElectronFile));
-    }
-    return {
-        files,
-        collectionName,
-        type,
-    };
-};
-
-export {
-    getElectronFilesFromGoogleZip,
-    setToUploadCollection,
-    setToUploadFiles,
-} from "../services/upload";

+ 0 - 26
desktop/src/constants/errors.ts

@@ -1,26 +0,0 @@
-/**
- * [Note: Custom errors across Electron/Renderer boundary]
- *
- * We need to use the `message` field to disambiguate between errors thrown by
- * the main process when invoked from the renderer process. This is because:
- *
- * > Errors thrown throw `handle` in the main process are not transparent as
- * > they are serialized and only the `message` property from the original error
- * > is provided to the renderer process.
- * >
- * > - https://www.electronjs.org/docs/latest/tutorial/ipc
- * >
- * > Ref: https://github.com/electron/electron/issues/24427
- */
-export const CustomErrors = {
-    WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED:
-        "Windows native image processing is not supported",
-    INVALID_OS: (os: string) => `Invalid OS - ${os}`,
-    WAIT_TIME_EXCEEDED: "Wait time exceeded",
-    UNSUPPORTED_PLATFORM: (platform: string, arch: string) =>
-        `Unsupported platform - ${platform} ${arch}`,
-    MODEL_DOWNLOAD_PENDING:
-        "Model download pending, skipping clip search request",
-    INVALID_FILE_PATH: "Invalid file path",
-    INVALID_CLIP_MODEL: (model: string) => `Invalid Clip model - ${model}`,
-};

+ 25 - 10
desktop/src/main.ts

@@ -12,6 +12,7 @@ import { app, BrowserWindow, Menu } from "electron/main";
 import serveNextAt from "next-electron-server";
 import { existsSync } from "node:fs";
 import fs from "node:fs/promises";
+import os from "node:os";
 import path from "node:path";
 import {
     addAllowOriginHeader,
@@ -19,7 +20,6 @@ import {
     handleDockIconHideOnAutoLaunch,
     handleDownloads,
     handleExternalLinks,
-    logStartupBanner,
     setupMacWindowOnDockIconClick,
     setupTrayItem,
 } from "./main/init";
@@ -72,6 +72,21 @@ const setupRendererServer = () => {
     serveNextAt(rendererURL);
 };
 
+/**
+ * Log a standard startup banner.
+ *
+ * This helps us identify app starts and other environment details in the logs.
+ */
+const logStartupBanner = () => {
+    const version = isDev ? "dev" : app.getVersion();
+    log.info(`Starting ente-photos-desktop ${version}`);
+
+    const platform = process.platform;
+    const osRelease = os.release();
+    const systemVersion = process.getSystemVersion();
+    log.info("Running on", { platform, osRelease, systemVersion });
+};
+
 function enableSharedArrayBufferSupport() {
     app.commandLine.appendSwitch("enable-features", "SharedArrayBuffer");
 }
@@ -126,12 +141,12 @@ const deleteLegacyDiskCacheDirIfExists = async () => {
     }
 };
 
-function setupAppEventEmitter(mainWindow: BrowserWindow) {
-    // fire event when mainWindow is in foreground
-    mainWindow.on("focus", () => {
-        mainWindow.webContents.send("app-in-foreground");
-    });
-}
+const attachEventHandlers = (mainWindow: BrowserWindow) => {
+    // Let ipcRenderer know when mainWindow is in the foreground.
+    mainWindow.on("focus", () =>
+        mainWindow.webContents.send("app-in-foreground"),
+    );
+};
 
 const main = () => {
     const gotTheLock = app.requestSingleInstanceLock();
@@ -144,6 +159,7 @@ const main = () => {
 
     initLogging();
     setupRendererServer();
+    logStartupBanner();
     handleDockIconHideOnAutoLaunch();
     increaseDiskCache();
     enableSharedArrayBufferSupport();
@@ -163,7 +179,6 @@ const main = () => {
     //
     // Note that some Electron APIs can only be used after this event occurs.
     app.on("ready", async () => {
-        logStartupBanner();
         mainWindow = await createWindow();
         const watcher = initWatcher(mainWindow);
         setupTrayItem(mainWindow);
@@ -175,13 +190,13 @@ const main = () => {
         handleDownloads(mainWindow);
         handleExternalLinks(mainWindow);
         addAllowOriginHeader(mainWindow);
-        setupAppEventEmitter(mainWindow);
+        attachEventHandlers(mainWindow);
 
         try {
             deleteLegacyDiskCacheDirIfExists();
         } catch (e) {
             // Log but otherwise ignore errors during non-critical startup
-            // actions
+            // actions.
             log.error("Ignoring startup error", e);
         }
     });

+ 0 - 13
desktop/src/main/init.ts

@@ -1,6 +1,5 @@
 import { app, BrowserWindow, nativeImage, Tray } from "electron";
 import { existsSync } from "node:fs";
-import os from "node:os";
 import path from "node:path";
 import { isAppQuitting, rendererURL } from "../main";
 import autoLauncher from "../services/autoLauncher";
@@ -77,8 +76,6 @@ export const createWindow = async () => {
     return mainWindow;
 };
 
-export async function handleUpdates(mainWindow: BrowserWindow) {}
-
 export const setupTrayItem = (mainWindow: BrowserWindow) => {
     const iconName = isPlatform("mac")
         ? "taskbar-icon-Template.png"
@@ -149,16 +146,6 @@ export async function handleDockIconHideOnAutoLaunch() {
     }
 }
 
-export function logStartupBanner() {
-    const version = isDev ? "dev" : app.getVersion();
-    log.info(`Hello from ente-photos-desktop ${version}`);
-
-    const platform = process.platform;
-    const osRelease = os.release();
-    const systemVersion = process.getSystemVersion();
-    log.info("Running on", { platform, osRelease, systemVersion });
-}
-
 function lowerCaseHeaders(responseHeaders: Record<string, string[]>) {
     const headers: Record<string, string[]> = {};
     for (const key of Object.keys(responseHeaders)) {

+ 33 - 37
desktop/src/main/ipc.ts

@@ -10,14 +10,6 @@
 
 import type { FSWatcher } from "chokidar";
 import { ipcMain } from "electron/main";
-import { clearElectronStore } from "../api/electronStore";
-import { getEncryptionKey, setEncryptionKey } from "../api/safeStorage";
-import {
-    getElectronFilesFromGoogleZip,
-    getPendingUploads,
-    setToUploadCollection,
-    setToUploadFiles,
-} from "../api/upload";
 import {
     appVersion,
     muteUpdateNotification,
@@ -25,15 +17,26 @@ import {
     updateAndRestart,
 } from "../services/appUpdater";
 import {
-    computeImageEmbedding,
-    computeTextEmbedding,
-} from "../services/clipService";
+    clipImageEmbedding,
+    clipTextEmbedding,
+} from "../services/clip-service";
 import { runFFmpegCmd } from "../services/ffmpeg";
 import { getDirFiles } from "../services/fs";
 import {
     convertToJPEG,
     generateImageThumbnail,
 } from "../services/imageProcessor";
+import {
+    clearElectronStore,
+    getEncryptionKey,
+    setEncryptionKey,
+} from "../services/store";
+import {
+    getElectronFilesFromGoogleZip,
+    getPendingUploads,
+    setToUploadCollection,
+    setToUploadFiles,
+} from "../services/upload";
 import {
     addWatchMapping,
     getWatchMappings,
@@ -41,12 +44,7 @@ import {
     updateWatchMappingIgnoredFiles,
     updateWatchMappingSyncedFiles,
 } from "../services/watch";
-import type {
-    ElectronFile,
-    FILE_PATH_TYPE,
-    Model,
-    WatchMapping,
-} from "../types/ipc";
+import type { ElectronFile, FILE_PATH_TYPE, WatchMapping } from "../types/ipc";
 import {
     selectDirectory,
     showUploadDirsDialog,
@@ -91,16 +89,16 @@ export const attachIPCHandlers = () => {
 
     // - General
 
-    ipcMain.handle("appVersion", (_) => appVersion());
+    ipcMain.handle("appVersion", () => appVersion());
 
     ipcMain.handle("openDirectory", (_, dirPath) => openDirectory(dirPath));
 
-    ipcMain.handle("openLogDirectory", (_) => openLogDirectory());
+    ipcMain.handle("openLogDirectory", () => openLogDirectory());
 
     // See [Note: Catching exception during .send/.on]
     ipcMain.on("logToDisk", (_, message) => logToDisk(message));
 
-    ipcMain.on("clear-electron-store", (_) => {
+    ipcMain.on("clear-electron-store", () => {
         clearElectronStore();
     });
 
@@ -108,11 +106,11 @@ export const attachIPCHandlers = () => {
         setEncryptionKey(encryptionKey),
     );
 
-    ipcMain.handle("getEncryptionKey", (_) => getEncryptionKey());
+    ipcMain.handle("getEncryptionKey", () => getEncryptionKey());
 
     // - App update
 
-    ipcMain.on("update-and-restart", (_) => updateAndRestart());
+    ipcMain.on("update-and-restart", () => updateAndRestart());
 
     ipcMain.on("skip-app-update", (_, version) => skipAppUpdate(version));
 
@@ -145,25 +143,23 @@ export const attachIPCHandlers = () => {
 
     // - ML
 
-    ipcMain.handle(
-        "computeImageEmbedding",
-        (_, model: Model, imageData: Uint8Array) =>
-            computeImageEmbedding(model, imageData),
+    ipcMain.handle("clipImageEmbedding", (_, jpegImageData: Uint8Array) =>
+        clipImageEmbedding(jpegImageData),
     );
 
-    ipcMain.handle("computeTextEmbedding", (_, model: Model, text: string) =>
-        computeTextEmbedding(model, text),
+    ipcMain.handle("clipTextEmbedding", (_, text: string) =>
+        clipTextEmbedding(text),
     );
 
     // - File selection
 
-    ipcMain.handle("selectDirectory", (_) => selectDirectory());
+    ipcMain.handle("selectDirectory", () => selectDirectory());
 
-    ipcMain.handle("showUploadFilesDialog", (_) => showUploadFilesDialog());
+    ipcMain.handle("showUploadFilesDialog", () => showUploadFilesDialog());
 
-    ipcMain.handle("showUploadDirsDialog", (_) => showUploadDirsDialog());
+    ipcMain.handle("showUploadDirsDialog", () => showUploadDirsDialog());
 
-    ipcMain.handle("showUploadZipDialog", (_) => showUploadZipDialog());
+    ipcMain.handle("showUploadZipDialog", () => showUploadZipDialog());
 
     // - FS
 
@@ -177,12 +173,12 @@ export const attachIPCHandlers = () => {
 
     ipcMain.handle(
         "saveStreamToDisk",
-        (_, path: string, fileStream: ReadableStream<any>) =>
+        (_, path: string, fileStream: ReadableStream) =>
             saveStreamToDisk(path, fileStream),
     );
 
-    ipcMain.handle("saveFileToDisk", (_, path: string, file: any) =>
-        saveFileToDisk(path, file),
+    ipcMain.handle("saveFileToDisk", (_, path: string, contents: string) =>
+        saveFileToDisk(path, contents),
     );
 
     ipcMain.handle("readTextFile", (_, path: string) => readTextFile(path));
@@ -203,7 +199,7 @@ export const attachIPCHandlers = () => {
 
     // - Upload
 
-    ipcMain.handle("getPendingUploads", (_) => getPendingUploads());
+    ipcMain.handle("getPendingUploads", () => getPendingUploads());
 
     ipcMain.handle(
         "setToUploadFiles",
@@ -252,7 +248,7 @@ export const attachFSWatchIPCHandlers = (watcher: FSWatcher) => {
         removeWatchMapping(watcher, folderPath),
     );
 
-    ipcMain.handle("getWatchMappings", (_) => getWatchMappings());
+    ipcMain.handle("getWatchMappings", () => getWatchMappings());
 
     ipcMain.handle(
         "updateWatchMappingSyncedFiles",

+ 15 - 29
desktop/src/main/log.ts

@@ -15,7 +15,7 @@ import { isDev } from "./util";
  */
 export const initLogging = () => {
     log.transports.file.fileName = "ente.log";
-    log.transports.file.maxSize = 50 * 1024 * 1024; // 50MB;
+    log.transports.file.maxSize = 50 * 1024 * 1024; // 50 MB
     log.transports.file.format = "[{y}-{m}-{d}T{h}:{i}:{s}{z}] {text}";
 
     log.transports.console.level = false;
@@ -31,25 +31,7 @@ export const logToDisk = (message: string) => {
     log.info(`[rndr] ${message}`);
 };
 
-export const logError = logErrorSentry;
-
-/** Deprecated, but no alternative yet */
-export function logErrorSentry(
-    error: any,
-    msg: string,
-    info?: Record<string, unknown>,
-) {
-    logToDisk(
-        `error: ${error?.name} ${error?.message} ${
-            error?.stack
-        } msg: ${msg} info: ${JSON.stringify(info)}`,
-    );
-    if (isDev) {
-        console.log(error, { msg, info });
-    }
-}
-
-const logError1 = (message: string, e?: unknown) => {
+const logError = (message: string, e?: unknown) => {
     if (!e) {
         logError_(message);
         return;
@@ -78,11 +60,14 @@ const logInfo = (...params: any[]) => {
         .map((p) => (typeof p == "string" ? p : util.inspect(p)))
         .join(" ");
     log.info(`[main] ${message}`);
-    if (isDev) console.log(message);
+    if (isDev) console.log(`[info] ${message}`);
 };
 
 const logDebug = (param: () => any) => {
-    if (isDev) console.log(`[debug] ${util.inspect(param())}`);
+    if (isDev) {
+        const p = param();
+        console.log(`[debug] ${typeof p == "string" ? p : util.inspect(p)}`);
+    }
 };
 
 /**
@@ -98,12 +83,13 @@ export default {
      * Log an error message with an optional associated error object.
      *
      * {@link e} is generally expected to be an `instanceof Error` but it can be
-     * any arbitrary object that we obtain, say, when in a try-catch handler.
+     * any arbitrary object that we obtain, say, when in a try-catch handler (in
+     * JavaScript any arbitrary value can be thrown).
      *
      * The log is written to disk. In development builds, the log is also
-     * printed to the (Node.js process') console.
+     * printed to the main (Node.js) process console.
      */
-    error: logError1,
+    error: logError,
     /**
      * Log a message.
      *
@@ -111,7 +97,7 @@ export default {
      * arbitrary number of arbitrary parameters that it then serializes.
      *
      * The log is written to disk. In development builds, the log is also
-     * printed to the (Node.js process') console.
+     * printed to the main (Node.js) process console.
      */
     info: logInfo,
     /**
@@ -121,11 +107,11 @@ export default {
      * function to call to get the log message instead of directly taking the
      * message. The provided function will only be called in development builds.
      *
-     * The function can return an arbitrary value which is serialied before
+     * The function can return an arbitrary value which is serialized before
      * being logged.
      *
-     * This log is not written to disk. It is printed to the (Node.js process')
-     * console only on development builds.
+     * This log is NOT written to disk. And it is printed to the main (Node.js)
+     * process console, but only on development builds.
      */
     debug: logDebug,
 };

+ 16 - 24
desktop/src/preload.ts

@@ -1,3 +1,4 @@
+/* eslint-disable no-unused-vars */
 /**
  * @file The preload script
  *
@@ -31,9 +32,9 @@
  * and when changing one of them, remember to see if the other two also need
  * changing:
  *
- * -    [renderer]  web/packages/shared/electron/types.ts     contains docs
- * -    [preload]   desktop/src/preload.ts                          ↕︎
- * -    [main]      desktop/src/main/ipc.ts                   contains impl
+ * -    [renderer]  web/packages/next/types/electron.ts      contains docs
+ * -    [preload]   desktop/src/preload.ts                         ↕︎
+ * -    [main]      desktop/src/main/ipc.ts                  contains impl
  */
 
 import { contextBridge, ipcRenderer } from "electron/renderer";
@@ -44,7 +45,6 @@ import type {
     AppUpdateInfo,
     ElectronFile,
     FILE_PATH_TYPE,
-    Model,
     WatchMapping,
 } from "./types/ipc";
 
@@ -53,7 +53,7 @@ import type {
 const appVersion = (): Promise<string> => ipcRenderer.invoke("appVersion");
 
 const openDirectory = (dirPath: string): Promise<void> =>
-    ipcRenderer.invoke("openDirectory");
+    ipcRenderer.invoke("openDirectory", dirPath);
 
 const openLogDirectory = (): Promise<void> =>
     ipcRenderer.invoke("openLogDirectory");
@@ -68,9 +68,7 @@ const fsExists = (path: string): Promise<boolean> =>
 
 const registerForegroundEventListener = (onForeground: () => void) => {
     ipcRenderer.removeAllListeners("app-in-foreground");
-    ipcRenderer.on("app-in-foreground", () => {
-        onForeground();
-    });
+    ipcRenderer.on("app-in-foreground", onForeground);
 };
 
 const clearElectronStore = () => {
@@ -142,17 +140,11 @@ const runFFmpegCmd = (
 
 // - ML
 
-const computeImageEmbedding = (
-    model: Model,
-    imageData: Uint8Array,
-): Promise<Float32Array> =>
-    ipcRenderer.invoke("computeImageEmbedding", model, imageData);
+const clipImageEmbedding = (jpegImageData: Uint8Array): Promise<Float32Array> =>
+    ipcRenderer.invoke("clipImageEmbedding", jpegImageData);
 
-const computeTextEmbedding = (
-    model: Model,
-    text: string,
-): Promise<Float32Array> =>
-    ipcRenderer.invoke("computeTextEmbedding", model, text);
+const clipTextEmbedding = (text: string): Promise<Float32Array> =>
+    ipcRenderer.invoke("clipTextEmbedding", text);
 
 // - File selection
 
@@ -228,11 +220,11 @@ const checkExistsAndCreateDir = (dirPath: string): Promise<void> =>
 
 const saveStreamToDisk = (
     path: string,
-    fileStream: ReadableStream<any>,
+    fileStream: ReadableStream,
 ): Promise<void> => ipcRenderer.invoke("saveStreamToDisk", path, fileStream);
 
-const saveFileToDisk = (path: string, file: any): Promise<void> =>
-    ipcRenderer.invoke("saveFileToDisk", path, file);
+const saveFileToDisk = (path: string, contents: string): Promise<void> =>
+    ipcRenderer.invoke("saveFileToDisk", path, contents);
 
 const readTextFile = (path: string): Promise<string> =>
     ipcRenderer.invoke("readTextFile", path);
@@ -308,7 +300,7 @@ const getDirFiles = (dirPath: string): Promise<ElectronFile[]> =>
 //
 // The copy itself is relatively fast, but the problem with transfering large
 // amounts of data is potentially running out of memory during the copy.
-contextBridge.exposeInMainWorld("ElectronAPIs", {
+contextBridge.exposeInMainWorld("electron", {
     // - General
     appVersion,
     openDirectory,
@@ -333,8 +325,8 @@ contextBridge.exposeInMainWorld("ElectronAPIs", {
     runFFmpegCmd,
 
     // - ML
-    computeImageEmbedding,
-    computeTextEmbedding,
+    clipImageEmbedding,
+    clipTextEmbedding,
 
     // - File selection
     selectDirectory,

+ 19 - 32
desktop/src/services/appUpdater.ts

@@ -1,9 +1,9 @@
 import { compareVersions } from "compare-versions";
 import { app, BrowserWindow } from "electron";
-import { default as ElectronLog, default as log } from "electron-log";
+import { default as electronLog } from "electron-log";
 import { autoUpdater } from "electron-updater";
 import { setIsAppQuitting, setIsUpdateAvailable } from "../main";
-import { logErrorSentry } from "../main/log";
+import log from "../main/log";
 import { AppUpdateInfo } from "../types/ipc";
 import {
     clearMuteUpdateNotificationVersion,
@@ -18,7 +18,7 @@ const FIVE_MIN_IN_MICROSECOND = 5 * 60 * 1000;
 const ONE_DAY_IN_MICROSECOND = 1 * 24 * 60 * 60 * 1000;
 
 export function setupAutoUpdater(mainWindow: BrowserWindow) {
-    autoUpdater.logger = log;
+    autoUpdater.logger = electronLog;
     autoUpdater.autoDownload = false;
     checkForUpdateAndNotify(mainWindow);
     setInterval(
@@ -33,49 +33,36 @@ export function forceCheckForUpdateAndNotify(mainWindow: BrowserWindow) {
         clearMuteUpdateNotificationVersion();
         checkForUpdateAndNotify(mainWindow);
     } catch (e) {
-        logErrorSentry(e, "forceCheckForUpdateAndNotify failed");
+        log.error("forceCheckForUpdateAndNotify failed", e);
     }
 }
 
 async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
     try {
-        log.debug("checkForUpdateAndNotify called");
-        const updateCheckResult = await autoUpdater.checkForUpdates();
-        log.debug("update version", updateCheckResult.updateInfo.version);
-        if (
-            compareVersions(
-                updateCheckResult.updateInfo.version,
-                app.getVersion(),
-            ) <= 0
-        ) {
-            log.debug("already at latest version");
+        log.debug(() => "checkForUpdateAndNotify");
+        const { updateInfo } = await autoUpdater.checkForUpdates();
+        log.debug(() => `Update version ${updateInfo.version}`);
+        if (compareVersions(updateInfo.version, app.getVersion()) <= 0) {
+            log.debug(() => "Skipping update, already at latest version");
             return;
         }
         const skipAppVersion = getSkipAppVersion();
-        if (
-            skipAppVersion &&
-            updateCheckResult.updateInfo.version === skipAppVersion
-        ) {
-            log.info(
-                "user chose to skip version ",
-                updateCheckResult.updateInfo.version,
-            );
+        if (skipAppVersion && updateInfo.version === skipAppVersion) {
+            log.info(`User chose to skip version ${updateInfo.version}`);
             return;
         }
 
         let timeout: NodeJS.Timeout;
-        log.debug("attempting auto update");
+        log.debug(() => "Attempting auto update");
         autoUpdater.downloadUpdate();
         const muteUpdateNotificationVersion =
             getMuteUpdateNotificationVersion();
         if (
             muteUpdateNotificationVersion &&
-            updateCheckResult.updateInfo.version ===
-                muteUpdateNotificationVersion
+            updateInfo.version === muteUpdateNotificationVersion
         ) {
             log.info(
-                "user chose to mute update notification for version ",
-                updateCheckResult.updateInfo.version,
+                `User has muted update notifications for version ${updateInfo.version}`,
             );
             return;
         }
@@ -84,28 +71,28 @@ async function checkForUpdateAndNotify(mainWindow: BrowserWindow) {
                 () =>
                     showUpdateDialog(mainWindow, {
                         autoUpdatable: true,
-                        version: updateCheckResult.updateInfo.version,
+                        version: updateInfo.version,
                     }),
                 FIVE_MIN_IN_MICROSECOND,
             );
         });
         autoUpdater.on("error", (error) => {
             clearTimeout(timeout);
-            logErrorSentry(error, "auto update failed");
+            log.error("Auto update failed", error);
             showUpdateDialog(mainWindow, {
                 autoUpdatable: false,
-                version: updateCheckResult.updateInfo.version,
+                version: updateInfo.version,
             });
         });
 
         setIsUpdateAvailable(true);
     } catch (e) {
-        logErrorSentry(e, "checkForUpdateAndNotify failed");
+        log.error("checkForUpdateAndNotify failed", e);
     }
 }
 
 export function updateAndRestart() {
-    ElectronLog.log("user quit the app");
+    log.info("user quit the app");
     setIsAppQuitting(true);
     autoUpdater.quitAndInstall();
 }

+ 2 - 2
desktop/src/services/chokidar.ts

@@ -1,7 +1,7 @@
 import chokidar from "chokidar";
 import { BrowserWindow } from "electron";
 import path from "path";
-import { logError } from "../main/log";
+import log from "../main/log";
 import { getWatchMappings } from "../services/watch";
 import { getElectronFile } from "./fs";
 
@@ -38,7 +38,7 @@ export function initWatcher(mainWindow: BrowserWindow) {
             );
         })
         .on("error", (error) => {
-            logError(error, "error while watching files");
+            log.error("Error while watching files", error);
         });
 
     return watcher;

+ 288 - 0
desktop/src/services/clip-service.ts

@@ -0,0 +1,288 @@
+/**
+ * @file Compute CLIP embeddings
+ *
+ * @see `web/apps/photos/src/services/clip-service.ts` for more details. This
+ * file implements the Node.js implementation of the actual embedding
+ * computation. By doing it in the Node.js layer, we can use the binary ONNX
+ * runtimes which are 10-20x faster than the WASM based web ones.
+ *
+ * The embeddings are computed using ONNX runtime. The model itself is not
+ * shipped with the app but is downloaded on demand.
+ */
+import { app, net } from "electron/main";
+import { existsSync } from "fs";
+import fs from "node:fs/promises";
+import path from "node:path";
+import { writeStream } from "../main/fs";
+import log from "../main/log";
+import { CustomErrors } from "../types/ipc";
+import Tokenizer from "../utils/clip-bpe-ts/mod";
+import { generateTempFilePath } from "../utils/temp";
+import { deleteTempFile } from "./ffmpeg";
+const jpeg = require("jpeg-js");
+const ort = require("onnxruntime-node");
+
+const textModelName = "clip-text-vit-32-uint8.onnx";
+const textModelByteSize = 64173509; // 61.2 MB
+
+const imageModelName = "clip-image-vit-32-float32.onnx";
+const imageModelByteSize = 351468764; // 335.2 MB
+
+/** Return the path where the given {@link modelName} is meant to be saved */
+const modelSavePath = (modelName: string) =>
+    path.join(app.getPath("userData"), "models", modelName);
+
+const downloadModel = async (saveLocation: string, name: string) => {
+    // `mkdir -p` the directory where we want to save the model.
+    const saveDir = path.dirname(saveLocation);
+    await fs.mkdir(saveDir, { recursive: true });
+    // Download
+    log.info(`Downloading CLIP model from ${name}`);
+    const url = `https://models.ente.io/${name}`;
+    const res = await net.fetch(url);
+    if (!res.ok) throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`);
+    // Save
+    await writeStream(saveLocation, res.body);
+    log.info(`Downloaded CLIP model ${name}`);
+};
+
+let activeImageModelDownload: Promise<void> | undefined;
+
+const imageModelPathDownloadingIfNeeded = async () => {
+    try {
+        const modelPath = modelSavePath(imageModelName);
+        if (activeImageModelDownload) {
+            log.info("Waiting for CLIP image model download to finish");
+            await activeImageModelDownload;
+        } else {
+            if (!existsSync(modelPath)) {
+                log.info("CLIP image model not found, downloading");
+                activeImageModelDownload = downloadModel(
+                    modelPath,
+                    imageModelName,
+                );
+                await activeImageModelDownload;
+            } else {
+                const localFileSize = (await fs.stat(modelPath)).size;
+                if (localFileSize !== imageModelByteSize) {
+                    log.error(
+                        `CLIP image model size ${localFileSize} does not match the expected size, downloading again`,
+                    );
+                    activeImageModelDownload = downloadModel(
+                        modelPath,
+                        imageModelName,
+                    );
+                    await activeImageModelDownload;
+                }
+            }
+        }
+        return modelPath;
+    } finally {
+        activeImageModelDownload = undefined;
+    }
+};
+
+let textModelDownloadInProgress = false;
+
+const textModelPathDownloadingIfNeeded = async () => {
+    if (textModelDownloadInProgress)
+        throw Error(CustomErrors.MODEL_DOWNLOAD_PENDING);
+
+    const modelPath = modelSavePath(textModelName);
+    if (!existsSync(modelPath)) {
+        log.info("CLIP text model not found, downloading");
+        textModelDownloadInProgress = true;
+        downloadModel(modelPath, textModelName)
+            .catch((e) => {
+                // log but otherwise ignore
+                log.error("CLIP text model download failed", e);
+            })
+            .finally(() => {
+                textModelDownloadInProgress = false;
+            });
+        throw Error(CustomErrors.MODEL_DOWNLOAD_PENDING);
+    } else {
+        const localFileSize = (await fs.stat(modelPath)).size;
+        if (localFileSize !== textModelByteSize) {
+            log.error(
+                `CLIP text model size ${localFileSize} does not match the expected size, downloading again`,
+            );
+            textModelDownloadInProgress = true;
+            downloadModel(modelPath, textModelName)
+                .catch((e) => {
+                    // log but otherwise ignore
+                    log.error("CLIP text model download failed", e);
+                })
+                .finally(() => {
+                    textModelDownloadInProgress = false;
+                });
+            throw Error(CustomErrors.MODEL_DOWNLOAD_PENDING);
+        }
+    }
+
+    return modelPath;
+};
+
+const createInferenceSession = async (modelPath: string) => {
+    return await ort.InferenceSession.create(modelPath, {
+        intraOpNumThreads: 1,
+        enableCpuMemArena: false,
+    });
+};
+
+let imageSessionPromise: Promise<any> | undefined;
+
+const onnxImageSession = async () => {
+    if (!imageSessionPromise) {
+        imageSessionPromise = (async () => {
+            const modelPath = await imageModelPathDownloadingIfNeeded();
+            return createInferenceSession(modelPath);
+        })();
+    }
+    return imageSessionPromise;
+};
+
+let _textSession: any = null;
+
+const onnxTextSession = async () => {
+    if (!_textSession) {
+        const modelPath = await textModelPathDownloadingIfNeeded();
+        _textSession = await createInferenceSession(modelPath);
+    }
+    return _textSession;
+};
+
+export const clipImageEmbedding = async (jpegImageData: Uint8Array) => {
+    const tempFilePath = await generateTempFilePath("");
+    const imageStream = new Response(jpegImageData.buffer).body;
+    await writeStream(tempFilePath, imageStream);
+    try {
+        return await clipImageEmbedding_(tempFilePath);
+    } finally {
+        await deleteTempFile(tempFilePath);
+    }
+};
+
+const clipImageEmbedding_ = async (jpegFilePath: string) => {
+    const imageSession = await onnxImageSession();
+    const t1 = Date.now();
+    const rgbData = await getRGBData(jpegFilePath);
+    const feeds = {
+        input: new ort.Tensor("float32", rgbData, [1, 3, 224, 224]),
+    };
+    const t2 = Date.now();
+    const results = await imageSession.run(feeds);
+    log.debug(
+        () =>
+            `CLIP image embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`,
+    );
+    const imageEmbedding = results["output"].data; // Float32Array
+    return normalizeEmbedding(imageEmbedding);
+};
+
+const getRGBData = async (jpegFilePath: string) => {
+    const jpegData = await fs.readFile(jpegFilePath);
+    const rawImageData = jpeg.decode(jpegData, {
+        useTArray: true,
+        formatAsRGBA: false,
+    });
+
+    const nx: number = rawImageData.width;
+    const ny: number = rawImageData.height;
+    const inputImage: Uint8Array = rawImageData.data;
+
+    const nx2: number = 224;
+    const ny2: number = 224;
+    const totalSize: number = 3 * nx2 * ny2;
+
+    const result: number[] = Array(totalSize).fill(0);
+    const scale: number = Math.max(nx, ny) / 224;
+
+    const nx3: number = Math.round(nx / scale);
+    const ny3: number = Math.round(ny / scale);
+
+    const mean: number[] = [0.48145466, 0.4578275, 0.40821073];
+    const std: number[] = [0.26862954, 0.26130258, 0.27577711];
+
+    for (let y = 0; y < ny3; y++) {
+        for (let x = 0; x < nx3; x++) {
+            for (let c = 0; c < 3; c++) {
+                // Linear interpolation
+                const sx: number = (x + 0.5) * scale - 0.5;
+                const sy: number = (y + 0.5) * scale - 0.5;
+
+                const x0: number = Math.max(0, Math.floor(sx));
+                const y0: number = Math.max(0, Math.floor(sy));
+
+                const x1: number = Math.min(x0 + 1, nx - 1);
+                const y1: number = Math.min(y0 + 1, ny - 1);
+
+                const dx: number = sx - x0;
+                const dy: number = sy - y0;
+
+                const j00: number = 3 * (y0 * nx + x0) + c;
+                const j01: number = 3 * (y0 * nx + x1) + c;
+                const j10: number = 3 * (y1 * nx + x0) + c;
+                const j11: number = 3 * (y1 * nx + x1) + c;
+
+                const v00: number = inputImage[j00];
+                const v01: number = inputImage[j01];
+                const v10: number = inputImage[j10];
+                const v11: number = inputImage[j11];
+
+                const v0: number = v00 * (1 - dx) + v01 * dx;
+                const v1: number = v10 * (1 - dx) + v11 * dx;
+
+                const v: number = v0 * (1 - dy) + v1 * dy;
+
+                const v2: number = Math.min(Math.max(Math.round(v), 0), 255);
+
+                // createTensorWithDataList is dumb compared to reshape and
+                // hence has to be given with one channel after another
+                const i: number = y * nx3 + x + (c % 3) * 224 * 224;
+
+                result[i] = (v2 / 255 - mean[c]) / std[c];
+            }
+        }
+    }
+
+    return result;
+};
+
+const normalizeEmbedding = (embedding: Float32Array) => {
+    let normalization = 0;
+    for (let index = 0; index < embedding.length; index++) {
+        normalization += embedding[index] * embedding[index];
+    }
+    const sqrtNormalization = Math.sqrt(normalization);
+    for (let index = 0; index < embedding.length; index++) {
+        embedding[index] = embedding[index] / sqrtNormalization;
+    }
+    return embedding;
+};
+
+let _tokenizer: Tokenizer = null;
+const getTokenizer = () => {
+    if (!_tokenizer) {
+        _tokenizer = new Tokenizer();
+    }
+    return _tokenizer;
+};
+
+export const clipTextEmbedding = async (text: string) => {
+    const imageSession = await onnxTextSession();
+    const t1 = Date.now();
+    const tokenizer = getTokenizer();
+    const tokenizedText = Int32Array.from(tokenizer.encodeForCLIP(text));
+    const feeds = {
+        input: new ort.Tensor("int32", tokenizedText, [1, 77]),
+    };
+    const t2 = Date.now();
+    const results = await imageSession.run(feeds);
+    log.debug(
+        () =>
+            `CLIP text embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`,
+    );
+    const textEmbedding = results["output"].data;
+    return normalizeEmbedding(textEmbedding);
+};

+ 0 - 506
desktop/src/services/clipService.ts

@@ -1,506 +0,0 @@
-import { app, net } from "electron/main";
-import { existsSync } from "fs";
-import fs from "node:fs/promises";
-import path from "node:path";
-import { CustomErrors } from "../constants/errors";
-import { writeStream } from "../main/fs";
-import log, { logErrorSentry } from "../main/log";
-import { execAsync, isDev } from "../main/util";
-import { Model } from "../types/ipc";
-import Tokenizer from "../utils/clip-bpe-ts/mod";
-import { getPlatform } from "../utils/common/platform";
-import { generateTempFilePath } from "../utils/temp";
-import { deleteTempFile } from "./ffmpeg";
-const jpeg = require("jpeg-js");
-
-const CLIP_MODEL_PATH_PLACEHOLDER = "CLIP_MODEL";
-const GGMLCLIP_PATH_PLACEHOLDER = "GGML_PATH";
-const INPUT_PATH_PLACEHOLDER = "INPUT";
-
-const IMAGE_EMBEDDING_EXTRACT_CMD: string[] = [
-    GGMLCLIP_PATH_PLACEHOLDER,
-    "-mv",
-    CLIP_MODEL_PATH_PLACEHOLDER,
-    "--image",
-    INPUT_PATH_PLACEHOLDER,
-];
-
-const TEXT_EMBEDDING_EXTRACT_CMD: string[] = [
-    GGMLCLIP_PATH_PLACEHOLDER,
-    "-mt",
-    CLIP_MODEL_PATH_PLACEHOLDER,
-    "--text",
-    INPUT_PATH_PLACEHOLDER,
-];
-const ort = require("onnxruntime-node");
-
-const TEXT_MODEL_DOWNLOAD_URL = {
-    ggml: "https://models.ente.io/clip-vit-base-patch32_ggml-text-model-f16.gguf",
-    onnx: "https://models.ente.io/clip-text-vit-32-uint8.onnx",
-};
-const IMAGE_MODEL_DOWNLOAD_URL = {
-    ggml: "https://models.ente.io/clip-vit-base-patch32_ggml-vision-model-f16.gguf",
-    onnx: "https://models.ente.io/clip-image-vit-32-float32.onnx",
-};
-
-const TEXT_MODEL_NAME = {
-    ggml: "clip-vit-base-patch32_ggml-text-model-f16.gguf",
-    onnx: "clip-text-vit-32-uint8.onnx",
-};
-const IMAGE_MODEL_NAME = {
-    ggml: "clip-vit-base-patch32_ggml-vision-model-f16.gguf",
-    onnx: "clip-image-vit-32-float32.onnx",
-};
-
-const IMAGE_MODEL_SIZE_IN_BYTES = {
-    ggml: 175957504, // 167.8 MB
-    onnx: 351468764, // 335.2 MB
-};
-const TEXT_MODEL_SIZE_IN_BYTES = {
-    ggml: 127853440, // 121.9 MB,
-    onnx: 64173509, // 61.2 MB
-};
-
-/** Return the path where the given {@link modelName} is meant to be saved */
-const getModelSavePath = (modelName: string) =>
-    path.join(app.getPath("userData"), "models", modelName);
-
-async function downloadModel(saveLocation: string, url: string) {
-    // confirm that the save location exists
-    const saveDir = path.dirname(saveLocation);
-    await fs.mkdir(saveDir, { recursive: true });
-    log.info("downloading clip model");
-    const res = await net.fetch(url);
-    if (!res.ok) throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`);
-    await writeStream(saveLocation, res.body);
-    log.info("clip model downloaded");
-}
-
-let imageModelDownloadInProgress: Promise<void> = null;
-
-export async function getClipImageModelPath(type: "ggml" | "onnx") {
-    try {
-        const modelSavePath = getModelSavePath(IMAGE_MODEL_NAME[type]);
-        if (imageModelDownloadInProgress) {
-            log.info("waiting for image model download to finish");
-            await imageModelDownloadInProgress;
-        } else {
-            if (!existsSync(modelSavePath)) {
-                log.info("clip image model not found, downloading");
-                imageModelDownloadInProgress = downloadModel(
-                    modelSavePath,
-                    IMAGE_MODEL_DOWNLOAD_URL[type],
-                );
-                await imageModelDownloadInProgress;
-            } else {
-                const localFileSize = (await fs.stat(modelSavePath)).size;
-                if (localFileSize !== IMAGE_MODEL_SIZE_IN_BYTES[type]) {
-                    log.info(
-                        `clip image model size mismatch, downloading again got: ${localFileSize}`,
-                    );
-                    imageModelDownloadInProgress = downloadModel(
-                        modelSavePath,
-                        IMAGE_MODEL_DOWNLOAD_URL[type],
-                    );
-                    await imageModelDownloadInProgress;
-                }
-            }
-        }
-        return modelSavePath;
-    } finally {
-        imageModelDownloadInProgress = null;
-    }
-}
-
-let textModelDownloadInProgress: boolean = false;
-
-export async function getClipTextModelPath(type: "ggml" | "onnx") {
-    const modelSavePath = getModelSavePath(TEXT_MODEL_NAME[type]);
-    if (textModelDownloadInProgress) {
-        throw Error(CustomErrors.MODEL_DOWNLOAD_PENDING);
-    } else {
-        if (!existsSync(modelSavePath)) {
-            log.info("clip text model not found, downloading");
-            textModelDownloadInProgress = true;
-            downloadModel(modelSavePath, TEXT_MODEL_DOWNLOAD_URL[type])
-                .catch(() => {
-                    // ignore
-                })
-                .finally(() => {
-                    textModelDownloadInProgress = false;
-                });
-            throw Error(CustomErrors.MODEL_DOWNLOAD_PENDING);
-        } else {
-            const localFileSize = (await fs.stat(modelSavePath)).size;
-            if (localFileSize !== TEXT_MODEL_SIZE_IN_BYTES[type]) {
-                log.info(
-                    `clip text model size mismatch, downloading again got: ${localFileSize}`,
-                );
-                textModelDownloadInProgress = true;
-                downloadModel(modelSavePath, TEXT_MODEL_DOWNLOAD_URL[type])
-                    .catch(() => {
-                        // ignore
-                    })
-                    .finally(() => {
-                        textModelDownloadInProgress = false;
-                    });
-                throw Error(CustomErrors.MODEL_DOWNLOAD_PENDING);
-            }
-        }
-    }
-    return modelSavePath;
-}
-
-function getGGMLClipPath() {
-    return isDev
-        ? path.join("./build", `ggmlclip-${getPlatform()}`)
-        : path.join(process.resourcesPath, `ggmlclip-${getPlatform()}`);
-}
-
-async function createOnnxSession(modelPath: string) {
-    return await ort.InferenceSession.create(modelPath, {
-        intraOpNumThreads: 1,
-        enableCpuMemArena: false,
-    });
-}
-
-let onnxImageSessionPromise: Promise<any> = null;
-
-async function getOnnxImageSession() {
-    if (!onnxImageSessionPromise) {
-        onnxImageSessionPromise = (async () => {
-            const clipModelPath = await getClipImageModelPath("onnx");
-            return createOnnxSession(clipModelPath);
-        })();
-    }
-    return onnxImageSessionPromise;
-}
-
-let onnxTextSession: any = null;
-
-async function getOnnxTextSession() {
-    if (!onnxTextSession) {
-        const clipModelPath = await getClipTextModelPath("onnx");
-        onnxTextSession = await createOnnxSession(clipModelPath);
-    }
-    return onnxTextSession;
-}
-
-let tokenizer: Tokenizer = null;
-function getTokenizer() {
-    if (!tokenizer) {
-        tokenizer = new Tokenizer();
-    }
-    return tokenizer;
-}
-
-export const computeImageEmbedding = async (
-    model: Model,
-    imageData: Uint8Array,
-): Promise<Float32Array> => {
-    let tempInputFilePath = null;
-    try {
-        tempInputFilePath = await generateTempFilePath("");
-        const imageStream = new Response(imageData.buffer).body;
-        await writeStream(tempInputFilePath, imageStream);
-        const embedding = await computeImageEmbedding_(
-            model,
-            tempInputFilePath,
-        );
-        return embedding;
-    } catch (err) {
-        if (isExecError(err)) {
-            const parsedExecError = parseExecError(err);
-            throw Error(parsedExecError);
-        } else {
-            throw err;
-        }
-    } finally {
-        if (tempInputFilePath) {
-            await deleteTempFile(tempInputFilePath);
-        }
-    }
-};
-
-const isExecError = (err: any) => {
-    return err.message.includes("Command failed:");
-};
-
-const parseExecError = (err: any) => {
-    const errMessage = err.message;
-    if (errMessage.includes("Bad CPU type in executable")) {
-        return CustomErrors.UNSUPPORTED_PLATFORM(
-            process.platform,
-            process.arch,
-        );
-    } else {
-        return errMessage;
-    }
-};
-
-async function computeImageEmbedding_(
-    model: Model,
-    inputFilePath: string,
-): Promise<Float32Array> {
-    if (!existsSync(inputFilePath)) {
-        throw Error(CustomErrors.INVALID_FILE_PATH);
-    }
-    if (model === Model.GGML_CLIP) {
-        return await computeGGMLImageEmbedding(inputFilePath);
-    } else if (model === Model.ONNX_CLIP) {
-        return await computeONNXImageEmbedding(inputFilePath);
-    } else {
-        throw Error(CustomErrors.INVALID_CLIP_MODEL(model));
-    }
-}
-
-export async function computeGGMLImageEmbedding(
-    inputFilePath: string,
-): Promise<Float32Array> {
-    try {
-        const clipModelPath = await getClipImageModelPath("ggml");
-        const ggmlclipPath = getGGMLClipPath();
-        const cmd = IMAGE_EMBEDDING_EXTRACT_CMD.map((cmdPart) => {
-            if (cmdPart === GGMLCLIP_PATH_PLACEHOLDER) {
-                return ggmlclipPath;
-            } else if (cmdPart === CLIP_MODEL_PATH_PLACEHOLDER) {
-                return clipModelPath;
-            } else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
-                return inputFilePath;
-            } else {
-                return cmdPart;
-            }
-        });
-
-        const { stdout } = await execAsync(cmd);
-        // parse stdout and return embedding
-        // get the last line of stdout
-        const lines = stdout.split("\n");
-        const lastLine = lines[lines.length - 1];
-        const embedding = JSON.parse(lastLine);
-        const embeddingArray = new Float32Array(embedding);
-        return embeddingArray;
-    } catch (err) {
-        log.error("Failed to compute GGML image embedding", err);
-        throw err;
-    }
-}
-
-export async function computeONNXImageEmbedding(
-    inputFilePath: string,
-): Promise<Float32Array> {
-    try {
-        const imageSession = await getOnnxImageSession();
-        const t1 = Date.now();
-        const rgbData = await getRGBData(inputFilePath);
-        const feeds = {
-            input: new ort.Tensor("float32", rgbData, [1, 3, 224, 224]),
-        };
-        const t2 = Date.now();
-        const results = await imageSession.run(feeds);
-        log.info(
-            `onnx image embedding time: ${Date.now() - t1} ms (prep:${
-                t2 - t1
-            } ms, extraction: ${Date.now() - t2} ms)`,
-        );
-        const imageEmbedding = results["output"].data; // Float32Array
-        return normalizeEmbedding(imageEmbedding);
-    } catch (err) {
-        log.error("Failed to compute ONNX image embedding", err);
-        throw err;
-    }
-}
-
-export async function computeTextEmbedding(
-    model: Model,
-    text: string,
-): Promise<Float32Array> {
-    try {
-        const embedding = computeTextEmbedding_(model, text);
-        return embedding;
-    } catch (err) {
-        if (isExecError(err)) {
-            const parsedExecError = parseExecError(err);
-            throw Error(parsedExecError);
-        } else {
-            throw err;
-        }
-    }
-}
-
-async function computeTextEmbedding_(
-    model: Model,
-    text: string,
-): Promise<Float32Array> {
-    if (model === Model.GGML_CLIP) {
-        return await computeGGMLTextEmbedding(text);
-    } else {
-        return await computeONNXTextEmbedding(text);
-    }
-}
-
-export async function computeGGMLTextEmbedding(
-    text: string,
-): Promise<Float32Array> {
-    try {
-        const clipModelPath = await getClipTextModelPath("ggml");
-        const ggmlclipPath = getGGMLClipPath();
-        const cmd = TEXT_EMBEDDING_EXTRACT_CMD.map((cmdPart) => {
-            if (cmdPart === GGMLCLIP_PATH_PLACEHOLDER) {
-                return ggmlclipPath;
-            } else if (cmdPart === CLIP_MODEL_PATH_PLACEHOLDER) {
-                return clipModelPath;
-            } else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
-                return text;
-            } else {
-                return cmdPart;
-            }
-        });
-
-        const { stdout } = await execAsync(cmd);
-        // parse stdout and return embedding
-        // get the last line of stdout
-        const lines = stdout.split("\n");
-        const lastLine = lines[lines.length - 1];
-        const embedding = JSON.parse(lastLine);
-        const embeddingArray = new Float32Array(embedding);
-        return embeddingArray;
-    } catch (err) {
-        if (err.message === CustomErrors.MODEL_DOWNLOAD_PENDING) {
-            log.info(CustomErrors.MODEL_DOWNLOAD_PENDING);
-        } else {
-            log.error("Failed to compute GGML text embedding", err);
-        }
-        throw err;
-    }
-}
-
-export async function computeONNXTextEmbedding(
-    text: string,
-): Promise<Float32Array> {
-    try {
-        const imageSession = await getOnnxTextSession();
-        const t1 = Date.now();
-        const tokenizer = getTokenizer();
-        const tokenizedText = Int32Array.from(tokenizer.encodeForCLIP(text));
-        const feeds = {
-            input: new ort.Tensor("int32", tokenizedText, [1, 77]),
-        };
-        const t2 = Date.now();
-        const results = await imageSession.run(feeds);
-        log.info(
-            `onnx text embedding time: ${Date.now() - t1} ms (prep:${
-                t2 - t1
-            } ms, extraction: ${Date.now() - t2} ms)`,
-        );
-        const textEmbedding = results["output"].data; // Float32Array
-        return normalizeEmbedding(textEmbedding);
-    } catch (err) {
-        if (err.message === CustomErrors.MODEL_DOWNLOAD_PENDING) {
-            log.info(CustomErrors.MODEL_DOWNLOAD_PENDING);
-        } else {
-            logErrorSentry(err, "Error in computeONNXTextEmbedding");
-        }
-        throw err;
-    }
-}
-
-async function getRGBData(inputFilePath: string) {
-    const jpegData = await fs.readFile(inputFilePath);
-    let rawImageData;
-    try {
-        rawImageData = jpeg.decode(jpegData, {
-            useTArray: true,
-            formatAsRGBA: false,
-        });
-    } catch (err) {
-        logErrorSentry(err, "JPEG decode error");
-        throw err;
-    }
-
-    const nx: number = rawImageData.width;
-    const ny: number = rawImageData.height;
-    const inputImage: Uint8Array = rawImageData.data;
-
-    const nx2: number = 224;
-    const ny2: number = 224;
-    const totalSize: number = 3 * nx2 * ny2;
-
-    const result: number[] = Array(totalSize).fill(0);
-    const scale: number = Math.max(nx, ny) / 224;
-
-    const nx3: number = Math.round(nx / scale);
-    const ny3: number = Math.round(ny / scale);
-
-    const mean: number[] = [0.48145466, 0.4578275, 0.40821073];
-    const std: number[] = [0.26862954, 0.26130258, 0.27577711];
-
-    for (let y = 0; y < ny3; y++) {
-        for (let x = 0; x < nx3; x++) {
-            for (let c = 0; c < 3; c++) {
-                // linear interpolation
-                const sx: number = (x + 0.5) * scale - 0.5;
-                const sy: number = (y + 0.5) * scale - 0.5;
-
-                const x0: number = Math.max(0, Math.floor(sx));
-                const y0: number = Math.max(0, Math.floor(sy));
-
-                const x1: number = Math.min(x0 + 1, nx - 1);
-                const y1: number = Math.min(y0 + 1, ny - 1);
-
-                const dx: number = sx - x0;
-                const dy: number = sy - y0;
-
-                const j00: number = 3 * (y0 * nx + x0) + c;
-                const j01: number = 3 * (y0 * nx + x1) + c;
-                const j10: number = 3 * (y1 * nx + x0) + c;
-                const j11: number = 3 * (y1 * nx + x1) + c;
-
-                const v00: number = inputImage[j00];
-                const v01: number = inputImage[j01];
-                const v10: number = inputImage[j10];
-                const v11: number = inputImage[j11];
-
-                const v0: number = v00 * (1 - dx) + v01 * dx;
-                const v1: number = v10 * (1 - dx) + v11 * dx;
-
-                const v: number = v0 * (1 - dy) + v1 * dy;
-
-                const v2: number = Math.min(Math.max(Math.round(v), 0), 255);
-
-                // createTensorWithDataList is dump compared to reshape and hence has to be given with one channel after another
-                const i: number = y * nx3 + x + (c % 3) * 224 * 224;
-
-                result[i] = (v2 / 255 - mean[c]) / std[c];
-            }
-        }
-    }
-
-    return result;
-}
-
-export const computeClipMatchScore = async (
-    imageEmbedding: Float32Array,
-    textEmbedding: Float32Array,
-) => {
-    if (imageEmbedding.length !== textEmbedding.length) {
-        throw Error("imageEmbedding and textEmbedding length mismatch");
-    }
-    let score = 0;
-    for (let index = 0; index < imageEmbedding.length; index++) {
-        score += imageEmbedding[index] * textEmbedding[index];
-    }
-    return score;
-};
-
-export const normalizeEmbedding = (embedding: Float32Array) => {
-    let normalization = 0;
-    for (let index = 0; index < embedding.length; index++) {
-        normalization += embedding[index] * embedding[index];
-    }
-    const sqrtNormalization = Math.sqrt(normalization);
-    for (let index = 0; index < embedding.length; index++) {
-        embedding[index] = embedding[index] / sqrtNormalization;
-    }
-    return embedding;
-};

+ 1 - 2
desktop/src/services/ffmpeg.ts

@@ -1,7 +1,6 @@
 import pathToFfmpeg from "ffmpeg-static";
 import { existsSync } from "node:fs";
 import fs from "node:fs/promises";
-import { CustomErrors } from "../constants/errors";
 import { writeStream } from "../main/fs";
 import log from "../main/log";
 import { execAsync } from "../main/util";
@@ -146,7 +145,7 @@ const promiseWithTimeout = async <T>(
     } = { current: null };
     const rejectOnTimeout = new Promise<null>((_, reject) => {
         timeoutRef.current = setTimeout(
-            () => reject(Error(CustomErrors.WAIT_TIME_EXCEEDED)),
+            () => reject(new Error("Operation timed out")),
             timeout,
         );
     });

+ 4 - 2
desktop/src/services/fs.ts

@@ -2,7 +2,7 @@ import StreamZip from "node-stream-zip";
 import { existsSync } from "node:fs";
 import fs from "node:fs/promises";
 import path from "node:path";
-import { logError } from "../main/log";
+import log from "../main/log";
 import { ElectronFile } from "../types/ipc";
 
 const FILE_STREAM_CHUNK_SIZE: number = 4 * 1024 * 1024;
@@ -115,7 +115,9 @@ export const getZipFileStream = async (
     const inProgress = {
         current: false,
     };
+    // eslint-disable-next-line no-unused-vars
     let resolveObj: (value?: any) => void = null;
+    // eslint-disable-next-line no-unused-vars
     let rejectObj: (reason?: any) => void = null;
     stream.on("readable", () => {
         try {
@@ -179,7 +181,7 @@ export const getZipFileStream = async (
                     controller.close();
                 }
             } catch (e) {
-                logError(e, "readableStream pull failed");
+                log.error("Failed to pull from readableStream", e);
                 controller.close();
             }
         },

+ 16 - 11
desktop/src/services/imageProcessor.ts

@@ -1,11 +1,10 @@
 import { existsSync } from "fs";
 import fs from "node:fs/promises";
 import path from "path";
-import { CustomErrors } from "../constants/errors";
 import { writeStream } from "../main/fs";
-import { logError, logErrorSentry } from "../main/log";
+import log from "../main/log";
 import { execAsync, isDev } from "../main/util";
-import { ElectronFile } from "../types/ipc";
+import { CustomErrors, ElectronFile } from "../types/ipc";
 import { isPlatform } from "../utils/common/platform";
 import { generateTempFilePath } from "../utils/temp";
 import { deleteTempFile } from "./ffmpeg";
@@ -103,18 +102,21 @@ async function convertToJPEG_(
 
         return new Uint8Array(await fs.readFile(tempOutputFilePath));
     } catch (e) {
-        logErrorSentry(e, "failed to convert heic");
+        log.error("Failed to convert HEIC", e);
         throw e;
     } finally {
         try {
             await fs.rm(tempInputFilePath, { force: true });
         } catch (e) {
-            logErrorSentry(e, "failed to remove tempInputFile");
+            log.error(`Failed to remove tempInputFile ${tempInputFilePath}`, e);
         }
         try {
             await fs.rm(tempOutputFilePath, { force: true });
         } catch (e) {
-            logErrorSentry(e, "failed to remove tempOutputFile");
+            log.error(
+                `Failed to remove tempOutputFile ${tempOutputFilePath}`,
+                e,
+            );
         }
     }
 }
@@ -150,7 +152,7 @@ function constructConvertCommand(
             },
         );
     } else {
-        throw Error(CustomErrors.INVALID_OS(process.platform));
+        throw new Error(`Unsupported OS ${process.platform}`);
     }
     return convertCmd;
 }
@@ -187,7 +189,7 @@ export async function generateImageThumbnail(
             try {
                 await deleteTempFile(inputFilePath);
             } catch (e) {
-                logError(e, "failed to deleteTempFile");
+                log.error(`Failed to deleteTempFile ${inputFilePath}`, e);
             }
         }
     }
@@ -217,13 +219,16 @@ async function generateImageThumbnail_(
         } while (thumbnail.length > maxSize && quality > MIN_QUALITY);
         return thumbnail;
     } catch (e) {
-        logErrorSentry(e, "generate image thumbnail failed");
+        log.error("Failed to generate image thumbnail", e);
         throw e;
     } finally {
         try {
             await fs.rm(tempOutputFilePath, { force: true });
         } catch (e) {
-            logErrorSentry(e, "failed to remove tempOutputFile");
+            log.error(
+                `Failed to remove tempOutputFile ${tempOutputFilePath}`,
+                e,
+            );
         }
     }
 }
@@ -283,7 +288,7 @@ function constructThumbnailGenerationCommand(
                 return cmdPart;
             });
     } else {
-        throw Error(CustomErrors.INVALID_OS(process.platform));
+        throw new Error(`Unsupported OS ${process.platform}`);
     }
     return thumbnailGenerationCmd;
 }

+ 26 - 0
desktop/src/services/store.ts

@@ -0,0 +1,26 @@
+import { safeStorage } from "electron/main";
+import { keysStore } from "../stores/keys.store";
+import { safeStorageStore } from "../stores/safeStorage.store";
+import { uploadStatusStore } from "../stores/upload.store";
+import { watchStore } from "../stores/watch.store";
+
+export const clearElectronStore = () => {
+    uploadStatusStore.clear();
+    keysStore.clear();
+    safeStorageStore.clear();
+    watchStore.clear();
+};
+
+export async function setEncryptionKey(encryptionKey: string) {
+    const encryptedKey: Buffer = await safeStorage.encryptString(encryptionKey);
+    const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64");
+    safeStorageStore.set("encryptionKey", b64EncryptedKey);
+}
+
+export async function getEncryptionKey(): Promise<string> {
+    const b64EncryptedKey = safeStorageStore.get("encryptionKey");
+    if (b64EncryptedKey) {
+        const keyBuffer = Buffer.from(b64EncryptedKey, "base64");
+        return await safeStorage.decryptString(keyBuffer);
+    }
+}

+ 29 - 0
desktop/src/services/upload.ts

@@ -1,10 +1,39 @@
 import StreamZip from "node-stream-zip";
 import path from "path";
+import { getElectronFile } from "../services/fs";
 import { uploadStatusStore } from "../stores/upload.store";
 import { ElectronFile, FILE_PATH_TYPE } from "../types/ipc";
 import { FILE_PATH_KEYS } from "../types/main";
 import { getValidPaths, getZipFileStream } from "./fs";
 
+export const getPendingUploads = async () => {
+    const filePaths = getSavedFilePaths(FILE_PATH_TYPE.FILES);
+    const zipPaths = getSavedFilePaths(FILE_PATH_TYPE.ZIPS);
+    const collectionName = uploadStatusStore.get("collectionName");
+
+    let files: ElectronFile[] = [];
+    let type: FILE_PATH_TYPE;
+    if (zipPaths.length) {
+        type = FILE_PATH_TYPE.ZIPS;
+        for (const zipPath of zipPaths) {
+            files = [
+                ...files,
+                ...(await getElectronFilesFromGoogleZip(zipPath)),
+            ];
+        }
+        const pendingFilePaths = new Set(filePaths);
+        files = files.filter((file) => pendingFilePaths.has(file.path));
+    } else if (filePaths.length) {
+        type = FILE_PATH_TYPE.FILES;
+        files = await Promise.all(filePaths.map(getElectronFile));
+    }
+    return {
+        files,
+        collectionName,
+        type,
+    };
+};
+
 export const getSavedFilePaths = (type: FILE_PATH_TYPE) => {
     const paths =
         getValidPaths(

+ 1 - 0
desktop/src/types/any-shell-escape.d.ts

@@ -19,6 +19,7 @@
  *     curl -v -H "Location;" -H "User-Agent: FooBar's so-called ""Browser""" "http://www.daveeddy.com/?name=dave&age=24"
 Which is suitable for being executed by the shell.
  */
+/* eslint-disable no-unused-vars */
 declare module "any-shell-escape" {
     declare const shellescape: (args: readonly string | string[]) => string;
     export default shellescape;

+ 27 - 5
desktop/src/types/ipc.ts

@@ -4,6 +4,32 @@
  * This file is manually kept in sync with the renderer code.
  * See [Note: types.ts <-> preload.ts <-> ipc.ts]
  */
+
+/**
+ * Errors that have special semantics on the web side.
+ *
+ * [Note: Custom errors across Electron/Renderer boundary]
+ *
+ * We need to use the `message` field to disambiguate between errors thrown by
+ * the main process when invoked from the renderer process. This is because:
+ *
+ * > Errors thrown throw `handle` in the main process are not transparent as
+ * > they are serialized and only the `message` property from the original error
+ * > is provided to the renderer process.
+ * >
+ * > - https://www.electronjs.org/docs/latest/tutorial/ipc
+ * >
+ * > Ref: https://github.com/electron/electron/issues/24427
+ */
+export const CustomErrors = {
+    WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED:
+        "Windows native image processing is not supported",
+    UNSUPPORTED_PLATFORM: (platform: string, arch: string) =>
+        `Unsupported platform - ${platform} ${arch}`,
+    MODEL_DOWNLOAD_PENDING:
+        "Model download pending, skipping clip search request",
+};
+
 /**
  * Deprecated - Use File + webUtils.getPathForFile instead
  *
@@ -45,6 +71,7 @@ export interface WatchStoreType {
 }
 
 export enum FILE_PATH_TYPE {
+    /* eslint-disable no-unused-vars */
     FILES = "files",
     ZIPS = "zips",
 }
@@ -53,8 +80,3 @@ export interface AppUpdateInfo {
     autoUpdatable: boolean;
     version: string;
 }
-
-export enum Model {
-    GGML_CLIP = "ggml-clip",
-    ONNX_CLIP = "onnx-clip",
-}

+ 1 - 0
desktop/src/types/main.ts

@@ -18,6 +18,7 @@ export interface KeysStoreType {
     };
 }
 
+/* eslint-disable no-unused-vars */
 export const FILE_PATH_KEYS: {
     [k in FILE_PATH_TYPE]: keyof UploadStoreType;
 } = {

+ 4 - 77
docs/docs/.vitepress/sidebar.ts

@@ -202,6 +202,10 @@ export const sidebar = [
             {
                 text: "Troubleshooting",
                 items: [
+                    {
+                        text: "Uploads",
+                        link: "/self-hosting/troubleshooting/uploads",
+                    },
                     {
                         text: "Yarn",
                         link: "/self-hosting/troubleshooting/yarn",
@@ -219,80 +223,3 @@ export const sidebar = [
         link: "/about/contribute",
     },
 ];
-
-function sidebarOld() {
-    return [
-        {
-            text: "Welcome",
-            items: [
-                {
-                    text: "Features",
-                    collapsed: true,
-                    items: [
-                        {
-                            text: "Family Plan",
-                            link: "/photos/features/family-plan",
-                        },
-                        { text: "Albums", link: "/photos/features/albums" },
-                        { text: "Archive", link: "/photos/features/archive" },
-                        { text: "Hidden", link: "/photos/features/hidden" },
-                        { text: "Map", link: "/photos/features/map" },
-                        {
-                            text: "Location Tags",
-                            link: "/photos/features/location",
-                        },
-                        {
-                            text: "Collect Photos",
-                            link: "/photos/features/collect",
-                        },
-                        {
-                            text: "Public links",
-                            link: "/photos/features/public-links",
-                        },
-                        {
-                            text: "Quick link",
-                            link: "/photos/features/quick-link",
-                        },
-                        {
-                            text: "Watch folder",
-                            link: "/photos/features/watch-folders",
-                        },
-                        { text: "Trash", link: "/photos/features/trash" },
-                        {
-                            text: "Uncategorized",
-                            link: "/photos/features/uncategorized",
-                        },
-                        {
-                            text: "Referral Plan",
-                            link: "/photos/features/referral",
-                        },
-                        {
-                            text: "Live & Motion Photos",
-                            link: "/photos/features/live-photos",
-                        },
-                        { text: "Cast", link: "/photos/features/cast" },
-                    ],
-                },
-                {
-                    text: "Troubleshoot",
-                    collapsed: true,
-                    link: "/photos/troubleshooting/files-not-uploading",
-                    items: [
-                        {
-                            text: "Files not uploading",
-                            link: "/photos/troubleshooting/files-not-uploading",
-                        },
-                        {
-                            text: "Failed to play video",
-                            link: "/photos/troubleshooting/video-not-playing",
-                        },
-                        {
-                            text: "Report bug",
-                            link: "/photos/troubleshooting/report-bug",
-                        },
-                    ],
-                },
-            ],
-        },
-    ];
-}

+ 10 - 8
docs/docs/photos/faq/export.md

@@ -12,15 +12,17 @@ in a local drive or NAS of your choice. This way, you can use Ente in your day
 to day use, but will have an additional guarantee that a copy of your original
 photos and videos are always available in normal directories and files.
 
-* You can use [Ente's CLI](https://github.com/ente-io/ente/tree/main/cli#export)
-  to export your data in a cron job to a location of your choice. The exports
-  are incremental, and will also gracefully handle interruptions.
+-   You can use
+    [Ente's CLI](https://github.com/ente-io/ente/tree/main/cli#export) to export
+    your data in a cron job to a location of your choice. The exports are
+    incremental, and will also gracefully handle interruptions.
 
-* Similarly, you can use Ente's [desktop app](https://ente.io/download/desktop)
-  to export your data to a folder of your choice. The desktop app also supports
-  "continuous" exports, where it will automatically export new items in the
-  background without you needing to run any other cron jobs. See
-  [migration/export](/photos/migration/export/) for more details.
+-   Similarly, you can use Ente's
+    [desktop app](https://ente.io/download/desktop) to export your data to a
+    folder of your choice. The desktop app also supports "continuous" exports,
+    where it will automatically export new items in the background without you
+    needing to run any other cron jobs. See
+    [migration/export](/photos/migration/export/) for more details.
 
 ## Does the exported data from Ente photos preserve the same folder and album structure as in the app?
 

+ 15 - 6
docs/docs/photos/faq/general.md

@@ -77,26 +77,35 @@ It's like cafe 😊. kaf-_ay_. en-_tay_.
 
 ## Does Ente apply compression to uploaded photos?
 
-Ente does not apply compression to uploaded photos. The file size of your photos in Ente will be similar to the original file sizes you have.
+Ente does not apply compression to uploaded photos. The file size of your photos
+in Ente will be similar to the original file sizes you have.
 
 ## Can I add photos from a shared album to albums that I created in Ente?
 
-Currently, Ente does not support adding photos from a shared album to your personal albums. If you want to include photos from a shared album in your own albums, you will need to ask the owner of the photos to add them to your album. 
+Currently, Ente does not support adding photos from a shared album to your
+personal albums. If you want to include photos from a shared album in your own
+albums, you will need to ask the owner of the photos to add them to your album.
 
 ## How do I ensure that the Ente desktop app stays up to date on my system?
 
-Ente desktop includes an auto-update feature, ensuring that whenever updates are deployed, the app will automatically download and install them. You don't need to manually update the software.
+Ente desktop includes an auto-update feature, ensuring that whenever updates are
+deployed, the app will automatically download and install them. You don't need
+to manually update the software.
 
 ## Can I sync a folder containing multiple subfolders, each representing an album?
 
-Yes, when you drag and drop the folder onto the desktop app, the app will detect the multiple folders and prompt you to choose whether you want to create a single album or separate albums for each folder.
+Yes, when you drag and drop the folder onto the desktop app, the app will detect
+the multiple folders and prompt you to choose whether you want to create a
+single album or separate albums for each folder.
 
 ## What is the difference between **Magic** and **Content** search results on the desktop?
 
-**Magic** is where you can search for long queries. Like, "baby in red dress", or "dog playing at the beach".
+**Magic** is where you can search for long queries. Like, "baby in red dress",
+or "dog playing at the beach".
 
 **Content** is where you can search for single-words. Like, "car" or "pizza".
 
 ## How do I identify which files experienced upload issues within the desktop app?
 
-Check the sections within the upload progress bar for "Failed Uploads," "Ignored Uploads," and "Unsuccessful Uploads."
+Check the sections within the upload progress bar for "Failed Uploads," "Ignored
+Uploads," and "Unsuccessful Uploads."

+ 3 - 1
docs/docs/photos/faq/subscription.md

@@ -159,4 +159,6 @@ We do offer a generous free trial for you to experience the product.
 
 ## Will I need to pay for Ente Auth after my Ente Photos free plan expires?
 
-No, you will not need to pay for Ente Auth after your Ente Photos free plan expires. Ente Auth is completely free to use, and the expiration of your Ente Photos free plan will not impact your ability to access or use Ente Auth.
+No, you will not need to pay for Ente Auth after your Ente Photos free plan
+expires. Ente Auth is completely free to use, and the expiration of your Ente
+Photos free plan will not impact your ability to access or use Ente Auth.

+ 0 - 1
docs/docs/photos/migration/export/index.md

@@ -50,7 +50,6 @@ videos you have uploaded to Ente.
 
 </div>
 
-
 ### Sync continuously
 
 You can switch on the toggle to **Sync continuously** to eliminate manual

+ 22 - 8
docs/docs/self-hosting/guides/admin.md

@@ -24,18 +24,32 @@ and subsequently increase the
 [storage and account validity](https://github.com/ente-io/ente/blob/main/cli/docs/generated/ente_admin_update-subscription.md)
 using the CLI.
 
-For the admin actions, you can create `server/museum.yaml`, and whitelist add
-the admin userID `internal.admins`. See
-[local.yaml](https://github.com/ente-io/ente/blob/main/server/configurations/local.yaml#L211C1-L232C1)
+For security purposes, we need to whitelist the user IDs that can perform admin
+actions on the server. To do this,
+
+-   Create a `museum.yaml` in the directory where you're starting museum from.
+    For example, if you're running using `docker compose up`, then this file
+    should be in the same directory as `compose.yaml` (generally,
+    `server/museum.yaml`).
+
+    > Docker might've created an empty `museum.yaml` _directory_ on your machine
+    > previously. If so, delete that empty directory and create a new file named
+    > `museum.yaml`.
+
+-   In this `museum.yaml` we can add overrides over the default configuration.
+
+For whitelisting the admin userIDs we need to define an `internal.admins`. See
+the "internal" section in
+[local.yaml](https://github.com/ente-io/ente/blob/main/server/configurations/local.yaml)
 in the server source code for details about how to define this.
 
+Here is an example. Suppose we wanted to whitelist a user with ID
+`1580559962386440`, we can create the following `museum.yaml`
+
 ```yaml
-....
 internal:
-  admins:
-    # - 1580559962386440
-
-....
+    admins:
+        - 1580559962386440
 ```
 
 You can use

+ 0 - 5
docs/docs/self-hosting/guides/custom-server/index.md

@@ -18,11 +18,6 @@ configure the endpoint the app should be connecting to.
 
 ![Setting a custom server on the onboarding screen](custom-server.png)
 
-> [!IMPORTANT]
->
-> This is only supported by the Ente Auth app currently. We'll add this same
-> functionality to the Ente Photos app soon.
-
 ## CLI
 
 > [!NOTE]

+ 2 - 2
docs/docs/self-hosting/index.md

@@ -26,8 +26,8 @@ docker compose up --build
 
 > [!TIP]
 >
-> You can also use a pre-built Docker image from `ghcr.io/ente-io/server` ([More
-> info](https://github.com/ente-io/ente/blob/main/server/docs/docker.md))
+> You can also use a pre-built Docker image from `ghcr.io/ente-io/server`
+> ([More info](https://github.com/ente-io/ente/blob/main/server/docs/docker.md))
 
 Then in a separate terminal, you can run (e.g) the web client
 

+ 13 - 0
docs/docs/self-hosting/troubleshooting/uploads.md

@@ -0,0 +1,13 @@
+---
+title: Uploads failing
+description: Fixing upload errors when trying to self host Ente
+---
+
+# Uploads failing
+
+If uploads to your self-hosted server are failing, make sure that
+`credentials.yaml` has `yourserverip:3200` for all three minio locations.
+
+By default it is `localhost:3200`, and it needs to be changed to an IP that is
+accessible from both where you are running the Ente clients (e.g. the mobile
+app) and also from within the Docker compose cluster.

+ 1 - 1
infra/services/listmonk/listmonk.service

@@ -12,7 +12,7 @@ ExecStartPre=-docker stop listmonk
 ExecStartPre=-docker rm listmonk
 ExecStartPre=-docker run --rm --name listmonk \
     -v /root/listmonk/config.toml:/listmonk/config.toml:ro \
-    listmonk/listmonk --upgrade --yes
+    listmonk/listmonk ./listmonk --upgrade --yes
 ExecStart=docker run --name listmonk \
     -p 9000:9000 \
     -v /root/listmonk/config.toml:/listmonk/config.toml:ro \

+ 1 - 0
infra/services/nginx/nginx.service

@@ -16,5 +16,6 @@ ExecStart=docker run --name nginx \
     -v /root/nginx/cert.pem:/etc/ssl/certs/cert.pem:ro \
     -v /root/nginx/key.pem:/etc/ssl/private/key.pem:ro \
     -v /root/nginx/conf.d:/etc/nginx/conf.d:ro \
+    --log-opt max-size=1g \
     nginx
 ExecReload=docker exec nginx nginx -s reload

+ 36 - 0
mobile/fastlane/metadata/android/tr/full_description.txt

@@ -0,0 +1,36 @@
+ente, fotoğraflarınızı ve videolarınızı yedeklemek ve paylaşmak için basit bir uygulamadır.
+
+Google Fotoğraflar'a gizlilik dostu bir alternatif arıyorsanız doğru yere geldiniz. Ente ile uçtan uca şifrelenmiş olarak (e2ee) saklanırlar. Bu, onları yalnızca sizin görebileceğiniz anlamına gelir.
+
+Android, iOS, web ve masaüstünde açık kaynaklı uygulamalarımız var ve fotoğraflarınız bunların tümü arasında uçtan uca şifrelenmiş (e2ee) şekilde sorunsuz bir şekilde senkronize edilecek.
+
+ente ayrıca, ente'de olmasalar bile albümlerinizi sevdiklerinizle paylaşmanızı da kolaylaştırır. Bir hesap veya uygulama olmadan bile albümünüzü görüntüleyebilecekleri ve albüme fotoğraf ekleyerek ortak çalışabilecekleri, herkese açık olarak görüntülenebilen bağlantıları paylaşabilirsiniz.
+
+Şifrelenmiş verileriniz, Paris'teki bir serpinti sığınağı da dahil olmak üzere birden fazla yerde depolanır. Gelecek nesilleri ciddiye alıyor ve anılarınızın sizden daha uzun yaşamasını sağlamayı kolaylaştırıyoruz.
+
+Şimdiye kadarki en güvenli fotoğraf uygulamasını yapmak için buradayız, gelin yolculuğumuza katılın!
+
+✨ÖZELLİKLER
+- Orijinal kalitede yedekler, çünkü her piksel önemlidir
+- Aile planları, böylece depolama alanını ailenizle paylaşabilirsiniz
+- Seyahatten sonra fotoğrafları bir araya toplayabilmeniz için ortak albümler
+- Partnerinizin "Kamera" tıklamalarınızın keyfini çıkarmasını istemeniz durumunda paylaşılan klasörler
+- Parola ile korunabilen ve süresi dolacak şekilde ayarlanabilen albüm bağlantıları
+- Güvenli bir şekilde yedeklenmiş dosyaları kaldırarak alan boşaltma yeteneği
+- İnsan desteği, çünkü sen buna değersin
+- Açıklamalar, böylece anılarınıza başlık yazabilir ve onları kolayca bulabilirsiniz
+- Son rötuşları eklemek için görüntü düzenleyici
+- Favori, sakla ve anılarını yeniden yaşa, çünkü onlar değerlidir
+- Google, Apple, sabit diskiniz ve daha fazlasından tek tıkla içe aktarma
+- Koyu tema, çünkü fotoğraflarınız bu temada güzel görünüyor
+- 2FA, 3FA, biyometrik kimlik doğrulama
+- ve çok daha fazlası!
+
+İZİNLER
+bir fotoğraf depolama sağlayıcısının amacına hizmet etmek için belirli izinlere yönelik taleplerde bulunulabilir; bu izinler burada incelenebilir: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
+
+FİYATLANDIRMA
+Sonsuza kadar ücretsiz planlar sunmuyoruz, çünkü sürdürülebilir kalmamız ve zamanın testine dayanmamız bizim için önemli. Bunun yerine, ailenizle özgürce paylaşabileceğiniz uygun fiyatlı planlar sunuyoruz. Daha fazla bilgiyi ente.io adresinde bulabilirsiniz.
+
+🙋DESTEK
+İnsan desteği sunmaktan gurur duyuyoruz. Ücretli müşterimiz iseniz team@ente.io adresine ulaşabilir ve ekibimizden 24 saat içinde yanıt bekleyebilirsiniz.

+ 1 - 0
mobile/fastlane/metadata/android/tr/short_description.txt

@@ -0,0 +1 @@
+ente uçtan uca şifrelenmiş bir fotoğraf depolama uygulamasıdır

+ 1 - 0
mobile/fastlane/metadata/android/tr/title.txt

@@ -0,0 +1 @@
+ente - şifrelenmiş depolama sistemi

+ 1 - 1
mobile/fastlane/metadata/ios/ru/name.txt

@@ -1 +1 @@
-ente Фото
+ente фотографии

+ 1 - 1
mobile/fastlane/metadata/ios/ru/subtitle.txt

@@ -1 +1 @@
-Система зашифрованного хранения фотографий
+Зашифрованное хранилище фотографий

+ 33 - 0
mobile/fastlane/metadata/ios/tr/description.txt

@@ -0,0 +1,33 @@
+Ente, fotoğraflarınızı ve videolarınızı yedekleyip paylaşmanızı sağlayan kullanimi kolay bir uygulamadır.
+
+Anılarınızı saklamak için gizlilik dostu bir alternatif arıyorsanız, doğru yere geldiniz. Ente ile uçtan uca şifrelenmiş olarak (e2ee) saklanırlar. Bu, onları yalnızca sizin görebileceğiniz anlamına gelir.
+
+Android, iOS, web ve Masaüstünde uygulamalarımız var ve fotoğraflarınız tüm cihazlarınız arasında uçtan uca şifrelenmiş (e2ee) bir şekilde sorunsuz bir şekilde senkronize edilecek.
+
+Ente, albümlerinizi sevdiklerinizle paylaşmanızı da kolaylaştırıyor. Bunları uçtan uca şifrelenmiş olarak doğrudan diğer Ente kullanıcılarıyla paylaşabilir veya herkese açık olarak görüntülenebilir bağlantılarla paylaşabilirsiniz.
+
+Şifrelenmiş verileriniz, Paris'teki bir serpinti sığınağı da dahil olmak üzere birden fazla yerde depolanır. Gelecek nesilleri ciddiye alıyor ve anılarınızın sizden daha uzun yaşamasını sağlamayı kolaylaştırıyoruz.
+
+Şimdiye kadarki en güvenli fotoğraf uygulamasını yapmak için buradayız, gelin yolculuğumuza katılın!
+
+✨ÖZELLİKLER
+- Orijinal kalitede yedekler, çünkü her piksel önemlidir
+- Aile planları, böylece depolama alanını ailenizle paylaşabilirsiniz
+- Partnerinizin "Kamera" tıklamalarınızın keyfini çıkarmasını istemeniz durumunda paylaşılan klasörler
+- Parola ile korunabilen ve süresi dolacak şekilde ayarlanabilen albüm bağlantıları
+- Güvenli bir şekilde yedeklenmiş dosyaları kaldırarak alan boşaltma yeteneği
+- Son rötuşları eklemek için görüntü düzenleyici
+- Favori, sakla ve anılarını yeniden yaşa, çünkü onlar değerlidir
+- Tüm büyük depolama sağlayıcılarından tek tıklamayla içe aktarma
+- Koyu tema, çünkü fotoğraflarınız bu temada güzel görünüyor
+- 2FA, 3FA, biyometrik kimlik doğrulama
+- ve çok daha fazlası!
+
+FİYATLANDIRMA
+Sonsuza kadar ücretsiz planlar sunmuyoruz, çünkü sürdürülebilir kalmamız ve zamanın testine dayanmamız bizim için önemli. Bunun yerine, ailenizle özgürce paylaşabileceğiniz uygun fiyatlı planlar sunuyoruz. Daha fazla bilgiyi ente.io adresinde bulabilirsiniz.
+
+🙋DESTEK
+İnsan desteği sunmaktan gurur duyuyoruz. Ücretli müşterimiz iseniz team@ente.io adresine ulaşabilir ve ekibimizden 24 saat içinde yanıt bekleyebilirsiniz.
+
+ŞARTLAR
+https://ente.io/terms

+ 1 - 0
mobile/fastlane/metadata/ios/tr/keywords.txt

@@ -0,0 +1 @@
+fotoğraflar,fotoğrafçılık,aile,gizlilik,bulut,yedekleme,videolar,fotoğraf,şifreleme,depolama,albüm,alternatif

+ 1 - 0
mobile/fastlane/metadata/ios/tr/name.txt

@@ -0,0 +1 @@
+ente fotoğraf uygulaması

+ 1 - 0
mobile/fastlane/metadata/ios/tr/subtitle.txt

@@ -0,0 +1 @@
+Şifrelenmiş depolama sistemi

+ 30 - 0
mobile/fastlane/metadata/playstore/tr/full_description.txt

@@ -0,0 +1,30 @@
+Ente, fotoğraflarınızı ve videolarınızı yedekleyip paylaşmanızı sağlayan kullanimi kolay bir uygulamadır.
+
+Anılarınızı saklamak için gizlilik dostu bir alternatif arıyorsanız, doğru yere geldiniz. Ente ile uçtan uca şifrelenmiş olarak (e2ee) saklanırlar. Bu, onları yalnızca sizin görüntüleyebileceğiniz anlamına gelir.
+
+Android, iOS, web ve Masaüstünde uygulamalarımız var ve fotoğraflarınız tüm cihazlarınız arasında uçtan uca şifrelenmiş (e2ee) bir şekilde sorunsuz bir şekilde senkronize edilecek.
+
+Ente, albümlerinizi sevdiklerinizle paylaşmanızı da kolaylaştırıyor. Bunları uçtan uca şifrelenmiş olarak doğrudan diğer Ente kullanıcılarıyla paylaşabilir veya herkese açık olarak görüntülenebilir bağlantılarla paylaşabilirsiniz.
+
+Şifrelenmiş verileriniz, Paris'teki bir serpinti sığınağı da dahil olmak üzere birden fazla yerde depolanır. Gelecek nesilleri ciddiye alıyor ve anılarınızın sizden daha uzun yaşamasını sağlamayı kolaylaştırıyoruz.
+
+Şimdiye kadarki en güvenli fotoğraf uygulamasını yapmak için buradayız, gelin yolculuğumuza katılın!
+
+✨ÖZELLİKLER
+- Orijinal kalitede yedekler, çünkü her piksel önemlidir
+- Aile planları, böylece depolama alanını ailenizle paylaşabilirsiniz
+- Partnerinizin "Kamera" tıklamalarınızın keyfini çıkarmasını istemeniz durumunda paylaşılan klasörler
+- Parola ile korunabilen ve süresi dolacak şekilde ayarlanabilen albüm bağlantıları
+- Güvenli bir şekilde yedeklenmiş dosyaları kaldırarak alan boşaltma yeteneği
+- Son rötuşları eklemek için görüntü düzenleyici
+- Favori, sakla ve anılarını yeniden yaşa, çünkü onlar değerlidir
+- Google, Apple, sabit diskiniz ve daha fazlasından tek tıkla içe aktarma
+- Koyu tema, çünkü fotoğraflarınız bu temada güzel görünüyor
+- 2FA, 3FA, biyometrik kimlik doğrulama
+- ve çok daha fazlası!
+
+💲 FİYATLANDIRMA
+Sonsuza kadar ücretsiz planlar sunmuyoruz, çünkü sürdürülebilir kalmamız ve zamanın testine dayanmamız bizim için önemli. Bunun yerine, ailenizle özgürce paylaşabileceğiniz uygun fiyatlı planlar sunuyoruz. Daha fazla bilgiyi ente.io adresinde bulabilirsiniz.
+
+🙋DESTEK
+İnsan desteği sunmaktan gurur duyuyoruz. Ücretli müşterimiz iseniz team@ente.io adresine ulaşabilir ve ekibimizden 24 saat içinde yanıt bekleyebilirsiniz.

+ 1 - 0
mobile/fastlane/metadata/playstore/tr/short_description.txt

@@ -0,0 +1 @@
+Şifreli fotoğraf depolama - fotoğraflarınızı ve videolarınızı yedekleyin, düzenleyin ve paylaşın

+ 130 - 0
mobile/integration_test/app_init_test.dart

@@ -0,0 +1,130 @@
+import "dart:async";
+
+import "package:flutter/material.dart";
+import "package:flutter_test/flutter_test.dart";
+import "package:integration_test/integration_test.dart";
+import "package:logging/logging.dart";
+import "package:photos/main.dart" as app;
+
+void main() {
+  group("App init test", () {
+    final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+    binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
+    testWidgets("App init test", semanticsEnabled: false, (tester) async {
+      // https://github.com/flutter/flutter/issues/89749#issuecomment-1029965407
+      tester.testTextInput.register();
+
+      await runZonedGuarded(
+        () async {
+          bool skipLogin = false;
+
+          ///Ignore exceptions thrown by the app for the test to pass
+          WidgetsFlutterBinding.ensureInitialized();
+          FlutterError.onError = (FlutterErrorDetails errorDetails) {
+            FlutterError.dumpErrorToConsole(errorDetails);
+          };
+
+          await binding.traceAction(
+            () async {
+              app.main();
+
+              await tester.pumpAndSettle(const Duration(seconds: 1));
+
+              await dismissUpdateAppDialog(tester);
+
+              final signInButton = find.byKey(const ValueKey("signInButton"));
+              skipLogin = !tester.any(signInButton);
+
+              if (!skipLogin) {
+                await tester.tap(signInButton);
+                await tester.pumpAndSettle();
+                final emailInputField = find.byType(TextFormField);
+                final logInButton = find.byKey(const ValueKey("logInButton"));
+                //Fill email id here
+                await tester.enterText(emailInputField, "*enter email here*");
+                await tester.pumpAndSettle(const Duration(seconds: 1));
+                await tester.tap(logInButton);
+                await tester.pumpAndSettle(const Duration(seconds: 3));
+                final passwordInputField =
+                    find.byKey(const ValueKey("passwordInputField"));
+                final verifyPasswordButton =
+                    find.byKey(const ValueKey("verifyPasswordButton"));
+                //Fill password here
+                await tester.enterText(
+                  passwordInputField,
+                  "*enter password here*",
+                );
+                await tester.pumpAndSettle(const Duration(seconds: 1));
+                await tester.tap(verifyPasswordButton);
+                await tester.pumpAndSettle();
+
+                await tester.pumpAndSettle(const Duration(seconds: 1));
+                await dismissUpdateAppDialog(tester);
+
+                //Grant permission to access photos. Must manually click the system dialog.
+                final grantPermissionButton =
+                    find.byKey(const ValueKey("grantPermissionButton"));
+                await tester.tap(grantPermissionButton);
+                await tester.pumpAndSettle(const Duration(seconds: 1));
+                await tester.pumpAndSettle(const Duration(seconds: 3));
+
+                //Automatically skips backup
+                final skipBackupButton =
+                    find.byKey(const ValueKey("skipBackupButton"));
+                await tester.tap(skipBackupButton);
+                await tester.pumpAndSettle(const Duration(seconds: 2));
+              }
+            },
+            reportKey: "app_init_summary",
+          );
+        },
+        (error, stack) {
+          Logger("app_init_test").info(error, stack);
+        },
+      );
+    });
+  });
+}
+
+Future<void> dismissUpdateAppDialog(WidgetTester tester) async {
+  await tester.tapAt(const Offset(0, 0));
+  await tester.pumpAndSettle();
+}
+
+
+///Use this widget as floating action buttom in HomeWidget so that frames
+///are built and rendered continuously so that timeline trace has continuous 
+///data. Change the duraiton in `_startTimer()` to control the duraiton of 
+///test on app init.
+
+// class TempWidget extends StatefulWidget {
+//   const TempWidget({super.key});
+
+//   @override
+//   TempWidgetState createState() => TempWidgetState();
+// }
+
+// class TempWidgetState extends State<TempWidget> {
+//   bool _isLoading = true;
+
+//   @override
+//   void initState() {
+//     super.initState();
+//     _startTimer();
+//   }
+
+//   void _startTimer() {
+//     Future.delayed(const Duration(seconds: 20), () {
+//       setState(() {
+//         _isLoading = false;
+//       });
+//     });
+//   }
+
+//   @override
+//   Widget build(BuildContext context) {
+//     return _isLoading
+//         ? const CircularProgressIndicator()
+//         : const SizedBox.shrink();
+//   }
+// }

+ 32 - 4
mobile/lib/db/files_db.dart

@@ -1,5 +1,6 @@
 import "dart:io";
 
+import "package:computer/computer.dart";
 import 'package:flutter/foundation.dart';
 import 'package:logging/logging.dart';
 import 'package:path/path.dart';
@@ -16,6 +17,7 @@ import 'package:photos/utils/file_uploader_util.dart';
 import 'package:sqflite/sqflite.dart';
 import 'package:sqflite_migration/sqflite_migration.dart';
 import 'package:sqlite3/sqlite3.dart' as sqlite3;
+import 'package:sqlite_async/sqlite_async.dart' as sqlite_async;
 
 class FilesDB {
   /*
@@ -102,6 +104,7 @@ class FilesDB {
   // only have a single app-wide reference to the database
   static Future<Database>? _dbFuture;
   static Future<sqlite3.Database>? _ffiDBFuture;
+  static Future<sqlite_async.SqliteDatabase>? _sqliteAsyncDBFuture;
 
   Future<Database> get database async {
     // lazily instantiate the db the first time it is accessed
@@ -114,6 +117,11 @@ class FilesDB {
     return _ffiDBFuture!;
   }
 
+  Future<sqlite_async.SqliteDatabase> get sqliteAsyncDB async {
+    _sqliteAsyncDBFuture ??= _initSqliteAsyncDatabase();
+    return _sqliteAsyncDBFuture!;
+  }
+
   // this opens the database (and creates it if it doesn't exist)
   Future<Database> _initDatabase() async {
     final Directory documentsDirectory =
@@ -131,6 +139,14 @@ class FilesDB {
     return sqlite3.sqlite3.open(path);
   }
 
+  Future<sqlite_async.SqliteDatabase> _initSqliteAsyncDatabase() async {
+    final Directory documentsDirectory =
+        await getApplicationDocumentsDirectory();
+    final String path = join(documentsDirectory.path, _databaseName);
+    _logger.info("DB path " + path);
+    return sqlite_async.SqliteDatabase(path: path);
+  }
+
   // SQL code to create the database table
   static List<String> createTable(String tableName) {
     return [
@@ -1467,6 +1483,14 @@ class FilesDB {
     return collectionIDsOfFile;
   }
 
+  List<EnteFile> convertToFilesForIsolate(Map args) {
+    final List<EnteFile> files = [];
+    for (final result in args["result"]) {
+      files.add(_getFileFromRow(result));
+    }
+    return files;
+  }
+
   List<EnteFile> convertToFiles(List<Map<String, dynamic>> results) {
     final List<EnteFile> files = [];
     for (final result in results) {
@@ -1593,10 +1617,14 @@ class FilesDB {
     Set<int> collectionsToIgnore, {
     bool dedupeByUploadId = true,
   }) async {
-    final db = await instance.database;
-    final List<Map<String, dynamic>> result =
-        await db.query(filesTable, orderBy: '$columnCreationTime DESC');
-    final List<EnteFile> files = convertToFiles(result);
+    final db = await instance.sqliteAsyncDB;
+
+    final result = await db.getAll(
+      'SELECT * FROM $filesTable ORDER BY $columnCreationTime DESC',
+    );
+    final List<EnteFile> files = await Computer.shared()
+        .compute(convertToFilesForIsolate, param: {"result": result});
+
     final List<EnteFile> deduplicatedFiles = await applyDBFilters(
       files,
       DBFilterOptions(

+ 29 - 29
mobile/lib/generated/intl/messages_en.dart

@@ -85,7 +85,7 @@ class MessageLookup extends MessageLookupByLibrary {
   static String m20(newEmail) => "Email changed to ${newEmail}";
 
   static String m21(email) =>
-      "${email} does not have an ente account.\n\nSend them an invite to share photos.";
+      "${email} does not have an Ente account.\n\nSend them an invite to share photos.";
 
   static String m22(count, formattedNumber) =>
       "${Intl.plural(count, one: '1 file', other: '${formattedNumber} files')} on this device have been backed up safely";
@@ -102,7 +102,7 @@ class MessageLookup extends MessageLookupByLibrary {
   static String m26(endDate) => "Free trial valid till ${endDate}";
 
   static String m27(count) =>
-      "You can still access ${Intl.plural(count, one: 'it', other: 'them')} on ente as long as you have an active subscription";
+      "You can still access ${Intl.plural(count, one: 'it', other: 'them')} on Ente as long as you have an active subscription";
 
   static String m28(sizeInMBorGB) => "Free up ${sizeInMBorGB}";
 
@@ -163,7 +163,7 @@ class MessageLookup extends MessageLookupByLibrary {
       "Hey, can you confirm that this is your ente.io verification ID: ${verificationID}";
 
   static String m50(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";
+      "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 m51(numberOfPeople) =>
       "${Intl.plural(numberOfPeople, zero: 'Share with specific people', one: 'Shared with 1 person', other: 'Shared with ${numberOfPeople} people')}";
@@ -174,9 +174,9 @@ class MessageLookup extends MessageLookupByLibrary {
       "This ${fileType} will be deleted from your device.";
 
   static String m54(fileType) =>
-      "This ${fileType} is in both ente and your device.";
+      "This ${fileType} is in both Ente and your device.";
 
-  static String m55(fileType) => "This ${fileType} will be deleted from ente.";
+  static String m55(fileType) => "This ${fileType} will be deleted from Ente.";
 
   static String m56(storageAmountInGB) => "${storageAmountInGB} GB";
 
@@ -185,7 +185,7 @@ class MessageLookup extends MessageLookupByLibrary {
       "${usedAmount} ${usedStorageUnit} of ${totalAmount} ${totalStorageUnit} used";
 
   static String m58(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\'\'";
+      "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 m59(endDate) =>
       "Your subscription will be cancelled on ${endDate}";
@@ -216,7 +216,7 @@ class MessageLookup extends MessageLookupByLibrary {
   final messages = _notInlinedMessages(_notInlinedMessages);
   static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
         "aNewVersionOfEnteIsAvailable": MessageLookupByLibrary.simpleMessage(
-            "A new version of ente is available."),
+            "A new version of Ente is available."),
         "about": MessageLookupByLibrary.simpleMessage("About"),
         "account": MessageLookupByLibrary.simpleMessage("Account"),
         "accountWelcomeBack":
@@ -244,7 +244,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "addPhotos": MessageLookupByLibrary.simpleMessage("Add photos"),
         "addSelected": MessageLookupByLibrary.simpleMessage("Add selected"),
         "addToAlbum": MessageLookupByLibrary.simpleMessage("Add to album"),
-        "addToEnte": MessageLookupByLibrary.simpleMessage("Add to ente"),
+        "addToEnte": MessageLookupByLibrary.simpleMessage("Add to Ente"),
         "addToHiddenAlbum":
             MessageLookupByLibrary.simpleMessage("Add to hidden album"),
         "addViewer": MessageLookupByLibrary.simpleMessage("Add viewer"),
@@ -431,7 +431,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "codeUsedByYou":
             MessageLookupByLibrary.simpleMessage("Code used by you"),
         "collabLinkSectionDescription": MessageLookupByLibrary.simpleMessage(
-            "Create a link to allow people to add and view photos in your shared album without needing an ente app or account. Great for collecting event photos."),
+            "Create a link to allow people to add and view photos in your shared album without needing an Ente app or account. Great for collecting event photos."),
         "collaborativeLink":
             MessageLookupByLibrary.simpleMessage("Collaborative link"),
         "collaborativeLinkCreatedFor": m11,
@@ -529,7 +529,7 @@ class MessageLookup extends MessageLookupByLibrary {
             "This will delete all empty albums. This is useful when you want to reduce the clutter in your album list."),
         "deleteAll": MessageLookupByLibrary.simpleMessage("Delete All"),
         "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
-            "This account is linked to other ente apps, if you use any. Your uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted."),
+            "This account is linked to other Ente apps, if you use any. Your uploaded data, across all Ente apps, will be scheduled for deletion, and your account will be permanently deleted."),
         "deleteEmailRequest": MessageLookupByLibrary.simpleMessage(
             "Please send an email to <warning>account-deletion@ente.io</warning> from your registered email address."),
         "deleteEmptyAlbums":
@@ -541,7 +541,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "deleteFromDevice":
             MessageLookupByLibrary.simpleMessage("Delete from device"),
         "deleteFromEnte":
-            MessageLookupByLibrary.simpleMessage("Delete from ente"),
+            MessageLookupByLibrary.simpleMessage("Delete from Ente"),
         "deleteItemCount": m14,
         "deleteLocation":
             MessageLookupByLibrary.simpleMessage("Delete location"),
@@ -567,7 +567,7 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("Designed to outlive"),
         "details": MessageLookupByLibrary.simpleMessage("Details"),
         "devAccountChanged": MessageLookupByLibrary.simpleMessage(
-            "The developer account we use to publish ente on App Store has changed. Because of this, you will need to login again.\n\nOur apologies for the inconvenience, but this was unavoidable."),
+            "The developer account we use to publish Ente on App Store has changed. Because of this, you will need to login again.\n\nOur apologies for the inconvenience, but this was unavoidable."),
         "developerSettings":
             MessageLookupByLibrary.simpleMessage("Developer settings"),
         "developerSettingsWarning": MessageLookupByLibrary.simpleMessage(
@@ -575,9 +575,9 @@ class MessageLookup extends MessageLookupByLibrary {
         "deviceCodeHint":
             MessageLookupByLibrary.simpleMessage("Enter the code"),
         "deviceFilesAutoUploading": MessageLookupByLibrary.simpleMessage(
-            "Files added to this device album will automatically get uploaded to ente."),
+            "Files added to this device album will automatically get uploaded to Ente."),
         "deviceLockExplanation": MessageLookupByLibrary.simpleMessage(
-            "Disable the device screen lock when ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster."),
+            "Disable the device screen lock when Ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster."),
         "deviceNotFound":
             MessageLookupByLibrary.simpleMessage("Device not found"),
         "didYouKnow": MessageLookupByLibrary.simpleMessage("Did you know?"),
@@ -643,11 +643,11 @@ class MessageLookup extends MessageLookupByLibrary {
             "End-to-end encrypted by default"),
         "enteCanEncryptAndPreserveFilesOnlyIfYouGrant":
             MessageLookupByLibrary.simpleMessage(
-                "ente can encrypt and preserve files only if you grant access to them"),
+                "Ente can encrypt and preserve files only if you grant access to them"),
         "entePhotosPerm": MessageLookupByLibrary.simpleMessage(
-            "ente <i>needs permission to</i> preserve your photos"),
+            "Ente <i>needs permission to</i> preserve your photos"),
         "enteSubscriptionPitch": MessageLookupByLibrary.simpleMessage(
-            "ente preserves your memories, so they\'re always available to you, even if you lose your device."),
+            "Ente preserves your memories, so they\'re always available to you, even if you lose your device."),
         "enteSubscriptionShareWithFamily": MessageLookupByLibrary.simpleMessage(
             "Your family can be added to your plan as well."),
         "enterAlbumName":
@@ -703,7 +703,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "failedToVerifyPaymentStatus": MessageLookupByLibrary.simpleMessage(
             "Failed to verify payment status"),
         "familyPlanOverview": MessageLookupByLibrary.simpleMessage(
-            "Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other\'s files unless they\'re shared.\n\nFamily plans are available to customers who have a paid ente subscription.\n\nSubscribe now to get started!"),
+            "Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other\'s files unless they\'re shared.\n\nFamily plans are available to customers who have a paid Ente subscription.\n\nSubscribe now to get started!"),
         "familyPlanPortalTitle": MessageLookupByLibrary.simpleMessage("Family"),
         "familyPlans": MessageLookupByLibrary.simpleMessage("Family plans"),
         "faq": MessageLookupByLibrary.simpleMessage("FAQ"),
@@ -777,7 +777,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "iOSOkButton": MessageLookupByLibrary.simpleMessage("OK"),
         "ignoreUpdate": MessageLookupByLibrary.simpleMessage("Ignore"),
         "ignoredFolderUploadReason": MessageLookupByLibrary.simpleMessage(
-            "Some files in this album are ignored from upload because they had previously been deleted from ente."),
+            "Some files in this album are ignored from upload because they had previously been deleted from Ente."),
         "importing": MessageLookupByLibrary.simpleMessage("Importing...."),
         "incorrectCode": MessageLookupByLibrary.simpleMessage("Incorrect code"),
         "incorrectPasswordTitle":
@@ -803,11 +803,11 @@ class MessageLookup extends MessageLookupByLibrary {
         "invalidRecoveryKey": MessageLookupByLibrary.simpleMessage(
             "The recovery key you entered is not valid. Please make sure it contains 24 words, and check the spelling of each.\n\nIf you entered an older recovery code, make sure it is 64 characters long, and check each of them."),
         "invite": MessageLookupByLibrary.simpleMessage("Invite"),
-        "inviteToEnte": MessageLookupByLibrary.simpleMessage("Invite to ente"),
+        "inviteToEnte": MessageLookupByLibrary.simpleMessage("Invite to Ente"),
         "inviteYourFriends":
             MessageLookupByLibrary.simpleMessage("Invite your friends"),
         "inviteYourFriendsToEnte":
-            MessageLookupByLibrary.simpleMessage("Invite your friends to ente"),
+            MessageLookupByLibrary.simpleMessage("Invite your friends to Ente"),
         "itLooksLikeSomethingWentWrongPleaseRetryAfterSome":
             MessageLookupByLibrary.simpleMessage(
                 "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team."),
@@ -934,7 +934,7 @@ class MessageLookup extends MessageLookupByLibrary {
             "Unable to connect to Ente, please check your network settings and contact support if the error persists."),
         "never": MessageLookupByLibrary.simpleMessage("Never"),
         "newAlbum": MessageLookupByLibrary.simpleMessage("New album"),
-        "newToEnte": MessageLookupByLibrary.simpleMessage("New to ente"),
+        "newToEnte": MessageLookupByLibrary.simpleMessage("New to Ente"),
         "newest": MessageLookupByLibrary.simpleMessage("Newest"),
         "no": MessageLookupByLibrary.simpleMessage("No"),
         "noAlbumsSharedByYouYet":
@@ -1226,7 +1226,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "selectYourPlan":
             MessageLookupByLibrary.simpleMessage("Select your plan"),
         "selectedFilesAreNotOnEnte": MessageLookupByLibrary.simpleMessage(
-            "Selected files are not on ente"),
+            "Selected files are not on Ente"),
         "selectedFoldersWillBeEncryptedAndBackedUp":
             MessageLookupByLibrary.simpleMessage(
                 "Selected folders will be encrypted and backed up"),
@@ -1263,15 +1263,15 @@ class MessageLookup extends MessageLookupByLibrary {
             "Share only with the people you want"),
         "shareTextConfirmOthersVerificationID": m49,
         "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage(
-            "Download ente so we can easily share original quality photos and videos\n\nhttps://ente.io"),
+            "Download Ente so we can easily share original quality photos and videos\n\nhttps://ente.io"),
         "shareTextReferralCode": m50,
         "shareWithNonenteUsers":
-            MessageLookupByLibrary.simpleMessage("Share with non-ente users"),
+            MessageLookupByLibrary.simpleMessage("Share with non-Ente users"),
         "shareWithPeopleSectionTitle": m51,
         "shareYourFirstAlbum":
             MessageLookupByLibrary.simpleMessage("Share your first album"),
         "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage(
-            "Create shared and collaborative albums with other ente users, including users on free plans."),
+            "Create shared and collaborative albums with other Ente users, including users on free plans."),
         "sharedByMe": MessageLookupByLibrary.simpleMessage("Shared by me"),
         "sharedByYou": MessageLookupByLibrary.simpleMessage("Shared by you"),
         "sharedPhotoNotifications":
@@ -1301,7 +1301,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "social": MessageLookupByLibrary.simpleMessage("Social"),
         "someItemsAreInBothEnteAndYourDevice":
             MessageLookupByLibrary.simpleMessage(
-                "Some items are in both ente and your device."),
+                "Some items are in both Ente and your device."),
         "someOfTheFilesYouAreTryingToDeleteAre":
             MessageLookupByLibrary.simpleMessage(
                 "Some of the files you are trying to delete are only available on your device and cannot be recovered if deleted"),
@@ -1418,7 +1418,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "trashDaysLeft": m63,
         "tryAgain": MessageLookupByLibrary.simpleMessage("Try again"),
         "turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage(
-            "Turn on backup to automatically upload files added to this device folder to ente."),
+            "Turn on backup to automatically upload files added to this device folder to Ente."),
         "twitter": MessageLookupByLibrary.simpleMessage("Twitter"),
         "twoMonthsFreeOnYearlyPlans": MessageLookupByLibrary.simpleMessage(
             "2 months free on yearly plans"),
@@ -1461,7 +1461,7 @@ class MessageLookup extends MessageLookupByLibrary {
             "Usable storage is limited by your current plan. Excess claimed storage will automatically become usable when you upgrade your plan."),
         "usePublicLinksForPeopleNotOnEnte":
             MessageLookupByLibrary.simpleMessage(
-                "Use public links for people not on ente"),
+                "Use public links for people not on Ente"),
         "useRecoveryKey":
             MessageLookupByLibrary.simpleMessage("Use recovery key"),
         "useSelectedPhoto":

+ 60 - 60
mobile/lib/generated/l10n.dart

@@ -290,10 +290,10 @@ class S {
     );
   }
 
-  /// `ente <i>needs permission to</i> preserve your photos`
+  /// `Ente <i>needs permission to</i> preserve your photos`
   String get entePhotosPerm {
     return Intl.message(
-      'ente <i>needs permission to</i> preserve your photos',
+      'Ente <i>needs permission to</i> preserve your photos',
       name: 'entePhotosPerm',
       desc: '',
       args: [],
@@ -1743,10 +1743,10 @@ class S {
     );
   }
 
-  /// `Create a link to allow people to add and view photos in your shared album without needing an ente app or account. Great for collecting event photos.`
+  /// `Create a link to allow people to add and view photos in your shared album without needing an Ente app or account. Great for collecting event photos.`
   String get collabLinkSectionDescription {
     return Intl.message(
-      'Create a link to allow people to add and view photos in your shared album without needing an ente app or account. Great for collecting event photos.',
+      'Create a link to allow people to add and view photos in your shared album without needing an Ente app or account. Great for collecting event photos.',
       name: 'collabLinkSectionDescription',
       desc: '',
       args: [],
@@ -1773,10 +1773,10 @@ class S {
     );
   }
 
-  /// `Share with non-ente users`
+  /// `Share with non-Ente users`
   String get shareWithNonenteUsers {
     return Intl.message(
-      'Share with non-ente users',
+      'Share with non-Ente users',
       name: 'shareWithNonenteUsers',
       desc: '',
       args: [],
@@ -1843,10 +1843,10 @@ class S {
     );
   }
 
-  /// `Create shared and collaborative albums with other ente users, including users on free plans.`
+  /// `Create shared and collaborative albums with other Ente users, including users on free plans.`
   String get sharedAlbumSectionDescription {
     return Intl.message(
-      'Create shared and collaborative albums with other ente users, including users on free plans.',
+      'Create shared and collaborative albums with other Ente users, including users on free plans.',
       name: 'sharedAlbumSectionDescription',
       desc: '',
       args: [],
@@ -1926,10 +1926,10 @@ class S {
     );
   }
 
-  /// `{email} does not have an ente account.\n\nSend them an invite to share photos.`
+  /// `{email} does not have an Ente account.\n\nSend them an invite to share photos.`
   String emailNoEnteAccount(Object email) {
     return Intl.message(
-      '$email does not have an ente account.\n\nSend them an invite to share photos.',
+      '$email does not have an Ente account.\n\nSend them an invite to share photos.',
       name: 'emailNoEnteAccount',
       desc: '',
       args: [email],
@@ -1976,10 +1976,10 @@ class S {
     );
   }
 
-  /// `Download ente so we can easily share original quality photos and videos\n\nhttps://ente.io`
+  /// `Download Ente so we can easily share original quality photos and videos\n\nhttps://ente.io`
   String get shareTextRecommendUsingEnte {
     return Intl.message(
-      'Download ente so we can easily share original quality photos and videos\n\nhttps://ente.io',
+      'Download Ente so we can easily share original quality photos and videos\n\nhttps://ente.io',
       name: 'shareTextRecommendUsingEnte',
       desc: '',
       args: [],
@@ -2116,11 +2116,11 @@ class S {
     );
   }
 
-  /// `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`
+  /// `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`
   String shareTextReferralCode(
       Object referralCode, Object referralStorageInGB) {
     return Intl.message(
-      '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',
+      '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',
       name: 'shareTextReferralCode',
       desc: '',
       args: [referralCode, referralStorageInGB],
@@ -2532,10 +2532,10 @@ class S {
     );
   }
 
-  /// `Invite to ente`
+  /// `Invite to Ente`
   String get inviteToEnte {
     return Intl.message(
-      'Invite to ente',
+      'Invite to Ente',
       name: 'inviteToEnte',
       desc: '',
       args: [],
@@ -2692,20 +2692,20 @@ class S {
     );
   }
 
-  /// `This {fileType} is in both ente and your device.`
+  /// `This {fileType} is in both Ente and your device.`
   String singleFileInBothLocalAndRemote(Object fileType) {
     return Intl.message(
-      'This $fileType is in both ente and your device.',
+      'This $fileType is in both Ente and your device.',
       name: 'singleFileInBothLocalAndRemote',
       desc: '',
       args: [fileType],
     );
   }
 
-  /// `This {fileType} will be deleted from ente.`
+  /// `This {fileType} will be deleted from Ente.`
   String singleFileInRemoteOnly(Object fileType) {
     return Intl.message(
-      'This $fileType will be deleted from ente.',
+      'This $fileType will be deleted from Ente.',
       name: 'singleFileInRemoteOnly',
       desc: '',
       args: [fileType],
@@ -2722,10 +2722,10 @@ class S {
     );
   }
 
-  /// `Delete from ente`
+  /// `Delete from Ente`
   String get deleteFromEnte {
     return Intl.message(
-      'Delete from ente',
+      'Delete from Ente',
       name: 'deleteFromEnte',
       desc: '',
       args: [],
@@ -3102,10 +3102,10 @@ class S {
     );
   }
 
-  /// `Disable the device screen lock when ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster.`
+  /// `Disable the device screen lock when Ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster.`
   String get deviceLockExplanation {
     return Intl.message(
-      'Disable the device screen lock when ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster.',
+      'Disable the device screen lock when Ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster.',
       name: 'deviceLockExplanation',
       desc: '',
       args: [],
@@ -3302,10 +3302,10 @@ class S {
     );
   }
 
-  /// `A new version of ente is available.`
+  /// `A new version of Ente is available.`
   String get aNewVersionOfEnteIsAvailable {
     return Intl.message(
-      'A new version of ente is available.',
+      'A new version of Ente is available.',
       name: 'aNewVersionOfEnteIsAvailable',
       desc: '',
       args: [],
@@ -3954,10 +3954,10 @@ class S {
     );
   }
 
-  /// `ente preserves your memories, so they're always available to you, even if you lose your device.`
+  /// `Ente preserves your memories, so they're always available to you, even if you lose your device.`
   String get enteSubscriptionPitch {
     return Intl.message(
-      'ente preserves your memories, so they\'re always available to you, even if you lose your device.',
+      'Ente preserves your memories, so they\'re always available to you, even if you lose your device.',
       name: 'enteSubscriptionPitch',
       desc: '',
       args: [],
@@ -3979,7 +3979,7 @@ class S {
     return Intl.message(
       'Current usage is ',
       name: 'currentUsageIs',
-      desc: 'This text is followed by storage usaged',
+      desc: 'This text is followed by storage usage',
       args: [],
     );
   }
@@ -4365,10 +4365,10 @@ class S {
     );
   }
 
-  /// `Your {id} is already linked to another ente account.\nIf you would like to use your {id} with this account, please contact our support''`
+  /// `Your {id} is already linked to another Ente account.\nIf you would like to use your {id} with this account, please contact our support''`
   String subAlreadyLinkedErrMessage(Object id) {
     return Intl.message(
-      'Your $id is already linked to another ente account.\nIf you would like to use your $id with this account, please contact our support\'\'',
+      'Your $id is already linked to another Ente account.\nIf you would like to use your $id with this account, please contact our support\'\'',
       name: 'subAlreadyLinkedErrMessage',
       desc: '',
       args: [id],
@@ -4715,10 +4715,10 @@ class S {
     );
   }
 
-  /// `New to ente`
+  /// `New to Ente`
   String get newToEnte {
     return Intl.message(
-      'New to ente',
+      'New to Ente',
       name: 'newToEnte',
       desc: '',
       args: [],
@@ -4735,10 +4735,10 @@ class S {
     );
   }
 
-  /// `The developer account we use to publish ente on App Store has changed. Because of this, you will need to login again.\n\nOur apologies for the inconvenience, but this was unavoidable.`
+  /// `The developer account we use to publish Ente on App Store has changed. Because of this, you will need to login again.\n\nOur apologies for the inconvenience, but this was unavoidable.`
   String get devAccountChanged {
     return Intl.message(
-      'The developer account we use to publish ente on App Store has changed. Because of this, you will need to login again.\n\nOur apologies for the inconvenience, but this was unavoidable.',
+      'The developer account we use to publish Ente on App Store has changed. Because of this, you will need to login again.\n\nOur apologies for the inconvenience, but this was unavoidable.',
       name: 'devAccountChanged',
       desc: '',
       args: [],
@@ -4806,10 +4806,10 @@ class S {
     );
   }
 
-  /// `ente can encrypt and preserve files only if you grant access to them`
+  /// `Ente can encrypt and preserve files only if you grant access to them`
   String get enteCanEncryptAndPreserveFilesOnlyIfYouGrant {
     return Intl.message(
-      'ente can encrypt and preserve files only if you grant access to them',
+      'Ente can encrypt and preserve files only if you grant access to them',
       name: 'enteCanEncryptAndPreserveFilesOnlyIfYouGrant',
       desc: '',
       args: [],
@@ -4856,10 +4856,10 @@ class S {
     );
   }
 
-  /// `Use public links for people not on ente`
+  /// `Use public links for people not on Ente`
   String get usePublicLinksForPeopleNotOnEnte {
     return Intl.message(
-      'Use public links for people not on ente',
+      'Use public links for people not on Ente',
       name: 'usePublicLinksForPeopleNotOnEnte',
       desc: '',
       args: [],
@@ -4931,7 +4931,7 @@ class S {
     return Intl.message(
       'On <branding>ente</branding>',
       name: 'onEnte',
-      desc: 'The text displayed above albums backed up to ente',
+      desc: 'The text displayed above albums backed up to Ente',
       args: [],
     );
   }
@@ -5177,10 +5177,10 @@ class S {
     );
   }
 
-  /// `Add to ente`
+  /// `Add to Ente`
   String get addToEnte {
     return Intl.message(
-      'Add to ente',
+      'Add to Ente',
       name: 'addToEnte',
       desc: '',
       args: [],
@@ -5655,10 +5655,10 @@ class S {
     );
   }
 
-  /// `Some files in this album are ignored from upload because they had previously been deleted from ente.`
+  /// `Some files in this album are ignored from upload because they had previously been deleted from Ente.`
   String get ignoredFolderUploadReason {
     return Intl.message(
-      'Some files in this album are ignored from upload because they had previously been deleted from ente.',
+      'Some files in this album are ignored from upload because they had previously been deleted from Ente.',
       name: 'ignoredFolderUploadReason',
       desc: '',
       args: [],
@@ -5675,20 +5675,20 @@ class S {
     );
   }
 
-  /// `Files added to this device album will automatically get uploaded to ente.`
+  /// `Files added to this device album will automatically get uploaded to Ente.`
   String get deviceFilesAutoUploading {
     return Intl.message(
-      'Files added to this device album will automatically get uploaded to ente.',
+      'Files added to this device album will automatically get uploaded to Ente.',
       name: 'deviceFilesAutoUploading',
       desc: '',
       args: [],
     );
   }
 
-  /// `Turn on backup to automatically upload files added to this device folder to ente.`
+  /// `Turn on backup to automatically upload files added to this device folder to Ente.`
   String get turnOnBackupForAutoUpload {
     return Intl.message(
-      'Turn on backup to automatically upload files added to this device folder to ente.',
+      'Turn on backup to automatically upload files added to this device folder to Ente.',
       name: 'turnOnBackupForAutoUpload',
       desc: '',
       args: [],
@@ -6149,10 +6149,10 @@ class S {
     );
   }
 
-  /// `You can still access {count, plural, one {it} other {them}} on ente as long as you have an active subscription`
+  /// `You can still access {count, plural, one {it} other {them}} on Ente as long as you have an active subscription`
   String freeUpAccessPostDelete(int count) {
     return Intl.message(
-      'You can still access ${Intl.plural(count, one: 'it', other: 'them')} on ente as long as you have an active subscription',
+      'You can still access ${Intl.plural(count, one: 'it', other: 'them')} on Ente as long as you have an active subscription',
       name: 'freeUpAccessPostDelete',
       desc: '',
       args: [count],
@@ -6469,10 +6469,10 @@ class S {
     );
   }
 
-  /// `Selected files are not on ente`
+  /// `Selected files are not on Ente`
   String get selectedFilesAreNotOnEnte {
     return Intl.message(
-      'Selected files are not on ente',
+      'Selected files are not on Ente',
       name: 'selectedFilesAreNotOnEnte',
       desc: '',
       args: [],
@@ -6559,10 +6559,10 @@ class S {
     );
   }
 
-  /// `Some items are in both ente and your device.`
+  /// `Some items are in both Ente and your device.`
   String get someItemsAreInBothEnteAndYourDevice {
     return Intl.message(
-      'Some items are in both ente and your device.',
+      'Some items are in both Ente and your device.',
       name: 'someItemsAreInBothEnteAndYourDevice',
       desc: '',
       args: [],
@@ -7472,10 +7472,10 @@ class S {
     );
   }
 
-  /// `Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other's files unless they're shared.\n\nFamily plans are available to customers who have a paid ente subscription.\n\nSubscribe now to get started!`
+  /// `Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other's files unless they're shared.\n\nFamily plans are available to customers who have a paid Ente subscription.\n\nSubscribe now to get started!`
   String get familyPlanOverview {
     return Intl.message(
-      'Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other\'s files unless they\'re shared.\n\nFamily plans are available to customers who have a paid ente subscription.\n\nSubscribe now to get started!',
+      'Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other\'s files unless they\'re shared.\n\nFamily plans are available to customers who have a paid Ente subscription.\n\nSubscribe now to get started!',
       name: 'familyPlanOverview',
       desc: '',
       args: [],
@@ -7845,10 +7845,10 @@ class S {
     );
   }
 
-  /// `Invite your friends to ente`
+  /// `Invite your friends to Ente`
   String get inviteYourFriendsToEnte {
     return Intl.message(
-      'Invite your friends to ente',
+      'Invite your friends to Ente',
       name: 'inviteYourFriendsToEnte',
       desc: '',
       args: [],
@@ -7945,10 +7945,10 @@ class S {
     );
   }
 
-  /// `This account is linked to other ente apps, if you use any. Your uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.`
+  /// `This account is linked to other Ente apps, if you use any. Your uploaded data, across all Ente apps, will be scheduled for deletion, and your account will be permanently deleted.`
   String get deleteConfirmDialogBody {
     return Intl.message(
-      'This account is linked to other ente apps, if you use any. Your uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.',
+      'This account is linked to other Ente apps, if you use any. Your uploaded data, across all Ente apps, will be scheduled for deletion, and your account will be permanently deleted.',
       name: 'deleteConfirmDialogBody',
       desc: '',
       args: [],

+ 31 - 31
mobile/lib/l10n/intl_en.arb

@@ -23,7 +23,7 @@
   "sendEmail": "Send email",
   "deleteRequestSLAText": "Your request will be processed within 72 hours.",
   "deleteEmailRequest": "Please send an email to <warning>account-deletion@ente.io</warning> from your registered email address.",
-  "entePhotosPerm": "ente <i>needs permission to</i> preserve your photos",
+  "entePhotosPerm": "Ente <i>needs permission to</i> preserve your photos",
   "ok": "Ok",
   "createAccount": "Create account",
   "createNewAccount": "Create new account",
@@ -225,17 +225,17 @@
     },
     "description": "Number of participants in an album, including the album owner."
   },
-  "collabLinkSectionDescription": "Create a link to allow people to add and view photos in your shared album without needing an ente app or account. Great for collecting event photos.",
+  "collabLinkSectionDescription": "Create a link to allow people to add and view photos in your shared album without needing an Ente app or account. Great for collecting event photos.",
   "collectPhotos": "Collect photos",
   "collaborativeLink": "Collaborative link",
-  "shareWithNonenteUsers": "Share with non-ente users",
+  "shareWithNonenteUsers": "Share with non-Ente users",
   "createPublicLink": "Create public link",
   "sendLink": "Send link",
   "copyLink": "Copy link",
   "linkHasExpired": "Link has expired",
   "publicLinkEnabled": "Public link enabled",
   "shareALink": "Share a link",
-  "sharedAlbumSectionDescription": "Create shared and collaborative albums with other ente users, including users on free plans.",
+  "sharedAlbumSectionDescription": "Create shared and collaborative albums with other Ente users, including users on free plans.",
   "shareWithPeopleSectionTitle": "{numberOfPeople, plural, =0 {Share with specific people} =1 {Shared with 1 person} other {Shared with {numberOfPeople} people}}",
   "@shareWithPeopleSectionTitle": {
     "placeholders": {
@@ -259,12 +259,12 @@
   },
   "verificationId": "Verification ID",
   "verifyEmailID": "Verify {email}",
-  "emailNoEnteAccount": "{email} does not have an ente account.\n\nSend them an invite to share photos.",
+  "emailNoEnteAccount": "{email} does not have an Ente account.\n\nSend them an invite to share photos.",
   "shareMyVerificationID": "Here's my verification ID: {verificationID} for ente.io.",
   "shareTextConfirmOthersVerificationID": "Hey, can you confirm that this is your ente.io verification ID: {verificationID}",
   "somethingWentWrong": "Something went wrong",
   "sendInvite": "Send invite",
-  "shareTextRecommendUsingEnte": "Download ente so we can easily share original quality photos and videos\n\nhttps://ente.io",
+  "shareTextRecommendUsingEnte": "Download Ente so we can easily share original quality photos and videos\n\nhttps://ente.io",
   "done": "Done",
   "applyCodeTitle": "Apply code",
   "enterCodeDescription": "Enter the code provided by your friend to claim free storage for both of you",
@@ -281,7 +281,7 @@
   "claimMore": "Claim more!",
   "theyAlsoGetXGb": "They also get {storageAmountInGB} GB",
   "freeStorageOnReferralSuccess": "{storageAmountInGB} GB each time someone signs up for a paid plan and applies your code",
-  "shareTextReferralCode": "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",
+  "shareTextReferralCode": "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",
   "claimFreeStorage": "Claim free storage",
   "inviteYourFriends": "Invite your friends",
   "failedToFetchReferralDetails": "Unable to fetch referral details. Please try again later.",
@@ -334,7 +334,7 @@
   "removeParticipantBody": "{userEmail} will be removed from this shared album\n\nAny photos added by them will also be removed from the album",
   "keepPhotos": "Keep Photos",
   "deletePhotos": "Delete photos",
-  "inviteToEnte": "Invite to ente",
+  "inviteToEnte": "Invite to Ente",
   "removePublicLink": "Remove public link",
   "disableLinkMessage": "This will remove the public link for accessing \"{albumName}\".",
   "sharing": "Sharing...",
@@ -350,10 +350,10 @@
   "videoSmallCase": "video",
   "photoSmallCase": "photo",
   "singleFileDeleteHighlight": "It will be deleted from all albums.",
-  "singleFileInBothLocalAndRemote": "This {fileType} is in both ente and your device.",
-  "singleFileInRemoteOnly": "This {fileType} will be deleted from ente.",
+  "singleFileInBothLocalAndRemote": "This {fileType} is in both Ente and your device.",
+  "singleFileInRemoteOnly": "This {fileType} will be deleted from Ente.",
   "singleFileDeleteFromDevice": "This {fileType} will be deleted from your device.",
-  "deleteFromEnte": "Delete from ente",
+  "deleteFromEnte": "Delete from Ente",
   "yesDelete": "Yes, delete",
   "movedToTrash": "Moved to trash",
   "deleteFromDevice": "Delete from device",
@@ -445,7 +445,7 @@
   "backupOverMobileData": "Backup over mobile data",
   "backupVideos": "Backup videos",
   "disableAutoLock": "Disable auto lock",
-  "deviceLockExplanation": "Disable the device screen lock when ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster.",
+  "deviceLockExplanation": "Disable the device screen lock when Ente is in the foreground and there is a backup in progress. This is normally not needed, but may help big uploads and initial imports of large libraries complete faster.",
   "about": "About",
   "weAreOpenSource": "We are open source!",
   "privacy": "Privacy",
@@ -465,7 +465,7 @@
   "authToInitiateAccountDeletion": "Please authenticate to initiate account deletion",
   "areYouSureYouWantToLogout": "Are you sure you want to logout?",
   "yesLogout": "Yes, logout",
-  "aNewVersionOfEnteIsAvailable": "A new version of ente is available.",
+  "aNewVersionOfEnteIsAvailable": "A new version of Ente is available.",
   "update": "Update",
   "installManually": "Install manually",
   "criticalUpdateAvailable": "Critical update available",
@@ -554,11 +554,11 @@
   "systemTheme": "System",
   "freeTrial": "Free trial",
   "selectYourPlan": "Select your plan",
-  "enteSubscriptionPitch": "ente preserves your memories, so they're always available to you, even if you lose your device.",
+  "enteSubscriptionPitch": "Ente preserves your memories, so they're always available to you, even if you lose your device.",
   "enteSubscriptionShareWithFamily": "Your family can be added to your plan as well.",
   "currentUsageIs": "Current usage is ",
   "@currentUsageIs": {
-    "description": "This text is followed by storage usaged",
+    "description": "This text is followed by storage usage",
     "examples": [
       "Current usage is 1.2 GB"
     ],
@@ -620,7 +620,7 @@
   "appleId": "Apple ID",
   "playstoreSubscription": "PlayStore subscription",
   "appstoreSubscription": "AppStore subscription",
-  "subAlreadyLinkedErrMessage": "Your {id} is already linked to another ente account.\nIf you would like to use your {id} with this account, please contact our support''",
+  "subAlreadyLinkedErrMessage": "Your {id} is already linked to another Ente account.\nIf you would like to use your {id} with this account, please contact our support''",
   "visitWebToManage": "Please visit web.ente.io to manage your subscription",
   "couldNotUpdateSubscription": "Could not update subscription",
   "pleaseContactSupportAndWeWillBeHappyToHelp": "Please contact support@ente.io and we will be happy to help!",
@@ -665,9 +665,9 @@
   "everywhere": "everywhere",
   "androidIosWebDesktop": "Android, iOS, Web, Desktop",
   "mobileWebDesktop": "Mobile, Web, Desktop",
-  "newToEnte": "New to ente",
+  "newToEnte": "New to Ente",
   "pleaseLoginAgain": "Please login again",
-  "devAccountChanged": "The developer account we use to publish ente on App Store has changed. Because of this, you will need to login again.\n\nOur apologies for the inconvenience, but this was unavoidable.",
+  "devAccountChanged": "The developer account we use to publish Ente on App Store has changed. Because of this, you will need to login again.\n\nOur apologies for the inconvenience, but this was unavoidable.",
   "yourSubscriptionHasExpired": "Your subscription has expired",
   "storageLimitExceeded": "Storage limit exceeded",
   "upgrade": "Upgrade",
@@ -678,12 +678,12 @@
   },
   "backupFailed": "Backup failed",
   "couldNotBackUpTryLater": "We could not backup your data.\nWe will retry later.",
-  "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "ente can encrypt and preserve files only if you grant access to them",
+  "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "Ente can encrypt and preserve files only if you grant access to them",
   "pleaseGrantPermissions": "Please grant permissions",
   "grantPermission": "Grant permission",
   "privateSharing": "Private sharing",
   "shareOnlyWithThePeopleYouWant": "Share only with the people you want",
-  "usePublicLinksForPeopleNotOnEnte": "Use public links for people not on ente",
+  "usePublicLinksForPeopleNotOnEnte": "Use public links for people not on Ente",
   "allowPeopleToAddPhotos": "Allow people to add photos",
   "shareAnAlbumNow": "Share an album now",
   "collectEventPhotos": "Collect event photos",
@@ -695,7 +695,7 @@
   },
   "onDevice": "On device",
   "@onEnte": {
-    "description": "The text displayed above albums backed up to ente",
+    "description": "The text displayed above albums backed up to Ente",
     "type": "text"
   },
   "onEnte": "On <branding>ente</branding>",
@@ -741,7 +741,7 @@
   "saveCollage": "Save collage",
   "collageSaved": "Collage saved to gallery",
   "collageLayout": "Layout",
-  "addToEnte": "Add to ente",
+  "addToEnte": "Add to Ente",
   "addToAlbum": "Add to album",
   "delete": "Delete",
   "hide": "Hide",
@@ -806,10 +806,10 @@
   "photosAddedByYouWillBeRemovedFromTheAlbum": "Photos added by you will be removed from the album",
   "youveNoFilesInThisAlbumThatCanBeDeleted": "You've no files in this album that can be deleted",
   "youDontHaveAnyArchivedItems": "You don't have any archived items.",
-  "ignoredFolderUploadReason": "Some files in this album are ignored from upload because they had previously been deleted from ente.",
+  "ignoredFolderUploadReason": "Some files in this album are ignored from upload because they had previously been deleted from Ente.",
   "resetIgnoredFiles": "Reset ignored files",
-  "deviceFilesAutoUploading": "Files added to this device album will automatically get uploaded to ente.",
-  "turnOnBackupForAutoUpload": "Turn on backup to automatically upload files added to this device folder to ente.",
+  "deviceFilesAutoUploading": "Files added to this device album will automatically get uploaded to Ente.",
+  "turnOnBackupForAutoUpload": "Turn on backup to automatically upload files added to this device folder to Ente.",
   "noHiddenPhotosOrVideos": "No hidden photos or videos",
   "toHideAPhotoOrVideo": "To hide a photo or video",
   "openTheItem": "• Open the item",
@@ -886,7 +886,7 @@
   "@freeUpSpaceSaving": {
     "description": "Text to tell user how much space they can free up by deleting items from the device"
   },
-  "freeUpAccessPostDelete": "You can still access {count, plural, one {it} other {them}} on ente as long as you have an active subscription",
+  "freeUpAccessPostDelete": "You can still access {count, plural, one {it} other {them}} on Ente as long as you have an active subscription",
   "@freeUpAccessPostDelete": {
     "placeholders": {
       "count": {
@@ -937,7 +937,7 @@
   "renameFile": "Rename file",
   "enterFileName": "Enter file name",
   "filesDeleted": "Files deleted",
-  "selectedFilesAreNotOnEnte": "Selected files are not on ente",
+  "selectedFilesAreNotOnEnte": "Selected files are not on Ente",
   "thisActionCannotBeUndone": "This action cannot be undone",
   "emptyTrash": "Empty trash?",
   "permDeleteWarning": "All items in trash will be permanently deleted\n\nThis action cannot be undone",
@@ -946,7 +946,7 @@
   "permanentlyDeleteFromDevice": "Permanently delete from device?",
   "someOfTheFilesYouAreTryingToDeleteAre": "Some of the files you are trying to delete are only available on your device and cannot be recovered if deleted",
   "theyWillBeDeletedFromAllAlbums": "They will be deleted from all albums.",
-  "someItemsAreInBothEnteAndYourDevice": "Some items are in both ente and your device.",
+  "someItemsAreInBothEnteAndYourDevice": "Some items are in both Ente and your device.",
   "selectedItemsWillBeDeletedFromAllAlbumsAndMoved": "Selected items will be deleted from all albums and moved to trash.",
   "theseItemsWillBeDeletedFromYourDevice": "These items will be deleted from your device.",
   "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.",
@@ -1052,7 +1052,7 @@
   },
   "setRadius": "Set radius",
   "familyPlanPortalTitle": "Family",
-  "familyPlanOverview": "Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other's files unless they're shared.\n\nFamily plans are available to customers who have a paid ente subscription.\n\nSubscribe now to get started!",
+  "familyPlanOverview": "Add 5 family members to your existing plan without paying extra.\n\nEach member gets their own private space, and cannot see each other's files unless they're shared.\n\nFamily plans are available to customers who have a paid Ente subscription.\n\nSubscribe now to get started!",
   "androidBiometricHint": "Verify identity",
   "@androidBiometricHint": {
     "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
@@ -1130,7 +1130,7 @@
   "noAlbumsSharedByYouYet": "No albums shared by you yet",
   "sharedWithYou": "Shared with you",
   "sharedByYou": "Shared by you",
-  "inviteYourFriendsToEnte": "Invite your friends to ente",
+  "inviteYourFriendsToEnte": "Invite your friends to Ente",
   "failedToDownloadVideo": "Failed to download video",
   "hiding": "Hiding...",
   "unhiding": "Unhiding...",
@@ -1140,7 +1140,7 @@
   "addToHiddenAlbum": "Add to hidden album",
   "moveToHiddenAlbum": "Move to hidden album",
   "fileTypes": "File types",
-  "deleteConfirmDialogBody": "This account is linked to other ente apps, if you use any. Your uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
+  "deleteConfirmDialogBody": "This account is linked to other Ente apps, if you use any. Your uploaded data, across all Ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
   "hearUsWhereTitle": "How did you hear about Ente? (optional)",
   "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
   "viewAddOnButton": "View add-ons",

+ 39 - 32
mobile/lib/l10n/intl_pt.arb

@@ -225,7 +225,7 @@
     },
     "description": "Number of participants in an album, including the album owner."
   },
-  "collabLinkSectionDescription": "Crie um link para permitir pessoas adicionar e ver fotos no seu álbum compartilhado sem a necessidade do aplicativo ou uma conta Ente. Ótimo para colecionar fotos de eventos.",
+  "collabLinkSectionDescription": "Crie um link para permitir que as pessoas adicionem e vejam fotos no seu álbum compartilhado sem a necessidade do aplicativo ou uma conta Ente. Ótimo para colecionar fotos de eventos.",
   "collectPhotos": "Colete fotos",
   "collaborativeLink": "Link Colaborativo",
   "shareWithNonenteUsers": "Compartilhar com usuários não-Ente",
@@ -259,12 +259,12 @@
   },
   "verificationId": "ID de Verificação",
   "verifyEmailID": "Verificar {email}",
-  "emailNoEnteAccount": "{email} Não possui uma conta Ente.\n\nEnvie um convite para compartilhar fotos.",
+  "emailNoEnteAccount": "{email} não possui uma conta Ente.\n\nEnvie um convite para compartilhar fotos.",
   "shareMyVerificationID": "Aqui está meu ID de verificação para o Ente.io: {verificationID}",
   "shareTextConfirmOthersVerificationID": "Ei, você pode confirmar que este é seu ID de verificação do Ente.io? {verificationID}",
   "somethingWentWrong": "Algo deu errado",
   "sendInvite": "Enviar convite",
-  "shareTextRecommendUsingEnte": "Baixe o Ente para podermos compartilhar facilmente fotos e vídeos de alta qualidade\n\nhttps://ente.io",
+  "shareTextRecommendUsingEnte": "Baixe o Ente para que possamos compartilhar facilmente fotos e vídeos de qualidade original\n\nhttps://ente.io",
   "done": "Concluído",
   "applyCodeTitle": "Aplicar código",
   "enterCodeDescription": "Digite o código fornecido pelo seu amigo para reivindicar o armazenamento gratuito para vocês dois",
@@ -350,8 +350,8 @@
   "videoSmallCase": "Video",
   "photoSmallCase": "Foto",
   "singleFileDeleteHighlight": "Ele será excluído de todos os álbuns.",
-  "singleFileInBothLocalAndRemote": "Este {fileType} está em ente e no seu dispositivo.",
-  "singleFileInRemoteOnly": "Este {fileType} será excluído do ente.",
+  "singleFileInBothLocalAndRemote": "Este {fileType} está tanto no Ente quanto no seu dispositivo.",
+  "singleFileInRemoteOnly": "Este {fileType} será excluído do Ente.",
   "singleFileDeleteFromDevice": "Este {fileType} será excluído do seu dispositivo.",
   "deleteFromEnte": "Excluir do ente",
   "yesDelete": "Sim, excluir",
@@ -445,7 +445,7 @@
   "backupOverMobileData": "Backup de dados móveis",
   "backupVideos": "Backup de videos",
   "disableAutoLock": "Desativar bloqueio automático",
-  "deviceLockExplanation": "Desative o bloqueio de tela do dispositivo quando o ente estiver em primeiro plano e houver um backup em andamento. Isso normalmente não é necessário, mas pode ajudar grandes uploads e importações iniciais de grandes bibliotecas a serem concluídos mais rapidamente.",
+  "deviceLockExplanation": "Desative o bloqueio de tela do dispositivo quando o Ente estiver em primeiro plano e houver um backup em andamento. Isso normalmente não é necessário, mas pode ajudar nos envios grandes e importações iniciais de grandes bibliotecas a serem concluídos mais rapidamente.",
   "about": "Sobre",
   "weAreOpenSource": "Somos de código aberto!",
   "privacy": "Privacidade",
@@ -464,8 +464,8 @@
   "logout": "Encerrar sessão",
   "authToInitiateAccountDeletion": "Por favor, autentique-se para iniciar a exclusão de conta",
   "areYouSureYouWantToLogout": "Você tem certeza que deseja encerrar a sessão?",
-  "yesLogout": "Sim, terminar sessão",
-  "aNewVersionOfEnteIsAvailable": "Uma nova versão do ente está disponível.",
+  "yesLogout": "Sim, encerrar sessão",
+  "aNewVersionOfEnteIsAvailable": "Uma nova versão do Ente está disponível.",
   "update": "Atualização",
   "installManually": "Instalar manualmente",
   "criticalUpdateAvailable": "Atualização crítica disponível",
@@ -515,7 +515,7 @@
     }
   },
   "familyPlans": "Plano familiar",
-  "referrals": "Indicações",
+  "referrals": "Referências",
   "notifications": "Notificações",
   "sharedPhotoNotifications": "Novas fotos compartilhadas",
   "sharedPhotoNotificationsExplanation": "Receber notificações quando alguém adicionar uma foto em um álbum compartilhado que você faz parte",
@@ -554,11 +554,11 @@
   "systemTheme": "Sistema",
   "freeTrial": "Teste gratuito",
   "selectYourPlan": "Selecione seu plano",
-  "enteSubscriptionPitch": "O ente preserva suas memórias, então eles estão sempre disponíveis para você, mesmo se você perder o seu dispositivo.",
+  "enteSubscriptionPitch": "O Ente preserva suas memórias, então eles estão sempre disponíveis para você, mesmo se você perder o seu dispositivo.",
   "enteSubscriptionShareWithFamily": "Sua família também pode ser adicionada ao seu plano.",
   "currentUsageIs": "O uso atual é ",
   "@currentUsageIs": {
-    "description": "This text is followed by storage usaged",
+    "description": "This text is followed by storage usage",
     "examples": {
       "0": "Current usage is 1.2 GB"
     },
@@ -620,7 +620,7 @@
   "appleId": "ID da Apple",
   "playstoreSubscription": "Assinatura da PlayStore",
   "appstoreSubscription": "Assinatura da AppStore",
-  "subAlreadyLinkedErrMessage": "Seu {id} já está vinculado a outra conta ente.\nSe você gostaria de usar seu {id} com esta conta, por favor contate nosso suporte''",
+  "subAlreadyLinkedErrMessage": "Seu {id} já está vinculado a outra conta Ente.\nSe você gostaria de usar seu {id} com esta conta, por favor contate nosso suporte''",
   "visitWebToManage": "Por favor visite web.ente.io para gerenciar sua assinatura",
   "couldNotUpdateSubscription": "Não foi possível atualizar a assinatura",
   "pleaseContactSupportAndWeWillBeHappyToHelp": "Por favor, entre em contato com support@ente.io e nós ficaremos felizes em ajudar!",
@@ -665,9 +665,9 @@
   "everywhere": "em todos os lugares",
   "androidIosWebDesktop": "Android, iOS, Web, Desktop",
   "mobileWebDesktop": "Mobile, Web, Desktop",
-  "newToEnte": "Novo no ente",
+  "newToEnte": "Novo no Ente",
   "pleaseLoginAgain": "Por favor, faça login novamente",
-  "devAccountChanged": "A conta de desenvolvedor que usamos para publicar o ente na App Store foi alterada. Por esse motivo, você precisará fazer login novamente.\n\nPedimos desculpas pelo inconveniente, mas isso era inevitável.",
+  "devAccountChanged": "A conta de desenvolvedor que usamos para publicar o Ente na App Store foi alterada. Por esse motivo, você precisará fazer entrar novamente.\n\nPedimos desculpas pelo inconveniente, mas isso era inevitável.",
   "yourSubscriptionHasExpired": "A sua assinatura expirou",
   "storageLimitExceeded": "Limite de armazenamento excedido",
   "upgrade": "Aprimorar",
@@ -678,12 +678,12 @@
   },
   "backupFailed": "Erro ao efetuar o backup",
   "couldNotBackUpTryLater": "Não foi possível fazer o backup de seus dados.\nTentaremos novamente mais tarde.",
-  "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "ente pode criptografar e preservar arquivos somente se você conceder acesso a eles",
+  "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "Ente pode criptografar e preservar arquivos apenas se você conceder acesso a eles",
   "pleaseGrantPermissions": "Por favor, conceda as permissões",
   "grantPermission": "Garantir permissão",
   "privateSharing": "Compartilhamento privado",
   "shareOnlyWithThePeopleYouWant": "Compartilhar apenas com as pessoas que você quiser",
-  "usePublicLinksForPeopleNotOnEnte": "Usar links públicos para pessoas que não estão no ente",
+  "usePublicLinksForPeopleNotOnEnte": "Usar links públicos para pessoas que não estão no Ente",
   "allowPeopleToAddPhotos": "Permitir que pessoas adicionem fotos",
   "shareAnAlbumNow": "Compartilhar um álbum agora",
   "collectEventPhotos": "Coletar fotos do evento",
@@ -695,7 +695,7 @@
   },
   "onDevice": "No dispositivo",
   "@onEnte": {
-    "description": "The text displayed above albums backed up to ente",
+    "description": "The text displayed above albums backed up to Ente",
     "type": "text"
   },
   "onEnte": "Em <branding>ente</branding>",
@@ -741,9 +741,9 @@
   "saveCollage": "Salvar colagem",
   "collageSaved": "Colagem salva na galeria",
   "collageLayout": "Layout",
-  "addToEnte": "Adicionar ao ente",
+  "addToEnte": "Adicionar ao Ente",
   "addToAlbum": "Adicionar ao álbum",
-  "delete": "Apagar",
+  "delete": "Excluir",
   "hide": "Ocultar",
   "share": "Compartilhar",
   "unhideToAlbum": "Reexibir para o álbum",
@@ -806,10 +806,10 @@
   "photosAddedByYouWillBeRemovedFromTheAlbum": "As fotos adicionadas por você serão removidas do álbum",
   "youveNoFilesInThisAlbumThatCanBeDeleted": "Você não tem arquivos neste álbum que possam ser excluídos",
   "youDontHaveAnyArchivedItems": "Você não tem nenhum item arquivado.",
-  "ignoredFolderUploadReason": "Alguns arquivos neste álbum são ignorados do upload porque eles tinham sido anteriormente excluídos do ente.",
+  "ignoredFolderUploadReason": "Alguns arquivos neste álbum são ignorados do envio porque eles tinham sido anteriormente excluídos do Ente.",
   "resetIgnoredFiles": "Redefinir arquivos ignorados",
-  "deviceFilesAutoUploading": "Arquivos adicionados a este álbum do dispositivo serão automaticamente enviados para o ente.",
-  "turnOnBackupForAutoUpload": "Ative o backup para enviar automaticamente arquivos adicionados a esta pasta do dispositivo para o ente.",
+  "deviceFilesAutoUploading": "Arquivos adicionados a este álbum do dispositivo serão automaticamente enviados para o Ente.",
+  "turnOnBackupForAutoUpload": "Ative o backup para enviar automaticamente arquivos adicionados a esta pasta do dispositivo para o Ente.",
   "noHiddenPhotosOrVideos": "Nenhuma foto ou vídeos ocultos",
   "toHideAPhotoOrVideo": "Para ocultar uma foto ou vídeo",
   "openTheItem": "• Abra o item",
@@ -886,7 +886,7 @@
   "@freeUpSpaceSaving": {
     "description": "Text to tell user how much space they can free up by deleting items from the device"
   },
-  "freeUpAccessPostDelete": "Você ainda pode acessar {count, plural, one {ele} other {eles}} no ente contanto que você tenha uma assinatura ativa",
+  "freeUpAccessPostDelete": "Você ainda pode acessar {count, plural, one {ele} other {eles}} no Ente contanto que você tenha uma assinatura ativa",
   "@freeUpAccessPostDelete": {
     "placeholders": {
       "count": {
@@ -937,7 +937,7 @@
   "renameFile": "Renomear arquivo",
   "enterFileName": "Digite o nome do arquivo",
   "filesDeleted": "Arquivos excluídos",
-  "selectedFilesAreNotOnEnte": "Os arquivos selecionados não estão no ente",
+  "selectedFilesAreNotOnEnte": "Os arquivos selecionados não estão no Ente",
   "thisActionCannotBeUndone": "Esta ação não pode ser desfeita",
   "emptyTrash": "Esvaziar a lixeira?",
   "permDeleteWarning": "Todos os itens na lixeira serão excluídos permanentemente\n\nEsta ação não pode ser desfeita",
@@ -946,7 +946,7 @@
   "permanentlyDeleteFromDevice": "Excluir permanentemente do dispositivo?",
   "someOfTheFilesYouAreTryingToDeleteAre": "Alguns dos arquivos que você está tentando excluir só estão disponíveis no seu dispositivo e não podem ser recuperados se forem excluídos",
   "theyWillBeDeletedFromAllAlbums": "Ele será excluído de todos os álbuns.",
-  "someItemsAreInBothEnteAndYourDevice": "Alguns itens estão tanto no ente quanto no seu dispositivo.",
+  "someItemsAreInBothEnteAndYourDevice": "Alguns itens estão tanto no Ente quanto no seu dispositivo.",
   "selectedItemsWillBeDeletedFromAllAlbumsAndMoved": "Os itens selecionados serão excluídos de todos os álbuns e movidos para o lixo.",
   "theseItemsWillBeDeletedFromYourDevice": "Estes itens serão excluídos do seu dispositivo.",
   "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Parece que algo deu errado. Por favor, tente novamente mais tarde. Se o erro persistir, entre em contato com nossa equipe de suporte.",
@@ -1052,7 +1052,7 @@
   },
   "setRadius": "Definir raio",
   "familyPlanPortalTitle": "Família",
-  "familyPlanOverview": "Adicione 5 membros da família ao seu plano existente sem pagar a mais.\n\nCada membro recebe seu próprio espaço privado, e nenhum membro pode ver os arquivos uns dos outros a menos que sejam compartilhados.\n\nPlanos de família estão disponíveis para os clientes que têm uma assinatura de ente paga.\n\nassine agora para começar!",
+  "familyPlanOverview": "Adicione 5 membros da família ao seu plano existente sem pagar a mais.\n\nCada membro recebe seu próprio espaço privado, e nenhum membro pode ver os arquivos uns dos outros a menos que sejam compartilhados.\n\nPlanos de família estão disponíveis para os clientes que têm uma assinatura do Ente paga.\n\nAssine agora para começar!",
   "androidBiometricHint": "Verificar identidade",
   "@androidBiometricHint": {
     "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
@@ -1113,7 +1113,7 @@
   },
   "maps": "Mapas",
   "enableMaps": "Habilitar mapa",
-  "enableMapsDesc": "Isto mostrará suas fotos em um mapa do mundo.\n\nEste mapa é hospedado pelo Open Street Map, e os exatos locais de suas fotos nunca são compartilhados.\n\nVocê pode desativar esse recurso a qualquer momento nas Configurações.",
+  "enableMapsDesc": "Isto mostrará suas fotos em um mapa do mundo.\n\nEste mapa é hospedado pelo OpenStreetMap, e os exatos locais de suas fotos nunca são compartilhados.\n\nVocê pode desativar esse recurso a qualquer momento nas Configurações.",
   "quickLinks": "Links rápidos",
   "selectItemsToAdd": "Selecionar itens para adicionar",
   "addSelected": "Adicionar selecionado",
@@ -1130,7 +1130,7 @@
   "noAlbumsSharedByYouYet": "Nenhum álbum compartilhado por você ainda",
   "sharedWithYou": "Compartilhado com você",
   "sharedByYou": "Compartilhado por você",
-  "inviteYourFriendsToEnte": "Convide seus amigos ao ente",
+  "inviteYourFriendsToEnte": "Convide seus amigos ao Ente",
   "failedToDownloadVideo": "Falha ao baixar vídeo",
   "hiding": "Ocultando...",
   "unhiding": "Desocultando...",
@@ -1140,7 +1140,7 @@
   "addToHiddenAlbum": "Adicionar a álbum oculto",
   "moveToHiddenAlbum": "Mover para álbum oculto",
   "fileTypes": "Tipos de arquivo",
-  "deleteConfirmDialogBody": "Esta conta está vinculada a outros aplicativos ente, se você usar algum. Seus dados enviados, em todos os aplicativos ente, serão agendados para exclusão, e sua conta será excluída permanentemente.",
+  "deleteConfirmDialogBody": "Esta conta está vinculada a outros aplicativos Ente, se você usar algum. Seus dados enviados, em todos os aplicativos Ente, serão agendados para exclusão, e sua conta será excluída permanentemente.",
   "hearUsWhereTitle": "Como você ouviu sobre o Ente? (opcional)",
   "hearUsExplanation": "Não rastreamos instalações do aplicativo. Seria útil se você nos contasse onde nos encontrou!",
   "viewAddOnButton": "Ver complementos",
@@ -1178,9 +1178,9 @@
   "contacts": "Contatos",
   "noInternetConnection": "Sem conexão à internet",
   "pleaseCheckYourInternetConnectionAndTryAgain": "Verifique sua conexão com a internet e tente novamente.",
-  "signOutFromOtherDevices": "Terminar sessão em outros dispositivos",
+  "signOutFromOtherDevices": "Encerrar sessão em outros dispositivos",
   "signOutOtherBody": "Se você acha que alguém pode saber sua senha, você pode forçar todos os outros dispositivos que estão com sua conta a desconectar.",
-  "signOutOtherDevices": "Terminar sessão em outros dispositivos",
+  "signOutOtherDevices": "Encerrar sessão em outros dispositivos",
   "doNotSignOut": "Não encerrar sessão",
   "editLocation": "Editar local",
   "selectALocation": "Selecionar um local",
@@ -1205,5 +1205,12 @@
   "addViewers": "{count, plural, zero {Adicionar visualizador} one {Adicionar visualizador} other {Adicionar Visualizadores}}",
   "addCollaborators": "{count, plural, zero {Adicionar colaborador} one {Adicionar coloborador} other {Adicionar colaboradores}}",
   "longPressAnEmailToVerifyEndToEndEncryption": "Pressione e segure um e-mail para verificar a criptografia de ponta a ponta.",
-  "createCollaborativeLink": "Create collaborative link"
+  "developerSettingsWarning": "Tem certeza de que deseja modificar as configurações de Desenvolvedor?",
+  "developerSettings": "Configurações de desenvolvedor",
+  "serverEndpoint": "Servidor endpoint",
+  "invalidEndpoint": "Endpoint inválido",
+  "invalidEndpointMessage": "Desculpe, o endpoint que você inseriu é inválido. Por favor, insira um endpoint válido e tente novamente.",
+  "endpointUpdatedMessage": "Endpoint atualizado com sucesso",
+  "customEndpoint": "Conectado a {endpoint}",
+  "createCollaborativeLink": "Criar link colaborativo"
 }

+ 53 - 46
mobile/lib/l10n/intl_zh.arb

@@ -23,7 +23,7 @@
   "sendEmail": "发送电子邮件",
   "deleteRequestSLAText": "您的请求将在 72 小时内处理。",
   "deleteEmailRequest": "请从您注册的电子邮件地址发送电子邮件到 <warning>account-delettion@ente.io</warning>。",
-  "entePhotosPerm": "ente <i>需要许可</i>才能保存您的照片",
+  "entePhotosPerm": "Ente <i>需要许可</i>才能保存您的照片",
   "ok": "OK",
   "createAccount": "创建账户",
   "createNewAccount": "创建新账号",
@@ -127,7 +127,7 @@
       }
     }
   },
-  "twofactorSetup": "双因素认证设置",
+  "twofactorSetup": "双认证设置",
   "enterCode": "输入代码",
   "scanCode": "扫描二维码/条码",
   "codeCopiedToClipboard": "代码已复制到剪贴板",
@@ -138,9 +138,9 @@
   "confirm": "确认",
   "setupComplete": "设置完成",
   "saveYourRecoveryKeyIfYouHaventAlready": "若您尚未保存,请妥善保存此恢复密钥",
-  "thisCanBeUsedToRecoverYourAccountIfYou": "如果您丢失了双因素验证方式,这可以用来恢复您的账户",
-  "twofactorAuthenticationPageTitle": "双因素认证",
-  "lostDevice": "丢失了设备吗?",
+  "thisCanBeUsedToRecoverYourAccountIfYou": "如果您丢失了双重认证方式,这可以用来恢复您的账户",
+  "twofactorAuthenticationPageTitle": "双认证",
+  "lostDevice": "设备丢失?",
   "verifyingRecoveryKey": "正在验证恢复密钥...",
   "recoveryKeyVerified": "恢复密钥已验证",
   "recoveryKeySuccessBody": "太棒了! 您的恢复密钥是有效的。 感谢您的验证。\n\n请记住要安全备份您的恢复密钥。",
@@ -225,17 +225,17 @@
     },
     "description": "Number of participants in an album, including the album owner."
   },
-  "collabLinkSectionDescription": "创建一个链接以允许其他人在您的共享相册中添加和查看照片,而无需应用程序或ente账户。 非常适合收集活动照片。",
+  "collabLinkSectionDescription": "创建一个链接来让他人无需 Ente 应用程序或账户即可在您的共享相册中添加和查看照片。非常适合收集活动照片。",
   "collectPhotos": "收集照片",
   "collaborativeLink": "协作链接",
-  "shareWithNonenteUsers": "与非ente 用户分享",
+  "shareWithNonenteUsers": "与非 Ente 用户共享",
   "createPublicLink": "创建公开链接",
   "sendLink": "发送链接",
   "copyLink": "复制链接",
   "linkHasExpired": "链接已过期",
   "publicLinkEnabled": "公开链接已启用",
   "shareALink": "分享链接",
-  "sharedAlbumSectionDescription": "与其他ente用户创建共享和协作相册,包括免费计划的用户。",
+  "sharedAlbumSectionDescription": "与其他 Ente 用户(包括免费计划用户)创建共享和协作相册。",
   "shareWithPeopleSectionTitle": "{numberOfPeople, plural, =0 {与特定人员共享} =1 {与 1 人共享} other {与 {numberOfPeople} 人共享}}",
   "@shareWithPeopleSectionTitle": {
     "placeholders": {
@@ -259,12 +259,12 @@
   },
   "verificationId": "验证 ID",
   "verifyEmailID": "验证 {email}",
-  "emailNoEnteAccount": "{email} 没有 ente 账户。\n\n向他们发送分享照片的邀请。",
+  "emailNoEnteAccount": "{email} 没有 Ente 帐户。\n\n向他们发出共享照片的邀请。",
   "shareMyVerificationID": "这是我的ente.io 的验证 ID: {verificationID}。",
   "shareTextConfirmOthersVerificationID": "嘿,你能确认这是你的 ente.io 验证 ID吗:{verificationID}",
   "somethingWentWrong": "出了些问题",
   "sendInvite": "发送邀请",
-  "shareTextRecommendUsingEnte": "下载 ente,以便我们轻松分享原始质量的照片和视频\n\nhttps://ente.io",
+  "shareTextRecommendUsingEnte": "下载 Ente,让我们轻松共享高质量的原始照片和视频",
   "done": "已完成",
   "applyCodeTitle": "应用代码",
   "enterCodeDescription": "输入您的朋友提供的代码来为您申请免费存储",
@@ -281,7 +281,7 @@
   "claimMore": "领取更多!",
   "theyAlsoGetXGb": "他们也会获得 {storageAmountInGB} GB",
   "freeStorageOnReferralSuccess": "每当有人使用您的代码注册付费计划时您将获得{storageAmountInGB} GB",
-  "shareTextReferralCode": "ente推荐码: {referralCode} \n\n注册付费计划后在设置 → 常规 → 推荐中应用它以免费获得 {referralStorageInGB} GB空间\n\nhttps://ente.io",
+  "shareTextReferralCode": "Ente 推荐代码:{referralCode}\n\n在 \"设置\"→\"通用\"→\"推荐 \"中应用它,即可在注册付费计划后免费获得 {referralStorageInGB} GB 存储空间\n\nhttps://ente.io",
   "claimFreeStorage": "领取免费存储",
   "inviteYourFriends": "邀请您的朋友",
   "failedToFetchReferralDetails": "无法获取引荐详细信息。 请稍后再试。",
@@ -334,9 +334,9 @@
   "removeParticipantBody": "{userEmail} 将从这个共享相册中删除\n\nTA们添加的任何照片也将从相册中删除",
   "keepPhotos": "保留照片",
   "deletePhotos": "删除照片",
-  "inviteToEnte": "邀请到 ente",
+  "inviteToEnte": "邀请到 Ente",
   "removePublicLink": "删除公开链接",
-  "disableLinkMessage": "这将删除用于访问\"{albumName}\"的公链接。",
+  "disableLinkMessage": "这将删除用于访问\"{albumName}\"的公链接。",
   "sharing": "正在分享...",
   "youCannotShareWithYourself": "莫开玩笑,您不能与自己分享",
   "archive": "存档",
@@ -350,10 +350,10 @@
   "videoSmallCase": "视频",
   "photoSmallCase": "照片",
   "singleFileDeleteHighlight": "它将从所有相册中删除。",
-  "singleFileInBothLocalAndRemote": "此 {fileType} 同时在ente和您的设备中。",
-  "singleFileInRemoteOnly": "此 {fileType} 将从ente中删除。",
+  "singleFileInBothLocalAndRemote": "{fileType} 已同时存在于 Ente 和您的设备中。",
+  "singleFileInRemoteOnly": "{fileType} 将从 Ente 中删除。",
   "singleFileDeleteFromDevice": "此 {fileType} 将从您的设备中删除。",
-  "deleteFromEnte": "从ente 中删除",
+  "deleteFromEnte": "从 Ente 中删除",
   "yesDelete": "是的, 删除",
   "movedToTrash": "已移至回收站",
   "deleteFromDevice": "从设备中删除",
@@ -445,7 +445,7 @@
   "backupOverMobileData": "通过移动数据备份",
   "backupVideos": "备份视频",
   "disableAutoLock": "禁用自动锁定",
-  "deviceLockExplanation": "当 ente 在前台并且正在进行备份时禁用设备屏幕锁定。 这通常不需要,但可以帮助大型库的大上传和初始导入更快地完成。",
+  "deviceLockExplanation": "当 Ente 置于前台且正在进行备份时将禁用设备屏幕锁定。这通常是不需要的,但可能有助于更快地完成大型上传和大型库的初始导入。",
   "about": "关于",
   "weAreOpenSource": "我们是开源的 !",
   "privacy": "隐私",
@@ -465,7 +465,7 @@
   "authToInitiateAccountDeletion": "请进行身份验证以启动账户删除",
   "areYouSureYouWantToLogout": "您确定要退出登录吗?",
   "yesLogout": "是的,退出登陆",
-  "aNewVersionOfEnteIsAvailable": "有新版本的 ente 可供使用。",
+  "aNewVersionOfEnteIsAvailable": "有新版本的 Ente 可供使用。",
   "update": "更新",
   "installManually": "手动安装",
   "criticalUpdateAvailable": "可用的关键更新",
@@ -515,7 +515,7 @@
     }
   },
   "familyPlans": "家庭计划",
-  "referrals": "推荐",
+  "referrals": "推荐",
   "notifications": "通知",
   "sharedPhotoNotifications": "新共享的照片",
   "sharedPhotoNotificationsExplanation": "当有人将照片添加到您所属的共享相册时收到通知",
@@ -523,15 +523,15 @@
   "general": "通用",
   "security": "安全",
   "authToViewYourRecoveryKey": "请验证以查看您的恢复密钥",
-  "twofactor": "两因素认证",
-  "authToConfigureTwofactorAuthentication": "请进行身份验证以配置双重身份证",
+  "twofactor": "双重认证",
+  "authToConfigureTwofactorAuthentication": "请进行身份验证以配置双重身份证",
   "lockscreen": "锁屏",
   "authToChangeLockscreenSetting": "请验证以更改锁屏设置",
   "lockScreenEnablePreSteps": "要启用锁屏,请在系统设置中设置设备密码或屏幕锁定。",
   "viewActiveSessions": "查看活动会话",
   "authToViewYourActiveSessions": "请验证以查看您的活动会话",
-  "disableTwofactor": "禁用双因素认证",
-  "confirm2FADisable": "您确定要禁用双因素认证吗?",
+  "disableTwofactor": "禁用双认证",
+  "confirm2FADisable": "您确定要禁用双认证吗?",
   "no": "否",
   "yes": "是",
   "social": "社交",
@@ -554,11 +554,11 @@
   "systemTheme": "适应系统",
   "freeTrial": "免费试用",
   "selectYourPlan": "选择您的计划",
-  "enteSubscriptionPitch": "ente 会保留您的回忆,因此即使您丢失了设备,它们也始终可供您使用。",
+  "enteSubscriptionPitch": "Ente 会保留您的回忆,因此即使您丢失了设备,也能随时找到它们。",
   "enteSubscriptionShareWithFamily": "您的家人也可以添加到您的计划中。",
   "currentUsageIs": "当前用量 ",
   "@currentUsageIs": {
-    "description": "This text is followed by storage usaged",
+    "description": "This text is followed by storage usage",
     "examples": {
       "0": "Current usage is 1.2 GB"
     },
@@ -620,7 +620,7 @@
   "appleId": "Apple ID",
   "playstoreSubscription": "PlayStore 订阅",
   "appstoreSubscription": "AppStore 订阅",
-  "subAlreadyLinkedErrMessage": "您的 {id} 已经链接到另一个ente账户。\n如果您想要通过此账户使用您的 {id} ,请联系我们的客服''",
+  "subAlreadyLinkedErrMessage": "您的 {id} 已链接到另一个 Ente 账户。\n如果您想在此账户中使用您的 {id} ,请联系我们的支持人员",
   "visitWebToManage": "请访问 web.ente.io 来管理您的订阅",
   "couldNotUpdateSubscription": "无法升级订阅",
   "pleaseContactSupportAndWeWillBeHappyToHelp": "请用英语联系 support@ente.io ,我们将乐意提供帮助!",
@@ -665,9 +665,9 @@
   "everywhere": "随时随地",
   "androidIosWebDesktop": "安卓, iOS, 网页端, 桌面端",
   "mobileWebDesktop": "移动端, 网页端, 桌面端",
-  "newToEnte": "刚来到ente",
+  "newToEnte": "初来 Ente",
   "pleaseLoginAgain": "请重新登录",
-  "devAccountChanged": "我们用于在 App Store 上发布 ente 的开发者账户已更改。 因此,您将需要重新登录。\n\n对于给您带来的不便,我们深表歉意,但这是不可避免的。",
+  "devAccountChanged": "我们用于在 App Store 上发布 Ente 的开发者账户已更改。因此,您需要重新登录。\n\n对于给您带来的不便,我们深表歉意,但这是不可避免的。",
   "yourSubscriptionHasExpired": "您的订阅已过期",
   "storageLimitExceeded": "已超出存储限制",
   "upgrade": "升级",
@@ -678,12 +678,12 @@
   },
   "backupFailed": "备份失败",
   "couldNotBackUpTryLater": "我们无法备份您的数据。\n我们将稍后再试。",
-  "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "只有您授予访问权限,ente 才能加密和保存文件",
+  "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "仅当您授予文件访问权限时,Ente 才能加密和保存文件",
   "pleaseGrantPermissions": "请授予权限",
   "grantPermission": "授予权限",
-  "privateSharing": "私人享",
+  "privateSharing": "私人享",
   "shareOnlyWithThePeopleYouWant": "仅与您想要的人分享",
-  "usePublicLinksForPeopleNotOnEnte": "为不在ente 上的人使用公共链接",
+  "usePublicLinksForPeopleNotOnEnte": "对不在 Ente 上的人使用公开链接",
   "allowPeopleToAddPhotos": "允许人们添加照片",
   "shareAnAlbumNow": "立即分享相册",
   "collectEventPhotos": "收集活动照片",
@@ -695,7 +695,7 @@
   },
   "onDevice": "在设备上",
   "@onEnte": {
-    "description": "The text displayed above albums backed up to ente",
+    "description": "The text displayed above albums backed up to Ente",
     "type": "text"
   },
   "onEnte": "在 <branding>ente</branding> 上",
@@ -741,7 +741,7 @@
   "saveCollage": "保存拼贴",
   "collageSaved": "拼贴已保存到相册",
   "collageLayout": "布局",
-  "addToEnte": "添加到 ente",
+  "addToEnte": "添加到 Ente",
   "addToAlbum": "添加到相册",
   "delete": "删除",
   "hide": "隐藏",
@@ -806,10 +806,10 @@
   "photosAddedByYouWillBeRemovedFromTheAlbum": "您添加的照片将从相册中移除",
   "youveNoFilesInThisAlbumThatCanBeDeleted": "您在此相册中没有可以删除的文件",
   "youDontHaveAnyArchivedItems": "您没有任何存档的项目。",
-  "ignoredFolderUploadReason": "此相册中的某些文件在上传时被忽略,因为它们之前已从 ente 中删除。",
+  "ignoredFolderUploadReason": "此相册中的某些文件在上传时会被忽略,因为它们之前已从 Ente 中删除。",
   "resetIgnoredFiles": "重置忽略的文件",
-  "deviceFilesAutoUploading": "添加到此设备相册的文件将自动上传到 ente。",
-  "turnOnBackupForAutoUpload": "打开备份以自动上传添加到此设备文件夹的文件。",
+  "deviceFilesAutoUploading": "添加到此设备相册的文件将自动上传到 Ente。",
+  "turnOnBackupForAutoUpload": "打开备份可自动上传添加到此设备文件夹的文件至 Ente。",
   "noHiddenPhotosOrVideos": "没有隐藏的照片或视频",
   "toHideAPhotoOrVideo": "隐藏照片或视频",
   "openTheItem": "• 打开该项目",
@@ -886,7 +886,7 @@
   "@freeUpSpaceSaving": {
     "description": "Text to tell user how much space they can free up by deleting items from the device"
   },
-  "freeUpAccessPostDelete": "只要您有有效的订阅,您仍然可以在 ente 上访问 {count, plural, one {it} other {them}}",
+  "freeUpAccessPostDelete": "只要您有有效的订阅,您仍然可以在 Ente 上访问 {count, plural, one {它} other {它们}}",
   "@freeUpAccessPostDelete": {
     "placeholders": {
       "count": {
@@ -904,15 +904,15 @@
   "authenticationSuccessful": "验证成功",
   "incorrectRecoveryKey": "不正确的恢复密钥",
   "theRecoveryKeyYouEnteredIsIncorrect": "您输入的恢复密钥不正确",
-  "twofactorAuthenticationSuccessfullyReset": "成功重置双因素认证",
+  "twofactorAuthenticationSuccessfullyReset": "成功重置双认证",
   "pleaseVerifyTheCodeYouHaveEntered": "请验证您输入的代码",
   "pleaseContactSupportIfTheProblemPersists": "如果问题仍然存在,请联系支持",
-  "twofactorAuthenticationHasBeenDisabled": "双因素认证已被禁用",
+  "twofactorAuthenticationHasBeenDisabled": "双认证已被禁用",
   "sorryTheCodeYouveEnteredIsIncorrect": "抱歉,您输入的代码不正确",
   "yourVerificationCodeHasExpired": "您的验证码已过期",
   "emailChangedTo": "电子邮件已更改为 {newEmail}",
   "verifying": "正在验证...",
-  "disablingTwofactorAuthentication": "正在禁用双因素认证...",
+  "disablingTwofactorAuthentication": "正在禁用双认证...",
   "allMemoriesPreserved": "所有回忆都已保存",
   "loadingGallery": "正在加载图库...",
   "syncing": "正在同步···",
@@ -937,7 +937,7 @@
   "renameFile": "重命名文件",
   "enterFileName": "请输入文件名",
   "filesDeleted": "文件已删除",
-  "selectedFilesAreNotOnEnte": "所选文件不在ente上",
+  "selectedFilesAreNotOnEnte": "所选文件不在 Ente 上",
   "thisActionCannotBeUndone": "此操作无法撤销",
   "emptyTrash": "要清空回收站吗?",
   "permDeleteWarning": "回收站中的所有项目将被永久删除\n\n此操作无法撤消",
@@ -946,7 +946,7 @@
   "permanentlyDeleteFromDevice": "要从设备中永久删除吗?",
   "someOfTheFilesYouAreTryingToDeleteAre": "您要删除的部分文件仅在您的设备上可用,且删除后无法恢复",
   "theyWillBeDeletedFromAllAlbums": "他们将从所有相册中删除。",
-  "someItemsAreInBothEnteAndYourDevice": "有些项目既在ente 也在您的设备中。",
+  "someItemsAreInBothEnteAndYourDevice": "有些项目同时存在于 Ente 和您的设备中。",
   "selectedItemsWillBeDeletedFromAllAlbumsAndMoved": "所选项目将从所有相册中删除并移动到回收站。",
   "theseItemsWillBeDeletedFromYourDevice": "这些项目将从您的设备中删除。",
   "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "看起来出了点问题。 请稍后重试。 如果错误仍然存在,请联系我们的支持团队。",
@@ -1052,7 +1052,7 @@
   },
   "setRadius": "设定半径",
   "familyPlanPortalTitle": "家庭",
-  "familyPlanOverview": "在您现有的计划中添加 5 名家庭成员而无需支付额外费用。\n\n每个成员都有自己的私人空间,除非共享,否则无法看到彼此的文件。\n\n家庭计划适用于已有付费订阅的客户。\n\n立即订阅以开始使用!",
+  "familyPlanOverview": "将 5 名家庭成员添加到您现有的计划中,无需支付额外费用。\n\n每个成员都有自己的私人空间,除非共享,否则无法看到彼此的文件。\n\n家庭计划适用于已付费 Ente 订阅的客户。\n\n立即订阅,开始体验!",
   "androidBiometricHint": "验证身份",
   "@androidBiometricHint": {
     "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
@@ -1130,7 +1130,7 @@
   "noAlbumsSharedByYouYet": "您尚未共享任何相册",
   "sharedWithYou": "已与您共享",
   "sharedByYou": "您共享的",
-  "inviteYourFriendsToEnte": "邀请您的好友加入ente",
+  "inviteYourFriendsToEnte": "邀请您的朋友加入 Ente",
   "failedToDownloadVideo": "视频下载失败",
   "hiding": "正在隐藏...",
   "unhiding": "正在取消隐藏...",
@@ -1140,7 +1140,7 @@
   "addToHiddenAlbum": "添加到隐藏相册",
   "moveToHiddenAlbum": "移至隐藏相册",
   "fileTypes": "文件类型",
-  "deleteConfirmDialogBody": "此账户已链接到其他 ente 旗下的应用程序(如果您使用任何 ente 旗下的应用程序)。\\n\\n您在所有 ente 旗下的应用程序中上传的数据将被安排删除,并且您的账户将被永久删除。",
+  "deleteConfirmDialogBody": "此账户已链接到其他 Ente 应用程序(如果您使用任何应用程序)。您在所有 Ente 应用程序中上传的数据将被安排删除,并且您的账户将被永久删除。",
   "hearUsWhereTitle": "您是如何知道Ente的? (可选的)",
   "hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
   "viewAddOnButton": "查看附加组件",
@@ -1204,5 +1204,12 @@
   "addViewers": "{count, plural, zero {添加查看者} one {添加查看者} other {添加查看者}}",
   "addCollaborators": "{count, plural, zero {添加协作者} one {添加协作者} other {添加协作者}}",
   "longPressAnEmailToVerifyEndToEndEncryption": "长按电子邮件以验证端到端加密。",
-  "createCollaborativeLink": "Create collaborative link"
+  "developerSettingsWarning": "您确定要修改开发者设置吗?",
+  "developerSettings": "开发者设置",
+  "serverEndpoint": "服务器端点",
+  "invalidEndpoint": "端点无效",
+  "invalidEndpointMessage": "抱歉,您输入的端点无效。请输入有效的端点,然后重试。",
+  "endpointUpdatedMessage": "端点更新成功",
+  "customEndpoint": "已连接至 {endpoint}",
+  "createCollaborativeLink": "创建协作链接"
 }

+ 27 - 1
mobile/lib/services/remote_sync_service.dart

@@ -52,6 +52,15 @@ class RemoteSyncService {
   bool _isExistingSyncSilent = false;
 
   static const kHasSyncedArchiveKey = "has_synced_archive";
+  /* This setting is used to maintain a list of local IDs for videos that the user has manually
+ marked for upload, even if the global video upload setting is currently disabled.
+ When the global video upload setting is disabled, we typically ignore all video uploads. However, for videos that have been added to this list, we
+ want to still allow them to be uploaded, despite the global setting being disabled.
+
+ This allows users to queue up videos for upload, and have them successfully upload
+ even if they later toggle the global video upload setting to disabled.
+   */
+  static const _ignoreBackUpSettingsForIDs_ = "ignoreBackUpSettingsForIDs";
   final String _isFirstRemoteSyncDone = "isFirstRemoteSyncDone";
 
   // 28 Sept, 2021 9:03:20 AM IST
@@ -189,6 +198,18 @@ class RemoteSyncService {
     return _prefs.containsKey(_isFirstRemoteSyncDone);
   }
 
+  Future<bool> whiteListVideoForUpload(EnteFile file) async {
+    if (file.fileType == FileType.video &&
+        !_config.shouldBackupVideos() &&
+        file.localID != null) {
+      final List<String> whitelistedIDs =
+          _prefs.getStringList(_ignoreBackUpSettingsForIDs_) ?? <String>[];
+      whitelistedIDs.add(file.localID!);
+      return _prefs.setStringList(_ignoreBackUpSettingsForIDs_, whitelistedIDs);
+    }
+    return false;
+  }
+
   Future<void> _pullDiff() async {
     _logger.info("Pulling remote diff");
     final isFirstSync = !_collectionsService.hasSyncedCollections();
@@ -524,8 +545,13 @@ class RemoteSyncService {
     final List<EnteFile> filesToBeUploaded = [];
     int ignoredForUpload = 0;
     int skippedVideos = 0;
+    final whitelistedIDs =
+        (_prefs.getStringList(_ignoreBackUpSettingsForIDs_) ?? <String>[])
+            .toSet();
     for (var file in originalFiles) {
-      if (shouldRemoveVideos && file.fileType == FileType.video) {
+      if (shouldRemoveVideos &&
+          (file.fileType == FileType.video &&
+              !whitelistedIDs.contains(file.localID))) {
         skippedVideos++;
         continue;
       }

+ 1 - 1
mobile/lib/ui/payment/stripe_subscription_page.dart

@@ -610,7 +610,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
             storage: _currentSubscription!.storage,
             price: _currentSubscription!.price,
             period: _currentSubscription!.period,
-            isActive: !_hasActiveSubscription,
+            isActive: _currentSubscription!.isValid(),
           ),
         ),
       ),

+ 123 - 122
mobile/lib/ui/viewer/file/file_app_bar.dart

@@ -133,134 +133,135 @@ class FileAppBarState extends State<FileAppBar> {
         ),
       );
     }
-    actions.add(
-      PopupMenuButton(
-        itemBuilder: (context) {
-          final List<PopupMenuItem> items = [];
-          if (widget.file.isRemoteFile) {
-            items.add(
-              PopupMenuItem(
-                value: 1,
-                child: Row(
-                  children: [
-                    Icon(
-                      Platform.isAndroid
-                          ? Icons.download
-                          : CupertinoIcons.cloud_download,
-                      color: Theme.of(context).iconTheme.color,
-                    ),
-                    const Padding(
-                      padding: EdgeInsets.all(8),
-                    ),
-                    Text(S.of(context).download),
-                  ],
-                ),
+
+    final List<PopupMenuItem> items = [];
+    if (widget.file.isRemoteFile) {
+      items.add(
+        PopupMenuItem(
+          value: 1,
+          child: Row(
+            children: [
+              Icon(
+                Platform.isAndroid
+                    ? Icons.download
+                    : CupertinoIcons.cloud_download,
+                color: Theme.of(context).iconTheme.color,
               ),
-            );
-          }
-          // options for files owned by the user
-          if (isOwnedByUser && !isFileHidden && isFileUploaded) {
-            final bool isArchived =
-                widget.file.magicMetadata.visibility == archiveVisibility;
-            items.add(
-              PopupMenuItem(
-                value: 2,
-                child: Row(
-                  children: [
-                    Icon(
-                      isArchived ? Icons.unarchive : Icons.archive_outlined,
-                      color: Theme.of(context).iconTheme.color,
-                    ),
-                    const Padding(
-                      padding: EdgeInsets.all(8),
-                    ),
-                    Text(
-                      isArchived
-                          ? S.of(context).unarchive
-                          : S.of(context).archive,
-                    ),
-                  ],
-                ),
+              const Padding(
+                padding: EdgeInsets.all(8),
               ),
-            );
-          }
-          if ((widget.file.fileType == FileType.image ||
-                  widget.file.fileType == FileType.livePhoto) &&
-              Platform.isAndroid) {
-            items.add(
-              PopupMenuItem(
-                value: 3,
-                child: Row(
-                  children: [
-                    Icon(
-                      Icons.wallpaper_outlined,
-                      color: Theme.of(context).iconTheme.color,
-                    ),
-                    const Padding(
-                      padding: EdgeInsets.all(8),
-                    ),
-                    Text(S.of(context).setAs),
-                  ],
-                ),
+              Text(S.of(context).download),
+            ],
+          ),
+        ),
+      );
+    }
+    // options for files owned by the user
+    if (isOwnedByUser && !isFileHidden && isFileUploaded) {
+      final bool isArchived =
+          widget.file.magicMetadata.visibility == archiveVisibility;
+      items.add(
+        PopupMenuItem(
+          value: 2,
+          child: Row(
+            children: [
+              Icon(
+                isArchived ? Icons.unarchive : Icons.archive_outlined,
+                color: Theme.of(context).iconTheme.color,
               ),
-            );
-          }
-          if (isOwnedByUser && widget.file.isUploaded) {
-            if (!isFileHidden) {
-              items.add(
-                PopupMenuItem(
-                  value: 4,
-                  child: Row(
-                    children: [
-                      Icon(
-                        Icons.visibility_off,
-                        color: Theme.of(context).iconTheme.color,
-                      ),
-                      const Padding(
-                        padding: EdgeInsets.all(8),
-                      ),
-                      Text(S.of(context).hide),
-                    ],
-                  ),
+              const Padding(
+                padding: EdgeInsets.all(8),
+              ),
+              Text(
+                isArchived ? S.of(context).unarchive : S.of(context).archive,
+              ),
+            ],
+          ),
+        ),
+      );
+    }
+    if ((widget.file.fileType == FileType.image ||
+            widget.file.fileType == FileType.livePhoto) &&
+        Platform.isAndroid) {
+      items.add(
+        PopupMenuItem(
+          value: 3,
+          child: Row(
+            children: [
+              Icon(
+                Icons.wallpaper_outlined,
+                color: Theme.of(context).iconTheme.color,
+              ),
+              const Padding(
+                padding: EdgeInsets.all(8),
+              ),
+              Text(S.of(context).setAs),
+            ],
+          ),
+        ),
+      );
+    }
+    if (isOwnedByUser && widget.file.isUploaded) {
+      if (!isFileHidden) {
+        items.add(
+          PopupMenuItem(
+            value: 4,
+            child: Row(
+              children: [
+                Icon(
+                  Icons.visibility_off,
+                  color: Theme.of(context).iconTheme.color,
                 ),
-              );
-            } else {
-              items.add(
-                PopupMenuItem(
-                  value: 5,
-                  child: Row(
-                    children: [
-                      Icon(
-                        Icons.visibility,
-                        color: Theme.of(context).iconTheme.color,
-                      ),
-                      const Padding(
-                        padding: EdgeInsets.all(8),
-                      ),
-                      Text(S.of(context).unhide),
-                    ],
-                  ),
+                const Padding(
+                  padding: EdgeInsets.all(8),
+                ),
+                Text(S.of(context).hide),
+              ],
+            ),
+          ),
+        );
+      } else {
+        items.add(
+          PopupMenuItem(
+            value: 5,
+            child: Row(
+              children: [
+                Icon(
+                  Icons.visibility,
+                  color: Theme.of(context).iconTheme.color,
+                ),
+                const Padding(
+                  padding: EdgeInsets.all(8),
                 ),
-              );
+                Text(S.of(context).unhide),
+              ],
+            ),
+          ),
+        );
+      }
+    }
+    if (items.isNotEmpty) {
+      actions.add(
+        PopupMenuButton(
+          itemBuilder: (context) {
+            return items;
+          },
+          onSelected: (dynamic value) async {
+            if (value == 1) {
+              await _download(widget.file);
+            } else if (value == 2) {
+              await _toggleFileArchiveStatus(widget.file);
+            } else if (value == 3) {
+              await _setAs(widget.file);
+            } else if (value == 4) {
+              await _handleHideRequest(context);
+            } else if (value == 5) {
+              await _handleUnHideRequest(context);
             }
-          }
-          return items;
-        },
-        onSelected: (dynamic value) async {
-          if (value == 1) {
-            await _download(widget.file);
-          } else if (value == 2) {
-            await _toggleFileArchiveStatus(widget.file);
-          } else if (value == 3) {
-            await _setAs(widget.file);
-          } else if (value == 4) {
-            await _handleHideRequest(context);
-          } else if (value == 5) {
-            await _handleUnHideRequest(context);
-          }
-        },
-      ),
-    );
+          },
+        ),
+      );
+    }
     return AppBar(
       iconTheme:
           const IconThemeData(color: Colors.white), //same for both themes

+ 2 - 0
mobile/lib/ui/viewer/file_details/upload_icon_widget.dart

@@ -125,6 +125,8 @@ class _UpdateIconWidgetState extends State<UploadIconWidget> {
                       .id;
                   await FilesDB.instance.insert(widget.file);
                 }
+                await RemoteSyncService.instance
+                    .whiteListVideoForUpload(widget.file);
                 RemoteSyncService.instance.sync().ignore();
                 if (mounted) {
                   setState(() {

+ 10 - 2
mobile/pubspec.lock

@@ -2051,10 +2051,18 @@ packages:
     dependency: "direct main"
     description:
       name: sqlite3_flutter_libs
-      sha256: "90963b515721d6a71e96f438175cf43c979493ed14822860a300b69694c74eb6"
+      sha256: d6c31c8511c441d1f12f20b607343df1afe4eddf24a1cf85021677c8eea26060
       url: "https://pub.dev"
     source: hosted
-    version: "0.5.19+1"
+    version: "0.5.20"
+  sqlite_async:
+    dependency: "direct main"
+    description:
+      name: sqlite_async
+      sha256: b252fd3a53766460b2f240e082517d3bc1cce7682a1550817f0f799d4a7a4087
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.5.2"
   stack_trace:
     dependency: transitive
     description:

+ 3 - 2
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.8.75+595
+version: 0.8.77+597
 publish_to: none
 
 environment:
@@ -151,7 +151,8 @@ dependencies:
   sqflite: ^2.3.0
   sqflite_migration: ^0.3.0
   sqlite3: ^2.1.0
-  sqlite3_flutter_libs: ^0.5.19+1
+  sqlite3_flutter_libs: ^0.5.20
+  sqlite_async: ^0.5.2
   step_progress_indicator: ^1.0.2
   styled_text: ^7.0.0
   syncfusion_flutter_core: ^19.2.49

+ 23 - 0
mobile/scripts/app_init_perf_test.sh

@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# Make sure to go through app_init_test.dart and 
+# fill in email and password.
+# Specify destination directory for the perf results in perf_driver.dart.
+# Specify the report_key of the test in perf_driver.dart. `report_key`` of
+# `traceAction`` in app_init_test.dart.
+
+# On first run, app will start from login page. from second run onwards, 
+# app will start from home page. --keep-app-running is for starting the 
+# app from home page instead of logging in on every run.
+
+export ENDPOINT="https://api.ente.io"
+
+flutter drive \
+  --driver=test_driver/perf_driver.dart \
+  --target=integration_test/app_init_test.dart \
+  --dart-define=endpoint=$ENDPOINT \
+  --profile --flavor independent \
+  --no-dds \
+  --keep-app-running
+
+exit $?

+ 2 - 1
mobile/gallery_scroll_perf_test.sh → mobile/scripts/gallery_scroll_perf_test.sh

@@ -3,7 +3,8 @@
 # Make sure to go through home_gallery_scroll_test.dart and 
 # fill in email and password.
 # Specify destination directory for the perf results in perf_driver.dart.
-
+# Specify the report_key of the test in perf_driver.dart. `report_key`` of
+# `traceAction`` in app_init_test.dart.
 
 export ENDPOINT="https://api.ente.io"
 

+ 3 - 2
mobile/test_driver/perf_driver.dart

@@ -8,13 +8,14 @@ Future<void> main() {
     responseDataCallback: (data) async {
       if (data != null) {
         final timeline = driver.Timeline.fromJson(
-          data['home_gallery_scrolling_summary'] as Map<String, dynamic>,
+          data['*`report_key` of traceAction of integration test*']
+              as Map<String, dynamic>,
         );
 
         final summary = driver.TimelineSummary.summarize(timeline);
 
         await summary.writeTimelineToFile(
-          'home_gallery_scrolling_summary',
+          '*title of file*',
           pretty: true,
           includeSummary: true,
           //Specify destination directory for the timeline files.

+ 9 - 0
server/migrations/83_fix_ott_index.down.sql

@@ -0,0 +1,9 @@
+BEGIN;
+ALTER TABLE
+    otts DROP CONSTRAINT IF EXISTS unique_otts_emailhash_app_ott;
+
+ALTER TABLE
+    otts
+    ADD
+        CONSTRAINT unique_otts_emailhash_ott UNIQUE (ott, email_hash);
+COMMIT;

+ 9 - 0
server/migrations/83_fix_ott_index.up.sql

@@ -0,0 +1,9 @@
+BEGIN;
+ALTER TABLE
+    otts DROP CONSTRAINT IF EXISTS unique_otts_emailhash_ott;
+
+ALTER TABLE
+    otts
+    ADD
+        CONSTRAINT  unique_otts_emailhash_app_ott UNIQUE (ott,app, email_hash);
+COMMIT;

+ 5 - 12
server/pkg/controller/mailing_lists.go

@@ -3,6 +3,7 @@ package controller
 import (
 	"fmt"
 	"net/url"
+	"strconv"
 	"strings"
 
 	"github.com/ente-io/museum/pkg/external/listmonk"
@@ -221,25 +222,17 @@ func (c *MailingListsController) listmonkSubscribe(email string) error {
 
 // Unsubscribes an email address to a particular listmonk campaign mailing list
 func (c *MailingListsController) listmonkUnsubscribe(email string) error {
-	// Listmonk dosen't provide an endpoint for unsubscribing users
+	// Listmonk doesn't provide an endpoint for unsubscribing users
 	// from a particular list directly via their email
 	//
 	// Thus, fetching subscriberID through email address,
-	// and then calling endpoint to modify subscription in a list
+	// and then calling the endpoint to delete that user
 	id, err := listmonk.GetSubscriberID(c.listmonkCredentials.BaseURL+"/api/subscribers",
 		c.listmonkCredentials.Username, c.listmonkCredentials.Password, email)
 	if err != nil {
 		stacktrace.Propagate(err, "")
 	}
-	// API endpoint expects an array of subscriber id as parameter
-	subscriberID := []int{id}
 
-	data := map[string]interface{}{
-		"ids":             subscriberID,
-		"action":          "remove",
-		"target_list_ids": c.listmonkListIDs,
-	}
-
-	return listmonk.SendRequest("PUT", c.listmonkCredentials.BaseURL+"/api/subscribers/lists", data,
-		c.listmonkCredentials.Username, c.listmonkCredentials.Password)
+	return listmonk.SendRequest("DELETE", c.listmonkCredentials.BaseURL+"/api/subscribers/"+strconv.Itoa(id),
+		map[string]interface{}{}, c.listmonkCredentials.Username, c.listmonkCredentials.Password)
 }

+ 7 - 6
server/pkg/controller/user/userauth.go

@@ -136,23 +136,24 @@ func (c *UserController) SendEmailOTT(context *gin.Context, email string, client
 // verifyEmailOtt should be deprecated in favor of verifyEmailOttWithSession once clients are updated.
 func (c *UserController) verifyEmailOtt(context *gin.Context, email string, ott string) error {
 	ott = strings.TrimSpace(ott)
+	app := auth.GetApp(context)
 	emailHash, err := crypto.GetHash(email, c.HashingKey)
 	if err != nil {
 		return stacktrace.Propagate(err, "")
 	}
-	wrongAttempt, err := c.UserAuthRepo.GetMaxWrongAttempts(emailHash)
+	wrongAttempt, err := c.UserAuthRepo.GetMaxWrongAttempts(emailHash, app)
 	if err != nil {
 		return stacktrace.Propagate(err, "")
 	}
 
 	if wrongAttempt >= OTTWrongAttemptLimit {
-		msg := "Too many wrong attempts for ott verification"
+		msg := fmt.Sprintf("Too many wrong ott verification attemp for app %s", app)
 		go c.DiscordController.NotifyPotentialAbuse(msg)
 		return stacktrace.Propagate(ente.ErrTooManyBadRequest, "User needs to wait before active ott are expired")
 	}
 
-	otts, err := c.UserAuthRepo.GetValidOTTs(emailHash, auth.GetApp(context))
-	log.Info("Valid otts for " + emailHash + " are " + strings.Join(otts, ","))
+	otts, err := c.UserAuthRepo.GetValidOTTs(emailHash, app)
+	log.Infof("Valid ott (app: %s) for %s are %s", app, emailHash, strings.Join(otts, ","))
 	if err != nil {
 		return stacktrace.Propagate(err, "")
 	}
@@ -166,12 +167,12 @@ func (c *UserController) verifyEmailOtt(context *gin.Context, email string, ott
 		}
 	}
 	if !isValidOTT {
-		if err = c.UserAuthRepo.RecordWrongAttemptForActiveOtt(emailHash); err != nil {
+		if err = c.UserAuthRepo.RecordWrongAttemptForActiveOtt(emailHash, app); err != nil {
 			log.WithError(err).Warn("Failed to track wrong attempt")
 		}
 		return stacktrace.Propagate(ente.ErrIncorrectOTT, "")
 	}
-	err = c.UserAuthRepo.RemoveOTT(emailHash, ott)
+	err = c.UserAuthRepo.RemoveOTT(emailHash, ott, app)
 	if err != nil {
 		return stacktrace.Propagate(err, "")
 	}

+ 8 - 8
server/pkg/repo/userauth.go

@@ -20,14 +20,14 @@ type UserAuthRepository struct {
 func (repo *UserAuthRepository) AddOTT(emailHash string, app ente.App, ott string, expirationTime int64) error {
 	_, err := repo.DB.Exec(`INSERT INTO otts(email_hash, ott, creation_time, expiration_time, app)
 				VALUES($1, $2, $3, $4, $5)
-				ON  CONFLICT ON CONSTRAINT unique_otts_emailhash_ott DO UPDATE SET creation_time = $3, expiration_time = $4`,
+				ON  CONFLICT ON CONSTRAINT unique_otts_emailhash_app_ott DO UPDATE SET creation_time = $3, expiration_time = $4`,
 		emailHash, ott, time.Microseconds(), expirationTime, app)
 	return stacktrace.Propagate(err, "")
 }
 
 // RemoveOTT removes the specified OTT (to be used when an OTT has been consumed)
-func (repo *UserAuthRepository) RemoveOTT(emailHash string, ott string) error {
-	_, err := repo.DB.Exec(`DELETE FROM otts WHERE email_hash = $1 AND ott = $2`, emailHash, ott)
+func (repo *UserAuthRepository) RemoveOTT(emailHash string, ott string, app ente.App) error {
+	_, err := repo.DB.Exec(`DELETE FROM otts WHERE email_hash = $1 AND ott = $2 AND app = $3`, emailHash, ott, app)
 	return stacktrace.Propagate(err, "")
 }
 
@@ -69,9 +69,9 @@ func (repo *UserAuthRepository) GetValidOTTs(emailHash string, app ente.App) ([]
 	return otts, nil
 }
 
-func (repo *UserAuthRepository) GetMaxWrongAttempts(emailHash string) (int, error) {
-	row := repo.DB.QueryRow(`SELECT COALESCE(MAX(wrong_attempt),0) FROM otts WHERE email_hash = $1 AND expiration_time > $2`,
-		emailHash, time.Microseconds())
+func (repo *UserAuthRepository) GetMaxWrongAttempts(emailHash string, app ente.App) (int, error) {
+	row := repo.DB.QueryRow(`SELECT COALESCE(MAX(wrong_attempt),0) FROM otts WHERE email_hash = $1 AND expiration_time > $2 AND app = $3`,
+		emailHash, time.Microseconds(), app)
 	var wrongAttempt int
 	if err := row.Scan(&wrongAttempt); err != nil {
 		return 0, stacktrace.Propagate(err, "Failed to scan row")
@@ -81,9 +81,9 @@ func (repo *UserAuthRepository) GetMaxWrongAttempts(emailHash string) (int, erro
 
 // RecordWrongAttemptForActiveOtt increases the wrong_attempt count for given emailHash and active ott.
 // Assuming tha we keep deleting expired OTT, max(wrong_attempt) can be used to track brute-force attack
-func (repo *UserAuthRepository) RecordWrongAttemptForActiveOtt(emailHash string) error {
+func (repo *UserAuthRepository) RecordWrongAttemptForActiveOtt(emailHash string, app ente.App) error {
 	_, err := repo.DB.Exec(`UPDATE otts SET wrong_attempt = otts.wrong_attempt + 1
-				WHERE email_hash = $1  AND expiration_time > $2`, emailHash, time.Microseconds())
+				WHERE email_hash = $1  AND expiration_time > $2 AND app=$3`, emailHash, time.Microseconds(), app)
 	if err != nil {
 		return stacktrace.Propagate(err, "Failed to update wrong attempt count")
 	}

+ 3 - 0
web/.gitignore

@@ -10,6 +10,9 @@ node_modules/
 # Local env files
 .env*.local
 
+# tsc
+*.tsbuildinfo
+
 # Vite
 dist
 

+ 0 - 654
web/apps/accounts/public/locales/bg-BG/translation.json

@@ -1,654 +0,0 @@
-{
-    "HERO_SLIDE_1_TITLE": "<div>Личен бекъп</div><div>на твоите спомени</div>",
-    "HERO_SLIDE_1": "Криптиран от край до край по подразбиране",
-    "HERO_SLIDE_2_TITLE": "",
-    "HERO_SLIDE_2": "",
-    "HERO_SLIDE_3_TITLE": "",
-    "HERO_SLIDE_3": "",
-    "LOGIN": "",
-    "SIGN_UP": "",
-    "NEW_USER": "",
-    "EXISTING_USER": "",
-    "ENTER_NAME": "",
-    "PUBLIC_UPLOADER_NAME_MESSAGE": "",
-    "ENTER_EMAIL": "",
-    "EMAIL_ERROR": "",
-    "REQUIRED": "",
-    "EMAIL_SENT": "",
-    "CHECK_INBOX": "",
-    "ENTER_OTT": "",
-    "RESEND_MAIL": "",
-    "VERIFY": "",
-    "UNKNOWN_ERROR": "",
-    "INVALID_CODE": "",
-    "EXPIRED_CODE": "",
-    "SENDING": "",
-    "SENT": "",
-    "PASSWORD": "",
-    "LINK_PASSWORD": "",
-    "RETURN_PASSPHRASE_HINT": "",
-    "SET_PASSPHRASE": "",
-    "VERIFY_PASSPHRASE": "",
-    "INCORRECT_PASSPHRASE": "",
-    "ENTER_ENC_PASSPHRASE": "",
-    "PASSPHRASE_DISCLAIMER": "",
-    "WELCOME_TO_ENTE_HEADING": "",
-    "WELCOME_TO_ENTE_SUBHEADING": "",
-    "WHERE_YOUR_BEST_PHOTOS_LIVE": "",
-    "KEY_GENERATION_IN_PROGRESS_MESSAGE": "",
-    "PASSPHRASE_HINT": "",
-    "CONFIRM_PASSPHRASE": "",
-    "REFERRAL_CODE_HINT": "",
-    "REFERRAL_INFO": "",
-    "PASSPHRASE_MATCH_ERROR": "",
-    "CREATE_COLLECTION": "",
-    "ENTER_ALBUM_NAME": "",
-    "CLOSE_OPTION": "",
-    "ENTER_FILE_NAME": "",
-    "CLOSE": "",
-    "NO": "",
-    "NOTHING_HERE": "",
-    "UPLOAD": "",
-    "IMPORT": "",
-    "ADD_PHOTOS": "",
-    "ADD_MORE_PHOTOS": "",
-    "add_photos_one": "",
-    "add_photos_other": "",
-    "SELECT_PHOTOS": "",
-    "FILE_UPLOAD": "",
-    "UPLOAD_STAGE_MESSAGE": {
-        "0": "",
-        "1": "",
-        "2": "",
-        "3": "",
-        "4": "",
-        "5": ""
-    },
-    "FILE_NOT_UPLOADED_LIST": "",
-    "SUBSCRIPTION_EXPIRED": "",
-    "SUBSCRIPTION_EXPIRED_MESSAGE": "",
-    "STORAGE_QUOTA_EXCEEDED": "",
-    "INITIAL_LOAD_DELAY_WARNING": "",
-    "USER_DOES_NOT_EXIST": "",
-    "NO_ACCOUNT": "",
-    "ACCOUNT_EXISTS": "",
-    "CREATE": "",
-    "DOWNLOAD": "",
-    "DOWNLOAD_OPTION": "",
-    "DOWNLOAD_FAVORITES": "",
-    "DOWNLOAD_UNCATEGORIZED": "",
-    "DOWNLOAD_HIDDEN_ITEMS": "",
-    "COPY_OPTION": "",
-    "TOGGLE_FULLSCREEN": "",
-    "ZOOM_IN_OUT": "",
-    "PREVIOUS": "",
-    "NEXT": "",
-    "TITLE_PHOTOS": "",
-    "TITLE_ALBUMS": "",
-    "TITLE_AUTH": "",
-    "UPLOAD_FIRST_PHOTO": "",
-    "IMPORT_YOUR_FOLDERS": "",
-    "UPLOAD_DROPZONE_MESSAGE": "",
-    "WATCH_FOLDER_DROPZONE_MESSAGE": "",
-    "TRASH_FILES_TITLE": "",
-    "TRASH_FILE_TITLE": "",
-    "DELETE_FILES_TITLE": "",
-    "DELETE_FILES_MESSAGE": "",
-    "DELETE": "",
-    "DELETE_OPTION": "",
-    "FAVORITE_OPTION": "",
-    "UNFAVORITE_OPTION": "",
-    "MULTI_FOLDER_UPLOAD": "",
-    "UPLOAD_STRATEGY_CHOICE": "",
-    "UPLOAD_STRATEGY_SINGLE_COLLECTION": "",
-    "OR": "",
-    "UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "",
-    "SESSION_EXPIRED_MESSAGE": "",
-    "SESSION_EXPIRED": "",
-    "PASSWORD_GENERATION_FAILED": "",
-    "CHANGE_PASSWORD": "",
-    "GO_BACK": "",
-    "RECOVERY_KEY": "",
-    "SAVE_LATER": "",
-    "SAVE": "",
-    "RECOVERY_KEY_DESCRIPTION": "",
-    "RECOVER_KEY_GENERATION_FAILED": "",
-    "KEY_NOT_STORED_DISCLAIMER": "",
-    "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": "",
-    "CONFIRM": "",
-    "CANCEL": "",
-    "LOGOUT": "",
-    "DELETE_ACCOUNT": "",
-    "DELETE_ACCOUNT_MESSAGE": "",
-    "LOGOUT_MESSAGE": "",
-    "CHANGE_EMAIL": "",
-    "OK": "",
-    "SUCCESS": "",
-    "ERROR": "",
-    "MESSAGE": "",
-    "INSTALL_MOBILE_APP": "",
-    "DOWNLOAD_APP_MESSAGE": "",
-    "DOWNLOAD_APP": "",
-    "EXPORT": "",
-    "SUBSCRIPTION": "",
-    "SUBSCRIBE": "",
-    "MANAGEMENT_PORTAL": "",
-    "MANAGE_FAMILY_PORTAL": "",
-    "LEAVE_FAMILY_PLAN": "",
-    "LEAVE": "",
-    "LEAVE_FAMILY_CONFIRM": "",
-    "CHOOSE_PLAN": "",
-    "MANAGE_PLAN": "",
-    "ACTIVE": "",
-    "OFFLINE_MSG": "",
-    "FREE_SUBSCRIPTION_INFO": "",
-    "FAMILY_SUBSCRIPTION_INFO": "",
-    "RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "",
-    "ADD_ON_AVAILABLE_TILL": "",
-    "STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "",
-    "SUBSCRIPTION_PURCHASE_SUCCESS": "",
-    "SUBSCRIPTION_PURCHASE_CANCELLED": "",
-    "SUBSCRIPTION_PURCHASE_FAILED": "",
-    "SUBSCRIPTION_UPDATE_FAILED": "",
-    "UPDATE_PAYMENT_METHOD_MESSAGE": "",
-    "STRIPE_AUTHENTICATION_FAILED": "",
-    "UPDATE_PAYMENT_METHOD": "",
-    "MONTHLY": "",
-    "YEARLY": "",
-    "UPDATE_SUBSCRIPTION_MESSAGE": "",
-    "UPDATE_SUBSCRIPTION": "",
-    "CANCEL_SUBSCRIPTION": "",
-    "CANCEL_SUBSCRIPTION_MESSAGE": "",
-    "CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "",
-    "SUBSCRIPTION_CANCEL_FAILED": "",
-    "SUBSCRIPTION_CANCEL_SUCCESS": "",
-    "REACTIVATE_SUBSCRIPTION": "",
-    "REACTIVATE_SUBSCRIPTION_MESSAGE": "",
-    "SUBSCRIPTION_ACTIVATE_SUCCESS": "",
-    "SUBSCRIPTION_ACTIVATE_FAILED": "",
-    "SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE": "",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "",
-    "MAIL_TO_MANAGE_SUBSCRIPTION": "",
-    "RENAME": "",
-    "RENAME_FILE": "",
-    "RENAME_COLLECTION": "",
-    "DELETE_COLLECTION_TITLE": "",
-    "DELETE_COLLECTION": "",
-    "DELETE_COLLECTION_MESSAGE": "",
-    "DELETE_PHOTOS": "",
-    "KEEP_PHOTOS": "",
-    "SHARE": "",
-    "SHARE_COLLECTION": "",
-    "SHAREES": "",
-    "SHARE_WITH_SELF": "",
-    "ALREADY_SHARED": "",
-    "SHARING_BAD_REQUEST_ERROR": "",
-    "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "",
-    "DOWNLOAD_COLLECTION": "",
-    "DOWNLOAD_COLLECTION_MESSAGE": "",
-    "CREATE_ALBUM_FAILED": "",
-    "SEARCH": "",
-    "SEARCH_RESULTS": "",
-    "NO_RESULTS": "",
-    "SEARCH_HINT": "",
-    "SEARCH_TYPE": {
-        "COLLECTION": "",
-        "LOCATION": "",
-        "CITY": "",
-        "DATE": "",
-        "FILE_NAME": "",
-        "THING": "",
-        "FILE_CAPTION": "",
-        "FILE_TYPE": "",
-        "CLIP": ""
-    },
-    "photos_count_zero": "",
-    "photos_count_one": "",
-    "photos_count_other": "",
-    "TERMS_AND_CONDITIONS": "",
-    "ADD_TO_COLLECTION": "",
-    "SELECTED": "",
-    "VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "",
-    "PEOPLE": "",
-    "INDEXING_SCHEDULED": "",
-    "ANALYZING_PHOTOS": "",
-    "INDEXING_PEOPLE": "",
-    "INDEXING_DONE": "",
-    "UNIDENTIFIED_FACES": "",
-    "OBJECTS": "",
-    "TEXT": "",
-    "INFO": "",
-    "INFO_OPTION": "",
-    "FILE_NAME": "",
-    "CAPTION_PLACEHOLDER": "",
-    "LOCATION": "",
-    "SHOW_ON_MAP": "",
-    "MAP": "",
-    "MAP_SETTINGS": "",
-    "ENABLE_MAPS": "",
-    "ENABLE_MAP": "",
-    "DISABLE_MAPS": "",
-    "ENABLE_MAP_DESCRIPTION": "",
-    "DISABLE_MAP_DESCRIPTION": "",
-    "DISABLE_MAP": "",
-    "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": "",
-    "LOST_DEVICE": "",
-    "INCORRECT_CODE": "",
-    "TWO_FACTOR_INFO": "",
-    "DISABLE_TWO_FACTOR_LABEL": "",
-    "UPDATE_TWO_FACTOR_LABEL": "",
-    "DISABLE": "",
-    "RECONFIGURE": "",
-    "UPDATE_TWO_FACTOR": "",
-    "UPDATE_TWO_FACTOR_MESSAGE": "",
-    "UPDATE": "",
-    "DISABLE_TWO_FACTOR": "",
-    "DISABLE_TWO_FACTOR_MESSAGE": "",
-    "TWO_FACTOR_DISABLE_FAILED": "",
-    "EXPORT_DATA": "",
-    "SELECT_FOLDER": "",
-    "DESTINATION": "",
-    "START": "",
-    "LAST_EXPORT_TIME": "",
-    "EXPORT_AGAIN": "",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE": "",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "",
-    "SEND_OTT": "",
-    "EMAIl_ALREADY_OWNED": "",
-    "ETAGS_BLOCKED": "",
-    "SKIPPED_VIDEOS_INFO": "",
-    "LIVE_PHOTOS_DETECTED": "",
-    "RETRY_FAILED": "",
-    "FAILED_UPLOADS": "",
-    "SKIPPED_FILES": "",
-    "THUMBNAIL_GENERATION_FAILED_UPLOADS": "",
-    "UNSUPPORTED_FILES": "",
-    "SUCCESSFUL_UPLOADS": "",
-    "SKIPPED_INFO": "",
-    "UNSUPPORTED_INFO": "",
-    "BLOCKED_UPLOADS": "",
-    "SKIPPED_VIDEOS": "",
-    "INPROGRESS_METADATA_EXTRACTION": "",
-    "INPROGRESS_UPLOADS": "",
-    "TOO_LARGE_UPLOADS": "",
-    "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "",
-    "LARGER_THAN_AVAILABLE_STORAGE_INFO": "",
-    "TOO_LARGE_INFO": "",
-    "THUMBNAIL_GENERATION_FAILED_INFO": "",
-    "UPLOAD_TO_COLLECTION": "",
-    "UNCATEGORIZED": "",
-    "ARCHIVE": "",
-    "FAVORITES": "",
-    "ARCHIVE_COLLECTION": "",
-    "ARCHIVE_SECTION_NAME": "",
-    "ALL_SECTION_NAME": "",
-    "MOVE_TO_COLLECTION": "",
-    "UNARCHIVE": "",
-    "UNARCHIVE_COLLECTION": "",
-    "HIDE_COLLECTION": "",
-    "UNHIDE_COLLECTION": "",
-    "MOVE": "",
-    "ADD": "",
-    "REMOVE": "",
-    "YES_REMOVE": "",
-    "REMOVE_FROM_COLLECTION": "",
-    "TRASH": "",
-    "MOVE_TO_TRASH": "",
-    "TRASH_FILES_MESSAGE": "",
-    "TRASH_FILE_MESSAGE": "",
-    "DELETE_PERMANENTLY": "",
-    "RESTORE": "",
-    "RESTORE_TO_COLLECTION": "",
-    "EMPTY_TRASH": "",
-    "EMPTY_TRASH_TITLE": "",
-    "EMPTY_TRASH_MESSAGE": "",
-    "LEAVE_SHARED_ALBUM": "",
-    "LEAVE_ALBUM": "",
-    "LEAVE_SHARED_ALBUM_TITLE": "",
-    "LEAVE_SHARED_ALBUM_MESSAGE": "",
-    "NOT_FILE_OWNER": "",
-    "CONFIRM_SELF_REMOVE_MESSAGE": "",
-    "CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "",
-    "SORT_BY_CREATION_TIME_ASCENDING": "",
-    "SORT_BY_UPDATION_TIME_DESCENDING": "",
-    "SORT_BY_NAME": "",
-    "COMPRESS_THUMBNAILS": "",
-    "THUMBNAIL_REPLACED": "",
-    "FIX_THUMBNAIL": "",
-    "FIX_THUMBNAIL_LATER": "",
-    "REPLACE_THUMBNAIL_NOT_STARTED": "",
-    "REPLACE_THUMBNAIL_COMPLETED": "",
-    "REPLACE_THUMBNAIL_NOOP": "",
-    "REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR": "",
-    "FIX_CREATION_TIME": "",
-    "FIX_CREATION_TIME_IN_PROGRESS": "",
-    "CREATION_TIME_UPDATED": "",
-    "UPDATE_CREATION_TIME_NOT_STARTED": "",
-    "UPDATE_CREATION_TIME_COMPLETED": "",
-    "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "",
-    "CAPTION_CHARACTER_LIMIT": "",
-    "DATE_TIME_ORIGINAL": "",
-    "DATE_TIME_DIGITIZED": "",
-    "METADATA_DATE": "",
-    "CUSTOM_TIME": "",
-    "REOPEN_PLAN_SELECTOR_MODAL": "",
-    "OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
-    "INSTALL": "",
-    "SHARING_DETAILS": "",
-    "MODIFY_SHARING": "",
-    "ADD_COLLABORATORS": "",
-    "ADD_NEW_EMAIL": "",
-    "shared_with_people_zero": "",
-    "shared_with_people_one": "",
-    "shared_with_people_other": "",
-    "participants_zero": "",
-    "participants_one": "",
-    "participants_other": "",
-    "ADD_VIEWERS": "",
-    "PARTICIPANTS": "",
-    "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": "",
-    "LINK_PASSWORD_LOCK": "",
-    "PUBLIC_COLLECT": "",
-    "LINK_DEVICE_LIMIT": "",
-    "NO_DEVICE_LIMIT": "",
-    "LINK_EXPIRY": "",
-    "NEVER": "",
-    "DISABLE_FILE_DOWNLOAD": "",
-    "DISABLE_FILE_DOWNLOAD_MESSAGE": "",
-    "MALICIOUS_CONTENT": "",
-    "COPYRIGHT": "",
-    "SHARED_USING": "",
-    "ENTE_IO": "",
-    "SHARING_REFERRAL_CODE": "",
-    "LIVE": "",
-    "DISABLE_PASSWORD": "",
-    "DISABLE_PASSWORD_MESSAGE": "",
-    "PASSWORD_LOCK": "",
-    "LOCK": "",
-    "DOWNLOAD_UPLOAD_LOGS": "",
-    "UPLOAD_FILES": "",
-    "UPLOAD_DIRS": "",
-    "UPLOAD_GOOGLE_TAKEOUT": "",
-    "DEDUPLICATE_FILES": "",
-    "AUTHENTICATOR_SECTION": "",
-    "NO_DUPLICATES_FOUND": "",
-    "CLUB_BY_CAPTURE_TIME": "",
-    "FILES": "",
-    "EACH": "",
-    "DEDUPLICATE_BASED_ON_SIZE": "",
-    "STOP_ALL_UPLOADS_MESSAGE": "",
-    "STOP_UPLOADS_HEADER": "",
-    "YES_STOP_UPLOADS": "",
-    "STOP_DOWNLOADS_HEADER": "",
-    "YES_STOP_DOWNLOADS": "",
-    "STOP_ALL_DOWNLOADS_MESSAGE": "",
-    "albums_one": "",
-    "albums_other": "",
-    "ALL_ALBUMS": "",
-    "ALBUMS": "",
-    "ALL_HIDDEN_ALBUMS": "",
-    "HIDDEN_ALBUMS": "",
-    "HIDDEN_ITEMS": "",
-    "HIDDEN_ITEMS_SECTION_NAME": "",
-    "ENTER_TWO_FACTOR_OTP": "",
-    "CREATE_ACCOUNT": "",
-    "COPIED": "",
-    "CANVAS_BLOCKED_TITLE": "",
-    "CANVAS_BLOCKED_MESSAGE": "",
-    "WATCH_FOLDERS": "",
-    "UPGRADE_NOW": "",
-    "RENEW_NOW": "",
-    "STORAGE": "",
-    "USED": "",
-    "YOU": "",
-    "FAMILY": "",
-    "FREE": "",
-    "OF": "",
-    "WATCHED_FOLDERS": "",
-    "NO_FOLDERS_ADDED": "",
-    "FOLDERS_AUTOMATICALLY_MONITORED": "",
-    "UPLOAD_NEW_FILES_TO_ENTE": "",
-    "REMOVE_DELETED_FILES_FROM_ENTE": "",
-    "ADD_FOLDER": "",
-    "STOP_WATCHING": "",
-    "STOP_WATCHING_FOLDER": "",
-    "STOP_WATCHING_DIALOG_MESSAGE": "",
-    "YES_STOP": "",
-    "MONTH_SHORT": "",
-    "YEAR": "",
-    "FAMILY_PLAN": "",
-    "DOWNLOAD_LOGS": "",
-    "DOWNLOAD_LOGS_MESSAGE": "",
-    "CHANGE_FOLDER": "",
-    "TWO_MONTHS_FREE": "",
-    "GB": "",
-    "POPULAR": "",
-    "FREE_PLAN_OPTION_LABEL": "",
-    "FREE_PLAN_DESCRIPTION": "",
-    "CURRENT_USAGE": "",
-    "WEAK_DEVICE": "",
-    "DRAG_AND_DROP_HINT": "",
-    "CONFIRM_ACCOUNT_DELETION_MESSAGE": "",
-    "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": "",
-    "NAME_PLACEHOLDER": "",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "",
-    "CHOSE_THEME": "",
-    "ML_SEARCH": "",
-    "ENABLE_ML_SEARCH_DESCRIPTION": "",
-    "ML_MORE_DETAILS": "",
-    "ENABLE_FACE_SEARCH": "",
-    "ENABLE_FACE_SEARCH_TITLE": "",
-    "ENABLE_FACE_SEARCH_DESCRIPTION": "",
-    "DISABLE_BETA": "",
-    "DISABLE_FACE_SEARCH": "",
-    "DISABLE_FACE_SEARCH_TITLE": "",
-    "DISABLE_FACE_SEARCH_DESCRIPTION": "",
-    "ADVANCED": "",
-    "FACE_SEARCH_CONFIRMATION": "",
-    "LABS": "",
-    "YOURS": "",
-    "PASSPHRASE_STRENGTH_WEAK": "",
-    "PASSPHRASE_STRENGTH_MODERATE": "",
-    "PASSPHRASE_STRENGTH_STRONG": "",
-    "PREFERENCES": "",
-    "LANGUAGE": "",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST": "",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "",
-    "SUBSCRIPTION_VERIFICATION_ERROR": "",
-    "STORAGE_UNITS": {
-        "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": "",
-    "EXPORT_NOTIFICATION": {
-        "START": "",
-        "IN_PROGRESS": "",
-        "FINISH": "",
-        "UP_TO_DATE": ""
-    },
-    "CONTINUOUS_EXPORT": "",
-    "TOTAL_ITEMS": "",
-    "PENDING_ITEMS": "",
-    "EXPORT_STARTING": "",
-    "DELETE_ACCOUNT_REASON_LABEL": "",
-    "DELETE_ACCOUNT_REASON_PLACEHOLDER": "",
-    "DELETE_REASON": {
-        "MISSING_FEATURE": "",
-        "BROKEN_BEHAVIOR": "",
-        "FOUND_ANOTHER_SERVICE": "",
-        "NOT_LISTED": ""
-    },
-    "DELETE_ACCOUNT_FEEDBACK_LABEL": "",
-    "DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "",
-    "CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "",
-    "CONFIRM_DELETE_ACCOUNT": "",
-    "FEEDBACK_REQUIRED": "",
-    "FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "",
-    "RECOVER_TWO_FACTOR": "",
-    "at": "",
-    "AUTH_NEXT": "",
-    "AUTH_DOWNLOAD_MOBILE_APP": "",
-    "HIDDEN": "",
-    "HIDE": "",
-    "UNHIDE": "",
-    "UNHIDE_TO_COLLECTION": "",
-    "SORT_BY": "",
-    "NEWEST_FIRST": "",
-    "OLDEST_FIRST": "",
-    "CONVERSION_FAILED_NOTIFICATION_MESSAGE": "",
-    "SELECT_COLLECTION": "",
-    "PIN_ALBUM": "",
-    "UNPIN_ALBUM": "",
-    "DOWNLOAD_COMPLETE": "",
-    "DOWNLOADING_COLLECTION": "",
-    "DOWNLOAD_FAILED": "",
-    "DOWNLOAD_PROGRESS": "",
-    "CHRISTMAS": "",
-    "CHRISTMAS_EVE": "",
-    "NEW_YEAR": "",
-    "NEW_YEAR_EVE": "",
-    "IMAGE": "",
-    "VIDEO": "",
-    "LIVE_PHOTO": "",
-    "CONVERT": "",
-    "CONFIRM_EDITOR_CLOSE_MESSAGE": "",
-    "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "",
-    "BRIGHTNESS": "",
-    "CONTRAST": "",
-    "SATURATION": "",
-    "BLUR": "",
-    "INVERT_COLORS": "",
-    "ASPECT_RATIO": "",
-    "SQUARE": "",
-    "ROTATE_LEFT": "",
-    "ROTATE_RIGHT": "",
-    "FLIP_VERTICALLY": "",
-    "FLIP_HORIZONTALLY": "",
-    "DOWNLOAD_EDITED": "",
-    "SAVE_A_COPY_TO_ENTE": "",
-    "RESTORE_ORIGINAL": "",
-    "TRANSFORM": "",
-    "COLORS": "",
-    "FLIP": "",
-    "ROTATION": "",
-    "RESET": "",
-    "PHOTO_EDITOR": "",
-    "FASTER_UPLOAD": "",
-    "FASTER_UPLOAD_DESCRIPTION": "",
-    "MAGIC_SEARCH_STATUS": "",
-    "INDEXED_ITEMS": "",
-    "CAST_ALBUM_TO_TV": "",
-    "ENTER_CAST_PIN_CODE": "",
-    "PAIR_DEVICE_TO_TV": "",
-    "TV_NOT_FOUND": "",
-    "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
-    "PAIR_WITH_PIN": "",
-    "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
-    "VISIT_CAST_ENTE_IO": "",
-    "CAST_AUTO_PAIR_FAILED": "",
-    "CACHE_DIRECTORY": "",
-    "FREEHAND": "",
-    "APPLY_CROP": "",
-    "PHOTO_EDIT_REQUIRED_TO_SAVE": "",
-    "PASSKEYS": "",
-    "DELETE_PASSKEY": "",
-    "DELETE_PASSKEY_CONFIRMATION": "",
-    "RENAME_PASSKEY": "",
-    "ADD_PASSKEY": "",
-    "ENTER_PASSKEY_NAME": "",
-    "PASSKEYS_DESCRIPTION": "",
-    "CREATED_AT": "",
-    "PASSKEY_LOGIN_FAILED": "",
-    "PASSKEY_LOGIN_URL_INVALID": "",
-    "PASSKEY_LOGIN_ERRORED": "",
-    "TRY_AGAIN": "",
-    "PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER": "",
-    "LOGIN_WITH_PASSKEY": ""
-}

+ 0 - 654
web/apps/accounts/public/locales/de-DE/translation.json

@@ -1,654 +0,0 @@
-{
-    "HERO_SLIDE_1_TITLE": "<div>Private Sicherungen</div><div>für deine Erinnerungen</div>",
-    "HERO_SLIDE_1": "Standardmäßig Ende-zu-Ende verschlüsselt",
-    "HERO_SLIDE_2_TITLE": "<div>Sicher gespeichert</div><div>in einem Luftschutzbunker</div>",
-    "HERO_SLIDE_2": "Entwickelt um zu bewahren",
-    "HERO_SLIDE_3_TITLE": "<div>Verfügbar</div><div> überall</div>",
-    "HERO_SLIDE_3": "Android, iOS, Web, Desktop",
-    "LOGIN": "Anmelden",
-    "SIGN_UP": "Registrieren",
-    "NEW_USER": "Neu bei ente",
-    "EXISTING_USER": "Existierender Benutzer",
-    "ENTER_NAME": "Name eingeben",
-    "PUBLIC_UPLOADER_NAME_MESSAGE": "Füge einen Namen hinzu, damit deine Freunde wissen, wem sie für diese tollen Fotos zu danken haben!",
-    "ENTER_EMAIL": "E-Mail-Adresse eingeben",
-    "EMAIL_ERROR": "Geben Sie eine gültige E-Mail-Adresse ein",
-    "REQUIRED": "Erforderlich",
-    "EMAIL_SENT": "Bestätigungscode an <a>{{email}}</a> gesendet",
-    "CHECK_INBOX": "Bitte überprüfe deinen E-Mail-Posteingang (und Spam), um die Verifizierung abzuschließen",
-    "ENTER_OTT": "Bestätigungscode",
-    "RESEND_MAIL": "Code erneut senden",
-    "VERIFY": "Überprüfen",
-    "UNKNOWN_ERROR": "Ein Fehler ist aufgetreten, bitte versuche es erneut",
-    "INVALID_CODE": "Falscher Bestätigungscode",
-    "EXPIRED_CODE": "Ihr Bestätigungscode ist abgelaufen",
-    "SENDING": "Wird gesendet...",
-    "SENT": "Gesendet!",
-    "PASSWORD": "Passwort",
-    "LINK_PASSWORD": "Passwort zum Entsperren des Albums eingeben",
-    "RETURN_PASSPHRASE_HINT": "Passwort",
-    "SET_PASSPHRASE": "Passwort setzen",
-    "VERIFY_PASSPHRASE": "Einloggen",
-    "INCORRECT_PASSPHRASE": "Falsches Passwort",
-    "ENTER_ENC_PASSPHRASE": "Bitte gib ein Passwort ein, mit dem wir deine Daten verschlüsseln können",
-    "PASSPHRASE_DISCLAIMER": "Wir speichern dein Passwort nicht. Wenn du es vergisst, <strong>können wir dir nicht helfen</strong>, deine Daten ohne einen Wiederherstellungsschlüssel wiederherzustellen.",
-    "WELCOME_TO_ENTE_HEADING": "Willkommen bei <a/>",
-    "WELCOME_TO_ENTE_SUBHEADING": "Ende-zu-Ende verschlüsselte Fotospeicherung und Freigabe",
-    "WHERE_YOUR_BEST_PHOTOS_LIVE": "Wo deine besten Fotos leben",
-    "KEY_GENERATION_IN_PROGRESS_MESSAGE": "Generierung von Verschlüsselungsschlüsseln...",
-    "PASSPHRASE_HINT": "Passwort",
-    "CONFIRM_PASSPHRASE": "Passwort bestätigen",
-    "REFERRAL_CODE_HINT": "Wie hast du von Ente erfahren? (optional)",
-    "REFERRAL_INFO": "Wir tracken keine App-Installationen. Es würde uns jedoch helfen, wenn du uns mitteilst, wie du von uns erfahren hast!",
-    "PASSPHRASE_MATCH_ERROR": "Die Passwörter stimmen nicht überein",
-    "CREATE_COLLECTION": "Neues Album",
-    "ENTER_ALBUM_NAME": "Albumname",
-    "CLOSE_OPTION": "Schließen (Esc)",
-    "ENTER_FILE_NAME": "Dateiname",
-    "CLOSE": "Schließen",
-    "NO": "Nein",
-    "NOTHING_HERE": "Hier gibt es noch nichts zu sehen 👀",
-    "UPLOAD": "Hochladen",
-    "IMPORT": "Importieren",
-    "ADD_PHOTOS": "Fotos hinzufügen",
-    "ADD_MORE_PHOTOS": "Mehr Fotos hinzufügen",
-    "add_photos_one": "Eine Datei hinzufügen",
-    "add_photos_other": "{{count, number}} Dateien hinzufügen",
-    "SELECT_PHOTOS": "Foto auswählen",
-    "FILE_UPLOAD": "Datei hochladen",
-    "UPLOAD_STAGE_MESSAGE": {
-        "0": "Hochladen wird vorbereitet",
-        "1": "Lese Google-Metadaten",
-        "2": "Metadaten von {{uploadCounter.finished, number}} / {{uploadCounter.total, number}} Dateien extrahiert",
-        "3": "{{uploadCounter.finished, number}} / {{uploadCounter.total, number}} Dateien verarbeitet",
-        "4": "Verbleibende Uploads werden abgebrochen",
-        "5": "Sicherung abgeschlossen"
-    },
-    "FILE_NOT_UPLOADED_LIST": "Die folgenden Dateien wurden nicht hochgeladen",
-    "SUBSCRIPTION_EXPIRED": "Abonnement abgelaufen",
-    "SUBSCRIPTION_EXPIRED_MESSAGE": "Dein Abonnement ist abgelaufen, bitte <a>erneuere es</a>",
-    "STORAGE_QUOTA_EXCEEDED": "Speichergrenze überschritten",
-    "INITIAL_LOAD_DELAY_WARNING": "Das erste Laden kann einige Zeit in Anspruch nehmen",
-    "USER_DOES_NOT_EXIST": "Leider konnte kein Benutzer mit dieser E-Mail gefunden werden",
-    "NO_ACCOUNT": "Kein Konto vorhanden",
-    "ACCOUNT_EXISTS": "Es ist bereits ein Account vorhanden",
-    "CREATE": "Erstellen",
-    "DOWNLOAD": "Herunterladen",
-    "DOWNLOAD_OPTION": "Herunterladen (D)",
-    "DOWNLOAD_FAVORITES": "Favoriten herunterladen",
-    "DOWNLOAD_UNCATEGORIZED": "Download unkategorisiert",
-    "DOWNLOAD_HIDDEN_ITEMS": "Versteckte Dateien herunterladen",
-    "COPY_OPTION": "Als PNG kopieren (Strg / Cmd - C)",
-    "TOGGLE_FULLSCREEN": "Vollbild umschalten (F)",
-    "ZOOM_IN_OUT": "Herein-/Herauszoomen",
-    "PREVIOUS": "Vorherige (←)",
-    "NEXT": "Weitere (→)",
-    "TITLE_PHOTOS": "Ente Fotos",
-    "TITLE_ALBUMS": "Ente Fotos",
-    "TITLE_AUTH": "Ente Auth",
-    "UPLOAD_FIRST_PHOTO": "Lade dein erstes Foto hoch",
-    "IMPORT_YOUR_FOLDERS": "Importiere deiner Ordner",
-    "UPLOAD_DROPZONE_MESSAGE": "Loslassen, um Dateien zu sichern",
-    "WATCH_FOLDER_DROPZONE_MESSAGE": "Loslassen, um beobachteten Ordner hinzuzufügen",
-    "TRASH_FILES_TITLE": "Dateien löschen?",
-    "TRASH_FILE_TITLE": "Datei löschen?",
-    "DELETE_FILES_TITLE": "Sofort löschen?",
-    "DELETE_FILES_MESSAGE": "Ausgewählte Dateien werden dauerhaft aus Ihrem Ente-Konto gelöscht.",
-    "DELETE": "Löschen",
-    "DELETE_OPTION": "Löschen (DEL)",
-    "FAVORITE_OPTION": "Zu Favoriten hinzufügen (L)",
-    "UNFAVORITE_OPTION": "Von Favoriten entfernen (L)",
-    "MULTI_FOLDER_UPLOAD": "Mehrere Ordner erkannt",
-    "UPLOAD_STRATEGY_CHOICE": "Möchtest du sie hochladen in",
-    "UPLOAD_STRATEGY_SINGLE_COLLECTION": "Ein einzelnes Album",
-    "OR": "oder",
-    "UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "Getrennte Alben",
-    "SESSION_EXPIRED_MESSAGE": "Ihre Sitzung ist abgelaufen. Bitte loggen Sie sich erneut ein, um fortzufahren",
-    "SESSION_EXPIRED": "Sitzung abgelaufen",
-    "PASSWORD_GENERATION_FAILED": "Dein Browser konnte keinen starken Schlüssel generieren, der den Verschlüsselungsstandards des Entes entspricht, bitte versuche die mobile App oder einen anderen Browser zu verwenden",
-    "CHANGE_PASSWORD": "Passwort ändern",
-    "GO_BACK": "Zurück",
-    "RECOVERY_KEY": "Wiederherstellungsschlüssel",
-    "SAVE_LATER": "Auf später verschieben",
-    "SAVE": "Schlüssel speichern",
-    "RECOVERY_KEY_DESCRIPTION": "Falls du dein Passwort vergisst, kannst du deine Daten nur mit diesem Schlüssel wiederherstellen.",
-    "RECOVER_KEY_GENERATION_FAILED": "Wiederherstellungsschlüssel konnte nicht generiert werden, bitte versuche es erneut",
-    "KEY_NOT_STORED_DISCLAIMER": "Wir speichern diesen Schlüssel nicht, also speichere ihn bitte an einem sicheren Ort",
-    "FORGOT_PASSWORD": "Passwort vergessen",
-    "RECOVER_ACCOUNT": "Konto wiederherstellen",
-    "RECOVERY_KEY_HINT": "Wiederherstellungsschlüssel",
-    "RECOVER": "Wiederherstellen",
-    "NO_RECOVERY_KEY": "Kein Wiederherstellungsschlüssel?",
-    "INCORRECT_RECOVERY_KEY": "Falscher Wiederherstellungs-Schlüssel",
-    "SORRY": "Entschuldigung",
-    "NO_RECOVERY_KEY_MESSAGE": "Aufgrund unseres Ende-zu-Ende-Verschlüsselungsprotokolls können Ihre Daten nicht ohne Ihr Passwort oder Ihren Wiederherstellungsschlüssel entschlüsselt werden",
-    "NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Bitte sende eine E-Mail an <a>{{emailID}}</a> von deiner registrierten E-Mail-Adresse",
-    "CONTACT_SUPPORT": "Support kontaktieren",
-    "REQUEST_FEATURE": "Feature anfragen",
-    "SUPPORT": "Support",
-    "CONFIRM": "Bestätigen",
-    "CANCEL": "Abbrechen",
-    "LOGOUT": "Ausloggen",
-    "DELETE_ACCOUNT": "Konto löschen",
-    "DELETE_ACCOUNT_MESSAGE": "<p>Bitte sende eine E-Mail an <a>{{emailID}}</a> mit deiner registrierten E-Mail-Adresse.</p><p>Deine Anfrage wird innerhalb von 72 Stunden bearbeitet.</p>",
-    "LOGOUT_MESSAGE": "Sind sie sicher, dass sie sich ausloggen möchten?",
-    "CHANGE_EMAIL": "E-Mail-Adresse ändern",
-    "OK": "OK",
-    "SUCCESS": "Erfolgreich",
-    "ERROR": "Fehler",
-    "MESSAGE": "Nachricht",
-    "INSTALL_MOBILE_APP": "Installiere unsere <a>Android</a> oder <b>iOS</b> App, um automatisch alle deine Fotos zu sichern",
-    "DOWNLOAD_APP_MESSAGE": "Entschuldigung, dieser Vorgang wird derzeit nur von unserer Desktop-App unterstützt",
-    "DOWNLOAD_APP": "Desktopanwendung herunterladen",
-    "EXPORT": "Daten exportieren",
-    "SUBSCRIPTION": "Abonnement",
-    "SUBSCRIBE": "Abonnieren",
-    "MANAGEMENT_PORTAL": "Zahlungsmethode verwalten",
-    "MANAGE_FAMILY_PORTAL": "Familiengruppe verwalten",
-    "LEAVE_FAMILY_PLAN": "Familienabo verlassen",
-    "LEAVE": "Verlassen",
-    "LEAVE_FAMILY_CONFIRM": "Bist du sicher, dass du den Familien-Tarif verlassen möchtest?",
-    "CHOOSE_PLAN": "Wähle dein Abonnement",
-    "MANAGE_PLAN": "Verwalte dein Abonnement",
-    "ACTIVE": "Aktiv",
-    "OFFLINE_MSG": "Du bist offline, gecachte Erinnerungen werden angezeigt",
-    "FREE_SUBSCRIPTION_INFO": "Du bist auf dem <strong>kostenlosen</strong> Plan, der am {{date, dateTime}} ausläuft",
-    "FAMILY_SUBSCRIPTION_INFO": "Sie haben einen Familienplan verwaltet von",
-    "RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "Erneuert am {{date, dateTime}}",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "Endet am {{date, dateTime}}",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "Ihr Abo endet am {{date, dateTime}}",
-    "ADD_ON_AVAILABLE_TILL": "Dein {{storage, string}} Add-on ist gültig bis {{date, dateTime}}",
-    "STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "Sie haben Ihr Speichervolumen überschritten, bitte <a>upgraden Sie</a>",
-    "SUBSCRIPTION_PURCHASE_SUCCESS": "<p>Wir haben deine Zahlung erhalten</p><p>Dein Abonnement ist gültig bis <strong>{{date, dateTime}}</strong></p>",
-    "SUBSCRIPTION_PURCHASE_CANCELLED": "Dein Kauf wurde abgebrochen. Bitte versuche es erneut, wenn du abonnieren willst",
-    "SUBSCRIPTION_PURCHASE_FAILED": "Kauf des Abonnements fehlgeschlagen Bitte versuchen Sie es erneut",
-    "SUBSCRIPTION_UPDATE_FAILED": "Aktualisierung des Abonnements fehlgeschlagen Bitte versuchen Sie es erneut",
-    "UPDATE_PAYMENT_METHOD_MESSAGE": "Es tut uns leid, die Zahlung ist fehlgeschlagen, als wir versuchten Ihre Karte zu belasten. Bitte aktualisieren Sie Ihre Zahlungsmethode und versuchen Sie es erneut",
-    "STRIPE_AUTHENTICATION_FAILED": "Wir können deine Zahlungsmethode nicht authentifizieren. Bitte wähle eine andere Zahlungsmethode und versuche es erneut",
-    "UPDATE_PAYMENT_METHOD": "Zahlungsmethode aktualisieren",
-    "MONTHLY": "Monatlich",
-    "YEARLY": "Jährlich",
-    "UPDATE_SUBSCRIPTION_MESSAGE": "Sind Sie sicher, dass Sie Ihren Tarif ändern möchten?",
-    "UPDATE_SUBSCRIPTION": "Plan ändern",
-    "CANCEL_SUBSCRIPTION": "Abonnement kündigen",
-    "CANCEL_SUBSCRIPTION_MESSAGE": "<p>Alle deine Daten werden am Ende dieses Abrechnungszeitraums von unseren Servern gelöscht.</p><p>Bist du sicher, dass du dein Abonnement kündigen möchtest?</p>",
-    "CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "<p>Bist du sicher, dass du dein Abonnement beenden möchtest?</p>",
-    "SUBSCRIPTION_CANCEL_FAILED": "Abonnement konnte nicht storniert werden",
-    "SUBSCRIPTION_CANCEL_SUCCESS": "Abonnement erfolgreich beendet",
-    "REACTIVATE_SUBSCRIPTION": "Abonnement reaktivieren",
-    "REACTIVATE_SUBSCRIPTION_MESSAGE": "Nach der Reaktivierung wird am {{date, dateTime}} abgerechnet",
-    "SUBSCRIPTION_ACTIVATE_SUCCESS": "Abonnement erfolgreich aktiviert ",
-    "SUBSCRIPTION_ACTIVATE_FAILED": "Reaktivierung der Abonnementverlängerung fehlgeschlagen",
-    "SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "Vielen Dank",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE": "Mobiles Abonnement kündigen",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "Bitte kündige dein Abonnement in der mobilen App, um hier ein Abonnement zu aktivieren",
-    "MAIL_TO_MANAGE_SUBSCRIPTION": "Bitte kontaktiere uns über <a>{{emailID}}</a>, um dein Abo zu verwalten",
-    "RENAME": "Umbenennen",
-    "RENAME_FILE": "Datei umbenennen",
-    "RENAME_COLLECTION": "Album umbenennen",
-    "DELETE_COLLECTION_TITLE": "Album löschen?",
-    "DELETE_COLLECTION": "Album löschen",
-    "DELETE_COLLECTION_MESSAGE": "Auch die Fotos (und Videos) in diesem Album aus <a>allen</a> anderen Alben löschen, die sie enthalten?",
-    "DELETE_PHOTOS": "Fotos löschen",
-    "KEEP_PHOTOS": "Fotos behalten",
-    "SHARE": "Teilen",
-    "SHARE_COLLECTION": "Album teilen",
-    "SHAREES": "Geteilt mit",
-    "SHARE_WITH_SELF": "Du kannst nicht mit dir selbst teilen",
-    "ALREADY_SHARED": "Hoppla, Sie teilen dies bereits mit {{email}}",
-    "SHARING_BAD_REQUEST_ERROR": "Albumfreigabe nicht erlaubt",
-    "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "Freigabe ist für kostenlose Konten deaktiviert",
-    "DOWNLOAD_COLLECTION": "Album herunterladen",
-    "DOWNLOAD_COLLECTION_MESSAGE": "<p>Bist du sicher, dass du das komplette Album herunterladen möchtest?</p><p>Alle Dateien werden der Warteschlange zum sequenziellen Download hinzugefügt</p>",
-    "CREATE_ALBUM_FAILED": "Fehler beim Erstellen des Albums, bitte versuche es erneut",
-    "SEARCH": "Suchen",
-    "SEARCH_RESULTS": "Ergebnisse durchsuchen",
-    "NO_RESULTS": "Keine Ergebnisse gefunden",
-    "SEARCH_HINT": "Suche nach Alben, Datum, Beschreibungen, ...",
-    "SEARCH_TYPE": {
-        "COLLECTION": "Album",
-        "LOCATION": "Standort",
-        "CITY": "Ort",
-        "DATE": "Datum",
-        "FILE_NAME": "Dateiname",
-        "THING": "Inhalt",
-        "FILE_CAPTION": "Beschreibung",
-        "FILE_TYPE": "Dateityp",
-        "CLIP": "Magie"
-    },
-    "photos_count_zero": "Keine Erinnerungen",
-    "photos_count_one": "Eine Erinnerung",
-    "photos_count_other": "{{count, number}} Erinnerungen",
-    "TERMS_AND_CONDITIONS": "Ich stimme den <a>Bedingungen</a> und <b>Datenschutzrichtlinien</b> zu",
-    "ADD_TO_COLLECTION": "Zum Album hinzufügen",
-    "SELECTED": "ausgewählt",
-    "VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "Dieses Video kann in deinem Browser nicht abgespielt werden",
-    "PEOPLE": "Personen",
-    "INDEXING_SCHEDULED": "Indizierung ist geplant...",
-    "ANALYZING_PHOTOS": "Indiziere Fotos ({{indexStatus.nSyncedFiles,number}} / {{indexStatus.nTotalFiles,number}})",
-    "INDEXING_PEOPLE": "Indiziere Personen in {{indexStatus.nSyncedFiles,number}} Fotos...",
-    "INDEXING_DONE": "{{indexStatus.nSyncedFiles,number}} Fotos wurden indiziert",
-    "UNIDENTIFIED_FACES": "unidentifizierte Gesichter",
-    "OBJECTS": "Objekte",
-    "TEXT": "Text",
-    "INFO": "Info ",
-    "INFO_OPTION": "Info (I)",
-    "FILE_NAME": "Dateiname",
-    "CAPTION_PLACEHOLDER": "Eine Beschreibung hinzufügen",
-    "LOCATION": "Standort",
-    "SHOW_ON_MAP": "In OpenStreetMap öffnen",
-    "MAP": "Karte",
-    "MAP_SETTINGS": "Karten\nEinstellungen",
-    "ENABLE_MAPS": "Karten aktivieren?",
-    "ENABLE_MAP": "Karte aktivieren",
-    "DISABLE_MAPS": "Karten deaktivieren?",
-    "ENABLE_MAP_DESCRIPTION": "<p>Dies wird deine Fotos auf einer Weltkarte anzeigen.</p> <p>Die Karte wird von <a>OpenStreetMap</a> gehostet und die genauen Standorte deiner Fotos werden niemals geteilt.</p> <p>Diese Funktion kannst du jederzeit in den Einstellungen deaktivieren.</p>",
-    "DISABLE_MAP_DESCRIPTION": "<p>Dies wird die Anzeige deiner Fotos auf einer Weltkarte deaktivieren.</p> <p>Du kannst diese Funktion jederzeit in den Einstellungen aktivieren.</p>",
-    "DISABLE_MAP": "Karte deaktivieren",
-    "DETAILS": "Details",
-    "VIEW_EXIF": "Alle EXIF-Daten anzeigen",
-    "NO_EXIF": "Keine EXIF-Daten",
-    "EXIF": "EXIF",
-    "ISO": "ISO",
-    "TWO_FACTOR": "Zwei-Faktor",
-    "TWO_FACTOR_AUTHENTICATION": "Zwei-Faktor-Authentifizierung",
-    "TWO_FACTOR_QR_INSTRUCTION": "Scanne den QR-Code unten mit deiner bevorzugten Authentifizierungs-App",
-    "ENTER_CODE_MANUALLY": "Geben Sie den Code manuell ein",
-    "TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "Bitte gib diesen Code in deiner bevorzugten Authentifizierungs-App ein",
-    "SCAN_QR_CODE": "QR‐Code stattdessen scannen",
-    "ENABLE_TWO_FACTOR": "Zwei-Faktor-Authentifizierung aktivieren",
-    "ENABLE": "Aktivieren",
-    "LOST_DEVICE": "Zwei-Faktor-Gerät verloren",
-    "INCORRECT_CODE": "Falscher Code",
-    "TWO_FACTOR_INFO": "Fügen Sie eine zusätzliche Sicherheitsebene hinzu, indem Sie mehr als Ihre E-Mail und Ihr Passwort benötigen, um sich mit Ihrem Account anzumelden",
-    "DISABLE_TWO_FACTOR_LABEL": "Deaktiviere die Zwei-Faktor-Authentifizierung",
-    "UPDATE_TWO_FACTOR_LABEL": "Authentifizierungsgerät aktualisieren",
-    "DISABLE": "Deaktivieren",
-    "RECONFIGURE": "Neu einrichten",
-    "UPDATE_TWO_FACTOR": "Zweiten Faktor aktualisieren",
-    "UPDATE_TWO_FACTOR_MESSAGE": "Fahren Sie fort, werden alle Ihre zuvor konfigurierten Authentifikatoren ungültig",
-    "UPDATE": "Aktualisierung",
-    "DISABLE_TWO_FACTOR": "Zweiten Faktor deaktivieren",
-    "DISABLE_TWO_FACTOR_MESSAGE": "Bist du sicher, dass du die Zwei-Faktor-Authentifizierung deaktivieren willst",
-    "TWO_FACTOR_DISABLE_FAILED": "Fehler beim Deaktivieren des zweiten Faktors, bitte versuchen Sie es erneut",
-    "EXPORT_DATA": "Daten exportieren",
-    "SELECT_FOLDER": "Ordner auswählen",
-    "DESTINATION": "Zielort",
-    "START": "Start",
-    "LAST_EXPORT_TIME": "Letztes Exportdatum",
-    "EXPORT_AGAIN": "Neusynchronisation",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE": "Lokaler Speicher nicht zugänglich",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "Ihr Browser oder ein Addon blockiert ente vor der Speicherung von Daten im lokalen Speicher. Bitte versuchen Sie, den Browser-Modus zu wechseln und die Seite neu zu laden.",
-    "SEND_OTT": "OTP senden",
-    "EMAIl_ALREADY_OWNED": "Diese E-Mail wird bereits verwendet",
-    "ETAGS_BLOCKED": "",
-    "SKIPPED_VIDEOS_INFO": "",
-    "LIVE_PHOTOS_DETECTED": "",
-    "RETRY_FAILED": "Fehlgeschlagene Uploads erneut probieren",
-    "FAILED_UPLOADS": "Fehlgeschlagene Uploads ",
-    "SKIPPED_FILES": "Ignorierte Uploads",
-    "THUMBNAIL_GENERATION_FAILED_UPLOADS": "Das Vorschaubild konnte nicht erzeugt werden",
-    "UNSUPPORTED_FILES": "Nicht unterstützte Dateien",
-    "SUCCESSFUL_UPLOADS": "Erfolgreiche Uploads",
-    "SKIPPED_INFO": "",
-    "UNSUPPORTED_INFO": "ente unterstützt diese Dateiformate noch nicht",
-    "BLOCKED_UPLOADS": "Blockierte Uploads",
-    "SKIPPED_VIDEOS": "Übersprungene Videos",
-    "INPROGRESS_METADATA_EXTRACTION": "In Bearbeitung",
-    "INPROGRESS_UPLOADS": "Upload läuft",
-    "TOO_LARGE_UPLOADS": "Große Dateien",
-    "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "Zu wenig Speicher",
-    "LARGER_THAN_AVAILABLE_STORAGE_INFO": "Diese Dateien wurden nicht hochgeladen, da sie die maximale Größe für Ihren Speicherplan überschreiten",
-    "TOO_LARGE_INFO": "Diese Dateien wurden nicht hochgeladen, da sie unsere maximale Dateigröße überschreiten",
-    "THUMBNAIL_GENERATION_FAILED_INFO": "Diese Dateien wurden hochgeladen, aber leider konnten wir nicht die Thumbnails für sie generieren.",
-    "UPLOAD_TO_COLLECTION": "In Album hochladen",
-    "UNCATEGORIZED": "Unkategorisiert",
-    "ARCHIVE": "Archiv",
-    "FAVORITES": "Favoriten",
-    "ARCHIVE_COLLECTION": "Album archivieren",
-    "ARCHIVE_SECTION_NAME": "Archiv",
-    "ALL_SECTION_NAME": "Alle",
-    "MOVE_TO_COLLECTION": "Zum Album verschieben",
-    "UNARCHIVE": "Dearchivieren",
-    "UNARCHIVE_COLLECTION": "Album dearchivieren",
-    "HIDE_COLLECTION": "Album ausblenden",
-    "UNHIDE_COLLECTION": "Album wieder einblenden",
-    "MOVE": "Verschieben",
-    "ADD": "Hinzufügen",
-    "REMOVE": "Entfernen",
-    "YES_REMOVE": "Ja, entfernen",
-    "REMOVE_FROM_COLLECTION": "Aus Album entfernen",
-    "TRASH": "Papierkorb",
-    "MOVE_TO_TRASH": "In Papierkorb verschieben",
-    "TRASH_FILES_MESSAGE": "",
-    "TRASH_FILE_MESSAGE": "",
-    "DELETE_PERMANENTLY": "Dauerhaft löschen",
-    "RESTORE": "Wiederherstellen",
-    "RESTORE_TO_COLLECTION": "In Album wiederherstellen",
-    "EMPTY_TRASH": "Papierkorb leeren",
-    "EMPTY_TRASH_TITLE": "Papierkorb leeren?",
-    "EMPTY_TRASH_MESSAGE": "",
-    "LEAVE_SHARED_ALBUM": "Ja, verlassen",
-    "LEAVE_ALBUM": "Album verlassen",
-    "LEAVE_SHARED_ALBUM_TITLE": "Geteiltes Album verlassen?",
-    "LEAVE_SHARED_ALBUM_MESSAGE": "",
-    "NOT_FILE_OWNER": "Dateien in einem freigegebenen Album können nicht gelöscht werden",
-    "CONFIRM_SELF_REMOVE_MESSAGE": "",
-    "CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "Einige der Elemente, die du entfernst, wurden von anderen Nutzern hinzugefügt und du wirst den Zugriff auf sie verlieren.",
-    "SORT_BY_CREATION_TIME_ASCENDING": "Ältestem",
-    "SORT_BY_UPDATION_TIME_DESCENDING": "Zuletzt aktualisiert",
-    "SORT_BY_NAME": "Name",
-    "COMPRESS_THUMBNAILS": "Vorschaubilder komprimieren",
-    "THUMBNAIL_REPLACED": "Vorschaubilder komprimiert",
-    "FIX_THUMBNAIL": "Komprimiere",
-    "FIX_THUMBNAIL_LATER": "Später komprimieren",
-    "REPLACE_THUMBNAIL_NOT_STARTED": "",
-    "REPLACE_THUMBNAIL_COMPLETED": "",
-    "REPLACE_THUMBNAIL_NOOP": "",
-    "REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR": "",
-    "FIX_CREATION_TIME": "Zeit reparieren",
-    "FIX_CREATION_TIME_IN_PROGRESS": "Zeit wird repariert",
-    "CREATION_TIME_UPDATED": "Datei-Zeit aktualisiert",
-    "UPDATE_CREATION_TIME_NOT_STARTED": "Wählen Sie die Option, die Sie verwenden möchten",
-    "UPDATE_CREATION_TIME_COMPLETED": "",
-    "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "",
-    "CAPTION_CHARACTER_LIMIT": "Maximal 5000 Zeichen",
-    "DATE_TIME_ORIGINAL": "",
-    "DATE_TIME_DIGITIZED": "",
-    "METADATA_DATE": "",
-    "CUSTOM_TIME": "Benutzerdefinierte Zeit",
-    "REOPEN_PLAN_SELECTOR_MODAL": "",
-    "OPEN_PLAN_SELECTOR_MODAL_FAILED": "Fehler beim Öffnen der Pläne",
-    "INSTALL": "Installieren",
-    "SHARING_DETAILS": "Details teilen",
-    "MODIFY_SHARING": "Freigabe ändern",
-    "ADD_COLLABORATORS": "Bearbeiter hinzufügen",
-    "ADD_NEW_EMAIL": "Neue E-Mail-Adresse hinzufügen",
-    "shared_with_people_zero": "Mit bestimmten Personen teilen",
-    "shared_with_people_one": "Geteilt mit einer Person",
-    "shared_with_people_other": "Geteilt mit {{count, number}} Personen",
-    "participants_zero": "Keine Teilnehmer",
-    "participants_one": "1 Teilnehmer",
-    "participants_other": "{{count, number}} Teilnehmer",
-    "ADD_VIEWERS": "Betrachter hinzufügen",
-    "PARTICIPANTS": "Teilnehmer",
-    "CHANGE_PERMISSIONS_TO_VIEWER": "",
-    "CHANGE_PERMISSIONS_TO_COLLABORATOR": "",
-    "CONVERT_TO_VIEWER": "Ja, zu \"Beobachter\" ändern",
-    "CONVERT_TO_COLLABORATOR": "",
-    "CHANGE_PERMISSION": "Berechtigung ändern?",
-    "REMOVE_PARTICIPANT": "Entfernen?",
-    "CONFIRM_REMOVE": "Ja, entfernen",
-    "MANAGE": "Verwalten",
-    "ADDED_AS": "Hinzugefügt als",
-    "COLLABORATOR_RIGHTS": "Bearbeiter können Fotos & Videos zu dem geteilten Album hinzufügen",
-    "REMOVE_PARTICIPANT_HEAD": "Teilnehmer entfernen",
-    "OWNER": "Besitzer",
-    "COLLABORATORS": "Bearbeiter",
-    "ADD_MORE": "Mehr hinzufügen",
-    "VIEWERS": "Zuschauer",
-    "OR_ADD_EXISTING": "Oder eine Vorherige auswählen",
-    "REMOVE_PARTICIPANT_MESSAGE": "",
-    "NOT_FOUND": "404 - Nicht gefunden",
-    "LINK_EXPIRED": "Link ist abgelaufen",
-    "LINK_EXPIRED_MESSAGE": "Dieser Link ist abgelaufen oder wurde deaktiviert!",
-    "MANAGE_LINK": "Link verwalten",
-    "LINK_TOO_MANY_REQUESTS": "Sorry, dieses Album wurde auf zu vielen Geräten angezeigt!",
-    "FILE_DOWNLOAD": "Downloads erlauben",
-    "LINK_PASSWORD_LOCK": "Passwort Sperre",
-    "PUBLIC_COLLECT": "Hinzufügen von Fotos erlauben",
-    "LINK_DEVICE_LIMIT": "Geräte Limit",
-    "NO_DEVICE_LIMIT": "Keins",
-    "LINK_EXPIRY": "Ablaufdatum des Links",
-    "NEVER": "Niemals",
-    "DISABLE_FILE_DOWNLOAD": "Download deaktivieren",
-    "DISABLE_FILE_DOWNLOAD_MESSAGE": "",
-    "MALICIOUS_CONTENT": "Enthält schädliche Inhalte",
-    "COPYRIGHT": "Verletzung des Urheberrechts von jemandem, den ich repräsentieren darf",
-    "SHARED_USING": "Freigegeben über ",
-    "ENTE_IO": "ente.io",
-    "SHARING_REFERRAL_CODE": "",
-    "LIVE": "LIVE",
-    "DISABLE_PASSWORD": "Passwort-Sperre deaktivieren",
-    "DISABLE_PASSWORD_MESSAGE": "Sind Sie sicher, dass Sie die Passwort-Sperre deaktivieren möchten?",
-    "PASSWORD_LOCK": "Passwort Sperre",
-    "LOCK": "Sperren",
-    "DOWNLOAD_UPLOAD_LOGS": "Debug-Logs",
-    "UPLOAD_FILES": "Datei",
-    "UPLOAD_DIRS": "Ordner",
-    "UPLOAD_GOOGLE_TAKEOUT": "Google Takeout",
-    "DEDUPLICATE_FILES": "",
-    "AUTHENTICATOR_SECTION": "Authenticator",
-    "NO_DUPLICATES_FOUND": "Du hast keine Duplikate, die gelöscht werden können",
-    "CLUB_BY_CAPTURE_TIME": "",
-    "FILES": "Dateien",
-    "EACH": "",
-    "DEDUPLICATE_BASED_ON_SIZE": "",
-    "STOP_ALL_UPLOADS_MESSAGE": "",
-    "STOP_UPLOADS_HEADER": "Hochladen stoppen?",
-    "YES_STOP_UPLOADS": "Ja, Hochladen stoppen",
-    "STOP_DOWNLOADS_HEADER": "",
-    "YES_STOP_DOWNLOADS": "",
-    "STOP_ALL_DOWNLOADS_MESSAGE": "",
-    "albums_one": "1 Album",
-    "albums_other": "",
-    "ALL_ALBUMS": "Alle Alben",
-    "ALBUMS": "Alben",
-    "ALL_HIDDEN_ALBUMS": "",
-    "HIDDEN_ALBUMS": "",
-    "HIDDEN_ITEMS": "",
-    "HIDDEN_ITEMS_SECTION_NAME": "",
-    "ENTER_TWO_FACTOR_OTP": "Gib den 6-stelligen Code aus\ndeiner Authentifizierungs-App ein.",
-    "CREATE_ACCOUNT": "Account erstellen",
-    "COPIED": "Kopiert",
-    "CANVAS_BLOCKED_TITLE": "Vorschaubild konnte nicht erstellt werden",
-    "CANVAS_BLOCKED_MESSAGE": "",
-    "WATCH_FOLDERS": "",
-    "UPGRADE_NOW": "Jetzt upgraden",
-    "RENEW_NOW": "",
-    "STORAGE": "Speicher",
-    "USED": "verwendet",
-    "YOU": "Sie",
-    "FAMILY": "Familie",
-    "FREE": "frei",
-    "OF": "von",
-    "WATCHED_FOLDERS": "",
-    "NO_FOLDERS_ADDED": "",
-    "FOLDERS_AUTOMATICALLY_MONITORED": "",
-    "UPLOAD_NEW_FILES_TO_ENTE": "",
-    "REMOVE_DELETED_FILES_FROM_ENTE": "",
-    "ADD_FOLDER": "Ordner hinzufügen",
-    "STOP_WATCHING": "",
-    "STOP_WATCHING_FOLDER": "",
-    "STOP_WATCHING_DIALOG_MESSAGE": "",
-    "YES_STOP": "Ja, Stopp",
-    "MONTH_SHORT": "",
-    "YEAR": "Jahr",
-    "FAMILY_PLAN": "Familientarif",
-    "DOWNLOAD_LOGS": "Logs herunterladen",
-    "DOWNLOAD_LOGS_MESSAGE": "",
-    "CHANGE_FOLDER": "Ordner ändern",
-    "TWO_MONTHS_FREE": "Erhalte 2 Monate kostenlos bei Jahresabonnements",
-    "GB": "GB",
-    "POPULAR": "Beliebt",
-    "FREE_PLAN_OPTION_LABEL": "Mit kostenloser Testversion fortfahren",
-    "FREE_PLAN_DESCRIPTION": "1 GB für 1 Jahr",
-    "CURRENT_USAGE": "Aktuelle Nutzung ist <strong>{{usage}}</strong>",
-    "WEAK_DEVICE": "",
-    "DRAG_AND_DROP_HINT": "",
-    "CONFIRM_ACCOUNT_DELETION_MESSAGE": "",
-    "AUTHENTICATE": "Authentifizieren",
-    "UPLOADED_TO_SINGLE_COLLECTION": "",
-    "UPLOADED_TO_SEPARATE_COLLECTIONS": "",
-    "NEVERMIND": "Egal",
-    "UPDATE_AVAILABLE": "Neue Version verfügbar",
-    "UPDATE_INSTALLABLE_MESSAGE": "",
-    "INSTALL_NOW": "Jetzt installieren",
-    "INSTALL_ON_NEXT_LAUNCH": "Beim nächsten Start installieren",
-    "UPDATE_AVAILABLE_MESSAGE": "",
-    "DOWNLOAD_AND_INSTALL": "",
-    "IGNORE_THIS_VERSION": "Diese Version ignorieren",
-    "TODAY": "Heute",
-    "YESTERDAY": "Gestern",
-    "NAME_PLACEHOLDER": "Name...",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "",
-    "CHOSE_THEME": "",
-    "ML_SEARCH": "",
-    "ENABLE_ML_SEARCH_DESCRIPTION": "",
-    "ML_MORE_DETAILS": "",
-    "ENABLE_FACE_SEARCH": "",
-    "ENABLE_FACE_SEARCH_TITLE": "",
-    "ENABLE_FACE_SEARCH_DESCRIPTION": "",
-    "DISABLE_BETA": "Beta deaktivieren",
-    "DISABLE_FACE_SEARCH": "",
-    "DISABLE_FACE_SEARCH_TITLE": "",
-    "DISABLE_FACE_SEARCH_DESCRIPTION": "",
-    "ADVANCED": "Erweitert",
-    "FACE_SEARCH_CONFIRMATION": "",
-    "LABS": "",
-    "YOURS": "",
-    "PASSPHRASE_STRENGTH_WEAK": "Passwortstärke: Schwach",
-    "PASSPHRASE_STRENGTH_MODERATE": "",
-    "PASSPHRASE_STRENGTH_STRONG": "Passwortstärke: Stark",
-    "PREFERENCES": "Einstellungen",
-    "LANGUAGE": "Sprache",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST": "",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "",
-    "SUBSCRIPTION_VERIFICATION_ERROR": "",
-    "STORAGE_UNITS": {
-        "B": "",
-        "KB": "KB",
-        "MB": "MB",
-        "GB": "GB",
-        "TB": "TB"
-    },
-    "AFTER_TIME": {
-        "HOUR": "nach einer Stunde",
-        "DAY": "nach einem Tag",
-        "WEEK": "nach 1 Woche",
-        "MONTH": "nach einem Monat",
-        "YEAR": "nach einem Jahr"
-    },
-    "COPY_LINK": "Link kopieren",
-    "DONE": "Fertig",
-    "LINK_SHARE_TITLE": "Oder einen Link teilen",
-    "REMOVE_LINK": "Link entfernen",
-    "CREATE_PUBLIC_SHARING": "Öffentlichen Link erstellen",
-    "PUBLIC_LINK_CREATED": "Öffentlicher Link erstellt",
-    "PUBLIC_LINK_ENABLED": "Öffentlicher Link aktiviert",
-    "COLLECT_PHOTOS": "",
-    "PUBLIC_COLLECT_SUBTEXT": "",
-    "STOP_EXPORT": "Stop",
-    "EXPORT_PROGRESS": "",
-    "MIGRATING_EXPORT": "",
-    "RENAMING_COLLECTION_FOLDERS": "",
-    "TRASHING_DELETED_FILES": "",
-    "TRASHING_DELETED_COLLECTIONS": "",
-    "EXPORT_NOTIFICATION": {
-        "START": "Export gestartet",
-        "IN_PROGRESS": "",
-        "FINISH": "Export abgeschlossen",
-        "UP_TO_DATE": ""
-    },
-    "CONTINUOUS_EXPORT": "",
-    "TOTAL_ITEMS": "",
-    "PENDING_ITEMS": "",
-    "EXPORT_STARTING": "",
-    "DELETE_ACCOUNT_REASON_LABEL": "",
-    "DELETE_ACCOUNT_REASON_PLACEHOLDER": "",
-    "DELETE_REASON": {
-        "MISSING_FEATURE": "",
-        "BROKEN_BEHAVIOR": "",
-        "FOUND_ANOTHER_SERVICE": "",
-        "NOT_LISTED": ""
-    },
-    "DELETE_ACCOUNT_FEEDBACK_LABEL": "",
-    "DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "",
-    "CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "",
-    "CONFIRM_DELETE_ACCOUNT": "Kontolöschung bestätigen",
-    "FEEDBACK_REQUIRED": "",
-    "FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "",
-    "RECOVER_TWO_FACTOR": "",
-    "at": "",
-    "AUTH_NEXT": "Weiter",
-    "AUTH_DOWNLOAD_MOBILE_APP": "",
-    "HIDDEN": "Versteckt",
-    "HIDE": "Ausblenden",
-    "UNHIDE": "Einblenden",
-    "UNHIDE_TO_COLLECTION": "",
-    "SORT_BY": "Sortieren nach",
-    "NEWEST_FIRST": "Neueste zuerst",
-    "OLDEST_FIRST": "Älteste zuerst",
-    "CONVERSION_FAILED_NOTIFICATION_MESSAGE": "Diese Datei konnte nicht in der Vorschau angezeigt werden. Klicken Sie hier, um das Original herunterzuladen.",
-    "SELECT_COLLECTION": "Album auswählen",
-    "PIN_ALBUM": "Album anheften",
-    "UNPIN_ALBUM": "Album lösen",
-    "DOWNLOAD_COMPLETE": "",
-    "DOWNLOADING_COLLECTION": "",
-    "DOWNLOAD_FAILED": "",
-    "DOWNLOAD_PROGRESS": "",
-    "CHRISTMAS": "",
-    "CHRISTMAS_EVE": "",
-    "NEW_YEAR": "",
-    "NEW_YEAR_EVE": "",
-    "IMAGE": "",
-    "VIDEO": "",
-    "LIVE_PHOTO": "",
-    "CONVERT": "",
-    "CONFIRM_EDITOR_CLOSE_MESSAGE": "",
-    "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "",
-    "BRIGHTNESS": "",
-    "CONTRAST": "",
-    "SATURATION": "",
-    "BLUR": "",
-    "INVERT_COLORS": "",
-    "ASPECT_RATIO": "",
-    "SQUARE": "",
-    "ROTATE_LEFT": "",
-    "ROTATE_RIGHT": "",
-    "FLIP_VERTICALLY": "",
-    "FLIP_HORIZONTALLY": "",
-    "DOWNLOAD_EDITED": "",
-    "SAVE_A_COPY_TO_ENTE": "",
-    "RESTORE_ORIGINAL": "",
-    "TRANSFORM": "",
-    "COLORS": "",
-    "FLIP": "",
-    "ROTATION": "",
-    "RESET": "",
-    "PHOTO_EDITOR": "",
-    "FASTER_UPLOAD": "",
-    "FASTER_UPLOAD_DESCRIPTION": "",
-    "MAGIC_SEARCH_STATUS": "",
-    "INDEXED_ITEMS": "",
-    "CAST_ALBUM_TO_TV": "",
-    "ENTER_CAST_PIN_CODE": "",
-    "PAIR_DEVICE_TO_TV": "",
-    "TV_NOT_FOUND": "",
-    "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
-    "PAIR_WITH_PIN": "",
-    "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
-    "VISIT_CAST_ENTE_IO": "",
-    "CAST_AUTO_PAIR_FAILED": "",
-    "CACHE_DIRECTORY": "",
-    "FREEHAND": "",
-    "APPLY_CROP": "",
-    "PHOTO_EDIT_REQUIRED_TO_SAVE": "",
-    "PASSKEYS": "",
-    "DELETE_PASSKEY": "",
-    "DELETE_PASSKEY_CONFIRMATION": "",
-    "RENAME_PASSKEY": "",
-    "ADD_PASSKEY": "",
-    "ENTER_PASSKEY_NAME": "",
-    "PASSKEYS_DESCRIPTION": "",
-    "CREATED_AT": "",
-    "PASSKEY_LOGIN_FAILED": "",
-    "PASSKEY_LOGIN_URL_INVALID": "",
-    "PASSKEY_LOGIN_ERRORED": "",
-    "TRY_AGAIN": "",
-    "PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER": "",
-    "LOGIN_WITH_PASSKEY": ""
-}

+ 0 - 654
web/apps/accounts/public/locales/en-US/translation.json

@@ -1,654 +0,0 @@
-{
-    "HERO_SLIDE_1_TITLE": "<div>Private backups</div><div>for your memories</div>",
-    "HERO_SLIDE_1": "End-to-end encrypted by default",
-    "HERO_SLIDE_2_TITLE": "<div>Safely stored</div><div>at a fallout shelter</div>",
-    "HERO_SLIDE_2": "Designed to outlive",
-    "HERO_SLIDE_3_TITLE": "<div>Available</div><div> everywhere</div>",
-    "HERO_SLIDE_3": "Android, iOS, Web, Desktop",
-    "LOGIN": "Login",
-    "SIGN_UP": "Signup",
-    "NEW_USER": "New to Ente",
-    "EXISTING_USER": "Existing user",
-    "ENTER_NAME": "Enter name",
-    "PUBLIC_UPLOADER_NAME_MESSAGE": "Add a name so that your friends know who to thank for these great photos!",
-    "ENTER_EMAIL": "Enter email address",
-    "EMAIL_ERROR": "Enter a valid email",
-    "REQUIRED": "Required",
-    "EMAIL_SENT": "Verification code sent to <a>{{email}}</a>",
-    "CHECK_INBOX": "Please check your inbox (and spam) to complete verification",
-    "ENTER_OTT": "Verification code",
-    "RESEND_MAIL": "Resend code",
-    "VERIFY": "Verify",
-    "UNKNOWN_ERROR": "Something went wrong, please try again",
-    "INVALID_CODE": "Invalid verification code",
-    "EXPIRED_CODE": "Your verification code has expired",
-    "SENDING": "Sending...",
-    "SENT": "Sent!",
-    "PASSWORD": "Password",
-    "LINK_PASSWORD": "Enter password to unlock the album",
-    "RETURN_PASSPHRASE_HINT": "Password",
-    "SET_PASSPHRASE": "Set password",
-    "VERIFY_PASSPHRASE": "Sign in",
-    "INCORRECT_PASSPHRASE": "Incorrect password",
-    "ENTER_ENC_PASSPHRASE": "Please enter a password that we can use to encrypt your data",
-    "PASSPHRASE_DISCLAIMER": "We don't store your password, so if you forget it, <strong>we will not be able to help you </strong>recover your data without a recovery key.",
-    "WELCOME_TO_ENTE_HEADING": "Welcome to <a/>",
-    "WELCOME_TO_ENTE_SUBHEADING": "End to end encrypted photo storage and sharing",
-    "WHERE_YOUR_BEST_PHOTOS_LIVE": "Where your best photos live",
-    "KEY_GENERATION_IN_PROGRESS_MESSAGE": "Generating encryption keys...",
-    "PASSPHRASE_HINT": "Password",
-    "CONFIRM_PASSPHRASE": "Confirm password",
-    "REFERRAL_CODE_HINT": "How did you hear about Ente? (optional)",
-    "REFERRAL_INFO": "We don't track app installs, It'd help us if you told us where you found us!",
-    "PASSPHRASE_MATCH_ERROR": "Passwords don't match",
-    "CREATE_COLLECTION": "New album",
-    "ENTER_ALBUM_NAME": "Album name",
-    "CLOSE_OPTION": "Close (Esc)",
-    "ENTER_FILE_NAME": "File name",
-    "CLOSE": "Close",
-    "NO": "No",
-    "NOTHING_HERE": "Nothing to see here yet 👀",
-    "UPLOAD": "Upload",
-    "IMPORT": "Import",
-    "ADD_PHOTOS": "Add photos",
-    "ADD_MORE_PHOTOS": "Add more photos",
-    "add_photos_one": "Add 1 item",
-    "add_photos_other": "Add {{count, number}} items",
-    "SELECT_PHOTOS": "Select photos",
-    "FILE_UPLOAD": "File Upload",
-    "UPLOAD_STAGE_MESSAGE": {
-        "0": "Preparing to upload",
-        "1": "Reading google metadata files",
-        "2": "{{uploadCounter.finished, number}} / {{uploadCounter.total, number}} files metadata extracted",
-        "3": "{{uploadCounter.finished, number}} / {{uploadCounter.total, number}} files processed",
-        "4": "Cancelling remaining uploads",
-        "5": "Backup complete"
-    },
-    "FILE_NOT_UPLOADED_LIST": "The following files were not uploaded",
-    "SUBSCRIPTION_EXPIRED": "Subscription expired",
-    "SUBSCRIPTION_EXPIRED_MESSAGE": "Your subscription has expired, please <a>renew</a>",
-    "STORAGE_QUOTA_EXCEEDED": "Storage limit exceeded",
-    "INITIAL_LOAD_DELAY_WARNING": "First load may take some time",
-    "USER_DOES_NOT_EXIST": "Sorry, could not find a user with that email",
-    "NO_ACCOUNT": "Don't have an account",
-    "ACCOUNT_EXISTS": "Already have an account",
-    "CREATE": "Create",
-    "DOWNLOAD": "Download",
-    "DOWNLOAD_OPTION": "Download (D)",
-    "DOWNLOAD_FAVORITES": "Download favorites",
-    "DOWNLOAD_UNCATEGORIZED": "Download uncategorized",
-    "DOWNLOAD_HIDDEN_ITEMS": "Download hidden items",
-    "COPY_OPTION": "Copy as PNG (Ctrl/Cmd - C)",
-    "TOGGLE_FULLSCREEN": "Toggle fullscreen (F)",
-    "ZOOM_IN_OUT": "Zoom in/out",
-    "PREVIOUS": "Previous (←)",
-    "NEXT": "Next (→)",
-    "TITLE_PHOTOS": "Ente Photos",
-    "TITLE_ALBUMS": "Ente Photos",
-    "TITLE_AUTH": "Ente Auth",
-    "UPLOAD_FIRST_PHOTO": "Upload your first photo",
-    "IMPORT_YOUR_FOLDERS": "Import your folders",
-    "UPLOAD_DROPZONE_MESSAGE": "Drop to backup your files",
-    "WATCH_FOLDER_DROPZONE_MESSAGE": "Drop to add watched folder",
-    "TRASH_FILES_TITLE": "Delete files?",
-    "TRASH_FILE_TITLE": "Delete file?",
-    "DELETE_FILES_TITLE": "Delete immediately?",
-    "DELETE_FILES_MESSAGE": "Selected files will be permanently deleted from your Ente account.",
-    "DELETE": "Delete",
-    "DELETE_OPTION": "Delete (DEL)",
-    "FAVORITE_OPTION": "Favorite (L)",
-    "UNFAVORITE_OPTION": "Unfavorite (L)",
-    "MULTI_FOLDER_UPLOAD": "Multiple folders detected",
-    "UPLOAD_STRATEGY_CHOICE": "Would you like to upload them into",
-    "UPLOAD_STRATEGY_SINGLE_COLLECTION": "A single album",
-    "OR": "or",
-    "UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "Separate albums",
-    "SESSION_EXPIRED_MESSAGE": "Your session has expired, please login again to continue",
-    "SESSION_EXPIRED": "Session expired",
-    "PASSWORD_GENERATION_FAILED": "Your browser was unable to generate a strong key that meets Ente's encryption standards, please try using the mobile app or another browser",
-    "CHANGE_PASSWORD": "Change password",
-    "GO_BACK": "Go back",
-    "RECOVERY_KEY": "Recovery key",
-    "SAVE_LATER": "Do this later",
-    "SAVE": "Save Key",
-    "RECOVERY_KEY_DESCRIPTION": "If you forget your password, the only way you can recover your data is with this key.",
-    "RECOVER_KEY_GENERATION_FAILED": "Recovery code could not be generated, please try again",
-    "KEY_NOT_STORED_DISCLAIMER": "We don't store this key, so please save this in a safe place",
-    "FORGOT_PASSWORD": "Forgot password",
-    "RECOVER_ACCOUNT": "Recover account",
-    "RECOVERY_KEY_HINT": "Recovery key",
-    "RECOVER": "Recover",
-    "NO_RECOVERY_KEY": "No recovery key?",
-    "INCORRECT_RECOVERY_KEY": "Incorrect recovery key",
-    "SORRY": "Sorry",
-    "NO_RECOVERY_KEY_MESSAGE": "Due to the nature of our end-to-end encryption protocol, your data cannot be decrypted without your password or recovery key",
-    "NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Please drop an email to <a>{{emailID}}</a> from your registered email address",
-    "CONTACT_SUPPORT": "Contact support",
-    "REQUEST_FEATURE": "Request Feature",
-    "SUPPORT": "Support",
-    "CONFIRM": "Confirm",
-    "CANCEL": "Cancel",
-    "LOGOUT": "Logout",
-    "DELETE_ACCOUNT": "Delete account",
-    "DELETE_ACCOUNT_MESSAGE": "<p>Please send an email to <a>{{emailID}}</a> from your registered email address.</p><p>Your request will be processed within 72 hours.</p>",
-    "LOGOUT_MESSAGE": "Are you sure you want to logout?",
-    "CHANGE_EMAIL": "Change email",
-    "OK": "OK",
-    "SUCCESS": "Success",
-    "ERROR": "Error",
-    "MESSAGE": "Message",
-    "INSTALL_MOBILE_APP": "Install our <a>Android</a> or <b>iOS</b> app to automatically backup all your photos",
-    "DOWNLOAD_APP_MESSAGE": "Sorry, this operation is currently only supported on our desktop app",
-    "DOWNLOAD_APP": "Download desktop app",
-    "EXPORT": "Export Data",
-    "SUBSCRIPTION": "Subscription",
-    "SUBSCRIBE": "Subscribe",
-    "MANAGEMENT_PORTAL": "Manage payment method",
-    "MANAGE_FAMILY_PORTAL": "Manage family",
-    "LEAVE_FAMILY_PLAN": "Leave family plan",
-    "LEAVE": "Leave",
-    "LEAVE_FAMILY_CONFIRM": "Are you sure that you want to leave family plan?",
-    "CHOOSE_PLAN": "Choose your plan",
-    "MANAGE_PLAN": "Manage your subscription",
-    "ACTIVE": "Active",
-    "OFFLINE_MSG": "You are offline, cached memories are being shown",
-    "FREE_SUBSCRIPTION_INFO": "You are on the <strong>free</strong> plan that expires on {{date, dateTime}}",
-    "FAMILY_SUBSCRIPTION_INFO": "You are on a family plan managed by",
-    "RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "Renews on {{date, dateTime}}",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "Ends on {{date, dateTime}}",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "Your subscription will be cancelled on {{date, dateTime}}",
-    "ADD_ON_AVAILABLE_TILL": "Your {{storage, string}} add-on is valid till {{date, dateTime}}",
-    "STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "You have exceeded your storage quota, please <a>upgrade</a>",
-    "SUBSCRIPTION_PURCHASE_SUCCESS": "<p>We've received your payment</p><p>Your subscription is valid till <strong>{{date, dateTime}}</strong></p>",
-    "SUBSCRIPTION_PURCHASE_CANCELLED": "Your purchase was canceled, please try again if you want to subscribe",
-    "SUBSCRIPTION_PURCHASE_FAILED": "Subscription purchase failed , please try again",
-    "SUBSCRIPTION_UPDATE_FAILED": "Subscription updated failed , please try again",
-    "UPDATE_PAYMENT_METHOD_MESSAGE": "We are sorry, payment failed when we tried to charge your card, please update your payment method and try again",
-    "STRIPE_AUTHENTICATION_FAILED": "We are unable to authenticate your payment method. please choose a different payment method and try again",
-    "UPDATE_PAYMENT_METHOD": "Update payment method",
-    "MONTHLY": "Monthly",
-    "YEARLY": "Yearly",
-    "UPDATE_SUBSCRIPTION_MESSAGE": "Are you sure you want to change your plan?",
-    "UPDATE_SUBSCRIPTION": "Change plan",
-    "CANCEL_SUBSCRIPTION": "Cancel subscription",
-    "CANCEL_SUBSCRIPTION_MESSAGE": "<p>All of your data will be deleted from our servers at the end of this billing period.</p><p>Are you sure that you want to cancel your subscription?</p>",
-    "CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "<p>Are you sure you want to cancel your subscription?</p>",
-    "SUBSCRIPTION_CANCEL_FAILED": "Failed to cancel subscription",
-    "SUBSCRIPTION_CANCEL_SUCCESS": "Subscription canceled successfully",
-    "REACTIVATE_SUBSCRIPTION": "Reactivate subscription",
-    "REACTIVATE_SUBSCRIPTION_MESSAGE": "Once reactivated, you will be billed on {{date, dateTime}}",
-    "SUBSCRIPTION_ACTIVATE_SUCCESS": "Subscription activated successfully ",
-    "SUBSCRIPTION_ACTIVATE_FAILED": "Failed to reactivate subscription renewals",
-    "SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "Thank you",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE": "Cancel mobile subscription",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "Please cancel your subscription from the mobile app to activate a subscription here",
-    "MAIL_TO_MANAGE_SUBSCRIPTION": "Please contact us at <a>{{emailID}}</a> to manage your subscription",
-    "RENAME": "Rename",
-    "RENAME_FILE": "Rename file",
-    "RENAME_COLLECTION": "Rename album",
-    "DELETE_COLLECTION_TITLE": "Delete album?",
-    "DELETE_COLLECTION": "Delete album",
-    "DELETE_COLLECTION_MESSAGE": "Also delete the photos (and videos) present in this album from <a>all</a> other albums they are part of?",
-    "DELETE_PHOTOS": "Delete photos",
-    "KEEP_PHOTOS": "Keep photos",
-    "SHARE": "Share",
-    "SHARE_COLLECTION": "Share album",
-    "SHAREES": "Shared with",
-    "SHARE_WITH_SELF": "Oops, you cannot share with yourself",
-    "ALREADY_SHARED": "Oops, you're already sharing this with {{email}}",
-    "SHARING_BAD_REQUEST_ERROR": "Sharing album not allowed",
-    "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "Sharing is disabled for free accounts",
-    "DOWNLOAD_COLLECTION": "Download album",
-    "DOWNLOAD_COLLECTION_MESSAGE": "<p>Are you sure you want to download the complete album?</p><p>All files will be queued for download sequentially</p>",
-    "CREATE_ALBUM_FAILED": "Failed to create album , please try again",
-    "SEARCH": "Search",
-    "SEARCH_RESULTS": "Search results",
-    "NO_RESULTS": "No results found",
-    "SEARCH_HINT": "Search for albums, dates, descriptions, ...",
-    "SEARCH_TYPE": {
-        "COLLECTION": "Album",
-        "LOCATION": "Location",
-        "CITY": "Location",
-        "DATE": "Date",
-        "FILE_NAME": "File name",
-        "THING": "Content",
-        "FILE_CAPTION": "Description",
-        "FILE_TYPE": "File type",
-        "CLIP": "Magic"
-    },
-    "photos_count_zero": "No memories",
-    "photos_count_one": "1 memory",
-    "photos_count_other": "{{count, number}} memories",
-    "TERMS_AND_CONDITIONS": "I agree to the <a>terms</a> and <b>privacy policy</b>",
-    "ADD_TO_COLLECTION": "Add to album",
-    "SELECTED": "selected",
-    "VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "This video cannot be played on your browser",
-    "PEOPLE": "People",
-    "INDEXING_SCHEDULED": "Indexing is scheduled...",
-    "ANALYZING_PHOTOS": "Indexing photos ({{indexStatus.nSyncedFiles,number}} / {{indexStatus.nTotalFiles,number}})",
-    "INDEXING_PEOPLE": "Indexing people in {{indexStatus.nSyncedFiles,number}} photos...",
-    "INDEXING_DONE": "Indexed {{indexStatus.nSyncedFiles,number}} photos",
-    "UNIDENTIFIED_FACES": "unidentified faces",
-    "OBJECTS": "objects",
-    "TEXT": "text",
-    "INFO": "Info ",
-    "INFO_OPTION": "Info (I)",
-    "FILE_NAME": "File name",
-    "CAPTION_PLACEHOLDER": "Add a description",
-    "LOCATION": "Location",
-    "SHOW_ON_MAP": "View on OpenStreetMap",
-    "MAP": "Map",
-    "MAP_SETTINGS": "Map Settings",
-    "ENABLE_MAPS": "Enable Maps?",
-    "ENABLE_MAP": "Enable map",
-    "DISABLE_MAPS": "Disable Maps?",
-    "ENABLE_MAP_DESCRIPTION": "<p>This will show your photos on a world map.</p> <p>The map is hosted by <a>OpenStreetMap</a>, and the exact locations of your photos are never shared.</p> <p>You can disable this feature anytime from Settings.</p>",
-    "DISABLE_MAP_DESCRIPTION": "<p>This will disable the display of your photos on a world map.</p> <p>You can enable this feature anytime from Settings.</p>",
-    "DISABLE_MAP": "Disable map",
-    "DETAILS": "Details",
-    "VIEW_EXIF": "View all EXIF data",
-    "NO_EXIF": "No EXIF data",
-    "EXIF": "EXIF",
-    "ISO": "ISO",
-    "TWO_FACTOR": "Two-factor",
-    "TWO_FACTOR_AUTHENTICATION": "Two-factor authentication",
-    "TWO_FACTOR_QR_INSTRUCTION": "Scan the QR code below with your favorite authenticator app",
-    "ENTER_CODE_MANUALLY": "Enter the code manually",
-    "TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "Please enter this code in your favorite authenticator app",
-    "SCAN_QR_CODE": "Scan QR code instead",
-    "ENABLE_TWO_FACTOR": "Enable two-factor",
-    "ENABLE": "Enable",
-    "LOST_DEVICE": "Lost two-factor device",
-    "INCORRECT_CODE": "Incorrect code",
-    "TWO_FACTOR_INFO": "Add an additional layer of security by requiring more than your email and password to log in to your account",
-    "DISABLE_TWO_FACTOR_LABEL": "Disable two-factor authentication",
-    "UPDATE_TWO_FACTOR_LABEL": "Update your authenticator device",
-    "DISABLE": "Disable",
-    "RECONFIGURE": "Reconfigure",
-    "UPDATE_TWO_FACTOR": "Update two-factor",
-    "UPDATE_TWO_FACTOR_MESSAGE": "Continuing forward will void any previously configured authenticators",
-    "UPDATE": "Update",
-    "DISABLE_TWO_FACTOR": "Disable two-factor",
-    "DISABLE_TWO_FACTOR_MESSAGE": "Are you sure you want to disable your two-factor authentication",
-    "TWO_FACTOR_DISABLE_FAILED": "Failed to disable two factor, please try again",
-    "EXPORT_DATA": "Export data",
-    "SELECT_FOLDER": "Select folder",
-    "DESTINATION": "Destination",
-    "START": "Start",
-    "LAST_EXPORT_TIME": "Last export time",
-    "EXPORT_AGAIN": "Resync",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE": "Local storage not accessible",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "Your browser or an addon is blocking Ente from saving data into local storage. please try loading this page after switching your browsing mode.",
-    "SEND_OTT": "Send OTP",
-    "EMAIl_ALREADY_OWNED": "Email already taken",
-    "ETAGS_BLOCKED": "<p>We were unable to upload the following files because of your browser configuration.</p><p>Please disable any addons that might be preventing Ente from using <code>eTags</code> to upload large files, or use our <a>desktop app</a> for a more reliable import experience.</p>",
-    "SKIPPED_VIDEOS_INFO": "<p>Presently we do not support adding videos via public links.</p><p>To share videos, please <a>signup</a> for Ente and share with the intended recipients using their email.</p>",
-    "LIVE_PHOTOS_DETECTED": "The photo and video files from your Live Photos have been merged into a single file",
-    "RETRY_FAILED": "Retry failed uploads",
-    "FAILED_UPLOADS": "Failed uploads ",
-    "SKIPPED_FILES": "Ignored uploads",
-    "THUMBNAIL_GENERATION_FAILED_UPLOADS": "Thumbnail generation failed",
-    "UNSUPPORTED_FILES": "Unsupported files",
-    "SUCCESSFUL_UPLOADS": "Successful uploads",
-    "SKIPPED_INFO": "Skipped these as there are files with matching names in the same album",
-    "UNSUPPORTED_INFO": "Ente does not support these file formats yet",
-    "BLOCKED_UPLOADS": "Blocked uploads",
-    "SKIPPED_VIDEOS": "Skipped videos",
-    "INPROGRESS_METADATA_EXTRACTION": "In progress",
-    "INPROGRESS_UPLOADS": "Uploads in progress",
-    "TOO_LARGE_UPLOADS": "Large files",
-    "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "Insufficient storage",
-    "LARGER_THAN_AVAILABLE_STORAGE_INFO": "These files were not uploaded as they exceed the maximum size limit for your storage plan",
-    "TOO_LARGE_INFO": "These files were not uploaded as they exceed our maximum file size limit",
-    "THUMBNAIL_GENERATION_FAILED_INFO": "These files were uploaded, but unfortunately we could not generate the thumbnails for them.",
-    "UPLOAD_TO_COLLECTION": "Upload to album",
-    "UNCATEGORIZED": "Uncategorized",
-    "ARCHIVE": "Archive",
-    "FAVORITES": "Favorites",
-    "ARCHIVE_COLLECTION": "Archive album",
-    "ARCHIVE_SECTION_NAME": "Archive",
-    "ALL_SECTION_NAME": "All",
-    "MOVE_TO_COLLECTION": "Move to album",
-    "UNARCHIVE": "Unarchive",
-    "UNARCHIVE_COLLECTION": "Unarchive album",
-    "HIDE_COLLECTION": "Hide album",
-    "UNHIDE_COLLECTION": "Unhide album",
-    "MOVE": "Move",
-    "ADD": "Add",
-    "REMOVE": "Remove",
-    "YES_REMOVE": "Yes, remove",
-    "REMOVE_FROM_COLLECTION": "Remove from album",
-    "TRASH": "Trash",
-    "MOVE_TO_TRASH": "Move to trash",
-    "TRASH_FILES_MESSAGE": "Selected files will be removed from all albums and moved to trash.",
-    "TRASH_FILE_MESSAGE": "The file will be removed from all albums and moved to trash.",
-    "DELETE_PERMANENTLY": "Delete permanently",
-    "RESTORE": "Restore",
-    "RESTORE_TO_COLLECTION": "Restore to album",
-    "EMPTY_TRASH": "Empty trash",
-    "EMPTY_TRASH_TITLE": "Empty trash?",
-    "EMPTY_TRASH_MESSAGE": "These files will be permanently deleted from your Ente account.",
-    "LEAVE_SHARED_ALBUM": "Yes, leave",
-    "LEAVE_ALBUM": "Leave album",
-    "LEAVE_SHARED_ALBUM_TITLE": "Leave shared album?",
-    "LEAVE_SHARED_ALBUM_MESSAGE": "You will leave the album, and it will stop being visible to you.",
-    "NOT_FILE_OWNER": "You cannot delete files in a shared album",
-    "CONFIRM_SELF_REMOVE_MESSAGE": "Selected items will be removed from this album. Items which are only in this album will be moved to Uncategorized.",
-    "CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "Some of the items you are removing were added by other people, and you will lose access to them.",
-    "SORT_BY_CREATION_TIME_ASCENDING": "Oldest",
-    "SORT_BY_UPDATION_TIME_DESCENDING": "Last updated",
-    "SORT_BY_NAME": "Name",
-    "COMPRESS_THUMBNAILS": "Compress thumbnails",
-    "THUMBNAIL_REPLACED": "Thumbnails compressed",
-    "FIX_THUMBNAIL": "Compress",
-    "FIX_THUMBNAIL_LATER": "Compress later",
-    "REPLACE_THUMBNAIL_NOT_STARTED": "Some of your videos thumbnails can be compressed to save space. would you like Ente to compress them?",
-    "REPLACE_THUMBNAIL_COMPLETED": "Successfully compressed all thumbnails",
-    "REPLACE_THUMBNAIL_NOOP": "You have no thumbnails that can be compressed further",
-    "REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR": "Could not compress some of your thumbnails, please retry",
-    "FIX_CREATION_TIME": "Fix time",
-    "FIX_CREATION_TIME_IN_PROGRESS": "Fixing time",
-    "CREATION_TIME_UPDATED": "File time updated",
-    "UPDATE_CREATION_TIME_NOT_STARTED": "Select the option you want to use",
-    "UPDATE_CREATION_TIME_COMPLETED": "Successfully updated all files",
-    "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "File time updation failed for some files, please retry",
-    "CAPTION_CHARACTER_LIMIT": "5000 characters max",
-    "DATE_TIME_ORIGINAL": "EXIF:DateTimeOriginal",
-    "DATE_TIME_DIGITIZED": "EXIF:DateTimeDigitized",
-    "METADATA_DATE": "EXIF:MetadataDate",
-    "CUSTOM_TIME": "Custom time",
-    "REOPEN_PLAN_SELECTOR_MODAL": "Re-open plans",
-    "OPEN_PLAN_SELECTOR_MODAL_FAILED": "Failed to open plans",
-    "INSTALL": "Install",
-    "SHARING_DETAILS": "Sharing details",
-    "MODIFY_SHARING": "Modify sharing",
-    "ADD_COLLABORATORS": "Add collaborators",
-    "ADD_NEW_EMAIL": "Add a new email",
-    "shared_with_people_zero": "Share with specific people",
-    "shared_with_people_one": "Shared with 1 person",
-    "shared_with_people_other": "Shared with {{count, number}} people",
-    "participants_zero": "No participants",
-    "participants_one": "1 participant",
-    "participants_other": "{{count, number}} participants",
-    "ADD_VIEWERS": "Add viewers",
-    "PARTICIPANTS": "Participants",
-    "CHANGE_PERMISSIONS_TO_VIEWER": "<p>{{selectedEmail}} will not be able to add more photos to the album</p> <p>They will still be able to remove photos added by them</p>",
-    "CHANGE_PERMISSIONS_TO_COLLABORATOR": "{{selectedEmail}} will be able to add photos to the album",
-    "CONVERT_TO_VIEWER": "Yes, convert to viewer",
-    "CONVERT_TO_COLLABORATOR": "Yes, convert to collaborator",
-    "CHANGE_PERMISSION": "Change permission?",
-    "REMOVE_PARTICIPANT": "Remove?",
-    "CONFIRM_REMOVE": "Yes, remove",
-    "MANAGE": "Manage",
-    "ADDED_AS": "Added as",
-    "COLLABORATOR_RIGHTS": "Collaborators can add photos and videos to the shared album",
-    "REMOVE_PARTICIPANT_HEAD": "Remove participant",
-    "OWNER": "Owner",
-    "COLLABORATORS": "Collaborators",
-    "ADD_MORE": "Add more",
-    "VIEWERS": "Viewers",
-    "OR_ADD_EXISTING": "Or pick an existing one",
-    "REMOVE_PARTICIPANT_MESSAGE": "<p>{{selectedEmail}} will be removed from the album</p> <p>Any photos added by them will also be removed from the album</p>",
-    "NOT_FOUND": "404 - not found",
-    "LINK_EXPIRED": "Link expired",
-    "LINK_EXPIRED_MESSAGE": "This link has either expired or been disabled!",
-    "MANAGE_LINK": "Manage link",
-    "LINK_TOO_MANY_REQUESTS": "Sorry, this album has been viewed on too many devices!",
-    "FILE_DOWNLOAD": "Allow downloads",
-    "LINK_PASSWORD_LOCK": "Password lock",
-    "PUBLIC_COLLECT": "Allow adding photos",
-    "LINK_DEVICE_LIMIT": "Device limit",
-    "NO_DEVICE_LIMIT": "None",
-    "LINK_EXPIRY": "Link expiry",
-    "NEVER": "Never",
-    "DISABLE_FILE_DOWNLOAD": "Disable download",
-    "DISABLE_FILE_DOWNLOAD_MESSAGE": "<p>Are you sure that you want to disable the download button for files?</p><p>Viewers can still take screenshots or save a copy of your photos using external tools.</p>",
-    "MALICIOUS_CONTENT": "Contains malicious content",
-    "COPYRIGHT": "Infringes on the copyright of someone I am authorized to represent",
-    "SHARED_USING": "Shared using ",
-    "ENTE_IO": "ente.io",
-    "SHARING_REFERRAL_CODE": "Use code <strong>{{referralCode}}</strong> to get 10 GB free",
-    "LIVE": "LIVE",
-    "DISABLE_PASSWORD": "Disable password lock",
-    "DISABLE_PASSWORD_MESSAGE": "Are you sure that you want to disable the password lock?",
-    "PASSWORD_LOCK": "Password lock",
-    "LOCK": "Lock",
-    "DOWNLOAD_UPLOAD_LOGS": "Debug logs",
-    "UPLOAD_FILES": "File",
-    "UPLOAD_DIRS": "Folder",
-    "UPLOAD_GOOGLE_TAKEOUT": "Google takeout",
-    "DEDUPLICATE_FILES": "Deduplicate files",
-    "AUTHENTICATOR_SECTION": "Authenticator",
-    "NO_DUPLICATES_FOUND": "You've no duplicate files that can be cleared",
-    "CLUB_BY_CAPTURE_TIME": "Club by capture time",
-    "FILES": "files",
-    "EACH": "each",
-    "DEDUPLICATE_BASED_ON_SIZE": "The following files were clubbed based on their sizes, please review and delete items you believe are duplicates",
-    "STOP_ALL_UPLOADS_MESSAGE": "Are you sure that you want to stop all the uploads in progress?",
-    "STOP_UPLOADS_HEADER": "Stop uploads?",
-    "YES_STOP_UPLOADS": "Yes, stop uploads",
-    "STOP_DOWNLOADS_HEADER": "Stop downloads?",
-    "YES_STOP_DOWNLOADS": "Yes, stop downloads",
-    "STOP_ALL_DOWNLOADS_MESSAGE": "Are you sure that you want to stop all the downloads in progress?",
-    "albums_one": "1 Album",
-    "albums_other": "{{count, number}} Albums",
-    "ALL_ALBUMS": "All Albums",
-    "ALBUMS": "Albums",
-    "ALL_HIDDEN_ALBUMS": "All hidden albums",
-    "HIDDEN_ALBUMS": "Hidden albums",
-    "HIDDEN_ITEMS": "Hidden items",
-    "HIDDEN_ITEMS_SECTION_NAME": "Hidden_items",
-    "ENTER_TWO_FACTOR_OTP": "Enter the 6-digit code from your authenticator app.",
-    "CREATE_ACCOUNT": "Create account",
-    "COPIED": "Copied",
-    "CANVAS_BLOCKED_TITLE": "Unable to generate thumbnail",
-    "CANVAS_BLOCKED_MESSAGE": "<p>It looks like your browser has disabled access to canvas, which is necessary to generate thumbnails for your photos </p> <p> Please enable access to your browser's canvas, or check out our desktop app</p>",
-    "WATCH_FOLDERS": "Watch folders",
-    "UPGRADE_NOW": "Upgrade now",
-    "RENEW_NOW": "Renew now",
-    "STORAGE": "Storage",
-    "USED": "used",
-    "YOU": "You",
-    "FAMILY": "Family",
-    "FREE": "free",
-    "OF": "of",
-    "WATCHED_FOLDERS": "Watched folders",
-    "NO_FOLDERS_ADDED": "No folders added yet!",
-    "FOLDERS_AUTOMATICALLY_MONITORED": "The folders you add here will monitored to automatically",
-    "UPLOAD_NEW_FILES_TO_ENTE": "Upload new files to Ente",
-    "REMOVE_DELETED_FILES_FROM_ENTE": "Remove deleted files from Ente",
-    "ADD_FOLDER": "Add folder",
-    "STOP_WATCHING": "Stop watching",
-    "STOP_WATCHING_FOLDER": "Stop watching folder?",
-    "STOP_WATCHING_DIALOG_MESSAGE": "Your existing files will not be deleted, but Ente will stop automatically updating the linked Ente album on changes in this folder.",
-    "YES_STOP": "Yes, stop",
-    "MONTH_SHORT": "mo",
-    "YEAR": "year",
-    "FAMILY_PLAN": "Family plan",
-    "DOWNLOAD_LOGS": "Download logs",
-    "DOWNLOAD_LOGS_MESSAGE": "<p>This will download debug logs, which you can email to us to help debug your issue.</p><p> Please note that file names will be included to help track issues with specific files. </p>",
-    "CHANGE_FOLDER": "Change Folder",
-    "TWO_MONTHS_FREE": "Get 2 months free on yearly plans",
-    "GB": "GB",
-    "POPULAR": "Popular",
-    "FREE_PLAN_OPTION_LABEL": "Continue with free trial",
-    "FREE_PLAN_DESCRIPTION": "1 GB for 1 year",
-    "CURRENT_USAGE": "Current usage is <strong>{{usage}}</strong>",
-    "WEAK_DEVICE": "The web browser you're using is not powerful enough to encrypt your photos. Please try to log in to Ente on your computer, or download the Ente mobile/desktop app.",
-    "DRAG_AND_DROP_HINT": "Or drag and drop into the Ente window",
-    "CONFIRM_ACCOUNT_DELETION_MESSAGE": "Your uploaded data will be scheduled for deletion, and your account will be permanently deleted.<br/><br/>This action is not reversible.",
-    "AUTHENTICATE": "Authenticate",
-    "UPLOADED_TO_SINGLE_COLLECTION": "Uploaded to single collection",
-    "UPLOADED_TO_SEPARATE_COLLECTIONS": "Uploaded to separate collections",
-    "NEVERMIND": "Nevermind",
-    "UPDATE_AVAILABLE": "Update available",
-    "UPDATE_INSTALLABLE_MESSAGE": "A new version of Ente is ready to be installed.",
-    "INSTALL_NOW": "Install now",
-    "INSTALL_ON_NEXT_LAUNCH": "Install on next launch",
-    "UPDATE_AVAILABLE_MESSAGE": "A new version of Ente has been released, but it cannot be automatically downloaded and installed.",
-    "DOWNLOAD_AND_INSTALL": "Download and install",
-    "IGNORE_THIS_VERSION": "Ignore this version",
-    "TODAY": "Today",
-    "YESTERDAY": "Yesterday",
-    "NAME_PLACEHOLDER": "Name...",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "Cannot create albums from file/folder mix",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "<p>You have dragged and dropped a mixture of files and folders.</p><p>Please provide either only files, or only folders when selecting option to create separate albums</p>",
-    "CHOSE_THEME": "Choose theme",
-    "ML_SEARCH": "Face recognition",
-    "ENABLE_ML_SEARCH_DESCRIPTION": "<p>This will enable on-device machine learning and face search which will start analyzing your uploaded photos locally.</p><p>For the first run after login or enabling this feature, it will download all images on local device to analyze them. So please only enable this if you are ok with bandwidth and local processing of all images in your photo library.</p><p>If this is the first time you're enabling this, we'll also ask your permission to process face data.</p>",
-    "ML_MORE_DETAILS": "More details",
-    "ENABLE_FACE_SEARCH": "Enable face recognition",
-    "ENABLE_FACE_SEARCH_TITLE": "Enable face recognition?",
-    "ENABLE_FACE_SEARCH_DESCRIPTION": "<p>If you enable face recognition, Ente will extract face geometry from your photos. This will happen on your device, and any generated biometric data will be end-to-encrypted.<p/><p><a>Please click here for more details about this feature in our privacy policy</a></p>",
-    "DISABLE_BETA": "Pause recognition",
-    "DISABLE_FACE_SEARCH": "Disable face recognition",
-    "DISABLE_FACE_SEARCH_TITLE": "Disable face recognition?",
-    "DISABLE_FACE_SEARCH_DESCRIPTION": "<p>Ente will stop processing face geometry.</p><p>You can reenable face recognition again if you wish, so this operation is safe.</p>",
-    "ADVANCED": "Advanced",
-    "FACE_SEARCH_CONFIRMATION": "I understand, and wish to allow Ente to process face geometry",
-    "LABS": "Labs",
-    "YOURS": "yours",
-    "PASSPHRASE_STRENGTH_WEAK": "Password strength: Weak",
-    "PASSPHRASE_STRENGTH_MODERATE": "Password strength: Moderate",
-    "PASSPHRASE_STRENGTH_STRONG": "Password strength: Strong",
-    "PREFERENCES": "Preferences",
-    "LANGUAGE": "Language",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST": "Invalid export directory",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "<p>The export directory you have selected does not exist.</p><p> Please select a valid directory.</p>",
-    "SUBSCRIPTION_VERIFICATION_ERROR": "Subscription verification failed",
-    "STORAGE_UNITS": {
-        "B": "B",
-        "KB": "KB",
-        "MB": "MB",
-        "GB": "GB",
-        "TB": "TB"
-    },
-    "AFTER_TIME": {
-        "HOUR": "after an hour",
-        "DAY": "after a day",
-        "WEEK": "after a week",
-        "MONTH": "after a month",
-        "YEAR": "after a year"
-    },
-    "COPY_LINK": "Copy link",
-    "DONE": "Done",
-    "LINK_SHARE_TITLE": "Or share a link",
-    "REMOVE_LINK": "Remove link",
-    "CREATE_PUBLIC_SHARING": "Create public link",
-    "PUBLIC_LINK_CREATED": "Public link created",
-    "PUBLIC_LINK_ENABLED": "Public link enabled",
-    "COLLECT_PHOTOS": "Collect photos",
-    "PUBLIC_COLLECT_SUBTEXT": "Allow people with the link to also add photos to the shared album.",
-    "STOP_EXPORT": "Stop",
-    "EXPORT_PROGRESS": "<a>{{progress.success, number}} / {{progress.total, number}}</a> items synced",
-    "MIGRATING_EXPORT": "Preparing...",
-    "RENAMING_COLLECTION_FOLDERS": "Renaming album folders...",
-    "TRASHING_DELETED_FILES": "Trashing deleted files...",
-    "TRASHING_DELETED_COLLECTIONS": "Trashing deleted albums...",
-    "EXPORT_NOTIFICATION": {
-        "START": "Export started",
-        "IN_PROGRESS": "Export already in progress",
-        "FINISH": "Export finished",
-        "UP_TO_DATE": "No new files to export"
-    },
-    "CONTINUOUS_EXPORT": "Sync continuously",
-    "TOTAL_ITEMS": "Total items",
-    "PENDING_ITEMS": "Pending items",
-    "EXPORT_STARTING": "Export starting...",
-    "DELETE_ACCOUNT_REASON_LABEL": "What is the main reason you are deleting your account?",
-    "DELETE_ACCOUNT_REASON_PLACEHOLDER": "Select a reason",
-    "DELETE_REASON": {
-        "MISSING_FEATURE": "It's missing a key feature that I need",
-        "BROKEN_BEHAVIOR": "The app or a certain feature does not behave as I think it should",
-        "FOUND_ANOTHER_SERVICE": "I found another service that I like better",
-        "NOT_LISTED": "My reason isn't listed"
-    },
-    "DELETE_ACCOUNT_FEEDBACK_LABEL": "We are sorry to see you go. Please explain why you are leaving to help us improve.",
-    "DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "Feedback",
-    "CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "Yes, I want to permanently delete this account and all its data",
-    "CONFIRM_DELETE_ACCOUNT": "Confirm Account Deletion",
-    "FEEDBACK_REQUIRED": "Kindly help us with this information",
-    "FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "What does the other service do better?",
-    "RECOVER_TWO_FACTOR": "Recover two-factor",
-    "at": "at",
-    "AUTH_NEXT": "next",
-    "AUTH_DOWNLOAD_MOBILE_APP": "Download our mobile app to manage your secrets",
-    "HIDDEN": "Hidden",
-    "HIDE": "Hide",
-    "UNHIDE": "Unhide",
-    "UNHIDE_TO_COLLECTION": "Unhide to album",
-    "SORT_BY": "Sort by",
-    "NEWEST_FIRST": "Newest first",
-    "OLDEST_FIRST": "Oldest first",
-    "CONVERSION_FAILED_NOTIFICATION_MESSAGE": "This file could not be previewed. Click here to download the original.",
-    "SELECT_COLLECTION": "Select album",
-    "PIN_ALBUM": "Pin album",
-    "UNPIN_ALBUM": "Unpin album",
-    "DOWNLOAD_COMPLETE": "Download complete",
-    "DOWNLOADING_COLLECTION": "Downloading {{name}}",
-    "DOWNLOAD_FAILED": "Download failed",
-    "DOWNLOAD_PROGRESS": "{{progress.current}} / {{progress.total}} files",
-    "CHRISTMAS": "Christmas",
-    "CHRISTMAS_EVE": "Christmas Eve",
-    "NEW_YEAR": "New Year",
-    "NEW_YEAR_EVE": "New Year's Eve",
-    "IMAGE": "Image",
-    "VIDEO": "Video",
-    "LIVE_PHOTO": "Live Photo",
-    "CONVERT": "Convert",
-    "CONFIRM_EDITOR_CLOSE_MESSAGE": "Are you sure you want to close the editor?",
-    "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "Download your edited image or save a copy to Ente to persist your changes.",
-    "BRIGHTNESS": "Brightness",
-    "CONTRAST": "Contrast",
-    "SATURATION": "Saturation",
-    "BLUR": "Blur",
-    "INVERT_COLORS": "Invert Colors",
-    "ASPECT_RATIO": "Aspect Ratio",
-    "SQUARE": "Square",
-    "ROTATE_LEFT": "Rotate Left",
-    "ROTATE_RIGHT": "Rotate Right",
-    "FLIP_VERTICALLY": "Flip Vertically",
-    "FLIP_HORIZONTALLY": "Flip Horizontally",
-    "DOWNLOAD_EDITED": "Download Edited",
-    "SAVE_A_COPY_TO_ENTE": "Save a copy to Ente",
-    "RESTORE_ORIGINAL": "Restore Original",
-    "TRANSFORM": "Transform",
-    "COLORS": "Colors",
-    "FLIP": "Flip",
-    "ROTATION": "Rotation",
-    "RESET": "Reset",
-    "PHOTO_EDITOR": "Photo Editor",
-    "FASTER_UPLOAD": "Faster uploads",
-    "FASTER_UPLOAD_DESCRIPTION": "Route uploads through nearby servers",
-    "MAGIC_SEARCH_STATUS": "Magic Search Status",
-    "INDEXED_ITEMS": "Indexed items",
-    "CAST_ALBUM_TO_TV": "Play album on TV",
-    "ENTER_CAST_PIN_CODE": "Enter the code you see on the TV below to pair this device.",
-    "PAIR_DEVICE_TO_TV": "Pair devices",
-    "TV_NOT_FOUND": "TV not found. Did you enter the PIN correctly?",
-    "AUTO_CAST_PAIR": "Auto Pair",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "Auto Pair requires connecting to Google servers and only works with Chromecast supported devices. Google will not receive sensitive data, such as your photos.",
-    "PAIR_WITH_PIN": "Pair with PIN",
-    "CHOOSE_DEVICE_FROM_BROWSER": "Choose a cast-compatible device from the browser popup.",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "Pair with PIN works for any large screen device you want to play your album on.",
-    "VISIT_CAST_ENTE_IO": "Visit cast.ente.io on the device you want to pair.",
-    "CAST_AUTO_PAIR_FAILED": "Chromecast Auto Pair failed. Please try again.",
-    "CACHE_DIRECTORY": "Cache folder",
-    "FREEHAND": "Freehand",
-    "APPLY_CROP": "Apply Crop",
-    "PHOTO_EDIT_REQUIRED_TO_SAVE": "At least one transformation or color adjustment must be performed before saving.",
-    "PASSKEYS": "Passkeys",
-    "DELETE_PASSKEY": "Delete passkey",
-    "DELETE_PASSKEY_CONFIRMATION": "Are you sure you want to delete this passkey? This action is irreversible.",
-    "RENAME_PASSKEY": "Rename passkey",
-    "ADD_PASSKEY": "Add passkey",
-    "ENTER_PASSKEY_NAME": "Enter passkey name",
-    "PASSKEYS_DESCRIPTION": "Passkeys are a modern and secure second-factor for your Ente account. They use on-device biometric authentication for convenience and security.",
-    "CREATED_AT": "Created at",
-    "PASSKEY_LOGIN_FAILED": "Passkey login failed",
-    "PASSKEY_LOGIN_URL_INVALID": "The login URL is invalid.",
-    "PASSKEY_LOGIN_ERRORED": "An error occurred while logging in with passkey.",
-    "TRY_AGAIN": "Try again",
-    "PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER": "Follow the steps from your browser to continue logging in.",
-    "LOGIN_WITH_PASSKEY": "Login with passkey"
-}

+ 0 - 654
web/apps/accounts/public/locales/es-ES/translation.json

@@ -1,654 +0,0 @@
-{
-    "HERO_SLIDE_1_TITLE": "<div>Copias de seguridad privadas</div><div>para su recuerdos</div>",
-    "HERO_SLIDE_1": "Encriptado de extremo a extremo por defecto",
-    "HERO_SLIDE_2_TITLE": "<div>Almacenado de forma segura</div><div>en un refugio de llenos</div>",
-    "HERO_SLIDE_2": "Diseñado para superar",
-    "HERO_SLIDE_3_TITLE": "<div>Disponible</div><div> en todas partes</div>",
-    "HERO_SLIDE_3": "Android, iOS, web, computadora",
-    "LOGIN": "Conectar",
-    "SIGN_UP": "Registro",
-    "NEW_USER": "Nuevo en ente",
-    "EXISTING_USER": "Usuario existente",
-    "ENTER_NAME": "Introducir nombre",
-    "PUBLIC_UPLOADER_NAME_MESSAGE": "¡Añade un nombre para que tus amigos sepan a quién dar las gracias por estas fotos geniales!",
-    "ENTER_EMAIL": "Introducir email",
-    "EMAIL_ERROR": "Introduce un email válido",
-    "REQUIRED": "Requerido",
-    "EMAIL_SENT": "Código de verificación enviado al <a>{{email}}</a>",
-    "CHECK_INBOX": "Revisa tu bandeja de entrada (y spam) para completar la verificación",
-    "ENTER_OTT": "Código de verificación",
-    "RESEND_MAIL": "Reenviar el código",
-    "VERIFY": "Verificar",
-    "UNKNOWN_ERROR": "Se produjo un error. Por favor, inténtalo de nuevo",
-    "INVALID_CODE": "Código de verificación inválido",
-    "EXPIRED_CODE": "Código de verificación expirado",
-    "SENDING": "Enviando...",
-    "SENT": "Enviado!",
-    "PASSWORD": "Contraseña",
-    "LINK_PASSWORD": "Introducir contraseña para desbloquear el álbum",
-    "RETURN_PASSPHRASE_HINT": "Contraseña",
-    "SET_PASSPHRASE": "Definir contraseña",
-    "VERIFY_PASSPHRASE": "Ingresar",
-    "INCORRECT_PASSPHRASE": "Contraseña incorrecta",
-    "ENTER_ENC_PASSPHRASE": "Introducir una contraseña que podamos usar para cifrar sus datos",
-    "PASSPHRASE_DISCLAIMER": "No guardamos su contraseña, así que si la olvida, <strong>no podremos ayudarte </strong>a recuperar tus datos sin una clave de recuperación.",
-    "WELCOME_TO_ENTE_HEADING": "Bienvenido a <a/>",
-    "WELCOME_TO_ENTE_SUBHEADING": "Almacenamiento y compartición de fotos cifradas de extremo a extremo",
-    "WHERE_YOUR_BEST_PHOTOS_LIVE": "Donde vivan su mejores fotos",
-    "KEY_GENERATION_IN_PROGRESS_MESSAGE": "Generando claves de encriptación...",
-    "PASSPHRASE_HINT": "Contraseña",
-    "CONFIRM_PASSPHRASE": "Confirmar contraseña",
-    "REFERRAL_CODE_HINT": "",
-    "REFERRAL_INFO": "",
-    "PASSPHRASE_MATCH_ERROR": "Las contraseñas no coinciden",
-    "CREATE_COLLECTION": "Nuevo álbum",
-    "ENTER_ALBUM_NAME": "Nombre del álbum",
-    "CLOSE_OPTION": "Cerrar (Esc)",
-    "ENTER_FILE_NAME": "Nombre del archivo",
-    "CLOSE": "Cerrar",
-    "NO": "No",
-    "NOTHING_HERE": "Nada para ver aquí aún 👀",
-    "UPLOAD": "Cargar",
-    "IMPORT": "Importar",
-    "ADD_PHOTOS": "Añadir fotos",
-    "ADD_MORE_PHOTOS": "Añadir más fotos",
-    "add_photos_one": "Añadir 1 foto",
-    "add_photos_other": "Añadir {{count}} fotos",
-    "SELECT_PHOTOS": "Seleccionar fotos",
-    "FILE_UPLOAD": "Subir archivo",
-    "UPLOAD_STAGE_MESSAGE": {
-        "0": "Preparando la subida",
-        "1": "Leyendo archivos de metadatos de google",
-        "2": "{{uploadCounter.finished}} / {{uploadCounter.total}} archivos metadatos extraídos",
-        "3": "{{uploadCounter.finished}} / {{uploadCounter.total}} archivos metadatos extraídos",
-        "4": "Cancelar subidas restantes",
-        "5": "Copia de seguridad completa"
-    },
-    "FILE_NOT_UPLOADED_LIST": "Los siguientes archivos no se han subido",
-    "SUBSCRIPTION_EXPIRED": "Suscripción caducada",
-    "SUBSCRIPTION_EXPIRED_MESSAGE": "Tu suscripción ha caducado, por favor <a>renuévala</a>",
-    "STORAGE_QUOTA_EXCEEDED": "Límite de datos excedido",
-    "INITIAL_LOAD_DELAY_WARNING": "La primera carga puede tomar algún tiempo",
-    "USER_DOES_NOT_EXIST": "Lo sentimos, no se pudo encontrar un usuario con ese email",
-    "NO_ACCOUNT": "No tienes una cuenta",
-    "ACCOUNT_EXISTS": "Ya tienes una cuenta",
-    "CREATE": "Crear",
-    "DOWNLOAD": "Descargar",
-    "DOWNLOAD_OPTION": "Descargar (D)",
-    "DOWNLOAD_FAVORITES": "Descargar favoritos",
-    "DOWNLOAD_UNCATEGORIZED": "Descargar no categorizados",
-    "DOWNLOAD_HIDDEN_ITEMS": "",
-    "COPY_OPTION": "Copiar como PNG (Ctrl/Cmd - C)",
-    "TOGGLE_FULLSCREEN": "Alternar pantalla completa (F)",
-    "ZOOM_IN_OUT": "Acercar/alejar",
-    "PREVIOUS": "Anterior (←)",
-    "NEXT": "Siguiente (→)",
-    "TITLE_PHOTOS": "ente Fotos",
-    "TITLE_ALBUMS": "ente Fotos",
-    "TITLE_AUTH": "ente Auth",
-    "UPLOAD_FIRST_PHOTO": "Carga tu primer archivo",
-    "IMPORT_YOUR_FOLDERS": "Importar tus carpetas",
-    "UPLOAD_DROPZONE_MESSAGE": "Soltar para respaldar tus archivos",
-    "WATCH_FOLDER_DROPZONE_MESSAGE": "Soltar para añadir carpeta vigilada",
-    "TRASH_FILES_TITLE": "Eliminar archivos?",
-    "TRASH_FILE_TITLE": "Eliminar archivo?",
-    "DELETE_FILES_TITLE": "Eliminar inmediatamente?",
-    "DELETE_FILES_MESSAGE": "Los archivos seleccionados serán eliminados permanentemente de tu cuenta ente.",
-    "DELETE": "Eliminar",
-    "DELETE_OPTION": "Eliminar (DEL)",
-    "FAVORITE_OPTION": "Favorito (L)",
-    "UNFAVORITE_OPTION": "No favorito (L)",
-    "MULTI_FOLDER_UPLOAD": "Múltiples carpetas detectadas",
-    "UPLOAD_STRATEGY_CHOICE": "Quieres subirlos a",
-    "UPLOAD_STRATEGY_SINGLE_COLLECTION": "Un solo álbum",
-    "OR": "o",
-    "UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "Separar álbumes",
-    "SESSION_EXPIRED_MESSAGE": "Tu sesión ha caducado. Inicia sesión de nuevo para continuar",
-    "SESSION_EXPIRED": "Sesión caducado",
-    "PASSWORD_GENERATION_FAILED": "Su navegador no ha podido generar una clave fuerte que cumpla con los estándares de cifrado de la entidad, por favor intente usar la aplicación móvil u otro navegador",
-    "CHANGE_PASSWORD": "Cambiar contraseña",
-    "GO_BACK": "Retroceder",
-    "RECOVERY_KEY": "Clave de recuperación",
-    "SAVE_LATER": "Hacer más tarde",
-    "SAVE": "Guardar Clave",
-    "RECOVERY_KEY_DESCRIPTION": "Si olvida su contraseña, la única forma de recuperar sus datos es con esta clave.",
-    "RECOVER_KEY_GENERATION_FAILED": "El código de recuperación no pudo ser generado, por favor inténtalo de nuevo",
-    "KEY_NOT_STORED_DISCLAIMER": "No almacenamos esta clave, así que por favor guarde esto en un lugar seguro",
-    "FORGOT_PASSWORD": "Contraseña olvidada",
-    "RECOVER_ACCOUNT": "Recuperar cuenta",
-    "RECOVERY_KEY_HINT": "Clave de recuperación",
-    "RECOVER": "Recuperar",
-    "NO_RECOVERY_KEY": "No hay clave de recuperación?",
-    "INCORRECT_RECOVERY_KEY": "Clave de recuperación incorrecta",
-    "SORRY": "Lo sentimos",
-    "NO_RECOVERY_KEY_MESSAGE": "Debido a la naturaleza de nuestro protocolo de cifrado de extremo a extremo, sus datos no pueden ser descifrados sin su contraseña o clave de recuperación",
-    "NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Por favor, envíe un email a <a>{{emailID}}</a> desde su dirección de correo electrónico registrada",
-    "CONTACT_SUPPORT": "Contacta con soporte",
-    "REQUEST_FEATURE": "Solicitar una función",
-    "SUPPORT": "Soporte",
-    "CONFIRM": "Confirmar",
-    "CANCEL": "Cancelar",
-    "LOGOUT": "Cerrar sesión",
-    "DELETE_ACCOUNT": "Eliminar cuenta",
-    "DELETE_ACCOUNT_MESSAGE": "<p>Por favor, envíe un email a <a>{{emailID}}</a> desde su dirección de correo electrónico registrada</p><p>Su solicitud será procesada en 72 horas.</p>",
-    "LOGOUT_MESSAGE": "Seguro que quiere cerrar la sesión?",
-    "CHANGE_EMAIL": "Cambiar email",
-    "OK": "OK",
-    "SUCCESS": "Completado",
-    "ERROR": "Error",
-    "MESSAGE": "Mensaje",
-    "INSTALL_MOBILE_APP": "Instala nuestra aplicación <a>Android</a> o <b>iOS</b> para hacer una copia de seguridad automática de todas usted fotos",
-    "DOWNLOAD_APP_MESSAGE": "Lo sentimos, esta operación sólo es compatible con nuestra aplicación de computadora",
-    "DOWNLOAD_APP": "Descargar aplicación de computadora",
-    "EXPORT": "Exportar datos",
-    "SUBSCRIPTION": "Suscripción",
-    "SUBSCRIBE": "Suscribir",
-    "MANAGEMENT_PORTAL": "Gestionar métodos de pago",
-    "MANAGE_FAMILY_PORTAL": "Administrar familia",
-    "LEAVE_FAMILY_PLAN": "Dejar plan familiar",
-    "LEAVE": "Dejar",
-    "LEAVE_FAMILY_CONFIRM": "Está seguro de que desea abandonar el plan familiar?",
-    "CHOOSE_PLAN": "Elije tu plan",
-    "MANAGE_PLAN": "Administra tu suscripción",
-    "ACTIVE": "Activo",
-    "OFFLINE_MSG": "Estás desconectado, se están mostrando recuerdos en caché",
-    "FREE_SUBSCRIPTION_INFO": "Estás en el plan <strong>gratis</strong> que expira el {{date, dateTime}}",
-    "FAMILY_SUBSCRIPTION_INFO": "Estás en un plan familiar administrado por",
-    "RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "Se renueva en {{date, dateTime}}",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "Termina el {{date, dateTime}}",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "Tu suscripción será cancelada el {{date, dateTime}}",
-    "ADD_ON_AVAILABLE_TILL": "",
-    "STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "Ha excedido su cuota de almacenamiento, por favor <a>actualice</a>",
-    "SUBSCRIPTION_PURCHASE_SUCCESS": "<p>Hemos recibido tu pago</p><p>¡Tu suscripción es válida hasta <strong>{{date, dateTime}}</strong></p>",
-    "SUBSCRIPTION_PURCHASE_CANCELLED": "Tu compra ha sido cancelada, por favor inténtalo de nuevo si quieres suscribirte",
-    "SUBSCRIPTION_PURCHASE_FAILED": "Compra de suscripción fallida, por favor inténtalo de nuevo",
-    "SUBSCRIPTION_UPDATE_FAILED": "Suscripción actualizada falló, inténtelo de nuevo",
-    "UPDATE_PAYMENT_METHOD_MESSAGE": "Lo sentimos, el pago falló cuando intentamos cargar a su tarjeta, por favor actualice su método de pago y vuelva a intentarlo",
-    "STRIPE_AUTHENTICATION_FAILED": "No podemos autenticar tu método de pago. Por favor, elige un método de pago diferente e inténtalo de nuevo",
-    "UPDATE_PAYMENT_METHOD": "Actualizar medio de pago",
-    "MONTHLY": "Mensual",
-    "YEARLY": "Anual",
-    "UPDATE_SUBSCRIPTION_MESSAGE": "Seguro de que desea cambiar su plan?",
-    "UPDATE_SUBSCRIPTION": "Cambiar de plan",
-    "CANCEL_SUBSCRIPTION": "Cancelar suscripción",
-    "CANCEL_SUBSCRIPTION_MESSAGE": "<p>Todos tus datos serán eliminados de nuestros servidores al final de este periodo de facturación.</p><p>¿Está seguro de que desea cancelar su suscripción?</p>",
-    "CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "",
-    "SUBSCRIPTION_CANCEL_FAILED": "No se pudo cancelar la suscripción",
-    "SUBSCRIPTION_CANCEL_SUCCESS": "Suscripción cancelada correctamente",
-    "REACTIVATE_SUBSCRIPTION": "Reactivar la suscripción",
-    "REACTIVATE_SUBSCRIPTION_MESSAGE": "Una vez reactivado, serás facturado el {{date, dateTime}}",
-    "SUBSCRIPTION_ACTIVATE_SUCCESS": "Suscripción activada correctamente ",
-    "SUBSCRIPTION_ACTIVATE_FAILED": "No se pudo reactivar las renovaciones de suscripción",
-    "SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "Gracias",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE": "Cancelar suscripción a móviles",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "Por favor, cancele su suscripción de la aplicación móvil para activar una suscripción aquí",
-    "MAIL_TO_MANAGE_SUBSCRIPTION": "Por favor, contáctenos en <a>{{emailID}}</a> para gestionar su suscripción",
-    "RENAME": "Renombrar",
-    "RENAME_FILE": "Renombrar archivo",
-    "RENAME_COLLECTION": "Renombrar álbum",
-    "DELETE_COLLECTION_TITLE": "Eliminar álbum?",
-    "DELETE_COLLECTION": "Eliminar álbum",
-    "DELETE_COLLECTION_MESSAGE": "También eliminar las fotos (y los vídeos) presentes en este álbum de <a>todos</a> álbumes de los que forman parte?",
-    "DELETE_PHOTOS": "Eliminar fotos",
-    "KEEP_PHOTOS": "Conservar fotos",
-    "SHARE": "Compartir",
-    "SHARE_COLLECTION": "Compartir álbum",
-    "SHAREES": "Compartido con",
-    "SHARE_WITH_SELF": "Uy, no puedes compartir contigo mismo",
-    "ALREADY_SHARED": "Uy, ya estás compartiendo esto con {{email}}",
-    "SHARING_BAD_REQUEST_ERROR": "Compartir álbum no permitido",
-    "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "Compartir está desactivado para cuentas gratis",
-    "DOWNLOAD_COLLECTION": "Descargar álbum",
-    "DOWNLOAD_COLLECTION_MESSAGE": "<p>¿Está seguro de que desea descargar el álbum completo?</p><p>Todos los archivos se pondrán en cola para su descarga secuencialmente</p>",
-    "CREATE_ALBUM_FAILED": "Error al crear el álbum, inténtalo de nuevo",
-    "SEARCH": "Buscar",
-    "SEARCH_RESULTS": "Buscar resultados",
-    "NO_RESULTS": "No se han encontrado resultados",
-    "SEARCH_HINT": "Buscar álbumes, fechas...",
-    "SEARCH_TYPE": {
-        "COLLECTION": "Álbum",
-        "LOCATION": "Localización",
-        "CITY": "",
-        "DATE": "Fecha",
-        "FILE_NAME": "Nombre del archivo",
-        "THING": "Contenido",
-        "FILE_CAPTION": "Descripción",
-        "FILE_TYPE": "",
-        "CLIP": ""
-    },
-    "photos_count_zero": "No hay recuerdos",
-    "photos_count_one": "1 recuerdo",
-    "photos_count_other": "{{count}} recuerdos",
-    "TERMS_AND_CONDITIONS": "Acepto los <a>términos</a> y <b>política de privacidad</b>",
-    "ADD_TO_COLLECTION": "Añadir al álbum",
-    "SELECTED": "seleccionado",
-    "VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "Este vídeo no se puede reproducir en tu navegador",
-    "PEOPLE": "Personajes",
-    "INDEXING_SCHEDULED": "el indexado está programado...",
-    "ANALYZING_PHOTOS": "analizando nuevas fotos {{indexStatus.nSyncedFiles}} de {{indexStatus.nTotalFiles}} hecho)...",
-    "INDEXING_PEOPLE": "indexando personas en {{indexStatus.nSyncedFiles}} fotos... ",
-    "INDEXING_DONE": "fotos {{indexStatus.nSyncedFiles}} indexadas",
-    "UNIDENTIFIED_FACES": "caras no identificadas",
-    "OBJECTS": "objetos",
-    "TEXT": "texto",
-    "INFO": "Info ",
-    "INFO_OPTION": "Info (I)",
-    "FILE_NAME": "Nombre del archivo",
-    "CAPTION_PLACEHOLDER": "Añadir una descripción",
-    "LOCATION": "Localización",
-    "SHOW_ON_MAP": "Ver en OpenStreetMap",
-    "MAP": "",
-    "MAP_SETTINGS": "",
-    "ENABLE_MAPS": "",
-    "ENABLE_MAP": "",
-    "DISABLE_MAPS": "",
-    "ENABLE_MAP_DESCRIPTION": "",
-    "DISABLE_MAP_DESCRIPTION": "",
-    "DISABLE_MAP": "",
-    "DETAILS": "Detalles",
-    "VIEW_EXIF": "Ver todos los datos de EXIF",
-    "NO_EXIF": "No hay datos EXIF",
-    "EXIF": "EXIF",
-    "ISO": "ISO",
-    "TWO_FACTOR": "Dos factores",
-    "TWO_FACTOR_AUTHENTICATION": "Autenticación de dos factores",
-    "TWO_FACTOR_QR_INSTRUCTION": "Escanea el código QR de abajo con tu aplicación de autenticación favorita",
-    "ENTER_CODE_MANUALLY": "Ingrese el código manualmente",
-    "TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "Por favor, introduce este código en tu aplicación de autenticación favorita",
-    "SCAN_QR_CODE": "Escanear código QR en su lugar",
-    "ENABLE_TWO_FACTOR": "Activar dos factores",
-    "ENABLE": "Activar",
-    "LOST_DEVICE": "Perdido el dispositivo de doble factor",
-    "INCORRECT_CODE": "Código incorrecto",
-    "TWO_FACTOR_INFO": "Añade una capa adicional de seguridad al requerir más de tu email y contraseña para iniciar sesión en tu cuenta",
-    "DISABLE_TWO_FACTOR_LABEL": "Deshabilitar la autenticación de dos factores",
-    "UPDATE_TWO_FACTOR_LABEL": "Actualice su dispositivo de autenticación",
-    "DISABLE": "Desactivar",
-    "RECONFIGURE": "Reconfigurar",
-    "UPDATE_TWO_FACTOR": "Actualizar doble factor",
-    "UPDATE_TWO_FACTOR_MESSAGE": "Continuar adelante anulará los autenticadores previamente configurados",
-    "UPDATE": "Actualizar",
-    "DISABLE_TWO_FACTOR": "Desactivar doble factor",
-    "DISABLE_TWO_FACTOR_MESSAGE": "¿Estás seguro de que desea deshabilitar la autenticación de doble factor?",
-    "TWO_FACTOR_DISABLE_FAILED": "Error al desactivar dos factores, inténtalo de nuevo",
-    "EXPORT_DATA": "Exportar datos",
-    "SELECT_FOLDER": "Seleccionar carpeta",
-    "DESTINATION": "Destinación",
-    "START": "Inicio",
-    "LAST_EXPORT_TIME": "Fecha de la última exportación",
-    "EXPORT_AGAIN": "Resinc",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE": "Almacenamiento local inaccesible",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "Su navegador o un addon está bloqueando a ente de guardar datos en almacenamiento local. Por favor, intente cargar esta página después de cambiar su modo de navegación.",
-    "SEND_OTT": "Enviar OTP",
-    "EMAIl_ALREADY_OWNED": "Email ya tomado",
-    "ETAGS_BLOCKED": "<p>No hemos podido subir los siguientes archivos debido a la configuración de tu navegador.</p><p>Por favor, deshabilite cualquier complemento que pueda estar impidiendo que ente utilice <code>eTags</code> para subir archivos grandes, o utilice nuestra <a>aplicación de escritorio</a> para una experiencia de importación más fiable.</p>",
-    "SKIPPED_VIDEOS_INFO": "<p>Actualmente no podemos añadir vídeos a través de enlaces públicos.</p><p>Para compartir vídeos, por favor <a>regístrate</a> en ente y comparte con los destinatarios a través de su correo electrónico.</p>",
-    "LIVE_PHOTOS_DETECTED": "Los archivos de foto y vídeo de tus fotos en vivo se han fusionado en un solo archivo",
-    "RETRY_FAILED": "Reintentar subidas fallidas",
-    "FAILED_UPLOADS": "Subidas fallidas ",
-    "SKIPPED_FILES": "Subidas ignoradas",
-    "THUMBNAIL_GENERATION_FAILED_UPLOADS": "Generación de miniaturas fallida",
-    "UNSUPPORTED_FILES": "Archivos no soportados",
-    "SUCCESSFUL_UPLOADS": "Subidas exitosas",
-    "SKIPPED_INFO": "Se han omitido ya que hay archivos con nombres coincidentes en el mismo álbum",
-    "UNSUPPORTED_INFO": "ente no soporta estos formatos de archivo aún",
-    "BLOCKED_UPLOADS": "Subidas bloqueadas",
-    "SKIPPED_VIDEOS": "Vídeos saltados",
-    "INPROGRESS_METADATA_EXTRACTION": "En proceso",
-    "INPROGRESS_UPLOADS": "Subidas en progreso",
-    "TOO_LARGE_UPLOADS": "Archivos grandes",
-    "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "Espacio insuficiente",
-    "LARGER_THAN_AVAILABLE_STORAGE_INFO": "Estos archivos no se han subido porque exceden el límite de tamaño máximo para tu plan de almacenamiento",
-    "TOO_LARGE_INFO": "Estos archivos no se han subido porque exceden nuestro límite máximo de tamaño de archivo",
-    "THUMBNAIL_GENERATION_FAILED_INFO": "Estos archivos fueron cargados, pero por desgracia no pudimos generar las miniaturas para ellos.",
-    "UPLOAD_TO_COLLECTION": "Subir al álbum",
-    "UNCATEGORIZED": "No clasificado",
-    "ARCHIVE": "Archivo",
-    "FAVORITES": "Favoritos",
-    "ARCHIVE_COLLECTION": "Archivo álbum",
-    "ARCHIVE_SECTION_NAME": "Archivo",
-    "ALL_SECTION_NAME": "Todo",
-    "MOVE_TO_COLLECTION": "Mover al álbum",
-    "UNARCHIVE": "Desarchivar",
-    "UNARCHIVE_COLLECTION": "Desarchivar álbum",
-    "HIDE_COLLECTION": "",
-    "UNHIDE_COLLECTION": "",
-    "MOVE": "Mover",
-    "ADD": "Añadir",
-    "REMOVE": "Eliminar",
-    "YES_REMOVE": "Sí, eliminar",
-    "REMOVE_FROM_COLLECTION": "Eliminar del álbum",
-    "TRASH": "Papelera",
-    "MOVE_TO_TRASH": "Mover a la papelera",
-    "TRASH_FILES_MESSAGE": "Los archivos seleccionados serán eliminados de todos los álbumes y movidos a la papelera.",
-    "TRASH_FILE_MESSAGE": "El archivo será eliminado de todos los álbumes y movido a la papelera.",
-    "DELETE_PERMANENTLY": "Eliminar para siempre",
-    "RESTORE": "Restaurar",
-    "RESTORE_TO_COLLECTION": "Restaurar al álbum",
-    "EMPTY_TRASH": "Vaciar papelera",
-    "EMPTY_TRASH_TITLE": "Vaciar papelera?",
-    "EMPTY_TRASH_MESSAGE": "Estos archivos serán eliminados permanentemente de su cuenta ente.",
-    "LEAVE_SHARED_ALBUM": "Sí, dejar",
-    "LEAVE_ALBUM": "Dejar álbum",
-    "LEAVE_SHARED_ALBUM_TITLE": "¿Dejar álbum compartido?",
-    "LEAVE_SHARED_ALBUM_MESSAGE": "Dejará el álbum, y dejará de ser visible para usted.",
-    "NOT_FILE_OWNER": "No puedes eliminar archivos de un álbum compartido",
-    "CONFIRM_SELF_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_SELF_AND_OTHER_REMOVE_MESSAGE": "Algunos de los elementos que estás eliminando fueron añadidos por otras personas, y perderás el acceso a ellos.",
-    "SORT_BY_CREATION_TIME_ASCENDING": "Antiguo",
-    "SORT_BY_UPDATION_TIME_DESCENDING": "Última actualización",
-    "SORT_BY_NAME": "Nombre",
-    "COMPRESS_THUMBNAILS": "Comprimir las miniaturas",
-    "THUMBNAIL_REPLACED": "Miniaturas comprimidas",
-    "FIX_THUMBNAIL": "Comprimir",
-    "FIX_THUMBNAIL_LATER": "Comprimir más tarde",
-    "REPLACE_THUMBNAIL_NOT_STARTED": "Algunas de tus miniaturas de vídeos pueden ser comprimidas para ahorrar espacio. ¿Te gustaría que ente las comprima?",
-    "REPLACE_THUMBNAIL_COMPLETED": "Todas las miniaturas se comprimieron con éxito",
-    "REPLACE_THUMBNAIL_NOOP": "No tienes miniaturas que se puedan comprimir más",
-    "REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR": "No se pudieron comprimir algunas de tus miniaturas, por favor inténtalo de nuevo",
-    "FIX_CREATION_TIME": "Fijar hora",
-    "FIX_CREATION_TIME_IN_PROGRESS": "Fijar hora",
-    "CREATION_TIME_UPDATED": "Hora del archivo actualizada",
-    "UPDATE_CREATION_TIME_NOT_STARTED": "Seleccione la cartera que desea utilizar",
-    "UPDATE_CREATION_TIME_COMPLETED": "Todos los archivos se han actualizado correctamente",
-    "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "Fallo en la hora del archivo para algunos archivos, por favor inténtelo de nuevo",
-    "CAPTION_CHARACTER_LIMIT": "Máximo 5000 caracteres",
-    "DATE_TIME_ORIGINAL": "EXIF: Fecha original",
-    "DATE_TIME_DIGITIZED": "EXIF: Fecha Digitalizado",
-    "METADATA_DATE": "",
-    "CUSTOM_TIME": "Hora personalizada",
-    "REOPEN_PLAN_SELECTOR_MODAL": "Reabrir planes",
-    "OPEN_PLAN_SELECTOR_MODAL_FAILED": "Error al abrir los planes",
-    "INSTALL": "Instalar",
-    "SHARING_DETAILS": "Compartir detalles",
-    "MODIFY_SHARING": "Modificar compartir",
-    "ADD_COLLABORATORS": "",
-    "ADD_NEW_EMAIL": "",
-    "shared_with_people_zero": "",
-    "shared_with_people_one": "",
-    "shared_with_people_other": "",
-    "participants_zero": "",
-    "participants_one": "",
-    "participants_other": "",
-    "ADD_VIEWERS": "",
-    "PARTICIPANTS": "",
-    "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": "Propietario",
-    "COLLABORATORS": "Colaboradores",
-    "ADD_MORE": "Añadir más",
-    "VIEWERS": "",
-    "OR_ADD_EXISTING": "O elige uno existente",
-    "REMOVE_PARTICIPANT_MESSAGE": "",
-    "NOT_FOUND": "404 - No Encontrado",
-    "LINK_EXPIRED": "Enlace expirado",
-    "LINK_EXPIRED_MESSAGE": "Este enlace ha caducado o ha sido desactivado!",
-    "MANAGE_LINK": "Administrar enlace",
-    "LINK_TOO_MANY_REQUESTS": "Este álbum es demasiado popular para que podamos manejarlo!",
-    "FILE_DOWNLOAD": "Permitir descargas",
-    "LINK_PASSWORD_LOCK": "Contraseña bloqueada",
-    "PUBLIC_COLLECT": "Permitir añadir fotos",
-    "LINK_DEVICE_LIMIT": "Límites del dispositivo",
-    "NO_DEVICE_LIMIT": "Ninguno",
-    "LINK_EXPIRY": "Enlace vencio",
-    "NEVER": "Nunca",
-    "DISABLE_FILE_DOWNLOAD": "Deshabilitar descarga",
-    "DISABLE_FILE_DOWNLOAD_MESSAGE": "<p>¿Está seguro que desea desactivar el botón de descarga de archivos?</p><p>Los visualizadores todavía pueden tomar capturas de pantalla o guardar una copia de sus fotos usando herramientas externas.</p>",
-    "MALICIOUS_CONTENT": "Contiene contenido malicioso",
-    "COPYRIGHT": "Infracciones sobre los derechos de autor de alguien que estoy autorizado a representar",
-    "SHARED_USING": "Compartido usando ",
-    "ENTE_IO": "ente.io",
-    "SHARING_REFERRAL_CODE": "Usa el código <strong>{{referralCode}}</strong> para obtener 10 GB gratis",
-    "LIVE": "VIVO",
-    "DISABLE_PASSWORD": "Desactivar contraseña",
-    "DISABLE_PASSWORD_MESSAGE": "Seguro que quieres cambiar la contrasena?",
-    "PASSWORD_LOCK": "Contraseña bloqueada",
-    "LOCK": "Bloquear",
-    "DOWNLOAD_UPLOAD_LOGS": "Logs de depuración",
-    "UPLOAD_FILES": "Archivo",
-    "UPLOAD_DIRS": "Carpeta",
-    "UPLOAD_GOOGLE_TAKEOUT": "Google Takeout",
-    "DEDUPLICATE_FILES": "Deduplicar archivos",
-    "AUTHENTICATOR_SECTION": "Autenticación",
-    "NO_DUPLICATES_FOUND": "No tienes archivos duplicados que puedan ser borrados",
-    "CLUB_BY_CAPTURE_TIME": "Club por tiempo de captura",
-    "FILES": "Archivos",
-    "EACH": "Cada",
-    "DEDUPLICATE_BASED_ON_SIZE": "Los siguientes archivos fueron organizados en base a sus tamaños, por favor revise y elimine elementos que cree que son duplicados",
-    "STOP_ALL_UPLOADS_MESSAGE": "¿Está seguro que desea detener todas las subidas en curso?",
-    "STOP_UPLOADS_HEADER": "Detener las subidas?",
-    "YES_STOP_UPLOADS": "Sí, detener las subidas",
-    "STOP_DOWNLOADS_HEADER": "¿Detener las descargas?",
-    "YES_STOP_DOWNLOADS": "Sí, detener las descargas",
-    "STOP_ALL_DOWNLOADS_MESSAGE": "¿Estás seguro de que quieres detener todas las descargas en curso?",
-    "albums_one": "1 álbum",
-    "albums_other": "{{count}} álbumes",
-    "ALL_ALBUMS": "Todos los álbumes",
-    "ALBUMS": "Álbumes",
-    "ALL_HIDDEN_ALBUMS": "",
-    "HIDDEN_ALBUMS": "",
-    "HIDDEN_ITEMS": "",
-    "HIDDEN_ITEMS_SECTION_NAME": "",
-    "ENTER_TWO_FACTOR_OTP": "Ingrese el código de seis dígitos de su aplicación de autenticación a continuación.",
-    "CREATE_ACCOUNT": "Crear cuenta",
-    "COPIED": "Copiado",
-    "CANVAS_BLOCKED_TITLE": "No se puede generar la miniatura",
-    "CANVAS_BLOCKED_MESSAGE": "<p>Parece que su navegador ha deshabilitado el acceso al lienzo, que es necesario para generar miniaturas para tus fotos </p> <p> Por favor, activa el acceso al lienzo de tu navegador, o revisa nuestra aplicación de escritorio</p>",
-    "WATCH_FOLDERS": "Ver carpetas",
-    "UPGRADE_NOW": "Mejorar ahora",
-    "RENEW_NOW": "Renovar ahora",
-    "STORAGE": "Almacén",
-    "USED": "usado",
-    "YOU": "Usted",
-    "FAMILY": "Familia",
-    "FREE": "gratis",
-    "OF": "de",
-    "WATCHED_FOLDERS": "Ver carpetas",
-    "NO_FOLDERS_ADDED": "No hay carpetas añadidas!",
-    "FOLDERS_AUTOMATICALLY_MONITORED": "Las carpetas que añadas aquí serán supervisadas automáticamente",
-    "UPLOAD_NEW_FILES_TO_ENTE": "Subir nuevos archivos a ente",
-    "REMOVE_DELETED_FILES_FROM_ENTE": "Eliminar archivos borrados de ente",
-    "ADD_FOLDER": "Añadir carpeta",
-    "STOP_WATCHING": "Dejar de ver",
-    "STOP_WATCHING_FOLDER": "Dejar de ver carpeta?",
-    "STOP_WATCHING_DIALOG_MESSAGE": "Tus archivos existentes no serán eliminados, pero ente dejará de actualizar automáticamente el álbum enlazado en caso de cambios en esta carpeta.",
-    "YES_STOP": "Sí, detener",
-    "MONTH_SHORT": "mes",
-    "YEAR": "año",
-    "FAMILY_PLAN": "Plan familiar",
-    "DOWNLOAD_LOGS": "Descargar logs",
-    "DOWNLOAD_LOGS_MESSAGE": "<p>Esto descargará los registros de depuración, que puede enviarnos por correo electrónico para ayudarnos a depurar su problema.</p><p> Tenga en cuenta que los nombres de los archivos se incluirán para ayudar al seguimiento de problemas con archivos específicos. </p>",
-    "CHANGE_FOLDER": "Cambiar carpeta",
-    "TWO_MONTHS_FREE": "Obtén 2 meses gratis en planes anuales",
-    "GB": "GB",
-    "POPULAR": "Popular",
-    "FREE_PLAN_OPTION_LABEL": "Continuar con el plan gratuito",
-    "FREE_PLAN_DESCRIPTION": "1 GB por 1 año",
-    "CURRENT_USAGE": "El uso actual es <strong>{{usage}}</strong>",
-    "WEAK_DEVICE": "El navegador web que está utilizando no es lo suficientemente poderoso para cifrar sus fotos. Por favor, intente iniciar sesión en ente en su computadora, o descargue la aplicación ente para móvil/escritorio.",
-    "DRAG_AND_DROP_HINT": "O arrastre y suelte en la ventana ente",
-    "CONFIRM_ACCOUNT_DELETION_MESSAGE": "Los datos subidos se eliminarán y su cuenta se eliminará de forma permanente.<br/><br/>Esta acción no es reversible.",
-    "AUTHENTICATE": "Autenticado",
-    "UPLOADED_TO_SINGLE_COLLECTION": "Subir a una sola colección",
-    "UPLOADED_TO_SEPARATE_COLLECTIONS": "Subir a colecciones separadas",
-    "NEVERMIND": "No importa",
-    "UPDATE_AVAILABLE": "Actualizacion disponible",
-    "UPDATE_INSTALLABLE_MESSAGE": "Una nueva versión de ente está lista para ser instalada.",
-    "INSTALL_NOW": "Instalar ahora",
-    "INSTALL_ON_NEXT_LAUNCH": "Instalar en el próximo lanzamiento",
-    "UPDATE_AVAILABLE_MESSAGE": "Una nueva versión de ente ha sido lanzada, pero no se puede descargar e instalar automáticamente.",
-    "DOWNLOAD_AND_INSTALL": "Descargar e instalar",
-    "IGNORE_THIS_VERSION": "Ignorar esta versión",
-    "TODAY": "Hoy",
-    "YESTERDAY": "Ayer",
-    "NAME_PLACEHOLDER": "Nombre...",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "No se puede crear álbumes de mezcla de archivos/carpetas",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "<p>Has arrastrado y soltado una mezcla de archivos y carpetas.</p><p>Por favor proporcione sólo archivos o carpetas cuando seleccione la opción de crear álbumes separados</p>",
-    "CHOSE_THEME": "Elegir tema",
-    "ML_SEARCH": "Buscar ML (beta)",
-    "ENABLE_ML_SEARCH_DESCRIPTION": "<p>Esto permitirá el aprendizaje automático en el dispositivo y la búsqueda facial que comenzará a analizar las fotos subidas localmente.</p><p>Para la primera ejecución después de iniciar sesión o habilitar esta función, se descargarán todas las imágenes en el dispositivo local para analizarlas. Así que por favor actívalo sólo si dispones ancho de banda y el almacenamiento suficiente para el procesamiento local de todas las imágenes en tu biblioteca de fotos.</p><p>Si esta es la primera vez que está habilitando, también le pediremos su permiso para procesar los datos faciales.</p>",
-    "ML_MORE_DETAILS": "Más detalles",
-    "ENABLE_FACE_SEARCH": "Activar búsqueda facial",
-    "ENABLE_FACE_SEARCH_TITLE": "Activar búsqueda facial?",
-    "ENABLE_FACE_SEARCH_DESCRIPTION": "<p>Si activas la búsqueda facial, ente extraerá la geometría facial de tus fotos. Esto sucederá en su dispositivo y cualquier dato biométrico generado será cifrado de extremo a extremo.<p/><p><a>Haga clic aquí para obtener más detalles sobre esta característica en nuestra política de privacidad</a></p>",
-    "DISABLE_BETA": "Desactivar beta",
-    "DISABLE_FACE_SEARCH": "Desactivar búsqueda facial",
-    "DISABLE_FACE_SEARCH_TITLE": "Desactivar búsqueda facial?",
-    "DISABLE_FACE_SEARCH_DESCRIPTION": "<p>ente dejará de procesar la geometría facial, y también desactivará la búsqueda ML (beta)</p><p>Puede volver a activar la búsqueda facial si lo desea, ya que esta operación es segura.</p>",
-    "ADVANCED": "Avanzado",
-    "FACE_SEARCH_CONFIRMATION": "Comprendo y deseo permitir que ente procese la geometría de la cara",
-    "LABS": "Labs",
-    "YOURS": "tuyo",
-    "PASSPHRASE_STRENGTH_WEAK": "Fortaleza de la contraseña: débil",
-    "PASSPHRASE_STRENGTH_MODERATE": "Fortaleza de contraseña: Moderar",
-    "PASSPHRASE_STRENGTH_STRONG": "Fortaleza de contraseña: fuerte",
-    "PREFERENCES": "Preferencias",
-    "LANGUAGE": "Idioma",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST": "Archivo de exportación inválido",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "<p>El directorio de exportación seleccionado no existe.</p><p> Por favor, seleccione un directorio válido.</p>",
-    "SUBSCRIPTION_VERIFICATION_ERROR": "Falló la verificación de la suscripción",
-    "STORAGE_UNITS": {
-        "B": "B",
-        "KB": "KB",
-        "MB": "MB",
-        "GB": "GB",
-        "TB": "TB"
-    },
-    "AFTER_TIME": {
-        "HOUR": "después de una hora",
-        "DAY": "después de un día",
-        "WEEK": "después de una semana",
-        "MONTH": "después de un mes",
-        "YEAR": "después de un año"
-    },
-    "COPY_LINK": "Copiar enlace",
-    "DONE": "Hecho",
-    "LINK_SHARE_TITLE": "O comparte un enlace",
-    "REMOVE_LINK": "Eliminar enlace",
-    "CREATE_PUBLIC_SHARING": "Crear un enlace público",
-    "PUBLIC_LINK_CREATED": "Enlace público creado",
-    "PUBLIC_LINK_ENABLED": "Enlace público activado",
-    "COLLECT_PHOTOS": "Obtener fotos",
-    "PUBLIC_COLLECT_SUBTEXT": "Permitir a las personas con el enlace añadir fotos al álbum compartido.",
-    "STOP_EXPORT": "Stop",
-    "EXPORT_PROGRESS": "<a>{{progress.success}} / {{progress.total}}</a> archivos exportados",
-    "MIGRATING_EXPORT": "",
-    "RENAMING_COLLECTION_FOLDERS": "",
-    "TRASHING_DELETED_FILES": "",
-    "TRASHING_DELETED_COLLECTIONS": "",
-    "EXPORT_NOTIFICATION": {
-        "START": "Exportar iniciando",
-        "IN_PROGRESS": "Exportación ya en curso",
-        "FINISH": "Exportación finalizada",
-        "UP_TO_DATE": "No hay nuevos archivos para exportar"
-    },
-    "CONTINUOUS_EXPORT": "Sincronizar continuamente",
-    "TOTAL_ITEMS": "Total de elementos",
-    "PENDING_ITEMS": "Elementos pendientes",
-    "EXPORT_STARTING": "Exportar iniciando...",
-    "DELETE_ACCOUNT_REASON_LABEL": "¿Cuál es la razón principal por la que eliminas tu cuenta?",
-    "DELETE_ACCOUNT_REASON_PLACEHOLDER": "Selecciona una razón",
-    "DELETE_REASON": {
-        "MISSING_FEATURE": "Falta una característica clave que necesito",
-        "BROKEN_BEHAVIOR": "La aplicación o una característica determinada no se comporta como creo que debería",
-        "FOUND_ANOTHER_SERVICE": "He encontrado otro servicio que me gusta más",
-        "NOT_LISTED": "Mi motivo no se encuentra en la lista"
-    },
-    "DELETE_ACCOUNT_FEEDBACK_LABEL": "Lamentamos que te vayas. Explica por qué te vas para ayudarnos a mejorar.",
-    "DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "Sugerencias",
-    "CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "Sí, quiero eliminar permanentemente esta cuenta y todos sus datos",
-    "CONFIRM_DELETE_ACCOUNT": "Corfirmar borrado de cuenta",
-    "FEEDBACK_REQUIRED": "Ayúdanos con esta información",
-    "FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "Qué hace mejor el otro servicio?",
-    "RECOVER_TWO_FACTOR": "Recuperar dos factores",
-    "at": "a las",
-    "AUTH_NEXT": "siguiente",
-    "AUTH_DOWNLOAD_MOBILE_APP": "Descarga nuestra aplicación móvil para administrar tus secretos",
-    "HIDDEN": "",
-    "HIDE": "Ocultar",
-    "UNHIDE": "Mostrar",
-    "UNHIDE_TO_COLLECTION": "Hacer visible al álbum",
-    "SORT_BY": "",
-    "NEWEST_FIRST": "",
-    "OLDEST_FIRST": "",
-    "CONVERSION_FAILED_NOTIFICATION_MESSAGE": "",
-    "SELECT_COLLECTION": "",
-    "PIN_ALBUM": "",
-    "UNPIN_ALBUM": "",
-    "DOWNLOAD_COMPLETE": "",
-    "DOWNLOADING_COLLECTION": "",
-    "DOWNLOAD_FAILED": "",
-    "DOWNLOAD_PROGRESS": "",
-    "CHRISTMAS": "",
-    "CHRISTMAS_EVE": "",
-    "NEW_YEAR": "",
-    "NEW_YEAR_EVE": "",
-    "IMAGE": "",
-    "VIDEO": "Video",
-    "LIVE_PHOTO": "",
-    "CONVERT": "",
-    "CONFIRM_EDITOR_CLOSE_MESSAGE": "",
-    "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "",
-    "BRIGHTNESS": "",
-    "CONTRAST": "",
-    "SATURATION": "",
-    "BLUR": "",
-    "INVERT_COLORS": "",
-    "ASPECT_RATIO": "",
-    "SQUARE": "",
-    "ROTATE_LEFT": "",
-    "ROTATE_RIGHT": "",
-    "FLIP_VERTICALLY": "",
-    "FLIP_HORIZONTALLY": "",
-    "DOWNLOAD_EDITED": "",
-    "SAVE_A_COPY_TO_ENTE": "",
-    "RESTORE_ORIGINAL": "",
-    "TRANSFORM": "Transformar",
-    "COLORS": "Colores",
-    "FLIP": "",
-    "ROTATION": "",
-    "RESET": "",
-    "PHOTO_EDITOR": "",
-    "FASTER_UPLOAD": "",
-    "FASTER_UPLOAD_DESCRIPTION": "",
-    "MAGIC_SEARCH_STATUS": "",
-    "INDEXED_ITEMS": "",
-    "CAST_ALBUM_TO_TV": "",
-    "ENTER_CAST_PIN_CODE": "",
-    "PAIR_DEVICE_TO_TV": "",
-    "TV_NOT_FOUND": "",
-    "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
-    "PAIR_WITH_PIN": "",
-    "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
-    "VISIT_CAST_ENTE_IO": "",
-    "CAST_AUTO_PAIR_FAILED": "",
-    "CACHE_DIRECTORY": "",
-    "FREEHAND": "",
-    "APPLY_CROP": "",
-    "PHOTO_EDIT_REQUIRED_TO_SAVE": "",
-    "PASSKEYS": "",
-    "DELETE_PASSKEY": "",
-    "DELETE_PASSKEY_CONFIRMATION": "",
-    "RENAME_PASSKEY": "",
-    "ADD_PASSKEY": "",
-    "ENTER_PASSKEY_NAME": "",
-    "PASSKEYS_DESCRIPTION": "",
-    "CREATED_AT": "",
-    "PASSKEY_LOGIN_FAILED": "",
-    "PASSKEY_LOGIN_URL_INVALID": "",
-    "PASSKEY_LOGIN_ERRORED": "",
-    "TRY_AGAIN": "",
-    "PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER": "",
-    "LOGIN_WITH_PASSKEY": ""
-}

+ 0 - 654
web/apps/accounts/public/locales/fa-IR/translation.json

@@ -1,654 +0,0 @@
-{
-    "HERO_SLIDE_1_TITLE": "",
-    "HERO_SLIDE_1": "",
-    "HERO_SLIDE_2_TITLE": "",
-    "HERO_SLIDE_2": "",
-    "HERO_SLIDE_3_TITLE": "",
-    "HERO_SLIDE_3": "",
-    "LOGIN": "",
-    "SIGN_UP": "",
-    "NEW_USER": "",
-    "EXISTING_USER": "",
-    "ENTER_NAME": "",
-    "PUBLIC_UPLOADER_NAME_MESSAGE": "",
-    "ENTER_EMAIL": "",
-    "EMAIL_ERROR": "",
-    "REQUIRED": "",
-    "EMAIL_SENT": "",
-    "CHECK_INBOX": "",
-    "ENTER_OTT": "",
-    "RESEND_MAIL": "",
-    "VERIFY": "",
-    "UNKNOWN_ERROR": "",
-    "INVALID_CODE": "",
-    "EXPIRED_CODE": "",
-    "SENDING": "",
-    "SENT": "",
-    "PASSWORD": "",
-    "LINK_PASSWORD": "",
-    "RETURN_PASSPHRASE_HINT": "",
-    "SET_PASSPHRASE": "",
-    "VERIFY_PASSPHRASE": "",
-    "INCORRECT_PASSPHRASE": "",
-    "ENTER_ENC_PASSPHRASE": "",
-    "PASSPHRASE_DISCLAIMER": "",
-    "WELCOME_TO_ENTE_HEADING": "به <a/> خوش آمدید",
-    "WELCOME_TO_ENTE_SUBHEADING": "",
-    "WHERE_YOUR_BEST_PHOTOS_LIVE": "",
-    "KEY_GENERATION_IN_PROGRESS_MESSAGE": "",
-    "PASSPHRASE_HINT": "",
-    "CONFIRM_PASSPHRASE": "",
-    "REFERRAL_CODE_HINT": "",
-    "REFERRAL_INFO": "",
-    "PASSPHRASE_MATCH_ERROR": "",
-    "CREATE_COLLECTION": "",
-    "ENTER_ALBUM_NAME": "",
-    "CLOSE_OPTION": "",
-    "ENTER_FILE_NAME": "",
-    "CLOSE": "",
-    "NO": "",
-    "NOTHING_HERE": "",
-    "UPLOAD": "",
-    "IMPORT": "",
-    "ADD_PHOTOS": "",
-    "ADD_MORE_PHOTOS": "",
-    "add_photos_one": "",
-    "add_photos_other": "",
-    "SELECT_PHOTOS": "",
-    "FILE_UPLOAD": "",
-    "UPLOAD_STAGE_MESSAGE": {
-        "0": "",
-        "1": "",
-        "2": "",
-        "3": "",
-        "4": "",
-        "5": ""
-    },
-    "FILE_NOT_UPLOADED_LIST": "",
-    "SUBSCRIPTION_EXPIRED": "",
-    "SUBSCRIPTION_EXPIRED_MESSAGE": "",
-    "STORAGE_QUOTA_EXCEEDED": "",
-    "INITIAL_LOAD_DELAY_WARNING": "",
-    "USER_DOES_NOT_EXIST": "",
-    "NO_ACCOUNT": "",
-    "ACCOUNT_EXISTS": "",
-    "CREATE": "",
-    "DOWNLOAD": "",
-    "DOWNLOAD_OPTION": "",
-    "DOWNLOAD_FAVORITES": "",
-    "DOWNLOAD_UNCATEGORIZED": "",
-    "DOWNLOAD_HIDDEN_ITEMS": "",
-    "COPY_OPTION": "",
-    "TOGGLE_FULLSCREEN": "",
-    "ZOOM_IN_OUT": "",
-    "PREVIOUS": "",
-    "NEXT": "",
-    "TITLE_PHOTOS": "",
-    "TITLE_ALBUMS": "",
-    "TITLE_AUTH": "",
-    "UPLOAD_FIRST_PHOTO": "",
-    "IMPORT_YOUR_FOLDERS": "",
-    "UPLOAD_DROPZONE_MESSAGE": "",
-    "WATCH_FOLDER_DROPZONE_MESSAGE": "",
-    "TRASH_FILES_TITLE": "",
-    "TRASH_FILE_TITLE": "",
-    "DELETE_FILES_TITLE": "",
-    "DELETE_FILES_MESSAGE": "",
-    "DELETE": "",
-    "DELETE_OPTION": "",
-    "FAVORITE_OPTION": "",
-    "UNFAVORITE_OPTION": "",
-    "MULTI_FOLDER_UPLOAD": "",
-    "UPLOAD_STRATEGY_CHOICE": "",
-    "UPLOAD_STRATEGY_SINGLE_COLLECTION": "",
-    "OR": "",
-    "UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "",
-    "SESSION_EXPIRED_MESSAGE": "",
-    "SESSION_EXPIRED": "",
-    "PASSWORD_GENERATION_FAILED": "",
-    "CHANGE_PASSWORD": "",
-    "GO_BACK": "",
-    "RECOVERY_KEY": "",
-    "SAVE_LATER": "",
-    "SAVE": "",
-    "RECOVERY_KEY_DESCRIPTION": "",
-    "RECOVER_KEY_GENERATION_FAILED": "",
-    "KEY_NOT_STORED_DISCLAIMER": "",
-    "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": "",
-    "CONFIRM": "",
-    "CANCEL": "",
-    "LOGOUT": "",
-    "DELETE_ACCOUNT": "",
-    "DELETE_ACCOUNT_MESSAGE": "",
-    "LOGOUT_MESSAGE": "",
-    "CHANGE_EMAIL": "",
-    "OK": "",
-    "SUCCESS": "",
-    "ERROR": "",
-    "MESSAGE": "",
-    "INSTALL_MOBILE_APP": "",
-    "DOWNLOAD_APP_MESSAGE": "",
-    "DOWNLOAD_APP": "",
-    "EXPORT": "",
-    "SUBSCRIPTION": "",
-    "SUBSCRIBE": "",
-    "MANAGEMENT_PORTAL": "",
-    "MANAGE_FAMILY_PORTAL": "",
-    "LEAVE_FAMILY_PLAN": "",
-    "LEAVE": "",
-    "LEAVE_FAMILY_CONFIRM": "",
-    "CHOOSE_PLAN": "",
-    "MANAGE_PLAN": "",
-    "ACTIVE": "",
-    "OFFLINE_MSG": "",
-    "FREE_SUBSCRIPTION_INFO": "",
-    "FAMILY_SUBSCRIPTION_INFO": "",
-    "RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "",
-    "ADD_ON_AVAILABLE_TILL": "",
-    "STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "",
-    "SUBSCRIPTION_PURCHASE_SUCCESS": "",
-    "SUBSCRIPTION_PURCHASE_CANCELLED": "",
-    "SUBSCRIPTION_PURCHASE_FAILED": "",
-    "SUBSCRIPTION_UPDATE_FAILED": "",
-    "UPDATE_PAYMENT_METHOD_MESSAGE": "",
-    "STRIPE_AUTHENTICATION_FAILED": "",
-    "UPDATE_PAYMENT_METHOD": "",
-    "MONTHLY": "",
-    "YEARLY": "",
-    "UPDATE_SUBSCRIPTION_MESSAGE": "",
-    "UPDATE_SUBSCRIPTION": "",
-    "CANCEL_SUBSCRIPTION": "",
-    "CANCEL_SUBSCRIPTION_MESSAGE": "",
-    "CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "",
-    "SUBSCRIPTION_CANCEL_FAILED": "",
-    "SUBSCRIPTION_CANCEL_SUCCESS": "",
-    "REACTIVATE_SUBSCRIPTION": "",
-    "REACTIVATE_SUBSCRIPTION_MESSAGE": "",
-    "SUBSCRIPTION_ACTIVATE_SUCCESS": "",
-    "SUBSCRIPTION_ACTIVATE_FAILED": "",
-    "SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE": "",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "",
-    "MAIL_TO_MANAGE_SUBSCRIPTION": "",
-    "RENAME": "",
-    "RENAME_FILE": "",
-    "RENAME_COLLECTION": "",
-    "DELETE_COLLECTION_TITLE": "",
-    "DELETE_COLLECTION": "",
-    "DELETE_COLLECTION_MESSAGE": "",
-    "DELETE_PHOTOS": "",
-    "KEEP_PHOTOS": "",
-    "SHARE": "",
-    "SHARE_COLLECTION": "",
-    "SHAREES": "",
-    "SHARE_WITH_SELF": "",
-    "ALREADY_SHARED": "",
-    "SHARING_BAD_REQUEST_ERROR": "",
-    "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "",
-    "DOWNLOAD_COLLECTION": "",
-    "DOWNLOAD_COLLECTION_MESSAGE": "",
-    "CREATE_ALBUM_FAILED": "",
-    "SEARCH": "",
-    "SEARCH_RESULTS": "",
-    "NO_RESULTS": "",
-    "SEARCH_HINT": "",
-    "SEARCH_TYPE": {
-        "COLLECTION": "",
-        "LOCATION": "",
-        "CITY": "",
-        "DATE": "",
-        "FILE_NAME": "",
-        "THING": "",
-        "FILE_CAPTION": "",
-        "FILE_TYPE": "",
-        "CLIP": ""
-    },
-    "photos_count_zero": "",
-    "photos_count_one": "",
-    "photos_count_other": "",
-    "TERMS_AND_CONDITIONS": "",
-    "ADD_TO_COLLECTION": "",
-    "SELECTED": "",
-    "VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "",
-    "PEOPLE": "",
-    "INDEXING_SCHEDULED": "",
-    "ANALYZING_PHOTOS": "",
-    "INDEXING_PEOPLE": "",
-    "INDEXING_DONE": "",
-    "UNIDENTIFIED_FACES": "",
-    "OBJECTS": "",
-    "TEXT": "",
-    "INFO": "",
-    "INFO_OPTION": "",
-    "FILE_NAME": "",
-    "CAPTION_PLACEHOLDER": "",
-    "LOCATION": "",
-    "SHOW_ON_MAP": "",
-    "MAP": "",
-    "MAP_SETTINGS": "",
-    "ENABLE_MAPS": "",
-    "ENABLE_MAP": "",
-    "DISABLE_MAPS": "",
-    "ENABLE_MAP_DESCRIPTION": "",
-    "DISABLE_MAP_DESCRIPTION": "",
-    "DISABLE_MAP": "",
-    "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": "",
-    "LOST_DEVICE": "",
-    "INCORRECT_CODE": "",
-    "TWO_FACTOR_INFO": "",
-    "DISABLE_TWO_FACTOR_LABEL": "",
-    "UPDATE_TWO_FACTOR_LABEL": "",
-    "DISABLE": "",
-    "RECONFIGURE": "",
-    "UPDATE_TWO_FACTOR": "",
-    "UPDATE_TWO_FACTOR_MESSAGE": "",
-    "UPDATE": "",
-    "DISABLE_TWO_FACTOR": "",
-    "DISABLE_TWO_FACTOR_MESSAGE": "",
-    "TWO_FACTOR_DISABLE_FAILED": "",
-    "EXPORT_DATA": "",
-    "SELECT_FOLDER": "",
-    "DESTINATION": "",
-    "START": "",
-    "LAST_EXPORT_TIME": "",
-    "EXPORT_AGAIN": "",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE": "",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "",
-    "SEND_OTT": "",
-    "EMAIl_ALREADY_OWNED": "",
-    "ETAGS_BLOCKED": "",
-    "SKIPPED_VIDEOS_INFO": "",
-    "LIVE_PHOTOS_DETECTED": "",
-    "RETRY_FAILED": "",
-    "FAILED_UPLOADS": "",
-    "SKIPPED_FILES": "",
-    "THUMBNAIL_GENERATION_FAILED_UPLOADS": "",
-    "UNSUPPORTED_FILES": "",
-    "SUCCESSFUL_UPLOADS": "",
-    "SKIPPED_INFO": "",
-    "UNSUPPORTED_INFO": "",
-    "BLOCKED_UPLOADS": "",
-    "SKIPPED_VIDEOS": "",
-    "INPROGRESS_METADATA_EXTRACTION": "",
-    "INPROGRESS_UPLOADS": "",
-    "TOO_LARGE_UPLOADS": "",
-    "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "",
-    "LARGER_THAN_AVAILABLE_STORAGE_INFO": "",
-    "TOO_LARGE_INFO": "",
-    "THUMBNAIL_GENERATION_FAILED_INFO": "",
-    "UPLOAD_TO_COLLECTION": "",
-    "UNCATEGORIZED": "",
-    "ARCHIVE": "",
-    "FAVORITES": "",
-    "ARCHIVE_COLLECTION": "",
-    "ARCHIVE_SECTION_NAME": "",
-    "ALL_SECTION_NAME": "",
-    "MOVE_TO_COLLECTION": "",
-    "UNARCHIVE": "",
-    "UNARCHIVE_COLLECTION": "",
-    "HIDE_COLLECTION": "",
-    "UNHIDE_COLLECTION": "",
-    "MOVE": "",
-    "ADD": "",
-    "REMOVE": "",
-    "YES_REMOVE": "",
-    "REMOVE_FROM_COLLECTION": "",
-    "TRASH": "",
-    "MOVE_TO_TRASH": "",
-    "TRASH_FILES_MESSAGE": "",
-    "TRASH_FILE_MESSAGE": "",
-    "DELETE_PERMANENTLY": "",
-    "RESTORE": "",
-    "RESTORE_TO_COLLECTION": "",
-    "EMPTY_TRASH": "",
-    "EMPTY_TRASH_TITLE": "",
-    "EMPTY_TRASH_MESSAGE": "",
-    "LEAVE_SHARED_ALBUM": "",
-    "LEAVE_ALBUM": "",
-    "LEAVE_SHARED_ALBUM_TITLE": "",
-    "LEAVE_SHARED_ALBUM_MESSAGE": "",
-    "NOT_FILE_OWNER": "",
-    "CONFIRM_SELF_REMOVE_MESSAGE": "",
-    "CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "",
-    "SORT_BY_CREATION_TIME_ASCENDING": "",
-    "SORT_BY_UPDATION_TIME_DESCENDING": "",
-    "SORT_BY_NAME": "",
-    "COMPRESS_THUMBNAILS": "",
-    "THUMBNAIL_REPLACED": "",
-    "FIX_THUMBNAIL": "",
-    "FIX_THUMBNAIL_LATER": "",
-    "REPLACE_THUMBNAIL_NOT_STARTED": "",
-    "REPLACE_THUMBNAIL_COMPLETED": "",
-    "REPLACE_THUMBNAIL_NOOP": "",
-    "REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR": "",
-    "FIX_CREATION_TIME": "",
-    "FIX_CREATION_TIME_IN_PROGRESS": "",
-    "CREATION_TIME_UPDATED": "",
-    "UPDATE_CREATION_TIME_NOT_STARTED": "",
-    "UPDATE_CREATION_TIME_COMPLETED": "",
-    "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "",
-    "CAPTION_CHARACTER_LIMIT": "",
-    "DATE_TIME_ORIGINAL": "",
-    "DATE_TIME_DIGITIZED": "",
-    "METADATA_DATE": "",
-    "CUSTOM_TIME": "",
-    "REOPEN_PLAN_SELECTOR_MODAL": "",
-    "OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
-    "INSTALL": "",
-    "SHARING_DETAILS": "",
-    "MODIFY_SHARING": "",
-    "ADD_COLLABORATORS": "",
-    "ADD_NEW_EMAIL": "",
-    "shared_with_people_zero": "",
-    "shared_with_people_one": "",
-    "shared_with_people_other": "",
-    "participants_zero": "",
-    "participants_one": "",
-    "participants_other": "",
-    "ADD_VIEWERS": "",
-    "PARTICIPANTS": "",
-    "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": "",
-    "LINK_PASSWORD_LOCK": "",
-    "PUBLIC_COLLECT": "",
-    "LINK_DEVICE_LIMIT": "",
-    "NO_DEVICE_LIMIT": "",
-    "LINK_EXPIRY": "",
-    "NEVER": "",
-    "DISABLE_FILE_DOWNLOAD": "",
-    "DISABLE_FILE_DOWNLOAD_MESSAGE": "",
-    "MALICIOUS_CONTENT": "",
-    "COPYRIGHT": "",
-    "SHARED_USING": "",
-    "ENTE_IO": "",
-    "SHARING_REFERRAL_CODE": "",
-    "LIVE": "",
-    "DISABLE_PASSWORD": "",
-    "DISABLE_PASSWORD_MESSAGE": "",
-    "PASSWORD_LOCK": "",
-    "LOCK": "",
-    "DOWNLOAD_UPLOAD_LOGS": "",
-    "UPLOAD_FILES": "",
-    "UPLOAD_DIRS": "",
-    "UPLOAD_GOOGLE_TAKEOUT": "",
-    "DEDUPLICATE_FILES": "",
-    "AUTHENTICATOR_SECTION": "",
-    "NO_DUPLICATES_FOUND": "",
-    "CLUB_BY_CAPTURE_TIME": "",
-    "FILES": "",
-    "EACH": "",
-    "DEDUPLICATE_BASED_ON_SIZE": "",
-    "STOP_ALL_UPLOADS_MESSAGE": "",
-    "STOP_UPLOADS_HEADER": "",
-    "YES_STOP_UPLOADS": "",
-    "STOP_DOWNLOADS_HEADER": "",
-    "YES_STOP_DOWNLOADS": "",
-    "STOP_ALL_DOWNLOADS_MESSAGE": "",
-    "albums_one": "",
-    "albums_other": "",
-    "ALL_ALBUMS": "",
-    "ALBUMS": "",
-    "ALL_HIDDEN_ALBUMS": "",
-    "HIDDEN_ALBUMS": "",
-    "HIDDEN_ITEMS": "",
-    "HIDDEN_ITEMS_SECTION_NAME": "",
-    "ENTER_TWO_FACTOR_OTP": "",
-    "CREATE_ACCOUNT": "",
-    "COPIED": "",
-    "CANVAS_BLOCKED_TITLE": "",
-    "CANVAS_BLOCKED_MESSAGE": "",
-    "WATCH_FOLDERS": "",
-    "UPGRADE_NOW": "",
-    "RENEW_NOW": "",
-    "STORAGE": "",
-    "USED": "",
-    "YOU": "",
-    "FAMILY": "",
-    "FREE": "",
-    "OF": "",
-    "WATCHED_FOLDERS": "",
-    "NO_FOLDERS_ADDED": "",
-    "FOLDERS_AUTOMATICALLY_MONITORED": "",
-    "UPLOAD_NEW_FILES_TO_ENTE": "",
-    "REMOVE_DELETED_FILES_FROM_ENTE": "",
-    "ADD_FOLDER": "",
-    "STOP_WATCHING": "",
-    "STOP_WATCHING_FOLDER": "",
-    "STOP_WATCHING_DIALOG_MESSAGE": "",
-    "YES_STOP": "",
-    "MONTH_SHORT": "",
-    "YEAR": "",
-    "FAMILY_PLAN": "",
-    "DOWNLOAD_LOGS": "",
-    "DOWNLOAD_LOGS_MESSAGE": "",
-    "CHANGE_FOLDER": "",
-    "TWO_MONTHS_FREE": "",
-    "GB": "",
-    "POPULAR": "",
-    "FREE_PLAN_OPTION_LABEL": "",
-    "FREE_PLAN_DESCRIPTION": "",
-    "CURRENT_USAGE": "",
-    "WEAK_DEVICE": "",
-    "DRAG_AND_DROP_HINT": "",
-    "CONFIRM_ACCOUNT_DELETION_MESSAGE": "",
-    "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": "",
-    "NAME_PLACEHOLDER": "",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "",
-    "CHOSE_THEME": "",
-    "ML_SEARCH": "",
-    "ENABLE_ML_SEARCH_DESCRIPTION": "",
-    "ML_MORE_DETAILS": "",
-    "ENABLE_FACE_SEARCH": "",
-    "ENABLE_FACE_SEARCH_TITLE": "",
-    "ENABLE_FACE_SEARCH_DESCRIPTION": "",
-    "DISABLE_BETA": "",
-    "DISABLE_FACE_SEARCH": "",
-    "DISABLE_FACE_SEARCH_TITLE": "",
-    "DISABLE_FACE_SEARCH_DESCRIPTION": "",
-    "ADVANCED": "",
-    "FACE_SEARCH_CONFIRMATION": "",
-    "LABS": "",
-    "YOURS": "",
-    "PASSPHRASE_STRENGTH_WEAK": "",
-    "PASSPHRASE_STRENGTH_MODERATE": "",
-    "PASSPHRASE_STRENGTH_STRONG": "",
-    "PREFERENCES": "",
-    "LANGUAGE": "",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST": "",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "",
-    "SUBSCRIPTION_VERIFICATION_ERROR": "",
-    "STORAGE_UNITS": {
-        "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": "",
-    "EXPORT_NOTIFICATION": {
-        "START": "",
-        "IN_PROGRESS": "",
-        "FINISH": "",
-        "UP_TO_DATE": ""
-    },
-    "CONTINUOUS_EXPORT": "",
-    "TOTAL_ITEMS": "",
-    "PENDING_ITEMS": "",
-    "EXPORT_STARTING": "",
-    "DELETE_ACCOUNT_REASON_LABEL": "",
-    "DELETE_ACCOUNT_REASON_PLACEHOLDER": "",
-    "DELETE_REASON": {
-        "MISSING_FEATURE": "",
-        "BROKEN_BEHAVIOR": "",
-        "FOUND_ANOTHER_SERVICE": "",
-        "NOT_LISTED": ""
-    },
-    "DELETE_ACCOUNT_FEEDBACK_LABEL": "",
-    "DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "",
-    "CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "",
-    "CONFIRM_DELETE_ACCOUNT": "",
-    "FEEDBACK_REQUIRED": "",
-    "FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "",
-    "RECOVER_TWO_FACTOR": "",
-    "at": "",
-    "AUTH_NEXT": "",
-    "AUTH_DOWNLOAD_MOBILE_APP": "",
-    "HIDDEN": "",
-    "HIDE": "",
-    "UNHIDE": "",
-    "UNHIDE_TO_COLLECTION": "",
-    "SORT_BY": "",
-    "NEWEST_FIRST": "",
-    "OLDEST_FIRST": "",
-    "CONVERSION_FAILED_NOTIFICATION_MESSAGE": "",
-    "SELECT_COLLECTION": "",
-    "PIN_ALBUM": "",
-    "UNPIN_ALBUM": "",
-    "DOWNLOAD_COMPLETE": "",
-    "DOWNLOADING_COLLECTION": "",
-    "DOWNLOAD_FAILED": "",
-    "DOWNLOAD_PROGRESS": "",
-    "CHRISTMAS": "",
-    "CHRISTMAS_EVE": "",
-    "NEW_YEAR": "",
-    "NEW_YEAR_EVE": "",
-    "IMAGE": "",
-    "VIDEO": "",
-    "LIVE_PHOTO": "",
-    "CONVERT": "",
-    "CONFIRM_EDITOR_CLOSE_MESSAGE": "",
-    "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "",
-    "BRIGHTNESS": "",
-    "CONTRAST": "",
-    "SATURATION": "",
-    "BLUR": "",
-    "INVERT_COLORS": "",
-    "ASPECT_RATIO": "",
-    "SQUARE": "",
-    "ROTATE_LEFT": "",
-    "ROTATE_RIGHT": "",
-    "FLIP_VERTICALLY": "",
-    "FLIP_HORIZONTALLY": "",
-    "DOWNLOAD_EDITED": "",
-    "SAVE_A_COPY_TO_ENTE": "",
-    "RESTORE_ORIGINAL": "",
-    "TRANSFORM": "",
-    "COLORS": "",
-    "FLIP": "",
-    "ROTATION": "",
-    "RESET": "",
-    "PHOTO_EDITOR": "",
-    "FASTER_UPLOAD": "",
-    "FASTER_UPLOAD_DESCRIPTION": "",
-    "MAGIC_SEARCH_STATUS": "",
-    "INDEXED_ITEMS": "",
-    "CAST_ALBUM_TO_TV": "",
-    "ENTER_CAST_PIN_CODE": "",
-    "PAIR_DEVICE_TO_TV": "",
-    "TV_NOT_FOUND": "",
-    "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
-    "PAIR_WITH_PIN": "",
-    "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
-    "VISIT_CAST_ENTE_IO": "",
-    "CAST_AUTO_PAIR_FAILED": "",
-    "CACHE_DIRECTORY": "",
-    "FREEHAND": "",
-    "APPLY_CROP": "",
-    "PHOTO_EDIT_REQUIRED_TO_SAVE": "",
-    "PASSKEYS": "",
-    "DELETE_PASSKEY": "",
-    "DELETE_PASSKEY_CONFIRMATION": "",
-    "RENAME_PASSKEY": "",
-    "ADD_PASSKEY": "",
-    "ENTER_PASSKEY_NAME": "",
-    "PASSKEYS_DESCRIPTION": "",
-    "CREATED_AT": "",
-    "PASSKEY_LOGIN_FAILED": "",
-    "PASSKEY_LOGIN_URL_INVALID": "",
-    "PASSKEY_LOGIN_ERRORED": "",
-    "TRY_AGAIN": "",
-    "PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER": "",
-    "LOGIN_WITH_PASSKEY": ""
-}

+ 0 - 654
web/apps/accounts/public/locales/fr-FR/translation.json

@@ -1,654 +0,0 @@
-{
-    "HERO_SLIDE_1_TITLE": "<div>Sauvegardes privées</div><div>pour vos souvenirs</div>",
-    "HERO_SLIDE_1": "Chiffrement de bout en bout par défaut",
-    "HERO_SLIDE_2_TITLE": "<div>Sécurisé </div><div>dans un abri antiatomique</div>",
-    "HERO_SLIDE_2": "Conçu pour survivre",
-    "HERO_SLIDE_3_TITLE": "<div>Disponible</div><div> en tout lieu</div>",
-    "HERO_SLIDE_3": "Android, iOS, Web, Ordinateur",
-    "LOGIN": "Connexion",
-    "SIGN_UP": "Inscription",
-    "NEW_USER": "Nouveau sur ente",
-    "EXISTING_USER": "Utilisateur existant",
-    "ENTER_NAME": "Saisir un nom",
-    "PUBLIC_UPLOADER_NAME_MESSAGE": "Ajouter un nom afin que vos amis sachent qui remercier pour ces magnifiques photos!",
-    "ENTER_EMAIL": "Saisir l'adresse e-mail",
-    "EMAIL_ERROR": "Saisir un e-mail valide",
-    "REQUIRED": "Nécessaire",
-    "EMAIL_SENT": "Code de vérification envoyé à <a>{{email}}</a>",
-    "CHECK_INBOX": "Veuillez consulter votre boite de réception (et indésirables) pour poursuivre la vérification",
-    "ENTER_OTT": "Code de vérification",
-    "RESEND_MAIL": "Renvoyer le code",
-    "VERIFY": "Vérifier",
-    "UNKNOWN_ERROR": "Quelque chose s'est mal passé, veuillez recommencer",
-    "INVALID_CODE": "Code de vérification non valide",
-    "EXPIRED_CODE": "Votre code de vérification a expiré",
-    "SENDING": "Envoi...",
-    "SENT": "Envoyé!",
-    "PASSWORD": "Mot de passe",
-    "LINK_PASSWORD": "Saisir le mot de passe pour déverrouiller l'album",
-    "RETURN_PASSPHRASE_HINT": "Mot de passe",
-    "SET_PASSPHRASE": "Définir le mot de passe",
-    "VERIFY_PASSPHRASE": "Connexion",
-    "INCORRECT_PASSPHRASE": "Mot de passe non valide",
-    "ENTER_ENC_PASSPHRASE": "Veuillez saisir un mot de passe que nous pourrons utiliser pour chiffrer vos données",
-    "PASSPHRASE_DISCLAIMER": "Nous ne stockons pas votre mot de passe, donc si vous le perdez, <strong>nous ne pourrons pas vous aider</strong> à récupérer vos données sans une clé de récupération.",
-    "WELCOME_TO_ENTE_HEADING": "Bienvenue sur <a/>",
-    "WELCOME_TO_ENTE_SUBHEADING": "Stockage et partage photo avec cryptage de bout en bout",
-    "WHERE_YOUR_BEST_PHOTOS_LIVE": "Là où vivent vos meilleures photos",
-    "KEY_GENERATION_IN_PROGRESS_MESSAGE": "Génération des clés de chiffrement...",
-    "PASSPHRASE_HINT": "Mot de passe",
-    "CONFIRM_PASSPHRASE": "Confirmer le mot de passe",
-    "REFERRAL_CODE_HINT": "Comment avez-vous entendu parler de Ente? (facultatif)",
-    "REFERRAL_INFO": "Nous ne suivons pas les installations d'applications. Il serait utile que vous nous disiez comment vous nous avez trouvés !",
-    "PASSPHRASE_MATCH_ERROR": "Les mots de passe ne correspondent pas",
-    "CREATE_COLLECTION": "Nouvel album",
-    "ENTER_ALBUM_NAME": "Nom de l'album",
-    "CLOSE_OPTION": "Fermer (Échap)",
-    "ENTER_FILE_NAME": "Nom du fichier",
-    "CLOSE": "Fermer",
-    "NO": "Non",
-    "NOTHING_HERE": "Il n'y a encore rien à voir ici 👀",
-    "UPLOAD": "Charger",
-    "IMPORT": "Importer",
-    "ADD_PHOTOS": "Ajouter des photos",
-    "ADD_MORE_PHOTOS": "Ajouter plus de photos",
-    "add_photos_one": "Ajouter une photo",
-    "add_photos_other": "Ajouter {{count}} photos",
-    "SELECT_PHOTOS": "Sélectionner des photos",
-    "FILE_UPLOAD": "Fichier chargé",
-    "UPLOAD_STAGE_MESSAGE": {
-        "0": "Préparation du chargement",
-        "1": "Lecture des fichiers de métadonnées de Google",
-        "2": "Métadonnées des fichiers {{uploadCounter.finished}} / {{uploadCounter.total}} extraites",
-        "3": "{{uploadCounter.finished}} / {{uploadCounter.total}} fichiers sauvegardés",
-        "4": "Annulation des chargements restants",
-        "5": "Sauvegarde terminée"
-    },
-    "FILE_NOT_UPLOADED_LIST": "Les fichiers suivants n'ont pas été chargés",
-    "SUBSCRIPTION_EXPIRED": "Abonnement expiré",
-    "SUBSCRIPTION_EXPIRED_MESSAGE": "Votre abonnement a expiré, veuillez <a>le renouveler </a>",
-    "STORAGE_QUOTA_EXCEEDED": "Limite de stockage atteinte",
-    "INITIAL_LOAD_DELAY_WARNING": "La première consultation peut prendre du temps",
-    "USER_DOES_NOT_EXIST": "Désolé, impossible de trouver un utilisateur avec cet e-mail",
-    "NO_ACCOUNT": "Je n'ai pas de compte",
-    "ACCOUNT_EXISTS": "J'ai déjà un compte",
-    "CREATE": "Créer",
-    "DOWNLOAD": "Télécharger",
-    "DOWNLOAD_OPTION": "Télécharger (D)",
-    "DOWNLOAD_FAVORITES": "Télécharger les favoris",
-    "DOWNLOAD_UNCATEGORIZED": "Télécharger les hors catégories",
-    "DOWNLOAD_HIDDEN_ITEMS": "Télécharger les fichiers masqués",
-    "COPY_OPTION": "Copier en PNG (Ctrl/Cmd - C)",
-    "TOGGLE_FULLSCREEN": "Plein écran (F)",
-    "ZOOM_IN_OUT": "Zoom +/-",
-    "PREVIOUS": "Précédent (←)",
-    "NEXT": "Suivant (→)",
-    "TITLE_PHOTOS": "Ente Photos",
-    "TITLE_ALBUMS": "Ente Photos",
-    "TITLE_AUTH": "Ente Auth",
-    "UPLOAD_FIRST_PHOTO": "Chargez votre 1ere photo",
-    "IMPORT_YOUR_FOLDERS": "Importez vos dossiers",
-    "UPLOAD_DROPZONE_MESSAGE": "Déposez pour sauvegarder vos fichiers",
-    "WATCH_FOLDER_DROPZONE_MESSAGE": "Déposez pour ajouter un dossier surveillé",
-    "TRASH_FILES_TITLE": "Supprimer les fichiers ?",
-    "TRASH_FILE_TITLE": "Supprimer le fichier ?",
-    "DELETE_FILES_TITLE": "Supprimer immédiatement?",
-    "DELETE_FILES_MESSAGE": "Les fichiers sélectionnés seront définitivement supprimés de votre compte ente.",
-    "DELETE": "Supprimer",
-    "DELETE_OPTION": "Supprimer (DEL)",
-    "FAVORITE_OPTION": "Favori (L)",
-    "UNFAVORITE_OPTION": "Non favori (L)",
-    "MULTI_FOLDER_UPLOAD": "Plusieurs dossiers détectés",
-    "UPLOAD_STRATEGY_CHOICE": "Voulez-vous les charger dans",
-    "UPLOAD_STRATEGY_SINGLE_COLLECTION": "Un seul album",
-    "OR": "ou",
-    "UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "Albums séparés",
-    "SESSION_EXPIRED_MESSAGE": "Votre session a expiré, veuillez vous reconnecter pour poursuivre",
-    "SESSION_EXPIRED": "Session expiré",
-    "PASSWORD_GENERATION_FAILED": "Votre navigateur ne permet pas de générer une clé forte correspondant aux standards de chiffrement de ente, veuillez réessayer en utilisant l'appli mobile ou un autre navigateur",
-    "CHANGE_PASSWORD": "Modifier le mot de passe",
-    "GO_BACK": "Retour",
-    "RECOVERY_KEY": "Clé de récupération",
-    "SAVE_LATER": "Plus tard",
-    "SAVE": "Sauvegarder la clé",
-    "RECOVERY_KEY_DESCRIPTION": "Si vous oubliez votre mot de passe, la seule façon de récupérer vos données sera grâce à cette clé.",
-    "RECOVER_KEY_GENERATION_FAILED": "Le code de récupération ne peut être généré, veuillez réessayer",
-    "KEY_NOT_STORED_DISCLAIMER": "Nous ne stockons pas cette clé, veuillez donc la sauvegarder dans un endroit sûr",
-    "FORGOT_PASSWORD": "Mot de passe oublié",
-    "RECOVER_ACCOUNT": "Récupérer le compte",
-    "RECOVERY_KEY_HINT": "Clé de récupération",
-    "RECOVER": "Récupérer",
-    "NO_RECOVERY_KEY": "Pas de clé de récupération?",
-    "INCORRECT_RECOVERY_KEY": "Clé de récupération non valide",
-    "SORRY": "Désolé",
-    "NO_RECOVERY_KEY_MESSAGE": "En raison de notre protocole de chiffrement de bout en bout, vos données ne peuvent être décryptées sans votre mot de passe ou clé de récupération",
-    "NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Veuillez envoyer un e-mail à <a>{{emailID}}</a> depuis votre adresse enregistrée",
-    "CONTACT_SUPPORT": "Contacter le support",
-    "REQUEST_FEATURE": "Soumettre une idée",
-    "SUPPORT": "Support",
-    "CONFIRM": "Confirmer",
-    "CANCEL": "Annuler",
-    "LOGOUT": "Déconnexion",
-    "DELETE_ACCOUNT": "Supprimer le compte",
-    "DELETE_ACCOUNT_MESSAGE": "<p>Veuillez envoyer un e-mail à <a>{{emailID}}</a>depuis Votre adresse enregistrée.</p><p> Votre demande sera traitée dans les 72 heures.</p>",
-    "LOGOUT_MESSAGE": "Voulez-vous vraiment vous déconnecter?",
-    "CHANGE_EMAIL": "Modifier l'e-mail",
-    "OK": "Ok",
-    "SUCCESS": "Parfait",
-    "ERROR": "Erreur",
-    "MESSAGE": "Message",
-    "INSTALL_MOBILE_APP": "Installez notre application <a>Android</a> or <b>iOS</b> pour sauvegarder automatiquement toutes vos photos",
-    "DOWNLOAD_APP_MESSAGE": "Désolé, cette opération est actuellement supportée uniquement sur notre appli pour ordinateur",
-    "DOWNLOAD_APP": "Télécharger l'appli pour ordinateur",
-    "EXPORT": "Exporter des données",
-    "SUBSCRIPTION": "Abonnement",
-    "SUBSCRIBE": "S'abonner",
-    "MANAGEMENT_PORTAL": "Gérer le mode de paiement",
-    "MANAGE_FAMILY_PORTAL": "Gérer la famille",
-    "LEAVE_FAMILY_PLAN": "Quitter le plan famille",
-    "LEAVE": "Quitter",
-    "LEAVE_FAMILY_CONFIRM": "Êtes-vous certains de vouloir quitter le plan famille?",
-    "CHOOSE_PLAN": "Choisir votre plan",
-    "MANAGE_PLAN": "Gérer votre abonnement",
-    "ACTIVE": "Actif",
-    "OFFLINE_MSG": "Vous êtes hors-ligne, les mémoires cache sont affichées",
-    "FREE_SUBSCRIPTION_INFO": "Vous êtes sur le plan <strong>gratuit</strong> qui expire le {{date, dateTime}}",
-    "FAMILY_SUBSCRIPTION_INFO": "Vous êtes sur le plan famille géré par",
-    "RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "Renouveler le {{date, dateTime}}",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "Pris fin le {{date, dateTime}}",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "Votre abonnement sera annulé le {{date, dateTime}}",
-    "ADD_ON_AVAILABLE_TILL": "Votre module {{storage, string}} est valable jusqu'au {{date, dateTime}}",
-    "STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "Vous avez dépassé votre quota de stockage, veuillez <a> mettre à niveau </a>",
-    "SUBSCRIPTION_PURCHASE_SUCCESS": "<p>Nous avons reçu votre paiement </p><p>Votre abonnement est valide jusqu'au <strong>{{date, dateTime}}</strong></p>",
-    "SUBSCRIPTION_PURCHASE_CANCELLED": "Votre achat est annulé, veuillez réessayer si vous souhaitez vous abonner",
-    "SUBSCRIPTION_PURCHASE_FAILED": "Échec lors de l'achat de l'abonnement, veuillez réessayer",
-    "SUBSCRIPTION_UPDATE_FAILED": "Échec lors de la mise à niveau de l'abonnement, veuillez réessayer",
-    "UPDATE_PAYMENT_METHOD_MESSAGE": "Désolé, échec de paiement lors de la saisie de votre carte, veuillez mettr eà jour votre moyen de paiement et réessayer",
-    "STRIPE_AUTHENTICATION_FAILED": "Nous n'avons pas pu authentifier votre moyen de paiement. Veuillez choisir un moyen différent et réessayer",
-    "UPDATE_PAYMENT_METHOD": "Mise à jour du moyen de paiement",
-    "MONTHLY": "Mensuel",
-    "YEARLY": "Annuel",
-    "UPDATE_SUBSCRIPTION_MESSAGE": "Êtes-vous certains de vouloir changer de plan?",
-    "UPDATE_SUBSCRIPTION": "Changer de plan",
-    "CANCEL_SUBSCRIPTION": "Annuler l'abonnement",
-    "CANCEL_SUBSCRIPTION_MESSAGE": "<p>Toutes vos données seront supprimées de nos serveurs à la fin de cette période d'abonnement.</p><p>Voulez-vous vraiment annuler votre abonnement?</p>",
-    "CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "Êtes-vous sûr de vouloir annuler votre abonnement ",
-    "SUBSCRIPTION_CANCEL_FAILED": "Échec lors de l'annulation de l'abonnement",
-    "SUBSCRIPTION_CANCEL_SUCCESS": "Votre abonnement a bien été annulé",
-    "REACTIVATE_SUBSCRIPTION": "Réactiver l'abonnement",
-    "REACTIVATE_SUBSCRIPTION_MESSAGE": "Une fois réactivée, vous serrez facturé de  {{val, datetime}}",
-    "SUBSCRIPTION_ACTIVATE_SUCCESS": "Votre abonnement est bien activé ",
-    "SUBSCRIPTION_ACTIVATE_FAILED": "Échec lors de la réactivation de l'abonnement",
-    "SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "Merci",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE": "Annuler l'abonnement mobile",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "Veuillez annuler votre abonnement depuis l'appli mobile pour activer un abonnement ici",
-    "MAIL_TO_MANAGE_SUBSCRIPTION": "Veuillez nous contacter à <a>{{emailID}}</a> pour gérer votre abonnement",
-    "RENAME": "Renommer",
-    "RENAME_FILE": "Renommer le fichier",
-    "RENAME_COLLECTION": "Renommer l'album",
-    "DELETE_COLLECTION_TITLE": "Supprimer l'album?",
-    "DELETE_COLLECTION": "Supprimer l'album",
-    "DELETE_COLLECTION_MESSAGE": "Supprimer aussi les photos (et vidéos) présentes dans cet album depuis <a>tous</a> les autres albums dont ils font partie?",
-    "DELETE_PHOTOS": "Supprimer des photos",
-    "KEEP_PHOTOS": "Conserver des photos",
-    "SHARE": "Partager",
-    "SHARE_COLLECTION": "Partager l'album",
-    "SHAREES": "Partager avec",
-    "SHARE_WITH_SELF": "Oups, vous ne pouvez pas partager avec vous-même",
-    "ALREADY_SHARED": "Oups, vous partager déjà cela avec {{email}}",
-    "SHARING_BAD_REQUEST_ERROR": "Partage d'album non autorisé",
-    "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "Le partage est désactivé pour les comptes gratuits",
-    "DOWNLOAD_COLLECTION": "Télécharger l'album",
-    "DOWNLOAD_COLLECTION_MESSAGE": "<p>Êtes-vous certains de vouloir télécharger l'album complet?</p><p>Tous les fichiers seront mis en file d'attente pour un téléchargement fractionné</p>",
-    "CREATE_ALBUM_FAILED": "Échec de création de l'album , veuillez réessayer",
-    "SEARCH": "Recherche",
-    "SEARCH_RESULTS": "Résultats de la recherche",
-    "NO_RESULTS": "Aucun résultat trouvé",
-    "SEARCH_HINT": "Recherche d'albums, dates, descriptions, ...",
-    "SEARCH_TYPE": {
-        "COLLECTION": "l'album",
-        "LOCATION": "Emplacement",
-        "CITY": "Adresse",
-        "DATE": "Date",
-        "FILE_NAME": "Nom de fichier",
-        "THING": "Chose",
-        "FILE_CAPTION": "Description",
-        "FILE_TYPE": "Type de fichier",
-        "CLIP": "Magique"
-    },
-    "photos_count_zero": "Pas de souvenirs",
-    "photos_count_one": "1 souvenir",
-    "photos_count_other": "{{count}} souvenirs",
-    "TERMS_AND_CONDITIONS": "J'accepte les <a>conditions</a> et la <b>politique de confidentialité</b>",
-    "ADD_TO_COLLECTION": "Ajouter à l'album",
-    "SELECTED": "Sélectionné",
-    "VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "Cette vidéo ne peut pas être lue sur votre navigateur",
-    "PEOPLE": "Visages",
-    "INDEXING_SCHEDULED": "L'indexation est planifiée...",
-    "ANALYZING_PHOTOS": "analyse des nouvelles photos {{indexStatus.nSyncedFiles}} sur {{indexStatus.nTotalFiles}} effectué)...",
-    "INDEXING_PEOPLE": "indexation des visages dans {{indexStatus.nSyncedFiles}} photos...",
-    "INDEXING_DONE": "{{indexStatus.nSyncedFiles}} photos indexées",
-    "UNIDENTIFIED_FACES": "visages non-identifiés",
-    "OBJECTS": "objets",
-    "TEXT": "texte",
-    "INFO": "Info ",
-    "INFO_OPTION": "Info (I)",
-    "FILE_NAME": "Nom de fichier",
-    "CAPTION_PLACEHOLDER": "Ajouter une description",
-    "LOCATION": "Emplacement",
-    "SHOW_ON_MAP": "Visualiser sur OpenStreetMap",
-    "MAP": "Carte",
-    "MAP_SETTINGS": "Paramètres de la carte",
-    "ENABLE_MAPS": "Activer la carte?",
-    "ENABLE_MAP": "Activer la carte",
-    "DISABLE_MAPS": "Désactiver la carte?",
-    "ENABLE_MAP_DESCRIPTION": "<p>Cette fonction affiche vos photos sur une carte du monde.</p> <p>La carte est hébergée par <a>OpenStreetMap</a>, et les emplacements exacts de vos photos ne sont jamais partagés.</p> <p>Vous pouvez désactiver cette fonction à tout moment dans des paramètres.</p>",
-    "DISABLE_MAP_DESCRIPTION": "<p>Cette fonction désactive l'affichage de vos photos sur une carte du monde.</p> <p>Vous pouvez activer cette fonction à tout moment dans les Paramètres.</p>",
-    "DISABLE_MAP": "Désactiver la carte",
-    "DETAILS": "Détails",
-    "VIEW_EXIF": "Visualiser toutes les données EXIF",
-    "NO_EXIF": "Aucune donnée EXIF",
-    "EXIF": "EXIF",
-    "ISO": "ISO",
-    "TWO_FACTOR": "Double authentification",
-    "TWO_FACTOR_AUTHENTICATION": "Authentification double-facteur",
-    "TWO_FACTOR_QR_INSTRUCTION": "Scannez le QRCode ci-dessous avec une appli d'authentification",
-    "ENTER_CODE_MANUALLY": "Saisir le code manuellement",
-    "TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "Veuillez saisir ce code dans votre appli d'authentification",
-    "SCAN_QR_CODE": "Scannez le QRCode de préférence",
-    "ENABLE_TWO_FACTOR": "Activer la double-authentification",
-    "ENABLE": "Activer",
-    "LOST_DEVICE": "Perte de l'appareil identificateur",
-    "INCORRECT_CODE": "Code non valide",
-    "TWO_FACTOR_INFO": "Rajoutez une couche de sécurité supplémentaire afin de pas utiliser simplement votre e-mail et mot de passe pour vous connecter à votre compte",
-    "DISABLE_TWO_FACTOR_LABEL": "Désactiver la double-authentification",
-    "UPDATE_TWO_FACTOR_LABEL": "Mise à jour de votre appareil identificateur",
-    "DISABLE": "Désactiver",
-    "RECONFIGURE": "Reconfigurer",
-    "UPDATE_TWO_FACTOR": "Mise à jour de la double-authentification",
-    "UPDATE_TWO_FACTOR_MESSAGE": "Continuer annulera tous les identificateurs précédemment configurés",
-    "UPDATE": "Mise à jour",
-    "DISABLE_TWO_FACTOR": "Désactiver la double-authentification",
-    "DISABLE_TWO_FACTOR_MESSAGE": "Êtes-vous certains de vouloir désactiver la double-authentification",
-    "TWO_FACTOR_DISABLE_FAILED": "Échec de désactivation de la double-authentification, veuillez réessayer",
-    "EXPORT_DATA": "Exporter les données",
-    "SELECT_FOLDER": "Sélectionner un dossier",
-    "DESTINATION": "Destination",
-    "START": "Démarrer",
-    "LAST_EXPORT_TIME": "Horaire du dernier export",
-    "EXPORT_AGAIN": "Resynchro",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE": "Stockage local non accessible",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "Votre navigateur ou un complément bloque ente qui ne peut sauvegarder les données sur votre stockage local. Veuillez relancer cette page après avoir changé de mode de navigation.",
-    "SEND_OTT": "Envoyer l'OTP",
-    "EMAIl_ALREADY_OWNED": "Cet e-mail est déjà pris",
-    "ETAGS_BLOCKED": "<p>Nosu n'avons pas pu charger les fichiers suivants à cause de la configuration de votre navigateur.</p><p>Veuillez désactiver tous les compléments qui pourraient empêcher ente d'utiliser les <code>eTags</code> pour charger de larges fichiers, ou bien utilisez notre <a>appli pour ordinateur</a>pour une meilleure expérience lors des chargements.</p>",
-    "SKIPPED_VIDEOS_INFO": "<p>Actuellement, nous ne supportons pas l'ajout de videos via des liens publics.</p><p>Pour partager des vidéos, veuillez <a>vous connecter à</a>ente et partager en utilisant l'e-mail concerné.</p>",
-    "LIVE_PHOTOS_DETECTED": "Les fichiers photos et vidéos depuis votre espace Live Photos ont été fusionnés en un seul fichier",
-    "RETRY_FAILED": "Réessayer les chargements ayant échoués",
-    "FAILED_UPLOADS": "Chargements échoués ",
-    "SKIPPED_FILES": "Chargements ignorés",
-    "THUMBNAIL_GENERATION_FAILED_UPLOADS": "Échec de création d'une miniature",
-    "UNSUPPORTED_FILES": "Fichiers non supportés",
-    "SUCCESSFUL_UPLOADS": "Chargements réussis",
-    "SKIPPED_INFO": "Ignorés car il y a des fichiers avec des noms identiques dans le même album",
-    "UNSUPPORTED_INFO": "ente ne supporte pas encore ces formats de fichiers",
-    "BLOCKED_UPLOADS": "Chargements bloqués",
-    "SKIPPED_VIDEOS": "Vidéos ignorées",
-    "INPROGRESS_METADATA_EXTRACTION": "En cours",
-    "INPROGRESS_UPLOADS": "Chargements en cours",
-    "TOO_LARGE_UPLOADS": "Gros fichiers",
-    "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "Stockage insuffisant",
-    "LARGER_THAN_AVAILABLE_STORAGE_INFO": "Ces fichiers n'ont pas été chargés car ils dépassent la taille maximale de votre plan de stockage",
-    "TOO_LARGE_INFO": "Ces fichiers n'ont pas été chargés car ils dépassent notre taille limite par fichier",
-    "THUMBNAIL_GENERATION_FAILED_INFO": "Ces fichiers sont bien chargés, mais nous ne pouvons pas créer de miniatures pour eux.",
-    "UPLOAD_TO_COLLECTION": "Charger dans l'album",
-    "UNCATEGORIZED": "Aucune catégorie",
-    "ARCHIVE": "Archiver",
-    "FAVORITES": "Favoris",
-    "ARCHIVE_COLLECTION": "Archiver l'album",
-    "ARCHIVE_SECTION_NAME": "Archivé",
-    "ALL_SECTION_NAME": "Tous",
-    "MOVE_TO_COLLECTION": "Déplacer vers l'album",
-    "UNARCHIVE": "Désarchiver",
-    "UNARCHIVE_COLLECTION": "Désarchiver l'album",
-    "HIDE_COLLECTION": "Masquer l'album",
-    "UNHIDE_COLLECTION": "Dévoiler l'album",
-    "MOVE": "Déplacer",
-    "ADD": "Ajouter",
-    "REMOVE": "Retirer",
-    "YES_REMOVE": "Oui, retirer",
-    "REMOVE_FROM_COLLECTION": "Retirer de l'album",
-    "TRASH": "Corbeille",
-    "MOVE_TO_TRASH": "Déplacer vers la corbeille",
-    "TRASH_FILES_MESSAGE": "Les fichiers sélectionnés seront retirés de tous les albums puis déplacés dans la corbeille.",
-    "TRASH_FILE_MESSAGE": "Le fichier sera retiré de tous les albums puis déplacé dans la corbeille.",
-    "DELETE_PERMANENTLY": "Supprimer définitivement",
-    "RESTORE": "Restaurer",
-    "RESTORE_TO_COLLECTION": "Restaurer vers l'album",
-    "EMPTY_TRASH": "Corbeille vide",
-    "EMPTY_TRASH_TITLE": "Vider la corbeille ?",
-    "EMPTY_TRASH_MESSAGE": "Ces fichiers seront définitivement supprimés de votre compte ente.",
-    "LEAVE_SHARED_ALBUM": "Oui, quitter",
-    "LEAVE_ALBUM": "Quitter l'album",
-    "LEAVE_SHARED_ALBUM_TITLE": "Quitter l'album partagé?",
-    "LEAVE_SHARED_ALBUM_MESSAGE": "Vous allez quitter cet album, il ne sera plus visible pour vous.",
-    "NOT_FILE_OWNER": "Vous ne pouvez pas supprimer les fichiers d'un album partagé",
-    "CONFIRM_SELF_REMOVE_MESSAGE": "Choisir les objets qui seront retirés de cet album. Ceux qui sont présents uniquement dans cet album seront déplacés comme hors catégorie.",
-    "CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "Certains des objets que vous êtes en train de retirer ont été ajoutés par d'autres personnes, vous perdrez l'accès vers ces objets.",
-    "SORT_BY_CREATION_TIME_ASCENDING": "Plus anciens",
-    "SORT_BY_UPDATION_TIME_DESCENDING": "Dernière mise à jour",
-    "SORT_BY_NAME": "Nom",
-    "COMPRESS_THUMBNAILS": "Compresser les miniatures",
-    "THUMBNAIL_REPLACED": "Les miniatures sont compressées",
-    "FIX_THUMBNAIL": "Compresser",
-    "FIX_THUMBNAIL_LATER": "Compresser plus tard",
-    "REPLACE_THUMBNAIL_NOT_STARTED": "Certaines miniatures de vidéos peuvent être compressées pour gagner de la place. Voulez-vous que ente les compresse?",
-    "REPLACE_THUMBNAIL_COMPLETED": "Toutes les miniatures ont été compressées",
-    "REPLACE_THUMBNAIL_NOOP": "Vous n'avez aucune miniature qui peut être encore plus compressée",
-    "REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR": "Impossible de compresser certaines miniatures, veuillez réessayer",
-    "FIX_CREATION_TIME": "Réajuster l'heure",
-    "FIX_CREATION_TIME_IN_PROGRESS": "Réajustement de l'heure",
-    "CREATION_TIME_UPDATED": "L'heure du fichier a été réajustée",
-    "UPDATE_CREATION_TIME_NOT_STARTED": "Sélectionnez l'option que vous souhaitez utiliser",
-    "UPDATE_CREATION_TIME_COMPLETED": "Mise à jour effectuée pour tous les fichiers",
-    "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "L'heure du fichier n'a pas été mise à jour pour certains fichiers, veuillez réessayer",
-    "CAPTION_CHARACTER_LIMIT": "5000 caractères max",
-    "DATE_TIME_ORIGINAL": "EXIF:DateTimeOriginal",
-    "DATE_TIME_DIGITIZED": "EXIF:DateTimeDigitized",
-    "METADATA_DATE": "EXIF:MetadataDate",
-    "CUSTOM_TIME": "Heure personnalisée",
-    "REOPEN_PLAN_SELECTOR_MODAL": "Rouvrir les plans",
-    "OPEN_PLAN_SELECTOR_MODAL_FAILED": "Échec pour rouvrir les plans",
-    "INSTALL": "Installer",
-    "SHARING_DETAILS": "Détails du partage",
-    "MODIFY_SHARING": "Modifier le partage",
-    "ADD_COLLABORATORS": "Ajouter des collaborateurs",
-    "ADD_NEW_EMAIL": "Ajouter un nouvel email",
-    "shared_with_people_zero": "Partager avec des personnes spécifiques",
-    "shared_with_people_one": "Partagé avec 1 personne",
-    "shared_with_people_other": "Partagé avec {{count, number}} personnes",
-    "participants_zero": "Aucun participant",
-    "participants_one": "1 participant",
-    "participants_other": "{{count, number}} participants",
-    "ADD_VIEWERS": "Ajouter un observateur",
-    "PARTICIPANTS": "Participants",
-    "CHANGE_PERMISSIONS_TO_VIEWER": "<p>{{selectedEmail}} ne pourra plus ajouter de photos à l'album</p> <p>Il pourra toujours supprimer les photos qu'il a ajoutées</p>",
-    "CHANGE_PERMISSIONS_TO_COLLABORATOR": "{{selectedEmail}} pourra ajouter des photos à l'album",
-    "CONVERT_TO_VIEWER": "Oui, convertir en observateur",
-    "CONVERT_TO_COLLABORATOR": "Oui, convertir en collaborateur",
-    "CHANGE_PERMISSION": "Modifier la permission?",
-    "REMOVE_PARTICIPANT": "Retirer?",
-    "CONFIRM_REMOVE": "Oui, supprimer",
-    "MANAGE": "Gérer",
-    "ADDED_AS": "Ajouté comme",
-    "COLLABORATOR_RIGHTS": "Les collaborateurs peuvent ajouter des photos et des vidéos à l'album partagé",
-    "REMOVE_PARTICIPANT_HEAD": "Supprimer le participant",
-    "OWNER": "Propriétaire",
-    "COLLABORATORS": "Collaborateurs",
-    "ADD_MORE": "Ajouter plus",
-    "VIEWERS": "Visionneurs",
-    "OR_ADD_EXISTING": "ou sélectionner un fichier existant",
-    "REMOVE_PARTICIPANT_MESSAGE": "<p>{{selectedEmail}} sera supprimé de l'album</p> <p>Toutes les photos ajoutées par cette personne seront également supprimées de l'album</p>",
-    "NOT_FOUND": "404 - non trouvé",
-    "LINK_EXPIRED": "Lien expiré",
-    "LINK_EXPIRED_MESSAGE": "Ce lien à soit expiré soit est supprimé!",
-    "MANAGE_LINK": "Gérer le lien",
-    "LINK_TOO_MANY_REQUESTS": "Désolé, cet album a été consulté sur trop d'appareils !",
-    "FILE_DOWNLOAD": "Autoriser les téléchargements",
-    "LINK_PASSWORD_LOCK": "Verrou par mot de passe",
-    "PUBLIC_COLLECT": "Autoriser l'ajout de photos",
-    "LINK_DEVICE_LIMIT": "Limite d'appareil",
-    "NO_DEVICE_LIMIT": "Aucune",
-    "LINK_EXPIRY": "Expiration du lien",
-    "NEVER": "Jamais",
-    "DISABLE_FILE_DOWNLOAD": "Désactiver le téléchargement",
-    "DISABLE_FILE_DOWNLOAD_MESSAGE": "<p>Êtes-vous certains de vouloir désactiver le bouton de téléchargement pour les fichiers?</p><p>Ceux qui les visualisent pourront tout de même faire des captures d'écrans ou sauvegarder une copie de vos photos en utilisant des outils externes.</p>",
-    "MALICIOUS_CONTENT": "Contient du contenu malveillant",
-    "COPYRIGHT": "Enfreint les droits d'une personne que je réprésente",
-    "SHARED_USING": "Partagé en utilisant ",
-    "ENTE_IO": "ente.io",
-    "SHARING_REFERRAL_CODE": "Utilisez le code <strong>{{referralCode}}</strong> pour obtenir 10 Go gratuits",
-    "LIVE": "LIVE",
-    "DISABLE_PASSWORD": "Désactiver le verrouillage par mot de passe",
-    "DISABLE_PASSWORD_MESSAGE": "Êtes-vous certains de vouloir désactiver le verrouillage par mot de passe ?",
-    "PASSWORD_LOCK": "Mot de passe verrou",
-    "LOCK": "Verrouiller",
-    "DOWNLOAD_UPLOAD_LOGS": "Journaux de débugs",
-    "UPLOAD_FILES": "Fichier",
-    "UPLOAD_DIRS": "Dossier",
-    "UPLOAD_GOOGLE_TAKEOUT": "Google Takeout",
-    "DEDUPLICATE_FILES": "Déduplication de fichiers",
-    "AUTHENTICATOR_SECTION": "Authentificateur",
-    "NO_DUPLICATES_FOUND": "Vous n'avez aucun fichier dédupliqué pouvant être nettoyé",
-    "CLUB_BY_CAPTURE_TIME": "Durée de la capture par club",
-    "FILES": "Fichiers",
-    "EACH": "Chacun",
-    "DEDUPLICATE_BASED_ON_SIZE": "Les fichiers suivants ont été clubbed, basé sur leurs tailles, veuillez corriger et supprimer les objets que vous pensez être dupliqués",
-    "STOP_ALL_UPLOADS_MESSAGE": "Êtes-vous certains de vouloir arrêter tous les chargements en cours?",
-    "STOP_UPLOADS_HEADER": "Arrêter les chargements ?",
-    "YES_STOP_UPLOADS": "Oui, arrêter tout",
-    "STOP_DOWNLOADS_HEADER": "Arrêter le téléchargement ?",
-    "YES_STOP_DOWNLOADS": "Oui, arrêter les téléchargements",
-    "STOP_ALL_DOWNLOADS_MESSAGE": "Êtes-vous certains de vouloir arrêter tous les chargements en cours?",
-    "albums_one": "1 album",
-    "albums_other": "{{count}} albums",
-    "ALL_ALBUMS": "Tous les albums",
-    "ALBUMS": "Albums",
-    "ALL_HIDDEN_ALBUMS": "Tous les albums masqués",
-    "HIDDEN_ALBUMS": "Albums masqués",
-    "HIDDEN_ITEMS": "Éléments masqués",
-    "HIDDEN_ITEMS_SECTION_NAME": "Éléments masqués",
-    "ENTER_TWO_FACTOR_OTP": "Saisir le code à 6 caractères de votre appli d'authentification.",
-    "CREATE_ACCOUNT": "Créer un compte",
-    "COPIED": "Copié",
-    "CANVAS_BLOCKED_TITLE": "Impossible de créer une miniature",
-    "CANVAS_BLOCKED_MESSAGE": "<p>Il semblerait que votre navigateur ait désactivé l'accès au canevas, qui est nécessaire pour créer les miniatures de vos photos </p><p>Veuillez activer l'accès au canevas du navigateur, ou consulter notre appli pour ordinateur</p></>",
-    "WATCH_FOLDERS": "Voir les dossiers",
-    "UPGRADE_NOW": "Mettre à niveau maintenant",
-    "RENEW_NOW": "Renouveler maintenant",
-    "STORAGE": "Stockage",
-    "USED": "utilisé",
-    "YOU": "Vous",
-    "FAMILY": "Famille",
-    "FREE": "gratuit",
-    "OF": "de",
-    "WATCHED_FOLDERS": "Voir les dossiers",
-    "NO_FOLDERS_ADDED": "Aucun dossiers d'ajouté!",
-    "FOLDERS_AUTOMATICALLY_MONITORED": "Les dossiers que vous ajoutez ici seront supervisés automatiquement",
-    "UPLOAD_NEW_FILES_TO_ENTE": "Charger de nouveaux fichiers sur ente",
-    "REMOVE_DELETED_FILES_FROM_ENTE": "Retirer de ente les fichiers supprimés",
-    "ADD_FOLDER": "Ajouter un dossier",
-    "STOP_WATCHING": "Arrêter de voir",
-    "STOP_WATCHING_FOLDER": "Arrêter de voir le dossier?",
-    "STOP_WATCHING_DIALOG_MESSAGE": "Vos fichiers existants ne seront pas supprimés, mais ente arrêtera automatiquement de mettre à jour le lien de l'album à chaque changements sur ce dossier.",
-    "YES_STOP": "Oui, arrêter",
-    "MONTH_SHORT": "mo",
-    "YEAR": "année",
-    "FAMILY_PLAN": "Plan famille",
-    "DOWNLOAD_LOGS": "Télécharger les logs",
-    "DOWNLOAD_LOGS_MESSAGE": "<p>Cela va télécharger les journaux de débug, que vous pourrez nosu envoyer par e-mail pour nous aider à résoudre votre problàme .</p><p>Veuillez noter que les noms de fichiers seront inclus .</p>",
-    "CHANGE_FOLDER": "Modifier le dossier",
-    "TWO_MONTHS_FREE": "Obtenir 2 mois gratuits sur les plans annuels",
-    "GB": "Go",
-    "POPULAR": "Populaire",
-    "FREE_PLAN_OPTION_LABEL": "Poursuivre avec la version d'essai gratuite",
-    "FREE_PLAN_DESCRIPTION": "1 Go pour 1 an",
-    "CURRENT_USAGE": "L'utilisation actuelle est de <strong>{{usage}}</strong>",
-    "WEAK_DEVICE": "Le navigateur que vous utilisez n'est pas assez puissant pour chiffrer vos photos. Veuillez essayer de vous connecter à ente sur votre ordinateur, ou télécharger l'appli ente mobile/ordinateur.",
-    "DRAG_AND_DROP_HINT": "Sinon glissez déposez dans la fenêtre ente",
-    "CONFIRM_ACCOUNT_DELETION_MESSAGE": "<p> Vos données chargées seront programmées pour suppression, et votre comptre sera supprimé définitivement .</p><p>Cette action n'est pas reversible.</p>",
-    "AUTHENTICATE": "Authentification",
-    "UPLOADED_TO_SINGLE_COLLECTION": "Chargé dans une seule collection",
-    "UPLOADED_TO_SEPARATE_COLLECTIONS": "Chargé dans des collections séparées",
-    "NEVERMIND": "Peu-importe",
-    "UPDATE_AVAILABLE": "Une mise à jour est disponible",
-    "UPDATE_INSTALLABLE_MESSAGE": "Une nouvelle version de ente est prête à être installée.",
-    "INSTALL_NOW": "Installer maintenant",
-    "INSTALL_ON_NEXT_LAUNCH": "Installer au prochain démarrage",
-    "UPDATE_AVAILABLE_MESSAGE": "Une nouvelle version de ente est sortie, mais elle ne peut pas être automatiquement téléchargée puis installée.",
-    "DOWNLOAD_AND_INSTALL": "Télécharger et installer",
-    "IGNORE_THIS_VERSION": "Ignorer cette version",
-    "TODAY": "Aujourd'hui",
-    "YESTERDAY": "Hier",
-    "NAME_PLACEHOLDER": "Nom...",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "Impossible de créer des albums depuis un mix fichier/dossier",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "<p>Vous avez glissé déposé un mélange de fichiers et dossiers.</p><p>Veuillez sélectionner soit uniquement des fichiers, ou des dossiers lors du choix d'options pour créer des albums séparés</p>",
-    "CHOSE_THEME": "Choisir un thème",
-    "ML_SEARCH": "ML search (beta)",
-    "ENABLE_ML_SEARCH_DESCRIPTION": "<p>Ceci activera l'apprentissage automatique sur l'appareil et la recherche faciale qui commencera à analyser vos photos chargées.</p><p>Pour la première exécution après la connexion ou l'activation de cette fonctionnalité, cela téléchargera toutes les images sur l'appareil local pour les analyser. Veuillez donc activer ceci uniquement si vous avez de la bande passante et le traitement local de toutes les images dans votre photothèque.</p><p>Si c'est la première fois que vous activez ceci, nous vous demanderons également la permission de traiter les données faciales.</p>",
-    "ML_MORE_DETAILS": "Plus de détails",
-    "ENABLE_FACE_SEARCH": "Activer la recherche faciale",
-    "ENABLE_FACE_SEARCH_TITLE": "Activer la recherche faciale ?",
-    "ENABLE_FACE_SEARCH_DESCRIPTION": "<p>If you enable face search, ente will extract face geometry from your photos. This will happen on your device, and any generated biometric data will be end-to-encrypted.</p><p><a>Please click here for more details about this feature in our privacy policy</a></p>",
-    "DISABLE_BETA": "Désactiver la bêta",
-    "DISABLE_FACE_SEARCH": "Désactiver la recherche faciale",
-    "DISABLE_FACE_SEARCH_TITLE": "Désactiver la recherche faciale ?",
-    "DISABLE_FACE_SEARCH_DESCRIPTION": "<p>ente will stop processing face geometry, and will also disable ML search (beta)</p><p>You can reenable face search again if you wish, so this operation is safe</p>",
-    "ADVANCED": "Avancé",
-    "FACE_SEARCH_CONFIRMATION": "Je comprends, et je souhaite permettre à ente de traiter la géométrie faciale",
-    "LABS": "Labs",
-    "YOURS": "Le vôtre",
-    "PASSPHRASE_STRENGTH_WEAK": "Sécurité du mot de passe : faible",
-    "PASSPHRASE_STRENGTH_MODERATE": "Sécurité du mot de passe : moyenne",
-    "PASSPHRASE_STRENGTH_STRONG": "Sécurité du mot de passe : forte",
-    "PREFERENCES": "Préférences",
-    "LANGUAGE": "Langue",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST": "Dossier d'export invalide",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "<p> Le dossier d'export que vous avez sélectionné n'existe pas </p><p>Veuillez sélectionner un dossier valide</p>",
-    "SUBSCRIPTION_VERIFICATION_ERROR": "Échec de la vérification de l'abonnement",
-    "STORAGE_UNITS": {
-        "B": "o",
-        "KB": "Ko",
-        "MB": "Mo",
-        "GB": "Go",
-        "TB": "To"
-    },
-    "AFTER_TIME": {
-        "HOUR": "dans une heure",
-        "DAY": "dans un jour",
-        "WEEK": "dans une semaine",
-        "MONTH": "dans un mois",
-        "YEAR": "dans un an"
-    },
-    "COPY_LINK": "Copier le lien",
-    "DONE": "Terminé",
-    "LINK_SHARE_TITLE": "Ou partager un lien",
-    "REMOVE_LINK": "Supprimer le lien",
-    "CREATE_PUBLIC_SHARING": "Créer un lien public",
-    "PUBLIC_LINK_CREATED": "Lien public créé",
-    "PUBLIC_LINK_ENABLED": "Lien public activé",
-    "COLLECT_PHOTOS": "Récupérer les photos",
-    "PUBLIC_COLLECT_SUBTEXT": "Autoriser les personnes ayant le lien d'ajouter des photos à l'album partagé.",
-    "STOP_EXPORT": "Stop",
-    "EXPORT_PROGRESS": "<a>{{progress.success}} / {{progress.total}}</a> fichiers exportés",
-    "MIGRATING_EXPORT": "Préparations...",
-    "RENAMING_COLLECTION_FOLDERS": "Renommage des dossiers de l'album en cours...",
-    "TRASHING_DELETED_FILES": "Mise à la corbeille des fichiers supprimés...",
-    "TRASHING_DELETED_COLLECTIONS": "Mise à la corbeille des albums supprimés...",
-    "EXPORT_NOTIFICATION": {
-        "START": "L'export a démarré",
-        "IN_PROGRESS": "Un export est déjà en cours",
-        "FINISH": "Export terminé",
-        "UP_TO_DATE": "Aucun nouveau fichier à exporter"
-    },
-    "CONTINUOUS_EXPORT": "Synchronisation en continu",
-    "TOTAL_ITEMS": "Total d'objets",
-    "PENDING_ITEMS": "Objets en attente",
-    "EXPORT_STARTING": "Démarrage de l'export...",
-    "DELETE_ACCOUNT_REASON_LABEL": "Quelle est la raison principale de la suppression de votre compte ?",
-    "DELETE_ACCOUNT_REASON_PLACEHOLDER": "Choisir une raison",
-    "DELETE_REASON": {
-        "MISSING_FEATURE": "Il manque une fonctionnalité essentielle dont j'ai besoin",
-        "BROKEN_BEHAVIOR": "L'application ou une certaine fonctionnalité ne se comporte pas comme je pense qu'elle devrait",
-        "FOUND_ANOTHER_SERVICE": "J'ai trouvé un autre service que je préfère",
-        "NOT_LISTED": "Ma raison n'est pas listée"
-    },
-    "DELETE_ACCOUNT_FEEDBACK_LABEL": "Nous sommes désolés de vous voir partir. Expliquez-nous les raisons de votre départ pour que nous puissions nous améliorer.",
-    "DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "Vos commentaires",
-    "CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "Oui, je veux supprimer définitivement ce compte et toutes ses données",
-    "CONFIRM_DELETE_ACCOUNT": "Confirmer la suppression du compte",
-    "FEEDBACK_REQUIRED": "Merci de nous aider avec cette information",
-    "FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "Qu'est-ce que l'autre service fait de mieux ?",
-    "RECOVER_TWO_FACTOR": "Récupérer la double-authentification",
-    "at": "à",
-    "AUTH_NEXT": "suivant",
-    "AUTH_DOWNLOAD_MOBILE_APP": "Téléchargez notre application mobile pour gérer vos secrets",
-    "HIDDEN": "Masqué",
-    "HIDE": "Masquer",
-    "UNHIDE": "Dévoiler",
-    "UNHIDE_TO_COLLECTION": "Afficher dans l'album",
-    "SORT_BY": "Trier par",
-    "NEWEST_FIRST": "Plus récent en premier",
-    "OLDEST_FIRST": "Plus ancien en premier",
-    "CONVERSION_FAILED_NOTIFICATION_MESSAGE": "Ce fichier n'a pas pu être aperçu. Cliquez ici pour télécharger l'original.",
-    "SELECT_COLLECTION": "Sélectionner album",
-    "PIN_ALBUM": "Épingler l'album",
-    "UNPIN_ALBUM": "Désépingler l'album",
-    "DOWNLOAD_COMPLETE": "Téléchargement terminé",
-    "DOWNLOADING_COLLECTION": "Téléchargement de {{name}}",
-    "DOWNLOAD_FAILED": "Échec du téléchargement",
-    "DOWNLOAD_PROGRESS": "{{progress.current}} / {{progress.total}} fichiers",
-    "CHRISTMAS": "Noël",
-    "CHRISTMAS_EVE": "Réveillon de Noël",
-    "NEW_YEAR": "Nouvel an",
-    "NEW_YEAR_EVE": "Réveillon de Nouvel An",
-    "IMAGE": "Image",
-    "VIDEO": "Vidéo",
-    "LIVE_PHOTO": "Photos en direct",
-    "CONVERT": "Convertir",
-    "CONFIRM_EDITOR_CLOSE_MESSAGE": "Êtes-vous sûr de vouloir fermer l'éditeur ?",
-    "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "Téléchargez votre image modifiée ou enregistrez une copie sur ente pour maintenir vos modifications.",
-    "BRIGHTNESS": "Luminosité",
-    "CONTRAST": "Contraste",
-    "SATURATION": "Saturation",
-    "BLUR": "Flou",
-    "INVERT_COLORS": "Inverser les couleurs",
-    "ASPECT_RATIO": "Ratio de l'image",
-    "SQUARE": "Carré",
-    "ROTATE_LEFT": "Pivoter vers la gauche",
-    "ROTATE_RIGHT": "Pivoter vers la droite",
-    "FLIP_VERTICALLY": "Basculer verticalement",
-    "FLIP_HORIZONTALLY": "Retourner horizontalement",
-    "DOWNLOAD_EDITED": "Téléchargement modifié",
-    "SAVE_A_COPY_TO_ENTE": "Enregistrer une copie dans ente",
-    "RESTORE_ORIGINAL": "Restaurer l'original",
-    "TRANSFORM": "Transformer",
-    "COLORS": "Couleurs",
-    "FLIP": "Retourner",
-    "ROTATION": "Rotation",
-    "RESET": "Réinitialiser",
-    "PHOTO_EDITOR": "Éditeur de photos",
-    "FASTER_UPLOAD": "Chargements plus rapides",
-    "FASTER_UPLOAD_DESCRIPTION": "Router les chargements vers les serveurs à proximité",
-    "MAGIC_SEARCH_STATUS": "Statut de la recherche magique",
-    "INDEXED_ITEMS": "Éléments indexés",
-    "CAST_ALBUM_TO_TV": "Jouer l'album sur la TV",
-    "ENTER_CAST_PIN_CODE": "Entrez le code que vous voyez sur la TV ci-dessous pour appairer cet appareil.",
-    "PAIR_DEVICE_TO_TV": "Associer les appareils",
-    "TV_NOT_FOUND": "TV introuvable. Avez-vous entré le code PIN correctement ?",
-    "AUTO_CAST_PAIR": "Paire automatique",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "La paire automatique nécessite la connexion aux serveurs Google et ne fonctionne qu'avec les appareils pris en charge par Chromecast. Google ne recevra pas de données sensibles, telles que vos photos.",
-    "PAIR_WITH_PIN": "Associer avec le code PIN",
-    "CHOOSE_DEVICE_FROM_BROWSER": "Choisissez un périphérique compatible avec la caste à partir de la fenêtre pop-up du navigateur.",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "L'association avec le code PIN fonctionne pour tout appareil grand écran sur lequel vous voulez lire votre album.",
-    "VISIT_CAST_ENTE_IO": "Visitez cast.ente.io sur l'appareil que vous voulez associer.",
-    "CAST_AUTO_PAIR_FAILED": "La paire automatique de Chromecast a échoué. Veuillez réessayer.",
-    "CACHE_DIRECTORY": "Dossier du cache",
-    "FREEHAND": "Main levée",
-    "APPLY_CROP": "Appliquer le recadrage",
-    "PHOTO_EDIT_REQUIRED_TO_SAVE": "Au moins une transformation ou un ajustement de couleur doit être effectué avant de sauvegarder.",
-    "PASSKEYS": "Clés d'accès",
-    "DELETE_PASSKEY": "",
-    "DELETE_PASSKEY_CONFIRMATION": "",
-    "RENAME_PASSKEY": "",
-    "ADD_PASSKEY": "",
-    "ENTER_PASSKEY_NAME": "",
-    "PASSKEYS_DESCRIPTION": "",
-    "CREATED_AT": "",
-    "PASSKEY_LOGIN_FAILED": "",
-    "PASSKEY_LOGIN_URL_INVALID": "",
-    "PASSKEY_LOGIN_ERRORED": "",
-    "TRY_AGAIN": "",
-    "PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER": "",
-    "LOGIN_WITH_PASSKEY": ""
-}

+ 0 - 654
web/apps/accounts/public/locales/it-IT/translation.json

@@ -1,654 +0,0 @@
-{
-    "HERO_SLIDE_1_TITLE": "<div>Backup privati</div><div>dei tuoi ricordi</div>",
-    "HERO_SLIDE_1": "Crittografia end-to-end",
-    "HERO_SLIDE_2_TITLE": "<div>Salvati in modo sicuro</div><div>in un rifugio antiatomico</div>",
-    "HERO_SLIDE_2": "Progettato per sopravvivere",
-    "HERO_SLIDE_3_TITLE": "<div>Disponibile</div><div> ovunque</div>",
-    "HERO_SLIDE_3": "Android, iOS, Web, Desktop",
-    "LOGIN": "Accedi",
-    "SIGN_UP": "Registrati",
-    "NEW_USER": "Nuovo utente",
-    "EXISTING_USER": "Accedi",
-    "ENTER_NAME": "Inserisci il nome",
-    "PUBLIC_UPLOADER_NAME_MESSAGE": "Aggiungi un nome in modo che i tuoi amici sappiano chi ringraziare per queste fantastiche foto!",
-    "ENTER_EMAIL": "Inserisci l'indirizzo email",
-    "EMAIL_ERROR": "Inserisci un indirizzo email valido",
-    "REQUIRED": "Campo obbligatorio",
-    "EMAIL_SENT": "Codice di verifica inviato a <a>{{email}}</a>",
-    "CHECK_INBOX": "Controlla la tua casella di posta (e lo spam) per completare la verifica",
-    "ENTER_OTT": "Codice di verifica",
-    "RESEND_MAIL": "Reinvia codice",
-    "VERIFY": "Verifica",
-    "UNKNOWN_ERROR": "Qualcosa è andato storto, per favore riprova",
-    "INVALID_CODE": "Codice di verifica non valido",
-    "EXPIRED_CODE": "Il tuo codice di verifica è scaduto",
-    "SENDING": "Invio in corso...",
-    "SENT": "Inviato!",
-    "PASSWORD": "Password",
-    "LINK_PASSWORD": "Inserisci la password per sbloccare l'album",
-    "RETURN_PASSPHRASE_HINT": "Password",
-    "SET_PASSPHRASE": "Imposta una password",
-    "VERIFY_PASSPHRASE": "Accedi",
-    "INCORRECT_PASSPHRASE": "Password sbagliata",
-    "ENTER_ENC_PASSPHRASE": "Inserisci una password per crittografare i tuoi dati",
-    "PASSPHRASE_DISCLAIMER": "Non memorizziamo la tua password, quindi se la dimentichi, <strong>non saremo in grado di aiutarti </strong>a recuperare i tuoi dati senza una chiave di recupero.",
-    "WELCOME_TO_ENTE_HEADING": "Benvenuto su <a/>",
-    "WELCOME_TO_ENTE_SUBHEADING": "Archiviazione e condivisione di foto crittografate end-to-end",
-    "WHERE_YOUR_BEST_PHOTOS_LIVE": "Dove vivono le tue migliori foto",
-    "KEY_GENERATION_IN_PROGRESS_MESSAGE": "Generazione delle chiavi di crittografia...",
-    "PASSPHRASE_HINT": "Password",
-    "CONFIRM_PASSPHRASE": "Conferma la password",
-    "REFERRAL_CODE_HINT": "Come hai conosciuto Ente? (opzionale)",
-    "REFERRAL_INFO": "",
-    "PASSPHRASE_MATCH_ERROR": "Le password non corrispondono",
-    "CREATE_COLLECTION": "Nuovo album",
-    "ENTER_ALBUM_NAME": "Nome album",
-    "CLOSE_OPTION": "Chiudi (Esc)",
-    "ENTER_FILE_NAME": "Nome del file",
-    "CLOSE": "Chiudi",
-    "NO": "No",
-    "NOTHING_HERE": "Nulla da vedere qui! 👀",
-    "UPLOAD": "Carica",
-    "IMPORT": "Importa",
-    "ADD_PHOTOS": "Aggiungi foto",
-    "ADD_MORE_PHOTOS": "Aggiungi altre foto",
-    "add_photos_one": "Aggiungi elemento",
-    "add_photos_other": "Aggiungi {{count, number}} elementi",
-    "SELECT_PHOTOS": "Seleziona foto",
-    "FILE_UPLOAD": "Carica file",
-    "UPLOAD_STAGE_MESSAGE": {
-        "0": "Preparazione all'upload",
-        "1": "Lettura dei file metadati di google",
-        "2": "{{uploadCounter.finished, number}} / {{uploadCounter.total, number}} file metadati estratti",
-        "3": "{{uploadCounter.finished, number}} / {{uploadCounter.total, number}} file salvati",
-        "4": "Annullamento dei caricamenti rimanenti",
-        "5": "Backup completato"
-    },
-    "FILE_NOT_UPLOADED_LIST": "I seguenti file non sono stati caricati",
-    "SUBSCRIPTION_EXPIRED": "Abbonamento scaduto",
-    "SUBSCRIPTION_EXPIRED_MESSAGE": "Il tuo abbonamento è scaduto, per favore <a>rinnova</a>",
-    "STORAGE_QUOTA_EXCEEDED": "Limite d'archiviazione superato",
-    "INITIAL_LOAD_DELAY_WARNING": "Il primo caricamento potrebbe richiedere del tempo",
-    "USER_DOES_NOT_EXIST": "Purtroppo non abbiamo trovato nessun account con quell'indirizzo e-mail",
-    "NO_ACCOUNT": "Non ho un account",
-    "ACCOUNT_EXISTS": "Ho già un account",
-    "CREATE": "Crea",
-    "DOWNLOAD": "Scarica",
-    "DOWNLOAD_OPTION": "Scarica (D)",
-    "DOWNLOAD_FAVORITES": "Scarica i preferiti",
-    "DOWNLOAD_UNCATEGORIZED": "Scarica i file senza categoria",
-    "DOWNLOAD_HIDDEN_ITEMS": "Scarica gli elementi nascosti",
-    "COPY_OPTION": "Copia come PNG (Ctrl/Cmd - C)",
-    "TOGGLE_FULLSCREEN": "Attiva/disattiva schermo intero (F)",
-    "ZOOM_IN_OUT": "Zoom in/out",
-    "PREVIOUS": "Precedente (←)",
-    "NEXT": "Successivo (→)",
-    "TITLE_PHOTOS": "",
-    "TITLE_ALBUMS": "",
-    "TITLE_AUTH": "",
-    "UPLOAD_FIRST_PHOTO": "Carica la tua prima foto",
-    "IMPORT_YOUR_FOLDERS": "Importa una cartella",
-    "UPLOAD_DROPZONE_MESSAGE": "Rilascia per eseguire il backup dei file",
-    "WATCH_FOLDER_DROPZONE_MESSAGE": "Rilascia per aggiungere la cartella osservata",
-    "TRASH_FILES_TITLE": "Elimina file?",
-    "TRASH_FILE_TITLE": "Eliminare il file?",
-    "DELETE_FILES_TITLE": "Eliminare immediatamente?",
-    "DELETE_FILES_MESSAGE": "I file selezionati verranno eliminati definitivamente dal tuo account ente.",
-    "DELETE": "Cancella",
-    "DELETE_OPTION": "Cancella (DEL)",
-    "FAVORITE_OPTION": "Preferito (L)",
-    "UNFAVORITE_OPTION": "Rimuovi dai preferiti (L)",
-    "MULTI_FOLDER_UPLOAD": "Selezionate più cartelle",
-    "UPLOAD_STRATEGY_CHOICE": "Vuoi caricarli in",
-    "UPLOAD_STRATEGY_SINGLE_COLLECTION": "Un album singolo",
-    "OR": "o",
-    "UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "Album separati",
-    "SESSION_EXPIRED_MESSAGE": "La sessione è scaduta. Per continuare, esegui nuovamente l'accesso",
-    "SESSION_EXPIRED": "Sessione scaduta",
-    "PASSWORD_GENERATION_FAILED": "Il tuo browser non è stato in grado di generare una chiave forte che soddisfa gli standard di crittografia ente, prova ad usare l'app per dispositivi mobili o un altro browser",
-    "CHANGE_PASSWORD": "Cambia password",
-    "GO_BACK": "Torna indietro",
-    "RECOVERY_KEY": "Chiave di recupero",
-    "SAVE_LATER": "Fallo più tardi",
-    "SAVE": "Salva Chiave",
-    "RECOVERY_KEY_DESCRIPTION": "Se dimentichi la tua password, l'unico modo per recuperare i tuoi dati è con questa chiave.",
-    "RECOVER_KEY_GENERATION_FAILED": "Impossibile generare il codice di recupero, riprova",
-    "KEY_NOT_STORED_DISCLAIMER": "Non memorizziamo questa chiave, quindi salvala in un luogo sicuro",
-    "FORGOT_PASSWORD": "Password dimenticata",
-    "RECOVER_ACCOUNT": "Recupera account",
-    "RECOVERY_KEY_HINT": "Chiave di recupero",
-    "RECOVER": "Recupera",
-    "NO_RECOVERY_KEY": "Nessuna chiave di recupero?",
-    "INCORRECT_RECOVERY_KEY": "Chiave di recupero errata",
-    "SORRY": "Siamo spiacenti",
-    "NO_RECOVERY_KEY_MESSAGE": "A causa della natura del nostro protocollo di crittografia end-to-end, i tuoi dati non possono essere decifrati senza la tua password o chiave di ripristino",
-    "NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "Per favore invia un'email a <a>{{emailID}}</a> dal tuo indirizzo email registrato",
-    "CONTACT_SUPPORT": "Contatta il supporto",
-    "REQUEST_FEATURE": "Richiedi una funzionalità",
-    "SUPPORT": "Supporto",
-    "CONFIRM": "Conferma",
-    "CANCEL": "Annulla",
-    "LOGOUT": "Disconnettiti",
-    "DELETE_ACCOUNT": "Elimina account",
-    "DELETE_ACCOUNT_MESSAGE": "<p>Per favore invia una email a <a>{{emailID}}</a> dal tuo indirizzo email registrato.</p><p>La tua richiesta verrà elaborata entro 72 ore.</p>",
-    "LOGOUT_MESSAGE": "Sei sicuro di volerti disconnettere?",
-    "CHANGE_EMAIL": "Cambia email",
-    "OK": "OK",
-    "SUCCESS": "Operazione riuscita",
-    "ERROR": "Errore",
-    "MESSAGE": "Messaggio",
-    "INSTALL_MOBILE_APP": "Installa la nostra app <a>Android</a> o <b>iOS</b> per eseguire il backup automatico di tutte le tue foto",
-    "DOWNLOAD_APP_MESSAGE": "Siamo spiacenti, questa operazione è attualmente supportata solo sulla nostra app desktop",
-    "DOWNLOAD_APP": "Scarica l'app per desktop",
-    "EXPORT": "Esporta Dati",
-    "SUBSCRIPTION": "Abbonamento",
-    "SUBSCRIBE": "Iscriviti",
-    "MANAGEMENT_PORTAL": "Gestisci i metodi di pagamento",
-    "MANAGE_FAMILY_PORTAL": "Gestisci piano famiglia",
-    "LEAVE_FAMILY_PLAN": "Abbandona il piano famiglia",
-    "LEAVE": "Lascia",
-    "LEAVE_FAMILY_CONFIRM": "Sei sicuro di voler uscire dal piano famiglia?",
-    "CHOOSE_PLAN": "Scegli il tuo piano",
-    "MANAGE_PLAN": "Gestisci il tuo abbonamento",
-    "ACTIVE": "Attivo",
-    "OFFLINE_MSG": "Sei offline, i ricordi memorizzati nella cache vengono mostrati",
-    "FREE_SUBSCRIPTION_INFO": "Sei sul piano <strong>gratuito</strong> che scade il {{date, dateTime}}",
-    "FAMILY_SUBSCRIPTION_INFO": "Fai parte di un piano famiglia gestito da",
-    "RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "Si rinnova il {{date, dateTime}}",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "Termina il {{date, dateTime}}",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "Il tuo abbonamento verrà annullato il {{date, dateTime}}",
-    "ADD_ON_AVAILABLE_TILL": "",
-    "STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "Hai superato la quota di archiviazione assegnata, si prega di aggiornare <a></a>",
-    "SUBSCRIPTION_PURCHASE_SUCCESS": "<p>Abbiamo ricevuto il tuo pagamento</p><p>Il tuo abbonamento è valido fino a <strong>{{date, dateTime}}</strong></p>",
-    "SUBSCRIPTION_PURCHASE_CANCELLED": "Il tuo acquisto è stato annullato, riprova se vuoi iscriverti",
-    "SUBSCRIPTION_PURCHASE_FAILED": "Acquisto abbonamento non riuscito, riprova",
-    "SUBSCRIPTION_UPDATE_FAILED": "L'aggiornamento dell'abbonamento non è riuscito, riprova",
-    "UPDATE_PAYMENT_METHOD_MESSAGE": "Siamo spiacenti, il pagamento non è andato a buon fine quando abbiamo provato ad addebitare alla sua carta, la preghiamo di aggiornare il suo metodo di pagamento e riprovare",
-    "STRIPE_AUTHENTICATION_FAILED": "Non siamo in grado di autenticare il tuo metodo di pagamento. Per favore scegli un metodo di pagamento diverso e riprova",
-    "UPDATE_PAYMENT_METHOD": "Aggiorna metodo di pagamento",
-    "MONTHLY": "Mensile",
-    "YEARLY": "Annuale",
-    "UPDATE_SUBSCRIPTION_MESSAGE": "Sei sicuro di voler cambiare il piano?",
-    "UPDATE_SUBSCRIPTION": "Cambia piano",
-    "CANCEL_SUBSCRIPTION": "Annulla abbonamento",
-    "CANCEL_SUBSCRIPTION_MESSAGE": "<p>Tutti i tuoi dati saranno cancellati dai nostri server alla fine di questo periodo di fatturazione.</p><p>Sei sicuro di voler annullare il tuo abbonamento?</p>",
-    "CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "",
-    "SUBSCRIPTION_CANCEL_FAILED": "Impossibile annullare l'abbonamento",
-    "SUBSCRIPTION_CANCEL_SUCCESS": "Abbonamento annullato con successo",
-    "REACTIVATE_SUBSCRIPTION": "Riattiva abbonamento",
-    "REACTIVATE_SUBSCRIPTION_MESSAGE": "Una volta riattivato, ti verrà addebitato il valore di {{date, dateTime}}",
-    "SUBSCRIPTION_ACTIVATE_SUCCESS": "Iscrizione attivata con successo ",
-    "SUBSCRIPTION_ACTIVATE_FAILED": "",
-    "SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "Grazie",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE": "Annulla abbonamento mobile",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "",
-    "MAIL_TO_MANAGE_SUBSCRIPTION": "Per favore contattaci su <a>{{emailID}}</a> per gestire il tuo abbonamento",
-    "RENAME": "Rinomina",
-    "RENAME_FILE": "Rinomina file",
-    "RENAME_COLLECTION": "Rinomina album",
-    "DELETE_COLLECTION_TITLE": "Eliminare l'album?",
-    "DELETE_COLLECTION": "Elimina album",
-    "DELETE_COLLECTION_MESSAGE": "",
-    "DELETE_PHOTOS": "Elimina foto",
-    "KEEP_PHOTOS": "Mantieni foto",
-    "SHARE": "Condividi",
-    "SHARE_COLLECTION": "Condividi album",
-    "SHAREES": "Condividi con",
-    "SHARE_WITH_SELF": "Ops, non puoi condividere a te stesso",
-    "ALREADY_SHARED": "Ops, lo stai già condividendo con {{email}}",
-    "SHARING_BAD_REQUEST_ERROR": "Condividere gli album non è consentito",
-    "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "La condivisione è disabilitata per gli account free",
-    "DOWNLOAD_COLLECTION": "Scarica album",
-    "DOWNLOAD_COLLECTION_MESSAGE": "<p>Sei sicuro di volere scaricare l'album interamente?</p><p>Tutti i file saranno messi in coda per il download</p>",
-    "CREATE_ALBUM_FAILED": "Operazione di creazione dell'album fallita, per favore riprova",
-    "SEARCH": "Ricerca",
-    "SEARCH_RESULTS": "Risultati della ricerca",
-    "NO_RESULTS": "",
-    "SEARCH_HINT": "",
-    "SEARCH_TYPE": {
-        "COLLECTION": "Album",
-        "LOCATION": "Posizione",
-        "CITY": "Posizione",
-        "DATE": "Data",
-        "FILE_NAME": "Nome file",
-        "THING": "Contenuto",
-        "FILE_CAPTION": "Descrizione",
-        "FILE_TYPE": "Tipo del file",
-        "CLIP": ""
-    },
-    "photos_count_zero": "Nessuna memoria",
-    "photos_count_one": "",
-    "photos_count_other": "",
-    "TERMS_AND_CONDITIONS": "",
-    "ADD_TO_COLLECTION": "Aggiungi all'album",
-    "SELECTED": "",
-    "VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "Questo video non può essere riprodotto nel tuo browser",
-    "PEOPLE": "Persone",
-    "INDEXING_SCHEDULED": "",
-    "ANALYZING_PHOTOS": "",
-    "INDEXING_PEOPLE": "",
-    "INDEXING_DONE": "",
-    "UNIDENTIFIED_FACES": "volti non identificati",
-    "OBJECTS": "",
-    "TEXT": "testo",
-    "INFO": "Info ",
-    "INFO_OPTION": "",
-    "FILE_NAME": "Nome file",
-    "CAPTION_PLACEHOLDER": "Aggiungi una descrizione",
-    "LOCATION": "Posizione",
-    "SHOW_ON_MAP": "Guarda su OpenStreetMap",
-    "MAP": "Mappa",
-    "MAP_SETTINGS": "Impostazioni Mappa",
-    "ENABLE_MAPS": "Attivare Mappa?",
-    "ENABLE_MAP": "Attivare mappa",
-    "DISABLE_MAPS": "Disattivare Mappa?",
-    "ENABLE_MAP_DESCRIPTION": "",
-    "DISABLE_MAP_DESCRIPTION": "",
-    "DISABLE_MAP": "",
-    "DETAILS": "",
-    "VIEW_EXIF": "",
-    "NO_EXIF": "",
-    "EXIF": "EXIF",
-    "ISO": "ISO",
-    "TWO_FACTOR": "Due fattori",
-    "TWO_FACTOR_AUTHENTICATION": "Autenticazione a due fattori",
-    "TWO_FACTOR_QR_INSTRUCTION": "Scansiona il codice QR qui sotto con la tua app di autenticazione preferita",
-    "ENTER_CODE_MANUALLY": "Inserisci il codice manualmente",
-    "TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "Inserisci questo codice nella tua app di autenticazione preferita",
-    "SCAN_QR_CODE": "Oppure scansiona il codice QR",
-    "ENABLE_TWO_FACTOR": "Attiva due fattori",
-    "ENABLE": "Attiva",
-    "LOST_DEVICE": "",
-    "INCORRECT_CODE": "Codice errato",
-    "TWO_FACTOR_INFO": "Aggiungi un ulteriore livello di sicurezza richiedendo più informazioni rispetto a email e password per eseguire l'accesso al tuo account",
-    "DISABLE_TWO_FACTOR_LABEL": "",
-    "UPDATE_TWO_FACTOR_LABEL": "",
-    "DISABLE": "",
-    "RECONFIGURE": "",
-    "UPDATE_TWO_FACTOR": "",
-    "UPDATE_TWO_FACTOR_MESSAGE": "",
-    "UPDATE": "",
-    "DISABLE_TWO_FACTOR": "",
-    "DISABLE_TWO_FACTOR_MESSAGE": "",
-    "TWO_FACTOR_DISABLE_FAILED": "",
-    "EXPORT_DATA": "Esporta dati",
-    "SELECT_FOLDER": "",
-    "DESTINATION": "",
-    "START": "",
-    "LAST_EXPORT_TIME": "",
-    "EXPORT_AGAIN": "",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE": "",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "",
-    "SEND_OTT": "Invia OTP",
-    "EMAIl_ALREADY_OWNED": "Email già in uso",
-    "ETAGS_BLOCKED": "",
-    "SKIPPED_VIDEOS_INFO": "",
-    "LIVE_PHOTOS_DETECTED": "",
-    "RETRY_FAILED": "",
-    "FAILED_UPLOADS": "Caricamento fallito ",
-    "SKIPPED_FILES": "Ignora caricamenti",
-    "THUMBNAIL_GENERATION_FAILED_UPLOADS": "",
-    "UNSUPPORTED_FILES": "",
-    "SUCCESSFUL_UPLOADS": "Caricamenti eseguiti con successo",
-    "SKIPPED_INFO": "",
-    "UNSUPPORTED_INFO": "",
-    "BLOCKED_UPLOADS": "",
-    "SKIPPED_VIDEOS": "Video saltati",
-    "INPROGRESS_METADATA_EXTRACTION": "In corso",
-    "INPROGRESS_UPLOADS": "Caricamenti in corso",
-    "TOO_LARGE_UPLOADS": "File pesanti",
-    "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "Spazio insufficiente",
-    "LARGER_THAN_AVAILABLE_STORAGE_INFO": "Questi file non sono stati caricati perché supererebbero la capacità massima del tuo piano di spazio d'archiviazione",
-    "TOO_LARGE_INFO": "Questi file non sono stati caricati perché superano il nostro limite di pesantezza di un file",
-    "THUMBNAIL_GENERATION_FAILED_INFO": "",
-    "UPLOAD_TO_COLLECTION": "",
-    "UNCATEGORIZED": "",
-    "ARCHIVE": "Archivio",
-    "FAVORITES": "Preferiti",
-    "ARCHIVE_COLLECTION": "Album archiviato",
-    "ARCHIVE_SECTION_NAME": "Archivio",
-    "ALL_SECTION_NAME": "Tutto",
-    "MOVE_TO_COLLECTION": "Sposta nell'album",
-    "UNARCHIVE": "Rimuovi dall'archivio",
-    "UNARCHIVE_COLLECTION": "Rimuovi album dall'archivio",
-    "HIDE_COLLECTION": "Nascondi album",
-    "UNHIDE_COLLECTION": "Rimuovi album dai nascosti",
-    "MOVE": "Sposta",
-    "ADD": "Aggiungi",
-    "REMOVE": "Rimuovi",
-    "YES_REMOVE": "Sì, rimuovi",
-    "REMOVE_FROM_COLLECTION": "Rimuovi dall'album",
-    "TRASH": "Cestino",
-    "MOVE_TO_TRASH": "Sposta nel cestino",
-    "TRASH_FILES_MESSAGE": "Gli elementi selezionati verranno eliminati da tutti gli album e spostati nel cestino.",
-    "TRASH_FILE_MESSAGE": "Il file verrà eliminato da tutti gli album e spostato nel cestino.",
-    "DELETE_PERMANENTLY": "Elimina definitivamente",
-    "RESTORE": "Ripristina",
-    "RESTORE_TO_COLLECTION": "Ripristina nell'album",
-    "EMPTY_TRASH": "Svuota il cestino",
-    "EMPTY_TRASH_TITLE": "Vuoi svuotare il cestino?",
-    "EMPTY_TRASH_MESSAGE": "I file selezionati verranno eliminati definitivamente dal tuo account ente.",
-    "LEAVE_SHARED_ALBUM": "Sì, esci",
-    "LEAVE_ALBUM": "Abbandona l'album",
-    "LEAVE_SHARED_ALBUM_TITLE": "Abbandonare l'album condiviso?",
-    "LEAVE_SHARED_ALBUM_MESSAGE": "",
-    "NOT_FILE_OWNER": "",
-    "CONFIRM_SELF_REMOVE_MESSAGE": "",
-    "CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "",
-    "SORT_BY_CREATION_TIME_ASCENDING": "Meno recente",
-    "SORT_BY_UPDATION_TIME_DESCENDING": "Ultimo aggiornamento",
-    "SORT_BY_NAME": "Nome",
-    "COMPRESS_THUMBNAILS": "Comprimi miniature",
-    "THUMBNAIL_REPLACED": "Miniature compresse",
-    "FIX_THUMBNAIL": "Comprimi",
-    "FIX_THUMBNAIL_LATER": "Comprimi più tardi",
-    "REPLACE_THUMBNAIL_NOT_STARTED": "",
-    "REPLACE_THUMBNAIL_COMPLETED": "",
-    "REPLACE_THUMBNAIL_NOOP": "",
-    "REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR": "",
-    "FIX_CREATION_TIME": "",
-    "FIX_CREATION_TIME_IN_PROGRESS": "",
-    "CREATION_TIME_UPDATED": "",
-    "UPDATE_CREATION_TIME_NOT_STARTED": "",
-    "UPDATE_CREATION_TIME_COMPLETED": "",
-    "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "",
-    "CAPTION_CHARACTER_LIMIT": "",
-    "DATE_TIME_ORIGINAL": "",
-    "DATE_TIME_DIGITIZED": "",
-    "METADATA_DATE": "",
-    "CUSTOM_TIME": "",
-    "REOPEN_PLAN_SELECTOR_MODAL": "",
-    "OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
-    "INSTALL": "Installa",
-    "SHARING_DETAILS": "",
-    "MODIFY_SHARING": "",
-    "ADD_COLLABORATORS": "",
-    "ADD_NEW_EMAIL": "",
-    "shared_with_people_zero": "",
-    "shared_with_people_one": "",
-    "shared_with_people_other": "",
-    "participants_zero": "Nessun partecipante",
-    "participants_one": "1 partecipante",
-    "participants_other": "{{count, number}} partecipanti",
-    "ADD_VIEWERS": "",
-    "PARTICIPANTS": "Partecipanti",
-    "CHANGE_PERMISSIONS_TO_VIEWER": "",
-    "CHANGE_PERMISSIONS_TO_COLLABORATOR": "",
-    "CONVERT_TO_VIEWER": "",
-    "CONVERT_TO_COLLABORATOR": "",
-    "CHANGE_PERMISSION": "",
-    "REMOVE_PARTICIPANT": "Rimuovere?",
-    "CONFIRM_REMOVE": "Sì, rimuovi",
-    "MANAGE": "Gestisci",
-    "ADDED_AS": "Aggiunto come",
-    "COLLABORATOR_RIGHTS": "",
-    "REMOVE_PARTICIPANT_HEAD": "Rimuovi partecipante",
-    "OWNER": "",
-    "COLLABORATORS": "",
-    "ADD_MORE": "",
-    "VIEWERS": "",
-    "OR_ADD_EXISTING": "",
-    "REMOVE_PARTICIPANT_MESSAGE": "",
-    "NOT_FOUND": "404 - non trovato",
-    "LINK_EXPIRED": "Link scaduto",
-    "LINK_EXPIRED_MESSAGE": "",
-    "MANAGE_LINK": "",
-    "LINK_TOO_MANY_REQUESTS": "",
-    "FILE_DOWNLOAD": "",
-    "LINK_PASSWORD_LOCK": "",
-    "PUBLIC_COLLECT": "",
-    "LINK_DEVICE_LIMIT": "",
-    "NO_DEVICE_LIMIT": "",
-    "LINK_EXPIRY": "",
-    "NEVER": "",
-    "DISABLE_FILE_DOWNLOAD": "",
-    "DISABLE_FILE_DOWNLOAD_MESSAGE": "",
-    "MALICIOUS_CONTENT": "",
-    "COPYRIGHT": "",
-    "SHARED_USING": "",
-    "ENTE_IO": "ente.io",
-    "SHARING_REFERRAL_CODE": "",
-    "LIVE": "",
-    "DISABLE_PASSWORD": "",
-    "DISABLE_PASSWORD_MESSAGE": "",
-    "PASSWORD_LOCK": "",
-    "LOCK": "",
-    "DOWNLOAD_UPLOAD_LOGS": "",
-    "UPLOAD_FILES": "",
-    "UPLOAD_DIRS": "Cartella",
-    "UPLOAD_GOOGLE_TAKEOUT": "",
-    "DEDUPLICATE_FILES": "",
-    "AUTHENTICATOR_SECTION": "",
-    "NO_DUPLICATES_FOUND": "",
-    "CLUB_BY_CAPTURE_TIME": "",
-    "FILES": "",
-    "EACH": "",
-    "DEDUPLICATE_BASED_ON_SIZE": "",
-    "STOP_ALL_UPLOADS_MESSAGE": "",
-    "STOP_UPLOADS_HEADER": "",
-    "YES_STOP_UPLOADS": "",
-    "STOP_DOWNLOADS_HEADER": "",
-    "YES_STOP_DOWNLOADS": "",
-    "STOP_ALL_DOWNLOADS_MESSAGE": "",
-    "albums_one": "1 Album",
-    "albums_other": "{{count, number}} Album",
-    "ALL_ALBUMS": "Tutti gli Album",
-    "ALBUMS": "Album",
-    "ALL_HIDDEN_ALBUMS": "",
-    "HIDDEN_ALBUMS": "",
-    "HIDDEN_ITEMS": "",
-    "HIDDEN_ITEMS_SECTION_NAME": "",
-    "ENTER_TWO_FACTOR_OTP": "",
-    "CREATE_ACCOUNT": "Crea account",
-    "COPIED": "",
-    "CANVAS_BLOCKED_TITLE": "",
-    "CANVAS_BLOCKED_MESSAGE": "",
-    "WATCH_FOLDERS": "",
-    "UPGRADE_NOW": "",
-    "RENEW_NOW": "",
-    "STORAGE": "",
-    "USED": "",
-    "YOU": "Tu",
-    "FAMILY": "Famiglia",
-    "FREE": "gratis",
-    "OF": "",
-    "WATCHED_FOLDERS": "",
-    "NO_FOLDERS_ADDED": "Ancora nessuna cartella aggiunta!",
-    "FOLDERS_AUTOMATICALLY_MONITORED": "",
-    "UPLOAD_NEW_FILES_TO_ENTE": "",
-    "REMOVE_DELETED_FILES_FROM_ENTE": "",
-    "ADD_FOLDER": "",
-    "STOP_WATCHING": "",
-    "STOP_WATCHING_FOLDER": "",
-    "STOP_WATCHING_DIALOG_MESSAGE": "",
-    "YES_STOP": "",
-    "MONTH_SHORT": "",
-    "YEAR": "",
-    "FAMILY_PLAN": "",
-    "DOWNLOAD_LOGS": "",
-    "DOWNLOAD_LOGS_MESSAGE": "",
-    "CHANGE_FOLDER": "Cambia Cartella",
-    "TWO_MONTHS_FREE": "Ottieni 2 mesi gratis sui piani annuali",
-    "GB": "GB",
-    "POPULAR": "",
-    "FREE_PLAN_OPTION_LABEL": "",
-    "FREE_PLAN_DESCRIPTION": "1 GB per 1 anno",
-    "CURRENT_USAGE": "",
-    "WEAK_DEVICE": "",
-    "DRAG_AND_DROP_HINT": "",
-    "CONFIRM_ACCOUNT_DELETION_MESSAGE": "",
-    "AUTHENTICATE": "Autenticati",
-    "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": "Oggi",
-    "YESTERDAY": "Ieri",
-    "NAME_PLACEHOLDER": "Nome...",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "",
-    "CHOSE_THEME": "Seleziona tema",
-    "ML_SEARCH": "",
-    "ENABLE_ML_SEARCH_DESCRIPTION": "",
-    "ML_MORE_DETAILS": "Più dettagli",
-    "ENABLE_FACE_SEARCH": "",
-    "ENABLE_FACE_SEARCH_TITLE": "",
-    "ENABLE_FACE_SEARCH_DESCRIPTION": "",
-    "DISABLE_BETA": "",
-    "DISABLE_FACE_SEARCH": "",
-    "DISABLE_FACE_SEARCH_TITLE": "",
-    "DISABLE_FACE_SEARCH_DESCRIPTION": "",
-    "ADVANCED": "Avanzate",
-    "FACE_SEARCH_CONFIRMATION": "",
-    "LABS": "",
-    "YOURS": "",
-    "PASSPHRASE_STRENGTH_WEAK": "Sicurezza password: Debole",
-    "PASSPHRASE_STRENGTH_MODERATE": "Sicurezza password: Moderata",
-    "PASSPHRASE_STRENGTH_STRONG": "Sicurezza password: Forte",
-    "PREFERENCES": "",
-    "LANGUAGE": "Lingua",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST": "",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "",
-    "SUBSCRIPTION_VERIFICATION_ERROR": "",
-    "STORAGE_UNITS": {
-        "B": "B",
-        "KB": "KB",
-        "MB": "MB",
-        "GB": "GB",
-        "TB": "TB"
-    },
-    "AFTER_TIME": {
-        "HOUR": "dopo un'ora",
-        "DAY": "dopo un giorno",
-        "WEEK": "dopo una settimana",
-        "MONTH": "dopo un mese",
-        "YEAR": "dopo un anno"
-    },
-    "COPY_LINK": "Copia link",
-    "DONE": "Fatto",
-    "LINK_SHARE_TITLE": "O condividi un link",
-    "REMOVE_LINK": "Rimuovi link",
-    "CREATE_PUBLIC_SHARING": "Crea link pubblico",
-    "PUBLIC_LINK_CREATED": "Link pubblick creato",
-    "PUBLIC_LINK_ENABLED": "Link pubblico attivato",
-    "COLLECT_PHOTOS": "",
-    "PUBLIC_COLLECT_SUBTEXT": "",
-    "STOP_EXPORT": "",
-    "EXPORT_PROGRESS": "",
-    "MIGRATING_EXPORT": "",
-    "RENAMING_COLLECTION_FOLDERS": "",
-    "TRASHING_DELETED_FILES": "",
-    "TRASHING_DELETED_COLLECTIONS": "",
-    "EXPORT_NOTIFICATION": {
-        "START": "",
-        "IN_PROGRESS": "",
-        "FINISH": "",
-        "UP_TO_DATE": ""
-    },
-    "CONTINUOUS_EXPORT": "",
-    "TOTAL_ITEMS": "",
-    "PENDING_ITEMS": "",
-    "EXPORT_STARTING": "",
-    "DELETE_ACCOUNT_REASON_LABEL": "",
-    "DELETE_ACCOUNT_REASON_PLACEHOLDER": "Seleziona un motivo",
-    "DELETE_REASON": {
-        "MISSING_FEATURE": "",
-        "BROKEN_BEHAVIOR": "",
-        "FOUND_ANOTHER_SERVICE": "",
-        "NOT_LISTED": ""
-    },
-    "DELETE_ACCOUNT_FEEDBACK_LABEL": "",
-    "DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "",
-    "CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "",
-    "CONFIRM_DELETE_ACCOUNT": "",
-    "FEEDBACK_REQUIRED": "",
-    "FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "",
-    "RECOVER_TWO_FACTOR": "",
-    "at": "",
-    "AUTH_NEXT": "",
-    "AUTH_DOWNLOAD_MOBILE_APP": "",
-    "HIDDEN": "",
-    "HIDE": "",
-    "UNHIDE": "",
-    "UNHIDE_TO_COLLECTION": "",
-    "SORT_BY": "",
-    "NEWEST_FIRST": "",
-    "OLDEST_FIRST": "",
-    "CONVERSION_FAILED_NOTIFICATION_MESSAGE": "",
-    "SELECT_COLLECTION": "",
-    "PIN_ALBUM": "",
-    "UNPIN_ALBUM": "",
-    "DOWNLOAD_COMPLETE": "",
-    "DOWNLOADING_COLLECTION": "",
-    "DOWNLOAD_FAILED": "",
-    "DOWNLOAD_PROGRESS": "",
-    "CHRISTMAS": "",
-    "CHRISTMAS_EVE": "",
-    "NEW_YEAR": "",
-    "NEW_YEAR_EVE": "",
-    "IMAGE": "",
-    "VIDEO": "",
-    "LIVE_PHOTO": "",
-    "CONVERT": "",
-    "CONFIRM_EDITOR_CLOSE_MESSAGE": "",
-    "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "",
-    "BRIGHTNESS": "",
-    "CONTRAST": "",
-    "SATURATION": "",
-    "BLUR": "",
-    "INVERT_COLORS": "",
-    "ASPECT_RATIO": "",
-    "SQUARE": "",
-    "ROTATE_LEFT": "",
-    "ROTATE_RIGHT": "",
-    "FLIP_VERTICALLY": "",
-    "FLIP_HORIZONTALLY": "",
-    "DOWNLOAD_EDITED": "",
-    "SAVE_A_COPY_TO_ENTE": "",
-    "RESTORE_ORIGINAL": "",
-    "TRANSFORM": "",
-    "COLORS": "",
-    "FLIP": "",
-    "ROTATION": "",
-    "RESET": "",
-    "PHOTO_EDITOR": "",
-    "FASTER_UPLOAD": "",
-    "FASTER_UPLOAD_DESCRIPTION": "",
-    "MAGIC_SEARCH_STATUS": "",
-    "INDEXED_ITEMS": "",
-    "CAST_ALBUM_TO_TV": "",
-    "ENTER_CAST_PIN_CODE": "",
-    "PAIR_DEVICE_TO_TV": "",
-    "TV_NOT_FOUND": "",
-    "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
-    "PAIR_WITH_PIN": "",
-    "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
-    "VISIT_CAST_ENTE_IO": "",
-    "CAST_AUTO_PAIR_FAILED": "",
-    "CACHE_DIRECTORY": "",
-    "FREEHAND": "",
-    "APPLY_CROP": "",
-    "PHOTO_EDIT_REQUIRED_TO_SAVE": "",
-    "PASSKEYS": "",
-    "DELETE_PASSKEY": "",
-    "DELETE_PASSKEY_CONFIRMATION": "",
-    "RENAME_PASSKEY": "",
-    "ADD_PASSKEY": "",
-    "ENTER_PASSKEY_NAME": "",
-    "PASSKEYS_DESCRIPTION": "",
-    "CREATED_AT": "",
-    "PASSKEY_LOGIN_FAILED": "",
-    "PASSKEY_LOGIN_URL_INVALID": "",
-    "PASSKEY_LOGIN_ERRORED": "",
-    "TRY_AGAIN": "",
-    "PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER": "",
-    "LOGIN_WITH_PASSKEY": ""
-}

+ 0 - 654
web/apps/accounts/public/locales/ko-KR/translation.json

@@ -1,654 +0,0 @@
-{
-    "HERO_SLIDE_1_TITLE": "추억을 안전하게 백업하세요",
-    "HERO_SLIDE_1": "종단간 암호화가 기본지원입니다",
-    "HERO_SLIDE_2_TITLE": "낙진대피소에 안전하게 보관됩니다",
-    "HERO_SLIDE_2": "오랫동안 보존할 수 있도록한 설계",
-    "HERO_SLIDE_3_TITLE": "<div>어디에서나</div><div> 이용가능</div>",
-    "HERO_SLIDE_3": "안드로이드, iOS, 웹, 데스크탑",
-    "LOGIN": "로그인",
-    "SIGN_UP": "회원가입",
-    "NEW_USER": "ente의 새소식",
-    "EXISTING_USER": "기존 사용자",
-    "ENTER_NAME": "이름 입력",
-    "PUBLIC_UPLOADER_NAME_MESSAGE": "친구들이 이 멋진 사진에 대해 고마워할 수 있도록 이름을 추가하세요!",
-    "ENTER_EMAIL": "이메일 주소를 입력하세요",
-    "EMAIL_ERROR": "올바른 이메일을 입력하세요",
-    "REQUIRED": "필수",
-    "EMAIL_SENT": "<a>{{email}}</a> 로 인증 코드가 전송되었습니다",
-    "CHECK_INBOX": "인증을 완료하기 위해 당신의 메일 수신함(그리고 스팸 수신함)을 확인하세요.",
-    "ENTER_OTT": "인증 코드",
-    "RESEND_MAIL": "코드 재전송하기",
-    "VERIFY": "인증",
-    "UNKNOWN_ERROR": "문제가 생긴 것 같아요. 다시 시도하세요",
-    "INVALID_CODE": "잘못된 인증 코드",
-    "EXPIRED_CODE": "입력한 인증 코드가 만료되었습니다",
-    "SENDING": "전송 중...",
-    "SENT": "발송 완료!",
-    "PASSWORD": "비밀번호",
-    "LINK_PASSWORD": "앨범 잠금해제를 위해 비밀번호를 입력하세요",
-    "RETURN_PASSPHRASE_HINT": "비밀번호",
-    "SET_PASSPHRASE": "비밀번호 설정",
-    "VERIFY_PASSPHRASE": "로그인",
-    "INCORRECT_PASSPHRASE": "잘못된 비밀번호입니다",
-    "ENTER_ENC_PASSPHRASE": "당신의 데이터를 암호화하는 데 사용할 수 있는 비밀번호를 입력하세요",
-    "PASSPHRASE_DISCLAIMER": "우리는 귀하의 비밀번호를 저장하지 않습니다. 만약 비밀번호를 잊어버린 경우 복구 키 없다면 <strong>데이터 복구를 도와드릴 수 없습니다</strong>.",
-    "WELCOME_TO_ENTE_HEADING": "환영합니다 <a/>",
-    "WELCOME_TO_ENTE_SUBHEADING": "End-to-End 암호화된 사진 저장 및 공유",
-    "WHERE_YOUR_BEST_PHOTOS_LIVE": "당신 최고의 사진이 있는 곳",
-    "KEY_GENERATION_IN_PROGRESS_MESSAGE": "암호 키 생성 중...",
-    "PASSPHRASE_HINT": "비밀번호",
-    "CONFIRM_PASSPHRASE": "비밀번호 확인",
-    "REFERRAL_CODE_HINT": "어떻게 Ente에 대해 들으셨나요? (선택사항)",
-    "REFERRAL_INFO": "우리는 앱 설치를 추적하지 않습니다. 우리를 알게 된 곳을 남겨주시면 우리에게 도움이 될꺼에요!",
-    "PASSPHRASE_MATCH_ERROR": "비밀번호가 일치하지 않습니다",
-    "CREATE_COLLECTION": "새 앨범",
-    "ENTER_ALBUM_NAME": "앨범 이름",
-    "CLOSE_OPTION": "닫기 (Esc)",
-    "ENTER_FILE_NAME": "파일 이름",
-    "CLOSE": "닫기",
-    "NO": "아니오",
-    "NOTHING_HERE": "아직 볼 수 있는 것이 없어요 👀",
-    "UPLOAD": "업로드",
-    "IMPORT": "가져오기",
-    "ADD_PHOTOS": "사진 추가",
-    "ADD_MORE_PHOTOS": "사진 더 추가하기",
-    "add_photos_one": "아이템 하나 추가",
-    "add_photos_other": "아이템 {{count, number}} 개 추가하기",
-    "SELECT_PHOTOS": "사진 선택하기",
-    "FILE_UPLOAD": "파일 업로드",
-    "UPLOAD_STAGE_MESSAGE": {
-        "0": "업로드 준비중",
-        "1": "구글 메타데이타 파일들 읽는중",
-        "2": "{{uploadCounter.finished, number}} / {{uploadCounter.total, number}} 파일 메타데이터가 추출되었습니다",
-        "3": "{{uploadCounter.finished, number}} / {{uploadCounter.total, number}} 파일이 처리되었습니다",
-        "4": "남은 업로드 취소중",
-        "5": "백업 완료"
-    },
-    "FILE_NOT_UPLOADED_LIST": "아래 파일들은 업로드 되지 않았습니다",
-    "SUBSCRIPTION_EXPIRED": "구독 만료",
-    "SUBSCRIPTION_EXPIRED_MESSAGE": "당신 구독이 만료되었으니, 구독을 <a>갱신</a>해주세요",
-    "STORAGE_QUOTA_EXCEEDED": "스토리지 제한이 초과되었습니다",
-    "INITIAL_LOAD_DELAY_WARNING": "처음 로딩시 다소 시간이 걸릴 수 있습니다",
-    "USER_DOES_NOT_EXIST": "죄송합니다. 해당 이메일을 사용하는 사용자를 찾을 수 없습니다",
-    "NO_ACCOUNT": "계정이 없습니다",
-    "ACCOUNT_EXISTS": "이미 계정이 있습니다",
-    "CREATE": "만들기",
-    "DOWNLOAD": "다운로드",
-    "DOWNLOAD_OPTION": "다운로드 (D)",
-    "DOWNLOAD_FAVORITES": "즐겨찾기 다운로드",
-    "DOWNLOAD_UNCATEGORIZED": "",
-    "DOWNLOAD_HIDDEN_ITEMS": "",
-    "COPY_OPTION": "",
-    "TOGGLE_FULLSCREEN": "",
-    "ZOOM_IN_OUT": "",
-    "PREVIOUS": "",
-    "NEXT": "",
-    "TITLE_PHOTOS": "",
-    "TITLE_ALBUMS": "",
-    "TITLE_AUTH": "",
-    "UPLOAD_FIRST_PHOTO": "",
-    "IMPORT_YOUR_FOLDERS": "",
-    "UPLOAD_DROPZONE_MESSAGE": "",
-    "WATCH_FOLDER_DROPZONE_MESSAGE": "",
-    "TRASH_FILES_TITLE": "",
-    "TRASH_FILE_TITLE": "",
-    "DELETE_FILES_TITLE": "",
-    "DELETE_FILES_MESSAGE": "",
-    "DELETE": "",
-    "DELETE_OPTION": "",
-    "FAVORITE_OPTION": "",
-    "UNFAVORITE_OPTION": "",
-    "MULTI_FOLDER_UPLOAD": "",
-    "UPLOAD_STRATEGY_CHOICE": "",
-    "UPLOAD_STRATEGY_SINGLE_COLLECTION": "",
-    "OR": "",
-    "UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "",
-    "SESSION_EXPIRED_MESSAGE": "",
-    "SESSION_EXPIRED": "",
-    "PASSWORD_GENERATION_FAILED": "",
-    "CHANGE_PASSWORD": "",
-    "GO_BACK": "",
-    "RECOVERY_KEY": "",
-    "SAVE_LATER": "",
-    "SAVE": "",
-    "RECOVERY_KEY_DESCRIPTION": "",
-    "RECOVER_KEY_GENERATION_FAILED": "",
-    "KEY_NOT_STORED_DISCLAIMER": "",
-    "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": "",
-    "CONFIRM": "",
-    "CANCEL": "",
-    "LOGOUT": "",
-    "DELETE_ACCOUNT": "",
-    "DELETE_ACCOUNT_MESSAGE": "",
-    "LOGOUT_MESSAGE": "",
-    "CHANGE_EMAIL": "",
-    "OK": "",
-    "SUCCESS": "",
-    "ERROR": "",
-    "MESSAGE": "",
-    "INSTALL_MOBILE_APP": "",
-    "DOWNLOAD_APP_MESSAGE": "",
-    "DOWNLOAD_APP": "",
-    "EXPORT": "",
-    "SUBSCRIPTION": "",
-    "SUBSCRIBE": "",
-    "MANAGEMENT_PORTAL": "",
-    "MANAGE_FAMILY_PORTAL": "",
-    "LEAVE_FAMILY_PLAN": "",
-    "LEAVE": "",
-    "LEAVE_FAMILY_CONFIRM": "",
-    "CHOOSE_PLAN": "",
-    "MANAGE_PLAN": "",
-    "ACTIVE": "",
-    "OFFLINE_MSG": "",
-    "FREE_SUBSCRIPTION_INFO": "",
-    "FAMILY_SUBSCRIPTION_INFO": "",
-    "RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "",
-    "RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "",
-    "ADD_ON_AVAILABLE_TILL": "",
-    "STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "",
-    "SUBSCRIPTION_PURCHASE_SUCCESS": "",
-    "SUBSCRIPTION_PURCHASE_CANCELLED": "",
-    "SUBSCRIPTION_PURCHASE_FAILED": "",
-    "SUBSCRIPTION_UPDATE_FAILED": "",
-    "UPDATE_PAYMENT_METHOD_MESSAGE": "",
-    "STRIPE_AUTHENTICATION_FAILED": "",
-    "UPDATE_PAYMENT_METHOD": "",
-    "MONTHLY": "",
-    "YEARLY": "",
-    "UPDATE_SUBSCRIPTION_MESSAGE": "",
-    "UPDATE_SUBSCRIPTION": "",
-    "CANCEL_SUBSCRIPTION": "",
-    "CANCEL_SUBSCRIPTION_MESSAGE": "",
-    "CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "",
-    "SUBSCRIPTION_CANCEL_FAILED": "",
-    "SUBSCRIPTION_CANCEL_SUCCESS": "",
-    "REACTIVATE_SUBSCRIPTION": "",
-    "REACTIVATE_SUBSCRIPTION_MESSAGE": "",
-    "SUBSCRIPTION_ACTIVATE_SUCCESS": "",
-    "SUBSCRIPTION_ACTIVATE_FAILED": "",
-    "SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE": "",
-    "CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "",
-    "MAIL_TO_MANAGE_SUBSCRIPTION": "",
-    "RENAME": "",
-    "RENAME_FILE": "",
-    "RENAME_COLLECTION": "",
-    "DELETE_COLLECTION_TITLE": "",
-    "DELETE_COLLECTION": "",
-    "DELETE_COLLECTION_MESSAGE": "",
-    "DELETE_PHOTOS": "",
-    "KEEP_PHOTOS": "",
-    "SHARE": "",
-    "SHARE_COLLECTION": "",
-    "SHAREES": "",
-    "SHARE_WITH_SELF": "",
-    "ALREADY_SHARED": "",
-    "SHARING_BAD_REQUEST_ERROR": "",
-    "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "",
-    "DOWNLOAD_COLLECTION": "",
-    "DOWNLOAD_COLLECTION_MESSAGE": "",
-    "CREATE_ALBUM_FAILED": "",
-    "SEARCH": "",
-    "SEARCH_RESULTS": "",
-    "NO_RESULTS": "",
-    "SEARCH_HINT": "",
-    "SEARCH_TYPE": {
-        "COLLECTION": "",
-        "LOCATION": "",
-        "CITY": "",
-        "DATE": "",
-        "FILE_NAME": "",
-        "THING": "",
-        "FILE_CAPTION": "",
-        "FILE_TYPE": "",
-        "CLIP": ""
-    },
-    "photos_count_zero": "",
-    "photos_count_one": "",
-    "photos_count_other": "",
-    "TERMS_AND_CONDITIONS": "",
-    "ADD_TO_COLLECTION": "",
-    "SELECTED": "",
-    "VIDEO_PLAYBACK_FAILED_DOWNLOAD_INSTEAD": "",
-    "PEOPLE": "",
-    "INDEXING_SCHEDULED": "",
-    "ANALYZING_PHOTOS": "",
-    "INDEXING_PEOPLE": "",
-    "INDEXING_DONE": "",
-    "UNIDENTIFIED_FACES": "",
-    "OBJECTS": "",
-    "TEXT": "",
-    "INFO": "",
-    "INFO_OPTION": "",
-    "FILE_NAME": "",
-    "CAPTION_PLACEHOLDER": "",
-    "LOCATION": "",
-    "SHOW_ON_MAP": "",
-    "MAP": "",
-    "MAP_SETTINGS": "",
-    "ENABLE_MAPS": "",
-    "ENABLE_MAP": "",
-    "DISABLE_MAPS": "",
-    "ENABLE_MAP_DESCRIPTION": "",
-    "DISABLE_MAP_DESCRIPTION": "",
-    "DISABLE_MAP": "",
-    "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": "",
-    "LOST_DEVICE": "",
-    "INCORRECT_CODE": "",
-    "TWO_FACTOR_INFO": "",
-    "DISABLE_TWO_FACTOR_LABEL": "",
-    "UPDATE_TWO_FACTOR_LABEL": "",
-    "DISABLE": "",
-    "RECONFIGURE": "",
-    "UPDATE_TWO_FACTOR": "",
-    "UPDATE_TWO_FACTOR_MESSAGE": "",
-    "UPDATE": "",
-    "DISABLE_TWO_FACTOR": "",
-    "DISABLE_TWO_FACTOR_MESSAGE": "",
-    "TWO_FACTOR_DISABLE_FAILED": "",
-    "EXPORT_DATA": "",
-    "SELECT_FOLDER": "",
-    "DESTINATION": "",
-    "START": "",
-    "LAST_EXPORT_TIME": "",
-    "EXPORT_AGAIN": "",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE": "",
-    "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "",
-    "SEND_OTT": "",
-    "EMAIl_ALREADY_OWNED": "",
-    "ETAGS_BLOCKED": "",
-    "SKIPPED_VIDEOS_INFO": "",
-    "LIVE_PHOTOS_DETECTED": "",
-    "RETRY_FAILED": "",
-    "FAILED_UPLOADS": "",
-    "SKIPPED_FILES": "",
-    "THUMBNAIL_GENERATION_FAILED_UPLOADS": "",
-    "UNSUPPORTED_FILES": "",
-    "SUCCESSFUL_UPLOADS": "",
-    "SKIPPED_INFO": "",
-    "UNSUPPORTED_INFO": "",
-    "BLOCKED_UPLOADS": "",
-    "SKIPPED_VIDEOS": "",
-    "INPROGRESS_METADATA_EXTRACTION": "",
-    "INPROGRESS_UPLOADS": "",
-    "TOO_LARGE_UPLOADS": "",
-    "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "",
-    "LARGER_THAN_AVAILABLE_STORAGE_INFO": "",
-    "TOO_LARGE_INFO": "",
-    "THUMBNAIL_GENERATION_FAILED_INFO": "",
-    "UPLOAD_TO_COLLECTION": "",
-    "UNCATEGORIZED": "",
-    "ARCHIVE": "",
-    "FAVORITES": "",
-    "ARCHIVE_COLLECTION": "",
-    "ARCHIVE_SECTION_NAME": "",
-    "ALL_SECTION_NAME": "",
-    "MOVE_TO_COLLECTION": "",
-    "UNARCHIVE": "",
-    "UNARCHIVE_COLLECTION": "",
-    "HIDE_COLLECTION": "",
-    "UNHIDE_COLLECTION": "",
-    "MOVE": "",
-    "ADD": "",
-    "REMOVE": "",
-    "YES_REMOVE": "",
-    "REMOVE_FROM_COLLECTION": "",
-    "TRASH": "",
-    "MOVE_TO_TRASH": "",
-    "TRASH_FILES_MESSAGE": "",
-    "TRASH_FILE_MESSAGE": "",
-    "DELETE_PERMANENTLY": "",
-    "RESTORE": "",
-    "RESTORE_TO_COLLECTION": "",
-    "EMPTY_TRASH": "",
-    "EMPTY_TRASH_TITLE": "",
-    "EMPTY_TRASH_MESSAGE": "",
-    "LEAVE_SHARED_ALBUM": "",
-    "LEAVE_ALBUM": "",
-    "LEAVE_SHARED_ALBUM_TITLE": "",
-    "LEAVE_SHARED_ALBUM_MESSAGE": "",
-    "NOT_FILE_OWNER": "",
-    "CONFIRM_SELF_REMOVE_MESSAGE": "",
-    "CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "",
-    "SORT_BY_CREATION_TIME_ASCENDING": "",
-    "SORT_BY_UPDATION_TIME_DESCENDING": "",
-    "SORT_BY_NAME": "",
-    "COMPRESS_THUMBNAILS": "",
-    "THUMBNAIL_REPLACED": "",
-    "FIX_THUMBNAIL": "",
-    "FIX_THUMBNAIL_LATER": "",
-    "REPLACE_THUMBNAIL_NOT_STARTED": "",
-    "REPLACE_THUMBNAIL_COMPLETED": "",
-    "REPLACE_THUMBNAIL_NOOP": "",
-    "REPLACE_THUMBNAIL_COMPLETED_WITH_ERROR": "",
-    "FIX_CREATION_TIME": "",
-    "FIX_CREATION_TIME_IN_PROGRESS": "",
-    "CREATION_TIME_UPDATED": "",
-    "UPDATE_CREATION_TIME_NOT_STARTED": "",
-    "UPDATE_CREATION_TIME_COMPLETED": "",
-    "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "",
-    "CAPTION_CHARACTER_LIMIT": "",
-    "DATE_TIME_ORIGINAL": "",
-    "DATE_TIME_DIGITIZED": "",
-    "METADATA_DATE": "",
-    "CUSTOM_TIME": "",
-    "REOPEN_PLAN_SELECTOR_MODAL": "",
-    "OPEN_PLAN_SELECTOR_MODAL_FAILED": "",
-    "INSTALL": "",
-    "SHARING_DETAILS": "",
-    "MODIFY_SHARING": "",
-    "ADD_COLLABORATORS": "",
-    "ADD_NEW_EMAIL": "",
-    "shared_with_people_zero": "",
-    "shared_with_people_one": "",
-    "shared_with_people_other": "",
-    "participants_zero": "",
-    "participants_one": "",
-    "participants_other": "",
-    "ADD_VIEWERS": "",
-    "PARTICIPANTS": "",
-    "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": "",
-    "LINK_PASSWORD_LOCK": "",
-    "PUBLIC_COLLECT": "",
-    "LINK_DEVICE_LIMIT": "",
-    "NO_DEVICE_LIMIT": "",
-    "LINK_EXPIRY": "",
-    "NEVER": "",
-    "DISABLE_FILE_DOWNLOAD": "",
-    "DISABLE_FILE_DOWNLOAD_MESSAGE": "",
-    "MALICIOUS_CONTENT": "",
-    "COPYRIGHT": "",
-    "SHARED_USING": "",
-    "ENTE_IO": "",
-    "SHARING_REFERRAL_CODE": "",
-    "LIVE": "",
-    "DISABLE_PASSWORD": "",
-    "DISABLE_PASSWORD_MESSAGE": "",
-    "PASSWORD_LOCK": "",
-    "LOCK": "",
-    "DOWNLOAD_UPLOAD_LOGS": "",
-    "UPLOAD_FILES": "",
-    "UPLOAD_DIRS": "",
-    "UPLOAD_GOOGLE_TAKEOUT": "",
-    "DEDUPLICATE_FILES": "",
-    "AUTHENTICATOR_SECTION": "",
-    "NO_DUPLICATES_FOUND": "",
-    "CLUB_BY_CAPTURE_TIME": "",
-    "FILES": "",
-    "EACH": "",
-    "DEDUPLICATE_BASED_ON_SIZE": "",
-    "STOP_ALL_UPLOADS_MESSAGE": "",
-    "STOP_UPLOADS_HEADER": "",
-    "YES_STOP_UPLOADS": "",
-    "STOP_DOWNLOADS_HEADER": "",
-    "YES_STOP_DOWNLOADS": "",
-    "STOP_ALL_DOWNLOADS_MESSAGE": "",
-    "albums_one": "",
-    "albums_other": "",
-    "ALL_ALBUMS": "",
-    "ALBUMS": "",
-    "ALL_HIDDEN_ALBUMS": "",
-    "HIDDEN_ALBUMS": "",
-    "HIDDEN_ITEMS": "",
-    "HIDDEN_ITEMS_SECTION_NAME": "",
-    "ENTER_TWO_FACTOR_OTP": "",
-    "CREATE_ACCOUNT": "",
-    "COPIED": "",
-    "CANVAS_BLOCKED_TITLE": "",
-    "CANVAS_BLOCKED_MESSAGE": "",
-    "WATCH_FOLDERS": "",
-    "UPGRADE_NOW": "",
-    "RENEW_NOW": "",
-    "STORAGE": "",
-    "USED": "",
-    "YOU": "",
-    "FAMILY": "",
-    "FREE": "",
-    "OF": "",
-    "WATCHED_FOLDERS": "",
-    "NO_FOLDERS_ADDED": "",
-    "FOLDERS_AUTOMATICALLY_MONITORED": "",
-    "UPLOAD_NEW_FILES_TO_ENTE": "",
-    "REMOVE_DELETED_FILES_FROM_ENTE": "",
-    "ADD_FOLDER": "",
-    "STOP_WATCHING": "",
-    "STOP_WATCHING_FOLDER": "",
-    "STOP_WATCHING_DIALOG_MESSAGE": "",
-    "YES_STOP": "",
-    "MONTH_SHORT": "",
-    "YEAR": "",
-    "FAMILY_PLAN": "",
-    "DOWNLOAD_LOGS": "",
-    "DOWNLOAD_LOGS_MESSAGE": "",
-    "CHANGE_FOLDER": "",
-    "TWO_MONTHS_FREE": "",
-    "GB": "",
-    "POPULAR": "",
-    "FREE_PLAN_OPTION_LABEL": "",
-    "FREE_PLAN_DESCRIPTION": "",
-    "CURRENT_USAGE": "",
-    "WEAK_DEVICE": "",
-    "DRAG_AND_DROP_HINT": "",
-    "CONFIRM_ACCOUNT_DELETION_MESSAGE": "",
-    "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": "",
-    "NAME_PLACEHOLDER": "",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "",
-    "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "",
-    "CHOSE_THEME": "",
-    "ML_SEARCH": "",
-    "ENABLE_ML_SEARCH_DESCRIPTION": "",
-    "ML_MORE_DETAILS": "",
-    "ENABLE_FACE_SEARCH": "",
-    "ENABLE_FACE_SEARCH_TITLE": "",
-    "ENABLE_FACE_SEARCH_DESCRIPTION": "",
-    "DISABLE_BETA": "",
-    "DISABLE_FACE_SEARCH": "",
-    "DISABLE_FACE_SEARCH_TITLE": "",
-    "DISABLE_FACE_SEARCH_DESCRIPTION": "",
-    "ADVANCED": "",
-    "FACE_SEARCH_CONFIRMATION": "",
-    "LABS": "",
-    "YOURS": "",
-    "PASSPHRASE_STRENGTH_WEAK": "",
-    "PASSPHRASE_STRENGTH_MODERATE": "",
-    "PASSPHRASE_STRENGTH_STRONG": "",
-    "PREFERENCES": "",
-    "LANGUAGE": "",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST": "",
-    "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "",
-    "SUBSCRIPTION_VERIFICATION_ERROR": "",
-    "STORAGE_UNITS": {
-        "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": "",
-    "EXPORT_NOTIFICATION": {
-        "START": "",
-        "IN_PROGRESS": "",
-        "FINISH": "",
-        "UP_TO_DATE": ""
-    },
-    "CONTINUOUS_EXPORT": "",
-    "TOTAL_ITEMS": "",
-    "PENDING_ITEMS": "",
-    "EXPORT_STARTING": "",
-    "DELETE_ACCOUNT_REASON_LABEL": "",
-    "DELETE_ACCOUNT_REASON_PLACEHOLDER": "",
-    "DELETE_REASON": {
-        "MISSING_FEATURE": "",
-        "BROKEN_BEHAVIOR": "",
-        "FOUND_ANOTHER_SERVICE": "",
-        "NOT_LISTED": ""
-    },
-    "DELETE_ACCOUNT_FEEDBACK_LABEL": "",
-    "DELETE_ACCOUNT_FEEDBACK_PLACEHOLDER": "",
-    "CONFIRM_DELETE_ACCOUNT_CHECKBOX_LABEL": "",
-    "CONFIRM_DELETE_ACCOUNT": "",
-    "FEEDBACK_REQUIRED": "",
-    "FEEDBACK_REQUIRED_FOUND_ANOTHER_SERVICE": "",
-    "RECOVER_TWO_FACTOR": "",
-    "at": "",
-    "AUTH_NEXT": "",
-    "AUTH_DOWNLOAD_MOBILE_APP": "",
-    "HIDDEN": "",
-    "HIDE": "",
-    "UNHIDE": "",
-    "UNHIDE_TO_COLLECTION": "",
-    "SORT_BY": "",
-    "NEWEST_FIRST": "",
-    "OLDEST_FIRST": "",
-    "CONVERSION_FAILED_NOTIFICATION_MESSAGE": "",
-    "SELECT_COLLECTION": "",
-    "PIN_ALBUM": "",
-    "UNPIN_ALBUM": "",
-    "DOWNLOAD_COMPLETE": "",
-    "DOWNLOADING_COLLECTION": "",
-    "DOWNLOAD_FAILED": "",
-    "DOWNLOAD_PROGRESS": "",
-    "CHRISTMAS": "",
-    "CHRISTMAS_EVE": "",
-    "NEW_YEAR": "",
-    "NEW_YEAR_EVE": "",
-    "IMAGE": "",
-    "VIDEO": "",
-    "LIVE_PHOTO": "",
-    "CONVERT": "",
-    "CONFIRM_EDITOR_CLOSE_MESSAGE": "",
-    "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "",
-    "BRIGHTNESS": "",
-    "CONTRAST": "",
-    "SATURATION": "",
-    "BLUR": "",
-    "INVERT_COLORS": "",
-    "ASPECT_RATIO": "",
-    "SQUARE": "",
-    "ROTATE_LEFT": "",
-    "ROTATE_RIGHT": "",
-    "FLIP_VERTICALLY": "",
-    "FLIP_HORIZONTALLY": "",
-    "DOWNLOAD_EDITED": "",
-    "SAVE_A_COPY_TO_ENTE": "",
-    "RESTORE_ORIGINAL": "",
-    "TRANSFORM": "",
-    "COLORS": "",
-    "FLIP": "",
-    "ROTATION": "",
-    "RESET": "",
-    "PHOTO_EDITOR": "",
-    "FASTER_UPLOAD": "",
-    "FASTER_UPLOAD_DESCRIPTION": "",
-    "MAGIC_SEARCH_STATUS": "",
-    "INDEXED_ITEMS": "",
-    "CAST_ALBUM_TO_TV": "",
-    "ENTER_CAST_PIN_CODE": "",
-    "PAIR_DEVICE_TO_TV": "",
-    "TV_NOT_FOUND": "",
-    "AUTO_CAST_PAIR": "",
-    "AUTO_CAST_PAIR_REQUIRES_CONNECTION_TO_GOOGLE": "",
-    "PAIR_WITH_PIN": "",
-    "CHOOSE_DEVICE_FROM_BROWSER": "",
-    "PAIR_WITH_PIN_WORKS_FOR_ANY_LARGE_SCREEN_DEVICE": "",
-    "VISIT_CAST_ENTE_IO": "",
-    "CAST_AUTO_PAIR_FAILED": "",
-    "CACHE_DIRECTORY": "",
-    "FREEHAND": "",
-    "APPLY_CROP": "",
-    "PHOTO_EDIT_REQUIRED_TO_SAVE": "",
-    "PASSKEYS": "",
-    "DELETE_PASSKEY": "",
-    "DELETE_PASSKEY_CONFIRMATION": "",
-    "RENAME_PASSKEY": "",
-    "ADD_PASSKEY": "",
-    "ENTER_PASSKEY_NAME": "",
-    "PASSKEYS_DESCRIPTION": "",
-    "CREATED_AT": "",
-    "PASSKEY_LOGIN_FAILED": "",
-    "PASSKEY_LOGIN_URL_INVALID": "",
-    "PASSKEY_LOGIN_ERRORED": "",
-    "TRY_AGAIN": "",
-    "PASSKEY_FOLLOW_THE_STEPS_FROM_YOUR_BROWSER": "",
-    "LOGIN_WITH_PASSKEY": ""
-}

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