diff --git a/.github/workflows/mobile-daily-internal.yml b/.github/workflows/mobile-daily-internal.yml index 0429b8c001..2dee4c4ef4 100644 --- a/.github/workflows/mobile-daily-internal.yml +++ b/.github/workflows/mobile-daily-internal.yml @@ -8,6 +8,7 @@ on: env: FLUTTER_VERSION: "3.32.8" + RUST_VERSION: "1.85.1" permissions: contents: read diff --git a/.github/workflows/mobile-internal-release.yml b/.github/workflows/mobile-internal-release.yml index 43b2124cb8..768609dfa3 100644 --- a/.github/workflows/mobile-internal-release.yml +++ b/.github/workflows/mobile-internal-release.yml @@ -5,6 +5,7 @@ on: env: FLUTTER_VERSION: "3.32.8" + RUST_VERSION: "1.85.1" permissions: contents: write diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 782a89b8a8..7db33d5dea 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -2,11 +2,12 @@ package cmd import ( "fmt" - "github.com/ente-io/cli/pkg" - "github.com/spf13/cobra/doc" "os" "runtime" + "github.com/ente-io/cli/pkg" + "github.com/spf13/cobra/doc" + "github.com/spf13/viper" "github.com/spf13/cobra" @@ -20,11 +21,6 @@ var ctrl *pkg.ClICtrl var rootCmd = &cobra.Command{ Use: "ente", Short: "CLI tool for exporting your photos from ente.io", - // Uncomment the following line if your bare application - // has an action associated with it: - Run: func(cmd *cobra.Command, args []string) { - fmt.Sprintf("Hello World") - }, } func GenerateDocs() error { diff --git a/docs/docs/cli/index.md b/docs/docs/cli/index.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/docs/self-hosting/administration/object-storage.md b/docs/docs/self-hosting/administration/object-storage.md index f92d6eae40..1ff597facb 100644 --- a/docs/docs/self-hosting/administration/object-storage.md +++ b/docs/docs/self-hosting/administration/object-storage.md @@ -63,11 +63,20 @@ It has no relation to Backblaze, Wasabi or Scaleway. Each bucket's endpoint, region, key and secret should be configured accordingly if using an external bucket. -A sample configuration for `b2-eu-cen` is provided, which can be used for other -2 buckets as well: +If a bucket has SSL support enabled, set `s3.are_local_buckets` to `false`. Enable path-style URL by setting `s3.use_path_style_urls` to `true`. + +::: note + +You can configure this for individual buckets over defining top-level configuration if you are using the latest server image (August 2025) + +::: + +A sample configuration for `b2-eu-cen` is provided, which can be used for other 2 buckets as well: ```yaml b2-eu-cen: + are_local_buckets: true + use_path_style_urls: true key: secret: endpoint: localhost:3200 diff --git a/docs/docs/self-hosting/installation/config.md b/docs/docs/self-hosting/installation/config.md index 7156046c73..c406d77fb6 100644 --- a/docs/docs/self-hosting/installation/config.md +++ b/docs/docs/self-hosting/installation/config.md @@ -96,8 +96,8 @@ provide correct credentials for proper connectivity within Museum. The `s3` section within `museum.yaml` is by default configured to use local MinIO buckets when using `quickstart.sh` or Docker Compose. -If you wish to use an external S3 provider, you can edit the configuration with -your provider's credentials, and set `s3.are_local_buckets` to `false`. +If you wish to use an external S3 provider with SSL, you can edit the configuration with +your provider's credentials, and set `s3.are_local_buckets` to `false`. Additionally, you can configure this for specific buckets in the corresponding bucket sections in the Compose file. If you are using default MinIO, it is accessible at port `3200`. Web Console can be accessed by enabling port `3201` in the Compose file. @@ -111,11 +111,11 @@ and [troubleshooting](/self-hosting/troubleshooting/uploads) sections. | Variable | Description | Default | | -------------------------------------- | -------------------------------------------- | ------- | -| `s3.b2-eu-cen` | Primary hot storage S3 config | | +| `s3.b2-eu-cen` | Primary hot storage bucket configuration | | | `s3.wasabi-eu-central-2-v3.compliance` | Whether to disable compliance lock on delete | `true` | -| `s3.scw-eu-fr-v3` | Optional secondary S3 config | | -| `s3.wasabi-eu-central-2-derived` | Derived data storage | | -| `s3.are_local_buckets` | Use local MinIO-compatible storage | `false` | +| `s3.scw-eu-fr-v3` | Cold storage bucket configuration | | +| `s3.wasabi-eu-central-2-v3` | Secondary hot storage configuration | | +| `s3.are_local_buckets` | | `true` | | `s3.use_path_style_urls` | Enable path-style URLs for MinIO | `false` | ### Encryption Keys diff --git a/mobile/apps/auth/android/app/build.gradle b/mobile/apps/auth/android/app/build.gradle index 3da027f155..15d144c0c4 100644 --- a/mobile/apps/auth/android/app/build.gradle +++ b/mobile/apps/auth/android/app/build.gradle @@ -34,6 +34,9 @@ android { ndkVersion flutter.ndkVersion compileOptions { + // Flag to enable support for the new language APIs + coreLibraryDesugaringEnabled true + // Sets Java compatibility to Java 8 sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -56,7 +59,7 @@ android { applicationId "io.ente.auth" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion 21 + minSdkVersion 22 targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -115,4 +118,7 @@ flutter { source '../..' } -dependencies {} +dependencies { + // For AGP 7.4+ + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") +} diff --git a/mobile/apps/auth/android/app/proguard-rules.pro b/mobile/apps/auth/android/app/proguard-rules.pro new file mode 100644 index 0000000000..bfd553ce58 --- /dev/null +++ b/mobile/apps/auth/android/app/proguard-rules.pro @@ -0,0 +1,6 @@ +# Please add these rules to your existing keep rules in order to suppress warnings. +# This is generated automatically by the Android Gradle plugin. +-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue +-dontwarn com.google.errorprone.annotations.CheckReturnValue +-dontwarn com.google.errorprone.annotations.Immutable +-dontwarn com.google.errorprone.annotations.RestrictedApi \ No newline at end of file diff --git a/mobile/apps/auth/android/build.gradle b/mobile/apps/auth/android/build.gradle index bc157bd1a1..b3c47285d6 100644 --- a/mobile/apps/auth/android/build.gradle +++ b/mobile/apps/auth/android/build.gradle @@ -6,6 +6,19 @@ allprojects { } rootProject.buildDir = '../build' + +subprojects { + afterEvaluate { project -> + if (project.hasProperty('android')) { + project.android { + if (namespace == null) { + namespace project.group + } + } + } + } +} + subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } diff --git a/mobile/apps/auth/android/gradle/wrapper/gradle-wrapper.properties b/mobile/apps/auth/android/gradle/wrapper/gradle-wrapper.properties index e1ca574ef0..3c85cfe057 100644 --- a/mobile/apps/auth/android/gradle/wrapper/gradle-wrapper.properties +++ b/mobile/apps/auth/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip diff --git a/mobile/apps/auth/android/settings.gradle b/mobile/apps/auth/android/settings.gradle index 748caceba7..3ddaae5aa3 100644 --- a/mobile/apps/auth/android/settings.gradle +++ b/mobile/apps/auth/android/settings.gradle @@ -19,8 +19,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.8.22" apply false + id "com.android.application" version "8.6.0" apply false + id "org.jetbrains.kotlin.android" version "2.1.10" apply false } include ":app" diff --git a/mobile/apps/auth/docs/vscode/launch.json b/mobile/apps/auth/docs/vscode/launch.json index cf6e037e34..1430e5bd9d 100644 --- a/mobile/apps/auth/docs/vscode/launch.json +++ b/mobile/apps/auth/docs/vscode/launch.json @@ -6,14 +6,14 @@ "request": "launch", "type": "dart", "flutterMode": "debug", - "program": "auth/lib/main.dart", + "program": "mobile/apps/auth/lib/main.dart", "args": ["--dart-define", "endpoint=http://localhost:8080"] }, { "name": "Auth Android Dev", "request": "launch", "type": "dart", - "program": "auth/lib/main.dart", + "program": "mobile/apps/auth/lib/main.dart", "args": [ "--dart-define", "endpoint=http://192.168.1.3:8080", @@ -25,21 +25,21 @@ "name": "Auth iOS Dev", "request": "launch", "type": "dart", - "program": "auth/lib/main.dart", + "program": "mobile/apps/auth/lib/main.dart", "args": ["--dart-define", "endpoint=http://192.168.1.30:8080"] }, { "name": "Auth iOS Prod", "request": "launch", "type": "dart", - "program": "auth/lib/main.dart", + "program": "mobile/apps/auth/lib/main.dart", "args": ["--target", "lib/main.dart"] }, { "name": "Auth Android Prod", "request": "launch", "type": "dart", - "program": "auth/lib/main.dart", + "program": "mobile/apps/auth/lib/main.dart", "args": ["--target", "lib/main.dart", "--flavor", "independent"] } ] diff --git a/mobile/apps/auth/ios/Podfile.lock b/mobile/apps/auth/ios/Podfile.lock index 6a2db70ea9..688ae6f430 100644 --- a/mobile/apps/auth/ios/Podfile.lock +++ b/mobile/apps/auth/ios/Podfile.lock @@ -3,7 +3,6 @@ PODS: - Flutter - connectivity_plus (0.0.1): - Flutter - - FlutterMacOS - cupertino_http (0.0.1): - Flutter - FlutterMacOS @@ -61,13 +60,12 @@ PODS: - Flutter - flutter_local_notifications (0.0.1): - Flutter - - flutter_native_splash (0.0.1): + - flutter_native_splash (2.4.3): - Flutter - flutter_secure_storage (6.0.0): - Flutter - fluttertoast (0.0.2): - Flutter - - Toast - local_auth_darwin (0.0.1): - Flutter - FlutterMacOS @@ -87,9 +85,9 @@ PODS: - qr_code_scanner (0.2.0): - Flutter - MTBBarcodeScanner - - SDWebImage (5.21.0): - - SDWebImage/Core (= 5.21.0) - - SDWebImage/Core (5.21.0) + - SDWebImage (5.21.1): + - SDWebImage/Core (= 5.21.1) + - SDWebImage/Core (5.21.1) - Sentry/HybridSDK (8.46.0) - sentry_flutter (8.14.2): - Flutter @@ -102,35 +100,38 @@ PODS: - FlutterMacOS - sodium_libs (2.2.1): - Flutter - - sqflite (0.0.3): + - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - - "sqlite3 (3.46.1+1)": - - "sqlite3/common (= 3.46.1+1)" - - "sqlite3/common (3.46.1+1)" - - "sqlite3/dbstatvtab (3.46.1+1)": + - sqlite3 (3.50.2): + - sqlite3/common (= 3.50.2) + - sqlite3/common (3.50.2) + - sqlite3/dbstatvtab (3.50.2): - sqlite3/common - - "sqlite3/fts5 (3.46.1+1)": + - sqlite3/fts5 (3.50.2): - sqlite3/common - - "sqlite3/perf-threadsafe (3.46.1+1)": + - sqlite3/math (3.50.2): - sqlite3/common - - "sqlite3/rtree (3.46.1+1)": + - sqlite3/perf-threadsafe (3.50.2): + - sqlite3/common + - sqlite3/rtree (3.50.2): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - Flutter - - "sqlite3 (~> 3.46.0+1)" + - FlutterMacOS + - sqlite3 (~> 3.50.1) - sqlite3/dbstatvtab - sqlite3/fts5 + - sqlite3/math - sqlite3/perf-threadsafe - sqlite3/rtree - SwiftyGif (5.4.5) - - Toast (4.1.1) - url_launcher_ios (0.0.1): - Flutter DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) - - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) @@ -155,8 +156,8 @@ DEPENDENCIES: - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sodium_libs (from `.symlinks/plugins/sodium_libs/ios`) - - sqflite (from `.symlinks/plugins/sqflite/darwin`) - - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: @@ -169,13 +170,12 @@ SPEC REPOS: - Sentry - sqlite3 - SwiftyGif - - Toast EXTERNAL SOURCES: app_links: :path: ".symlinks/plugins/app_links/ios" connectivity_plus: - :path: ".symlinks/plugins/connectivity_plus/darwin" + :path: ".symlinks/plugins/connectivity_plus/ios" cupertino_http: :path: ".symlinks/plugins/cupertino_http/darwin" device_info_plus: @@ -224,51 +224,50 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" sodium_libs: :path: ".symlinks/plugins/sodium_libs/ios" - sqflite: - :path: ".symlinks/plugins/sqflite/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" sqlite3_flutter_libs: - :path: ".symlinks/plugins/sqlite3_flutter_libs/ios" + :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0 - connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db + app_links: f3e17e4ee5e357b39d8b95290a9b2c299fca71c6 + connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 + file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_email_sender: 10a22605f92809a11ef52b2f412db806c6082d40 + flutter_email_sender: e03bdda7637bcd3539bfe718fddd980e9508efaa flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb - flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 - flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 + flutter_local_notifications: df98d66e515e1ca797af436137b4459b160ad8c9 + flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c - local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 + fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f + local_auth_darwin: fa4b06454df7df8e97c18d7ee55151c57e7af0de move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb objective_c: 77e887b5ba1827970907e10e832eec1683f3431d OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e - SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 + SDWebImage: f29024626962457f3470184232766516dee8dfea Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854 sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b - share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb - sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d + sqlite3: 3e82a2daae39ba3b41ae6ee84a130494585460fc + sqlite3_flutter_libs: 2c48c4ee7217fd653251975e43412250d5bcbbe2 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 78f002751f1a8f65042b8da97902ba4124271c5a diff --git a/mobile/apps/auth/lib/app/view/app.dart b/mobile/apps/auth/lib/app/view/app.dart index ff37df574a..cfae7f23ff 100644 --- a/mobile/apps/auth/lib/app/view/app.dart +++ b/mobile/apps/auth/lib/app/view/app.dart @@ -2,20 +2,21 @@ import 'dart:async'; import 'dart:io'; import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:ente_accounts/services/user_service.dart'; import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/ente_theme_data.dart'; -import 'package:ente_auth/events/signed_in_event.dart'; -import 'package:ente_auth/events/signed_out_event.dart'; -import "package:ente_auth/l10n/l10n.dart"; +import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/locale.dart'; import "package:ente_auth/onboarding/view/onboarding_page.dart"; import 'package:ente_auth/services/authenticator_service.dart'; import 'package:ente_auth/services/update_service.dart'; -import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/services/window_listener_service.dart'; import 'package:ente_auth/ui/home_page.dart'; import 'package:ente_auth/ui/settings/app_update_dialog.dart'; +import 'package:ente_events/event_bus.dart'; +import 'package:ente_events/models/signed_in_event.dart'; +import 'package:ente_events/models/signed_out_event.dart'; +import 'package:ente_strings/l10n/strings_localizations.dart'; import 'package:flutter/foundation.dart'; import "package:flutter/material.dart"; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -131,6 +132,7 @@ class _AppState extends State localeListResolutionCallback: localResolutionCallBack, localizationsDelegates: const [ AppLocalizations.delegate, + StringsLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, @@ -150,6 +152,7 @@ class _AppState extends State localeListResolutionCallback: localResolutionCallBack, localizationsDelegates: const [ AppLocalizations.delegate, + StringsLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, diff --git a/mobile/apps/auth/lib/core/configuration.dart b/mobile/apps/auth/lib/core/configuration.dart index 7ba6fa4922..79299ed3c6 100644 --- a/mobile/apps/auth/lib/core/configuration.dart +++ b/mobile/apps/auth/lib/core/configuration.dart @@ -1,95 +1,36 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:io' as io; import 'dart:typed_data'; -import 'package:bip39/bip39.dart' as bip39; -import 'package:ente_auth/core/constants.dart'; -import 'package:ente_auth/core/event_bus.dart'; -import 'package:ente_auth/events/endpoint_updated_event.dart'; -import 'package:ente_auth/events/signed_in_event.dart'; -import 'package:ente_auth/events/signed_out_event.dart'; -import 'package:ente_auth/models/key_attributes.dart'; -import 'package:ente_auth/models/key_gen_result.dart'; -import 'package:ente_auth/models/private_key_attributes.dart'; -import 'package:ente_auth/store/authenticator_db.dart'; -import 'package:ente_auth/utils/directory_utils.dart'; +import 'package:ente_base/models/database.dart'; +import 'package:ente_configuration/base_configuration.dart'; import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:logging/logging.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; -import 'package:tuple/tuple.dart'; -class Configuration { +class Configuration extends BaseConfiguration { Configuration._privateConstructor(); static final Configuration instance = Configuration._privateConstructor(); - static const endpoint = String.fromEnvironment( - "endpoint", - defaultValue: kDefaultProductionEndpoint, - ); - static const emailKey = "email"; - static const keyAttributesKey = "key_attributes"; - - static const lastTempFolderClearTimeKey = "last_temp_folder_clear_time"; - static const keyKey = "key"; - static const secretKeyKey = "secret_key"; static const authSecretKeyKey = "auth_secret_key"; static const offlineAuthSecretKey = "offline_auth_secret_key"; - static const tokenKey = "token"; - static const encryptedTokenKey = "encrypted_token"; - static const userIDKey = "user_id"; - static const hasMigratedSecureStorageKey = "has_migrated_secure_storage"; static const hasOptedForOfflineModeKey = "has_opted_for_offline_mode"; - static const endPointKey = "endpoint"; - final List onlineSecureKeys = [ - keyKey, - secretKeyKey, - authSecretKeyKey, - ]; - final kTempFolderDeletionTimeBuffer = const Duration(days: 1).inMicroseconds; - - static final _logger = Logger("Configuration"); - - String? _cachedToken; late SharedPreferences _preferences; - String? _key; - String? _secretKey; String? _authSecretKey; String? _offlineAuthKey; late FlutterSecureStorage _secureStorage; - late String _tempDirectory; - String? _volatilePassword; - - Future init() async { + @override + Future init(List dbs) async { + await super.init(dbs); _preferences = await SharedPreferences.getInstance(); - sqfliteFfiInit(); _secureStorage = const FlutterSecureStorage( iOptions: IOSOptions( accessibility: KeychainAccessibility.first_unlock_this_device, ), ); - _tempDirectory = (await DirectoryUtils.getDirectoryForInit()).path; - final tempDirectory = io.Directory(_tempDirectory); - try { - final currentTime = DateTime.now().microsecondsSinceEpoch; - if (tempDirectory.existsSync() && - (_preferences.getInt(lastTempFolderClearTimeKey) ?? 0) < - (currentTime - kTempFolderDeletionTimeBuffer)) { - await tempDirectory.delete(recursive: true); - await _preferences.setInt(lastTempFolderClearTimeKey, currentTime); - _logger.info("Cleared temp folder"); - } else { - _logger.info("Skipping temp folder clear"); - } - } catch (e) { - _logger.warning(e); - } - tempDirectory.createSync(recursive: true); - await _initOnlineAccount(); + sqfliteFfiInit(); await _initOfflineAccount(); } @@ -99,303 +40,10 @@ class Configuration { ); } - Future _initOnlineAccount() async { - if (!_preferences.containsKey(tokenKey)) { - for (final key in onlineSecureKeys) { - unawaited( - _secureStorage.delete( - key: key, - ), - ); - } - } else { - _key = await _secureStorage.read( - key: keyKey, - ); - _secretKey = await _secureStorage.read( - key: secretKeyKey, - ); - _authSecretKey = await _secureStorage.read( - key: authSecretKeyKey, - ); - if (_key == null) { - await logout(autoLogout: true); - } - } - } - + @override Future logout({bool autoLogout = false}) async { - await _preferences.clear(); - for (String key in onlineSecureKeys) { - await _secureStorage.delete( - key: key, - ); - } - await AuthenticatorDB.instance.clearTable(); - _key = null; - _cachedToken = null; - _secretKey = null; _authSecretKey = null; - Bus.instance.fire(SignedOutEvent()); - } - - Future generateKey(String password) async { - // Create a master key - final masterKey = CryptoUtil.generateKey(); - - // Create a recovery key - final recoveryKey = CryptoUtil.generateKey(); - - // Encrypt master key and recovery key with each other - final encryptedMasterKey = CryptoUtil.encryptSync(masterKey, recoveryKey); - final encryptedRecoveryKey = CryptoUtil.encryptSync(recoveryKey, masterKey); - - // Derive a key from the password that will be used to encrypt and - // decrypt the master key - final kekSalt = CryptoUtil.getSaltToDeriveKey(); - final derivedKeyResult = await CryptoUtil.deriveSensitiveKey( - utf8.encode(password), - kekSalt, - ); - final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key); - - // Encrypt the key with this derived key - final encryptedKeyData = - CryptoUtil.encryptSync(masterKey, derivedKeyResult.key); - - // Generate a public-private keypair and encrypt the latter - final keyPair = CryptoUtil.generateKeyPair(); - final encryptedSecretKeyData = - CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey); - - final attributes = KeyAttributes( - CryptoUtil.bin2base64(kekSalt), - CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), - CryptoUtil.bin2base64(encryptedKeyData.nonce!), - CryptoUtil.bin2base64(keyPair.publicKey), - CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!), - CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!), - derivedKeyResult.memLimit, - derivedKeyResult.opsLimit, - CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!), - CryptoUtil.bin2base64(encryptedMasterKey.nonce!), - CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!), - CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!), - ); - final privateAttributes = PrivateKeyAttributes( - CryptoUtil.bin2base64(masterKey), - CryptoUtil.bin2hex(recoveryKey), - CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()), - ); - return KeyGenResult(attributes, privateAttributes, loginKey); - } - - Future> getAttributesForNewPassword( - String password, - ) async { - // Get master key - final masterKey = getKey(); - - // Derive a key from the password that will be used to encrypt and - // decrypt the master key - final kekSalt = CryptoUtil.getSaltToDeriveKey(); - final derivedKeyResult = await CryptoUtil.deriveSensitiveKey( - utf8.encode(password), - kekSalt, - ); - final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key); - - // Encrypt the key with this derived key - final encryptedKeyData = - CryptoUtil.encryptSync(masterKey!, derivedKeyResult.key); - - final existingAttributes = getKeyAttributes(); - - final updatedAttributes = existingAttributes!.copyWith( - kekSalt: CryptoUtil.bin2base64(kekSalt), - encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), - keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!), - memLimit: derivedKeyResult.memLimit, - opsLimit: derivedKeyResult.opsLimit, - ); - return Tuple2(updatedAttributes, loginKey); - } - - // decryptSecretsAndGetLoginKey decrypts the master key and recovery key - // with the given password and save them in local secure storage. - // This method also returns the keyEncKey that can be used for performing - // SRP setup for existing users. - Future decryptSecretsAndGetKeyEncKey( - String password, - KeyAttributes attributes, { - Uint8List? keyEncryptionKey, - }) async { - _logger.info('Start decryptAndSaveSecrets'); - keyEncryptionKey ??= await CryptoUtil.deriveKey( - utf8.encode(password), - CryptoUtil.base642bin(attributes.kekSalt), - attributes.memLimit, - attributes.opsLimit, - ); - - _logger.info('user-key done'); - Uint8List key; - try { - key = CryptoUtil.decryptSync( - CryptoUtil.base642bin(attributes.encryptedKey), - keyEncryptionKey, - CryptoUtil.base642bin(attributes.keyDecryptionNonce), - ); - } catch (e) { - _logger.severe('master-key failed, incorrect password?', e); - throw Exception("Incorrect password"); - } - _logger.info("master-key done"); - await setKey(CryptoUtil.bin2base64(key)); - final secretKey = CryptoUtil.decryptSync( - CryptoUtil.base642bin(attributes.encryptedSecretKey), - key, - CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce), - ); - _logger.info("secret-key done"); - await setSecretKey(CryptoUtil.bin2base64(secretKey)); - final token = CryptoUtil.openSealSync( - CryptoUtil.base642bin(getEncryptedToken()!), - CryptoUtil.base642bin(attributes.publicKey), - secretKey, - ); - _logger.info('appToken done'); - await setToken( - CryptoUtil.bin2base64(token, urlSafe: true), - ); - return keyEncryptionKey; - } - - Future recover(String recoveryKey) async { - // check if user has entered mnemonic code - if (recoveryKey.contains(' ')) { - final split = recoveryKey.split(' '); - if (split.length != mnemonicKeyWordCount) { - String wordThatIsFollowedByEmptySpaceInSplit = ''; - for (int i = 0; i < split.length; i++) { - String word = split[i]; - if (word.isEmpty) { - wordThatIsFollowedByEmptySpaceInSplit = - '\n\nExtra space after word at position $i'; - break; - } - } - throw AssertionError( - '\nRecovery code should have $mnemonicKeyWordCount words, ' - 'found ${split.length} words instead.$wordThatIsFollowedByEmptySpaceInSplit', - ); - } - recoveryKey = bip39.mnemonicToEntropy(recoveryKey); - } - final attributes = getKeyAttributes(); - Uint8List masterKey; - try { - masterKey = await CryptoUtil.decrypt( - CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey), - CryptoUtil.hex2bin(recoveryKey), - CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce), - ); - } catch (e) { - _logger.severe(e); - rethrow; - } - await setKey(CryptoUtil.bin2base64(masterKey)); - final secretKey = CryptoUtil.decryptSync( - CryptoUtil.base642bin(attributes.encryptedSecretKey), - masterKey, - CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce), - ); - await setSecretKey(CryptoUtil.bin2base64(secretKey)); - final token = CryptoUtil.openSealSync( - CryptoUtil.base642bin(getEncryptedToken()!), - CryptoUtil.base642bin(attributes.publicKey), - secretKey, - ); - await setToken( - CryptoUtil.bin2base64(token, urlSafe: true), - ); - } - - String getHttpEndpoint() { - return _preferences.getString(endPointKey) ?? endpoint; - } - - Future setHttpEndpoint(String endpoint) async { - await _preferences.setString(endPointKey, endpoint); - Bus.instance.fire(EndpointUpdatedEvent()); - } - - String? getToken() { - _cachedToken ??= _preferences.getString(tokenKey); - return _cachedToken; - } - - bool isLoggedIn() { - return getToken() != null; - } - - Future setToken(String token) async { - _cachedToken = token; - await _preferences.setString(tokenKey, token); - Bus.instance.fire(SignedInEvent()); - } - - Future setEncryptedToken(String encryptedToken) async { - await _preferences.setString(encryptedTokenKey, encryptedToken); - } - - String? getEncryptedToken() { - return _preferences.getString(encryptedTokenKey); - } - - String? getEmail() { - return _preferences.getString(emailKey); - } - - Future setEmail(String email) async { - await _preferences.setString(emailKey, email); - } - - int? getUserID() { - return _preferences.getInt(userIDKey); - } - - Future setUserID(int userID) async { - await _preferences.setInt(userIDKey, userID); - } - - Future setKeyAttributes(KeyAttributes attributes) async { - await _preferences.setString(keyAttributesKey, attributes.toJson()); - } - - KeyAttributes? getKeyAttributes() { - final jsonValue = _preferences.getString(keyAttributesKey); - if (jsonValue == null) { - return null; - } else { - return KeyAttributes.fromJson(jsonValue); - } - } - - Future setKey(String key) async { - _key = key; - await _secureStorage.write( - key: keyKey, - value: key, - ); - } - - Future setSecretKey(String? secretKey) async { - _secretKey = secretKey; - await _secureStorage.write( - key: secretKeyKey, - value: secretKey, - ); + await super.logout(); } Future setAuthSecretKey(String? authSecretKey) async { @@ -406,14 +54,6 @@ class Configuration { ); } - Uint8List? getKey() { - return _key == null ? null : CryptoUtil.base642bin(_key!); - } - - Uint8List? getSecretKey() { - return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!); - } - Uint8List? getAuthSecretKey() { return _authSecretKey == null ? null @@ -426,24 +66,6 @@ class Configuration { : CryptoUtil.base642bin(_offlineAuthKey!); } - Uint8List getRecoveryKey() { - final keyAttributes = getKeyAttributes()!; - return CryptoUtil.decryptSync( - CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey), - getKey()!, - CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce), - ); - } - - // Caution: This directory is cleared on app start - String getTempDirectory() { - return _tempDirectory; - } - - bool hasConfiguredAccount() { - return getToken() != null && _key != null; - } - bool hasOptedForOfflineMode() { return _preferences.getBool(hasOptedForOfflineModeKey) ?? false; } @@ -464,16 +86,4 @@ class Configuration { } await _preferences.setBool(hasOptedForOfflineModeKey, true); } - - void setVolatilePassword(String volatilePassword) { - _volatilePassword = volatilePassword; - } - - void resetVolatilePassword() { - _volatilePassword = null; - } - - String? getVolatilePassword() { - return _volatilePassword; - } } diff --git a/mobile/apps/auth/lib/events/codes_updated_event.dart b/mobile/apps/auth/lib/events/codes_updated_event.dart index b678413e8d..2966735f73 100644 --- a/mobile/apps/auth/lib/events/codes_updated_event.dart +++ b/mobile/apps/auth/lib/events/codes_updated_event.dart @@ -1,3 +1,3 @@ -import 'package:ente_auth/events/event.dart'; +import 'package:ente_events/models/event.dart'; class CodesUpdatedEvent extends Event {} diff --git a/mobile/apps/auth/lib/events/endpoint_updated_event.dart b/mobile/apps/auth/lib/events/endpoint_updated_event.dart deleted file mode 100644 index 0a9915479a..0000000000 --- a/mobile/apps/auth/lib/events/endpoint_updated_event.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:ente_auth/events/event.dart'; - -class EndpointUpdatedEvent extends Event {} diff --git a/mobile/apps/auth/lib/events/icons_changed_event.dart b/mobile/apps/auth/lib/events/icons_changed_event.dart index 5cc3d8b419..545ce0a49d 100644 --- a/mobile/apps/auth/lib/events/icons_changed_event.dart +++ b/mobile/apps/auth/lib/events/icons_changed_event.dart @@ -1,3 +1,3 @@ -import 'package:ente_auth/events/event.dart'; +import 'package:ente_events/models/event.dart'; class IconsChangedEvent extends Event {} diff --git a/mobile/apps/auth/lib/events/notification_event.dart b/mobile/apps/auth/lib/events/notification_event.dart deleted file mode 100644 index 87fe1d9060..0000000000 --- a/mobile/apps/auth/lib/events/notification_event.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:ente_auth/events/event.dart'; - -// NotificationEvent event is used to re-fresh the UI to show latest notification -// (if any) -class NotificationEvent extends Event {} diff --git a/mobile/apps/auth/lib/events/signed_in_event.dart b/mobile/apps/auth/lib/events/signed_in_event.dart deleted file mode 100644 index f0720fac9a..0000000000 --- a/mobile/apps/auth/lib/events/signed_in_event.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:ente_auth/events/event.dart'; - -class SignedInEvent extends Event {} diff --git a/mobile/apps/auth/lib/events/signed_out_event.dart b/mobile/apps/auth/lib/events/signed_out_event.dart index abe4927706..613744d49d 100644 --- a/mobile/apps/auth/lib/events/signed_out_event.dart +++ b/mobile/apps/auth/lib/events/signed_out_event.dart @@ -1,3 +1 @@ -import 'package:ente_auth/events/event.dart'; - -class SignedOutEvent extends Event {} +// TODO Implement this library. diff --git a/mobile/apps/auth/lib/events/trigger_logout_event.dart b/mobile/apps/auth/lib/events/trigger_logout_event.dart index bfdc79e940..a6c9b724f5 100644 --- a/mobile/apps/auth/lib/events/trigger_logout_event.dart +++ b/mobile/apps/auth/lib/events/trigger_logout_event.dart @@ -1,3 +1,3 @@ -import 'package:ente_auth/events/event.dart'; +import 'package:ente_events/models/event.dart'; class TriggerLogoutEvent extends Event {} diff --git a/mobile/apps/auth/lib/gateway/authenticator.dart b/mobile/apps/auth/lib/gateway/authenticator.dart index 1375146e48..fa0d595b55 100644 --- a/mobile/apps/auth/lib/gateway/authenticator.dart +++ b/mobile/apps/auth/lib/gateway/authenticator.dart @@ -1,8 +1,8 @@ import 'package:dio/dio.dart'; import 'package:ente_auth/core/errors.dart'; -import 'package:ente_auth/core/network.dart'; import 'package:ente_auth/models/authenticator/auth_entity.dart'; import 'package:ente_auth/models/authenticator/auth_key.dart'; +import 'package:ente_network/network.dart'; class AuthenticatorGateway { late Dio _enteDio; diff --git a/mobile/apps/auth/lib/l10n/arb/app_el.arb b/mobile/apps/auth/lib/l10n/arb/app_el.arb index 8027a2fbc2..2a2a1d803a 100644 --- a/mobile/apps/auth/lib/l10n/arb/app_el.arb +++ b/mobile/apps/auth/lib/l10n/arb/app_el.arb @@ -88,6 +88,8 @@ "useRecoveryKey": "Χρήση κλειδιού ανάκτησης", "incorrectPasswordTitle": "Λάθος κωδικός πρόσβασης", "welcomeBack": "Καλωσορίσατε και πάλι!", + "emailAlreadyRegistered": "Το email είναι ήδη καταχωρημένο.", + "emailNotRegistered": "Το email δεν έχει καταχωρηθεί.", "madeWithLoveAtPrefix": "φτιαγμένη με ❤️ στο ", "supportDevs": "Εγγραφείτε στο ente για να μας υποστηρίξετε", "supportDiscount": "Χρησιμοποιήστε τον κωδικό κουπονιού \"AUTH\" για να λάβετε 10% έκπτωση για τον πρώτο χρόνο", @@ -171,6 +173,7 @@ "invalidQRCode": "Μη έγκυρος κωδικός QR", "noRecoveryKeyTitle": "Χωρίς κλειδί ανάκτησης;", "enterEmailHint": "Εισάγετε τη διεύθυνση email σας", + "enterNewEmailHint": "Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας", "invalidEmailTitle": "Μη έγκυρη διεύθυνση email", "invalidEmailMessage": "Παρακαλούμε εισάγετε μια έγκυρη διεύθυνση email.", "deleteAccount": "Διαγραφή λογαριασμού", @@ -258,6 +261,10 @@ "areYouSureYouWantToLogout": "Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;", "yesLogout": "Ναι, αποσύνδεση", "exit": "Εξοδος", + "theme": "Θέμα", + "lightTheme": "Φωτεινό", + "darkTheme": "Σκοτεινό", + "systemTheme": "Σύστημα", "verifyingRecoveryKey": "Επαλήθευση κλειδιού ανάκτησης...", "recoveryKeyVerified": "Το κλειδί ανάκτησης επαληθεύτηκε", "recoveryKeySuccessBody": "Τέλεια! Το κλειδί ανάκτησης σας είναι έγκυρο. Σας ευχαριστούμε για την επαλήθευση.\n\nΠαρακαλώ θυμηθείτε να κρατήσετε το κλειδί ανάκτησης σας και σε αντίγραφο ασφαλείας.", @@ -490,5 +497,24 @@ "appLockNotEnabled": "Το κλείδωμα εφαρμογής δεν είναι ενεργοποιημένο", "appLockNotEnabledDescription": "Παρακαλώ ενεργοποιήστε το κλείδωμα εφαρμογής μέσω της επιλογής Ασφάλεια > Κλείδωμα εφαρμογής", "authToViewPasskey": "Παρακαλώ πιστοποιηθείτε για να δείτε το κλειδί πρόσβασης", - "appLockOfflineModeWarning": "Έχετε επιλέξει να προχωρήσετε χωρίς αντίγραφα ασφαλείας. Αν ξεχάσετε τον κωδικό της εφαρμογής, θα κλειδωθείτε από την πρόσβαση στα δεδομένα σας." + "appLockOfflineModeWarning": "Έχετε επιλέξει να προχωρήσετε χωρίς αντίγραφα ασφαλείας. Αν ξεχάσετε τον κωδικό της εφαρμογής, θα κλειδωθείτε από την πρόσβαση στα δεδομένα σας.", + "duplicateCodes": "Διπλότυποι κωδικοί", + "noDuplicates": "✨ Δεν υπάρχουν διπλότυπα", + "youveNoDuplicateCodesThatCanBeCleared": "Δεν υπάρχουν διπλότυπα αρχεία που μπορούν να εκκαθαριστούν", + "deduplicateCodes": "Διπλότυποι κωδικοί", + "deselectAll": "Αποεπιλογή όλων", + "selectAll": "Επιλογή όλων", + "deleteDuplicates": "Διαγραφή διπλότυπων", + "plainHTML": "Απλό HTML", + "dropReviewiOS": "Αφήστε μια κριτική στο App Store", + "dropReviewAndroid": "Αφήστε μια κριτική στο Play Store", + "giveUsAStarOnGithub": "Δώστε μας ένα αστέρι στο Github", + "free5GB": "5GB δωρεάν στο ente Photos", + "freeStorageOffer": "10% έκπτωση στο ente photos", + "freeStorageOfferDescription": "Χρησιμοποιήστε τον κωδικό \"AUTH\" για να λάβετε 10% έκπτωση για τον πρώτο χρόνο", + "advanced": "Για προχωρημένους", + "algorithm": "Αλγόριθμος", + "type": "Τύπος", + "period": "Περίοδος", + "digits": "Ψηφία" } \ No newline at end of file diff --git a/mobile/apps/auth/lib/l10n/arb/app_pl.arb b/mobile/apps/auth/lib/l10n/arb/app_pl.arb index f17467db12..a4177c8133 100644 --- a/mobile/apps/auth/lib/l10n/arb/app_pl.arb +++ b/mobile/apps/auth/lib/l10n/arb/app_pl.arb @@ -45,7 +45,7 @@ "timeBasedKeyType": "Oparte na czasie (TOTP)", "counterBasedKeyType": "Oparte na liczniku (HOTP)", "saveAction": "Zapisz", - "nextTotpTitle": "następny", + "nextTotpTitle": "dalej", "deleteCodeTitle": "Usunąć kod?", "deleteCodeMessage": "Czy na pewno chcesz usunąć ten kod? Ta akcja jest nieodwracalna.", "trashCode": "Przenieść kod do kosza?", diff --git a/mobile/apps/auth/lib/l10n/l10n.dart b/mobile/apps/auth/lib/l10n/l10n.dart index aea60bcdc7..56a3f09493 100644 --- a/mobile/apps/auth/lib/l10n/l10n.dart +++ b/mobile/apps/auth/lib/l10n/l10n.dart @@ -6,4 +6,4 @@ export "package:ente_auth/l10n/arb/app_localizations.dart" extension AppLocalizationsX on BuildContext { AppLocalizations get l10n => AppLocalizations.of(this); -} +} \ No newline at end of file diff --git a/mobile/apps/auth/lib/main.dart b/mobile/apps/auth/lib/main.dart index 2bca2feb0d..5136e72feb 100644 --- a/mobile/apps/auth/lib/main.dart +++ b/mobile/apps/auth/lib/main.dart @@ -2,34 +2,38 @@ import 'dart:async'; import 'dart:io'; import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:ente_accounts/services/user_service.dart'; import "package:ente_auth/app/view/app.dart"; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/constants.dart'; -import 'package:ente_auth/core/logging/super_logging.dart'; -import 'package:ente_auth/core/network.dart'; import 'package:ente_auth/ente_theme_data.dart'; +import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/locale.dart'; import 'package:ente_auth/services/authenticator_service.dart'; import 'package:ente_auth/services/billing_service.dart'; import 'package:ente_auth/services/notification_service.dart'; import 'package:ente_auth/services/preference_service.dart'; import 'package:ente_auth/services/update_service.dart'; -import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/services/window_listener_service.dart'; +import 'package:ente_auth/store/authenticator_db.dart'; import 'package:ente_auth/store/code_display_store.dart'; import 'package:ente_auth/store/code_store.dart'; -import 'package:ente_auth/ui/tools/app_lock.dart'; -import 'package:ente_auth/ui/tools/lock_screen.dart'; +import 'package:ente_auth/ui/home_page.dart'; import 'package:ente_auth/ui/utils/icon_utils.dart'; import 'package:ente_auth/utils/directory_utils.dart'; -import 'package:ente_auth/utils/lock_screen_settings.dart'; import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/window_protocol_handler.dart'; import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_lock_screen/lock_screen_settings.dart'; +import 'package:ente_lock_screen/ui/app_lock.dart'; +import 'package:ente_lock_screen/ui/lock_screen.dart'; +import 'package:ente_logging/logging.dart'; +import 'package:ente_network/network.dart'; +import 'package:ente_strings/l10n/strings_localizations.dart'; +import 'package:ente_ui/theme/theme_config.dart'; import 'package:flutter/foundation.dart'; import "package:flutter/material.dart"; import 'package:flutter_displaymode/flutter_displaymode.dart'; -import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:tray_manager/tray_manager.dart'; import 'package:window_manager/window_manager.dart'; @@ -89,7 +93,9 @@ void main() async { } Future _runInForeground() async { + AppThemeConfig.initialize(EnteApp.auth); final savedThemeMode = _themeMode(await AdaptiveTheme.getThemeMode()); + final configuration = Configuration.instance; return await _runWithLogs(() async { _logger.info("Starting app in foreground"); try { @@ -103,12 +109,19 @@ Future _runInForeground() async { runApp( AppLock( builder: (args) => App(locale: locale), - lockScreen: const LockScreen(), + lockScreen: LockScreen(configuration), enabled: await LockScreenSettings.instance.shouldShowLockScreen(), locale: locale, lightTheme: lightThemeData, darkTheme: darkThemeData, savedThemeMode: savedThemeMode, + localeListResolutionCallback: localResolutionCallBack, + localizationsDelegates: const [ + ...StringsLocalizations.localizationsDelegates, + ...AppLocalizations.localizationsDelegates, + ], + supportedLocales: appSupportedLocales, + backgroundLockLatency: const Duration(seconds: 0), ), ); }); @@ -154,13 +167,13 @@ Future _init(bool bool, {String? via}) async { await PreferenceService.instance.init(); await CodeStore.instance.init(); await CodeDisplayStore.instance.init(); - await Configuration.instance.init(); - await Network.instance.init(); - await UserService.instance.init(); + await Configuration.instance.init([AuthenticatorDB.instance]); + await Network.instance.init(Configuration.instance); + await UserService.instance.init(Configuration.instance, const HomePage()); await AuthenticatorService.instance.init(); await BillingService.instance.init(); await NotificationService.instance.init(); await UpdateService.instance.init(); await IconUtils.instance.init(); - await LockScreenSettings.instance.init(); + await LockScreenSettings.instance.init(Configuration.instance); } diff --git a/mobile/apps/auth/lib/models/subscription.dart b/mobile/apps/auth/lib/models/subscription.dart deleted file mode 100644 index 3804f9a2e8..0000000000 --- a/mobile/apps/auth/lib/models/subscription.dart +++ /dev/null @@ -1,65 +0,0 @@ -const freeProductID = "free"; -const stripe = "stripe"; -const appStore = "appstore"; -const playStore = "playstore"; - -class Subscription { - final String productID; - final int storage; - final String originalTransactionID; - final String paymentProvider; - final int expiryTime; - final String price; - final String period; - final Attributes? attributes; - - Subscription({ - required this.productID, - required this.storage, - required this.originalTransactionID, - required this.paymentProvider, - required this.expiryTime, - required this.price, - required this.period, - this.attributes, - }); - - bool isValid() { - return expiryTime > DateTime.now().microsecondsSinceEpoch; - } - - bool isYearlyPlan() { - return 'year' == period; - } - - static fromMap(Map? map) { - if (map == null) return null; - return Subscription( - productID: map['productID'], - storage: map['storage'], - originalTransactionID: map['originalTransactionID'], - paymentProvider: map['paymentProvider'], - expiryTime: map['expiryTime'], - price: map['price'], - period: map['period'], - attributes: map["attributes"] != null - ? Attributes.fromJson(map["attributes"]) - : null, - ); - } -} - -class Attributes { - bool? isCancelled; - String? customerID; - - Attributes({ - this.isCancelled, - this.customerID, - }); - - Attributes.fromJson(dynamic json) { - isCancelled = json["isCancelled"]; - customerID = json["customerID"]; - } -} diff --git a/mobile/apps/auth/lib/models/user_details.dart b/mobile/apps/auth/lib/models/user_details.dart deleted file mode 100644 index e50d3ee295..0000000000 --- a/mobile/apps/auth/lib/models/user_details.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'dart:convert'; -import 'dart:math'; - -import 'package:collection/collection.dart'; -import 'package:ente_auth/models/subscription.dart'; - -class UserDetails { - final String email; - final int usage; - final int fileCount; - final int sharedCollectionsCount; - final Subscription subscription; - final FamilyData? familyData; - final ProfileData? profileData; - - UserDetails( - this.email, - this.usage, - this.fileCount, - this.sharedCollectionsCount, - this.subscription, - this.familyData, - this.profileData, - ); - - bool isPartOfFamily() { - return familyData?.members?.isNotEmpty ?? false; - } - - bool isFamilyAdmin() { - assert(isPartOfFamily(), "verify user is part of family before calling"); - final FamilyMember currentUserMember = familyData!.members! - .firstWhere((element) => element.email.trim() == email.trim()); - return currentUserMember.isAdmin; - } - - // getFamilyOrPersonalUsage will return total usage for family if user - // belong to family group. Otherwise, it will return storage consumed by - // current user - int getFamilyOrPersonalUsage() { - return isPartOfFamily() ? familyData!.getTotalUsage() : usage; - } - - int getFreeStorage() { - return max( - isPartOfFamily() - ? (familyData!.storage - familyData!.getTotalUsage()) - : (subscription.storage - (usage)), - 0, - ); - } - - int getTotalStorage() { - return isPartOfFamily() ? familyData!.storage : subscription.storage; - } - - factory UserDetails.fromMap(Map map) { - return UserDetails( - map['email'] as String, - map['usage'] as int, - (map['fileCount'] ?? 0) as int, - (map['sharedCollectionsCount'] ?? 0) as int, - Subscription.fromMap(map['subscription']), - FamilyData.fromMap(map['familyData']), - ProfileData.fromJson(map['profileData']), - ); - } - -} - -class FamilyMember { - final String email; - final int usage; - final String id; - final bool isAdmin; - - FamilyMember(this.email, this.usage, this.id, this.isAdmin); - - factory FamilyMember.fromMap(Map map) { - return FamilyMember( - (map['email'] ?? '') as String, - map['usage'] as int, - map['id'] as String, - map['isAdmin'] as bool, - ); - } -} -class ProfileData { - bool canDisableEmailMFA; - bool isEmailMFAEnabled; - bool isTwoFactorEnabled; - - // Constructor with default values - ProfileData({ - this.canDisableEmailMFA = false, - this.isEmailMFAEnabled = false, - this.isTwoFactorEnabled = false, - }); - - // Factory method to create ProfileData instance from JSON - factory ProfileData.fromJson(Map? json) { - if (json == null) null; - - return ProfileData( - canDisableEmailMFA: json!['canDisableEmailMFA'] ?? false, - isEmailMFAEnabled: json['isEmailMFAEnabled'] ?? false, - isTwoFactorEnabled: json['isTwoFactorEnabled'] ?? false, - ); - } - - // Method to convert ProfileData instance to JSON - Map toJson() { - return { - 'canDisableEmailMFA': canDisableEmailMFA, - 'isEmailMFAEnabled': isEmailMFAEnabled, - 'isTwoFactorEnabled': isTwoFactorEnabled, - }; - } - String toJsonString() => json.encode(toJson()); -} -class FamilyData { - final List? members; - - // Storage available based on the family plan - final int storage; - final int expiryTime; - - FamilyData(this.members, this.storage, this.expiryTime); - - int getTotalUsage() { - return members!.map((e) => e.usage).toList().sum; - } - - static fromMap(Map? map) { - if (map == null) return null; - assert(map['members'] != null && map['members'].length >= 0); - final members = List.from( - map['members'].map((x) => FamilyMember.fromMap(x)), - ); - return FamilyData( - members, - map['storage'] as int, - map['expiryTime'] as int, - ); - } -} diff --git a/mobile/apps/auth/lib/onboarding/view/onboarding_page.dart b/mobile/apps/auth/lib/onboarding/view/onboarding_page.dart index ef5a297392..5dc8d92763 100644 --- a/mobile/apps/auth/lib/onboarding/view/onboarding_page.dart +++ b/mobile/apps/auth/lib/onboarding/view/onboarding_page.dart @@ -1,19 +1,18 @@ import 'dart:async'; import 'dart:io'; +import 'package:ente_accounts/pages/email_entry_page.dart'; +import 'package:ente_accounts/pages/login_page.dart'; +import 'package:ente_accounts/pages/password_entry_page.dart'; +import 'package:ente_accounts/pages/password_reentry_page.dart'; import 'package:ente_auth/app/view/app.dart'; import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/ente_theme_data.dart'; import 'package:ente_auth/events/trigger_logout_event.dart'; import "package:ente_auth/l10n/l10n.dart"; import 'package:ente_auth/locale.dart'; import 'package:ente_auth/theme/text_style.dart'; -import 'package:ente_auth/ui/account/email_entry_page.dart'; -import 'package:ente_auth/ui/account/login_page.dart'; import 'package:ente_auth/ui/account/logout_dialog.dart'; -import 'package:ente_auth/ui/account/password_entry_page.dart'; -import 'package:ente_auth/ui/account/password_reentry_page.dart'; import 'package:ente_auth/ui/common/gradient_button.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/components/models/button_result.dart'; @@ -24,6 +23,7 @@ import 'package:ente_auth/ui/settings/language_picker.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_events/event_bus.dart'; import 'package:flutter/foundation.dart'; import "package:flutter/material.dart"; import 'package:local_auth/local_auth.dart'; @@ -260,17 +260,22 @@ class _OnboardingPageState extends State { void _navigateToSignUpPage() { Widget page; if (Configuration.instance.getEncryptedToken() == null) { - page = const EmailEntryPage(); + page = EmailEntryPage(Configuration.instance); } else { // No key if (Configuration.instance.getKeyAttributes() == null) { // Never had a key - page = const PasswordEntryPage( - mode: PasswordEntryMode.set, + page = PasswordEntryPage( + Configuration.instance, + PasswordEntryMode.set, + const HomePage(), ); } else if (Configuration.instance.getKey() == null) { // Yet to decrypt the key - page = const PasswordReentryPage(); + page = PasswordReentryPage( + Configuration.instance, + const HomePage(), + ); } else { // All is well, user just has not subscribed page = const HomePage(); @@ -288,17 +293,22 @@ class _OnboardingPageState extends State { void _navigateToSignInPage() { Widget page; if (Configuration.instance.getEncryptedToken() == null) { - page = const LoginPage(); + page = LoginPage(Configuration.instance); } else { // No key if (Configuration.instance.getKeyAttributes() == null) { // Never had a key - page = const PasswordEntryPage( - mode: PasswordEntryMode.set, + page = PasswordEntryPage( + Configuration.instance, + PasswordEntryMode.set, + const HomePage(), ); } else if (Configuration.instance.getKey() == null) { // Yet to decrypt the key - page = const PasswordReentryPage(); + page = PasswordReentryPage( + Configuration.instance, + const HomePage(), + ); } else { // All is well, user just has not subscribed // page = getSubscriptionPage(isOnBoarding: true); diff --git a/mobile/apps/auth/lib/onboarding/view/setup_enter_secret_key_page.dart b/mobile/apps/auth/lib/onboarding/view/setup_enter_secret_key_page.dart index e06bcb934d..60cc814a9a 100644 --- a/mobile/apps/auth/lib/onboarding/view/setup_enter_secret_key_page.dart +++ b/mobile/apps/auth/lib/onboarding/view/setup_enter_secret_key_page.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/events/codes_updated_event.dart'; import "package:ente_auth/l10n/l10n.dart"; import 'package:ente_auth/models/all_icon_data.dart'; @@ -23,6 +22,7 @@ import 'package:ente_auth/ui/utils/icon_utils.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:ente_auth/utils/totp_util.dart'; +import 'package:ente_events/event_bus.dart'; import "package:flutter/material.dart"; import 'package:logging/logging.dart'; diff --git a/mobile/apps/auth/lib/services/authenticator_service.dart b/mobile/apps/auth/lib/services/authenticator_service.dart index 559e85f81a..77f814d828 100644 --- a/mobile/apps/auth/lib/services/authenticator_service.dart +++ b/mobile/apps/auth/lib/services/authenticator_service.dart @@ -4,9 +4,7 @@ import 'dart:math'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/errors.dart'; -import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/events/codes_updated_event.dart'; -import 'package:ente_auth/events/signed_in_event.dart'; import 'package:ente_auth/events/trigger_logout_event.dart'; import 'package:ente_auth/gateway/authenticator.dart'; import 'package:ente_auth/models/authenticator/auth_entity.dart'; @@ -17,6 +15,8 @@ import 'package:ente_auth/services/preference_service.dart'; import 'package:ente_auth/store/authenticator_db.dart'; import 'package:ente_auth/store/offline_authenticator_db.dart'; import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_events/event_bus.dart'; +import 'package:ente_events/models/signed_in_event.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:shared_preferences/shared_preferences.dart'; diff --git a/mobile/apps/auth/lib/services/billing_service.dart b/mobile/apps/auth/lib/services/billing_service.dart index 363425cd53..8aad05e5c7 100644 --- a/mobile/apps/auth/lib/services/billing_service.dart +++ b/mobile/apps/auth/lib/services/billing_service.dart @@ -1,11 +1,11 @@ import 'dart:io'; import 'package:dio/dio.dart'; +import 'package:ente_accounts/ente_accounts.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/errors.dart'; -import 'package:ente_auth/core/network.dart'; -import 'package:ente_auth/models/billing_plan.dart'; -import 'package:ente_auth/models/subscription.dart'; +import 'package:ente_auth/models/billing_plan.dart'; +import 'package:ente_network/network.dart'; import 'package:logging/logging.dart'; const kWebPaymentRedirectUrl = "https://payments.ente.io/frameRedirect"; diff --git a/mobile/apps/auth/lib/services/preference_service.dart b/mobile/apps/auth/lib/services/preference_service.dart index 773ee1b149..fd3b6d68c7 100644 --- a/mobile/apps/auth/lib/services/preference_service.dart +++ b/mobile/apps/auth/lib/services/preference_service.dart @@ -1,5 +1,5 @@ -import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/events/icons_changed_event.dart'; +import 'package:ente_events/event_bus.dart'; import 'package:shared_preferences/shared_preferences.dart'; enum CodeSortKey { diff --git a/mobile/apps/auth/lib/services/update_service.dart b/mobile/apps/auth/lib/services/update_service.dart index 716d553f1b..625c646556 100644 --- a/mobile/apps/auth/lib/services/update_service.dart +++ b/mobile/apps/auth/lib/services/update_service.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'dart:io'; import 'package:ente_auth/core/constants.dart'; -import 'package:ente_auth/core/network.dart'; import 'package:ente_auth/services/notification_service.dart'; import 'package:ente_auth/utils/platform_util.dart'; +import 'package:ente_network/network.dart'; import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; diff --git a/mobile/apps/auth/lib/store/authenticator_db.dart b/mobile/apps/auth/lib/store/authenticator_db.dart index cc47745d59..8e8ac11bb0 100644 --- a/mobile/apps/auth/lib/store/authenticator_db.dart +++ b/mobile/apps/auth/lib/store/authenticator_db.dart @@ -4,13 +4,14 @@ import 'dart:io'; import 'package:ente_auth/models/authenticator/auth_entity.dart'; import 'package:ente_auth/models/authenticator/local_auth_entity.dart'; import 'package:ente_auth/utils/directory_utils.dart'; +import 'package:ente_base/ente_base.dart'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; -class AuthenticatorDB { +class AuthenticatorDB extends EnteBaseDatabase { static const _databaseName = "ente.authenticator.db"; static const _databaseVersion = 1; diff --git a/mobile/apps/auth/lib/store/code_store.dart b/mobile/apps/auth/lib/store/code_store.dart index e6f984f3eb..8abb5657a4 100644 --- a/mobile/apps/auth/lib/store/code_store.dart +++ b/mobile/apps/auth/lib/store/code_store.dart @@ -3,12 +3,12 @@ import 'dart:convert'; import 'package:collection/collection.dart'; import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/events/codes_updated_event.dart'; import 'package:ente_auth/models/authenticator/entity_result.dart'; import 'package:ente_auth/models/code.dart'; import 'package:ente_auth/services/authenticator_service.dart'; import 'package:ente_auth/store/offline_authenticator_db.dart'; +import 'package:ente_events/event_bus.dart'; import 'package:logging/logging.dart'; class CodeStore { diff --git a/mobile/apps/auth/lib/ui/account/logout_dialog.dart b/mobile/apps/auth/lib/ui/account/logout_dialog.dart index bb5defbe78..a2e7af6e62 100644 --- a/mobile/apps/auth/lib/ui/account/logout_dialog.dart +++ b/mobile/apps/auth/lib/ui/account/logout_dialog.dart @@ -1,7 +1,7 @@ import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/store/authenticator_db.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; +import 'package:ente_auth/utils/dialog_util.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; diff --git a/mobile/apps/auth/lib/ui/code_widget.dart b/mobile/apps/auth/lib/ui/code_widget.dart index be9c2b8a69..df57d9b8a6 100644 --- a/mobile/apps/auth/lib/ui/code_widget.dart +++ b/mobile/apps/auth/lib/ui/code_widget.dart @@ -9,7 +9,6 @@ import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/models/code.dart'; import 'package:ente_auth/onboarding/view/setup_enter_secret_key_page.dart'; import 'package:ente_auth/onboarding/view/view_qr_page.dart'; -import 'package:ente_auth/services/local_authentication_service.dart'; import 'package:ente_auth/services/preference_service.dart'; import 'package:ente_auth/store/code_store.dart'; import 'package:ente_auth/theme/ente_theme.dart'; @@ -22,6 +21,7 @@ import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:ente_auth/utils/totp_util.dart'; +import 'package:ente_lock_screen/local_authentication_service.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_context_menu/flutter_context_menu.dart'; diff --git a/mobile/apps/auth/lib/ui/common/dialogs.dart b/mobile/apps/auth/lib/ui/common/dialogs.dart index 60879335e5..4f76bcf7c1 100644 --- a/mobile/apps/auth/lib/ui/common/dialogs.dart +++ b/mobile/apps/auth/lib/ui/common/dialogs.dart @@ -1,9 +1,9 @@ -import 'package:ente_auth/ente_theme_data.dart'; -import 'package:ente_auth/models/typedefs.dart'; +import 'package:ente_auth/ente_theme_data.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/components/dialog_widget.dart'; import 'package:ente_auth/ui/components/models/button_result.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; +import 'package:ente_base/typedefs.dart'; import 'package:flutter/material.dart'; enum DialogUserChoice { firstChoice, secondChoice } diff --git a/mobile/apps/auth/lib/ui/components/buttons/button_widget.dart b/mobile/apps/auth/lib/ui/components/buttons/button_widget.dart index 9e589cd7a0..10abb5c39f 100644 --- a/mobile/apps/auth/lib/ui/components/buttons/button_widget.dart +++ b/mobile/apps/auth/lib/ui/components/buttons/button_widget.dart @@ -1,5 +1,4 @@ -import 'package:ente_auth/models/execution_states.dart'; -import 'package:ente_auth/models/typedefs.dart'; +import 'package:ente_auth/models/execution_states.dart'; import 'package:ente_auth/theme/colors.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/theme/text_style.dart'; @@ -9,6 +8,7 @@ import 'package:ente_auth/ui/components/models/button_type.dart'; import 'package:ente_auth/ui/components/models/custom_button_style.dart'; import 'package:ente_auth/utils/debouncer.dart'; import "package:ente_auth/utils/dialog_util.dart"; +import 'package:ente_base/typedefs.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; diff --git a/mobile/apps/auth/lib/ui/components/dialog_widget.dart b/mobile/apps/auth/lib/ui/components/dialog_widget.dart index bebd5c9f42..8b8b417074 100644 --- a/mobile/apps/auth/lib/ui/components/dialog_widget.dart +++ b/mobile/apps/auth/lib/ui/components/dialog_widget.dart @@ -1,7 +1,6 @@ import 'dart:math'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/typedefs.dart'; +import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/theme/colors.dart'; import 'package:ente_auth/theme/effects.dart'; import 'package:ente_auth/theme/ente_theme.dart'; @@ -11,6 +10,7 @@ import 'package:ente_auth/ui/components/models/button_result.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; import 'package:ente_auth/ui/components/separators.dart'; import 'package:ente_auth/ui/components/text_input_widget.dart'; +import 'package:ente_base/typedefs.dart'; import 'package:flutter/material.dart'; ///Will return null if dismissed by tapping outside diff --git a/mobile/apps/auth/lib/ui/components/menu_item_widget.dart b/mobile/apps/auth/lib/ui/components/menu_item_widget.dart index f5d6e9dff7..ced35b51e1 100644 --- a/mobile/apps/auth/lib/ui/components/menu_item_widget.dart +++ b/mobile/apps/auth/lib/ui/components/menu_item_widget.dart @@ -1,8 +1,8 @@ -import 'package:ente_auth/models/execution_states.dart'; -import 'package:ente_auth/models/typedefs.dart'; +import 'package:ente_auth/models/execution_states.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/components/menu_item_child_widgets.dart'; import 'package:ente_auth/utils/debouncer.dart'; +import 'package:ente_base/typedefs.dart'; import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; diff --git a/mobile/apps/auth/lib/ui/components/text_input_widget.dart b/mobile/apps/auth/lib/ui/components/text_input_widget.dart index 2a06d3b71a..65c8b7fd11 100644 --- a/mobile/apps/auth/lib/ui/components/text_input_widget.dart +++ b/mobile/apps/auth/lib/ui/components/text_input_widget.dart @@ -1,9 +1,9 @@ -import 'package:ente_auth/models/execution_states.dart'; -import 'package:ente_auth/models/typedefs.dart'; +import 'package:ente_auth/models/execution_states.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/common/loading_widget.dart'; import 'package:ente_auth/ui/components/separators.dart'; import 'package:ente_auth/utils/debouncer.dart'; +import 'package:ente_base/typedefs.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:logging/logging.dart'; diff --git a/mobile/apps/auth/lib/ui/components/toggle_switch_widget.dart b/mobile/apps/auth/lib/ui/components/toggle_switch_widget.dart index dc1ca06c92..207bdcf905 100644 --- a/mobile/apps/auth/lib/ui/components/toggle_switch_widget.dart +++ b/mobile/apps/auth/lib/ui/components/toggle_switch_widget.dart @@ -1,9 +1,9 @@ import 'package:ente_auth/ente_theme_data.dart'; -import 'package:ente_auth/models/execution_states.dart'; -import 'package:ente_auth/models/typedefs.dart'; -import 'package:ente_auth/theme/colors.dart'; +import 'package:ente_auth/models/execution_states.dart'; +import 'package:ente_auth/theme/colors.dart'; import 'package:ente_auth/ui/common/loading_widget.dart'; import 'package:ente_auth/utils/debouncer.dart'; +import 'package:ente_base/typedefs.dart'; import 'package:flutter/material.dart'; typedef OnChangedCallBack = void Function(bool); diff --git a/mobile/apps/auth/lib/ui/home/coach_mark_widget.dart b/mobile/apps/auth/lib/ui/home/coach_mark_widget.dart index 43ec481700..1df21e431d 100644 --- a/mobile/apps/auth/lib/ui/home/coach_mark_widget.dart +++ b/mobile/apps/auth/lib/ui/home/coach_mark_widget.dart @@ -1,10 +1,10 @@ import 'dart:ui'; -import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/events/codes_updated_event.dart'; import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/services/preference_service.dart'; import 'package:ente_auth/utils/platform_util.dart'; +import 'package:ente_events/event_bus.dart'; import 'package:flutter/material.dart'; class CoachMarkWidget extends StatelessWidget { diff --git a/mobile/apps/auth/lib/ui/home_page.dart b/mobile/apps/auth/lib/ui/home_page.dart index 3a3d650259..32c8c2683f 100644 --- a/mobile/apps/auth/lib/ui/home_page.dart +++ b/mobile/apps/auth/lib/ui/home_page.dart @@ -3,8 +3,8 @@ import 'dart:io'; import 'package:app_links/app_links.dart'; import 'package:collection/collection.dart'; +import 'package:ente_accounts/services/user_service.dart'; import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/ente_theme_data.dart'; import 'package:ente_auth/events/codes_updated_event.dart'; import 'package:ente_auth/events/icons_changed_event.dart'; @@ -15,7 +15,6 @@ import 'package:ente_auth/onboarding/model/tag_enums.dart'; import 'package:ente_auth/onboarding/view/common/tag_chip.dart'; import 'package:ente_auth/onboarding/view/setup_enter_secret_key_page.dart'; import 'package:ente_auth/services/preference_service.dart'; -import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/store/code_display_store.dart'; import 'package:ente_auth/store/code_store.dart'; import 'package:ente_auth/theme/ente_theme.dart'; @@ -34,11 +33,13 @@ import 'package:ente_auth/ui/reorder_codes_page.dart'; import 'package:ente_auth/ui/scanner_page.dart'; import 'package:ente_auth/ui/settings_page.dart'; import 'package:ente_auth/ui/sort_option_menu.dart'; -import 'package:ente_auth/ui/tools/app_lock.dart'; import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/lock_screen_settings.dart'; import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/totp_util.dart'; +import 'package:ente_events/event_bus.dart'; +import 'package:ente_lock_screen/lock_screen_settings.dart'; +import 'package:ente_lock_screen/ui/app_lock.dart'; +import 'package:ente_ui/pages/base_home_page.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -47,7 +48,7 @@ import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:logging/logging.dart'; import 'package:move_to_background/move_to_background.dart'; -class HomePage extends StatefulWidget { +class HomePage extends BaseHomePage { const HomePage({super.key}); @override diff --git a/mobile/apps/auth/lib/ui/scanner_page.dart b/mobile/apps/auth/lib/ui/scanner_page.dart index a0f88b7c87..877920390e 100644 --- a/mobile/apps/auth/lib/ui/scanner_page.dart +++ b/mobile/apps/auth/lib/ui/scanner_page.dart @@ -4,7 +4,7 @@ import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/models/code.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:flutter/material.dart'; -import 'package:qr_code_scanner/qr_code_scanner.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; class ScannerPage extends StatefulWidget { const ScannerPage({super.key}); diff --git a/mobile/apps/auth/lib/ui/settings/account_section_widget.dart b/mobile/apps/auth/lib/ui/settings/account_section_widget.dart index d28dc5a0fd..2ab0e978be 100644 --- a/mobile/apps/auth/lib/ui/settings/account_section_widget.dart +++ b/mobile/apps/auth/lib/ui/settings/account_section_widget.dart @@ -1,20 +1,21 @@ +import 'package:ente_accounts/pages/change_email_dialog.dart'; +import 'package:ente_accounts/pages/delete_account_page.dart'; +import 'package:ente_accounts/pages/password_entry_page.dart'; +import 'package:ente_accounts/pages/recovery_key_page.dart'; +import 'package:ente_accounts/services/user_service.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/services/local_authentication_service.dart'; -import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/theme/ente_theme.dart'; -import 'package:ente_auth/ui/account/change_email_dialog.dart'; -import 'package:ente_auth/ui/account/delete_account_page.dart'; -import 'package:ente_auth/ui/account/password_entry_page.dart'; -import 'package:ente_auth/ui/account/recovery_key_page.dart'; import 'package:ente_auth/ui/components/captioned_text_widget.dart'; import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart'; import 'package:ente_auth/ui/components/menu_item_widget.dart'; +import 'package:ente_auth/ui/home_page.dart'; import 'package:ente_auth/ui/settings/common_settings.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_lock_screen/local_authentication_service.dart'; import 'package:flutter/material.dart'; class AccountSectionWidget extends StatelessWidget { @@ -81,8 +82,10 @@ class AccountSectionWidget extends StatelessWidget { Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) { - return const PasswordEntryPage( - mode: PasswordEntryMode.update, + return PasswordEntryPage( + Configuration.instance, + PasswordEntryMode.update, + const HomePage(), ); }, ), @@ -121,6 +124,7 @@ class AccountSectionWidget extends StatelessWidget { routeToPage( context, RecoveryKeyPage( + Configuration.instance, recoveryKey, l10n.ok, showAppBar: true, @@ -151,8 +155,9 @@ class AccountSectionWidget extends StatelessWidget { trailingIcon: Icons.chevron_right_outlined, trailingIconIsMuted: true, onTap: () async { + final config = Configuration.instance; // ignore: unawaited_futures - routeToPage(context, const DeleteAccountPage()); + routeToPage(context, DeleteAccountPage(config)); }, ), sectionOptionSpacing, diff --git a/mobile/apps/auth/lib/ui/settings/data/duplicate_code_page.dart b/mobile/apps/auth/lib/ui/settings/data/duplicate_code_page.dart index 4dcfaf3409..6e1d6da25d 100644 --- a/mobile/apps/auth/lib/ui/settings/data/duplicate_code_page.dart +++ b/mobile/apps/auth/lib/ui/settings/data/duplicate_code_page.dart @@ -1,11 +1,11 @@ import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/models/code.dart'; import 'package:ente_auth/services/deduplication_service.dart'; -import 'package:ente_auth/services/local_authentication_service.dart'; import 'package:ente_auth/store/code_store.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/code_widget.dart'; import 'package:ente_auth/utils/dialog_util.dart'; +import 'package:ente_lock_screen/local_authentication_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:logging/logging.dart'; diff --git a/mobile/apps/auth/lib/ui/settings/data/export_widget.dart b/mobile/apps/auth/lib/ui/settings/data/export_widget.dart index d385119421..0f635bada3 100644 --- a/mobile/apps/auth/lib/ui/settings/data/export_widget.dart +++ b/mobile/apps/auth/lib/ui/settings/data/export_widget.dart @@ -2,8 +2,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/export/ente.dart'; -import 'package:ente_auth/services/local_authentication_service.dart'; +import 'package:ente_auth/models/export/ente.dart'; import 'package:ente_auth/store/code_store.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/components/dialog_widget.dart'; @@ -14,6 +13,7 @@ import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/share_utils.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_lock_screen/local_authentication_service.dart'; import 'package:file_saver/file_saver.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; diff --git a/mobile/apps/auth/lib/ui/settings/general_section_widget.dart b/mobile/apps/auth/lib/ui/settings/general_section_widget.dart index cb29d8483a..8475f45c3a 100644 --- a/mobile/apps/auth/lib/ui/settings/general_section_widget.dart +++ b/mobile/apps/auth/lib/ui/settings/general_section_widget.dart @@ -1,8 +1,6 @@ import 'dart:io'; import 'package:ente_auth/app/view/app.dart'; -import 'package:ente_auth/core/event_bus.dart'; -import 'package:ente_auth/core/logging/super_logging.dart'; import 'package:ente_auth/events/icons_changed_event.dart'; import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/locale.dart'; @@ -16,6 +14,8 @@ import 'package:ente_auth/ui/settings/common_settings.dart'; import 'package:ente_auth/ui/settings/language_picker.dart'; import 'package:ente_auth/utils/navigation_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_events/event_bus.dart'; +import 'package:ente_logging/logging.dart'; import 'package:flutter/material.dart'; class AdvancedSectionWidget extends StatefulWidget { diff --git a/mobile/apps/auth/lib/ui/settings/notification_banner_widget.dart b/mobile/apps/auth/lib/ui/settings/notification_banner_widget.dart index 1c30dd2bbc..83162b04f8 100644 --- a/mobile/apps/auth/lib/ui/settings/notification_banner_widget.dart +++ b/mobile/apps/auth/lib/ui/settings/notification_banner_widget.dart @@ -1,10 +1,10 @@ import 'dart:io'; +import 'package:ente_accounts/models/user_details.dart'; +import 'package:ente_accounts/services/user_service.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/user_details.dart'; import 'package:ente_auth/services/preference_service.dart'; -import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/ui/components/banner_widget.dart'; import 'package:flutter/material.dart'; diff --git a/mobile/apps/auth/lib/ui/settings/security_section_widget.dart b/mobile/apps/auth/lib/ui/settings/security_section_widget.dart index dab16cb56f..7406a9fd94 100644 --- a/mobile/apps/auth/lib/ui/settings/security_section_widget.dart +++ b/mobile/apps/auth/lib/ui/settings/security_section_widget.dart @@ -1,15 +1,14 @@ import 'dart:async'; import 'dart:typed_data'; +import 'package:ente_accounts/models/user_details.dart'; +import 'package:ente_accounts/pages/request_pwd_verification_page.dart'; +import 'package:ente_accounts/pages/sessions_page.dart'; +import 'package:ente_accounts/services/passkey_service.dart'; +import 'package:ente_accounts/services/user_service.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/user_details.dart'; -import 'package:ente_auth/services/local_authentication_service.dart'; -import 'package:ente_auth/services/passkey_service.dart'; -import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/theme/ente_theme.dart'; -import 'package:ente_auth/ui/account/request_pwd_verification_page.dart'; -import 'package:ente_auth/ui/account/sessions_page.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/components/captioned_text_widget.dart'; import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart'; @@ -17,14 +16,15 @@ import 'package:ente_auth/ui/components/menu_item_widget.dart'; import 'package:ente_auth/ui/components/models/button_result.dart'; import 'package:ente_auth/ui/components/toggle_switch_widget.dart'; import 'package:ente_auth/ui/settings/common_settings.dart'; -import 'package:ente_auth/ui/settings/lock_screen/lock_screen_options.dart'; -import 'package:ente_auth/utils/auth_util.dart'; import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/lock_screen_settings.dart'; import 'package:ente_auth/utils/navigation_util.dart'; import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_lock_screen/auth_util.dart'; +import 'package:ente_lock_screen/local_authentication_service.dart'; +import 'package:ente_lock_screen/lock_screen_settings.dart'; +import 'package:ente_lock_screen/ui/lock_screen_options.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; @@ -128,7 +128,7 @@ class _SecuritySectionWidgetState extends State { Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) { - return const SessionsPage(); + return SessionsPage(Configuration.instance); }, ), ); @@ -243,6 +243,7 @@ class _SecuritySectionWidgetState extends State { await routeToPage( context, RequestPasswordVerificationPage( + Configuration.instance, onPasswordVerified: (Uint8List keyEncryptionKey) async { final Uint8List loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey); diff --git a/mobile/apps/auth/lib/ui/settings_page.dart b/mobile/apps/auth/lib/ui/settings_page.dart index eb6ffca0fe..d1965eb009 100644 --- a/mobile/apps/auth/lib/ui/settings_page.dart +++ b/mobile/apps/auth/lib/ui/settings_page.dart @@ -1,10 +1,9 @@ import 'dart:io'; +import 'package:ente_accounts/services/user_service.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/onboarding/view/onboarding_page.dart'; -import 'package:ente_auth/services/local_authentication_service.dart'; -import 'package:ente_auth/services/user_service.dart'; +import 'package:ente_auth/onboarding/view/onboarding_page.dart'; import 'package:ente_auth/store/code_store.dart'; import 'package:ente_auth/theme/colors.dart'; import 'package:ente_auth/theme/ente_theme.dart'; @@ -27,6 +26,7 @@ import 'package:ente_auth/ui/settings/title_bar_widget.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; import 'package:ente_auth/utils/platform_util.dart'; +import 'package:ente_lock_screen/local_authentication_service.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/mobile/apps/auth/lib/utils/dialog_util.dart b/mobile/apps/auth/lib/utils/dialog_util.dart index dca3b9d1f9..538c0835cd 100644 --- a/mobile/apps/auth/lib/utils/dialog_util.dart +++ b/mobile/apps/auth/lib/utils/dialog_util.dart @@ -3,8 +3,7 @@ import 'dart:math'; import 'package:confetti/confetti.dart'; import "package:dio/dio.dart"; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/typedefs.dart'; +import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/theme/colors.dart'; import 'package:ente_auth/ui/common/loading_widget.dart'; import 'package:ente_auth/ui/common/progress_dialog.dart'; @@ -16,6 +15,7 @@ import 'package:ente_auth/ui/components/models/button_result.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; import 'package:ente_auth/utils/email_util.dart'; import 'package:ente_auth/utils/platform_util.dart'; +import 'package:ente_base/typedefs.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/mobile/apps/auth/lib/utils/email_util.dart b/mobile/apps/auth/lib/utils/email_util.dart index 4847e726fe..4fd0c563eb 100644 --- a/mobile/apps/auth/lib/utils/email_util.dart +++ b/mobile/apps/auth/lib/utils/email_util.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:archive/archive_io.dart'; import 'package:email_validator/email_validator.dart'; import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/logging/super_logging.dart'; import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/components/dialog_widget.dart'; @@ -14,11 +13,11 @@ import 'package:ente_auth/utils/directory_utils.dart'; import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/share_utils.dart'; import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_logging/logging.dart'; import "package:file_saver/file_saver.dart"; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import "package:intl/intl.dart"; -import 'package:logging/logging.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; diff --git a/mobile/apps/auth/lib/utils/platform_util.dart b/mobile/apps/auth/lib/utils/platform_util.dart index bad3c8e738..40ae35ae53 100644 --- a/mobile/apps/auth/lib/utils/platform_util.dart +++ b/mobile/apps/auth/lib/utils/platform_util.dart @@ -59,14 +59,14 @@ class PlatformUtil { if (Platform.isAndroid || Platform.isIOS) { await FileSaver.instance.saveAs( name: fileName, - ext: extension, + fileExtension: extension, bytes: bytes, mimeType: type, ); } else { await FileSaver.instance.saveFile( name: fileName, - ext: extension, + fileExtension: extension, bytes: bytes, mimeType: type, ); diff --git a/mobile/apps/auth/pubspec.lock b/mobile/apps/auth/pubspec.lock index b8a9a9f6f0..8a8b4fd6b1 100644 --- a/mobile/apps/auth/pubspec.lock +++ b/mobile/apps/auth/pubspec.lock @@ -393,6 +393,27 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + ente_accounts: + dependency: "direct main" + description: + path: "../../packages/accounts" + relative: true + source: path + version: "1.0.0" + ente_base: + dependency: "direct main" + description: + path: "../../packages/base" + relative: true + source: path + version: "1.0.0" + ente_configuration: + dependency: "direct main" + description: + path: "../../packages/configuration" + relative: true + source: path + version: "1.0.0" ente_crypto_dart: dependency: "direct main" description: @@ -402,6 +423,55 @@ packages: url: "https://github.com/ente-io/ente_crypto_dart.git" source: git version: "1.0.0" + ente_events: + dependency: "direct main" + description: + path: "../../packages/events" + relative: true + source: path + version: "1.0.0" + ente_lock_screen: + dependency: "direct main" + description: + path: "../../packages/lock_screen" + relative: true + source: path + version: "1.0.0" + ente_logging: + dependency: "direct main" + description: + path: "../../packages/logging" + relative: true + source: path + version: "1.0.0" + ente_network: + dependency: "direct main" + description: + path: "../../packages/network" + relative: true + source: path + version: "1.0.0" + ente_strings: + dependency: "direct main" + description: + path: "../../packages/strings" + relative: true + source: path + version: "1.0.0" + ente_ui: + dependency: "direct main" + description: + path: "../../packages/ui" + relative: true + source: path + version: "1.0.0" + ente_utils: + dependency: transitive + description: + path: "../../packages/utils" + relative: true + source: path + version: "1.0.0" event_bus: dependency: "direct main" description: @@ -470,10 +540,10 @@ packages: dependency: "direct main" description: name: file_saver - sha256: "448b1e30142cffe52f37ee085ea9ca50670d5425bb09b649d193549b2dcf6e26" + sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.3.1" fixnum: dependency: "direct main" description: @@ -485,10 +555,11 @@ packages: fk_user_agent: dependency: "direct main" description: - name: fk_user_agent - sha256: fd6c94e120786985a292d12f61422a581f4e851148d5940af38b819357b8ad0d - url: "https://pub.dev" - source: hosted + path: "." + ref: "458046cd9a88924e5074d96ba45397219d53b230" + resolved-ref: "458046cd9a88924e5074d96ba45397219d53b230" + url: "https://github.com/flutter-fast-kit/fk_user_agent" + source: git version: "2.1.0" flutter: dependency: "direct main" @@ -538,21 +609,19 @@ packages: flutter_inappwebview: dependency: "direct main" description: - path: flutter_inappwebview - ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - url: "https://github.com/pichillilorenzo/flutter_inappwebview.git" - source: git - version: "6.2.0-beta.3" + name: flutter_inappwebview + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" + url: "https://pub.dev" + source: hosted + version: "6.1.5" flutter_inappwebview_android: dependency: transitive description: - path: flutter_inappwebview_android - ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - url: "https://github.com/pichillilorenzo/flutter_inappwebview.git" - source: git - version: "1.2.0-beta.3" + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" + url: "https://pub.dev" + source: hosted + version: "1.1.3" flutter_inappwebview_internal_annotations: dependency: transitive description: @@ -564,48 +633,43 @@ packages: flutter_inappwebview_ios: dependency: transitive description: - path: flutter_inappwebview_ios - ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - url: "https://github.com/pichillilorenzo/flutter_inappwebview.git" - source: git - version: "1.2.0-beta.3" + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" flutter_inappwebview_macos: dependency: transitive description: - path: flutter_inappwebview_macos - ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - url: "https://github.com/pichillilorenzo/flutter_inappwebview.git" - source: git - version: "1.2.0-beta.3" + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 + url: "https://pub.dev" + source: hosted + version: "1.1.2" flutter_inappwebview_platform_interface: dependency: transitive description: - path: flutter_inappwebview_platform_interface - ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - url: "https://github.com/pichillilorenzo/flutter_inappwebview.git" - source: git - version: "1.4.0-beta.3" + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" flutter_inappwebview_web: dependency: transitive description: - path: flutter_inappwebview_web - ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - url: "https://github.com/pichillilorenzo/flutter_inappwebview.git" - source: git - version: "1.2.0-beta.3" + name: flutter_inappwebview_web + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" + url: "https://pub.dev" + source: hosted + version: "1.1.2" flutter_inappwebview_windows: dependency: transitive description: - path: flutter_inappwebview_windows - ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26" - url: "https://github.com/pichillilorenzo/flutter_inappwebview.git" - source: git - version: "0.7.0-beta.3" + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" flutter_launcher_icons: dependency: "direct main" description: @@ -1081,10 +1145,11 @@ packages: move_to_background: dependency: "direct main" description: - name: move_to_background - sha256: "00caad17a6ce149910777131503f43f8ed80025681f94684e3a6a87d979b914c" - url: "https://pub.dev" - source: hosted + path: "." + ref: "91e4d1a9c55b28bf93425d1f12faf410efc1e48d" + resolved-ref: "91e4d1a9c55b28bf93425d1f12faf410efc1e48d" + url: "https://github.com/Sayegh7/move_to_background" + source: git version: "1.0.2" native_dio_adapter: dependency: "direct main" @@ -1329,11 +1394,12 @@ packages: qr_code_scanner: dependency: "direct main" description: - name: qr_code_scanner - sha256: f23b68d893505a424f0bd2e324ebea71ed88465d572d26bb8d2e78a4749591fd - url: "https://pub.dev" - source: hosted - version: "1.0.1" + path: "." + ref: a2d31633d4744f72ada87cfa85d221358ab082af + resolved-ref: a2d31633d4744f72ada87cfa85d221358ab082af + url: "https://github.com/juliuscanute/qr_code_scanner" + source: git + version: "1.0.0" qr_flutter: dependency: "direct main" description: @@ -1731,6 +1797,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + ua_client_hints: + dependency: transitive + description: + name: ua_client_hints + sha256: "1b8759a46bfeab355252881df27f2604c01bded86aa2b578869fb1b638b23118" + url: "https://pub.dev" + source: hosted + version: "1.4.1" universal_io: dependency: transitive description: diff --git a/mobile/apps/auth/pubspec.yaml b/mobile/apps/auth/pubspec.yaml index 4feeb58eb4..8145cada7d 100644 --- a/mobile/apps/auth/pubspec.yaml +++ b/mobile/apps/auth/pubspec.yaml @@ -1,19 +1,18 @@ - name: ente_auth description: ente two-factor authenticator -version: 4.4.3+443 +version: 4.4.6+446 publish_to: none environment: sdk: ">=3.0.0 <4.0.0" dependencies: - adaptive_theme: ^3.1.0 # done + adaptive_theme: ^3.1.0 app_links: ^6.3.3 archive: ^4.0.7 auto_size_text: ^3.0.0 base32: ^2.1.3 - bip39: ^1.0.6 #done + bip39: ^1.0.6 bloc: ^9.0.0 clipboard: ^0.1.3 collection: ^1.18.0 # dart @@ -25,19 +24,39 @@ dependencies: dotted_border: ^3.1.0 dropdown_button2: ^2.3.9 email_validator: ^3.0.0 + ente_accounts: + path: ../../packages/accounts + ente_base: + path: ../../packages/base + ente_configuration: + path: ../../packages/configuration ente_crypto_dart: git: url: https://github.com/ente-io/ente_crypto_dart.git + ente_events: + path: ../../packages/events + ente_lock_screen: + path: ../../packages/lock_screen + ente_logging: + path: ../../packages/logging + ente_network: + path: ../../packages/network + ente_strings: + path: ../../packages/strings + ente_ui: + path: ../../packages/ui event_bus: ^2.0.0 expandable: ^5.0.1 expansion_tile_card: ^3.0.0 ffi: ^2.1.0 figma_squircle: ^0.6.3 file_picker: ^10.2.0 - # https://github.com/incrediblezayed/file_saver/issues/86 - file_saver: ^0.3.0 + file_saver: ^0.3.1 fixnum: ^1.1.0 - fk_user_agent: ^2.1.0 + fk_user_agent: # no package updates on pub.dev + git: + url: https://github.com/flutter-fast-kit/fk_user_agent + ref: 458046cd9a88924e5074d96ba45397219d53b230 flutter: sdk: flutter flutter_animate: ^4.1.0 @@ -47,13 +66,9 @@ dependencies: flutter_email_sender: ^7.0.0 # revert to pub.dev when merged # https://github.com/pichillilorenzo/flutter_inappwebview/pull/2548 - flutter_inappwebview: - git: - url: https://github.com/pichillilorenzo/flutter_inappwebview.git - path: flutter_inappwebview - ref: 3e6c4c4a25340cd363af9d38891d88498b90be26 + flutter_inappwebview: ^6.1.4 flutter_launcher_icons: ^0.14.1 - flutter_local_authentication: + flutter_local_authentication: # linux fprintd fix is not published on pub.dev git: url: https://github.com/eaceto/flutter_local_authentication ref: 1ac346a04592a05fd75acccf2e01fa3c7e955d96 @@ -77,7 +92,10 @@ dependencies: local_auth_darwin: ^1.2.2 logging: ^1.0.1 modal_bottom_sheet: ^3.0.0 - move_to_background: ^1.0.2 + move_to_background: # no package updates on pub.dev + git: + url: https://github.com/Sayegh7/move_to_background + ref: 91e4d1a9c55b28bf93425d1f12faf410efc1e48d native_dio_adapter: ^1.4.0 otp: ^3.1.1 package_info_plus: ^8.0.2 @@ -88,8 +106,11 @@ dependencies: pointycastle: ^3.7.3 privacy_screen: ^0.0.6 protobuf: ^4.1.0 - qr_code_scanner: ^1.0.1 - qr_flutter: ^4.1.0 + qr_code_scanner: # no package updates on pub.dev + git: + url: https://github.com/juliuscanute/qr_code_scanner + ref: a2d31633d4744f72ada87cfa85d221358ab082af + qr_flutter: ^4.1.0 sentry: ^8.14.2 sentry_flutter: ^8.14.2 share_plus: ^11.0.0 diff --git a/mobile/apps/locker/.gitignore b/mobile/apps/locker/.gitignore new file mode 100644 index 0000000000..9e1d55375a --- /dev/null +++ b/mobile/apps/locker/.gitignore @@ -0,0 +1,54 @@ +# Let folks use their custom editor settings +.vscode +.idea + +# macOS +.DS_Store + +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# Editors +.vscode/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages + +android/key.properties +android/app/.settings/* +android/.settings/ +.env + +fastlane/report.xml + +# Android related +android/app/build/ + +devtools_options.yaml diff --git a/mobile/apps/locker/README.md b/mobile/apps/locker/README.md new file mode 100644 index 0000000000..29c7d095bf --- /dev/null +++ b/mobile/apps/locker/README.md @@ -0,0 +1 @@ +# soon. diff --git a/mobile/apps/locker/analysis_options.yaml b/mobile/apps/locker/analysis_options.yaml new file mode 100644 index 0000000000..1bd78bc1b0 --- /dev/null +++ b/mobile/apps/locker/analysis_options.yaml @@ -0,0 +1,72 @@ +# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html +# or https://pub.dev/packages/lint (Effective dart) +# use "flutter analyze ." or "dart analyze ." for running lint checks + +include: package:flutter_lints/flutter.yaml +linter: + rules: + # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml + # Ref https://dart-lang.github.io/linter/lints/ + - avoid_print + - avoid_unnecessary_containers + - avoid_web_libraries_in_flutter + - no_logic_in_create_state + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_final_locals + - require_trailing_commas + - sized_box_for_whitespace + - use_full_hex_values_for_flutter_colors + - use_key_in_widget_constructors + - cancel_subscriptions + + + - avoid_empty_else + - exhaustive_cases + + # just style suggestions + - sort_pub_dependencies + - use_rethrow_when_possible + - prefer_double_quotes + - directives_ordering + - always_use_package_imports + - sort_child_properties_last + - unawaited_futures + +analyzer: + errors: + avoid_empty_else: error + exhaustive_cases: error + curly_braces_in_flow_control_structures: error + directives_ordering: error + require_trailing_commas: error + always_use_package_imports: warning + prefer_final_fields: error + unused_import: error + camel_case_types: error + prefer_is_empty: warning + use_rethrow_when_possible: info + unused_field: warning + use_key_in_widget_constructors: warning + sort_child_properties_last: warning + sort_pub_dependencies: warning + library_private_types_in_public_api: warning + constant_identifier_names: ignore + prefer_const_constructors: warning + prefer_const_declarations: warning + prefer_const_constructors_in_immutables: warning + prefer_final_locals: warning + unnecessary_const: error + cancel_subscriptions: error + unrelated_type_equality_checks: error + unnecessary_cast: info + + + unawaited_futures: warning # convert to warning after fixing existing issues + invalid_dependency: info + use_build_context_synchronously: ignore # experimental lint, requires many changes + prefer_interpolation_to_compose_strings: ignore # later too many warnings + prefer_double_quotes: ignore # too many warnings + avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides diff --git a/mobile/apps/locker/android/.gitignore b/mobile/apps/locker/android/.gitignore new file mode 100644 index 0000000000..55afd919c6 --- /dev/null +++ b/mobile/apps/locker/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/mobile/apps/locker/android/app/build.gradle b/mobile/apps/locker/android/app/build.gradle new file mode 100644 index 0000000000..6aa7b5d563 --- /dev/null +++ b/mobile/apps/locker/android/app/build.gradle @@ -0,0 +1,44 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "io.ente.locker" + compileSdk = 35 + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "io.ente.locker" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = 26 + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/mobile/apps/locker/android/app/src/debug/AndroidManifest.xml b/mobile/apps/locker/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000000..399f6981d5 --- /dev/null +++ b/mobile/apps/locker/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile/apps/locker/android/app/src/main/AndroidManifest.xml b/mobile/apps/locker/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..97161a63ea --- /dev/null +++ b/mobile/apps/locker/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/locker/android/app/src/main/kotlin/io/ente/locker/MainActivity.kt b/mobile/apps/locker/android/app/src/main/kotlin/io/ente/locker/MainActivity.kt new file mode 100644 index 0000000000..8cf01b258d --- /dev/null +++ b/mobile/apps/locker/android/app/src/main/kotlin/io/ente/locker/MainActivity.kt @@ -0,0 +1,5 @@ +package io.ente.locker + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/mobile/apps/locker/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile/apps/locker/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000000..f74085f3f6 --- /dev/null +++ b/mobile/apps/locker/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile/apps/locker/android/app/src/main/res/drawable/launch_background.xml b/mobile/apps/locker/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000000..304732f884 --- /dev/null +++ b/mobile/apps/locker/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile/apps/locker/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/apps/locker/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..db77bb4b7b Binary files /dev/null and b/mobile/apps/locker/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/mobile/apps/locker/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/apps/locker/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..17987b79bb Binary files /dev/null and b/mobile/apps/locker/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/mobile/apps/locker/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile/apps/locker/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..09d4391482 Binary files /dev/null and b/mobile/apps/locker/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/mobile/apps/locker/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/apps/locker/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..d5f1c8d34e Binary files /dev/null and b/mobile/apps/locker/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/mobile/apps/locker/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/apps/locker/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..4d6372eebd Binary files /dev/null and b/mobile/apps/locker/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/mobile/apps/locker/android/app/src/main/res/values-night/styles.xml b/mobile/apps/locker/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000000..06952be745 --- /dev/null +++ b/mobile/apps/locker/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile/apps/locker/android/app/src/main/res/values/styles.xml b/mobile/apps/locker/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000..cb1ef88056 --- /dev/null +++ b/mobile/apps/locker/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile/apps/locker/android/app/src/profile/AndroidManifest.xml b/mobile/apps/locker/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000000..399f6981d5 --- /dev/null +++ b/mobile/apps/locker/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile/apps/locker/android/build.gradle b/mobile/apps/locker/android/build.gradle new file mode 100644 index 0000000000..d2ffbffa4c --- /dev/null +++ b/mobile/apps/locker/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/mobile/apps/locker/android/gradle.properties b/mobile/apps/locker/android/gradle.properties new file mode 100644 index 0000000000..2597170821 --- /dev/null +++ b/mobile/apps/locker/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/mobile/apps/locker/android/gradle/wrapper/gradle-wrapper.properties b/mobile/apps/locker/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..7bb2df6ba6 --- /dev/null +++ b/mobile/apps/locker/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/mobile/apps/locker/android/settings.gradle b/mobile/apps/locker/android/settings.gradle new file mode 100644 index 0000000000..b9e43bd376 --- /dev/null +++ b/mobile/apps/locker/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/mobile/apps/locker/assets/fonts/Inter-Bold.ttf b/mobile/apps/locker/assets/fonts/Inter-Bold.ttf new file mode 100644 index 0000000000..7e1deec31e Binary files /dev/null and b/mobile/apps/locker/assets/fonts/Inter-Bold.ttf differ diff --git a/mobile/apps/locker/assets/fonts/Inter-Light.ttf b/mobile/apps/locker/assets/fonts/Inter-Light.ttf new file mode 100644 index 0000000000..ebaa005740 Binary files /dev/null and b/mobile/apps/locker/assets/fonts/Inter-Light.ttf differ diff --git a/mobile/apps/locker/assets/fonts/Inter-Medium.ttf b/mobile/apps/locker/assets/fonts/Inter-Medium.ttf new file mode 100644 index 0000000000..7e573f6498 Binary files /dev/null and b/mobile/apps/locker/assets/fonts/Inter-Medium.ttf differ diff --git a/mobile/apps/locker/assets/fonts/Inter-Regular.ttf b/mobile/apps/locker/assets/fonts/Inter-Regular.ttf new file mode 100644 index 0000000000..012d1b470d Binary files /dev/null and b/mobile/apps/locker/assets/fonts/Inter-Regular.ttf differ diff --git a/mobile/apps/locker/assets/fonts/Inter-SemiBold.ttf b/mobile/apps/locker/assets/fonts/Inter-SemiBold.ttf new file mode 100644 index 0000000000..4be54399d6 Binary files /dev/null and b/mobile/apps/locker/assets/fonts/Inter-SemiBold.ttf differ diff --git a/mobile/apps/locker/assets/fonts/Montserrat-Bold.ttf b/mobile/apps/locker/assets/fonts/Montserrat-Bold.ttf new file mode 100644 index 0000000000..55e0b1a553 Binary files /dev/null and b/mobile/apps/locker/assets/fonts/Montserrat-Bold.ttf differ diff --git a/mobile/apps/locker/assets/locker.png b/mobile/apps/locker/assets/locker.png new file mode 100644 index 0000000000..d8174de79e Binary files /dev/null and b/mobile/apps/locker/assets/locker.png differ diff --git a/mobile/apps/locker/ios/.gitignore b/mobile/apps/locker/ios/.gitignore new file mode 100644 index 0000000000..7a7f9873ad --- /dev/null +++ b/mobile/apps/locker/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/mobile/apps/locker/ios/Flutter/AppFrameworkInfo.plist b/mobile/apps/locker/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000000..7c56964006 --- /dev/null +++ b/mobile/apps/locker/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/mobile/apps/locker/ios/Flutter/Debug.xcconfig b/mobile/apps/locker/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000000..ec97fc6f30 --- /dev/null +++ b/mobile/apps/locker/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/mobile/apps/locker/ios/Flutter/Release.xcconfig b/mobile/apps/locker/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000000..c4855bfe20 --- /dev/null +++ b/mobile/apps/locker/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/mobile/apps/locker/ios/Podfile b/mobile/apps/locker/ios/Podfile new file mode 100644 index 0000000000..7e99f94391 --- /dev/null +++ b/mobile/apps/locker/ios/Podfile @@ -0,0 +1,59 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end + target 'Share Extension' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + '$(inherited)', + + ## dart: PermissionGroup.camera + 'PERMISSION_CAMERA=1', + + ## dart: PermissionGroup.photos + 'PERMISSION_PHOTOS=1', + ] + end + end +end diff --git a/mobile/apps/locker/ios/Podfile.lock b/mobile/apps/locker/ios/Podfile.lock new file mode 100644 index 0000000000..a6199b577f --- /dev/null +++ b/mobile/apps/locker/ios/Podfile.lock @@ -0,0 +1,225 @@ +PODS: + - app_links (0.0.2): + - Flutter + - cupertino_http (0.0.1): + - Flutter + - FlutterMacOS + - device_info_plus (0.0.1): + - Flutter + - DKImagePickerController/Core (4.3.9): + - DKImagePickerController/ImageDataManager + - DKImagePickerController/Resource + - DKImagePickerController/ImageDataManager (4.3.9) + - DKImagePickerController/PhotoGallery (4.3.9): + - DKImagePickerController/Core + - DKPhotoGallery + - DKImagePickerController/Resource (4.3.9) + - DKPhotoGallery (0.0.19): + - DKPhotoGallery/Core (= 0.0.19) + - DKPhotoGallery/Model (= 0.0.19) + - DKPhotoGallery/Preview (= 0.0.19) + - DKPhotoGallery/Resource (= 0.0.19) + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Core (0.0.19): + - DKPhotoGallery/Model + - DKPhotoGallery/Preview + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Model (0.0.19): + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Preview (0.0.19): + - DKPhotoGallery/Model + - DKPhotoGallery/Resource + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Resource (0.0.19): + - SDWebImage + - SwiftyGif + - file_picker (0.0.1): + - DKImagePickerController/PhotoGallery + - Flutter + - file_saver (0.0.1): + - Flutter + - Flutter (1.0.0) + - flutter_email_sender (0.0.1): + - Flutter + - flutter_inappwebview_ios (0.0.1): + - Flutter + - flutter_inappwebview_ios/Core (= 0.0.1) + - OrderedSet (~> 6.0.3) + - flutter_inappwebview_ios/Core (0.0.1): + - Flutter + - OrderedSet (~> 6.0.3) + - flutter_local_authentication (1.2.0): + - Flutter + - flutter_secure_storage (6.0.0): + - Flutter + - fluttertoast (0.0.2): + - Flutter + - listen_sharing_intent (1.9.2): + - Flutter + - local_auth_darwin (0.0.1): + - Flutter + - FlutterMacOS + - objective_c (0.0.1): + - Flutter + - open_file_ios (0.0.1): + - Flutter + - OrderedSet (6.0.3) + - package_info_plus (0.4.5): + - Flutter + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - privacy_screen (0.0.1): + - Flutter + - SDWebImage (5.21.0): + - SDWebImage/Core (= 5.21.0) + - SDWebImage/Core (5.21.0) + - Sentry/HybridSDK (8.46.0) + - sentry_flutter (8.14.2): + - Flutter + - FlutterMacOS + - Sentry/HybridSDK (= 8.46.0) + - share_plus (0.0.1): + - Flutter + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sodium_libs (2.2.1): + - Flutter + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + - SwiftyGif (5.4.5) + - ua_client_hints (1.4.1): + - Flutter + - url_launcher_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - app_links (from `.symlinks/plugins/app_links/ios`) + - cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) + - file_picker (from `.symlinks/plugins/file_picker/ios`) + - file_saver (from `.symlinks/plugins/file_saver/ios`) + - Flutter (from `Flutter`) + - flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`) + - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) + - flutter_local_authentication (from `.symlinks/plugins/flutter_local_authentication/ios`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - listen_sharing_intent (from `.symlinks/plugins/listen_sharing_intent/ios`) + - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) + - objective_c (from `.symlinks/plugins/objective_c/ios`) + - open_file_ios (from `.symlinks/plugins/open_file_ios/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - privacy_screen (from `.symlinks/plugins/privacy_screen/ios`) + - 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`) + - sodium_libs (from `.symlinks/plugins/sodium_libs/ios`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + - ua_client_hints (from `.symlinks/plugins/ua_client_hints/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +SPEC REPOS: + trunk: + - DKImagePickerController + - DKPhotoGallery + - OrderedSet + - SDWebImage + - Sentry + - SwiftyGif + +EXTERNAL SOURCES: + app_links: + :path: ".symlinks/plugins/app_links/ios" + cupertino_http: + :path: ".symlinks/plugins/cupertino_http/darwin" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" + file_picker: + :path: ".symlinks/plugins/file_picker/ios" + file_saver: + :path: ".symlinks/plugins/file_saver/ios" + Flutter: + :path: Flutter + flutter_email_sender: + :path: ".symlinks/plugins/flutter_email_sender/ios" + flutter_inappwebview_ios: + :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" + flutter_local_authentication: + :path: ".symlinks/plugins/flutter_local_authentication/ios" + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" + fluttertoast: + :path: ".symlinks/plugins/fluttertoast/ios" + listen_sharing_intent: + :path: ".symlinks/plugins/listen_sharing_intent/ios" + local_auth_darwin: + :path: ".symlinks/plugins/local_auth_darwin/darwin" + objective_c: + :path: ".symlinks/plugins/objective_c/ios" + open_file_ios: + :path: ".symlinks/plugins/open_file_ios/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + privacy_screen: + :path: ".symlinks/plugins/privacy_screen/ios" + sentry_flutter: + :path: ".symlinks/plugins/sentry_flutter/ios" + share_plus: + :path: ".symlinks/plugins/share_plus/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sodium_libs: + :path: ".symlinks/plugins/sodium_libs/ios" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" + ua_client_hints: + :path: ".symlinks/plugins/ua_client_hints/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + app_links: f3e17e4ee5e357b39d8b95290a9b2c299fca71c6 + cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba + device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 + DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c + DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 + file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 + file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_email_sender: e03bdda7637bcd3539bfe718fddd980e9508efaa + flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 + flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb + flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 + fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f + listen_sharing_intent: 74a842adcbcf7bedf7bbc938c749da9155141b9a + local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 + objective_c: 77e887b5ba1827970907e10e832eec1683f3431d + open_file_ios: 461db5853723763573e140de3193656f91990d9e + OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e + SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 + Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854 + sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318 + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d + SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 + ua_client_hints: aeabd123262c087f0ce151ef96fa3ab77bfc8b38 + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + +PODFILE CHECKSUM: d2d3220ea22664a259778d9e314054751db31361 + +COCOAPODS: 1.16.2 diff --git a/mobile/apps/locker/ios/Runner.xcodeproj/project.pbxproj b/mobile/apps/locker/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..3bda4ae6a4 --- /dev/null +++ b/mobile/apps/locker/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,1027 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 27AD87F62E16554700B24C05 /* Share Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 27AD87EC2E16554700B24C05 /* Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 4CDDC127876941DC8FF88929 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81319B7747381DEFA0F961F7 /* Pods_Runner.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 92402655AF53FB304C48E4BC /* Pods_Share_Extension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B8B5400AA6FED21CC714A5A0 /* Pods_Share_Extension.framework */; }; + 9373AE318AB19621F0BB5602 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 871674596693C14F59CB67C9 /* Pods_RunnerTests.framework */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 27AD87F42E16554700B24C05 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 27AD87EB2E16554700B24C05; + remoteInfo = "Share Extension"; + }; + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 27AD87D02E1647E900B24C05 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 27AD87F62E16554700B24C05 /* Share Extension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 041816B42CE5846482BE66D6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 27AD87D62E16497400B24C05 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 27AD87EC2E16554700B24C05 /* Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AEDBF29614F8677DCE69E7E /* Pods-Ente Locker.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Ente Locker.release.xcconfig"; path = "Target Support Files/Pods-Ente Locker/Pods-Ente Locker.release.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 405BE3630B56B70E48B35F05 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 43F401025D1F7ED737A76C71 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 611BD59A807D8565F74139DC /* Pods_Ente_Locker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Ente_Locker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 81319B7747381DEFA0F961F7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 871674596693C14F59CB67C9 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 89AC4F9CF1F94E149746EB60 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A1080DB69690A2BD27969FC2 /* Pods-Share Extension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.profile.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.profile.xcconfig"; sourceTree = ""; }; + A68B5D5A3F2AA255856692FC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + B8B5400AA6FED21CC714A5A0 /* Pods_Share_Extension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Share_Extension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B96B59C79D8F854FB9838CDF /* Pods-Ente Locker.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Ente Locker.profile.xcconfig"; path = "Target Support Files/Pods-Ente Locker/Pods-Ente Locker.profile.xcconfig"; sourceTree = ""; }; + C775876A93D19660332985CC /* Pods-Ente Locker.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Ente Locker.debug.xcconfig"; path = "Target Support Files/Pods-Ente Locker/Pods-Ente Locker.debug.xcconfig"; sourceTree = ""; }; + D09D02A78DCA8C010704EF91 /* Pods-Share Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.debug.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.debug.xcconfig"; sourceTree = ""; }; + E11D72536D63426171C60203 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + F4C835C880E118F472E3A8B3 /* Pods-Share Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.release.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 27AD87F72E16554700B24C05 /* Exceptions for "Share Extension" folder in "Share Extension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 27AD87EB2E16554700B24C05 /* Share Extension */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 27AD87C62E1647E900B24C05 /* Ente Locker */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = "Ente Locker"; + sourceTree = ""; + }; + 27AD87ED2E16554700B24C05 /* Share Extension */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 27AD87F72E16554700B24C05 /* Exceptions for "Share Extension" folder in "Share Extension" target */, + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = "Share Extension"; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 27AD87E92E16554700B24C05 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 92402655AF53FB304C48E4BC /* Pods_Share_Extension.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4CDDC127876941DC8FF88929 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + CF47A9397DB3A7B2B1C598E3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9373AE318AB19621F0BB5602 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 35603BA520E189E527465052 /* Pods */ = { + isa = PBXGroup; + children = ( + A68B5D5A3F2AA255856692FC /* Pods-Runner.debug.xcconfig */, + 89AC4F9CF1F94E149746EB60 /* Pods-Runner.release.xcconfig */, + 405BE3630B56B70E48B35F05 /* Pods-Runner.profile.xcconfig */, + E11D72536D63426171C60203 /* Pods-RunnerTests.debug.xcconfig */, + 43F401025D1F7ED737A76C71 /* Pods-RunnerTests.release.xcconfig */, + 041816B42CE5846482BE66D6 /* Pods-RunnerTests.profile.xcconfig */, + C775876A93D19660332985CC /* Pods-Ente Locker.debug.xcconfig */, + 3AEDBF29614F8677DCE69E7E /* Pods-Ente Locker.release.xcconfig */, + B96B59C79D8F854FB9838CDF /* Pods-Ente Locker.profile.xcconfig */, + D09D02A78DCA8C010704EF91 /* Pods-Share Extension.debug.xcconfig */, + F4C835C880E118F472E3A8B3 /* Pods-Share Extension.release.xcconfig */, + A1080DB69690A2BD27969FC2 /* Pods-Share Extension.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 27AD87C62E1647E900B24C05 /* Ente Locker */, + 27AD87ED2E16554700B24C05 /* Share Extension */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 35603BA520E189E527465052 /* Pods */, + DE91D11FDE3D2B3ABE02BCCD /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + 27AD87EC2E16554700B24C05 /* Share Extension.appex */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 27AD87D62E16497400B24C05 /* Runner.entitlements */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + DE91D11FDE3D2B3ABE02BCCD /* Frameworks */ = { + isa = PBXGroup; + children = ( + 81319B7747381DEFA0F961F7 /* Pods_Runner.framework */, + 871674596693C14F59CB67C9 /* Pods_RunnerTests.framework */, + 611BD59A807D8565F74139DC /* Pods_Ente_Locker.framework */, + B8B5400AA6FED21CC714A5A0 /* Pods_Share_Extension.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 27AD87EB2E16554700B24C05 /* Share Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 27AD87F82E16554700B24C05 /* Build configuration list for PBXNativeTarget "Share Extension" */; + buildPhases = ( + 1304177914FB1E5B189BACD6 /* [CP] Check Pods Manifest.lock */, + 27AD87E82E16554700B24C05 /* Sources */, + 27AD87E92E16554700B24C05 /* Frameworks */, + 27AD87EA2E16554700B24C05 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 27AD87ED2E16554700B24C05 /* Share Extension */, + ); + name = "Share Extension"; + productName = "Share Extension"; + productReference = 27AD87EC2E16554700B24C05 /* Share Extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + AF55335C8D25422D2D282490 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + CF47A9397DB3A7B2B1C598E3 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + DA099182B8E4842D754F598F /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 27AD87D02E1647E900B24C05 /* Embed Foundation Extensions */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 82B64A7C257381AA0C0D6B15 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 27AD87F52E16554700B24C05 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1620; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 27AD87EB2E16554700B24C05 = { + CreatedOnToolsVersion = 16.2; + }; + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + 27AD87EB2E16554700B24C05 /* Share Extension */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 27AD87EA2E16554700B24C05 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1304177914FB1E5B189BACD6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Share Extension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 82B64A7C257381AA0C0D6B15 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + AF55335C8D25422D2D282490 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + DA099182B8E4842D754F598F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 27AD87E82E16554700B24C05 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 27AD87F52E16554700B24C05 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 27AD87EB2E16554700B24C05 /* Share Extension */; + targetProxy = 27AD87F42E16554700B24C05 /* PBXContainerItemProxy */; + }; + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CUSTOM_GROUP_ID = group.io.ente.locker.share; + DEVELOPMENT_TEAM = 6Z68YJY9Q2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + NEW_SETTING = ""; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 27AD87F92E16554700B24C05 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D09D02A78DCA8C010704EF91 /* Pods-Share Extension.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + CUSTOM_GROUP_ID = group.io.ente.locker.share; + DEVELOPMENT_TEAM = 6Z68YJY9Q2; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Share Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Share Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "io.ente.locker.Share-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 27AD87FA2E16554700B24C05 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F4C835C880E118F472E3A8B3 /* Pods-Share Extension.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + CUSTOM_GROUP_ID = group.io.ente.locker.share; + DEVELOPMENT_TEAM = 6Z68YJY9Q2; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Share Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Share Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "io.ente.locker.Share-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 27AD87FB2E16554700B24C05 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A1080DB69690A2BD27969FC2 /* Pods-Share Extension.profile.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + CUSTOM_GROUP_ID = group.io.ente.locker.share; + DEVELOPMENT_TEAM = 6Z68YJY9Q2; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Share Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Share Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "io.ente.locker.Share-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E11D72536D63426171C60203 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 43F401025D1F7ED737A76C71 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 041816B42CE5846482BE66D6 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CUSTOM_GROUP_ID = group.io.ente.locker.share; + DEVELOPMENT_TEAM = 6Z68YJY9Q2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + NEW_SETTING = ""; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CUSTOM_GROUP_ID = group.io.ente.locker.share; + DEVELOPMENT_TEAM = 6Z68YJY9Q2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + NEW_SETTING = ""; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 27AD87F82E16554700B24C05 /* Build configuration list for PBXNativeTarget "Share Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 27AD87F92E16554700B24C05 /* Debug */, + 27AD87FA2E16554700B24C05 /* Release */, + 27AD87FB2E16554700B24C05 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/mobile/apps/locker/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mobile/apps/locker/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..919434a625 --- /dev/null +++ b/mobile/apps/locker/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile/apps/locker/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/apps/locker/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/mobile/apps/locker/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/apps/locker/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/apps/locker/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..f9b0d7c5ea --- /dev/null +++ b/mobile/apps/locker/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile/apps/locker/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/apps/locker/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000..e3773d42e2 --- /dev/null +++ b/mobile/apps/locker/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/locker/ios/Runner.xcworkspace/contents.xcworkspacedata b/mobile/apps/locker/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..21a3cc14c7 --- /dev/null +++ b/mobile/apps/locker/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/mobile/apps/locker/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/apps/locker/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/mobile/apps/locker/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/apps/locker/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/apps/locker/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..f9b0d7c5ea --- /dev/null +++ b/mobile/apps/locker/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile/apps/locker/ios/Runner/AppDelegate.swift b/mobile/apps/locker/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000000..626664468b --- /dev/null +++ b/mobile/apps/locker/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d36b1fab2d --- /dev/null +++ b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000..dc9ada4725 Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000..7353c41ecf Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000..797d452e45 Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000..6ed2d933e1 Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000..4cd7b0099c Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000..fe730945a0 Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000000..321773cd85 Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000000..797d452e45 Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000..502f463a9b Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000..0ec3034392 Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000..0ec3034392 Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000..e9f5fea27c Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000..84ac32ae7d Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000000..8953cba090 Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000..0467bf12aa Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000000..0bedcf2fd4 --- /dev/null +++ b/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000..9da19eacad Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000..9da19eacad Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000..9da19eacad Binary files /dev/null and b/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000000..89c2725b70 --- /dev/null +++ b/mobile/apps/locker/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/mobile/apps/locker/ios/Runner/Base.lproj/LaunchScreen.storyboard b/mobile/apps/locker/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..f2e259c7c9 --- /dev/null +++ b/mobile/apps/locker/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/locker/ios/Runner/Base.lproj/Main.storyboard b/mobile/apps/locker/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..f3c28516fb --- /dev/null +++ b/mobile/apps/locker/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/locker/ios/Runner/Info.plist b/mobile/apps/locker/ios/Runner/Info.plist new file mode 100644 index 0000000000..836320f49d --- /dev/null +++ b/mobile/apps/locker/ios/Runner/Info.plist @@ -0,0 +1,68 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Ente Locker + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Ente Locker + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + NSCameraUsageDescription + Please allow access to camera so that Ente can scan and backup your documents. + NSPhotoLibraryUsageDescription + Please allow access to your library so that Ente can encrypt and back up your documents. + NSPhotoLibraryAddUsageDescription + Please allow access to your library so that Ente can encrypt and back up your documents. + AppGroupId + $(CUSTOM_GROUP_ID) + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER) + + + + + diff --git a/mobile/apps/locker/ios/Runner/Runner-Bridging-Header.h b/mobile/apps/locker/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000000..308a2a560b --- /dev/null +++ b/mobile/apps/locker/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/mobile/apps/locker/ios/Runner/Runner.entitlements b/mobile/apps/locker/ios/Runner/Runner.entitlements new file mode 100644 index 0000000000..b79c362eed --- /dev/null +++ b/mobile/apps/locker/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.io.ente.locker.share + + + diff --git a/mobile/apps/locker/ios/RunnerTests/RunnerTests.swift b/mobile/apps/locker/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000000..86a7c3b1b6 --- /dev/null +++ b/mobile/apps/locker/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/mobile/apps/locker/ios/Share Extension/Base.lproj/MainInterface.storyboard b/mobile/apps/locker/ios/Share Extension/Base.lproj/MainInterface.storyboard new file mode 100644 index 0000000000..286a50894d --- /dev/null +++ b/mobile/apps/locker/ios/Share Extension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/locker/ios/Share Extension/Info.plist b/mobile/apps/locker/ios/Share Extension/Info.plist new file mode 100644 index 0000000000..1efefe2ea7 --- /dev/null +++ b/mobile/apps/locker/ios/Share Extension/Info.plist @@ -0,0 +1,33 @@ + + + + + AppGroupId + $(CUSTOM_GROUP_ID) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + SUBQUERY ( + extensionItems, + $extensionItem, + SUBQUERY ( + $extensionItem.attachments, + $attachment, + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.data" AND + !(ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" OR + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.plain-text" OR + ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.utf8-plain-text") + ).@count == 1 + ).@count == 1 + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + + diff --git a/mobile/apps/locker/ios/Share Extension/Share Extension.entitlements b/mobile/apps/locker/ios/Share Extension/Share Extension.entitlements new file mode 100644 index 0000000000..b79c362eed --- /dev/null +++ b/mobile/apps/locker/ios/Share Extension/Share Extension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.io.ente.locker.share + + + diff --git a/mobile/apps/locker/ios/Share Extension/ShareViewController.swift b/mobile/apps/locker/ios/Share Extension/ShareViewController.swift new file mode 100644 index 0000000000..f5aabe3e2a --- /dev/null +++ b/mobile/apps/locker/ios/Share Extension/ShareViewController.swift @@ -0,0 +1,10 @@ +import listen_sharing_intent + +class ShareViewController: RSIShareViewController { + + // Use this method to return false if you don't want to redirect to host app automatically. + // Default is true + override func shouldAutoRedirect() -> Bool { + return true + } +} diff --git a/mobile/apps/locker/l10n.yaml b/mobile/apps/locker/l10n.yaml new file mode 100644 index 0000000000..15338f2ddc --- /dev/null +++ b/mobile/apps/locker/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart diff --git a/mobile/apps/locker/lib/app.dart b/mobile/apps/locker/lib/app.dart new file mode 100644 index 0000000000..05aea2ae6d --- /dev/null +++ b/mobile/apps/locker/lib/app.dart @@ -0,0 +1,220 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:ente_accounts/services/user_service.dart'; +import 'package:ente_events/event_bus.dart'; +import 'package:ente_events/models/signed_in_event.dart'; +import 'package:ente_events/models/signed_out_event.dart'; +import 'package:ente_strings/l10n/strings_localizations.dart'; +import 'package:ente_ui/theme/colors.dart'; +import 'package:ente_ui/theme/ente_theme_data.dart'; +import 'package:ente_ui/utils/window_listener_service.dart'; +import 'package:flutter/foundation.dart'; +import "package:flutter/material.dart"; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:locker/core/locale.dart'; +import 'package:locker/l10n/l10n.dart'; +import 'package:locker/services/configuration.dart'; +import 'package:locker/ui/pages/home_page.dart'; +import 'package:locker/ui/pages/onboarding_page.dart'; +import 'package:tray_manager/tray_manager.dart'; +import 'package:window_manager/window_manager.dart'; + +class App extends StatefulWidget { + final Locale? locale; + const App({super.key, this.locale = const Locale("en")}); + + static void setLocale(BuildContext context, Locale newLocale) { + final _AppState state = context.findAncestorStateOfType<_AppState>()!; + state.setLocale(newLocale); + } + + @override + State createState() => _AppState(); +} + +class _AppState extends State + with WindowListener, TrayListener, WidgetsBindingObserver { + late StreamSubscription _signedOutEvent; + late StreamSubscription _signedInEvent; + Locale? locale; + setLocale(Locale newLocale) { + setState(() { + locale = newLocale; + }); + } + + Future initWindowManager() async { + windowManager.addListener(this); + } + + Future initTrayManager() async { + trayManager.addListener(this); + } + + @override + void initState() { + initWindowManager(); + initTrayManager(); + WidgetsBinding.instance.addObserver(this); + + _signedOutEvent = Bus.instance.on().listen((event) { + if (mounted) { + setState(() {}); + } + }); + _signedInEvent = Bus.instance.on().listen((event) { + UserService.instance.getUserDetailsV2().ignore(); + if (mounted) { + setState(() {}); + } + }); + locale = widget.locale; + super.initState(); + } + + @override + void dispose() { + super.dispose(); + + windowManager.removeListener(this); + trayManager.removeListener(this); + + _signedOutEvent.cancel(); + _signedInEvent.cancel(); + } + + @override + Widget build(BuildContext context) { + final schemes = ColorSchemeBuilder.fromCustomColors( + primary700: const Color(0xFF1565C0), // Dark blue + primary500: const Color(0xFF2196F3), // Material blue + primary400: const Color(0xFF42A5F5), // Light blue + primary300: const Color(0xFF90CAF9), // Very light blue + iconButtonColor: const Color(0xFF1976D2), // Custom icon color + gradientButtonBgColors: const [ + Color(0xFF1565C0), + Color(0xFF2196F3), + Color(0xFF42A5F5), + ], + ); + + final lightTheme = createAppThemeData( + brightness: Brightness.light, + colorScheme: schemes.light, + ); + + final darkTheme = createAppThemeData( + brightness: Brightness.dark, + colorScheme: schemes.dark, + ); + + Widget buildApp() { + if (Platform.isAndroid || + Platform.isWindows || + Platform.isLinux || + kDebugMode) { + return AdaptiveTheme( + light: lightTheme, + dark: darkTheme, + initial: AdaptiveThemeMode.system, + builder: (lightTheme, dartTheme) => MaterialApp( + title: "ente", + themeMode: ThemeMode.system, + theme: lightTheme, + darkTheme: dartTheme, + debugShowCheckedModeBanner: false, + locale: locale, + supportedLocales: appSupportedLocales, + localeListResolutionCallback: localResolutionCallBack, + localizationsDelegates: const [ + AppLocalizations.delegate, + StringsLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + routes: _getRoutes, + ), + ); + } else { + return MaterialApp( + title: "ente", + themeMode: ThemeMode.system, + theme: lightTheme, + darkTheme: darkTheme, + debugShowCheckedModeBanner: false, + locale: locale, + supportedLocales: appSupportedLocales, + localeListResolutionCallback: localResolutionCallBack, + localizationsDelegates: const [ + AppLocalizations.delegate, + StringsLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + routes: _getRoutes, + ); + } + } + + // Wrap the app with MediaQuery to control text scaling and ensure + // consistent font sizes across Android and iOS + return MediaQuery.withClampedTextScaling( + minScaleFactor: 0.8, + maxScaleFactor: 1.3, + child: buildApp(), + ); + } + + Map get _getRoutes { + return { + "/": (context) => Configuration.instance.hasConfiguredAccount() + ? const HomePage() + : const OnboardingPage(), + }; + } + + @override + void onWindowResize() { + WindowListenerService.instance.onWindowResize().ignore(); + } + + @override + void onTrayIconMouseDown() { + if (Platform.isWindows) { + windowManager.show(); + } else { + trayManager.popUpContextMenu(); + } + } + + @override + void onTrayIconRightMouseDown() { + if (Platform.isWindows) { + trayManager.popUpContextMenu(); + } else { + windowManager.show(); + } + } + + @override + void onTrayIconRightMouseUp() {} + + @override + void onTrayMenuItemClick(MenuItem menuItem) { + switch (menuItem.key) { + case 'hide_window': + windowManager.hide(); + break; + case 'show_window': + windowManager.show(); + break; + case 'exit_app': + windowManager.destroy(); + break; + } + } +} diff --git a/mobile/apps/locker/lib/core/constants.dart b/mobile/apps/locker/lib/core/constants.dart new file mode 100644 index 0000000000..b1ff31d0cb --- /dev/null +++ b/mobile/apps/locker/lib/core/constants.dart @@ -0,0 +1,45 @@ +import 'package:flutter/foundation.dart'; + +const String kAccountsUrl = "https://accounts.ente.io"; +const int microSecondsInDay = 86400000000; +const int android11SDKINT = 30; + +// 256 bit key maps to 24 words +// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#Generating_the_mnemonic +const mnemonicKeyWordCount = 24; + +const kDefaultProductionEndpoint = 'https://api.ente.io'; + +final tempDirCleanUpInterval = kDebugMode + ? const Duration(hours: 1).inMicroseconds + : const Duration(hours: 6).inMicroseconds; + +const uploadTempFilePrefix = "upload_file_"; + +const blackThumbnailBase64 = '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB' + 'AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQ' + 'EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARC' + 'ACWASwDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUF' + 'BAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk' + '6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztL' + 'W2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAA' + 'AAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVY' + 'nLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImK' + 'kpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oAD' + 'AMBAAIRAxEAPwD/AD/6ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' + 'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' + 'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKAC' + 'gAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' + 'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' + 'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' + 'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' + 'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' + 'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA' + 'KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' + 'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' + 'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' + 'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAK' + 'ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA' + 'KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' + 'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' + 'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD/9k='; diff --git a/mobile/apps/locker/lib/core/errors.dart b/mobile/apps/locker/lib/core/errors.dart new file mode 100644 index 0000000000..6722276ad3 --- /dev/null +++ b/mobile/apps/locker/lib/core/errors.dart @@ -0,0 +1,41 @@ +class UnauthorizedError extends Error {} + +class PassKeySessionNotVerifiedError extends Error {} + +class PassKeySessionExpiredError extends Error {} + +class SrpSetupNotCompleteError extends Error {} + +class StorageLimitExceededError extends Error {} + +class NoActiveSubscriptionError extends Error {} + +// error when file size + current usage >= storage plan limit + buffer +class FileTooLargeForPlanError extends Error {} + +class WiFiUnavailableError extends Error {} + +class SilentlyCancelUploadsError extends Error {} + +class InvalidFileError extends ArgumentError { + final InvalidReason reason; + + InvalidFileError(String super.message, this.reason); + + @override + String toString() { + return 'InvalidFileError: $message (reason: $reason)'; + } +} + +enum InvalidReason { + assetDeleted, + assetDeletedEvent, + sourceFileMissing, + livePhotoToImageTypeChanged, + imageToLivePhotoTypeChanged, + livePhotoVideoMissing, + thumbnailMissing, + tooLargeFile, + unknown, +} diff --git a/mobile/apps/locker/lib/core/locale.dart b/mobile/apps/locker/lib/core/locale.dart new file mode 100644 index 0000000000..7fc71a1eda --- /dev/null +++ b/mobile/apps/locker/lib/core/locale.dart @@ -0,0 +1,74 @@ +import 'package:flutter/cupertino.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +// list of locales which are enabled for auth app. +// Add more language to the list only when at least 90% of the strings are +// translated in the corresponding language. +const List appSupportedLocales = [ + Locale('en'), +]; + +Locale? autoDetectedLocale; +// This function takes device locales and supported locales as input +// and returns the best matching locale. +// The device locales are sorted by priority, so the first one is the most preferred. +Locale localResolutionCallBack(onDeviceLocales, supportedLocales) { + final Set languageSupport = {}; + for (Locale supportedLocale in appSupportedLocales) { + languageSupport.add(supportedLocale.languageCode); + } + for (Locale locale in onDeviceLocales) { + // check if exact local is supported, if yes, return it + if (appSupportedLocales.contains(locale)) { + autoDetectedLocale = locale; + return locale; + } + // check if language code is supported, if yes, return it + if (languageSupport.contains(locale.languageCode)) { + autoDetectedLocale = locale; + return locale; + } + } + // Return the first language code match or default to 'en' + return autoDetectedLocale ?? const Locale('en'); +} + +Future getLocale({ + bool noFallback = false, +}) async { + final String? savedValue = + (await SharedPreferences.getInstance()).getString('locale'); + // if savedLocale is not null and is supported by the app, return it + if (savedValue != null) { + late Locale savedLocale; + if (savedValue.contains('_')) { + final List parts = savedValue.split('_'); + savedLocale = Locale(parts[0], parts[1]); + } else { + savedLocale = Locale(savedValue); + } + if (appSupportedLocales.contains(savedLocale)) { + return savedLocale; + } + } + if (autoDetectedLocale != null) { + return autoDetectedLocale!; + } + if (noFallback) { + return null; + } + return const Locale('en'); +} + +Future setLocale(Locale locale) async { + if (!appSupportedLocales.contains(locale)) { + throw Exception('Locale $locale is not supported by the app'); + } + final StringBuffer out = StringBuffer(locale.languageCode); + if (locale.countryCode != null && locale.countryCode!.isNotEmpty) { + out.write('_'); + out.write(locale.countryCode); + } + await (await SharedPreferences.getInstance()) + .setString('locale', out.toString()); +} diff --git a/mobile/apps/locker/lib/events/backup_updated_event.dart b/mobile/apps/locker/lib/events/backup_updated_event.dart new file mode 100644 index 0000000000..d4dd91e44a --- /dev/null +++ b/mobile/apps/locker/lib/events/backup_updated_event.dart @@ -0,0 +1,10 @@ +import "dart:collection"; + +import "package:ente_events/models/event.dart"; +import "package:locker/services/files/upload/models/backup_item.dart"; + +class BackupUpdatedEvent extends Event { + final LinkedHashMap items; + + BackupUpdatedEvent(this.items); +} diff --git a/mobile/apps/locker/lib/events/collections_updated_event.dart b/mobile/apps/locker/lib/events/collections_updated_event.dart new file mode 100644 index 0000000000..d35e3a15cf --- /dev/null +++ b/mobile/apps/locker/lib/events/collections_updated_event.dart @@ -0,0 +1,3 @@ +import 'package:ente_events/models/event.dart'; + +class CollectionsUpdatedEvent extends Event {} diff --git a/mobile/apps/locker/lib/l10n/app_en.arb b/mobile/apps/locker/lib/l10n/app_en.arb new file mode 100644 index 0000000000..0d05f8ec45 --- /dev/null +++ b/mobile/apps/locker/lib/l10n/app_en.arb @@ -0,0 +1,312 @@ +{ + "onBoardingBody": "Securely backup your documents", + "newUser": "New to Ente", + "existingUser": "Existing User", + "developerSettings": "Developer settings", + "yes": "Yes", + "developerSettingsWarning": "Are you sure that you want to modify Developer settings?", + "serverEndpoint": "Server endpoint", + "endpointUpdatedMessage": "Endpoint updated successfully", + "invalidEndpoint": "Invalid endpoint", + "invalidEndpointMessage": "Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.", + "saveAction": "Save", + "untitled": "Untitled", + "edit": "Edit", + "delete": "Delete", + "noFilesFound": "No files here", + "addFiles": "Add Files", + "name": "Name", + "date": "Date", + "pleaseWait": "Please wait...", + "downloading": "Downloading...", + "downloadingProgress": "Downloading... {percentage}%", + "@downloadingProgress": { + "description": "Download progress message", + "placeholders": { + "percentage": { + "type": "int", + "example": "75" + } + } + }, + "downloadFailed": "Download Failed", + "failedToDownloadOrDecrypt": "Failed to download or decrypt file", + "errorOpeningFile": "Error opening file", + "errorOpeningFileMessage": "Error opening file: {error}", + "@errorOpeningFileMessage": { + "description": "Error message when opening a file fails", + "placeholders": { + "error": { + "type": "String", + "example": "File not found" + } + } + }, + "couldNotOpenFile": "Could not open file: {error}", + "@couldNotOpenFile": { + "description": "Error message when file cannot be opened", + "placeholders": { + "error": { + "type": "String", + "example": "Permission denied" + } + } + }, + "minutesAgo": "{minutes}m ago", + "@minutesAgo": { + "description": "Time format for minutes ago", + "placeholders": { + "minutes": { + "type": "int", + "example": "2" + } + } + }, + "hoursAgo": "{hours}h ago", + "@hoursAgo": { + "description": "Time format for hours ago", + "placeholders": { + "hours": { + "type": "int", + "example": "2" + } + } + }, + "yesterday": "Yesterday", + "daysAgo": "{days}d ago", + "@daysAgo": { + "description": "Time format for days ago", + "placeholders": { + "days": { + "type": "int", + "example": "5" + } + } + }, + "collectionRenamedSuccessfully": "Collection renamed successfully", + "failedToRenameCollection": "Failed to rename collection: {error}", + "@failedToRenameCollection": { + "description": "Error message when collection rename fails", + "placeholders": { + "error": { + "type": "String", + "example": "Network error" + } + } + }, + "collectionCannotBeDeleted": "This collection cannot be deleted", + "deleteCollection": "Delete collection", + "deleteCollectionConfirmation": "Are you sure you want to delete \"{collectionName}\"?", + "@deleteCollectionConfirmation": { + "description": "Confirmation message for deleting a collection", + "placeholders": { + "collectionName": { + "type": "String", + "example": "My Collection" + } + } + }, + "cancel": "Cancel", + "collectionDeletedSuccessfully": "Collection deleted successfully", + "failedToDeleteCollection": "Failed to delete collection: {error}", + "@failedToDeleteCollection": { + "description": "Error message when collection deletion fails", + "placeholders": { + "error": { + "type": "String", + "example": "Network error" + } + } + }, + "collections": "Collections", + "retry": "Retry", + "failedToSyncTrash": "Failed to sync trash: {error}", + "@failedToSyncTrash": { + "description": "Error message when trash sync fails", + "placeholders": { + "error": { + "type": "String", + "example": "Network error" + } + } + }, + "pleaseSelectAtLeastOneCollection": "Please select at least one collection", + "unknownItemType": "Unknown item type", + "fileDeletedSuccessfully": "File deleted successfully", + "failedToDeleteFile": "Failed to delete file: {error}", + "@failedToDeleteFile": { + "description": "Error message when file deletion fails", + "placeholders": { + "error": { + "type": "String", + "example": "Network error" + } + } + }, + "fileUpdatedSuccessfully": "File updated successfully!", + "failedToUpdateFile": "Failed to update file: {error}", + "@failedToUpdateFile": { + "description": "Error message when file update fails", + "placeholders": { + "error": { + "type": "String", + "example": "Network error" + } + } + }, + "noChangesWereMade": "No changes were made", + "noCollectionsAvailableForRestore": "No collections available for restore", + "trashClearedSuccessfully": "Trash cleared successfully", + "failedToClearTrash": "Failed to clear trash: {error}", + "@failedToClearTrash": { + "description": "Error message when clearing trash fails", + "placeholders": { + "error": { + "type": "String", + "example": "Network error" + } + } + }, + "trash": "Trash", + "deletedPermanently": "Deleted \"{fileName}\" permanently", + "@deletedPermanently": { + "description": "Message when file is permanently deleted from trash", + "placeholders": { + "fileName": { + "type": "String", + "example": "photo.jpg" + } + } + }, + "failedToRestoreFile": "Failed to restore \"{fileName}\": {error}", + "@failedToRestoreFile": { + "description": "Error message when file restoration fails", + "placeholders": { + "fileName": { + "type": "String", + "example": "photo.jpg" + }, + "error": { + "type": "String", + "example": "Network error" + } + } + }, + "createNewCollection": "Create new collection", + "create": "Create", + "renameCollection": "Rename collection", + "save": "Save", + "deleteFile": "Delete File", + "deleteFileConfirmation": "Are you sure you want to delete \"{fileName}\"?", + "@deleteFileConfirmation": { + "description": "Confirmation message for file deletion", + "placeholders": { + "fileName": { + "type": "String", + "example": "photo.jpg" + } + } + }, + "noCollectionsFound": "No collections found", + "createYourFirstCollection": "Create your first collection to get started", + "createCollection": "Create Collection", + "nothingYet": "Nothing yet", + "uploadYourFirstDocument": "Upload your first document to get started", + "uploadDocument": "Upload Document", + "items": "{count} items", + "@items": { + "description": "Number of items in a collection", + "placeholders": { + "count": { + "type": "int", + "example": "5" + } + } + }, + "files": "{count, plural, =0{no files} =1{1 file} other{{count} files}}", + "@files": { + "description": "Number of files in a collection", + "placeholders": { + "count": { + "type": "int", + "example": "5" + } + } + }, + "createCollectionTooltip": "Create collection", + "uploadDocumentTooltip": "Upload document", + "restore": "Restore", + "restoreFile": "Restore {fileName}", + "@restoreFile": { + "description": "Dialog title for restoring a file from trash", + "placeholders": { + "fileName": { + "type": "String", + "example": "photo.jpg" + } + } + }, + "noCollectionsAvailable": "No collections available", + "emptyTrash": "Empty trash", + "emptyTrashTooltip": "Empty trash", + "emptyTrashConfirmation": "Are you sure you want to permanently delete all items in trash? This action cannot be undone.", + "fileTitle": "File title", + "note": "Note", + "optionalNote": "Optional note", + "upload": "Upload", + "documentsHint": "Documents", + "searchHint": "Search...", + "noCollectionsFoundForQuery": "No collections found for \"{query}\"", + "@noCollectionsFoundForQuery": { + "description": "Message when no collections match the search query", + "placeholders": { + "query": { + "type": "String", + "example": "vacation" + } + } + }, + "uncategorized": "Uncategorized", + "syncingTrash": "Syncing trash...", + "restoring": "Restoring...", + "restoredFileToCollection": "Restored \"{fileName}\" to \"{collectionName}\"", + "@restoredFileToCollection": { + "description": "Success message when file is restored to a collection", + "placeholders": { + "fileName": { + "type": "String", + "example": "photo.jpg" + }, + "collectionName": { + "type": "String", + "example": "Vacation Photos" + } + } + }, + "clearingTrash": "Clearing trash...", + "trashIsEmpty": "Trash is empty", + "share": "Share", + "shareLink": "Share link", + "creatingShareLink": "Creating link...", + "shareThisLink": "Anyone with this link can access your file.", + "copyLink": "Copy link", + "deleteLink": "Delete link", + "close": "Close", + "linkCopiedToClipboard": "Link copied to clipboard", + "deleteShareLink": "Delete link", + "deleteShareLinkDialogTitle": "Delete link?", + "deleteShareLinkConfirmation": "People with this link will no longer be able to access the file.", + "deletingShareLink": "Deleting link...", + "shareLinkDeletedSuccessfully": "Link deleted successfully", + "failedToCreateShareLink": "Failed to create link", + "failedToDeleteShareLink": "Failed to delete link", + "deletingFile": "Deleting file...", + "addInformation": "Add information", + "addInformationDialogSubtitle": "Choose the type of information you want to add", + "physicalDocument": "Physical document", + "physicalDocumentDescription": "Save information about documents and items in the real world.", + "emergencyContact": "Emergency contact", + "emergencyContactDescription": "Save information about important contacts.", + "accountCredential": "Account credential", + "accountCredentialDescription": "Save information about your important account credentials." +} diff --git a/mobile/apps/locker/lib/l10n/app_localizations.dart b/mobile/apps/locker/lib/l10n/app_localizations.dart new file mode 100644 index 0000000000..5b6d76b735 --- /dev/null +++ b/mobile/apps/locker/lib/l10n/app_localizations.dart @@ -0,0 +1,805 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [Locale('en')]; + + /// No description provided for @onBoardingBody. + /// + /// In en, this message translates to: + /// **'Securely backup your documents'** + String get onBoardingBody; + + /// No description provided for @newUser. + /// + /// In en, this message translates to: + /// **'New to Ente'** + String get newUser; + + /// No description provided for @existingUser. + /// + /// In en, this message translates to: + /// **'Existing User'** + String get existingUser; + + /// No description provided for @developerSettings. + /// + /// In en, this message translates to: + /// **'Developer settings'** + String get developerSettings; + + /// No description provided for @yes. + /// + /// In en, this message translates to: + /// **'Yes'** + String get yes; + + /// No description provided for @developerSettingsWarning. + /// + /// In en, this message translates to: + /// **'Are you sure that you want to modify Developer settings?'** + String get developerSettingsWarning; + + /// No description provided for @serverEndpoint. + /// + /// In en, this message translates to: + /// **'Server endpoint'** + String get serverEndpoint; + + /// No description provided for @endpointUpdatedMessage. + /// + /// In en, this message translates to: + /// **'Endpoint updated successfully'** + String get endpointUpdatedMessage; + + /// No description provided for @invalidEndpoint. + /// + /// In en, this message translates to: + /// **'Invalid endpoint'** + String get invalidEndpoint; + + /// No description provided for @invalidEndpointMessage. + /// + /// In en, this message translates to: + /// **'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'** + String get invalidEndpointMessage; + + /// No description provided for @saveAction. + /// + /// In en, this message translates to: + /// **'Save'** + String get saveAction; + + /// No description provided for @untitled. + /// + /// In en, this message translates to: + /// **'Untitled'** + String get untitled; + + /// No description provided for @edit. + /// + /// In en, this message translates to: + /// **'Edit'** + String get edit; + + /// No description provided for @delete. + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// No description provided for @noFilesFound. + /// + /// In en, this message translates to: + /// **'No files here'** + String get noFilesFound; + + /// No description provided for @addFiles. + /// + /// In en, this message translates to: + /// **'Add Files'** + String get addFiles; + + /// No description provided for @name. + /// + /// In en, this message translates to: + /// **'Name'** + String get name; + + /// No description provided for @date. + /// + /// In en, this message translates to: + /// **'Date'** + String get date; + + /// No description provided for @pleaseWait. + /// + /// In en, this message translates to: + /// **'Please wait...'** + String get pleaseWait; + + /// No description provided for @downloading. + /// + /// In en, this message translates to: + /// **'Downloading...'** + String get downloading; + + /// Download progress message + /// + /// In en, this message translates to: + /// **'Downloading... {percentage}%'** + String downloadingProgress(int percentage); + + /// No description provided for @downloadFailed. + /// + /// In en, this message translates to: + /// **'Download Failed'** + String get downloadFailed; + + /// No description provided for @failedToDownloadOrDecrypt. + /// + /// In en, this message translates to: + /// **'Failed to download or decrypt file'** + String get failedToDownloadOrDecrypt; + + /// No description provided for @errorOpeningFile. + /// + /// In en, this message translates to: + /// **'Error opening file'** + String get errorOpeningFile; + + /// Error message when opening a file fails + /// + /// In en, this message translates to: + /// **'Error opening file: {error}'** + String errorOpeningFileMessage(String error); + + /// Error message when file cannot be opened + /// + /// In en, this message translates to: + /// **'Could not open file: {error}'** + String couldNotOpenFile(String error); + + /// Time format for minutes ago + /// + /// In en, this message translates to: + /// **'{minutes}m ago'** + String minutesAgo(int minutes); + + /// Time format for hours ago + /// + /// In en, this message translates to: + /// **'{hours}h ago'** + String hoursAgo(int hours); + + /// No description provided for @yesterday. + /// + /// In en, this message translates to: + /// **'Yesterday'** + String get yesterday; + + /// Time format for days ago + /// + /// In en, this message translates to: + /// **'{days}d ago'** + String daysAgo(int days); + + /// No description provided for @collectionRenamedSuccessfully. + /// + /// In en, this message translates to: + /// **'Collection renamed successfully'** + String get collectionRenamedSuccessfully; + + /// Error message when collection rename fails + /// + /// In en, this message translates to: + /// **'Failed to rename collection: {error}'** + String failedToRenameCollection(String error); + + /// No description provided for @collectionCannotBeDeleted. + /// + /// In en, this message translates to: + /// **'This collection cannot be deleted'** + String get collectionCannotBeDeleted; + + /// No description provided for @deleteCollection. + /// + /// In en, this message translates to: + /// **'Delete collection'** + String get deleteCollection; + + /// Confirmation message for deleting a collection + /// + /// In en, this message translates to: + /// **'Are you sure you want to delete \"{collectionName}\"?'** + String deleteCollectionConfirmation(String collectionName); + + /// No description provided for @cancel. + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; + + /// No description provided for @collectionDeletedSuccessfully. + /// + /// In en, this message translates to: + /// **'Collection deleted successfully'** + String get collectionDeletedSuccessfully; + + /// Error message when collection deletion fails + /// + /// In en, this message translates to: + /// **'Failed to delete collection: {error}'** + String failedToDeleteCollection(String error); + + /// No description provided for @collections. + /// + /// In en, this message translates to: + /// **'Collections'** + String get collections; + + /// No description provided for @retry. + /// + /// In en, this message translates to: + /// **'Retry'** + String get retry; + + /// Error message when trash sync fails + /// + /// In en, this message translates to: + /// **'Failed to sync trash: {error}'** + String failedToSyncTrash(String error); + + /// No description provided for @pleaseSelectAtLeastOneCollection. + /// + /// In en, this message translates to: + /// **'Please select at least one collection'** + String get pleaseSelectAtLeastOneCollection; + + /// No description provided for @unknownItemType. + /// + /// In en, this message translates to: + /// **'Unknown item type'** + String get unknownItemType; + + /// No description provided for @fileDeletedSuccessfully. + /// + /// In en, this message translates to: + /// **'File deleted successfully'** + String get fileDeletedSuccessfully; + + /// Error message when file deletion fails + /// + /// In en, this message translates to: + /// **'Failed to delete file: {error}'** + String failedToDeleteFile(String error); + + /// No description provided for @fileUpdatedSuccessfully. + /// + /// In en, this message translates to: + /// **'File updated successfully!'** + String get fileUpdatedSuccessfully; + + /// Error message when file update fails + /// + /// In en, this message translates to: + /// **'Failed to update file: {error}'** + String failedToUpdateFile(String error); + + /// No description provided for @noChangesWereMade. + /// + /// In en, this message translates to: + /// **'No changes were made'** + String get noChangesWereMade; + + /// No description provided for @noCollectionsAvailableForRestore. + /// + /// In en, this message translates to: + /// **'No collections available for restore'** + String get noCollectionsAvailableForRestore; + + /// No description provided for @trashClearedSuccessfully. + /// + /// In en, this message translates to: + /// **'Trash cleared successfully'** + String get trashClearedSuccessfully; + + /// Error message when clearing trash fails + /// + /// In en, this message translates to: + /// **'Failed to clear trash: {error}'** + String failedToClearTrash(String error); + + /// No description provided for @trash. + /// + /// In en, this message translates to: + /// **'Trash'** + String get trash; + + /// Message when file is permanently deleted from trash + /// + /// In en, this message translates to: + /// **'Deleted \"{fileName}\" permanently'** + String deletedPermanently(String fileName); + + /// Error message when file restoration fails + /// + /// In en, this message translates to: + /// **'Failed to restore \"{fileName}\": {error}'** + String failedToRestoreFile(String fileName, String error); + + /// No description provided for @createNewCollection. + /// + /// In en, this message translates to: + /// **'Create new collection'** + String get createNewCollection; + + /// No description provided for @create. + /// + /// In en, this message translates to: + /// **'Create'** + String get create; + + /// No description provided for @renameCollection. + /// + /// In en, this message translates to: + /// **'Rename collection'** + String get renameCollection; + + /// No description provided for @save. + /// + /// In en, this message translates to: + /// **'Save'** + String get save; + + /// No description provided for @deleteFile. + /// + /// In en, this message translates to: + /// **'Delete File'** + String get deleteFile; + + /// Confirmation message for file deletion + /// + /// In en, this message translates to: + /// **'Are you sure you want to delete \"{fileName}\"?'** + String deleteFileConfirmation(String fileName); + + /// No description provided for @noCollectionsFound. + /// + /// In en, this message translates to: + /// **'No collections found'** + String get noCollectionsFound; + + /// No description provided for @createYourFirstCollection. + /// + /// In en, this message translates to: + /// **'Create your first collection to get started'** + String get createYourFirstCollection; + + /// No description provided for @createCollection. + /// + /// In en, this message translates to: + /// **'Create Collection'** + String get createCollection; + + /// No description provided for @nothingYet. + /// + /// In en, this message translates to: + /// **'Nothing yet'** + String get nothingYet; + + /// No description provided for @uploadYourFirstDocument. + /// + /// In en, this message translates to: + /// **'Upload your first document to get started'** + String get uploadYourFirstDocument; + + /// No description provided for @uploadDocument. + /// + /// In en, this message translates to: + /// **'Upload Document'** + String get uploadDocument; + + /// Number of items in a collection + /// + /// In en, this message translates to: + /// **'{count} items'** + String items(int count); + + /// Number of files in a collection + /// + /// In en, this message translates to: + /// **'{count, plural, =0{no files} =1{1 file} other{{count} files}}'** + String files(int count); + + /// No description provided for @createCollectionTooltip. + /// + /// In en, this message translates to: + /// **'Create collection'** + String get createCollectionTooltip; + + /// No description provided for @uploadDocumentTooltip. + /// + /// In en, this message translates to: + /// **'Upload document'** + String get uploadDocumentTooltip; + + /// No description provided for @restore. + /// + /// In en, this message translates to: + /// **'Restore'** + String get restore; + + /// Dialog title for restoring a file from trash + /// + /// In en, this message translates to: + /// **'Restore {fileName}'** + String restoreFile(String fileName); + + /// No description provided for @noCollectionsAvailable. + /// + /// In en, this message translates to: + /// **'No collections available'** + String get noCollectionsAvailable; + + /// No description provided for @emptyTrash. + /// + /// In en, this message translates to: + /// **'Empty trash'** + String get emptyTrash; + + /// No description provided for @emptyTrashTooltip. + /// + /// In en, this message translates to: + /// **'Empty trash'** + String get emptyTrashTooltip; + + /// No description provided for @emptyTrashConfirmation. + /// + /// In en, this message translates to: + /// **'Are you sure you want to permanently delete all items in trash? This action cannot be undone.'** + String get emptyTrashConfirmation; + + /// No description provided for @fileTitle. + /// + /// In en, this message translates to: + /// **'File title'** + String get fileTitle; + + /// No description provided for @note. + /// + /// In en, this message translates to: + /// **'Note'** + String get note; + + /// No description provided for @optionalNote. + /// + /// In en, this message translates to: + /// **'Optional note'** + String get optionalNote; + + /// No description provided for @upload. + /// + /// In en, this message translates to: + /// **'Upload'** + String get upload; + + /// No description provided for @documentsHint. + /// + /// In en, this message translates to: + /// **'Documents'** + String get documentsHint; + + /// No description provided for @searchHint. + /// + /// In en, this message translates to: + /// **'Search...'** + String get searchHint; + + /// Message when no collections match the search query + /// + /// In en, this message translates to: + /// **'No collections found for \"{query}\"'** + String noCollectionsFoundForQuery(String query); + + /// No description provided for @uncategorized. + /// + /// In en, this message translates to: + /// **'Uncategorized'** + String get uncategorized; + + /// No description provided for @syncingTrash. + /// + /// In en, this message translates to: + /// **'Syncing trash...'** + String get syncingTrash; + + /// No description provided for @restoring. + /// + /// In en, this message translates to: + /// **'Restoring...'** + String get restoring; + + /// Success message when file is restored to a collection + /// + /// In en, this message translates to: + /// **'Restored \"{fileName}\" to \"{collectionName}\"'** + String restoredFileToCollection(String fileName, String collectionName); + + /// No description provided for @clearingTrash. + /// + /// In en, this message translates to: + /// **'Clearing trash...'** + String get clearingTrash; + + /// No description provided for @trashIsEmpty. + /// + /// In en, this message translates to: + /// **'Trash is empty'** + String get trashIsEmpty; + + /// No description provided for @share. + /// + /// In en, this message translates to: + /// **'Share'** + String get share; + + /// No description provided for @shareLink. + /// + /// In en, this message translates to: + /// **'Share link'** + String get shareLink; + + /// No description provided for @creatingShareLink. + /// + /// In en, this message translates to: + /// **'Creating link...'** + String get creatingShareLink; + + /// No description provided for @shareThisLink. + /// + /// In en, this message translates to: + /// **'Anyone with this link can access your file.'** + String get shareThisLink; + + /// No description provided for @copyLink. + /// + /// In en, this message translates to: + /// **'Copy link'** + String get copyLink; + + /// No description provided for @deleteLink. + /// + /// In en, this message translates to: + /// **'Delete link'** + String get deleteLink; + + /// No description provided for @close. + /// + /// In en, this message translates to: + /// **'Close'** + String get close; + + /// No description provided for @linkCopiedToClipboard. + /// + /// In en, this message translates to: + /// **'Link copied to clipboard'** + String get linkCopiedToClipboard; + + /// No description provided for @deleteShareLink. + /// + /// In en, this message translates to: + /// **'Delete link'** + String get deleteShareLink; + + /// No description provided for @deleteShareLinkDialogTitle. + /// + /// In en, this message translates to: + /// **'Delete link?'** + String get deleteShareLinkDialogTitle; + + /// No description provided for @deleteShareLinkConfirmation. + /// + /// In en, this message translates to: + /// **'People with this link will no longer be able to access the file.'** + String get deleteShareLinkConfirmation; + + /// No description provided for @deletingShareLink. + /// + /// In en, this message translates to: + /// **'Deleting link...'** + String get deletingShareLink; + + /// No description provided for @shareLinkDeletedSuccessfully. + /// + /// In en, this message translates to: + /// **'Link deleted successfully'** + String get shareLinkDeletedSuccessfully; + + /// No description provided for @failedToCreateShareLink. + /// + /// In en, this message translates to: + /// **'Failed to create link'** + String get failedToCreateShareLink; + + /// No description provided for @failedToDeleteShareLink. + /// + /// In en, this message translates to: + /// **'Failed to delete link'** + String get failedToDeleteShareLink; + + /// No description provided for @deletingFile. + /// + /// In en, this message translates to: + /// **'Deleting file...'** + String get deletingFile; + + /// No description provided for @addInformation. + /// + /// In en, this message translates to: + /// **'Add information'** + String get addInformation; + + /// No description provided for @addInformationDialogSubtitle. + /// + /// In en, this message translates to: + /// **'Choose the type of information you want to add'** + String get addInformationDialogSubtitle; + + /// No description provided for @physicalDocument. + /// + /// In en, this message translates to: + /// **'Physical document'** + String get physicalDocument; + + /// No description provided for @physicalDocumentDescription. + /// + /// In en, this message translates to: + /// **'Save information about documents and items in the real world.'** + String get physicalDocumentDescription; + + /// No description provided for @emergencyContact. + /// + /// In en, this message translates to: + /// **'Emergency contact'** + String get emergencyContact; + + /// No description provided for @emergencyContactDescription. + /// + /// In en, this message translates to: + /// **'Save information about important contacts.'** + String get emergencyContactDescription; + + /// No description provided for @accountCredential. + /// + /// In en, this message translates to: + /// **'Account credential'** + String get accountCredential; + + /// No description provided for @accountCredentialDescription. + /// + /// In en, this message translates to: + /// **'Save information about your important account credentials.'** + String get accountCredentialDescription; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['en'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': + return AppLocalizationsEn(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.'); +} diff --git a/mobile/apps/locker/lib/l10n/app_localizations_en.dart b/mobile/apps/locker/lib/l10n/app_localizations_en.dart new file mode 100644 index 0000000000..5fd46a81d3 --- /dev/null +++ b/mobile/apps/locker/lib/l10n/app_localizations_en.dart @@ -0,0 +1,410 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get onBoardingBody => 'Securely backup your documents'; + + @override + String get newUser => 'New to Ente'; + + @override + String get existingUser => 'Existing User'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get yes => 'Yes'; + + @override + String get developerSettingsWarning => + 'Are you sure that you want to modify Developer settings?'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get saveAction => 'Save'; + + @override + String get untitled => 'Untitled'; + + @override + String get edit => 'Edit'; + + @override + String get delete => 'Delete'; + + @override + String get noFilesFound => 'No files here'; + + @override + String get addFiles => 'Add Files'; + + @override + String get name => 'Name'; + + @override + String get date => 'Date'; + + @override + String get pleaseWait => 'Please wait...'; + + @override + String get downloading => 'Downloading...'; + + @override + String downloadingProgress(int percentage) { + return 'Downloading... $percentage%'; + } + + @override + String get downloadFailed => 'Download Failed'; + + @override + String get failedToDownloadOrDecrypt => 'Failed to download or decrypt file'; + + @override + String get errorOpeningFile => 'Error opening file'; + + @override + String errorOpeningFileMessage(String error) { + return 'Error opening file: $error'; + } + + @override + String couldNotOpenFile(String error) { + return 'Could not open file: $error'; + } + + @override + String minutesAgo(int minutes) { + return '${minutes}m ago'; + } + + @override + String hoursAgo(int hours) { + return '${hours}h ago'; + } + + @override + String get yesterday => 'Yesterday'; + + @override + String daysAgo(int days) { + return '${days}d ago'; + } + + @override + String get collectionRenamedSuccessfully => 'Collection renamed successfully'; + + @override + String failedToRenameCollection(String error) { + return 'Failed to rename collection: $error'; + } + + @override + String get collectionCannotBeDeleted => 'This collection cannot be deleted'; + + @override + String get deleteCollection => 'Delete collection'; + + @override + String deleteCollectionConfirmation(String collectionName) { + return 'Are you sure you want to delete \"$collectionName\"?'; + } + + @override + String get cancel => 'Cancel'; + + @override + String get collectionDeletedSuccessfully => 'Collection deleted successfully'; + + @override + String failedToDeleteCollection(String error) { + return 'Failed to delete collection: $error'; + } + + @override + String get collections => 'Collections'; + + @override + String get retry => 'Retry'; + + @override + String failedToSyncTrash(String error) { + return 'Failed to sync trash: $error'; + } + + @override + String get pleaseSelectAtLeastOneCollection => + 'Please select at least one collection'; + + @override + String get unknownItemType => 'Unknown item type'; + + @override + String get fileDeletedSuccessfully => 'File deleted successfully'; + + @override + String failedToDeleteFile(String error) { + return 'Failed to delete file: $error'; + } + + @override + String get fileUpdatedSuccessfully => 'File updated successfully!'; + + @override + String failedToUpdateFile(String error) { + return 'Failed to update file: $error'; + } + + @override + String get noChangesWereMade => 'No changes were made'; + + @override + String get noCollectionsAvailableForRestore => + 'No collections available for restore'; + + @override + String get trashClearedSuccessfully => 'Trash cleared successfully'; + + @override + String failedToClearTrash(String error) { + return 'Failed to clear trash: $error'; + } + + @override + String get trash => 'Trash'; + + @override + String deletedPermanently(String fileName) { + return 'Deleted \"$fileName\" permanently'; + } + + @override + String failedToRestoreFile(String fileName, String error) { + return 'Failed to restore \"$fileName\": $error'; + } + + @override + String get createNewCollection => 'Create new collection'; + + @override + String get create => 'Create'; + + @override + String get renameCollection => 'Rename collection'; + + @override + String get save => 'Save'; + + @override + String get deleteFile => 'Delete File'; + + @override + String deleteFileConfirmation(String fileName) { + return 'Are you sure you want to delete \"$fileName\"?'; + } + + @override + String get noCollectionsFound => 'No collections found'; + + @override + String get createYourFirstCollection => + 'Create your first collection to get started'; + + @override + String get createCollection => 'Create Collection'; + + @override + String get nothingYet => 'Nothing yet'; + + @override + String get uploadYourFirstDocument => + 'Upload your first document to get started'; + + @override + String get uploadDocument => 'Upload Document'; + + @override + String items(int count) { + return '$count items'; + } + + @override + String files(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count files', + one: '1 file', + zero: 'no files', + ); + return '$_temp0'; + } + + @override + String get createCollectionTooltip => 'Create collection'; + + @override + String get uploadDocumentTooltip => 'Upload document'; + + @override + String get restore => 'Restore'; + + @override + String restoreFile(String fileName) { + return 'Restore $fileName'; + } + + @override + String get noCollectionsAvailable => 'No collections available'; + + @override + String get emptyTrash => 'Empty trash'; + + @override + String get emptyTrashTooltip => 'Empty trash'; + + @override + String get emptyTrashConfirmation => + 'Are you sure you want to permanently delete all items in trash? This action cannot be undone.'; + + @override + String get fileTitle => 'File title'; + + @override + String get note => 'Note'; + + @override + String get optionalNote => 'Optional note'; + + @override + String get upload => 'Upload'; + + @override + String get documentsHint => 'Documents'; + + @override + String get searchHint => 'Search...'; + + @override + String noCollectionsFoundForQuery(String query) { + return 'No collections found for \"$query\"'; + } + + @override + String get uncategorized => 'Uncategorized'; + + @override + String get syncingTrash => 'Syncing trash...'; + + @override + String get restoring => 'Restoring...'; + + @override + String restoredFileToCollection(String fileName, String collectionName) { + return 'Restored \"$fileName\" to \"$collectionName\"'; + } + + @override + String get clearingTrash => 'Clearing trash...'; + + @override + String get trashIsEmpty => 'Trash is empty'; + + @override + String get share => 'Share'; + + @override + String get shareLink => 'Share link'; + + @override + String get creatingShareLink => 'Creating link...'; + + @override + String get shareThisLink => 'Anyone with this link can access your file.'; + + @override + String get copyLink => 'Copy link'; + + @override + String get deleteLink => 'Delete link'; + + @override + String get close => 'Close'; + + @override + String get linkCopiedToClipboard => 'Link copied to clipboard'; + + @override + String get deleteShareLink => 'Delete link'; + + @override + String get deleteShareLinkDialogTitle => 'Delete link?'; + + @override + String get deleteShareLinkConfirmation => + 'People with this link will no longer be able to access the file.'; + + @override + String get deletingShareLink => 'Deleting link...'; + + @override + String get shareLinkDeletedSuccessfully => 'Link deleted successfully'; + + @override + String get failedToCreateShareLink => 'Failed to create link'; + + @override + String get failedToDeleteShareLink => 'Failed to delete link'; + + @override + String get deletingFile => 'Deleting file...'; + + @override + String get addInformation => 'Add information'; + + @override + String get addInformationDialogSubtitle => + 'Choose the type of information you want to add'; + + @override + String get physicalDocument => 'Physical document'; + + @override + String get physicalDocumentDescription => + 'Save information about documents and items in the real world.'; + + @override + String get emergencyContact => 'Emergency contact'; + + @override + String get emergencyContactDescription => + 'Save information about important contacts.'; + + @override + String get accountCredential => 'Account credential'; + + @override + String get accountCredentialDescription => + 'Save information about your important account credentials.'; +} diff --git a/mobile/apps/locker/lib/l10n/l10n.dart b/mobile/apps/locker/lib/l10n/l10n.dart new file mode 100644 index 0000000000..64aa328de9 --- /dev/null +++ b/mobile/apps/locker/lib/l10n/l10n.dart @@ -0,0 +1,8 @@ +import "package:flutter/widgets.dart"; +import 'package:locker/l10n/app_localizations.dart'; + +export "app_localizations.dart"; + +extension AppLocalizationsX on BuildContext { + AppLocalizations get l10n => AppLocalizations.of(this)!; +} diff --git a/mobile/apps/locker/lib/main.dart b/mobile/apps/locker/lib/main.dart new file mode 100644 index 0000000000..b0e7d68eb9 --- /dev/null +++ b/mobile/apps/locker/lib/main.dart @@ -0,0 +1,163 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:ente_accounts/services/user_service.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_lock_screen/lock_screen_settings.dart'; +import 'package:ente_lock_screen/ui/app_lock.dart'; +import 'package:ente_lock_screen/ui/lock_screen.dart'; +import 'package:ente_logging/logging.dart'; +import 'package:ente_network/network.dart'; +import 'package:ente_ui/utils/window_listener_service.dart'; +import 'package:ente_utils/platform_util.dart'; +import "package:flutter/material.dart"; +import 'package:flutter_displaymode/flutter_displaymode.dart'; +import 'package:locker/app.dart'; +import 'package:locker/core/locale.dart'; +import 'package:locker/l10n/app_localizations.dart'; +import 'package:locker/services/collections/collections_api_client.dart'; +import "package:locker/services/collections/collections_db.dart"; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/configuration.dart'; +import 'package:locker/services/files/download/service_locator.dart'; +import "package:locker/services/files/links/links_client.dart"; +import "package:locker/services/files/links/links_service.dart"; +import "package:locker/services/trash/trash_db.dart"; +import 'package:locker/services/trash/trash_service.dart'; +import 'package:locker/ui/pages/home_page.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tray_manager/tray_manager.dart'; +import 'package:window_manager/window_manager.dart'; + +final _logger = Logger("main"); + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + if (PlatformUtil.isDesktop()) { + await windowManager.ensureInitialized(); + await WindowListenerService.instance.init(); + final WindowOptions windowOptions = WindowOptions( + size: WindowListenerService.instance.getWindowSize(), + maximumSize: const Size(8192, 8192), + ); + await windowManager.waitUntilReadyToShow(windowOptions, () async { + await windowManager.show(); + await windowManager.focus(); + _initSystemTray().ignore(); + }); + } + + await _runInForeground(); + if (Platform.isAndroid) { + FlutterDisplayMode.setHighRefreshRate().ignore(); + } +} + +Future _initSystemTray() async { + if (PlatformUtil.isMobile()) return; + final String path = Platform.isWindows + ? 'assets/icons/locker-icon.ico' + : 'assets/icons/locker-icon.png'; + await trayManager.setIcon(path); + final Menu menu = Menu( + items: [ + MenuItem( + key: 'hide_window', + label: 'Hide Window', + ), + MenuItem( + key: 'show_window', + label: 'Show Window', + ), + MenuItem.separator(), + MenuItem( + key: 'exit_app', + label: 'Exit App', + ), + ], + ); + await trayManager.setContextMenu(menu); +} + +Future _runInForeground() async { + final savedThemeMode = _themeMode(await AdaptiveTheme.getThemeMode()); + return await _runWithLogs(() async { + _logger.info("Starting app in foreground"); + try { + await _init(false, via: 'mainMethod'); + } catch (e, s) { + _logger.severe("Failed to init", e, s); + rethrow; + } + final Locale? locale = await getLocale(noFallback: true); + runApp( + AppLock( + builder: (args) => App(locale: locale), + lockScreen: LockScreen(Configuration.instance), + enabled: await LockScreenSettings.instance.shouldShowLockScreen(), + locale: locale, + savedThemeMode: savedThemeMode, + supportedLocales: appSupportedLocales, + localizationsDelegates: AppLocalizations.localizationsDelegates, + localeListResolutionCallback: localResolutionCallBack, + ), + ); + }); +} + +ThemeMode _themeMode(AdaptiveThemeMode? savedThemeMode) { + if (savedThemeMode == null) return ThemeMode.system; + if (savedThemeMode.isLight) return ThemeMode.light; + if (savedThemeMode.isDark) return ThemeMode.dark; + return ThemeMode.system; +} + +Future _runWithLogs(Function() function, {String prefix = ""}) async { + String dir = ""; + try { + dir = "${(await getApplicationSupportDirectory()).path}/logs"; + } catch (_) {} + await SuperLogging.main( + LogConfig( + body: function, + logDirPath: dir, + maxLogFiles: 5, + enableInDebugMode: true, + prefix: prefix, + ), + ); +} + +Future _init(bool bool, {String? via}) async { + final SharedPreferences preferences = await SharedPreferences.getInstance(); + final PackageInfo packageInfo = await PackageInfo.fromPlatform(); + + await CryptoUtil.init(); + + await CollectionDB.instance.init(); + await TrashDB.instance.init(); + + await Configuration.instance.init([ + CollectionDB.instance, + TrashDB.instance, + ]); + + await Network.instance.init(Configuration.instance); + await UserService.instance.init(Configuration.instance, const HomePage()); + await LockScreenSettings.instance.init(Configuration.instance); + await CollectionApiClient.instance.init(); + await CollectionService.instance.init(); + await LinksClient.instance.init(); + await LinksService.instance.init(); + await ServiceLocator.instance.init( + preferences, + Network.instance.enteDio, + Network.instance.getDio(), + packageInfo, + ); + await TrashService.instance.init(preferences); +} diff --git a/mobile/apps/locker/lib/services/collections/collections_api_client.dart b/mobile/apps/locker/lib/services/collections/collections_api_client.dart new file mode 100644 index 0000000000..bb2c2003a4 --- /dev/null +++ b/mobile/apps/locker/lib/services/collections/collections_api_client.dart @@ -0,0 +1,452 @@ +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:dio/dio.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_network/network.dart'; +import 'package:locker/core/errors.dart'; +import "package:locker/services/collections/collections_service.dart"; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/collections/models/collection_file_item.dart'; +import 'package:locker/services/collections/models/collection_magic.dart'; +import 'package:locker/services/collections/models/diff.dart'; +import 'package:locker/services/configuration.dart'; +import "package:locker/services/files/sync/metadata_updater_service.dart"; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/services/files/sync/models/file_magic.dart'; +import 'package:locker/services/trash/models/trash_item_request.dart'; +import "package:locker/utils/crypto_helper.dart"; +import 'package:logging/logging.dart'; + +class CollectionApiClient { + CollectionApiClient._privateConstructor(); + + static final CollectionApiClient instance = + CollectionApiClient._privateConstructor(); + + final _logger = Logger("CollectionApiClient"); + final _enteDio = Network.instance.enteDio; + final _config = Configuration.instance; + + Future init() async {} + + Future> getCollections(int sinceTime) async { + try { + final response = await _enteDio.get( + "/collections/v2", + queryParameters: { + "sinceTime": sinceTime, + }, + ); + final List collections = []; + final c = response.data["collections"]; + for (final collectionData in c) { + final Collection collection = + await _fromRemoteCollection(collectionData); + collections.add(collection); + } + return collections; + } catch (e, s) { + _logger.warning(e, s); + if (e is DioException && e.response?.statusCode == 401) { + throw UnauthorizedError(); + } + rethrow; + } + } + + Future getFiles(Collection collection, int sinceTime) async { + _logger.info( + "[Collection-${collection.id}] fetch diff since: $sinceTime", + ); + bool hasMore = true; + final List updatedFiles = []; + final List deletedFiles = []; + int latestUpdatedAtTime = 0; + final collectionKey = CryptoHelper.instance.getCollectionKey(collection); + while (hasMore) { + final diff = await _getDiff(collection.id, collectionKey, sinceTime); + updatedFiles.addAll(diff.updatedFiles); + deletedFiles.addAll(diff.deletedFiles); + latestUpdatedAtTime = max(latestUpdatedAtTime, diff.latestUpdatedAtTime); + hasMore = diff.hasMore; + } + final finalDiff = Diff( + updatedFiles, + deletedFiles, + false, + latestUpdatedAtTime, + ); + _logger.info("[Collection-${collection.id}] fetched"); + return finalDiff; + } + + Future addToCollection( + Collection collection, + List files, + ) async { + final pendingUpload = files.any( + (element) => element.uploadedFileID == null, + ); + if (pendingUpload) { + throw ArgumentError('Can only add uploaded files'); + } + if (files.isEmpty) { + _logger.info("nothing to add to the collection"); + return; + } + final params = {}; + params["collectionID"] = collection.id; + final collectionKey = CryptoHelper.instance.getCollectionKey(collection); + params["files"] = []; + for (final file in files) { + final int uploadedFileID = file.uploadedFileID!; + final fileKey = await CollectionService.instance.getFileKey(file); + final encryptedKeyData = CryptoUtil.encryptSync(fileKey, collectionKey); + final String encryptedKey = + CryptoUtil.bin2base64(encryptedKeyData.encryptedData!); + final String keyDecryptionNonce = + CryptoUtil.bin2base64(encryptedKeyData.nonce!); + params["files"].add( + CollectionFileItem(uploadedFileID, encryptedKey, keyDecryptionNonce) + .toMap(), + ); + } + try { + await _enteDio.post( + "/collections/add-files", + data: params, + ); + } catch (e) { + _logger.warning('failed to add files to collection', e); + rethrow; + } + } + + Future trash(List requests) async { + final requestData = {}; + requestData["items"] = []; + for (final request in requests) { + requestData["items"].add(request.toJson()); + } + final response = await _enteDio.post( + "/files/trash", + data: requestData, + ); + if (response.statusCode != 200) { + throw Exception("Failed to remove files from collection"); + } + } + + Future rename(Collection collection, String newName) async { + final collectionKey = CryptoHelper.instance.getCollectionKey(collection); + final encryptedName = CryptoUtil.encryptSync( + utf8.encode(newName), + collectionKey, + ); + final params = {}; + params["collectionID"] = collection.id; + params["encryptedName"] = + CryptoUtil.bin2base64(encryptedName.encryptedData!); + params["nameDecryptionNonce"] = CryptoUtil.bin2base64(encryptedName.nonce!); + try { + await _enteDio.post( + "/collections/rename", + data: params, + ); + } catch (e) { + _logger.warning("failed to rename collection", e); + rethrow; + } + } + + Future move( + EnteFile file, + Collection fromCollection, + Collection toCollection, + ) async { + final params = {}; + params["fromCollectionID"] = fromCollection.id; + params["toCollectionID"] = toCollection.id; + final fileKey = await CollectionService.instance.getFileKey(file); + final encryptedKeyData = CryptoUtil.encryptSync( + fileKey, + CryptoHelper.instance.getCollectionKey(toCollection), + ); + params["files"] = []; + params["files"].add( + CollectionFileItem( + file.uploadedFileID!, + CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), + CryptoUtil.bin2base64(encryptedKeyData.nonce!), + ).toMap(), + ); + await _enteDio.post( + "/collections/move-files", + data: params, + ); + } + + Future trashCollection(Collection collection) async { + try { + await _enteDio.delete( + "/collections/v3/${collection.id}?keepFiles=False&collectionID=${collection.id}", + ); + } catch (e) { + _logger.severe('failed to trash collection', e); + rethrow; + } + } + + Future _getDiff( + int collectionID, + Uint8List collectionKey, + int sinceTime, + ) async { + try { + final response = await _enteDio.get( + "/collections/v2/diff", + queryParameters: { + "collectionID": collectionID, + "sinceTime": sinceTime, + }, + ); + int latestUpdatedAtTime = 0; + final diff = response.data["diff"] as List; + final bool hasMore = response.data["hasMore"] as bool; + final startTime = DateTime.now(); + final deletedFiles = []; + final updatedFiles = []; + + for (final item in diff) { + final file = EnteFile(); + file.uploadedFileID = item["id"]; + file.collectionID = item["collectionID"]; + file.updationTime = item["updationTime"]; + latestUpdatedAtTime = max(latestUpdatedAtTime, file.updationTime!); + if (item["isDeleted"]) { + deletedFiles.add(file); + continue; + } + file.ownerID = item["ownerID"]; + file.fileDecryptionHeader = item["file"]["decryptionHeader"]; + file.thumbnailDecryptionHeader = item["thumbnail"]["decryptionHeader"]; + file.metadataDecryptionHeader = item["metadata"]["decryptionHeader"]; + if (item["info"] != null) { + file.fileSize = item["info"]["fileSize"]; + } + file.encryptedKey = item["encryptedKey"]; + file.keyDecryptionNonce = item["keyDecryptionNonce"]; + final fileKey = CryptoHelper.instance.getFileKey( + file.encryptedKey!, + file.keyDecryptionNonce!, + collectionKey, + ); + final encodedMetadata = await CryptoUtil.decryptData( + CryptoUtil.base642bin(item["metadata"]["encryptedData"]), + fileKey, + CryptoUtil.base642bin(file.metadataDecryptionHeader!), + ); + final Map metadata = + jsonDecode(utf8.decode(encodedMetadata)); + file.applyMetadata(metadata); + if (item['magicMetadata'] != null) { + final utfEncodedMmd = await CryptoUtil.decryptData( + CryptoUtil.base642bin(item['magicMetadata']['data']), + fileKey, + CryptoUtil.base642bin(item['magicMetadata']['header']), + ); + file.mMdEncodedJson = utf8.decode(utfEncodedMmd); + file.mMdVersion = item['magicMetadata']['version']; + file.magicMetadata = + MagicMetadata.fromEncodedJson(file.mMdEncodedJson!); + } + if (item['pubMagicMetadata'] != null) { + final utfEncodedMmd = await CryptoUtil.decryptData( + CryptoUtil.base642bin(item['pubMagicMetadata']['data']), + fileKey, + CryptoUtil.base642bin(item['pubMagicMetadata']['header']), + ); + file.pubMmdEncodedJson = utf8.decode(utfEncodedMmd); + file.pubMmdVersion = item['pubMagicMetadata']['version']; + file.pubMagicMetadata = + PubMagicMetadata.fromEncodedJson(file.pubMmdEncodedJson!); + } + updatedFiles.add(file); + } + _logger.info('[Collection-$collectionID] parsed ${diff.length} ' + 'diff items ( ${updatedFiles.length} updated) in ${DateTime.now().difference(startTime).inMilliseconds}ms'); + return Diff(updatedFiles, deletedFiles, hasMore, latestUpdatedAtTime); + } catch (e, s) { + _logger.severe(e, s); + rethrow; + } + } + + Future _fromRemoteCollection( + Map? collectionData, + ) async { + final Collection collection = Collection.fromMap(collectionData); + if (collectionData != null && !collection.isDeleted) { + final collectionKey = CryptoHelper.instance.getCollectionKey(collection); + if (collectionData['magicMetadata'] != null) { + final utfEncodedMmd = await CryptoUtil.decryptData( + CryptoUtil.base642bin(collectionData['magicMetadata']['data']), + collectionKey, + CryptoUtil.base642bin(collectionData['magicMetadata']['header']), + ); + collection.mMdEncodedJson = utf8.decode(utfEncodedMmd); + collection.mMdVersion = collectionData['magicMetadata']['version']; + collection.magicMetadata = CollectionMagicMetadata.fromEncodedJson( + collection.mMdEncodedJson ?? '{}', + ); + } + + if (collectionData['pubMagicMetadata'] != null) { + final utfEncodedMmd = await CryptoUtil.decryptData( + CryptoUtil.base642bin(collectionData['pubMagicMetadata']['data']), + collectionKey, + CryptoUtil.base642bin( + collectionData['pubMagicMetadata']['header'], + ), + ); + collection.mMdPubEncodedJson = utf8.decode(utfEncodedMmd); + collection.mMbPubVersion = + collectionData['pubMagicMetadata']['version']; + collection.pubMagicMetadata = + CollectionPubMagicMetadata.fromEncodedJson( + collection.mMdPubEncodedJson ?? '{}', + ); + } + if (collectionData['sharedMagicMetadata'] != null) { + final utfEncodedMmd = await CryptoUtil.decryptData( + CryptoUtil.base642bin( + collectionData['sharedMagicMetadata']['data'], + ), + collectionKey, + CryptoUtil.base642bin( + collectionData['sharedMagicMetadata']['header'], + ), + ); + collection.sharedMmdJson = utf8.decode(utfEncodedMmd); + collection.sharedMmdVersion = + collectionData['sharedMagicMetadata']['version']; + collection.sharedMagicMetadata = ShareeMagicMetadata.fromEncodedJson( + collection.sharedMmdJson ?? '{}', + ); + } + } + collection.setName(_getDecryptedCollectionName(collection)); + return collection; + } + + String _getDecryptedCollectionName(Collection collection) { + if (collection.isDeleted) { + return "Deleted Collection"; + } + if (collection.encryptedName != null && + collection.encryptedName!.isNotEmpty) { + try { + final collectionKey = + CryptoHelper.instance.getCollectionKey(collection); + final result = CryptoUtil.decryptSync( + CryptoUtil.base642bin(collection.encryptedName!), + collectionKey, + CryptoUtil.base642bin(collection.nameDecryptionNonce!), + ); + return utf8.decode(result); + } catch (e, s) { + _logger.severe( + "failed to decrypt collection name: ${collection.id}", + e, + s, + ); + } + } + return collection.name ?? "Untitled"; + } + + Future create(String name, CollectionType type) async { + final collectionKey = CryptoUtil.generateKey(); + final encryptedKeyData = + CryptoUtil.encryptSync(collectionKey, _config.getKey()!); + final encryptedName = CryptoUtil.encryptSync( + utf8.encode(name), + collectionKey, + ); + final request = CreateRequest( + encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), + keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!), + encryptedName: CryptoUtil.bin2base64(encryptedName.encryptedData!), + nameDecryptionNonce: CryptoUtil.bin2base64(encryptedName.nonce!), + type: type, + attributes: CollectionAttributes(), + ); + return _enteDio + .post( + "/collections", + data: request.toJson(), + ) + .then((response) async { + final collectionData = response.data["collection"]; + final collection = await _fromRemoteCollection(collectionData); + return collection; + }); + } +} + +class CreateRequest { + String encryptedKey; + String keyDecryptionNonce; + String encryptedName; + String nameDecryptionNonce; + CollectionType type; + CollectionAttributes? attributes; + MetadataRequest? magicMetadata; + + CreateRequest({ + required this.encryptedKey, + required this.keyDecryptionNonce, + required this.encryptedName, + required this.nameDecryptionNonce, + required this.type, + this.attributes, + this.magicMetadata, + }); + + CreateRequest copyWith({ + String? encryptedKey, + String? keyDecryptionNonce, + String? encryptedName, + String? nameDecryptionNonce, + CollectionType? type, + CollectionAttributes? attributes, + MetadataRequest? magicMetadata, + }) => + CreateRequest( + encryptedKey: encryptedKey ?? this.encryptedKey, + keyDecryptionNonce: keyDecryptionNonce ?? this.keyDecryptionNonce, + encryptedName: encryptedName ?? this.encryptedName, + nameDecryptionNonce: nameDecryptionNonce ?? this.nameDecryptionNonce, + type: type ?? this.type, + attributes: attributes ?? this.attributes, + magicMetadata: magicMetadata ?? this.magicMetadata, + ); + + Map toJson() { + final map = {}; + map['encryptedKey'] = encryptedKey; + map['keyDecryptionNonce'] = keyDecryptionNonce; + map['encryptedName'] = encryptedName; + map['nameDecryptionNonce'] = nameDecryptionNonce; + map['type'] = typeToString(type); + if (attributes != null) { + map['attributes'] = attributes!.toMap(); + } + if (magicMetadata != null) { + map['magicMetadata'] = magicMetadata!.toJson(); + } + return map; + } +} diff --git a/mobile/apps/locker/lib/services/collections/collections_db.dart b/mobile/apps/locker/lib/services/collections/collections_db.dart new file mode 100644 index 0000000000..6a1b22a6c9 --- /dev/null +++ b/mobile/apps/locker/lib/services/collections/collections_db.dart @@ -0,0 +1,535 @@ +import 'dart:convert'; + +import "package:ente_base/models/database.dart"; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/collections/models/public_url.dart'; +import 'package:locker/services/collections/models/user.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sqflite/sqflite.dart'; + +class CollectionDB extends EnteBaseDatabase { + CollectionDB._privateConstructor(); + + static final CollectionDB instance = CollectionDB._privateConstructor(); + + Database? _database; + int _collectionSyncTime = 0; + final Map _collectionSyncTimesCache = {}; + + static const String _collectionsTable = 'collections'; + static const String _filesTable = 'files'; + static const String _collectionFilesTable = 'collection_files'; + static const String _syncTimesTable = 'sync_times'; + + Future init() async { + _database = await _initDatabase(); + await _loadCaches(); + } + + Future _loadCaches() async { + final syncTimes = await _db.query( + _syncTimesTable, + where: 'key LIKE ?', + whereArgs: ['collection_sync_time_%'], + ); + + for (final row in syncTimes) { + final key = row['key'] as String; + final collectionId = + int.tryParse(key.replaceFirst('collection_sync_time_', '')); + if (collectionId != null) { + _collectionSyncTimesCache[collectionId] = row['value'] as int; + } + } + + final globalSyncTime = await getSyncTimeAsync(); + _collectionSyncTime = globalSyncTime; + } + + Future _initDatabase() async { + final documentsDirectory = await getApplicationDocumentsDirectory(); + final path = join(documentsDirectory.path, 'collection_store.db'); + + return await openDatabase( + path, + version: 1, + onCreate: _createTables, + ); + } + + Future _createTables(Database db, int version) async { + await db.execute(''' + CREATE TABLE $_collectionsTable ( + id INTEGER PRIMARY KEY, + owner_id INTEGER, + owner_email TEXT, + owner_name TEXT, + encrypted_key TEXT, + key_decryption_nonce TEXT, + name TEXT, + type TEXT, + attributes_version INTEGER, + attributes_encrypted_path TEXT, + attributes_path_decryption_nonce TEXT, + sharees TEXT, + public_urls TEXT, + updation_time INTEGER, + is_deleted INTEGER DEFAULT 0, + decrypted_path TEXT, + m_md_encoded_json TEXT, + m_md_pub_encoded_json TEXT, + shared_mmd_json TEXT, + m_md_version INTEGER DEFAULT 0, + m_mb_pub_version INTEGER DEFAULT 0, + shared_mmd_version INTEGER DEFAULT 0 + ) + '''); + + await db.execute(''' + CREATE TABLE $_filesTable ( + uploaded_file_id INTEGER, + local_path TEXT, + owner_id INTEGER, + collection_id INTEGER, + title TEXT, + creation_time INTEGER, + modification_time INTEGER, + updation_time INTEGER, + added_time INTEGER, + hash TEXT, + metadata_version INTEGER, + encrypted_key TEXT, + key_decryption_nonce TEXT, + file_decryption_header TEXT, + thumbnail_decryption_header TEXT, + metadata_decryption_header TEXT, + file_size INTEGER, + m_md_encoded_json TEXT, + m_md_version INTEGER DEFAULT 0, + pub_mmd_encoded_json TEXT, + pub_mmd_version INTEGER DEFAULT 1 + ) + '''); + + await db.execute(''' + CREATE TABLE $_collectionFilesTable ( + collection_id INTEGER, + uploaded_file_id INTEGER, + PRIMARY KEY (collection_id, uploaded_file_id) + ) + '''); + + await db.execute(''' + CREATE TABLE $_syncTimesTable ( + key TEXT PRIMARY KEY, + value INTEGER NOT NULL + ) + '''); + + await db.execute(''' + CREATE INDEX idx_files_collection_id ON $_filesTable (collection_id) + '''); + + await db.execute(''' + CREATE INDEX idx_files_uploaded_file_id ON $_filesTable (uploaded_file_id) + '''); + + await db.execute(''' + CREATE INDEX idx_collection_files_uploaded_file_id ON $_collectionFilesTable (uploaded_file_id) + '''); + + await db.execute(''' + CREATE INDEX idx_collections_updation_time ON $_collectionsTable (updation_time) + '''); + } + + Database get _db { + if (_database == null) { + throw Exception('Database not initialized. Call init() first.'); + } + return _database!; + } + + Future setSyncTime(int lastSyncTime) async { + _collectionSyncTime = lastSyncTime; + await _db.insert( + _syncTimesTable, + {'key': 'collection_sync_time', 'value': lastSyncTime}, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + int getSyncTime() { + return _collectionSyncTime; + } + + Future getSyncTimeAsync() async { + final result = await _db.query( + _syncTimesTable, + where: 'key = ?', + whereArgs: ['collection_sync_time'], + ); + + if (result.isNotEmpty) { + _collectionSyncTime = result.first['value'] as int; + return _collectionSyncTime; + } + return 0; + } + + int getCollectionSyncTime(int collectionId) { + return _collectionSyncTimesCache[collectionId] ?? 0; + } + + Future getCollectionSyncTimeAsync(int collectionId) async { + final result = await _db.query( + _syncTimesTable, + where: 'key = ?', + whereArgs: ['collection_sync_time_$collectionId'], + ); + + int syncTime = 0; + if (result.isNotEmpty) { + syncTime = result.first['value'] as int; + } + + _collectionSyncTimesCache[collectionId] = syncTime; + return syncTime; + } + + Future setCollectionSyncTime(int collectionId, int lastSyncTime) async { + await _db.insert( + _syncTimesTable, + {'key': 'collection_sync_time_$collectionId', 'value': lastSyncTime}, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + + _collectionSyncTimesCache[collectionId] = lastSyncTime; + } + + Future getCollection(int id) async { + final result = await _db.query( + _collectionsTable, + where: 'id = ?', + whereArgs: [id], + ); + + if (result.isEmpty) { + throw Exception('Collection with id $id not found'); + } + + return _mapToCollection(result.first); + } + + Future updateCollections(List collections) async { + final batch = _db.batch(); + + for (final collection in collections) { + batch.delete( + _collectionsTable, + where: 'id = ?', + whereArgs: [collection.id], + ); + + batch.insert( + _collectionsTable, + _collectionToMap(collection), + ); + } + + await batch.commit(); + } + + Future deleteCollection(Collection collection) async { + final batch = _db.batch(); + + batch.delete( + _collectionsTable, + where: 'id = ?', + whereArgs: [collection.id], + ); + + batch.delete( + _collectionFilesTable, + where: 'collection_id = ?', + whereArgs: [collection.id], + ); + + final filesInCollection = await _db.query( + _collectionFilesTable, + where: 'collection_id = ?', + whereArgs: [collection.id], + ); + + for (final fileMap in filesInCollection) { + final uploadedFileId = fileMap['uploaded_file_id'] as int; + final otherCollections = await _db.query( + _collectionFilesTable, + where: 'uploaded_file_id = ? AND collection_id != ?', + whereArgs: [uploadedFileId, collection.id], + ); + + if (otherCollections.isEmpty) { + batch.delete( + _filesTable, + where: 'uploaded_file_id = ?', + whereArgs: [uploadedFileId], + ); + } + } + + await batch.commit(); + } + + Future> getCollections() async { + final result = await _db.query(_collectionsTable); + return result.map((row) => _mapToCollection(row)).toList(); + } + + Future addFilesToCollection( + Collection collection, + List files, + ) async { + final batch = _db.batch(); + + for (final file in files) { + batch.insert( + _filesTable, + _fileToMap(file), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + + batch.insert( + _collectionFilesTable, + { + 'collection_id': collection.id, + 'uploaded_file_id': file.uploadedFileID!, + }, + conflictAlgorithm: ConflictAlgorithm.ignore, + ); + } + + await batch.commit(); + } + + Future deleteFilesFromCollection( + Collection collection, + List files, + ) async { + final batch = _db.batch(); + + for (final file in files) { + batch.delete( + _collectionFilesTable, + where: 'collection_id = ? AND uploaded_file_id = ?', + whereArgs: [collection.id, file.uploadedFileID!], + ); + + final otherCollections = await _db.query( + _collectionFilesTable, + where: 'uploaded_file_id = ? AND collection_id != ?', + whereArgs: [file.uploadedFileID!, collection.id], + ); + + if (otherCollections.isEmpty) { + batch.delete( + _filesTable, + where: 'uploaded_file_id = ?', + whereArgs: [file.uploadedFileID!], + ); + } + } + + await batch.commit(); + } + + Future> getFilesInCollection(Collection collection) async { + final result = await _db.rawQuery( + ''' + SELECT f.* + FROM $_filesTable f + INNER JOIN $_collectionFilesTable cf ON f.uploaded_file_id = cf.uploaded_file_id + WHERE cf.collection_id = ? + ''', + [collection.id], + ); + + return result.map((row) => _mapToFile(row)).toList(); + } + + Future> getCollectionsForFile(EnteFile file) async { + final result = await _db.rawQuery( + ''' + SELECT c.* + FROM $_collectionsTable c + INNER JOIN $_collectionFilesTable cf ON c.id = cf.collection_id + WHERE cf.uploaded_file_id = ? + ''', + [file.uploadedFileID!], + ); + + return result.map((row) => _mapToCollection(row)).toList(); + } + + Future> getAllFiles() async { + final result = await _db.query(_filesTable); + return result.map((row) => _mapToFile(row)).toList(); + } + + Map _collectionToMap(Collection collection) { + return { + 'id': collection.id, + 'owner_id': collection.owner.id, + 'owner_email': collection.owner.email, + 'owner_name': collection.owner.name, + 'encrypted_key': collection.encryptedKey, + 'key_decryption_nonce': collection.keyDecryptionNonce, + 'name': collection.name, + 'type': typeToString(collection.type), + 'attributes_version': collection.attributes.version, + 'attributes_encrypted_path': collection.attributes.encryptedPath, + 'attributes_path_decryption_nonce': + collection.attributes.pathDecryptionNonce, + 'sharees': + jsonEncode(collection.sharees.map((user) => user.toMap()).toList()), + 'public_urls': + jsonEncode(collection.publicURLs.map((url) => url.toMap()).toList()), + 'updation_time': collection.updationTime, + 'is_deleted': collection.isDeleted ? 1 : 0, + 'decrypted_path': collection.decryptedPath, + 'm_md_encoded_json': collection.mMdEncodedJson, + 'm_md_pub_encoded_json': collection.mMdPubEncodedJson, + 'shared_mmd_json': collection.sharedMmdJson, + 'm_md_version': collection.mMdVersion, + 'm_mb_pub_version': collection.mMbPubVersion, + 'shared_mmd_version': collection.sharedMmdVersion, + }; + } + + Collection _mapToCollection(Map map) { + final owner = User( + id: map['owner_id'] as int?, + email: map['owner_email'] as String, + name: map['owner_name'] as String?, + ); + + final attributes = CollectionAttributes( + version: map['attributes_version'] as int? ?? 0, + encryptedPath: map['attributes_encrypted_path'] as String?, + pathDecryptionNonce: map['attributes_path_decryption_nonce'] as String?, + ); + + final shareesJson = map['sharees'] as String? ?? '[]'; + final shareesData = jsonDecode(shareesJson) as List; + final sharees = shareesData + .map((shareeeMap) => User.fromMap(shareeeMap as Map)) + .where((user) => user != null) + .cast() + .toList(); + + final publicUrlsJson = map['public_urls'] as String? ?? '[]'; + final publicURLsData = jsonDecode(publicUrlsJson) as List; + final publicURLs = publicURLsData + .map((urlMap) => PublicURL.fromMap(urlMap as Map)) + .where((url) => url != null) + .cast() + .toList(); + + final collection = Collection( + map['id'] as int, + owner, + map['encrypted_key'] as String, + map['key_decryption_nonce'] as String?, + map['name'] as String?, + null, + null, + typeFromString(map['type'] as String), + attributes, + sharees, + publicURLs, + map['updation_time'] as int, + isDeleted: (map['is_deleted'] as int? ?? 0) == 1, + ); + + collection.decryptedPath = map['decrypted_path'] as String?; + collection.mMdEncodedJson = map['m_md_encoded_json'] as String?; + collection.mMdPubEncodedJson = map['m_md_pub_encoded_json'] as String?; + collection.sharedMmdJson = map['shared_mmd_json'] as String?; + collection.mMdVersion = map['m_md_version'] as int? ?? 0; + collection.mMbPubVersion = map['m_mb_pub_version'] as int? ?? 0; + collection.sharedMmdVersion = map['shared_mmd_version'] as int? ?? 0; + + return collection; + } + + Map _fileToMap(EnteFile file) { + return { + 'uploaded_file_id': file.uploadedFileID, + 'local_path': file.localPath, + 'owner_id': file.ownerID, + 'collection_id': file.collectionID, + 'title': file.title, + 'creation_time': file.creationTime, + 'modification_time': file.modificationTime, + 'updation_time': file.updationTime, + 'added_time': file.addedTime, + 'hash': file.hash, + 'metadata_version': file.metadataVersion, + 'encrypted_key': file.encryptedKey, + 'key_decryption_nonce': file.keyDecryptionNonce, + 'file_decryption_header': file.fileDecryptionHeader, + 'thumbnail_decryption_header': file.thumbnailDecryptionHeader, + 'metadata_decryption_header': file.metadataDecryptionHeader, + 'file_size': file.fileSize, + 'm_md_encoded_json': file.mMdEncodedJson, + 'm_md_version': file.mMdVersion, + 'pub_mmd_encoded_json': file.pubMmdEncodedJson, + 'pub_mmd_version': file.pubMmdVersion, + }; + } + + EnteFile _mapToFile(Map map) { + final file = EnteFile(); + + file.localPath = map['local_path']; + file.uploadedFileID = map['uploaded_file_id']; + file.ownerID = map['owner_id']; + file.collectionID = map['collection_id']; + file.title = map['title']; + file.creationTime = map['creation_time']; + file.modificationTime = map['modification_time']; + file.updationTime = map['updation_time']; + file.addedTime = map['added_time']; + file.hash = map['hash']; + file.metadataVersion = map['metadata_version']; + file.encryptedKey = map['encrypted_key']; + file.keyDecryptionNonce = map['key_decryption_nonce']; + file.fileDecryptionHeader = map['file_decryption_header']; + file.thumbnailDecryptionHeader = map['thumbnail_decryption_header']; + file.metadataDecryptionHeader = map['metadata_decryption_header']; + file.fileSize = map['file_size']; + file.mMdEncodedJson = map['m_md_encoded_json']; + file.mMdVersion = map['m_md_version'] ?? 0; + file.pubMmdEncodedJson = map['pub_mmd_encoded_json']; + file.pubMmdVersion = map['pub_mmd_version'] ?? 1; + + return file; + } + + Future close() async { + await _database?.close(); + _database = null; + } + + @override + Future clearTable() async { + await _database?.delete(_collectionsTable); + await _database?.delete(_filesTable); + await _database?.delete(_collectionFilesTable); + await _database?.delete(_syncTimesTable); + _collectionSyncTimesCache.clear(); + _collectionSyncTime = 0; + } +} diff --git a/mobile/apps/locker/lib/services/collections/collections_service.dart b/mobile/apps/locker/lib/services/collections/collections_service.dart new file mode 100644 index 0000000000..9813e68e49 --- /dev/null +++ b/mobile/apps/locker/lib/services/collections/collections_service.dart @@ -0,0 +1,343 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:ente_events/event_bus.dart'; +import 'package:ente_events/models/signed_in_event.dart'; +import 'package:locker/events/collections_updated_event.dart'; +import "package:locker/services/collections/collections_api_client.dart"; +import "package:locker/services/collections/collections_db.dart"; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/configuration.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/services/trash/models/trash_item_request.dart'; +import "package:locker/services/trash/trash_service.dart"; +import "package:locker/utils/crypto_helper.dart"; +import 'package:logging/logging.dart'; + +class CollectionService { + CollectionService._privateConstructor(); + + static final CollectionService instance = + CollectionService._privateConstructor(); + + // Fixed set of suggested collection names + static const Set _suggestedCollectionNames = { + 'Personal', + 'Work', + 'Travel', + 'Family', + 'Projects', + 'School', + 'Music', + 'Books', + 'Events', + 'Holidays', + }; + + final _logger = Logger("CollectionService"); + final _apiClient = CollectionApiClient.instance; + + Future init() async { + if (Configuration.instance.hasConfiguredAccount()) { + await _init(); + } else { + Bus.instance.on().listen((event) { + _logger.info("User signed in, starting initial sync."); + _init(); + }); + } + } + + Future sync() async { + final updatedCollections = await CollectionApiClient.instance + .getCollections(CollectionDB.instance.getSyncTime()); + if (updatedCollections.isEmpty) { + _logger.info("No collections to sync."); + return; + } + await CollectionDB.instance.updateCollections(updatedCollections); + await CollectionDB.instance + .setSyncTime(updatedCollections.last.updationTime); + final List fileFutures = []; + for (final collection in updatedCollections) { + if (collection.isDeleted) { + await CollectionDB.instance.deleteCollection(collection); + continue; + } + final syncTime = + CollectionDB.instance.getCollectionSyncTime(collection.id); + fileFutures.add( + CollectionApiClient.instance + .getFiles(collection, syncTime) + .then((diff) async { + if (diff.updatedFiles.isNotEmpty) { + await CollectionDB.instance.addFilesToCollection( + collection, + diff.updatedFiles, + ); + } + if (diff.deletedFiles.isNotEmpty) { + await CollectionDB.instance.deleteFilesFromCollection( + collection, + diff.deletedFiles, + ); + } + await CollectionDB.instance + .setCollectionSyncTime(collection.id, diff.latestUpdatedAtTime); + }).catchError((e) { + _logger.warning( + "Failed to fetch files for collection ${collection.id}: $e", + ); + }), + ); + } + await Future.wait(fileFutures); + if (updatedCollections.isNotEmpty) { + Bus.instance.fire(CollectionsUpdatedEvent()); + } + } + + bool hasCompletedFirstSync() { + return Configuration.instance.hasConfiguredAccount() && + CollectionDB.instance.getSyncTime() > 0; + } + + Future createCollection( + String name, { + CollectionType type = CollectionType.folder, + }) async { + try { + final collection = await _apiClient.create(name, type); + _logger.info("Created collection: ${collection.name}"); + // Let sync update the local state + await sync(); + return collection; + } catch (e) { + _logger.severe("Failed to create collection: $e"); + rethrow; + } + } + + Future> getCollections() async { + return CollectionDB.instance.getCollections(); + } + + Future> getCollectionsForFile(EnteFile file) async { + return CollectionDB.instance.getCollectionsForFile(file); + } + + Future> getFilesInCollection(Collection collection) async { + try { + final files = + await CollectionDB.instance.getFilesInCollection(collection); + return files; + } catch (e) { + _logger.severe( + "Failed to fetch files for collection ${collection.name}: $e", + ); + rethrow; + } + } + + Future> getAllFiles() async { + try { + final allFiles = await CollectionDB.instance.getAllFiles(); + return allFiles; + } catch (e) { + _logger.severe("Failed to fetch all files: $e"); + rethrow; + } + } + + Future addToCollection(Collection collection, EnteFile file) async { + try { + await _apiClient.addToCollection(collection, [file]); + _logger.info("Added file ${file.title} to collection ${collection.name}"); + // Let sync update the local state + await sync(); + } catch (e) { + _logger.severe("Failed to add file to collection: $e"); + rethrow; + } + } + + Future trashFile(EnteFile file, Collection collection) async { + try { + final List requests = []; + requests.add(TrashRequest(file.uploadedFileID!, collection.id)); + await _apiClient.trash(requests); + // Let sync update the local state + await sync(); + await TrashService.instance.syncTrash(); + } catch (e) { + _logger.severe("Failed to remove file from collections: $e"); + rethrow; + } + } + + Future rename(Collection collection, String newName) async { + try { + await CollectionApiClient.instance.rename( + collection, + newName, + ); + _logger.info("Renamed collection ${collection.name} to $newName"); + // Let sync update the local state + await sync(); + } catch (e, s) { + _logger.warning("failed to rename collection", e, s); + rethrow; + } + } + + Future getOrCreateUncategorizedCollection() async { + final collections = await getCollections(); + for (final collection in collections) { + if (collection.type == CollectionType.uncategorized) { + return collection; + } + } + _logger.info("No collections found, creating uncategorized collection."); + return await createCollection( + "Uncategorized", + type: CollectionType.uncategorized, + ); + } + + Future _init() async { + // ignore: unawaited_futures + sync().then((_) { + setupDefaultCollections(); + }).catchError((error) { + _logger.severe("Failed to initialize collections: $error"); + }); + } + + Future _getOrCreateImportantCollection() async { + final collections = await getCollections(); + for (final collection in collections) { + if (collection.type == CollectionType.favorites) { + return collection; + } + } + _logger.info("No collections found, creating important collection."); + return await createCollection("Important", type: CollectionType.favorites); + } + + Future move(EnteFile file, Collection from, Collection to) async { + try { + await _apiClient.move(file, from, to); + _logger.info("Moved file ${file.title} from ${from.name} to ${to.name}"); + // Let sync update the local state + await sync(); + } catch (e) { + _logger.severe("Failed to move file: $e"); + rethrow; + } + } + + Future trashCollection(Collection collection) async { + try { + await _apiClient.trashCollection(collection); + _logger.info("Trashed collection: ${collection.name}"); + // Let sync update the local state + await sync(); + } catch (e) { + _logger.severe("Failed to trash collection: $e"); + rethrow; + } + } + + Future setupDefaultCollections() async { + try { + _logger.info("Setting up default collections..."); + + // Create uncategorized collection if it doesn't exist + await getOrCreateUncategorizedCollection(); + + // Create important (favorites) collection if it doesn't exist + await _getOrCreateImportantCollection(); + + // Create Documents collection if it doesn't exist + await _getOrCreateDocumentsCollection(); + + _logger.info("Default collections setup completed."); + } catch (e, s) { + _logger.severe("Failed to setup default collections", e, s); + } + } + + Future _getOrCreateDocumentsCollection() async { + final collections = await getCollections(); + for (final collection in collections) { + if (collection.type == CollectionType.folder && + collection.name == "Documents") { + return collection; + } + } + _logger + .info("No Documents collection found, creating Documents collection."); + return createCollection("Documents", type: CollectionType.folder); + } + + /// Returns one random collection name that doesn't already exist + /// If all names are used, returns "Documents" + Future getRandomUnusedCollectionName() async { + try { + final existingCollections = await getCollections(); + final existingNames = existingCollections + .map((collection) => collection.name?.toLowerCase()) + .where((name) => name != null) + .toSet(); + + final availableNames = _suggestedCollectionNames + .where((name) => !existingNames.contains(name.toLowerCase())) + .toList(); + + if (availableNames.isEmpty) { + _logger.info( + "All suggested collection names are used, returning 'Documents'", + ); + return "Documents"; + } + + final random = Random(); + final randomName = availableNames[random.nextInt(availableNames.length)]; + _logger.info("Selected random unused collection name: $randomName"); + return randomName; + } catch (e) { + _logger.severe("Failed to get random unused collection name: $e"); + return "Documents"; + } + } + + Future getCollection(int collectionID) async { + return await CollectionDB.instance.getCollection(collectionID); + } + + Future getCollectionKey(int collectionID) async { + final collection = await getCollection(collectionID); + final collectionKey = CryptoHelper.instance.getCollectionKey(collection); + return collectionKey; + } + + Future getFileKey(EnteFile file) async { + try { + final collection = await getCollection(file.collectionID!); + final collectionKey = CryptoHelper.instance.getCollectionKey(collection); + + final fileKey = CryptoHelper.instance.getFileKey( + file.encryptedKey!, + file.keyDecryptionNonce!, + collectionKey, + ); + + _logger.info("Successfully decrypted file key for file ${file.title}"); + return fileKey; + } catch (e) { + _logger.severe("Failed to get file key: $e"); + rethrow; + } + } +} diff --git a/mobile/apps/locker/lib/services/collections/models/collection.dart b/mobile/apps/locker/lib/services/collections/models/collection.dart new file mode 100644 index 0000000000..2338bc11d9 --- /dev/null +++ b/mobile/apps/locker/lib/services/collections/models/collection.dart @@ -0,0 +1,352 @@ +import 'dart:core'; + +import 'package:flutter/foundation.dart'; +import 'package:locker/services/collections/models/collection_magic.dart'; +import 'package:locker/services/collections/models/public_url.dart'; +import 'package:locker/services/collections/models/user.dart'; +import 'package:locker/services/files/sync/models/common_keys.dart'; + +class Collection { + final int id; + final User owner; + final String encryptedKey; + final String? keyDecryptionNonce; + String? name; + + final String? encryptedName; + final String? nameDecryptionNonce; + final CollectionType type; + final CollectionAttributes attributes; + final List sharees; + final List publicURLs; + final int updationTime; + final bool isDeleted; + + // decryptedPath will be null for collections now owned by user, deleted + // collections, && collections which don't have a path. The path is used + // to map local on-device album on mobile to remote collection on ente. + String? decryptedPath; + String? mMdEncodedJson; + String? mMdPubEncodedJson; + String? sharedMmdJson; + int mMdVersion = 0; + int mMbPubVersion = 0; + int sharedMmdVersion = 0; + CollectionMagicMetadata? _mmd; + CollectionPubMagicMetadata? _pubMmd; + ShareeMagicMetadata? _sharedMmd; + + CollectionMagicMetadata get magicMetadata => + _mmd ?? CollectionMagicMetadata.fromEncodedJson(mMdEncodedJson ?? '{}'); + + CollectionPubMagicMetadata get pubMagicMetadata => + _pubMmd ?? + CollectionPubMagicMetadata.fromEncodedJson(mMdPubEncodedJson ?? '{}'); + + ShareeMagicMetadata get sharedMagicMetadata => + _sharedMmd ?? ShareeMagicMetadata.fromEncodedJson(sharedMmdJson ?? '{}'); + + set magicMetadata(CollectionMagicMetadata? val) => _mmd = val; + + set pubMagicMetadata(CollectionPubMagicMetadata? val) => _pubMmd = val; + + set sharedMagicMetadata(ShareeMagicMetadata? val) => _sharedMmd = val; + + void setName(String newName) { + name = newName; + } + + Collection( + this.id, + this.owner, + this.encryptedKey, + this.keyDecryptionNonce, + this.name, + this.encryptedName, + this.nameDecryptionNonce, + this.type, + this.attributes, + this.sharees, + this.publicURLs, + this.updationTime, { + this.isDeleted = false, + }); + + bool isArchived() { + return mMdVersion > 0 && magicMetadata.visibility == archiveVisibility; + } + + bool hasShareeArchived() { + return sharedMmdVersion > 0 && + sharedMagicMetadata.visibility == archiveVisibility; + } + + // hasLink returns true if there's any link attached to the collection + // including expired links + bool get hasLink => publicURLs.isNotEmpty; + + bool get hasCover => (pubMagicMetadata.coverID ?? 0) > 0; + + // hasSharees returns true if the collection is shared with other ente users + bool get hasSharees => sharees.isNotEmpty; + + bool get isPinned => (magicMetadata.order ?? 0) != 0; + + bool isHidden() { + if (isDefaultHidden()) { + return true; + } + return mMdVersion > 0 && (magicMetadata.visibility == hiddenVisibility); + } + + bool isDefaultHidden() { + return (magicMetadata.subType ?? 0) == subTypeDefaultHidden; + } + + bool isQuickLinkCollection() { + return (magicMetadata.subType ?? 0) == subTypeSharedFilesCollection && + !hasSharees; + } + + List getSharees() { + return sharees; + } + + bool isOwner(int userID) { + return (owner.id ?? -100) == userID; + } + + bool isDownloadEnabledForPublicLink() { + if (publicURLs.isEmpty) { + return false; + } + return publicURLs.first.enableDownload; + } + + bool isCollectEnabledForPublicLink() { + if (publicURLs.isEmpty) { + return false; + } + return publicURLs.first.enableCollect; + } + + bool get isJoinEnabled { + if (publicURLs.isEmpty) { + return false; + } + return publicURLs.first.enableJoin; + } + + CollectionParticipantRole getRole(int userID) { + if (isOwner(userID)) { + return CollectionParticipantRole.owner; + } + if (sharees.isEmpty) { + return CollectionParticipantRole.unknown; + } + for (final User u in sharees) { + if (u.id == userID) { + if (u.isViewer) { + return CollectionParticipantRole.viewer; + } else if (u.isCollaborator) { + return CollectionParticipantRole.collaborator; + } + } + } + return CollectionParticipantRole.unknown; + } + + void updateSharees(List newSharees) { + sharees.clear(); + sharees.addAll(newSharees); + } + + Collection copyWith({ + int? id, + User? owner, + String? encryptedKey, + String? keyDecryptionNonce, + String? name, + String? encryptedName, + String? nameDecryptionNonce, + CollectionType? type, + CollectionAttributes? attributes, + List? sharees, + List? publicURLs, + int? updationTime, + bool? isDeleted, + String? mMdEncodedJson, + int? mMdVersion, + String? decryptedName, + String? decryptedPath, + }) { + final Collection result = Collection( + id ?? this.id, + owner ?? this.owner, + encryptedKey ?? this.encryptedKey, + keyDecryptionNonce ?? this.keyDecryptionNonce, + // ignore: deprecated_member_use_from_same_package + name ?? this.name, + encryptedName ?? this.encryptedName, + nameDecryptionNonce ?? this.nameDecryptionNonce, + type ?? this.type, + attributes ?? this.attributes, + sharees ?? this.sharees, + publicURLs ?? this.publicURLs, + updationTime ?? this.updationTime, + isDeleted: isDeleted ?? this.isDeleted, + ); + result.mMdVersion = mMdVersion ?? this.mMdVersion; + result.mMdEncodedJson = mMdEncodedJson ?? this.mMdEncodedJson; + result.decryptedPath = decryptedPath ?? this.decryptedPath; + result.mMbPubVersion = mMbPubVersion; + result.mMdPubEncodedJson = mMdPubEncodedJson; + result.sharedMmdVersion = sharedMmdVersion; + result.sharedMmdJson = sharedMmdJson; + return result; + } + + static fromMap(Map? map) { + if (map == null) return null; + final sharees = (map['sharees'] == null || map['sharees'].length == 0) + ? [] + : List.from(map['sharees'].map((x) => User.fromMap(x))); + final publicURLs = + (map['publicURLs'] == null || map['publicURLs'].length == 0) + ? [] + : List.from( + map['publicURLs'].map((x) => PublicURL.fromMap(x)), + ); + return Collection( + map['id'], + User.fromMap(map['owner']), + map['encryptedKey'], + map['keyDecryptionNonce'], + map['name'], + map['encryptedName'], + map['nameDecryptionNonce'], + typeFromString(map['type']), + CollectionAttributes.fromMap(map['attributes']), + sharees, + publicURLs, + map['updationTime'], + isDeleted: map['isDeleted'] ?? false, + ); + } + + @override + bool operator ==(Object o) { + if (identical(this, o)) return true; + + return o is Collection && o.id == id; + } + + @override + int get hashCode { + return id.hashCode; + } +} + +enum CollectionType { + folder, + favorites, + uncategorized, + album, + unknown, +} + +CollectionType typeFromString(String type) { + switch (type) { + case "folder": + return CollectionType.folder; + case "favorites": + return CollectionType.favorites; + case "uncategorized": + return CollectionType.uncategorized; + case "album": + return CollectionType.album; + case "unknown": + return CollectionType.unknown; + } + debugPrint("unexpected collection type $type"); + return CollectionType.unknown; +} + +String typeToString(CollectionType type) { + switch (type) { + case CollectionType.folder: + return "folder"; + case CollectionType.favorites: + return "favorites"; + case CollectionType.album: + return "album"; + case CollectionType.uncategorized: + return "uncategorized"; + case CollectionType.unknown: + return "unknown"; + } +} + +extension CollectionTypeExtn on CollectionType { + bool get canDelete => + this != CollectionType.favorites && this != CollectionType.uncategorized; +} + +enum CollectionParticipantRole { + unknown, + viewer, + collaborator, + owner, +} + +extension CollectionParticipantRoleExtn on CollectionParticipantRole { + static CollectionParticipantRole fromString(String? val) { + if ((val ?? '') == '') { + return CollectionParticipantRole.viewer; + } + for (var x in CollectionParticipantRole.values) { + if (x.name.toUpperCase() == val!.toUpperCase()) { + return x; + } + } + return CollectionParticipantRole.unknown; + } + + String toStringVal() { + return name.toUpperCase(); + } +} + +class CollectionAttributes { + final String? encryptedPath; + final String? pathDecryptionNonce; + final int? version; + + CollectionAttributes({ + this.encryptedPath, + this.pathDecryptionNonce, + this.version, + }); + + Map toMap() { + final map = {}; + if (encryptedPath != null) { + map['encryptedPath'] = encryptedPath; + } + if (pathDecryptionNonce != null) { + map['pathDecryptionNonce'] = pathDecryptionNonce; + } + map['version'] = version ?? 0; + return map; + } + + static fromMap(Map? map) { + if (map == null) return null; + + return CollectionAttributes( + encryptedPath: map['encryptedPath'], + pathDecryptionNonce: map['pathDecryptionNonce'], + version: map['version'] ?? 0, + ); + } +} diff --git a/mobile/apps/locker/lib/services/collections/models/collection_file_item.dart b/mobile/apps/locker/lib/services/collections/models/collection_file_item.dart new file mode 100644 index 0000000000..514808cb9a --- /dev/null +++ b/mobile/apps/locker/lib/services/collections/models/collection_file_item.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; + +class CollectionFileItem { + final int id; + final String encryptedKey; + final String keyDecryptionNonce; + + CollectionFileItem( + this.id, + this.encryptedKey, + this.keyDecryptionNonce, + ); + + CollectionFileItem copyWith({ + int? id, + String? encryptedKey, + String? keyDecryptionNonce, + }) { + return CollectionFileItem( + id ?? this.id, + encryptedKey ?? this.encryptedKey, + keyDecryptionNonce ?? this.keyDecryptionNonce, + ); + } + + Map toMap() { + return { + 'id': id, + 'encryptedKey': encryptedKey, + 'keyDecryptionNonce': keyDecryptionNonce, + }; + } + + static fromMap(Map? map) { + if (map == null) return null; + + return CollectionFileItem( + map['id'], + map['encryptedKey'], + map['keyDecryptionNonce'], + ); + } + + String toJson() => json.encode(toMap()); + + factory CollectionFileItem.fromJson(String source) => + CollectionFileItem.fromMap(json.decode(source)); + + @override + String toString() => + 'CollectionFileItem(id: $id, encryptedKey: $encryptedKey, keyDecryptionNonce: $keyDecryptionNonce)'; + + @override + bool operator ==(Object o) { + if (identical(this, o)) return true; + + return o is CollectionFileItem && + o.id == id && + o.encryptedKey == encryptedKey && + o.keyDecryptionNonce == keyDecryptionNonce; + } + + @override + int get hashCode => + id.hashCode ^ encryptedKey.hashCode ^ keyDecryptionNonce.hashCode; +} diff --git a/mobile/apps/locker/lib/services/collections/models/collection_magic.dart b/mobile/apps/locker/lib/services/collections/models/collection_magic.dart new file mode 100644 index 0000000000..8d3963a5ed --- /dev/null +++ b/mobile/apps/locker/lib/services/collections/models/collection_magic.dart @@ -0,0 +1,126 @@ +import 'dart:convert'; + +import "package:locker/services/files/sync/models/common_keys.dart"; + +// Collection SubType Constants +const subTypeDefaultHidden = 1; +const subTypeSharedFilesCollection = 2; + +// key for collection subType +const subTypeKey = 'subType'; + +const muteKey = "mute"; + +const orderKey = "order"; + +class CollectionMagicMetadata { + // 0 -> visible + // 1 -> archived + // 2 -> hidden + int visibility; + + // null/0 value -> no subType + // 1 -> DEFAULT_HIDDEN COLLECTION for files hidden individually + // 2 -> Collections created for sharing selected files + int? subType; + + /* order is initially just used for pinned collections. + Later it can be used for custom sort order for if needed. + Higher the value, higher the preference of the collection to show up first. + */ + int? order; + + CollectionMagicMetadata({required this.visibility, this.subType, this.order}); + + Map toJson() { + final result = {magicKeyVisibility: visibility}; + if (subType != null) { + result[subTypeKey] = subType!; + } + if (order != null) { + result[orderKey] = order!; + } + return result; + } + + factory CollectionMagicMetadata.fromEncodedJson(String encodedJson) => + CollectionMagicMetadata.fromJson(jsonDecode(encodedJson)); + + factory CollectionMagicMetadata.fromJson(dynamic json) => + CollectionMagicMetadata.fromMap(json); + + static fromMap(Map? map) { + if (map == null) return null; + return CollectionMagicMetadata( + visibility: map[magicKeyVisibility] ?? visibleVisibility, + subType: map[subTypeKey], + order: map[orderKey], + ); + } +} + +class CollectionPubMagicMetadata { + // sort order while showing collection + bool? asc; + + // cover photo id for the collection + int? coverID; + + CollectionPubMagicMetadata({this.asc, this.coverID}); + + Map toJson() { + final Map result = {"asc": asc ?? false}; + if (coverID != null) { + result["coverID"] = coverID!; + } + return result; + } + + factory CollectionPubMagicMetadata.fromEncodedJson(String encodedJson) => + CollectionPubMagicMetadata.fromJson(jsonDecode(encodedJson)); + + factory CollectionPubMagicMetadata.fromJson(dynamic json) => + CollectionPubMagicMetadata.fromMap(json); + + static fromMap(Map? map) { + if (map == null) return null; + return CollectionPubMagicMetadata( + asc: map["asc"] as bool?, + coverID: map["coverID"], + ); + } +} + +class ShareeMagicMetadata { + // 0 -> visible + // 1 -> archived + // 2 -> hidden etc? + int visibility; + + // null/false value -> no mute + bool? mute; + + ShareeMagicMetadata({required this.visibility, this.mute}); + + Map toJson() { + final Map result = {magicKeyVisibility: visibility}; + if (mute != null) { + result[muteKey] = mute!; + } + return result; + } + + factory ShareeMagicMetadata.fromEncodedJson(String encodedJson) => + ShareeMagicMetadata.fromJson(jsonDecode(encodedJson)); + + factory ShareeMagicMetadata.fromJson(dynamic json) => + ShareeMagicMetadata.fromMap(json); + + static fromMap(Map? map) { + if (map == null) return null; + return ShareeMagicMetadata( + visibility: map[magicKeyVisibility] ?? visibleVisibility, + mute: map[muteKey], + ); + } +} diff --git a/mobile/apps/locker/lib/services/collections/models/diff.dart b/mobile/apps/locker/lib/services/collections/models/diff.dart new file mode 100644 index 0000000000..c37beaa4ad --- /dev/null +++ b/mobile/apps/locker/lib/services/collections/models/diff.dart @@ -0,0 +1,15 @@ +import 'package:locker/services/files/sync/models/file.dart'; + +class Diff { + final List updatedFiles; + final List deletedFiles; + final bool hasMore; + final int latestUpdatedAtTime; + + Diff( + this.updatedFiles, + this.deletedFiles, + this.hasMore, + this.latestUpdatedAtTime, + ); +} diff --git a/mobile/apps/locker/lib/services/collections/models/public_url.dart b/mobile/apps/locker/lib/services/collections/models/public_url.dart new file mode 100644 index 0000000000..12556873a0 --- /dev/null +++ b/mobile/apps/locker/lib/services/collections/models/public_url.dart @@ -0,0 +1,63 @@ +class PublicURL { + String url; + int deviceLimit; + int validTill; + bool enableDownload; + bool enableCollect; + bool passwordEnabled; + bool enableJoin; + String? nonce; + int? opsLimit; + int? memLimit; + + PublicURL({ + required this.url, + required this.deviceLimit, + required this.validTill, + this.enableDownload = true, + this.passwordEnabled = false, + this.enableCollect = false, + this.enableJoin = false, + this.nonce, + this.opsLimit, + this.memLimit, + }); + + Map toMap() { + return { + 'url': url, + 'deviceLimit': deviceLimit, + 'validTill': validTill, + 'enableDownload': enableDownload, + 'passwordEnabled': passwordEnabled, + 'enableCollect': enableCollect, + 'nonce': nonce, + 'memLimit': memLimit, + 'opsLimit': opsLimit, + 'enableJoin': enableJoin, + }; + } + + bool get hasExpiry => validTill != 0; + + // isExpired indicates whether the link has expired or not + bool get isExpired => + hasExpiry && validTill < DateTime.now().microsecondsSinceEpoch; + + static fromMap(Map? map) { + if (map == null) return null; + + return PublicURL( + url: map['url'], + deviceLimit: map['deviceLimit'], + validTill: map['validTill'] ?? 0, + enableDownload: map['enableDownload'] ?? true, + passwordEnabled: map['passwordEnabled'] ?? false, + enableCollect: map['enableCollect'] ?? false, + nonce: map['nonce'], + opsLimit: map['opsLimit'], + memLimit: map['memLimit'], + enableJoin: map['enableJoin'] ?? false, + ); + } +} diff --git a/mobile/apps/locker/lib/services/collections/models/user.dart b/mobile/apps/locker/lib/services/collections/models/user.dart new file mode 100644 index 0000000000..30785b0504 --- /dev/null +++ b/mobile/apps/locker/lib/services/collections/models/user.dart @@ -0,0 +1,44 @@ +import "dart:convert"; + +class User { + int? id; + String email; + @Deprecated( + "Use displayName() extension method instead. Note: Some early users have" + " value in name field.", + ) + String? name; + String? role; + + User({ + this.id, + required this.email, + this.name, + this.role, + }); + + bool get isViewer => role == null || role?.toUpperCase() == 'VIEWER'; + + bool get isCollaborator => + role != null && role?.toUpperCase() == 'COLLABORATOR'; + + Map toMap() { + // ignore: deprecated_member_use_from_same_package + return {'id': id, 'email': email, 'name': name, "role": role}; + } + + static fromMap(Map? map) { + if (map == null) return null; + + return User( + id: map['id'], + email: map['email'], + name: map['name'], + role: map['role'] ?? 'VIEWER', + ); + } + + String toJson() => json.encode(toMap()); + + factory User.fromJson(String source) => User.fromMap(json.decode(source)); +} diff --git a/mobile/apps/locker/lib/services/configuration.dart b/mobile/apps/locker/lib/services/configuration.dart new file mode 100644 index 0000000000..f1a0b6d97d --- /dev/null +++ b/mobile/apps/locker/lib/services/configuration.dart @@ -0,0 +1,6 @@ +import 'package:ente_configuration/base_configuration.dart'; + +class Configuration extends BaseConfiguration { + Configuration._privateConstructor(); + static final Configuration instance = Configuration._privateConstructor(); +} diff --git a/mobile/apps/locker/lib/services/files/download/file_downloader.dart b/mobile/apps/locker/lib/services/files/download/file_downloader.dart new file mode 100644 index 0000000000..77acd82ef3 --- /dev/null +++ b/mobile/apps/locker/lib/services/files/download/file_downloader.dart @@ -0,0 +1,115 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:dio/dio.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_network/network.dart'; +import 'package:ente_utils/fake_progress.dart'; +import 'package:locker/services/configuration.dart'; +import 'package:locker/services/files/download/models/task.dart'; +import 'package:locker/services/files/download/service_locator.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/utils/data_util.dart'; +import 'package:logging/logging.dart'; + +final _logger = Logger("FileDownloader"); + +Future downloadAndDecrypt( + EnteFile file, + Uint8List fileKey, { + ProgressCallback? progressCallback, + bool shouldFakeProgress = false, + bool shouldUseCache = false, +}) async { + final String logPrefix = 'File-${file.uploadedFileID}:'; + _logger.info('$logPrefix starting download'); + + final String tempDir = Configuration.instance.getTempDirectory(); + final String cacheDir = Configuration.instance.getCacheDirectory(); + + String encryptedFilePath = "$tempDir${file.uploadedFileID}.encrypted"; + File encryptedFile = File(encryptedFilePath); + + final String decryptedFilePath = shouldUseCache + ? "$cacheDir${file.displayName}" + : "$tempDir${file.displayName}"; + + final startTime = DateTime.now().millisecondsSinceEpoch; + + try { + if (downloadManager.enableResumableDownload(file.fileSize)) { + final DownloadResult result = await downloadManager.download( + file.uploadedFileID!, + file.displayName, + file.fileSize!, + ); + if (result.success) { + encryptedFilePath = result.task.filePath!; + encryptedFile = File(encryptedFilePath); + } else { + _logger.warning( + '$logPrefix download failed ${result.task.error} ${result.task.status}', + ); + return null; + } + } else { + // If the file is small, download it directly to the final location + final response = await Network.instance.getDio().download( + file.downloadUrl, + encryptedFilePath, + options: Options( + headers: {"X-Auth-Token": Configuration.instance.getToken()}, + ), + onReceiveProgress: (a, b) { + progressCallback?.call(a, b); + }, + ); + if (response.statusCode != 200 || !encryptedFile.existsSync()) { + _logger.warning('$logPrefix download failed ${response.toString()}'); + return null; + } + } + + final int sizeInBytes = file.fileSize ?? await encryptedFile.length(); + final double elapsedSeconds = + (DateTime.now().millisecondsSinceEpoch - startTime) / 1000; + final double speedInKBps = sizeInBytes / 1024.0 / elapsedSeconds; + + _logger.info( + '$logPrefix download completed: ${formatBytes(sizeInBytes)}, avg speed: ${speedInKBps.toStringAsFixed(2)} KB/s', + ); + + // As decryption can take time, emit fake progress for large files during + // decryption + final FakePeriodicProgress? fakeProgress = shouldFakeProgress + ? FakePeriodicProgress( + callback: (count) { + progressCallback?.call(sizeInBytes, sizeInBytes); + }, + duration: const Duration(milliseconds: 5000), + ) + : null; + try { + // Start the periodic callback after initial 5 seconds + fakeProgress?.start(); + await CryptoUtil.decryptFile( + encryptedFilePath, + decryptedFilePath, + CryptoUtil.base642bin(file.fileDecryptionHeader!), + fileKey, + ); + fakeProgress?.stop(); + _logger + .info('$logPrefix decryption completed (ID ${file.uploadedFileID})'); + } catch (e, s) { + fakeProgress?.stop(); + _logger.severe("Critical: $logPrefix failed to decrypt", e, s); + return null; + } + await encryptedFile.delete(); + return File(decryptedFilePath); + } catch (e, s) { + _logger.severe("$logPrefix failed to download or decrypt", e, s); + return null; + } +} diff --git a/mobile/apps/locker/lib/services/files/download/file_url.dart b/mobile/apps/locker/lib/services/files/download/file_url.dart new file mode 100644 index 0000000000..ec93dd4867 --- /dev/null +++ b/mobile/apps/locker/lib/services/files/download/file_url.dart @@ -0,0 +1,41 @@ +import "package:locker/core/constants.dart"; +import "package:locker/services/configuration.dart"; + +enum FileUrlType { + download, + publicDownload, + thumbnail, + publicThumbnail, + directDownload, +} + +class FileUrl { + static String getUrl(int fileID, FileUrlType type) { + final endpoint = Configuration.instance.getHttpEndpoint(); + final disableWorker = endpoint != kDefaultProductionEndpoint; + + switch (type) { + case FileUrlType.directDownload: + return "$endpoint/files/download/$fileID"; + case FileUrlType.download: + return disableWorker + ? "$endpoint/files/download/$fileID" + : "https://files.ente.io/?fileID=$fileID"; + + case FileUrlType.publicDownload: + return disableWorker + ? "$endpoint/public-collection/files/download/$fileID" + : "https://public-albums.ente.io/download/?fileID=$fileID"; + + case FileUrlType.thumbnail: + return disableWorker + ? "$endpoint/files/preview/$fileID" + : "https://thumbnails.ente.io/?fileID=$fileID"; + + case FileUrlType.publicThumbnail: + return disableWorker + ? "$endpoint/public-collection/files/preview/$fileID" + : "https://public-albums.ente.io/preview/?fileID=$fileID"; + } + } +} diff --git a/mobile/apps/locker/lib/services/files/download/manager.dart b/mobile/apps/locker/lib/services/files/download/manager.dart new file mode 100644 index 0000000000..2685f2e053 --- /dev/null +++ b/mobile/apps/locker/lib/services/files/download/manager.dart @@ -0,0 +1,373 @@ +import "dart:async"; +import "dart:io"; + +import "package:dio/dio.dart"; +import "package:locker/services/configuration.dart"; +import "package:locker/services/files/download/file_url.dart"; +import "package:locker/services/files/download/models/task.dart"; +import "package:logging/logging.dart"; + +class DownloadManager { + final _logger = Logger('DownloadManager'); + static const int downloadChunkSize = 40 * 1024 * 1024; + + final Dio _dio; + + // In-memory storage for download tasks + final Map _tasks = {}; + + // Active downloads with their completers and streams + final Map> _completers = {}; + final Map> _streams = {}; + final Map _cancelTokens = {}; + + DownloadManager(this._dio); + + /// Subscribe to download progress updates for a specific file ID + Stream watchDownload(int fileId) { + _streams[fileId] ??= StreamController.broadcast(); + return _streams[fileId]!.stream; + } + + bool enableResumableDownload(int? size) { + if (size == null) return false; + //todo: Use FileUrlType.direct instead of FileUrlType.directDownload + return size > downloadChunkSize; + } + + /// Start download and return a Future that completes when download finishes + /// If download was paused, calling this again will resume it + Future download( + int fileId, + String filename, + int totalBytes, + ) async { + // If already downloading, return existing future + if (_completers.containsKey(fileId)) { + return _completers[fileId]!.future; + } + + final completer = Completer(); + _completers[fileId] = completer; + + // Get or create task + final existingTask = _tasks[fileId]; + final task = existingTask ?? + DownloadTask( + id: fileId, + filename: filename, + totalBytes: totalBytes, + ); + + // Store task in memory + _tasks[fileId] = task; + + // Don't restart if already completed + if (task.isCompleted) { + // ensure that the file exists + final filePath = task.filePath; + if (filePath == null || !(await File(filePath).exists())) { + // If the file doesn't exist, mark the task as error + _logger.warning( + 'File not found for ${task.filename} (${task.bytesDownloaded}/${task.totalBytes} bytes)', + ); + final updatedTask = task.copyWith( + status: DownloadStatus.error, + error: 'File not found', + filePath: null, + ); + _updateTask(updatedTask); + final result = DownloadResult(updatedTask, false); + completer.complete(result); + return result; + } else { + _logger.info( + 'Download already completed for ${task.filename} (${task.bytesDownloaded}/${task.totalBytes} bytes)', + ); + final result = DownloadResult(task, true); + completer.complete(result); + return result; + } + } + unawaited(_startDownload(task, completer)); + return completer.future; + } + + /// Pause download + Future pause(int fileId) async { + final token = _cancelTokens[fileId]; + if (token != null && !token.isCancelled) { + token.cancel('paused'); + } + + final task = _tasks[fileId]; + if (task != null && task.isActive) { + _updateTask(task.copyWith(status: DownloadStatus.paused)); + } + + // Clean up streams if no listeners + final stream = _streams[fileId]; + if (stream != null && !stream.hasListener) { + await stream.close(); + _streams.remove(fileId); + } + } + + /// Cancel and delete download + Future cancel(int fileId) async { + final token = _cancelTokens[fileId]; + if (token != null && !token.isCancelled) { + token.cancel('cancelled'); + } + + final task = _tasks[fileId]; + if (task != null) { + await _deleteFiles(task); + _updateTask(task.copyWith(status: DownloadStatus.cancelled)); + _tasks.remove(fileId); + } + _cleanup(fileId); + } + + /// Get current download status + Future getDownload(int fileId) async => _tasks[fileId]; + + /// Get all downloads + Future> getAllDownloads() async => _tasks.values.toList(); + + Future _startDownload( + DownloadTask task, + Completer completer, + ) async { + try { + task = task.copyWith(status: DownloadStatus.downloading); + _updateTask(task); + + final cancelToken = CancelToken(); + _cancelTokens[task.id] = cancelToken; + + final directory = Configuration.instance.getTempDirectory(); + final basePath = '$directory${task.id}.encrypted'; + + // Check existing chunks and calculate progress + final totalChunks = (task.totalBytes / downloadChunkSize).ceil(); + final existingChunks = + await _validateExistingChunks(basePath, task.totalBytes, totalChunks); + + task = task.copyWith( + bytesDownloaded: _calculateDownloadedBytes( + existingChunks, + task.totalBytes, + totalChunks, + ), + ); + _updateTask(task); + + _logger.info( + 'Resuming download for ${task.filename} (${task.bytesDownloaded}/${task.totalBytes} bytes)', + ); + for (int i = 0; i < totalChunks; i++) { + if (existingChunks[i] || cancelToken.isCancelled) continue; + _logger.info('Downloading chunk ${i + 1} of $totalChunks'); + await _downloadChunk(task, basePath, i, totalChunks, cancelToken); + existingChunks[i] = true; + } + + if (!cancelToken.isCancelled) { + final finalPath = await _combineChunks(basePath, totalChunks); + task = task.copyWith( + status: DownloadStatus.completed, + filePath: finalPath, + bytesDownloaded: task.totalBytes, + ); + _updateTask(task); + completer.complete(DownloadResult(task, true)); + } + } catch (e) { + if (e is DioException && e.type == DioExceptionType.cancel) { + // Complete future with current task state (paused or cancelled) + final currentTask = _tasks[task.id]; + if (currentTask != null && !completer.isCompleted) { + completer.complete(DownloadResult(currentTask, false)); + } + return; + } + + task = task.copyWith(status: DownloadStatus.error, error: e.toString()); + _updateTask(task); + if (!completer.isCompleted) { + completer.complete(DownloadResult(task, false)); + } + } finally { + _cleanup(task.id); + } + } + + String _getChunkPath(String basePath, int part) { + return '$basePath.${part}_part'; + } + + Future> _validateExistingChunks( + String basePath, + int totalBytes, + int totalChunks, + ) async { + final existingChunks = List.filled(totalChunks, false); + + for (int i = 0; i < totalChunks; i++) { + final chunkFile = File(_getChunkPath(basePath, i + 1)); + if (!await chunkFile.exists()) continue; + + final expectedSize = i == totalChunks - 1 + ? totalBytes - (i * downloadChunkSize) + : downloadChunkSize; + + final actualSize = await chunkFile.length(); + if (actualSize == expectedSize) { + _logger.info('existing chunk ${i + 1} is valid'); + existingChunks[i] = true; + } else { + _logger.warning( + 'Chunk ${i + 1} is corrupted: expected $expectedSize bytes, ' + 'but got $actualSize bytes', + ); + existingChunks[i] = false; + await chunkFile.delete(); // Remove corrupted chunk + } + } + + return existingChunks; + } + + int _calculateDownloadedBytes( + List existingChunks, + int totalBytes, + int totalChunks, + ) { + int bytes = 0; + for (int i = 0; i < existingChunks.length; i++) { + if (existingChunks[i]) { + bytes += i == totalChunks - 1 + ? totalBytes - (i * downloadChunkSize) + : downloadChunkSize; + } + } + return bytes; + } + + Future _downloadChunk( + DownloadTask task, + String basePath, + int chunkIndex, + int totalChunks, + CancelToken cancelToken, + ) async { + final chunkPath = _getChunkPath(basePath, chunkIndex + 1); + final startByte = chunkIndex * downloadChunkSize; + final endByte = chunkIndex == totalChunks - 1 + ? task.totalBytes - 1 + : (startByte + downloadChunkSize) - 1; + + await _dio.download( + FileUrl.getUrl(task.id, FileUrlType.directDownload), + chunkPath, + options: Options( + headers: { + "X-Auth-Token": Configuration.instance.getToken(), + "Range": "bytes=$startByte-$endByte", + }, + ), + cancelToken: cancelToken, + onReceiveProgress: (received, total) async { + final updatedTask = task.copyWith( + bytesDownloaded: (chunkIndex) * downloadChunkSize + received, + ); + _notifyProgress(updatedTask); + }, + ); + // Update progress after chunk completion + final chunkFileSize = await File(chunkPath).length(); + task = task.copyWith( + bytesDownloaded: (chunkIndex) * downloadChunkSize + chunkFileSize, + ); + _updateTask(task); + } + + Future _combineChunks(String basePath, int totalChunks) async { + final finalFile = File(basePath); + final sink = finalFile.openWrite(); + try { + for (int i = 1; i <= totalChunks; i++) { + final chunkFile = File(_getChunkPath(basePath, i)); + final bytes = await chunkFile.readAsBytes(); + sink.add(bytes); + await chunkFile.delete(); + } + } finally { + await sink.close(); + } + return finalFile.path; + } + + Future _deleteFiles(DownloadTask task) async { + try { + final directory = Configuration.instance.getTempDirectory(); + final basePath = '$directory${task.id}.encrypted'; + final finalFile = File(basePath); + if (await finalFile.exists()) await finalFile.delete(); + + // Delete chunk files + final totalChunks = (task.totalBytes / downloadChunkSize).ceil(); + for (int i = 1; i <= totalChunks; i++) { + final chunkFile = File(_getChunkPath(basePath, i)); + if (await chunkFile.exists()) await chunkFile.delete(); + } + } catch (e) { + _logger.warning('Error deleting files: $e'); + } + } + + void _updateTask(DownloadTask task) { + _tasks[task.id] = task; + _notifyProgress(task); + } + + void _notifyProgress(DownloadTask task) { + final stream = _streams[task.id]; + if (stream != null && !stream.isClosed) { + stream.add(task); + } + } + + void _cleanup(int fileId) { + _completers.remove(fileId); + _cancelTokens.remove(fileId); + + final stream = _streams[fileId]; + if (stream != null && !stream.hasListener) { + stream.close(); + _streams.remove(fileId); + } + } + + Future dispose() async { + for (final completer in _completers.values) { + if (!completer.isCompleted) { + completer.completeError('Disposed'); + } + } + _completers.clear(); + + for (final token in _cancelTokens.values) { + token.cancel('Disposed'); + } + _cancelTokens.clear(); + + for (final stream in _streams.values) { + await stream.close(); + } + _streams.clear(); + + _tasks.clear(); + } +} diff --git a/mobile/apps/locker/lib/services/files/download/models/task.dart b/mobile/apps/locker/lib/services/files/download/models/task.dart new file mode 100644 index 0000000000..1bd96ca295 --- /dev/null +++ b/mobile/apps/locker/lib/services/files/download/models/task.dart @@ -0,0 +1,80 @@ +enum DownloadStatus { + pending, + downloading, + paused, + completed, + error, + cancelled +} + +class DownloadTask { + final int id; + final String filename; + final int totalBytes; + int bytesDownloaded; + DownloadStatus status; + String? error; + String? filePath; + + DownloadTask({ + required this.id, + required this.filename, + required this.totalBytes, + this.bytesDownloaded = 0, + this.status = DownloadStatus.pending, + this.error, + this.filePath, + }); + + double get progress => totalBytes > 0 ? bytesDownloaded / totalBytes : 0.0; + bool get isCompleted => status == DownloadStatus.completed; + bool get isActive => status == DownloadStatus.downloading; + bool get isFinished => [ + DownloadStatus.completed, + DownloadStatus.error, + DownloadStatus.cancelled, + ].contains(status); + + Map toMap() => { + 'id': id, + 'filename': filename, + 'totalBytes': totalBytes, + 'bytesDownloaded': bytesDownloaded, + 'status': status.name, + 'error': error, + 'filePath': filePath, + }; + + static DownloadTask fromMap(Map map) => DownloadTask( + id: map['id'], + filename: map['filename'], + totalBytes: map['totalBytes'], + bytesDownloaded: map['bytesDownloaded'] ?? 0, + status: DownloadStatus.values.byName(map['status']), + error: map['error'], + filePath: map['filePath'], + ); + + DownloadTask copyWith({ + int? bytesDownloaded, + DownloadStatus? status, + String? error, + String? filePath, + }) => + DownloadTask( + id: id, + filename: filename, + totalBytes: totalBytes, + bytesDownloaded: bytesDownloaded ?? this.bytesDownloaded, + status: status ?? this.status, + error: error ?? this.error, + filePath: filePath ?? this.filePath, + ); +} + +class DownloadResult { + final DownloadTask task; + final bool success; + + DownloadResult(this.task, this.success); +} diff --git a/mobile/apps/locker/lib/services/files/download/service_locator.dart b/mobile/apps/locker/lib/services/files/download/service_locator.dart new file mode 100644 index 0000000000..5de5f31b4e --- /dev/null +++ b/mobile/apps/locker/lib/services/files/download/service_locator.dart @@ -0,0 +1,36 @@ +import 'package:dio/dio.dart'; +import 'package:locker/services/files/download/manager.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class ServiceLocator { + late final SharedPreferences prefs; + late final Dio enteDio; + late final Dio nonEnteDio; + late final PackageInfo packageInfo; + + // instance + ServiceLocator._privateConstructor(); + + static final ServiceLocator instance = ServiceLocator._privateConstructor(); + + init( + SharedPreferences prefs, + Dio enteDio, + Dio nonEnteDio, + PackageInfo packageInfo, + ) { + this.prefs = prefs; + this.enteDio = enteDio; + this.nonEnteDio = nonEnteDio; + this.packageInfo = packageInfo; + } +} + +DownloadManager? _downloadManager; +DownloadManager get downloadManager { + _downloadManager ??= DownloadManager( + ServiceLocator.instance.nonEnteDio, + ); + return _downloadManager!; +} diff --git a/mobile/apps/locker/lib/services/files/links/links_client.dart b/mobile/apps/locker/lib/services/files/links/links_client.dart new file mode 100644 index 0000000000..ca7b6ec8c4 --- /dev/null +++ b/mobile/apps/locker/lib/services/files/links/links_client.dart @@ -0,0 +1,39 @@ +import "package:ente_network/network.dart"; +import "package:locker/services/files/links/models/shareable_link.dart"; +import "package:logging/logging.dart"; + +class LinksClient { + LinksClient._(); + + static final LinksClient instance = LinksClient._(); + + final _logger = Logger("LinksClient"); + final _enteDio = Network.instance.enteDio; + + Future init() async {} + + Future getOrCreateLink(int fileID) async { + try { + final response = await _enteDio.post( + '/files/share-url', + data: { + 'fileID': fileID, + 'app': 'locker', + }, + ); + return ShareableLink.fromJson(response.data as Map); + } catch (e, s) { + _logger.severe('Failed to get or create link for file ID: $fileID', e, s); + rethrow; + } + } + + Future deleteLink(int linkID) async { + try { + await _enteDio.delete('/files/share-url/$linkID'); + } catch (e, s) { + _logger.severe('Failed to delete link with ID: $linkID', e, s); + rethrow; + } + } +} diff --git a/mobile/apps/locker/lib/services/files/links/links_service.dart b/mobile/apps/locker/lib/services/files/links/links_service.dart new file mode 100644 index 0000000000..9114e0c918 --- /dev/null +++ b/mobile/apps/locker/lib/services/files/links/links_service.dart @@ -0,0 +1,29 @@ +import "package:fast_base58/fast_base58.dart"; +import "package:locker/services/collections/collections_service.dart"; +import "package:locker/services/files/links/links_client.dart"; +import "package:locker/services/files/links/models/shareable_link.dart"; +import "package:locker/services/files/sync/models/file.dart"; + +class LinksService { + LinksService._(); + + static final LinksService instance = LinksService._(); + + late final LinksClient _client; + + Future init() async { + _client = LinksClient.instance; + } + + Future getOrCreateLink(EnteFile file) async { + final link = await _client.getOrCreateLink(file.uploadedFileID!); + link.fullURL = link.url + + "#" + + Base58Encode(await CollectionService.instance.getFileKey(file)); + return link; + } + + Future deleteLink(int fileID) async { + await _client.deleteLink(fileID); + } +} diff --git a/mobile/apps/locker/lib/services/files/links/models/shareable_link.dart b/mobile/apps/locker/lib/services/files/links/models/shareable_link.dart new file mode 100644 index 0000000000..9e71fdd401 --- /dev/null +++ b/mobile/apps/locker/lib/services/files/links/models/shareable_link.dart @@ -0,0 +1,148 @@ +class ShareableLink { + final String linkID; + final String url; + final int ownerID; + final int fileID; + final int? validTill; + final int? deviceLimit; + final bool passwordEnabled; + final String? nonce; + final int? memLimit; + final int? opsLimit; + final bool enableDownload; + final int createdAt; + String? fullURL; + + ShareableLink({ + required this.linkID, + required this.url, + required this.ownerID, + required this.fileID, + this.validTill, + this.deviceLimit, + required this.passwordEnabled, + this.nonce, + this.memLimit, + this.opsLimit, + required this.enableDownload, + required this.createdAt, + }); + + factory ShareableLink.fromJson(Map json) { + return ShareableLink( + linkID: json['linkID'] as String, + url: json['url'] as String, + ownerID: json['ownerID'] as int, + fileID: json['fileID'] as int, + validTill: json['validTill'] as int?, + deviceLimit: json['deviceLimit'] as int?, + passwordEnabled: json['passwordEnabled'] as bool, + nonce: json['nonce'] as String?, + memLimit: json['memLimit'] as int?, + opsLimit: json['opsLimit'] as int?, + enableDownload: json['enableDownload'] as bool, + createdAt: json['createdAt'] as int, + ); + } + + Map toJson() { + return { + 'linkID': linkID, + 'url': url, + 'ownerID': ownerID, + 'fileID': fileID, + if (validTill != null) 'validTill': validTill, + if (deviceLimit != null) 'deviceLimit': deviceLimit, + 'passwordEnabled': passwordEnabled, + if (nonce != null) 'nonce': nonce, + if (memLimit != null) 'memLimit': memLimit, + if (opsLimit != null) 'opsLimit': opsLimit, + 'enableDownload': enableDownload, + 'createdAt': createdAt, + }; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is ShareableLink && + other.linkID == linkID && + other.url == url && + other.ownerID == ownerID && + other.fileID == fileID && + other.validTill == validTill && + other.deviceLimit == deviceLimit && + other.passwordEnabled == passwordEnabled && + other.nonce == nonce && + other.memLimit == memLimit && + other.opsLimit == opsLimit && + other.enableDownload == enableDownload && + other.createdAt == createdAt; + } + + @override + int get hashCode { + return Object.hash( + linkID, + url, + ownerID, + fileID, + validTill, + deviceLimit, + passwordEnabled, + nonce, + memLimit, + opsLimit, + enableDownload, + createdAt, + ); + } + + @override + String toString() { + return 'FileUrl(' + 'linkID: $linkID, ' + 'url: $url, ' + 'ownerID: $ownerID, ' + 'fileID: $fileID, ' + 'validTill: $validTill, ' + 'deviceLimit: $deviceLimit, ' + 'passwordEnabled: $passwordEnabled, ' + 'nonce: $nonce, ' + 'memLimit: $memLimit, ' + 'opsLimit: $opsLimit, ' + 'enableDownload: $enableDownload, ' + 'createdAt: $createdAt' + ')'; + } + + ShareableLink copyWith({ + String? linkID, + String? url, + int? ownerID, + int? fileID, + int? validTill, + int? deviceLimit, + bool? passwordEnabled, + String? nonce, + int? memLimit, + int? opsLimit, + bool? enableDownload, + int? createdAt, + }) { + return ShareableLink( + linkID: linkID ?? this.linkID, + url: url ?? this.url, + ownerID: ownerID ?? this.ownerID, + fileID: fileID ?? this.fileID, + validTill: validTill ?? this.validTill, + deviceLimit: deviceLimit ?? this.deviceLimit, + passwordEnabled: passwordEnabled ?? this.passwordEnabled, + nonce: nonce ?? this.nonce, + memLimit: memLimit ?? this.memLimit, + opsLimit: opsLimit ?? this.opsLimit, + enableDownload: enableDownload ?? this.enableDownload, + createdAt: createdAt ?? this.createdAt, + ); + } +} diff --git a/mobile/apps/locker/lib/services/files/sync/metadata_updater_service.dart b/mobile/apps/locker/lib/services/files/sync/metadata_updater_service.dart new file mode 100644 index 0000000000..c9bdc14fc5 --- /dev/null +++ b/mobile/apps/locker/lib/services/files/sync/metadata_updater_service.dart @@ -0,0 +1,202 @@ +import 'dart:convert'; + +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_network/network.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/configuration.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/services/files/sync/models/file_magic.dart'; +import 'package:logging/logging.dart'; + +class MetadataUpdaterService { + MetadataUpdaterService._privateConstructor(); + + static final MetadataUpdaterService instance = + MetadataUpdaterService._privateConstructor(); + + Future init() async {} + + final _logger = Logger("MetadataUpdaterService"); + final _enteDio = Network.instance.enteDio; + + Future editFileCaption(EnteFile file, String caption) async { + try { + await _updatePublicMetadata([file], captionKey, caption); + await CollectionService.instance.sync(); + return true; + } catch (e) { + return false; + } + } + + Future editFileNameAndCaption( + EnteFile file, + String name, + String caption, + ) async { + try { + final Map updates = { + editNameKey: name, + captionKey: caption, + }; + await _updatePublicMetadataBulk([file], updates); + await CollectionService.instance.sync(); + return true; + } catch (e) { + return false; + } + } + + Future _updatePublicMetadata( + List files, + String key, + dynamic value, + ) async { + if (files.isEmpty) { + return; + } + try { + final Map update = {key: value}; + await _updatePublicMagicMetadata(files, update); + } catch (e, s) { + _logger.severe( + "Failed to update public metadata for files: $files", e, s,); + rethrow; + } + } + + Future _updatePublicMetadataBulk( + List files, + Map updates, + ) async { + if (files.isEmpty) { + return; + } + try { + await _updatePublicMagicMetadata(files, updates); + } catch (e, s) { + _logger.severe( + "Failed to update public metadata for files: $files", e, s,); + rethrow; + } + } + + Future _updatePublicMagicMetadata( + List files, + Map? newMetadataUpdate, { + Map>? metadataUpdateMap, + }) async { + final params = {}; + params['metadataList'] = []; + final int ownerID = Configuration.instance.getUserID()!; + try { + for (final file in files) { + if (file.uploadedFileID == null) { + throw AssertionError( + "operation is only supported on backed up files", + ); + } else if (file.ownerID != ownerID) { + throw AssertionError("cannot modify memories not owned by you"); + } + // read the existing magic metadata and apply new updates to existing data + // current update is simple replace. This will be enhanced in the future, + // as required. + final newUpdates = metadataUpdateMap != null + ? metadataUpdateMap[file.uploadedFileID] + : newMetadataUpdate; + assert( + newUpdates != null && newUpdates.isNotEmpty, + "can not apply empty updates", + ); + final Map jsonToUpdate = + jsonDecode(file.pubMmdEncodedJson ?? '{}'); + newUpdates!.forEach((key, value) { + jsonToUpdate[key] = value; + }); + + // update the local information so that it's reflected on UI + file.pubMmdEncodedJson = jsonEncode(jsonToUpdate); + file.pubMagicMetadata = PubMagicMetadata.fromJson(jsonToUpdate); + + final fileKey = await CollectionService.instance.getFileKey(file); + + final encryptedMMd = await CryptoUtil.encryptData( + utf8.encode(jsonEncode(jsonToUpdate)), + fileKey, + ); + params['metadataList'].add( + UpdateMagicMetadataRequest( + id: file.uploadedFileID!, + magicMetadata: MetadataRequest( + version: file.pubMmdVersion, + count: jsonToUpdate.length, + data: CryptoUtil.bin2base64(encryptedMMd.encryptedData!), + header: CryptoUtil.bin2base64(encryptedMMd.header!), + ), + ), + ); + file.pubMmdVersion = file.pubMmdVersion + 1; + } + + await _enteDio.put("/files/public-magic-metadata", data: params); + } catch (e, s) { + _logger.severe(e, s); + rethrow; + } + } +} + +class UpdateMagicMetadataRequest { + final int id; + final MetadataRequest? magicMetadata; + + UpdateMagicMetadataRequest({required this.id, required this.magicMetadata}); + + factory UpdateMagicMetadataRequest.fromJson(dynamic json) { + return UpdateMagicMetadataRequest( + id: json['id'], + magicMetadata: json['magicMetadata'] != null + ? MetadataRequest.fromJson(json['magicMetadata']) + : null, + ); + } + + Map toJson() { + final map = {}; + map['id'] = id; + if (magicMetadata != null) { + map['magicMetadata'] = magicMetadata!.toJson(); + } + return map; + } +} + +class MetadataRequest { + int? version; + int? count; + String? data; + String? header; + + MetadataRequest({ + required this.version, + required this.count, + required this.data, + required this.header, + }); + + MetadataRequest.fromJson(dynamic json) { + version = json['version']; + count = json['count']; + data = json['data']; + header = json['header']; + } + + Map toJson() { + final map = {}; + map['version'] = version; + map['count'] = count; + map['data'] = data; + map['header'] = header; + return map; + } +} diff --git a/mobile/apps/locker/lib/services/files/sync/models/common_keys.dart b/mobile/apps/locker/lib/services/files/sync/models/common_keys.dart new file mode 100644 index 0000000000..1156623e8a --- /dev/null +++ b/mobile/apps/locker/lib/services/files/sync/models/common_keys.dart @@ -0,0 +1,8 @@ +const magicKeyVisibility = 'visibility'; + +// Visibility Constants +const visibleVisibility = 0; +const archiveVisibility = 1; + +///Do not use [hiddenVisibility] for hidden files +const hiddenVisibility = 2; diff --git a/mobile/apps/locker/lib/services/files/sync/models/file.dart b/mobile/apps/locker/lib/services/files/sync/models/file.dart new file mode 100644 index 0000000000..50046746e3 --- /dev/null +++ b/mobile/apps/locker/lib/services/files/sync/models/file.dart @@ -0,0 +1,123 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:locker/services/files/download/file_url.dart'; +import 'package:locker/services/files/sync/models/file_magic.dart'; +import 'package:logging/logging.dart'; + +class EnteFile { + String? localPath; + int? uploadedFileID; + int? ownerID; + int? collectionID; + String? title; + int? creationTime; + int? modificationTime; + int? updationTime; + int? addedTime; + String? hash; + int? metadataVersion; + String? encryptedKey; + String? keyDecryptionNonce; + String? fileDecryptionHeader; + String? thumbnailDecryptionHeader; + String? metadataDecryptionHeader; + int? fileSize; + + String? mMdEncodedJson; + int mMdVersion = 0; + MagicMetadata? _mmd; + + MagicMetadata get magicMetadata => + _mmd ?? MagicMetadata.fromEncodedJson(mMdEncodedJson ?? '{}'); + + set magicMetadata(val) => _mmd = val; + + String? pubMmdEncodedJson; + int pubMmdVersion = 1; + PubMagicMetadata? _pubMmd; + + PubMagicMetadata get pubMagicMetadata => + _pubMmd ?? PubMagicMetadata.fromEncodedJson(pubMmdEncodedJson ?? '{}'); + + set pubMagicMetadata(val) => _pubMmd = val; + + static const kCurrentMetadataVersion = 2; + + static final _logger = Logger('File'); + + EnteFile(); + + static EnteFile fromFile(File file) { + final enteFile = EnteFile(); + enteFile.localPath = file.path; + enteFile.title = file.path.split('/').last; + enteFile.creationTime = file.statSync().changed.millisecondsSinceEpoch; + enteFile.modificationTime = file.statSync().modified.millisecondsSinceEpoch; + return enteFile; + } + + Map get metadata { + final metadata = {}; + metadata["title"] = title; + metadata["localPath"] = localPath; + metadata["creationTime"] = creationTime; + metadata["modificationTime"] = modificationTime; + if (hash != null) { + metadata["hash"] = hash; + } + if (metadataVersion != null) { + metadata["version"] = metadataVersion; + } + return metadata; + } + + String get downloadUrl => + FileUrl.getUrl(uploadedFileID!, FileUrlType.download); + + String? get caption { + return pubMagicMetadata.caption; + } + + String? debugCaption; + + String get displayName { + if (pubMagicMetadata.editedName != null) { + return pubMagicMetadata.editedName!; + } + if (title == null && kDebugMode) _logger.severe('File title is null'); + return title ?? ''; + } + + bool get isUploaded { + return uploadedFileID != null; + } + + void applyMetadata(Map metadata) { + title = metadata["title"]; + localPath = metadata["localPath"]; + creationTime = metadata["creationTime"] ?? 0; + modificationTime = metadata["modificationTime"] ?? creationTime; + hash = metadata["hash"]; + metadataVersion = metadata["version"] ?? 0; + } + + @override + String toString() { + return '''File(title: $title, uploadedFileId: $uploadedFileID, + modificationTime: $modificationTime, ownerID: $ownerID, + collectionID: $collectionID, updationTime: $updationTime)'''; + } + + @override + bool operator ==(Object o) { + if (identical(this, o)) return true; + + return o is EnteFile && o.uploadedFileID == uploadedFileID; + } + + @override + int get hashCode { + return uploadedFileID.hashCode; + } +} diff --git a/mobile/apps/locker/lib/services/files/sync/models/file_magic.dart b/mobile/apps/locker/lib/services/files/sync/models/file_magic.dart new file mode 100644 index 0000000000..f99f1e8e4f --- /dev/null +++ b/mobile/apps/locker/lib/services/files/sync/models/file_magic.dart @@ -0,0 +1,127 @@ +import "dart:convert"; + +import "package:flutter/cupertino.dart"; +import "package:locker/services/files/sync/models/common_keys.dart"; + +const editTimeKey = 'editedTime'; +const editNameKey = 'editedName'; +const captionKey = "caption"; +const uploaderNameKey = "uploaderName"; +const widthKey = 'w'; +const heightKey = 'h'; +const streamVersionKey = 'sv'; +const mediaTypeKey = 'mediaType'; +const latKey = "lat"; +const longKey = "long"; +const motionVideoIndexKey = "mvi"; +const noThumbKey = "noThumb"; +const dateTimeKey = 'dateTime'; +const offsetTimeKey = 'offsetTime'; + +class MagicMetadata { + // 0 -> visible + // 1 -> archived + // 2 -> hidden etc? + int visibility; + + MagicMetadata({required this.visibility}); + + factory MagicMetadata.fromEncodedJson(String encodedJson) => + MagicMetadata.fromJson(jsonDecode(encodedJson)); + + factory MagicMetadata.fromJson(dynamic json) => MagicMetadata.fromMap(json); + + static fromMap(Map? map) { + if (map == null) return null; + return MagicMetadata( + visibility: map[magicKeyVisibility] ?? visibleVisibility, + ); + } +} + +class PubMagicMetadata { + int? editedTime; + String? editedName; + String? caption; + String? uploaderName; + int? w; + int? h; + double? lat; + double? long; + + // Indicates streaming version of the file. + // If this is set, then the file is a streaming version of the original file. + int? sv; + + // ISO 8601 datetime without timezone. This contains the date and time of the photo in the original tz + // where the photo was taken. + String? dateTime; + String? offsetTime; + + // Motion Video Index. Positive value (>0) indicates that the file is a motion + // photo + int? mvi; + + // if true, then the thumbnail is not available + // Note: desktop/web sets hasStaticThumbnail in the file metadata. + // As we don't want to support updating the og file metadata (yet), adding + // this new field to the pub metadata. For static thumbnail, all thumbnails + // should have exact same hash with should match the constant `blackThumbnailBase64` + bool? noThumb; + + // null -> not computed + // 0 -> normal + // 1 -> panorama + int? mediaType; + + PubMagicMetadata({ + this.editedTime, + this.editedName, + this.caption, + this.uploaderName, + this.w, + this.h, + this.lat, + this.long, + this.mvi, + this.noThumb, + this.mediaType, + this.dateTime, + this.offsetTime, + this.sv, + }); + + factory PubMagicMetadata.fromEncodedJson(String encodedJson) => + PubMagicMetadata.fromJson(jsonDecode(encodedJson)); + + factory PubMagicMetadata.fromJson(dynamic json) => + PubMagicMetadata.fromMap(json); + + static fromMap(Map? map) { + if (map == null) return null; + return PubMagicMetadata( + editedTime: map[editTimeKey], + editedName: map[editNameKey], + caption: map[captionKey], + uploaderName: map[uploaderNameKey], + w: safeParseInt(map[widthKey], widthKey), + h: safeParseInt(map[heightKey], heightKey), + lat: map[latKey], + long: map[longKey], + mvi: map[motionVideoIndexKey], + noThumb: map[noThumbKey], + mediaType: map[mediaTypeKey], + dateTime: map[dateTimeKey], + offsetTime: map[offsetTimeKey], + sv: safeParseInt(map[streamVersionKey], streamVersionKey), + ); + } + + static int? safeParseInt(dynamic value, String key) { + if (value == null) return null; + if (value is int) return value; + debugPrint("PubMagicMetadata key: $key Unexpected value: $value"); + if (value is String) return int.tryParse(value); + return null; + } +} diff --git a/mobile/apps/locker/lib/services/files/sync/models/metadata_request.dart b/mobile/apps/locker/lib/services/files/sync/models/metadata_request.dart new file mode 100644 index 0000000000..de0589e1ed --- /dev/null +++ b/mobile/apps/locker/lib/services/files/sync/models/metadata_request.dart @@ -0,0 +1,29 @@ +class MetadataRequest { + int? version; + int? count; + String? data; + String? header; + + MetadataRequest({ + required this.version, + required this.count, + required this.data, + required this.header, + }); + + MetadataRequest.fromJson(dynamic json) { + version = json['version']; + count = json['count']; + data = json['data']; + header = json['header']; + } + + Map toJson() { + final map = {}; + map['version'] = version; + map['count'] = count; + map['data'] = data; + map['header'] = header; + return map; + } +} diff --git a/mobile/apps/locker/lib/services/files/upload/file_upload_service.dart b/mobile/apps/locker/lib/services/files/upload/file_upload_service.dart new file mode 100644 index 0000000000..f56b4611cd --- /dev/null +++ b/mobile/apps/locker/lib/services/files/upload/file_upload_service.dart @@ -0,0 +1,684 @@ +import 'dart:async'; +import 'dart:collection'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math' as math; + +import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; +import 'package:ente_accounts/services/user_service.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_events/event_bus.dart'; +import 'package:ente_network/network.dart'; +import 'package:flutter/foundation.dart'; +import 'package:locker/core/constants.dart'; +import 'package:locker/core/errors.dart'; +import 'package:locker/events/backup_updated_event.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/configuration.dart'; +import "package:locker/services/files/sync/metadata_updater_service.dart"; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/services/files/sync/models/file_magic.dart'; +import 'package:locker/services/files/upload/models/backup_item.dart'; +import 'package:locker/services/files/upload/models/backup_item_status.dart'; +import 'package:locker/services/files/upload/models/upload_url.dart'; +import "package:locker/utils/crypto_helper.dart"; +import 'package:locker/utils/data_util.dart'; +import 'package:logging/logging.dart'; +import "package:path/path.dart"; +import 'package:shared_preferences/shared_preferences.dart'; +import "package:uuid/uuid.dart"; + +class FileUploader { + static const kMaximumConcurrentUploads = 4; + static const kMaximumConcurrentVideoUploads = 2; + static const kMaximumThumbnailCompressionAttempts = 2; + static const kMaximumUploadAttempts = 4; + static const kMaxFileSize5Gib = 5368709120; + static const kBlockedUploadsPollFrequency = Duration(seconds: 2); + static const kFileUploadTimeout = Duration(minutes: 50); + static const k20MBStorageBuffer = 20 * 1024 * 1024; + static const _lastStaleFileCleanupTime = "lastStaleFileCleanupTime"; + + final _logger = Logger("FileUploader"); + final _dio = Network.instance.getDio(); + final _enteDio = Network.instance.enteDio; + final LinkedHashMap _queue = + LinkedHashMap(); + final LinkedHashMap _allBackups = + LinkedHashMap(); + final kSafeBufferForLockExpiry = const Duration(hours: 4).inMicroseconds; + final kBGTaskDeathTimeout = const Duration(seconds: 5).inMicroseconds; + final _uploadURLs = Queue(); + + LinkedHashMap get allBackups => _allBackups; + + // Maintains the count of files in the current upload session. + // Upload session is the period between the first entry into the _queue and last entry out of the _queue + int _totalCountInUploadSession = 0; + + // _uploadCounter indicates number of uploads which are currently in progress + int _uploadCounter = 0; + late SharedPreferences _prefs; + + // _hasInitiatedForceUpload is used to track if user attempted force upload + // where files are uploaded directly (without adding them to DB). In such + // cases, we don't want to clear the stale upload files. See #removeStaleFiles + // as it can result in clearing files which are still being force uploaded. + final bool _hasInitiatedForceUpload = false; + + FileUploader._privateConstructor(); + + static FileUploader instance = FileUploader._privateConstructor(); + + Future init(SharedPreferences preferences, bool isBackground) async { + _prefs = preferences; + final currentTime = DateTime.now().microsecondsSinceEpoch; + if (currentTime - (_prefs.getInt(_lastStaleFileCleanupTime) ?? 0) > + tempDirCleanUpInterval) { + await removeStaleFiles(); + await _prefs.setInt(_lastStaleFileCleanupTime, currentTime); + } + } + + Future upload(File file, Collection collection) { + _totalCountInUploadSession++; + final String path = file.path; + final completer = Completer(); + _queue[path] = FileUploadItem(file, collection, completer); + _allBackups[path] = BackupItem( + status: BackupItemStatus.inQueue, + file: file, + collection: collection, + completer: completer, + ); + Bus.instance.fire(BackupUpdatedEvent(_allBackups)); + _pollQueue(); + return completer.future; + } + + int getCurrentSessionUploadCount() { + return _totalCountInUploadSession; + } + + void clearQueue(final Error reason) { + final List uploadsToBeRemoved = []; + _queue.entries + .where((entry) => entry.value.status == UploadStatus.notStarted) + .forEach((pendingUpload) { + uploadsToBeRemoved.add(pendingUpload.key); + }); + for (final id in uploadsToBeRemoved) { + _queue.remove(id)?.completer.completeError(reason); + _allBackups[id] = _allBackups[id]!.copyWith( + status: BackupItemStatus.retry, + error: reason, + ); + Bus.instance.fire(BackupUpdatedEvent(_allBackups)); + } + _totalCountInUploadSession = 0; + } + + void clearCachedUploadURLs() { + _uploadURLs.clear(); + } + + void removeFromQueueWhere( + final bool Function(File) fn, + final Error reason, + ) { + final List uploadsToBeRemoved = []; + _queue.entries + .where((entry) => entry.value.status == UploadStatus.notStarted) + .forEach((pendingUpload) { + if (fn(pendingUpload.value.file)) { + uploadsToBeRemoved.add(pendingUpload.key); + } + }); + for (final id in uploadsToBeRemoved) { + _queue.remove(id)?.completer.completeError(reason); + _allBackups[id] = _allBackups[id]! + .copyWith(status: BackupItemStatus.retry, error: reason); + Bus.instance.fire(BackupUpdatedEvent(_allBackups)); + } + _logger.info( + 'number of enteries removed from queue ${uploadsToBeRemoved.length}', + ); + _totalCountInUploadSession -= uploadsToBeRemoved.length; + } + + void _pollQueue() { + if (_queue.isEmpty) { + // Upload session completed + _totalCountInUploadSession = 0; + return; + } + if (_uploadCounter < kMaximumConcurrentUploads) { + final pendingEntry = _queue.entries + .firstWhereOrNull( + (entry) => entry.value.status == UploadStatus.notStarted, + ) + ?.value; + if (pendingEntry != null) { + pendingEntry.status = UploadStatus.inProgress; + _allBackups[pendingEntry.file.path] = + _allBackups[pendingEntry.file.path]! + .copyWith(status: BackupItemStatus.uploading); + Bus.instance.fire(BackupUpdatedEvent(_allBackups)); + _encryptAndUploadFileToCollection( + pendingEntry.file, + pendingEntry.collection, + ); + } + } + } + + Future _encryptAndUploadFileToCollection( + File file, + Collection collection, { + bool forcedUpload = false, + }) async { + _uploadCounter++; + final path = file.path; + try { + final uploadedFile = + await _tryToUpload(file, collection, forcedUpload).timeout( + kFileUploadTimeout, + onTimeout: () { + final message = "Upload timed out for file $file"; + _logger.warning(message); + throw TimeoutException(message); + }, + ); + _queue.remove(path)!.completer.complete(uploadedFile); + _allBackups[path] = + _allBackups[path]!.copyWith(status: BackupItemStatus.uploaded); + Bus.instance.fire(BackupUpdatedEvent(_allBackups)); + return uploadedFile; + } catch (e) { + _queue.remove(path)!.completer.completeError(e); + _allBackups[path] = + _allBackups[path]!.copyWith(status: BackupItemStatus.retry, error: e); + Bus.instance.fire(BackupUpdatedEvent(_allBackups)); + return null; + } finally { + _uploadCounter--; + _pollQueue(); + } + } + + Future removeStaleFiles() async { + if (_hasInitiatedForceUpload) { + _logger.info( + "Force upload was initiated, skipping stale file cleanup", + ); + return; + } + try { + final String dir = Configuration.instance.getTempDirectory(); + // delete all files in the temp directory that start with upload_ and + // ends with .encrypted. Fetch files in async manner + final files = await Directory(dir).list().toList(); + final filesToDelete = files.where((file) { + return file.path.contains(uploadTempFilePrefix) && + file.path.contains(".encrypted"); + }); + if (filesToDelete.isNotEmpty) { + _logger.info('Deleting ${filesToDelete.length} stale upload files '); + for (final file in filesToDelete) { + await file.delete(); + } + } + } catch (e, s) { + _logger.severe("Failed to remove stale files", e, s); + } + } + + Future _tryToUpload( + File file, + Collection collection, + bool forcedUpload, + ) async { + if (_allBackups[file.path] != null && + _allBackups[file.path]!.status != BackupItemStatus.uploading) { + _allBackups[file.path] = _allBackups[file.path]!.copyWith( + status: BackupItemStatus.uploading, + ); + Bus.instance.fire(BackupUpdatedEvent(_allBackups)); + } + + final tempDirectory = Configuration.instance.getTempDirectory(); + final String uniqueID = + '${const Uuid().v4().toString()}_${file.path.split('/').last}'; + + final encryptedFilePath = + '$tempDirectory$uploadTempFilePrefix${uniqueID}_file.encrypted'; + final encryptedThumbnailPath = + '$tempDirectory$uploadTempFilePrefix${uniqueID}_thumb.encrypted'; + late final int encFileSize; + + var uploadCompleted = false; + // This flag is used to decide whether to clear the iOS origin file cache + // or not. + var uploadHardFailure = false; + try { + _logger.info( + 'starting ${forcedUpload ? 'forced' : ''} ' + 'upload of ${file.toString()}', + ); + + Uint8List? key; + final encryptedFileExists = File(encryptedFilePath).existsSync(); + + if (encryptedFileExists) { + // otherwise just delete the file for singlepart upload + await File(encryptedFilePath).delete(); + } + await _checkIfWithinStorageLimit(file); + final encryptedFile = File(encryptedFilePath); + + final fileAttributes = await CryptoUtil.encryptFile( + file.path, + encryptedFilePath, + key: key, + ); + encFileSize = await encryptedFile.length(); + + final thumbnailData = base64Decode(blackThumbnailBase64); + final encryptedThumbnailData = await CryptoUtil.encryptData( + thumbnailData, + fileAttributes.key!, + ); + if (File(encryptedThumbnailPath).existsSync()) { + await File(encryptedThumbnailPath).delete(); + } + final encryptedThumbnailFile = File(encryptedThumbnailPath); + await encryptedThumbnailFile + .writeAsBytes(encryptedThumbnailData.encryptedData!); + final encThumbSize = await encryptedThumbnailFile.length(); + + final thumbnailUploadURL = await _getUploadURL(); + final thumbnailObjectKey = await _putFile( + thumbnailUploadURL, + encryptedThumbnailFile, + encThumbSize, + ); + + final fileUploadURL = await _getUploadURL(); + final fileObjectKey = + await _putFile(fileUploadURL, encryptedFile, encFileSize); + + final enteFile = EnteFile.fromFile(file); + + final encryptedMetadataResult = await CryptoUtil.encryptData( + utf8.encode(jsonEncode(enteFile.metadata)), + fileAttributes.key!, + ); + final fileDecryptionHeader = + CryptoUtil.bin2base64(fileAttributes.header!); + final thumbnailDecryptionHeader = + CryptoUtil.bin2base64(encryptedThumbnailData.header!); + final encryptedMetadata = CryptoUtil.bin2base64( + encryptedMetadataResult.encryptedData!, + ); + final metadataDecryptionHeader = + CryptoUtil.bin2base64(encryptedMetadataResult.header!); + final encryptedFileKeyData = CryptoUtil.encryptSync( + fileAttributes.key!, + CryptoHelper.instance.getCollectionKey(collection), + ); + final encryptedKey = + CryptoUtil.bin2base64(encryptedFileKeyData.encryptedData!); + final keyDecryptionNonce = + CryptoUtil.bin2base64(encryptedFileKeyData.nonce!); + final Map pubMetadata = {}; + pubMetadata["noThumb"] = true; + MetadataRequest? pubMetadataRequest; + if (pubMetadata.isNotEmpty) { + pubMetadataRequest = await getPubMetadataRequest( + enteFile, + pubMetadata, + fileAttributes.key!, + ); + } + final remoteFile = await _uploadFile( + enteFile, + collection.id, + encryptedKey, + keyDecryptionNonce, + fileObjectKey, + fileDecryptionHeader, + encFileSize, + thumbnailObjectKey, + thumbnailDecryptionHeader, + encThumbSize, + encryptedMetadata, + metadataDecryptionHeader, + pubMetadata: pubMetadataRequest, + ); + _logger.info("File upload complete for $remoteFile"); + uploadCompleted = true; + return remoteFile; + } catch (e, s) { + if (!(e is NoActiveSubscriptionError || + e is StorageLimitExceededError || + e is WiFiUnavailableError || + e is SilentlyCancelUploadsError || + e is InvalidFileError || + e is FileTooLargeForPlanError)) { + _logger.severe("File upload failed for $file", e, s); + } + if (e is InvalidFileError) { + _logger.severe("File upload ignored for $file", e); + } + if ((e is StorageLimitExceededError || + e is FileTooLargeForPlanError || + e is NoActiveSubscriptionError)) { + // file upload can not be retried in such cases without user intervention + uploadHardFailure = true; + } + rethrow; + } finally { + await _onUploadDone( + file, + uploadCompleted, + uploadHardFailure, + encryptedFilePath, + encryptedThumbnailPath, + ); + } + } + + Future getPubMetadataRequest( + EnteFile file, + Map newData, + Uint8List fileKey, + ) async { + final Map jsonToUpdate = + jsonDecode(file.pubMmdEncodedJson ?? '{}'); + newData.forEach((key, value) { + jsonToUpdate[key] = value; + }); + + // update the local information so that it's reflected on UI + file.pubMmdEncodedJson = jsonEncode(jsonToUpdate); + file.pubMagicMetadata = PubMagicMetadata.fromJson(jsonToUpdate); + final encryptedMMd = await CryptoUtil.encryptData( + utf8.encode(jsonEncode(jsonToUpdate)), + fileKey, + ); + return MetadataRequest( + version: file.pubMmdVersion == 0 ? 1 : file.pubMmdVersion, + count: jsonToUpdate.length, + data: CryptoUtil.bin2base64(encryptedMMd.encryptedData!), + header: CryptoUtil.bin2base64(encryptedMMd.header!), + ); + } + + Future _onUploadDone( + File sourceFile, + bool uploadCompleted, + bool uploadHardFailure, + String encryptedFilePath, + String encryptedThumbnailPath, + ) async { + // Note: Consider removing source file if upload has completed / failed + if (File(encryptedFilePath).existsSync()) { + await File(encryptedFilePath).delete(); + } + if (File(encryptedThumbnailPath).existsSync()) { + await File(encryptedThumbnailPath).delete(); + } + } + + /* + _checkIfWithinStorageLimit verifies if the file size for encryption and upload + is within the storage limit. It throws StorageLimitExceededError if the limit + is exceeded. This check is best effort and may not be completely accurate + due to UserDetail cache. It prevents infinite loops when clients attempt to + upload files that exceed the server's storage limit + buffer. + Note: Local storageBuffer is 20MB, server storageBuffer is 50MB, and an + additional 30MB is reserved for thumbnails and encryption overhead. + */ + Future _checkIfWithinStorageLimit(File fileToBeUploaded) async { + try { + final userDetails = UserService.instance.getCachedUserDetails(); + if (userDetails == null) { + return; + } + // add k20MBStorageBuffer to the free storage + final num freeStorage = userDetails.getFreeStorage() + k20MBStorageBuffer; + final num fileSize = await fileToBeUploaded.length(); + if (fileSize > freeStorage) { + _logger.warning('Storage limit exceeded fileSize $fileSize and ' + 'freeStorage $freeStorage'); + throw StorageLimitExceededError(); + } + if (fileSize > kMaxFileSize5Gib) { + _logger.warning('File size exceeds 5GiB fileSize $fileSize'); + throw InvalidFileError( + 'file size above 5GiB', + InvalidReason.tooLargeFile, + ); + } + } catch (e) { + if (e is StorageLimitExceededError || e is InvalidFileError) { + rethrow; + } else { + _logger.severe('Error checking storage limit', e); + } + } + } + + Future _uploadFile( + EnteFile file, + int collectionID, + String encryptedKey, + String keyDecryptionNonce, + String fileObjectKey, + String fileDecryptionHeader, + int fileSize, + String thumbnailObjectKey, + String thumbnailDecryptionHeader, + int thumbnailSize, + String encryptedMetadata, + String metadataDecryptionHeader, { + MetadataRequest? pubMetadata, + int attempt = 1, + }) async { + final request = { + "collectionID": collectionID, + "encryptedKey": encryptedKey, + "keyDecryptionNonce": keyDecryptionNonce, + "file": { + "objectKey": fileObjectKey, + "decryptionHeader": fileDecryptionHeader, + "size": fileSize, + }, + "thumbnail": { + "objectKey": thumbnailObjectKey, + "decryptionHeader": thumbnailDecryptionHeader, + "size": thumbnailSize, + }, + "metadata": { + "encryptedData": encryptedMetadata, + "decryptionHeader": metadataDecryptionHeader, + }, + }; + if (pubMetadata != null) { + request["pubMagicMetadata"] = pubMetadata; + } + try { + final response = await _enteDio.post("/files", data: request); + final data = response.data; + file.uploadedFileID = data["id"]; + file.collectionID = collectionID; + file.updationTime = data["updationTime"]; + file.ownerID = data["ownerID"]; + file.encryptedKey = encryptedKey; + file.keyDecryptionNonce = keyDecryptionNonce; + file.thumbnailDecryptionHeader = thumbnailDecryptionHeader; + file.fileDecryptionHeader = fileDecryptionHeader; + file.metadataDecryptionHeader = metadataDecryptionHeader; + return file; + } on DioException catch (e) { + final int statusCode = e.response?.statusCode ?? -1; + if (statusCode == 413) { + throw FileTooLargeForPlanError(); + } else if (statusCode == 426) { + _onStorageLimitExceeded(); + } else if (attempt < kMaximumUploadAttempts && statusCode == -1) { + // retry when DioException contains no response/status code + _logger.info( + "Upload file (${file.displayName}) failed, will retry in 3 seconds", + ); + await Future.delayed(const Duration(seconds: 3)); + return _uploadFile( + file, + collectionID, + encryptedKey, + keyDecryptionNonce, + fileObjectKey, + fileDecryptionHeader, + fileSize, + thumbnailObjectKey, + thumbnailDecryptionHeader, + thumbnailSize, + encryptedMetadata, + metadataDecryptionHeader, + attempt: attempt + 1, + pubMetadata: pubMetadata, + ); + } else { + _logger.severe("Failed to upload file ${file.displayName}", e); + } + rethrow; + } + } + + Future _getUploadURL() async { + if (_uploadURLs.isEmpty) { + // the queue is empty, fetch at least for one file to handle force uploads + // that are not in the queue. This is to also avoid + await fetchUploadURLs(math.max(_queue.length, 1)); + } + try { + return _uploadURLs.removeFirst(); + } catch (e) { + if (e is StateError && e.message == 'No element' && _queue.isEmpty) { + _logger.warning("Oops, uploadUrls has no element now, fetching again"); + return _getUploadURL(); + } else { + rethrow; + } + } + } + + Future? _uploadURLFetchInProgress; + + Future fetchUploadURLs(int fileCount) async { + _uploadURLFetchInProgress ??= Future(() async { + try { + final response = await _enteDio.get( + "/files/upload-urls", + queryParameters: { + "count": math.min(42, fileCount * 2), // m4gic number + }, + ); + final urls = (response.data["urls"] as List) + .map((e) => UploadURL.fromMap(e)) + .toList(); + _uploadURLs.addAll(urls); + } on DioException catch (e, s) { + if (e.response != null) { + if (e.response!.statusCode == 402) { + final error = NoActiveSubscriptionError(); + clearQueue(error); + throw error; + } else if (e.response!.statusCode == 426) { + final error = StorageLimitExceededError(); + clearQueue(error); + throw error; + } else { + _logger.warning("Could not fetch upload URLs", e, s); + } + } + rethrow; + } finally { + _uploadURLFetchInProgress = null; + } + }); + return _uploadURLFetchInProgress; + } + + void _onStorageLimitExceeded() { + clearQueue(StorageLimitExceededError()); + throw StorageLimitExceededError(); + } + + Future _putFile( + UploadURL uploadURL, + File file, + int fileSize, { + int attempt = 1, + }) async { + final startTime = DateTime.now().millisecondsSinceEpoch; + final fileName = basename(file.path); + try { + await _dio.put( + uploadURL.url, + data: file.openRead(), + options: Options( + headers: { + Headers.contentLengthHeader: fileSize, + }, + ), + ); + _logger.info( + "Uploaded object $fileName of size: ${formatBytes(fileSize)} at speed: ${(fileSize / (DateTime.now().millisecondsSinceEpoch - startTime)).toStringAsFixed(2)} KB/s", + ); + + return uploadURL.objectKey; + } on DioException catch (e) { + if (e.message?.startsWith("HttpException: Content size") ?? false) { + rethrow; + } else if (attempt < kMaximumUploadAttempts) { + _logger.info("Upload failed for $fileName, retrying"); + final newUploadURL = await _getUploadURL(); + return _putFile( + newUploadURL, + file, + fileSize, + attempt: attempt + 1, + ); + } else { + _logger.info( + "Failed to upload file ${basename(file.path)} after $attempt attempts", + e, + ); + rethrow; + } + } + } +} + +class FileUploadItem { + final File file; + final Collection collection; + final Completer completer; + UploadStatus status; + + FileUploadItem( + this.file, + this.collection, + this.completer, { + this.status = UploadStatus.notStarted, + }); +} + +enum UploadStatus { notStarted, inProgress, inBackground, completed } + +enum ProcessType { + background, + foreground, +} diff --git a/mobile/apps/locker/lib/services/files/upload/models/backup_item.dart b/mobile/apps/locker/lib/services/files/upload/models/backup_item.dart new file mode 100644 index 0000000000..10cc725667 --- /dev/null +++ b/mobile/apps/locker/lib/services/files/upload/models/backup_item.dart @@ -0,0 +1,62 @@ +import "dart:async"; +import "dart:io"; + +import "package:locker/services/collections/models/collection.dart"; +import "package:locker/services/files/sync/models/file.dart"; +import "package:locker/services/files/upload/models/backup_item_status.dart"; + +class BackupItem { + final BackupItemStatus status; + final File file; + final Collection collection; + final Completer? completer; + final Object? error; + + BackupItem({ + required this.status, + required this.file, + required this.collection, + required this.completer, + this.error, + }); + + BackupItem copyWith({ + BackupItemStatus? status, + File? file, + Collection? collection, + Completer? completer, + Object? error, + }) { + return BackupItem( + status: status ?? this.status, + file: file ?? this.file, + collection: collection ?? this.collection, + completer: completer ?? this.completer, + error: error ?? this.error, + ); + } + + @override + String toString() { + return 'BackupItem(status: $status, file: $file, collection: $collection, error: $error)'; + } + + @override + bool operator ==(covariant BackupItem other) { + if (identical(this, other)) return true; + + return other.status == status && + other.file == file && + other.collection == collection && + other.completer == completer && + other.error == error; + } + + @override + int get hashCode { + return status.hashCode ^ + file.hashCode ^ + collection.hashCode ^ + completer.hashCode; + } +} diff --git a/mobile/apps/locker/lib/services/files/upload/models/backup_item_status.dart b/mobile/apps/locker/lib/services/files/upload/models/backup_item_status.dart new file mode 100644 index 0000000000..f9de2673ce --- /dev/null +++ b/mobile/apps/locker/lib/services/files/upload/models/backup_item_status.dart @@ -0,0 +1,7 @@ +enum BackupItemStatus { + uploading, + inQueue, + retry, + inBackground, + uploaded, +} diff --git a/mobile/apps/locker/lib/services/files/upload/models/upload_url.dart b/mobile/apps/locker/lib/services/files/upload/models/upload_url.dart new file mode 100644 index 0000000000..49aa3b6ad7 --- /dev/null +++ b/mobile/apps/locker/lib/services/files/upload/models/upload_url.dart @@ -0,0 +1,26 @@ +import 'dart:convert'; + +class UploadURL { + final String url; + final String objectKey; + + UploadURL(this.url, this.objectKey); + Map toMap() { + return { + 'url': url, + 'objectKey': objectKey, + }; + } + + factory UploadURL.fromMap(Map map) { + return UploadURL( + map['url'], + map['objectKey'], + ); + } + + String toJson() => json.encode(toMap()); + + factory UploadURL.fromJson(String source) => + UploadURL.fromMap(json.decode(source)); +} diff --git a/mobile/apps/locker/lib/services/trash/models/trash_file.dart b/mobile/apps/locker/lib/services/trash/models/trash_file.dart new file mode 100644 index 0000000000..50537e7ac7 --- /dev/null +++ b/mobile/apps/locker/lib/services/trash/models/trash_file.dart @@ -0,0 +1,14 @@ +import 'package:locker/services/files/sync/models/file.dart'; + +class TrashFile extends EnteFile { + // time when file was put in the trash for first time + late int createdAt; + + // for non-deleted trash items, updateAt is usually equal to the latest time + // when the file was moved to trash + late int updateAt; + + // time after which will will be deleted from trash & user's storage usage + // will go down + late int deleteBy; +} diff --git a/mobile/apps/locker/lib/services/trash/models/trash_item_request.dart b/mobile/apps/locker/lib/services/trash/models/trash_item_request.dart new file mode 100644 index 0000000000..b4169f738d --- /dev/null +++ b/mobile/apps/locker/lib/services/trash/models/trash_item_request.dart @@ -0,0 +1,22 @@ +class TrashRequest { + final int fileID; + final int collectionID; + + TrashRequest(this.fileID, this.collectionID); + + factory TrashRequest.fromJson(Map json) { + return TrashRequest(json['fileID'], json['collectionID']); + } + + Map toJson() { + final Map data = {}; + data['fileID'] = fileID; + data['collectionID'] = collectionID; + return data; + } + + @override + String toString() { + return 'TrashItemRequest{fileID: $fileID, collectionID: $collectionID}'; + } +} diff --git a/mobile/apps/locker/lib/services/trash/trash_db.dart b/mobile/apps/locker/lib/services/trash/trash_db.dart new file mode 100644 index 0000000000..7557e3426a --- /dev/null +++ b/mobile/apps/locker/lib/services/trash/trash_db.dart @@ -0,0 +1,173 @@ +import "package:ente_base/models/database.dart"; +import 'package:locker/services/trash/models/trash_file.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sqflite/sqflite.dart'; + +class TrashDB extends EnteBaseDatabase { + TrashDB._privateConstructor(); + + static final TrashDB instance = TrashDB._privateConstructor(); + + Database? _database; + + static const String _trashTable = 'trash_files'; + + Future init() async { + _database = await _initDatabase(); + } + + Future _initDatabase() async { + final documentsDirectory = await getApplicationDocumentsDirectory(); + final path = join(documentsDirectory.path, 'trash.db'); + + return await openDatabase( + path, + version: 1, + onCreate: _createTables, + ); + } + + Future _createTables(Database db, int version) async { + await db.execute(''' + CREATE TABLE $_trashTable ( + uploaded_file_id INTEGER PRIMARY KEY, + local_path TEXT, + owner_id INTEGER, + collection_id INTEGER, + title TEXT, + creation_time INTEGER, + modification_time INTEGER, + updation_time INTEGER, + added_time INTEGER, + hash TEXT, + metadata_version INTEGER, + encrypted_key TEXT, + key_decryption_nonce TEXT, + file_decryption_header TEXT, + thumbnail_decryption_header TEXT, + metadata_decryption_header TEXT, + file_size INTEGER, + m_md_encoded_json TEXT, + m_md_version INTEGER, + pub_mmd_encoded_json TEXT, + pub_mmd_version INTEGER, + created_at INTEGER NOT NULL, + update_at INTEGER NOT NULL, + delete_by INTEGER NOT NULL + ) + '''); + } + + Database get _db { + if (_database == null) { + throw Exception('TrashDB not initialized. Call init() first.'); + } + return _database!; + } + + Future insertMultiple(List trashFiles) async { + final batch = _db.batch(); + + for (final trashFile in trashFiles) { + batch.insert( + _trashTable, + _trashFileToMap(trashFile), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + await batch.commit(); + } + + Future delete(List uploadedFileIDs) async { + final batch = _db.batch(); + + for (final uploadedFileID in uploadedFileIDs) { + batch.delete( + _trashTable, + where: 'uploaded_file_id = ?', + whereArgs: [uploadedFileID], + ); + } + + await batch.commit(); + } + + Future> getAllTrashFiles() async { + final result = await _db.query(_trashTable); + return result.map((row) => _mapToTrashFile(row)).toList(); + } + + @override + Future clearTable() async { + await _db.delete(_trashTable); + } + + Map _trashFileToMap(TrashFile trashFile) { + return { + 'uploaded_file_id': trashFile.uploadedFileID!, + 'local_path': trashFile.localPath, + 'owner_id': trashFile.ownerID, + 'collection_id': trashFile.collectionID, + 'title': trashFile.title, + 'creation_time': trashFile.creationTime, + 'modification_time': trashFile.modificationTime, + 'updation_time': trashFile.updationTime, + 'added_time': trashFile.addedTime, + 'hash': trashFile.hash, + 'metadata_version': trashFile.metadataVersion, + 'encrypted_key': trashFile.encryptedKey, + 'key_decryption_nonce': trashFile.keyDecryptionNonce, + 'file_decryption_header': trashFile.fileDecryptionHeader, + 'thumbnail_decryption_header': trashFile.thumbnailDecryptionHeader, + 'metadata_decryption_header': trashFile.metadataDecryptionHeader, + 'file_size': trashFile.fileSize, + 'm_md_encoded_json': trashFile.mMdEncodedJson, + 'm_md_version': trashFile.mMdVersion, + 'pub_mmd_encoded_json': trashFile.pubMmdEncodedJson, + 'pub_mmd_version': trashFile.pubMmdVersion, + 'created_at': trashFile.createdAt, + 'update_at': trashFile.updateAt, + 'delete_by': trashFile.deleteBy, + }; + } + + TrashFile _mapToTrashFile(Map map) { + final trashFile = TrashFile(); + + trashFile.localPath = map['local_path']; + trashFile.uploadedFileID = map['uploaded_file_id']; + trashFile.ownerID = map['owner_id']; + trashFile.collectionID = map['collection_id']; + trashFile.title = map['title']; + trashFile.creationTime = map['creation_time']; + trashFile.modificationTime = map['modification_time']; + trashFile.updationTime = map['updation_time']; + trashFile.addedTime = map['added_time']; + trashFile.hash = map['hash']; + trashFile.metadataVersion = map['metadata_version']; + trashFile.encryptedKey = map['encrypted_key']; + trashFile.keyDecryptionNonce = map['key_decryption_nonce']; + trashFile.fileDecryptionHeader = map['file_decryption_header']; + trashFile.thumbnailDecryptionHeader = map['thumbnail_decryption_header']; + trashFile.metadataDecryptionHeader = map['metadata_decryption_header']; + trashFile.fileSize = map['file_size']; + trashFile.mMdEncodedJson = map['m_md_encoded_json']; + trashFile.mMdVersion = map['m_md_version'] ?? 0; + trashFile.pubMmdEncodedJson = map['pub_mmd_encoded_json']; + trashFile.pubMmdVersion = map['pub_mmd_version'] ?? 1; + + // TrashFile specific fields + trashFile.createdAt = map['created_at']; + trashFile.updateAt = map['update_at']; + trashFile.deleteBy = map['delete_by']; + + return trashFile; + } + + Future close() async { + await _database?.close(); + _database = null; + } +} diff --git a/mobile/apps/locker/lib/services/trash/trash_service.dart b/mobile/apps/locker/lib/services/trash/trash_service.dart new file mode 100644 index 0000000000..a6ef50f6b9 --- /dev/null +++ b/mobile/apps/locker/lib/services/trash/trash_service.dart @@ -0,0 +1,279 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; + +import 'package:dio/dio.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import "package:ente_events/event_bus.dart"; +import "package:ente_events/models/signed_in_event.dart"; +import 'package:ente_network/network.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/collections/models/collection_file_item.dart'; +import "package:locker/services/configuration.dart"; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/services/files/sync/models/file_magic.dart'; +import 'package:locker/services/trash/models/trash_file.dart'; +import 'package:locker/services/trash/trash_db.dart'; +import "package:locker/utils/crypto_helper.dart"; +import 'package:logging/logging.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class TrashService { + TrashService._privateConstructor(); + static final TrashService instance = TrashService._privateConstructor(); + + static const kLastTrashSyncTime = "last_trash_sync_time"; + final _logger = Logger("TrashService"); + late SharedPreferences _prefs; + late Dio _enteDio; + late TrashDB _trashDB; + + Future init(SharedPreferences preferences) async { + _prefs = preferences; + _enteDio = Network.instance.enteDio; + _trashDB = TrashDB.instance; + + if (Configuration.instance.hasConfiguredAccount()) { + unawaited(syncTrash()); + } else { + Bus.instance.on().listen((event) { + _logger.info("User signed in, starting initial trash sync."); + unawaited(syncTrash()); + }); + } + } + + Future syncTrash() async { + final lastSyncTime = _getSyncTime(); + _logger.fine('sync trash sinceTime : $lastSyncTime'); + final diff = await getTrashFilesDiff(lastSyncTime); + if (diff.trashedFiles.isNotEmpty) { + _logger.fine("inserting ${diff.trashedFiles.length} items in trash"); + await _trashDB.insertMultiple(diff.trashedFiles); + } + if (diff.deletedUploadIDs.isNotEmpty) { + _logger.fine("discard ${diff.deletedUploadIDs.length} deleted items"); + await _trashDB.delete(diff.deletedUploadIDs); + } + if (diff.restoredFiles.isNotEmpty) { + _logger.fine("discard ${diff.restoredFiles.length} restored items"); + await _trashDB + .delete(diff.restoredFiles.map((e) => e.uploadedFileID!).toList()); + } + + if (diff.lastSyncedTimeStamp != 0) { + await _setSyncTime(diff.lastSyncedTimeStamp); + } + if (diff.hasMore) { + return syncTrash(); + } + } + + Future> getTrashFiles() async { + return await _trashDB.getAllTrashFiles(); + } + + Future _setSyncTime(int time) async { + return _prefs.setInt(kLastTrashSyncTime, time); + } + + int _getSyncTime() { + return _prefs.getInt(kLastTrashSyncTime) ?? 0; + } + + Future getTrashFilesDiff(int sinceTime) async { + try { + final response = await _enteDio.get( + "/trash/v2/diff", + queryParameters: { + "sinceTime": sinceTime, + }, + ); + int latestUpdatedAtTime = 0; + final trashedFiles = []; + final deletedUploadIDs = []; + final restoredFiles = []; + + final diff = response.data["diff"] as List; + final bool hasMore = response.data["hasMore"] as bool; + final startTime = DateTime.now(); + for (final item in diff) { + final trash = TrashFile(); + trash.createdAt = item['createdAt']; + trash.updateAt = item['updatedAt']; + latestUpdatedAtTime = max(latestUpdatedAtTime, trash.updateAt); + if (item["isDeleted"]) { + deletedUploadIDs.add(item["file"]["id"]); + continue; + } + + trash.deleteBy = item['deleteBy']; + trash.uploadedFileID = item["file"]["id"]; + trash.collectionID = item["file"]["collectionID"]; + trash.updationTime = item["file"]["updationTime"]; + trash.ownerID = item["file"]["ownerID"]; + trash.encryptedKey = item["file"]["encryptedKey"]; + trash.keyDecryptionNonce = item["file"]["keyDecryptionNonce"]; + trash.fileDecryptionHeader = item["file"]["file"]["decryptionHeader"]; + trash.thumbnailDecryptionHeader = + item["file"]["thumbnail"]["decryptionHeader"]; + trash.metadataDecryptionHeader = + item["file"]["metadata"]["decryptionHeader"]; + // TODO: Refactor + final collections = await CollectionService.instance.getCollections(); + final Collection? collection = + collections.where((c) => c.id == trash.collectionID).isNotEmpty + ? collections.firstWhere((c) => c.id == trash.collectionID) + : null; + if (collection == null) { + continue; + } + final collectionKey = + CryptoHelper.instance.getCollectionKey(collection); + final key = CryptoHelper.instance.getFileKey( + trash.encryptedKey!, + trash.keyDecryptionNonce!, + collectionKey, + ); + final encodedMetadata = await CryptoUtil.decryptData( + CryptoUtil.base642bin(item["file"]["metadata"]["encryptedData"]), + key, + CryptoUtil.base642bin(trash.metadataDecryptionHeader!), + ); + final Map metadata = + jsonDecode(utf8.decode(encodedMetadata)); + trash.applyMetadata(metadata); + if (item["file"]['magicMetadata'] != null) { + final utfEncodedMmd = await CryptoUtil.decryptData( + CryptoUtil.base642bin(item["file"]['magicMetadata']['data']), + key, + CryptoUtil.base642bin(item["file"]['magicMetadata']['header']), + ); + trash.mMdEncodedJson = utf8.decode(utfEncodedMmd); + trash.mMdVersion = item["file"]['magicMetadata']['version']; + } + if (item["file"]['pubMagicMetadata'] != null) { + final utfEncodedMmd = await CryptoUtil.decryptData( + CryptoUtil.base642bin(item["file"]['pubMagicMetadata']['data']), + key, + CryptoUtil.base642bin(item["file"]['pubMagicMetadata']['header']), + ); + trash.pubMmdEncodedJson = utf8.decode(utfEncodedMmd); + trash.pubMmdVersion = item["file"]['pubMagicMetadata']['version']; + trash.pubMagicMetadata = + PubMagicMetadata.fromEncodedJson(trash.pubMmdEncodedJson!); + } + if (item['isRestored']) { + restoredFiles.add(trash); + continue; + } + trashedFiles.add(trash); + } + + final endTime = DateTime.now(); + _logger.info( + "time for parsing ${diff.length}: ${Duration( + microseconds: (endTime.microsecondsSinceEpoch - + startTime.microsecondsSinceEpoch), + ).inMilliseconds}", + ); + return TrashDiff( + trashedFiles, + restoredFiles, + deletedUploadIDs, + hasMore, + latestUpdatedAtTime, + ); + } catch (e, s) { + _logger.severe(e, s); + rethrow; + } + } + + Future deleteFromTrash(List files) async { + final params = {}; + final uniqueFileIds = files.map((e) => e.uploadedFileID!).toSet().toList(); + params["fileIDs"] = []; + for (final fileID in uniqueFileIds) { + params["fileIDs"].add(fileID); + } + try { + await _enteDio.post( + "/trash/delete", + data: params, + ); + await _trashDB.delete(uniqueFileIds); + } catch (e, s) { + _logger.severe("failed to delete from trash", e, s); + rethrow; + } + // no need to await on syncing trash from remote + unawaited(syncTrash()); + } + + Future emptyTrash() async { + final params = {}; + params["lastUpdatedAt"] = _getSyncTime(); + try { + await _enteDio.post( + "/trash/empty", + data: params, + ); + await _trashDB.clearTable(); + unawaited(syncTrash()); + } catch (e, s) { + _logger.severe("failed to empty trash", e, s); + rethrow; + } + } + + Future restore(List files, Collection toCollection) async { + final params = {}; + params["collectionID"] = toCollection.id; + final toCollectionKey = + CryptoHelper.instance.getCollectionKey(toCollection); + params["files"] = []; + for (final file in files) { + final fileKey = await CollectionService.instance.getFileKey(file); + file.collectionID = toCollection.id; + final encryptedKeyData = CryptoUtil.encryptSync(fileKey, toCollectionKey); + final encryptedKey = + CryptoUtil.bin2base64(encryptedKeyData.encryptedData!); + final keyDecryptionNonce = CryptoUtil.bin2base64(encryptedKeyData.nonce!); + params["files"].add( + CollectionFileItem( + file.uploadedFileID!, + encryptedKey, + keyDecryptionNonce, + ).toMap(), + ); + } + try { + await _enteDio.post( + "/collections/restore-files", + data: params, + ); + await _trashDB.delete(files.map((e) => e.uploadedFileID!).toList()); + // Force reload home gallery to pull in the restored files + } catch (e, s) { + _logger.severe("failed to restore files", e, s); + rethrow; + } + } +} + +class TrashDiff { + final List trashedFiles; + final List restoredFiles; + final List deletedUploadIDs; + final bool hasMore; + final int lastSyncedTimeStamp; + TrashDiff( + this.trashedFiles, + this.restoredFiles, + this.deletedUploadIDs, + this.hasMore, + this.lastSyncedTimeStamp, + ); +} diff --git a/mobile/apps/locker/lib/ui/components/collection_selection_widget.dart b/mobile/apps/locker/lib/ui/components/collection_selection_widget.dart new file mode 100644 index 0000000000..b263ddc960 --- /dev/null +++ b/mobile/apps/locker/lib/ui/components/collection_selection_widget.dart @@ -0,0 +1,250 @@ +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/utils/collection_actions.dart'; + +class CollectionSelectionWidget extends StatefulWidget { + final List collections; + final Set selectedCollectionIds; + final Function(int) onToggleCollection; + final Function(List)? onCollectionsUpdated; + + const CollectionSelectionWidget({ + super.key, + required this.collections, + required this.selectedCollectionIds, + required this.onToggleCollection, + this.onCollectionsUpdated, + }); + + @override + State createState() => + _CollectionSelectionWidgetState(); +} + +class _CollectionSelectionWidgetState extends State { + List _availableCollections = []; + + @override + void initState() { + super.initState(); + _availableCollections = List.from(widget.collections); + } + + @override + void didUpdateWidget(CollectionSelectionWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.collections != widget.collections) { + _availableCollections = List.from(widget.collections); + } + } + + Future _createNewCollection() async { + final newCollection = await CollectionActions.createCollection(context); + + if (newCollection != null) { + setState(() { + _availableCollections.add(newCollection); + }); + + widget.onToggleCollection(newCollection.id); + + widget.onCollectionsUpdated?.call(_availableCollections); + } + } + + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + final textTheme = getEnteTextTheme(context); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Collections', + style: textTheme.small.copyWith( + color: colorScheme.textBase, + ), + ), + const SizedBox(height: 8), + Container( + height: 150, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: colorScheme.strokeFaint), + ), + child: _availableCollections.isEmpty + ? Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: colorScheme.fillFaint, + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'No collections available', + style: textTheme.body.copyWith( + color: colorScheme.textMuted, + ), + ), + const SizedBox(height: 8), + InkWell( + onTap: _createNewCollection, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: colorScheme.primary300.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: colorScheme.primary500, + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.add, + size: 16, + color: colorScheme.primary700, + ), + const SizedBox(width: 4), + Text( + 'Create collection', + style: textTheme.small.copyWith( + color: colorScheme.primary700, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ) + : Scrollbar( + thumbVisibility: true, + thickness: 6, + radius: const Radius.circular(3), + child: GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 3.5, + crossAxisSpacing: 6, + mainAxisSpacing: 6, + ), + padding: const EdgeInsets.all(6), + itemCount: _availableCollections.length + + 1, // +1 for "Create New" option + itemBuilder: (context, index) { + if (index < _availableCollections.length) { + final collection = _availableCollections[index]; + final isSelected = widget.selectedCollectionIds + .contains(collection.id); + final collectionName = + collection.name ?? 'Unnamed Collection'; + + return InkWell( + onTap: () => widget.onToggleCollection(collection.id), + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 4, + ), + decoration: BoxDecoration( + color: isSelected + ? colorScheme.primary300.withOpacity(0.3) + : colorScheme.fillFaint, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isSelected + ? colorScheme.primary500 + : colorScheme.strokeFaint, + width: isSelected ? 2 : 1, + ), + ), + child: Center( + child: Text( + collectionName, + style: textTheme.small.copyWith( + color: isSelected + ? colorScheme.primary500 + : colorScheme.textBase, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.normal, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + ), + ), + ); + } + + return InkWell( + onTap: _createNewCollection, + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 4, + ), + decoration: BoxDecoration( + color: colorScheme.backgroundElevated, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: colorScheme.strokeFaint.withOpacity(0.5), + width: 1, + style: BorderStyle.solid, + ), + ), + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.add_outlined, + size: 14, + color: colorScheme.textBase, + ), + const SizedBox(width: 4), + Flexible( + child: Text( + 'Collection', + style: textTheme.small.copyWith( + color: colorScheme.textBase, + fontWeight: FontWeight.w400, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ), + ); + }, + ), + ), + ), + ], + ); + } + + List get availableCollections => _availableCollections; +} diff --git a/mobile/apps/locker/lib/ui/components/file_edit_dialog.dart b/mobile/apps/locker/lib/ui/components/file_edit_dialog.dart new file mode 100644 index 0000000000..0851a7acb3 --- /dev/null +++ b/mobile/apps/locker/lib/ui/components/file_edit_dialog.dart @@ -0,0 +1,234 @@ +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/components/text_input_widget.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:locker/l10n/l10n.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/ui/components/collection_selection_widget.dart'; +import 'package:locker/utils/file_icon_utils.dart'; +import 'package:locker/utils/snack_bar_utils.dart'; + +class FileEditDialogResult { + final String title; + final String caption; + final List selectedCollections; + + FileEditDialogResult({ + required this.title, + required this.caption, + required this.selectedCollections, + }); +} + +class FileEditDialog extends StatefulWidget { + final EnteFile file; + final List collections; + + const FileEditDialog({ + super.key, + required this.file, + required this.collections, + }); + + @override + State createState() => _FileEditDialogState(); +} + +class _FileEditDialogState extends State { + final TextEditingController _titleController = TextEditingController(); + final TextEditingController _captionController = TextEditingController(); + final Set _selectedCollectionIds = {}; + List _availableCollections = []; + + @override + void initState() { + super.initState(); + + _availableCollections = List.from(widget.collections); + + _titleController.text = widget.file.displayName; + + _captionController.text = widget.file.caption ?? ''; + + CollectionService.instance + .getCollectionsForFile(widget.file) + .then((fileCollections) { + for (final collection in fileCollections) { + _selectedCollectionIds.add(collection.id); + } + setState(() {}); + }); + } + + @override + void dispose() { + _titleController.dispose(); + _captionController.dispose(); + super.dispose(); + } + + void _toggleCollection(int collectionId) { + setState(() { + if (_selectedCollectionIds.contains(collectionId)) { + _selectedCollectionIds.remove(collectionId); + } else { + _selectedCollectionIds.add(collectionId); + } + }); + } + + void _onCollectionsUpdated(List updatedCollections) { + setState(() { + _availableCollections = updatedCollections; + }); + } + + Future _onCancel() async { + Navigator.of(context).pop(); + } + + Future _onSave() async { + final selectedCollections = _availableCollections + .where((c) => _selectedCollectionIds.contains(c.id)) + .toList(); + + if (selectedCollections.isEmpty) { + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.pleaseSelectAtLeastOneCollection, + ); + return; + } + + final result = FileEditDialogResult( + title: _titleController.text.trim(), + caption: _captionController.text.trim(), + selectedCollections: selectedCollections, + ); + + Navigator.of(context).pop(result); + } + + String get _fileName { + return widget.file.displayName; + } + + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + final textTheme = getEnteTextTheme(context); + + return Dialog( + backgroundColor: colorScheme.backgroundElevated, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Container( + width: 400, + constraints: const BoxConstraints(maxHeight: 600), + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + FileIconUtils.getFileIcon(_fileName), + color: FileIconUtils.getFileIconColor(_fileName), + size: 24, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + _fileName, + style: textTheme.largeBold, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 20), + Text( + 'Title', + style: textTheme.small.copyWith( + color: colorScheme.textBase, + ), + ), + const SizedBox(height: 8), + TextInputWidget( + hintText: context.l10n.fileTitle, + initialValue: _titleController.text, + onChange: (value) => _titleController.text = value, + maxLength: 200, + textCapitalization: TextCapitalization.words, + ), + const SizedBox(height: 16), + Text( + context.l10n.note, + style: textTheme.small.copyWith( + color: colorScheme.textBase, + ), + ), + const SizedBox(height: 8), + TextInputWidget( + hintText: context.l10n.optionalNote, + initialValue: _captionController.text, + onChange: (value) => _captionController.text = value, + maxLength: 500, + textCapitalization: TextCapitalization.sentences, + ), + const SizedBox(height: 16), + CollectionSelectionWidget( + collections: _availableCollections, + selectedCollectionIds: _selectedCollectionIds, + onToggleCollection: _toggleCollection, + onCollectionsUpdated: _onCollectionsUpdated, + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: ButtonWidget( + buttonType: ButtonType.secondary, + labelText: context.l10n.cancel, + onTap: _onCancel, + ), + ), + const SizedBox(width: 12), + Flexible( + child: ButtonWidget( + buttonType: ButtonType.primary, + labelText: context.l10n.save, + onTap: _onSave, + isDisabled: _selectedCollectionIds.isEmpty, + ), + ), + ], + ), + ], + ), + ), + ); + } +} + +Future showFileEditDialog( + BuildContext context, { + required EnteFile file, + required List collections, +}) async { + return showDialog( + context: context, + barrierColor: getEnteColorScheme(context).backdropBase, + builder: (context) => FileEditDialog( + file: file, + collections: collections, + ), + ); +} diff --git a/mobile/apps/locker/lib/ui/components/file_upload_dialog.dart b/mobile/apps/locker/lib/ui/components/file_upload_dialog.dart new file mode 100644 index 0000000000..f1bcdd38c9 --- /dev/null +++ b/mobile/apps/locker/lib/ui/components/file_upload_dialog.dart @@ -0,0 +1,208 @@ +import 'dart:io'; + +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/components/text_input_widget.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:locker/l10n/l10n.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/ui/components/collection_selection_widget.dart'; +import 'package:locker/utils/file_icon_utils.dart'; +import 'package:locker/utils/snack_bar_utils.dart'; +import 'package:path/path.dart' as path; + +class FileUploadDialogResult { + final String note; + final List selectedCollections; + + FileUploadDialogResult({ + required this.note, + required this.selectedCollections, + }); +} + +class FileUploadDialog extends StatefulWidget { + final File file; + final List collections; + final Collection? selectedCollection; + + const FileUploadDialog({ + super.key, + required this.file, + required this.collections, + this.selectedCollection, + }); + + @override + State createState() => _FileUploadDialogState(); +} + +class _FileUploadDialogState extends State { + final TextEditingController _noteController = TextEditingController(); + final Set _selectedCollectionIds = {}; + List _availableCollections = []; + + @override + void initState() { + super.initState(); + _availableCollections = List.from(widget.collections); + if (widget.selectedCollection != null) { + _selectedCollectionIds.add(widget.selectedCollection!.id); + } + } + + @override + void dispose() { + _noteController.dispose(); + super.dispose(); + } + + void _toggleCollection(int collectionId) { + setState(() { + if (_selectedCollectionIds.contains(collectionId)) { + _selectedCollectionIds.remove(collectionId); + } else { + _selectedCollectionIds.add(collectionId); + } + }); + } + + void _onCollectionsUpdated(List updatedCollections) { + setState(() { + _availableCollections = updatedCollections; + }); + } + + Future _onCancel() async { + Navigator.of(context).pop(); + } + + Future _onSave() async { + final selectedCollections = _availableCollections + .where((c) => _selectedCollectionIds.contains(c.id)) + .toList(); + + if (selectedCollections.isEmpty) { + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.pleaseSelectAtLeastOneCollection, + ); + return; + } + + final result = FileUploadDialogResult( + note: _noteController.text.trim(), + selectedCollections: selectedCollections, + ); + + Navigator.of(context).pop(result); + } + + String get _fileName { + return path.basename(widget.file.path); + } + + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + final textTheme = getEnteTextTheme(context); + + return Dialog( + backgroundColor: colorScheme.backgroundElevated, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Container( + width: 400, + constraints: const BoxConstraints(maxHeight: 600), + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + FileIconUtils.getFileIcon(_fileName), + color: FileIconUtils.getFileIconColor(_fileName), + size: 24, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + _fileName, + style: textTheme.largeBold, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 20), + CollectionSelectionWidget( + collections: _availableCollections, + selectedCollectionIds: _selectedCollectionIds, + onToggleCollection: _toggleCollection, + onCollectionsUpdated: _onCollectionsUpdated, + ), + const SizedBox(height: 16), + Text( + 'Note', + style: textTheme.small.copyWith( + color: colorScheme.textBase, + ), + ), + const SizedBox(height: 8), + TextInputWidget( + hintText: context.l10n.optionalNote, + initialValue: _noteController.text, + onChange: (value) => _noteController.text = value, + maxLength: 500, + textCapitalization: TextCapitalization.sentences, + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: ButtonWidget( + buttonType: ButtonType.secondary, + labelText: context.l10n.cancel, + onTap: _onCancel, + ), + ), + const SizedBox(width: 12), + Flexible( + child: ButtonWidget( + buttonType: ButtonType.primary, + labelText: context.l10n.upload, + onTap: _onSave, + isDisabled: _selectedCollectionIds.isEmpty, + ), + ), + ], + ), + ], + ), + ), + ); + } +} + +Future showFileUploadDialog( + BuildContext context, { + required File file, + required List collections, + Collection? selectedCollection, +}) async { + return showDialog( + context: context, + barrierColor: getEnteColorScheme(context).backdropBase, + builder: (context) => FileUploadDialog( + file: file, + collections: collections, + selectedCollection: selectedCollection, + ), + ); +} diff --git a/mobile/apps/locker/lib/ui/components/information_addition_dialog.dart b/mobile/apps/locker/lib/ui/components/information_addition_dialog.dart new file mode 100644 index 0000000000..63021a39a7 --- /dev/null +++ b/mobile/apps/locker/lib/ui/components/information_addition_dialog.dart @@ -0,0 +1,208 @@ +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:locker/l10n/l10n.dart'; + +enum InformationType { + physicalDocument, + emergencyContact, + accountCredential, +} + +class InformationAdditionResult { + final InformationType type; + + InformationAdditionResult({ + required this.type, + }); +} + +class InformationAdditionDialog extends StatefulWidget { + const InformationAdditionDialog({super.key}); + + @override + State createState() => + _InformationAdditionDialogState(); +} + +class _InformationAdditionDialogState extends State { + void _onTypeSelected(InformationType type) { + final result = InformationAdditionResult(type: type); + Navigator.of(context).pop(result); + } + + Future _onCancel() async { + Navigator.of(context).pop(); + } + + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + final textTheme = getEnteTextTheme(context); + + return Dialog( + backgroundColor: colorScheme.backgroundElevated, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Container( + width: 400, + constraints: const BoxConstraints(maxHeight: 600), + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon( + Icons.post_add, + color: Colors.blue, + size: 24, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + context.l10n.addInformation, + style: textTheme.largeBold, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + context.l10n.addInformationDialogSubtitle, + style: textTheme.body.copyWith( + color: colorScheme.textMuted, + ), + ), + const SizedBox(height: 20), + Flexible( + child: SingleChildScrollView( + child: Column( + children: [ + _buildOptionTile( + type: InformationType.physicalDocument, + icon: Icons.description, + title: context.l10n.physicalDocument, + subtitle: context.l10n.physicalDocumentDescription, + colorScheme: colorScheme, + textTheme: textTheme, + ), + const SizedBox(height: 12), + _buildOptionTile( + type: InformationType.emergencyContact, + icon: Icons.emergency, + title: context.l10n.emergencyContact, + subtitle: context.l10n.emergencyContactDescription, + colorScheme: colorScheme, + textTheme: textTheme, + ), + const SizedBox(height: 12), + _buildOptionTile( + type: InformationType.accountCredential, + icon: Icons.key, + title: context.l10n.accountCredential, + subtitle: context.l10n.accountCredentialDescription, + colorScheme: colorScheme, + textTheme: textTheme, + ), + ], + ), + ), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: ButtonWidget( + buttonType: ButtonType.secondary, + labelText: context.l10n.cancel, + onTap: _onCancel, + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildOptionTile({ + required InformationType type, + required IconData icon, + required String title, + required String subtitle, + required colorScheme, + required textTheme, + }) { + return InkWell( + onTap: () => _onTypeSelected(type), + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: colorScheme.fillFaint, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: colorScheme.strokeFaint, + width: 1, + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: colorScheme.fillMuted, + borderRadius: BorderRadius.circular(6), + ), + child: Icon( + icon, + color: colorScheme.textMuted, + size: 20, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: textTheme.body.copyWith( + fontWeight: FontWeight.w600, + color: colorScheme.textBase, + ), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: textTheme.small.copyWith( + color: colorScheme.textMuted, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +Future showInformationAdditionDialog( + BuildContext context, +) async { + return showDialog( + context: context, + barrierColor: getEnteColorScheme(context).backdropBase, + builder: (context) => const InformationAdditionDialog(), + ); +} diff --git a/mobile/apps/locker/lib/ui/components/item_list_view.dart b/mobile/apps/locker/lib/ui/components/item_list_view.dart new file mode 100644 index 0000000000..5da2fe18e2 --- /dev/null +++ b/mobile/apps/locker/lib/ui/components/item_list_view.dart @@ -0,0 +1,1296 @@ +import 'dart:io'; + +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_utils/share_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:locker/l10n/l10n.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/configuration.dart'; +import 'package:locker/services/files/download/file_downloader.dart'; +import 'package:locker/services/files/links/links_service.dart'; +import 'package:locker/services/files/sync/metadata_updater_service.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/ui/components/file_edit_dialog.dart'; +import 'package:locker/ui/pages/collection_page.dart'; +import 'package:locker/utils/collection_actions.dart'; +import 'package:locker/utils/collection_sort_util.dart'; +import 'package:locker/utils/date_time_util.dart'; +import 'package:locker/utils/file_icon_utils.dart'; +import 'package:locker/utils/snack_bar_utils.dart'; +import 'package:open_file/open_file.dart'; + +class OverflowMenuAction { + final String id; + final String label; + final IconData icon; + final void Function( + BuildContext context, + EnteFile? file, + Collection? collection, + ) onTap; + + const OverflowMenuAction({ + required this.id, + required this.label, + required this.icon, + required this.onTap, + }); +} + +class ItemListView extends StatefulWidget { + final List files; + final List collections; + final bool enableSorting; + final Widget? emptyStateWidget; + final List? fileOverflowActions; + final List? collectionOverflowActions; + + const ItemListView({ + super.key, + this.files = const [], + this.collections = const [], + this.enableSorting = false, + this.emptyStateWidget, + this.fileOverflowActions, + this.collectionOverflowActions, + }); + + @override + State createState() => _ItemListViewState(); +} + +class _ItemListViewState extends State { + List<_ListItem> _sortedItems = []; + int _sortColumnIndex = 1; + bool _sortAscending = false; + + @override + void initState() { + super.initState(); + _updateItems(); + } + + @override + void didUpdateWidget(ItemListView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.files != oldWidget.files || + widget.collections != oldWidget.collections) { + _updateItems(); + } + } + + void _updateItems() { + final sortedCollections = + CollectionSortUtil.getSortedCollections(widget.collections); + + _sortedItems = [ + ...sortedCollections.map((c) => _CollectionListItem(c)), + ...widget.files.map((f) => _FileListItem(f)), + ]; + + if (widget.enableSorting) { + _sortItems(_sortColumnIndex, _sortAscending); + } + } + + void _sortItems(int columnIndex, bool ascending) { + if (!widget.enableSorting) return; + + setState(() { + _sortColumnIndex = columnIndex; + _sortAscending = ascending; + + final files = _sortedItems.whereType<_FileListItem>().toList(); + final collections = + _sortedItems.whereType<_CollectionListItem>().toList(); + + switch (columnIndex) { + case 0: + files.sort((a, b) { + final nameA = a.name.toLowerCase(); + final nameB = b.name.toLowerCase(); + return ascending ? nameA.compareTo(nameB) : nameB.compareTo(nameA); + }); + collections.sort((a, b) { + return CollectionSortUtil.compareCollectionsWithFavoritesPriority( + a.collection, + b.collection, + ascending, + ); + }); + break; + case 1: + files.sort((a, b) { + final dateA = a.modificationTime; + final dateB = b.modificationTime; + return ascending ? dateA.compareTo(dateB) : dateB.compareTo(dateA); + }); + collections.sort((a, b) { + return CollectionSortUtil + .compareCollectionsByDateWithFavoritesPriority( + a.collection, + b.collection, + ascending, + ); + }); + break; + } + + _sortedItems = [...collections, ...files]; + }); + } + + @override + Widget build(BuildContext context) { + if (_sortedItems.isEmpty && widget.emptyStateWidget != null) { + return widget.emptyStateWidget!; + } + + if (_sortedItems.isEmpty) { + return _buildDefaultEmptyState(context); + } + + return Card( + margin: EdgeInsets.zero, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.enableSorting) _buildSortingHeader(context), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: _sortedItems.length, + itemBuilder: (context, index) { + final item = _sortedItems[index]; + final isLastItem = index == _sortedItems.length - 1; + return ListItemWidget( + item: item, + collections: widget.collections, + fileOverflowActions: widget.fileOverflowActions, + collectionOverflowActions: widget.collectionOverflowActions, + isLastItem: isLastItem, + ); + }, + ), + ], + ), + ); + } + + Widget _buildDefaultEmptyState(BuildContext context) { + return Center( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.folder_off, + size: 64, + color: Colors.grey, + ), + const SizedBox(height: 16), + Text( + context.l10n.noFilesFound, + style: getEnteTextTheme(context).body.copyWith( + color: Colors.grey, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } + + Widget _buildSortingHeader(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Theme.of(context).dividerColor, + width: 0.1, + ), + ), + ), + child: Row( + children: [ + Expanded( + flex: 2, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => + _sortItems(0, _sortColumnIndex == 0 ? !_sortAscending : true), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Flexible( + child: Text( + context.l10n.name, + style: const TextStyle(fontWeight: FontWeight.bold), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + const SizedBox(width: 8), + if (_sortColumnIndex == 0) + Icon( + _sortAscending + ? Icons.arrow_upward + : Icons.arrow_downward, + size: 16, + color: Theme.of(context).primaryColor, + ), + ], + ), + ), + ), + ), + Expanded( + flex: 1, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => + _sortItems(1, _sortColumnIndex == 1 ? !_sortAscending : true), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + Flexible( + child: Text( + context.l10n.date, + style: const TextStyle(fontWeight: FontWeight.bold), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + const SizedBox(width: 8), + if (_sortColumnIndex == 1) + Icon( + _sortAscending + ? Icons.arrow_upward + : Icons.arrow_downward, + size: 16, + color: Theme.of(context).primaryColor, + ), + ], + ), + ), + ), + ), + const SizedBox(width: 48), + ], + ), + ); + } +} + +abstract class _ListItem { + String get name; + DateTime get modificationTime; + bool get isCollection; + + Collection? get collection => null; + + EnteFile? get file => null; +} + +class _CollectionListItem extends _ListItem { + final Collection _collection; + + _CollectionListItem(this._collection); + + @override + String get name { + return _collection.name ?? 'Unnamed Collection'; + } + + @override + DateTime get modificationTime { + return DateTime.fromMicrosecondsSinceEpoch(_collection.updationTime); + } + + @override + bool get isCollection => true; + + @override + Collection get collection => _collection; +} + +class _FileListItem extends _ListItem { + final EnteFile _file; + + _FileListItem(this._file); + + @override + String get name { + return _file.displayName; + } + + @override + DateTime get modificationTime { + if (_file.updationTime != null) { + return DateTime.fromMicrosecondsSinceEpoch(_file.updationTime!); + } + if (_file.modificationTime != null) { + return DateTime.fromMillisecondsSinceEpoch(_file.modificationTime!); + } + if (_file.creationTime != null) { + return DateTime.fromMillisecondsSinceEpoch(_file.creationTime!); + } + return DateTime.now(); + } + + @override + bool get isCollection => false; + + @override + EnteFile get file => _file; +} + +class ListItemWidget extends StatelessWidget { + // ignore: library_private_types_in_public_api + final _ListItem item; + final List collections; + final List? fileOverflowActions; + final List? collectionOverflowActions; + final bool isLastItem; + + const ListItemWidget({ + super.key, + // ignore: library_private_types_in_public_api + required this.item, + required this.collections, + this.fileOverflowActions, + this.collectionOverflowActions, + this.isLastItem = false, + }); + + @override + Widget build(BuildContext context) { + if (item.isCollection && item.collection != null) { + return CollectionRowWidget( + collection: item.collection!, + overflowActions: collectionOverflowActions, + isLastItem: isLastItem, + ); + } else if (!item.isCollection && item.file != null) { + return FileRowWidget( + file: item.file!, + collections: collections, + overflowActions: fileOverflowActions, + isLastItem: isLastItem, + ); + } else { + return Container( + padding: const EdgeInsets.all(16), + child: Text(context.l10n.unknownItemType), + ); + } + } +} + +class CollectionRowWidget extends StatelessWidget { + final Collection collection; + final List? overflowActions; + final bool isLastItem; + + const CollectionRowWidget({ + super.key, + required this.collection, + this.overflowActions, + this.isLastItem = false, + }); + + @override + Widget build(BuildContext context) { + final updateTime = + DateTime.fromMicrosecondsSinceEpoch(collection.updationTime); + + return InkWell( + onTap: () => _openCollection(context), + child: Container( + padding: EdgeInsets.fromLTRB(16.0, 2, 16.0, isLastItem ? 8 : 2), + decoration: BoxDecoration( + border: isLastItem + ? null + : Border( + bottom: BorderSide( + color: Theme.of(context).dividerColor.withOpacity(0.3), + width: 0.5, + ), + ), + ), + child: Row( + children: [ + Expanded( + flex: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.folder_open, + color: collection.type == CollectionType.favorites + ? getEnteColorScheme(context).primary500 + : Colors.grey, + size: 20, + ), + const SizedBox(width: 12), + Flexible( + child: Text( + collection.name ?? 'Unnamed Collection', + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: getEnteTextTheme(context).body, + ), + ), + ], + ), + ], + ), + ), + Expanded( + flex: 1, + child: Text( + formatDate(context, updateTime), + style: getEnteTextTheme(context).small.copyWith( + color: Theme.of(context).textTheme.bodySmall?.color, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + PopupMenuButton( + onSelected: (value) => _handleMenuAction(context, value), + icon: const Icon( + Icons.more_vert, + size: 20, + ), + itemBuilder: (BuildContext context) { + if (overflowActions != null && overflowActions!.isNotEmpty) { + return overflowActions! + .map( + (action) => PopupMenuItem( + value: action.id, + child: Row( + children: [ + Icon(action.icon, size: 16), + const SizedBox(width: 8), + Text(action.label), + ], + ), + ), + ) + .toList(); + } else { + return [ + PopupMenuItem( + value: 'edit', + child: Row( + children: [ + const Icon(Icons.edit, size: 16), + const SizedBox(width: 8), + Text(context.l10n.edit), + ], + ), + ), + PopupMenuItem( + value: 'delete', + child: Row( + children: [ + const Icon(Icons.delete, size: 16), + const SizedBox(width: 8), + Text(context.l10n.delete), + ], + ), + ), + ]; + } + }, + ), + ], + ), + ), + ); + } + + void _handleMenuAction(BuildContext context, String action) { + if (overflowActions != null && overflowActions!.isNotEmpty) { + final customAction = overflowActions!.firstWhere( + (a) => a.id == action, + orElse: () => throw StateError('Action not found'), + ); + customAction.onTap(context, null, collection); + } else { + switch (action) { + case 'edit': + _editCollection(context); + break; + case 'delete': + _deleteCollection(context); + break; + } + } + } + + void _editCollection(BuildContext context) { + CollectionActions.editCollection(context, collection); + } + + void _deleteCollection(BuildContext context) { + CollectionActions.deleteCollection(context, collection); + } + + void _openCollection(BuildContext context) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => CollectionPage(collection: collection), + ), + ); + } +} + +class FileRowWidget extends StatelessWidget { + final EnteFile file; + final List collections; + final List? overflowActions; + final bool isLastItem; + + const FileRowWidget({ + super.key, + required this.file, + required this.collections, + this.overflowActions, + this.isLastItem = false, + }); + + @override + Widget build(BuildContext context) { + final updateTime = file.updationTime != null + ? DateTime.fromMicrosecondsSinceEpoch(file.updationTime!) + : (file.modificationTime != null + ? DateTime.fromMillisecondsSinceEpoch(file.modificationTime!) + : (file.creationTime != null + ? DateTime.fromMillisecondsSinceEpoch(file.creationTime!) + : DateTime.now())); + + return InkWell( + onTap: () => _openFile(context), + child: Container( + padding: EdgeInsets.fromLTRB(16.0, 2, 16.0, isLastItem ? 8 : 2), + decoration: BoxDecoration( + border: isLastItem + ? null + : Border( + bottom: BorderSide( + color: Theme.of(context).dividerColor.withOpacity(0.3), + width: 0.5, + ), + ), + ), + child: Row( + children: [ + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.only(right: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + FileIconUtils.getFileIcon(file.displayName), + color: + FileIconUtils.getFileIconColor(file.displayName), + size: 20, + ), + const SizedBox(width: 12), + Flexible( + child: Text( + file.displayName, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: getEnteTextTheme(context).body, + ), + ), + ], + ), + ], + ), + ), + ), + Expanded( + flex: 1, + child: Text( + formatDate(context, updateTime), + style: getEnteTextTheme(context).small.copyWith( + color: Theme.of(context).textTheme.bodySmall?.color, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + PopupMenuButton( + onSelected: (value) => _handleMenuAction(context, value), + icon: const Icon( + Icons.more_vert, + size: 20, + ), + itemBuilder: (BuildContext context) { + if (overflowActions != null && overflowActions!.isNotEmpty) { + return overflowActions! + .map( + (action) => PopupMenuItem( + value: action.id, + child: Row( + children: [ + Icon(action.icon, size: 16), + const SizedBox(width: 8), + Text(action.label), + ], + ), + ), + ) + .toList(); + } else { + return [ + PopupMenuItem( + value: 'edit', + child: Row( + children: [ + const Icon(Icons.edit, size: 16), + const SizedBox(width: 8), + Text(context.l10n.edit), + ], + ), + ), + PopupMenuItem( + value: 'share_link', + child: Row( + children: [ + const Icon(Icons.share, size: 16), + const SizedBox(width: 8), + Text(context.l10n.share), + ], + ), + ), + PopupMenuItem( + value: 'delete', + child: Row( + children: [ + const Icon(Icons.delete, size: 16), + const SizedBox(width: 8), + Text(context.l10n.delete), + ], + ), + ), + ]; + } + }, + ), + ], + ), + ), + ); + } + + void _handleMenuAction(BuildContext context, String action) { + if (overflowActions != null && overflowActions!.isNotEmpty) { + final customAction = overflowActions!.firstWhere( + (a) => a.id == action, + orElse: () => throw StateError('Action not found'), + ); + customAction.onTap(context, file, null); + } else { + switch (action) { + case 'edit': + _showEditDialog(context); + break; + case 'share_link': + _shareLink(context); + break; + case 'delete': + _showDeleteConfirmationDialog(context); + break; + } + } + } + + Future _shareLink(BuildContext context) async { + final dialog = createProgressDialog( + context, + context.l10n.creatingShareLink, + isDismissible: false, + ); + + try { + await dialog.show(); + + // Get or create the share link + final shareableLink = await LinksService.instance.getOrCreateLink(file); + + await dialog.hide(); + + // Show the link dialog with copy and delete options + if (context.mounted) { + await _showShareLinkDialog( + context, + shareableLink.fullURL!, + shareableLink.linkID, + ); + } + } catch (e) { + await dialog.hide(); + + if (context.mounted) { + SnackBarUtils.showWarningSnackBar( + context, + '${context.l10n.failedToCreateShareLink}: ${e.toString()}', + ); + } + } + } + + Future _showShareLinkDialog( + BuildContext context, + String url, + String linkID, + ) async { + final colorScheme = getEnteColorScheme(context); + final textTheme = getEnteTextTheme(context); + // Capture the root context (with Scaffold) before showing dialog + final rootContext = context; + + await showDialog( + context: context, + builder: (BuildContext dialogContext) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + title: Text( + dialogContext.l10n.share, + style: textTheme.largeBold, + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + dialogContext.l10n.shareThisLink, + style: textTheme.body, + ), + const SizedBox(height: 20), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colorScheme.fillFaint, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: colorScheme.strokeFaint), + ), + child: Row( + children: [ + Expanded( + child: SelectableText( + url, + style: textTheme.small, + ), + ), + const SizedBox(width: 8), + _CopyButton( + url: url, + ), + ], + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () async { + Navigator.of(context).pop(); + await _deleteShareLink(rootContext, file.uploadedFileID!); + }, + child: Text( + dialogContext.l10n.deleteLink, + style: + textTheme.body.copyWith(color: colorScheme.warning500), + ), + ), + TextButton( + onPressed: () async { + Navigator.of(dialogContext).pop(); + // Use system share sheet to share the URL + await shareText( + url, + context: rootContext, + ); + }, + child: Text( + dialogContext.l10n.shareLink, + style: + textTheme.body.copyWith(color: colorScheme.primary500), + ), + ), + ], + ); + }, + ); + }, + ); + } + + Future _deleteShareLink(BuildContext context, int fileID) async { + final result = await showChoiceDialog( + context, + title: context.l10n.deleteShareLinkDialogTitle, + body: context.l10n.deleteShareLinkConfirmation, + firstButtonLabel: context.l10n.delete, + secondButtonLabel: context.l10n.cancel, + firstButtonType: ButtonType.critical, + isCritical: true, + ); + if (result?.action == ButtonAction.first && context.mounted) { + final dialog = createProgressDialog( + context, + context.l10n.deletingShareLink, + isDismissible: false, + ); + + try { + await dialog.show(); + await LinksService.instance.deleteLink(fileID); + await dialog.hide(); + + if (context.mounted) { + SnackBarUtils.showInfoSnackBar( + context, + context.l10n.shareLinkDeletedSuccessfully, + ); + } + } catch (e) { + await dialog.hide(); + + if (context.mounted) { + SnackBarUtils.showWarningSnackBar( + context, + '${context.l10n.failedToDeleteShareLink}: ${e.toString()}', + ); + } + } + } + } + + Future _showDeleteConfirmationDialog(BuildContext context) async { + final result = await showChoiceDialog( + context, + title: context.l10n.deleteFile, + body: context.l10n.deleteFileConfirmation(file.displayName), + firstButtonLabel: context.l10n.delete, + secondButtonLabel: context.l10n.cancel, + firstButtonType: ButtonType.critical, + isCritical: true, + ); + + if (result?.action == ButtonAction.first && context.mounted) { + await _deleteFile(context); + } + } + + Future _deleteFile(BuildContext context) async { + final dialog = createProgressDialog( + context, + context.l10n.deletingFile, + isDismissible: false, + ); + + try { + await dialog.show(); + + final collections = + await CollectionService.instance.getCollectionsForFile(file); + if (collections.isNotEmpty) { + await CollectionService.instance.trashFile(file, collections.first); + } + + await dialog.hide(); + + SnackBarUtils.showInfoSnackBar( + context, + context.l10n.fileDeletedSuccessfully, + ); + } catch (e) { + await dialog.hide(); + + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.failedToDeleteFile(e.toString()), + ); + } + } + + Future _showEditDialog(BuildContext context) async { + final allCollections = await CollectionService.instance.getCollections(); + allCollections.removeWhere( + (c) => c.type == CollectionType.uncategorized, + ); + + final result = await showFileEditDialog( + context, + file: file, + collections: allCollections, + ); + + if (result != null && context.mounted) { + List currentCollections; + try { + currentCollections = + await CollectionService.instance.getCollectionsForFile(file); + } catch (e) { + currentCollections = []; + } + + final currentCollectionsSet = currentCollections.toSet(); + + final newCollectionsSet = result.selectedCollections.toSet(); + + final collectionsToAdd = + newCollectionsSet.difference(currentCollectionsSet).toList(); + + final collectionsToRemove = + currentCollectionsSet.difference(newCollectionsSet).toList(); + + final currentTitle = file.displayName; + final currentCaption = file.caption ?? ''; + final hasMetadataChanged = + result.title != currentTitle || result.caption != currentCaption; + + if (hasMetadataChanged || currentCollectionsSet != newCollectionsSet) { + final dialog = createProgressDialog( + context, + context.l10n.pleaseWait, + isDismissible: false, + ); + await dialog.show(); + + try { + final List> apiCalls = []; + for (final collection in collectionsToAdd) { + apiCalls.add( + CollectionService.instance.addToCollection(collection, file), + ); + } + await Future.wait(apiCalls); + apiCalls.clear(); + + for (final collection in collectionsToRemove) { + apiCalls.add( + CollectionService.instance + .move(file, collection, newCollectionsSet.first), + ); + } + if (hasMetadataChanged) { + apiCalls.add( + MetadataUpdaterService.instance + .editFileNameAndCaption(file, result.title, result.caption), + ); + } + await Future.wait(apiCalls); + + await dialog.hide(); + + SnackBarUtils.showInfoSnackBar( + context, + context.l10n.fileUpdatedSuccessfully, + ); + } catch (e) { + await dialog.hide(); + + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.failedToUpdateFile(e.toString()), + ); + } + } else { + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.noChangesWereMade, + ); + } + } + } + + Future _openFile(BuildContext context) async { + if (file.localPath != null) { + final localFile = File(file.localPath!); + if (await localFile.exists()) { + await _launchFile(context, localFile, file.displayName); + return; + } + } + + final String cachedFilePath = + "${Configuration.instance.getCacheDirectory()}${file.displayName}"; + final File cachedFile = File(cachedFilePath); + if (await cachedFile.exists()) { + await _launchFile(context, cachedFile, file.displayName); + return; + } + + final dialog = createProgressDialog( + context, + context.l10n.downloading, + isDismissible: false, + ); + + try { + await dialog.show(); + final fileKey = await CollectionService.instance.getFileKey(file); + final decryptedFile = await downloadAndDecrypt( + file, + fileKey, + progressCallback: (downloaded, total) { + if (total > 0 && downloaded >= 0) { + final percentage = + ((downloaded / total) * 100).clamp(0, 100).round(); + dialog.update( + message: context.l10n.downloadingProgress(percentage), + ); + } else { + dialog.update(message: context.l10n.downloading); + } + }, + shouldUseCache: true, + ); + + await dialog.hide(); + + if (decryptedFile != null) { + await _launchFile(context, decryptedFile, file.displayName); + } else { + await showErrorDialog( + context, + context.l10n.downloadFailed, + context.l10n.failedToDownloadOrDecrypt, + ); + } + } catch (e) { + await dialog.hide(); + await showErrorDialog( + context, + context.l10n.errorOpeningFile, + context.l10n.errorOpeningFileMessage(e.toString()), + ); + } + } + + Future _launchFile( + BuildContext context, + File file, + String fileName, + ) async { + try { + await OpenFile.open(file.path); + } catch (e) { + await showErrorDialog( + context, + context.l10n.errorOpeningFile, + context.l10n.couldNotOpenFile(e.toString()), + ); + } + } +} + +class _CopyButton extends StatefulWidget { + final String url; + + const _CopyButton({ + required this.url, + }); + + @override + State<_CopyButton> createState() => _CopyButtonState(); +} + +class _CopyButtonState extends State<_CopyButton> { + bool _isCopied = false; + + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + + return IconButton( + onPressed: () async { + await Clipboard.setData(ClipboardData(text: widget.url)); + setState(() { + _isCopied = true; + }); + // Reset the state after 2 seconds + Future.delayed(const Duration(seconds: 2), () { + if (mounted) { + setState(() { + _isCopied = false; + }); + } + }); + }, + icon: Icon( + _isCopied ? Icons.check : Icons.copy, + size: 16, + color: _isCopied ? colorScheme.primary500 : colorScheme.primary500, + ), + iconSize: 16, + constraints: const BoxConstraints(), + padding: const EdgeInsets.all(4), + tooltip: _isCopied + ? context.l10n.linkCopiedToClipboard + : context.l10n.copyLink, + ); + } +} + +class FileListViewHelpers { + static Widget createSearchEmptyState({ + required String searchQuery, + String? message, + }) { + return Center( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.search_off, + size: 64, + color: Colors.grey, + ), + const SizedBox(height: 16), + Text( + message ?? 'No results found for "$searchQuery"', + style: const TextStyle( + color: Colors.grey, + fontSize: 16, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + const Text( + 'Try adjusting your search query', + style: TextStyle( + color: Colors.grey, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } + + static Widget createSearchEverywhereFooter({ + required String searchQuery, + required VoidCallback onTap, + BuildContext? context, + }) { + return Container( + margin: const EdgeInsets.all(16.0), + child: Card( + elevation: 2, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8.0), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Icon( + Icons.search, + color: context != null + ? Theme.of(context).primaryColor + : Colors.blue, + size: 24, + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Search everywhere for "$searchQuery"', + style: context != null + ? getEnteTextTheme(context).large.copyWith( + fontWeight: FontWeight.w500, + color: Theme.of(context).primaryColor, + ) + : const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.blue, + ), + ), + const SizedBox(height: 4), + Text( + 'Search across all collections and files', + style: context != null + ? getEnteTextTheme(context).body.copyWith( + color: Colors.grey[600], + ) + : TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Icon( + Icons.arrow_forward_ios, + color: Colors.grey[400], + size: 16, + ), + ], + ), + ), + ), + ), + ); + } +} + +class FileDataTable extends StatelessWidget { + final List files; + final Function(EnteFile)? onFileTap; + final bool enableSorting; + + const FileDataTable({ + super.key, + required this.files, + this.onFileTap, + this.enableSorting = false, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + child: const Text( + 'FileDataTable is deprecated. Use FileListView instead.', + style: TextStyle(color: Colors.red), + ), + ); + } +} diff --git a/mobile/apps/locker/lib/ui/components/recents_section_widget.dart b/mobile/apps/locker/lib/ui/components/recents_section_widget.dart new file mode 100644 index 0000000000..ed30e5fbf8 --- /dev/null +++ b/mobile/apps/locker/lib/ui/components/recents_section_widget.dart @@ -0,0 +1,462 @@ +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/ui/components/item_list_view.dart'; + +class RecentsSectionWidget extends StatefulWidget { + final List collections; + final List recentFiles; + + const RecentsSectionWidget({ + super.key, + required this.collections, + required this.recentFiles, + }); + + @override + State createState() => _RecentsSectionWidgetState(); +} + +class _RecentsSectionWidgetState extends State { + final Set _selectedCollections = {}; + final List _selectionOrder = []; + List _filteredFilesByCollections = []; + List _availableCollections = []; + late List _originalCollectionOrder; + + @override + void initState() { + super.initState(); + _originalCollectionOrder = List.from(widget.collections); + _availableCollections = List.from(widget.collections); + _updateFilteredFilesByCollections(); + _updateAvailableCollections(); + } + + @override + void didUpdateWidget(RecentsSectionWidget oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.recentFiles != widget.recentFiles || + oldWidget.collections != widget.collections) { + _originalCollectionOrder = List.from(widget.collections); + _updateFilteredFilesByCollections(); + _updateAvailableCollections(); + } + } + + List get _displayedFiles { + if (_selectedCollections.isNotEmpty) { + return _filteredFilesByCollections; + } else { + return widget.recentFiles; + } + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildRecentsHeader(), + const SizedBox(height: 12), + if (widget.collections.isNotEmpty) ...[ + _buildCollectionChips(), + const SizedBox(height: 16), + ], + _buildRecentsTable(), + ], + ); + } + + Widget _buildRecentsHeader() { + return Text( + 'Recents', + style: getEnteTextTheme(context).h3Bold, + ); + } + + Widget _buildCollectionChips() { + final orderedCollections = _getOrderedCollections(); + + if (orderedCollections.isEmpty) { + return SizedBox( + height: 40, + child: Center( + child: Text( + 'No collections to filter by', + style: TextStyle( + color: Colors.grey[600], + fontSize: 14, + ), + ), + ), + ); + } + + return SizedBox( + height: 40, + child: Row( + children: [ + Expanded( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: ListView.builder( + key: ValueKey(orderedCollections.map((c) => c.id).join('-')), + scrollDirection: Axis.horizontal, + itemCount: orderedCollections.length, + itemBuilder: (context, index) { + final collection = orderedCollections[index]; + final isSelected = _selectedCollections.contains(collection); + + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + margin: const EdgeInsets.only(right: 8), + child: _buildCollectionChip(collection, isSelected), + ); + }, + ), + ), + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + switchInCurve: Curves.easeOut, + switchOutCurve: Curves.easeIn, + transitionBuilder: (Widget child, Animation animation) { + return SlideTransition( + position: Tween( + begin: const Offset(1.0, 0.0), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: animation, + curve: Curves.easeOut, + ), + ), + child: FadeTransition( + opacity: animation, + child: child, + ), + ); + }, + child: _selectedCollections.isNotEmpty + ? Container( + key: const ValueKey('clear_button'), + margin: const EdgeInsets.only(left: 8), + child: _buildClearAllButton(), + ) + : const SizedBox.shrink(key: ValueKey('no_clear_button')), + ), + ], + ), + ); + } + + Widget _buildCollectionChip(Collection collection, bool isSelected) { + return AnimatedSize( + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + alignment: Alignment.centerLeft, + child: GestureDetector( + onTap: () => _onCollectionSelected(collection), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: isSelected ? getEnteColorScheme(context).fillMuted : null, + border: Border.all( + color: isSelected + ? getEnteColorScheme(context).strokeBase + : getEnteColorScheme(context).fillFaint, + width: 1, + ), + borderRadius: const BorderRadius.all(Radius.circular(20.0)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + collection.name ?? 'Untitled', + style: getEnteTextTheme(context).mini, + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + switchInCurve: Curves.easeOut, + switchOutCurve: Curves.easeIn, + transitionBuilder: (Widget child, Animation animation) { + return SlideTransition( + position: Tween( + begin: const Offset(1.0, 0.0), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: animation, + curve: Curves.easeOut, + ), + ), + child: FadeTransition( + opacity: animation, + child: child, + ), + ); + }, + child: isSelected + ? Row( + key: const ValueKey('close_button'), + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: 8), + GestureDetector( + onTap: () => _onCollectionSelected(collection), + child: Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + color: getEnteColorScheme(context).strokeBase, + shape: BoxShape.circle, + ), + child: Icon( + Icons.close, + size: 10, + color: getEnteColorScheme(context).backdropBase, + ), + ), + ), + ], + ) + : const SizedBox.shrink(key: ValueKey('no_button')), + ), + ], + ), + ), + ), + ); + } + + Widget _buildClearAllButton() { + return GestureDetector( + onTap: _clearAllSelections, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: getEnteColorScheme(context).fillFaint, + border: Border.all( + color: getEnteColorScheme(context).strokeMuted, + width: 1, + ), + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.clear_all, + size: 14, + color: getEnteColorScheme(context).textMuted, + ), + const SizedBox(width: 4), + Text( + 'Clear', + style: getEnteTextTheme(context).miniMuted, + ), + ], + ), + ), + ); + } + + Widget _buildRecentsTable() { + if (_displayedFiles.isEmpty) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 40), + child: Center( + child: Column( + children: [ + Icon( + Icons.folder_off, + size: 48, + color: Colors.grey[400], + ), + const SizedBox(height: 16), + Text( + 'No common items in selected collections', + style: getEnteTextTheme(context).body.copyWith( + color: Colors.grey[600], + ), + ), + ], + ), + ), + ); + } + + return ItemListView( + files: _displayedFiles, + enableSorting: true, + ); + } + + void _onCollectionSelected(Collection collection) { + HapticFeedback.lightImpact(); + + setState(() { + if (_selectedCollections.contains(collection)) { + _selectedCollections.remove(collection); + _selectionOrder.remove(collection); + } else { + _selectedCollections.add(collection); + _selectionOrder.add(collection); + } + }); + + _updateFilteredFilesByCollections(); + _updateAvailableCollections(); + } + + void _clearAllSelections() { + HapticFeedback.lightImpact(); + + setState(() { + _selectedCollections.clear(); + _selectionOrder.clear(); + }); + + _updateFilteredFilesByCollections(); + _updateAvailableCollections(); + } + + Future _updateAvailableCollections() async { + try { + final collectionsWithCommonFiles = await _getCollectionsWithCommonFiles( + _selectedCollections.toList(), + widget.collections, + ); + + if (mounted) { + setState(() { + _availableCollections = collectionsWithCommonFiles; + }); + } + } catch (e) { + if (mounted) { + setState(() { + _availableCollections = List.from(widget.collections); + }); + } + } + } + + Future _updateFilteredFilesByCollections() async { + if (_selectedCollections.isEmpty) { + _filteredFilesByCollections = []; + return; + } + + final filteredFiles = []; + for (final file in widget.recentFiles) { + try { + final fileCollections = + await CollectionService.instance.getCollectionsForFile(file); + final hasAllSelectedCollections = _selectedCollections.every( + (selectedCollection) => fileCollections.contains(selectedCollection), + ); + if (hasAllSelectedCollections) { + filteredFiles.add(file); + } + } catch (e) { + continue; + } + } + + if (mounted) { + setState(() { + _filteredFilesByCollections = filteredFiles; + }); + } + } + + Future> _getCollectionsWithCommonFiles( + List selectedCollections, + List allCollections, + ) async { + if (selectedCollections.isEmpty) { + return allCollections; + } + + if (selectedCollections.length == allCollections.length) { + return allCollections; + } + + try { + final Map> collectionFileCache = {}; + + Future> getCollectionFileIds(Collection collection) async { + if (collectionFileCache.containsKey(collection.id)) { + return collectionFileCache[collection.id]!; + } + + final files = + await CollectionService.instance.getFilesInCollection(collection); + final fileIds = files + .where((file) => file.uploadedFileID != null) + .map((file) => file.uploadedFileID!) + .toSet(); + + collectionFileCache[collection.id] = fileIds; + return fileIds; + } + + final selectedFileIdSets = await Future.wait( + selectedCollections.map(getCollectionFileIds), + ); + + if (selectedFileIdSets.any((set) => set.isEmpty)) { + return selectedCollections; + } + + final commonFileIds = + selectedFileIdSets.reduce((a, b) => a.intersection(b)); + + if (commonFileIds.isEmpty) { + return selectedCollections; + } + + final result = []; + + for (final collection in allCollections) { + if (selectedCollections + .any((selected) => selected.id == collection.id)) { + result.add(collection); + } else { + final collectionFileIds = await getCollectionFileIds(collection); + if (commonFileIds.any(collectionFileIds.contains)) { + result.add(collection); + } + } + } + + return result; + } catch (e) { + return allCollections; + } + } + + List _getOrderedCollections() { + final orderedCollections = []; + + for (final collection in _selectionOrder) { + if (_availableCollections.contains(collection)) { + orderedCollections.add(collection); + } + } + + for (final collection in _originalCollectionOrder) { + if (_availableCollections.contains(collection) && + !_selectedCollections.contains(collection)) { + orderedCollections.add(collection); + } + } + + return orderedCollections; + } +} diff --git a/mobile/apps/locker/lib/ui/components/search_result_view.dart b/mobile/apps/locker/lib/ui/components/search_result_view.dart new file mode 100644 index 0000000000..6096495a57 --- /dev/null +++ b/mobile/apps/locker/lib/ui/components/search_result_view.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/ui/components/item_list_view.dart'; + +class SearchResultView extends StatelessWidget { + final List collections; + final List files; + final String searchQuery; + final bool enableSorting; + final VoidCallback? onCollectionTap; + final bool isHomePage; + final VoidCallback? onSearchEverywhere; + final bool showCollections; + + const SearchResultView({ + super.key, + required this.collections, + required this.files, + this.searchQuery = '', + this.enableSorting = false, + this.onCollectionTap, + this.isHomePage = false, + this.onSearchEverywhere, + this.showCollections = true, + }); + + @override + Widget build(BuildContext context) { + final displayCollections = showCollections ? collections : []; + + // For non-home pages, show search everywhere option + return Padding( + padding: const EdgeInsets.only(left: 16, right: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ItemListView( + files: files, + collections: displayCollections, + enableSorting: enableSorting, + emptyStateWidget: searchQuery.isNotEmpty + ? FileListViewHelpers.createSearchEmptyState( + searchQuery: searchQuery, + ) + : null, + ), + if (!isHomePage && + onSearchEverywhere != null && + searchQuery.isNotEmpty) + FileListViewHelpers.createSearchEverywhereFooter( + searchQuery: searchQuery, + onTap: onSearchEverywhere!, + context: context, + ), + ], + ), + ); + } +} diff --git a/mobile/apps/locker/lib/ui/mixins/search_mixin.dart b/mobile/apps/locker/lib/ui/mixins/search_mixin.dart new file mode 100644 index 0000000000..e52437cf94 --- /dev/null +++ b/mobile/apps/locker/lib/ui/mixins/search_mixin.dart @@ -0,0 +1,348 @@ +import 'dart:async'; + +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:locker/l10n/l10n.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/files/sync/models/file.dart'; + +class CollectionSearchResult { + final bool matches; + final bool nameMatches; + final List files; + + CollectionSearchResult({ + required this.matches, + required this.nameMatches, + required this.files, + }); +} + +mixin SearchMixin on State { + String _searchQuery = ''; + bool _isSearchActive = false; + bool _isSearching = false; + Timer? _searchDebounceTimer; + final TextEditingController _searchController = TextEditingController(); + final FocusNode _searchFocusNode = FocusNode(); + + List get allCollections; + List get allFiles; + + void onSearchResultsChanged( + List collections, + List files, + ); + + void onSearchStateChanged(bool isActive) {} + + @override + void dispose() { + _searchController.dispose(); + _searchFocusNode.dispose(); + _searchDebounceTimer?.cancel(); + super.dispose(); + } + + Widget buildSearchAction() { + if (_isSearchActive) { + return Flexible( + child: Container( + margin: const EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 8.0), + constraints: const BoxConstraints( + minWidth: 200, + maxWidth: double.infinity, + ), + child: TextField( + controller: _searchController, + focusNode: _searchFocusNode, + autofocus: true, + onChanged: _onSearchChanged, + decoration: InputDecoration( + hintText: context.l10n.searchHint, + hintStyle: TextStyle( + color: Theme.of(context) + .textTheme + .bodyMedium + ?.color + ?.withOpacity(0.6), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20.0), + borderSide: BorderSide.none, + ), + filled: true, + fillColor: getEnteColorScheme(context).backdropBase, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + prefixIcon: _isSearching + ? Container( + width: 20, + height: 20, + padding: const EdgeInsets.all(8.0), + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Theme.of(context) + .textTheme + .bodyMedium + ?.color + ?.withOpacity(0.6) ?? + Colors.grey, + ), + ), + ) + : Icon( + Icons.search, + size: 20, + color: Theme.of(context) + .textTheme + .bodyMedium + ?.color + ?.withOpacity(0.6), + ), + ), + style: TextStyle( + fontSize: 14, + color: Theme.of(context).textTheme.bodyMedium?.color, + ), + ), + ), + ); + } else { + return IconButton( + icon: const Icon(Icons.search), + onPressed: _activateSearch, + ); + } + } + + List buildSearchActions() { + if (_isSearchActive) { + return [ + IconButton( + icon: const Icon(Icons.close), + onPressed: _deactivateSearch, + ), + ]; + } + return []; + } + + Widget? buildSearchLeading({Widget? defaultLeading}) { + if (_isSearchActive) { + return null; + } + return defaultLeading; + } + + void _activateSearch() { + setState(() { + _isSearchActive = true; + }); + onSearchStateChanged(true); + WidgetsBinding.instance.addPostFrameCallback((_) { + _searchFocusNode.requestFocus(); + }); + } + + void _deactivateSearch() { + setState(() { + _isSearchActive = false; + _searchQuery = ''; + }); + _searchController.clear(); + onSearchStateChanged(false); + unawaited(_performSearch('')); + } + + void _clearSearch() { + _searchController.clear(); + _onSearchChanged(''); + } + + void _onSearchChanged(String query) { + _searchDebounceTimer?.cancel(); + + if (query.isEmpty && _searchQuery.isNotEmpty) { + setState(() { + _searchQuery = query; + }); + unawaited(_performSearch(query)); + return; + } + + _searchDebounceTimer = Timer(const Duration(milliseconds: 300), () { + if (mounted) { + setState(() { + _searchQuery = query; + }); + unawaited(_performSearch(query)); + } + }); + } + + Future _performSearch(String query) async { + if (!mounted) return; + + setState(() { + _isSearching = true; + }); + + try { + if (query.isEmpty) { + if (mounted) { + onSearchResultsChanged(allCollections, allFiles); + } + return; + } + + final List filteredCollections = []; + final List collectionFiles = []; + + for (final collection in allCollections) { + if (!mounted) return; + final searchResult = await _searchInCollection(collection, query); + if (searchResult.matches) { + filteredCollections.add(collection); + if (searchResult.nameMatches) { + collectionFiles.addAll(searchResult.files); + } + } + } + + final Set addedFileIds = + collectionFiles.map((f) => f.uploadedFileID.toString()).toSet(); + final filteredFiles = allFiles.where((file) { + if (addedFileIds.contains(file.uploadedFileID.toString())) { + return false; + } + return _searchInFile(file, query); + }).toList(); + + final List allFilteredFiles = [ + ...collectionFiles, + ...filteredFiles, + ]; + + if (mounted) { + onSearchResultsChanged(filteredCollections, allFilteredFiles); + } + } catch (e) { + debugPrint('Search error: $e'); + if (mounted) { + onSearchResultsChanged(allCollections, allFiles); + } + } finally { + if (mounted) { + setState(() { + _isSearching = false; + }); + } + } + } + + Future _searchInCollection( + Collection collection, + String query, + ) async { + try { + final files = + await CollectionService.instance.getFilesInCollection(collection); + final collectionNameMatches = _containsQuery( + collection.name ?? '', + query, + ); + final fileMatches = files.any((file) => _searchInFile(file, query)); + + return CollectionSearchResult( + matches: collectionNameMatches || fileMatches, + nameMatches: collectionNameMatches, + files: collectionNameMatches ? files : [], + ); + } catch (e) { + debugPrint('Error searching in collection ${collection.name}: $e'); + final collectionNameMatches = _containsQuery( + collection.name ?? '', + query, + ); + return CollectionSearchResult( + matches: collectionNameMatches, + nameMatches: collectionNameMatches, + files: [], + ); + } + } + + bool _searchInFile(EnteFile file, String query) { + return _containsQuery(file.displayName, query) || + _containsQuery(file.title ?? '', query) || + _containsQuery(file.caption ?? '', query) || + _containsQuery(file.pubMagicMetadata.editedName ?? '', query) || + _containsQuery(file.pubMagicMetadata.uploaderName ?? '', query); + } + + bool _containsQuery(String text, String query) { + if (text.isEmpty) return false; + final lowerText = text.toLowerCase(); + final lowerQuery = query.toLowerCase(); + + if (lowerText.contains(lowerQuery)) { + return true; + } + final words = lowerText.split(RegExp(r'[\s\-_\.]+')); + return words.any((word) => word.startsWith(lowerQuery)); + } + + /// Handle keyboard shortcuts + bool handleKeyEvent(KeyEvent event) { + if (event is KeyDownEvent) { + // Clear search or close search on ESC + if (event.logicalKey == LogicalKeyboardKey.escape) { + if (_isSearchActive) { + if (_searchQuery.isNotEmpty) { + _clearSearch(); + } else { + _deactivateSearch(); + } + return true; + } + } + // Activate search on Ctrl+F (Cmd+F on Mac) + else if (event.logicalKey == LogicalKeyboardKey.keyF && + (HardwareKeyboard.instance.isMetaPressed || + HardwareKeyboard.instance.isControlPressed)) { + if (!_isSearchActive) { + _activateSearch(); + return true; + } + } + } + return false; + } + + String get searchQuery => _searchQuery; + bool get isSearchActive => _isSearchActive; + bool get isSearching => _isSearching; + TextEditingController get searchController => _searchController; + + /// Programmatically activate search with a specific query + void activateSearchWithQuery(String query) { + setState(() { + _isSearchActive = true; + _searchQuery = query; + }); + _searchController.text = query; + onSearchStateChanged(true); + unawaited(_performSearch(query)); + + // Focus the search field after the widget is built + WidgetsBinding.instance.addPostFrameCallback((_) { + _searchFocusNode.requestFocus(); + }); + } +} diff --git a/mobile/apps/locker/lib/ui/pages/all_collections_page.dart b/mobile/apps/locker/lib/ui/pages/all_collections_page.dart new file mode 100644 index 0000000000..f225f4f0e2 --- /dev/null +++ b/mobile/apps/locker/lib/ui/pages/all_collections_page.dart @@ -0,0 +1,408 @@ +import 'dart:async'; + +import 'package:ente_events/event_bus.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:locker/events/collections_updated_event.dart'; +import 'package:locker/l10n/l10n.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/services/trash/trash_service.dart'; +import 'package:locker/ui/components/item_list_view.dart'; +import 'package:locker/ui/components/search_result_view.dart'; +import 'package:locker/ui/mixins/search_mixin.dart'; +import 'package:locker/ui/pages/collection_page.dart'; +import 'package:locker/ui/pages/home_page.dart'; +import 'package:locker/ui/pages/trash_page.dart'; +import 'package:locker/utils/collection_sort_util.dart'; +import 'package:logging/logging.dart'; + +class AllCollectionsPage extends StatefulWidget { + const AllCollectionsPage({super.key}); + + @override + State createState() => _AllCollectionsPageState(); +} + +class _AllCollectionsPageState extends State + with SearchMixin { + List _sortedCollections = []; + List _allCollections = []; + Collection? _uncategorizedCollection; + int? _uncategorizedFileCount; + List _allFiles = []; + bool _isLoading = true; + String? _error; + final _logger = Logger("AllCollectionsPage"); + + @override + List get allCollections => _allCollections; + + @override + List get allFiles => _allFiles; + + @override + void onSearchResultsChanged( + List collections, + List files, + ) { + setState(() { + if (searchQuery.isEmpty) { + final regularCollections = collections + .where((c) => c.type != CollectionType.uncategorized) + .toList(); + _sortedCollections = + CollectionSortUtil.getSortedCollections(regularCollections); + } else { + _sortedCollections = + CollectionSortUtil.getSortedCollections(collections); + } + }); + } + + @override + void initState() { + super.initState(); + _loadCollections(); + Bus.instance.on().listen((event) async { + await _loadCollections(); + }); + } + + Future _loadCollections() async { + setState(() { + _isLoading = true; + _error = null; + }); + + try { + final collections = await CollectionService.instance.getCollections(); + + final regularCollections = []; + Collection? uncategorized; + + for (final collection in collections) { + if (collection.type == CollectionType.uncategorized) { + uncategorized = collection; + } else { + regularCollections.add(collection); + } + } + + CollectionSortUtil.sortCollections(regularCollections); + + _allCollections = List.from(collections); + _sortedCollections = List.from(regularCollections); + _uncategorizedCollection = uncategorized; + _uncategorizedFileCount = uncategorized != null + ? (await CollectionService.instance + .getFilesInCollection(uncategorized)) + .length + : 0; + _allFiles = await CollectionService.instance.getAllFiles(); + + setState(() { + _isLoading = false; + }); + } catch (e) { + _logger.severe("Failed to load collections", e); + setState(() { + _error = 'Failed to load collections: $e'; + _isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return KeyboardListener( + focusNode: FocusNode(), + onKeyEvent: handleKeyEvent, + child: Scaffold( + appBar: AppBar( + leading: buildSearchLeading(), + title: Text(context.l10n.collections), + centerTitle: false, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + foregroundColor: Theme.of(context).textTheme.bodyLarge?.color, + actions: [ + buildSearchAction(), + ...buildSearchActions(), + ], + ), + body: _buildBody(), + ), + ); + } + + Widget _buildBody() { + if (_isLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + if (_error != null) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.error_outline, + color: Colors.red, + size: 64, + ), + const SizedBox(height: 16), + Text( + _error!, + style: const TextStyle(color: Colors.red), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: _loadCollections, + child: Text(context.l10n.retry), + ), + ], + ), + ); + } + + if (isSearchActive) { + return Padding( + padding: const EdgeInsets.only(top: 16), + child: SearchResultView( + collections: _sortedCollections, + files: const [], + searchQuery: searchQuery, + enableSorting: true, + isHomePage: false, + onSearchEverywhere: _searchEverywhere, + ), + ); + } + + if (_sortedCollections.isEmpty) { + if (searchQuery.isNotEmpty) { + return FileListViewHelpers.createSearchEmptyState( + searchQuery: searchQuery, + message: context.l10n.noCollectionsFoundForQuery(searchQuery), + ); + } else { + return Center( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.folder_open, + size: 64, + color: Colors.grey, + ), + const SizedBox(height: 16), + Text( + context.l10n.noCollectionsFound, + style: getEnteTextTheme(context).large.copyWith( + color: Colors.grey, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } + } + + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + if (searchQuery.isNotEmpty) + Container( + padding: const EdgeInsets.only(bottom: 16.0), + alignment: Alignment.centerLeft, + child: Text( + '${_sortedCollections.length} result${_sortedCollections.length == 1 ? '' : 's'} for "$searchQuery"', + style: getEnteTextTheme(context).small.copyWith( + color: Theme.of(context).textTheme.bodySmall?.color, + ), + ), + ), + Flexible( + child: ItemListView( + collections: _sortedCollections, + enableSorting: true, + ), + ), + if (!isSearchActive && _uncategorizedCollection != null) + _buildUncategorizedHook(), + _buildTrashHook(), + ], + ), + ); + } + + Widget _buildTrashHook() { + return Container( + margin: const EdgeInsets.only(top: 8.0, bottom: 16.0), + child: InkWell( + onTap: _openTrash, + borderRadius: BorderRadius.circular(12.0), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface.withOpacity(0.3), + border: Border.all( + color: Theme.of(context).dividerColor.withOpacity(0.5), + width: 0.5, + ), + borderRadius: BorderRadius.circular(12.0), + ), + child: Row( + children: [ + Icon( + Icons.delete_outline, + color: Theme.of(context) + .textTheme + .bodyLarge + ?.color + ?.withOpacity(0.7), + size: 22, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + context.l10n.trash, + style: getEnteTextTheme(context).large.copyWith( + fontWeight: FontWeight.w500, + ), + ), + ), + Icon( + Icons.chevron_right, + color: Theme.of(context) + .textTheme + .bodyMedium + ?.color + ?.withOpacity(0.6), + size: 20, + ), + ], + ), + ), + ), + ); + } + + Future _openTrash() async { + final trashFiles = await TrashService.instance.getTrashFiles(); + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TrashPage(trashFiles: trashFiles), + ), + ); + } + + void _searchEverywhere() { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => HomePage(initialSearchQuery: searchQuery), + ), + (route) => false, + ); + } + + Widget _buildUncategorizedHook() { + if (_uncategorizedCollection == null) return const SizedBox.shrink(); + + return Container( + margin: const EdgeInsets.only(top: 16.0, bottom: 8.0), + child: InkWell( + onTap: () => _openUncategorized(), + borderRadius: BorderRadius.circular(12.0), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface.withOpacity(0.3), + border: Border.all( + color: Theme.of(context).dividerColor.withOpacity(0.5), + width: 0.5, + ), + borderRadius: BorderRadius.circular(12.0), + ), + child: Row( + children: [ + Icon( + Icons.folder_open_outlined, + color: Theme.of(context) + .textTheme + .bodyLarge + ?.color + ?.withOpacity(0.7), + size: 22, + ), + const SizedBox(width: 12), + Expanded( + child: Row( + children: [ + Text( + context.l10n.uncategorized, + style: getEnteTextTheme(context).large.copyWith( + fontWeight: FontWeight.w500, + ), + ), + if (_uncategorizedFileCount! > 0) ...[ + const SizedBox(width: 8), + Text( + '•', + style: getEnteTextTheme(context).small.copyWith( + color: Theme.of(context) + .textTheme + .bodySmall + ?.color + ?.withOpacity(0.5), + ), + ), + const SizedBox(width: 8), + Text( + '${_uncategorizedFileCount!}', + style: getEnteTextTheme(context).small.copyWith( + color: Theme.of(context) + .textTheme + .bodySmall + ?.color + ?.withOpacity(0.7), + ), + ), + ], + ], + ), + ), + Icon( + Icons.chevron_right, + color: Theme.of(context) + .textTheme + .bodyMedium + ?.color + ?.withOpacity(0.6), + size: 20, + ), + ], + ), + ), + ), + ); + } + + Future _openUncategorized() async { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + CollectionPage(collection: _uncategorizedCollection!), + ), + ); + } +} diff --git a/mobile/apps/locker/lib/ui/pages/collection_page.dart b/mobile/apps/locker/lib/ui/pages/collection_page.dart new file mode 100644 index 0000000000..4185890b45 --- /dev/null +++ b/mobile/apps/locker/lib/ui/pages/collection_page.dart @@ -0,0 +1,275 @@ +import 'package:ente_events/event_bus.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:locker/events/collections_updated_event.dart'; +import 'package:locker/l10n/l10n.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/ui/components/item_list_view.dart'; +import 'package:locker/ui/components/search_result_view.dart'; +import 'package:locker/ui/mixins/search_mixin.dart'; +import 'package:locker/ui/pages/home_page.dart'; +import 'package:locker/ui/pages/uploader_page.dart'; +import 'package:locker/utils/collection_actions.dart'; + +class CollectionPage extends UploaderPage { + final Collection collection; + + const CollectionPage({ + super.key, + required this.collection, + }); + + @override + State createState() => _CollectionPageState(); +} + +class _CollectionPageState extends UploaderPageState + with SearchMixin { + late Collection _collection; + List _files = []; + List _filteredFiles = []; + + @override + void onFileUploadComplete() { + CollectionService.instance.getCollections().then((collections) { + setState(() { + _initializeData(collections.where((c) => c.id == _collection.id).first); + }); + }); + } + + @override + List get allCollections => []; + + @override + List get allFiles => _files; + + @override + Collection get selectedCollection => _collection; + + @override + void onSearchResultsChanged( + List collections, List files,) { + setState(() { + _filteredFiles = files; + }); + } + + @override + void onSearchStateChanged(bool isActive) { + if (!isActive) { + setState(() { + _filteredFiles = _files; + }); + } + } + + List get _displayedFiles => + isSearchActive ? _filteredFiles : _files; + + @override + void initState() { + super.initState(); + _initializeData(widget.collection); + Bus.instance.on().listen((event) async { + final collection = (await CollectionService.instance.getCollections()) + .where( + (c) => c.id == widget.collection.id, + ) + .first; + await _initializeData(collection); + }); + } + + Future _initializeData(Collection collection) async { + _collection = collection; + _files = await CollectionService.instance.getFilesInCollection(_collection); + _filteredFiles = _files; + setState(() {}); + } + + Future _deleteCollection() async { + await CollectionActions.deleteCollection( + context, + _collection, + onSuccess: () { + if (mounted) { + Navigator.of(context).pop(); + } + }, + ); + } + + Future _editCollection() async { + await CollectionActions.editCollection( + context, + _collection, + onSuccess: () { + setState(() {}); + }, + ); + } + + @override + Widget build(BuildContext context) { + return KeyboardListener( + focusNode: FocusNode(), + onKeyEvent: handleKeyEvent, + child: Scaffold( + appBar: _buildAppBar(), + body: _buildBody(), + floatingActionButton: + isSearchActive ? const SizedBox.shrink() : _buildFAB(), + ), + ); + } + + PreferredSizeWidget _buildAppBar() { + return AppBar( + leading: buildSearchLeading(), + title: Text( + _collection.name ?? context.l10n.untitled, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + elevation: 0, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + foregroundColor: Theme.of(context).textTheme.bodyLarge?.color, + actions: [ + buildSearchAction(), + ...buildSearchActions(), + _buildMenuButton(), + ], + ); + } + + Widget _buildMenuButton() { + return PopupMenuButton( + icon: const Icon(Icons.more_vert), + onSelected: (value) { + switch (value) { + case 'edit': + _editCollection(); + break; + case 'delete': + _deleteCollection(); + break; + } + }, + itemBuilder: (BuildContext context) { + return [ + PopupMenuItem( + value: 'edit', + child: Row( + children: [ + const Icon(Icons.edit), + const SizedBox(width: 12), + Text(context.l10n.edit), + ], + ), + ), + PopupMenuItem( + value: 'delete', + child: Row( + children: [ + const Icon(Icons.delete, color: Colors.red), + const SizedBox(width: 12), + Text( + context.l10n.delete, + style: const TextStyle(color: Colors.red), + ), + ], + ), + ), + ]; + }, + ); + } + + Widget _buildBody() { + if (isSearchActive) { + return SearchResultView( + collections: const [], // CollectionPage primarily shows files + files: _filteredFiles, + searchQuery: searchQuery, + enableSorting: true, + isHomePage: false, + onSearchEverywhere: _searchEverywhere, + ); + } + + return SingleChildScrollView( + padding: const EdgeInsets.only(left: 16, right: 16), + child: _buildFilesList(), + ); + } + + Widget _buildFilesList() { + return _displayedFiles.isEmpty + ? SizedBox( + height: 400, + child: _buildEmptyState(), + ) + : ItemListView( + key: ValueKey(_displayedFiles.length), + files: _displayedFiles, + enableSorting: true, + ); + } + + Widget _buildEmptyState() { + return Padding( + padding: const EdgeInsets.all(32.0), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + isSearchActive ? Icons.search_off : Icons.folder_off, + size: 64, + color: Colors.grey, + ), + const SizedBox(height: 16), + Text( + isSearchActive + ? 'No files found for "$searchQuery"' + : context.l10n.noFilesFound, + style: getEnteTextTheme(context).large.copyWith( + color: Colors.grey, + ), + textAlign: TextAlign.center, + ), + if (isSearchActive) ...[ + const SizedBox(height: 8), + Text( + 'Try adjusting your search query', + style: getEnteTextTheme(context).body.copyWith( + color: Colors.grey[600], + ), + textAlign: TextAlign.center, + ), + ], + ], + ), + ), + ); + } + + void _searchEverywhere() { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => HomePage(initialSearchQuery: searchQuery), + ), + (route) => false, + ); + } + + Widget _buildFAB() { + return FloatingActionButton( + onPressed: addFile, + tooltip: context.l10n.addFiles, + child: const Icon(Icons.add), + ); + } +} diff --git a/mobile/apps/locker/lib/ui/pages/home_page.dart b/mobile/apps/locker/lib/ui/pages/home_page.dart new file mode 100644 index 0000000000..86aa5b708e --- /dev/null +++ b/mobile/apps/locker/lib/ui/pages/home_page.dart @@ -0,0 +1,785 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import 'package:ente_events/event_bus.dart'; +import 'package:ente_ui/components/buttons/gradient_button.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_utils/email_util.dart'; +import 'package:flutter/material.dart'; +import 'package:listen_sharing_intent/listen_sharing_intent.dart'; +import 'package:locker/events/collections_updated_event.dart'; +import 'package:locker/l10n/l10n.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/ui/components/information_addition_dialog.dart'; +import 'package:locker/ui/components/recents_section_widget.dart'; +import 'package:locker/ui/components/search_result_view.dart'; +import 'package:locker/ui/mixins/search_mixin.dart'; +import 'package:locker/ui/pages/all_collections_page.dart'; +import 'package:locker/ui/pages/collection_page.dart'; +import 'package:locker/ui/pages/uploader_page.dart'; +import 'package:locker/utils/collection_actions.dart'; +import 'package:locker/utils/collection_sort_util.dart'; +import "package:locker/utils/snack_bar_utils.dart"; +import 'package:logging/logging.dart'; + +class HomePage extends UploaderPage { + final String? initialSearchQuery; + + const HomePage({super.key, this.initialSearchQuery}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends UploaderPageState + with TickerProviderStateMixin, SearchMixin { + List _collections = []; + List _filteredCollections = []; + List _recentFiles = []; + List _filteredFiles = []; + Map _collectionFileCounts = {}; + bool _isLoading = true; + String? _error; + final _logger = Logger('HomePage'); + StreamSubscription? _mediaStreamSubscription; + + @override + void onFileUploadComplete() { + _loadCollections(); + } + + @override + List get allCollections => _collections; + + @override + List get allFiles => _recentFiles; + + @override + void onSearchResultsChanged( + List collections, + List files, + ) { + setState(() { + _filteredCollections = + CollectionSortUtil.filterAndSortCollections(collections); + _filteredFiles = files; + }); + } + + @override + void onSearchStateChanged(bool isActive) { + if (!isActive) { + setState(() { + _filteredCollections = + CollectionSortUtil.filterAndSortCollections(_collections); + _filteredFiles = _recentFiles; + }); + } + } + + List get _displayedCollections { + final collections = isSearchActive ? _filteredCollections : _collections; + return CollectionSortUtil.filterAndSortCollections(collections); + } + + final ValueNotifier _isFabOpen = ValueNotifier(false); + late AnimationController _animationController; + late Animation _animation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _animation = Tween( + begin: 0.0, + end: 1.0, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ), + ); + + if (CollectionService.instance.hasCompletedFirstSync()) { + _loadCollections(); + } + + // Initialize sharing functionality to handle shared files + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + // Add a small delay to ensure the app is fully loaded + Future.delayed(const Duration(milliseconds: 1000), () { + if (mounted) { + initializeSharing(); + } + }); + } + }); + + // Activate search if initial query is provided (after collections are loaded) + if (widget.initialSearchQuery != null && + widget.initialSearchQuery!.isNotEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + // Wait a bit more to ensure collections are loaded + Future.delayed(const Duration(milliseconds: 100), () { + if (mounted) { + activateSearchWithQuery(widget.initialSearchQuery!); + } + }); + }); + } + + Bus.instance.on().listen((event) async { + await _loadCollections(); + }); + } + + @override + void dispose() { + _animationController.dispose(); + _isFabOpen.dispose(); + disposeSharing(); + super.dispose(); + } + + void initializeSharing() { + _logger.info('Initializing sharing functionality...'); + + try { + _mediaStreamSubscription = + ReceiveSharingIntent.instance.getMediaStream().listen( + (List value) { + _logger + .info('Received shared media files via stream: ${value.length}'); + for (var file in value) { + _logger.info('Shared file: ${file.path}, type: ${file.type}'); + } + if (value.isNotEmpty) { + _handleSharedFiles(value); + } + }, + onError: (err) { + _logger.severe('Error receiving shared media: $err'); + }, + ); + + _logger.info('Media stream subscription created successfully'); + } catch (e) { + _logger.severe('Error setting up media stream: $e'); + } + + _checkInitialSharedContent(); + } + + Future _checkInitialSharedContent() async { + try { + _logger.info('Checking for initial shared content...'); + + final initialMedia = + await ReceiveSharingIntent.instance.getInitialMedia(); + _logger.info('Initial media check result: ${initialMedia.length} files'); + + if (initialMedia.isNotEmpty) { + _logger + .info('Found initial shared media files: ${initialMedia.length}'); + for (var file in initialMedia) { + _logger.info('Initial shared file: ${file.path}, type: ${file.type}'); + } + await _handleSharedFiles(initialMedia); + } else { + _logger.info('No initial shared media files found'); + } + } catch (e) { + _logger.severe('Error checking initial shared content: $e'); + } + } + + Future _handleSharedFiles(List sharedFiles) async { + _logger.info('_handleSharedFiles called with ${sharedFiles.length} files'); + + if (!mounted) { + _logger.warning('Context not mounted, cannot handle shared files'); + return; + } + + try { + for (final sharedFile in sharedFiles) { + _logger.info('Processing shared file: ${sharedFile.path}'); + if (sharedFile.path.isNotEmpty) { + final file = File(sharedFile.path); + if (await file.exists()) { + _logger.info('File exists, uploading: ${sharedFile.path}'); + await uploadFile(file); + } else { + _logger.warning('Shared file does not exist: ${sharedFile.path}'); + } + } else { + _logger.warning('Shared file has empty path'); + } + } + + await ReceiveSharingIntent.instance.reset(); + _logger.info('Reset sharing intent after handling files'); + } catch (e) { + _logger.severe('Error handling shared files: $e'); + if (mounted) { + await showErrorDialog( + context, + 'Upload Error', + 'Failed to process shared files: $e', + ); + } + } + } + + void disposeSharing() { + _mediaStreamSubscription?.cancel(); + ReceiveSharingIntent.instance.reset(); + _logger.info('Sharing functionality disposed'); + } + + Future _loadCollections() async { + try { + setState(() { + _isLoading = true; + _error = null; + }); + + final collections = await CollectionService.instance.getCollections(); + await _loadRecentFiles(collections); + + final sortedCollections = + CollectionSortUtil.getSortedCollections(collections); + + setState(() { + _collections = sortedCollections; + _filteredCollections = + CollectionSortUtil.filterAndSortCollections(sortedCollections); + _filteredFiles = _recentFiles; + _isLoading = false; + }); + + await _loadCollectionFileCounts(); + } catch (error) { + setState(() { + _error = 'Error fetching collections: $error'; + _isLoading = false; + }); + } + } + + Future _loadRecentFiles(List collections) async { + final allFiles = await CollectionService.instance.getAllFiles(); + + final uniqueFilesMap = {}; + + for (final file in allFiles) { + final key = file.uploadedFileID?.toString() ?? file.toString(); + if (!uniqueFilesMap.containsKey(key)) { + uniqueFilesMap[key] = file; + } + } + + final uniqueFiles = uniqueFilesMap.values.toList(); + uniqueFiles.sort((a, b) { + final timeA = a.updationTime ?? a.modificationTime ?? 0; + final timeB = b.updationTime ?? b.modificationTime ?? 0; + return timeB.compareTo(timeA); + }); + + _recentFiles = uniqueFiles; + } + + void _navigateToCollection(Collection collection) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => CollectionPage(collection: collection), + ), + ); + } + + @override + Widget build(BuildContext context) { + return KeyboardListener( + focusNode: FocusNode(), + onKeyEvent: handleKeyEvent, + child: Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + leading: buildSearchLeading(), + title: GestureDetector( + onLongPress: () { + sendLogs( + context, + 'vishnu@ente.io', + subject: 'Locker logs', + body: 'Debug logs for Locker app.\n\n', + ); + }, + child: const Text( + 'Locker', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + elevation: 0, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + foregroundColor: Theme.of(context).textTheme.bodyLarge?.color, + actions: [ + buildSearchAction(), + ...buildSearchActions(), + ], + ), + body: _buildBody(), + floatingActionButton: + isSearchActive ? const SizedBox.shrink() : _buildMultiOptionFab(), + ), + ); + } + + Widget _buildBody() { + if (_isLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + if (_error != null) { + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: SizedBox( + height: MediaQuery.of(context).size.height - 200, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.error_outline, + color: Colors.red, + size: 64, + ), + const SizedBox(height: 16), + Text( + _error!, + style: const TextStyle(color: Colors.red), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => _loadCollections(), + child: Text(context.l10n.retry), + ), + ], + ), + ), + ), + ); + } + + if (isSearchActive) { + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: SearchResultView( + collections: _filteredCollections, + files: _filteredFiles, + searchQuery: searchQuery, + enableSorting: true, + isHomePage: true, + ), + ); + } + + if (_displayedCollections.isEmpty) { + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: SizedBox( + height: MediaQuery.of(context).size.height - 200, + child: _buildEmptyState( + icon: Icons.folder_outlined, + title: context.l10n.noCollectionsFound, + subtitle: context.l10n.createYourFirstCollection, + action: Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: GradientButton( + onTap: _createCollection, + text: context.l10n.createCollection, + iconData: Icons.add, + paddingValue: 8.0, + ), + ), + ), + ), + ); + } + + return LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + physics: const AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, // Account for padding + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildCollectionsHeader(), + const SizedBox(height: 24), + _buildCollectionsGrid(), + const SizedBox(height: 24), + _buildRecentsSection(), + ], + ), + ), + ); + }, + ); + } + + Widget _buildRecentsSection() { + if (_recentFiles.isEmpty) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 40), + child: _buildEmptyState( + icon: Icons.description_outlined, + title: context.l10n.nothingYet, + subtitle: context.l10n.uploadYourFirstDocument, + action: GradientButton( + onTap: addFile, + text: context.l10n.uploadDocument, + iconData: Icons.file_upload, + paddingValue: 8.0, + ), + ), + ); + } + return RecentsSectionWidget( + collections: CollectionSortUtil.filterAndSortCollections(_collections), + recentFiles: _recentFiles, + ); + } + + Future _createCollection() async { + final createdCollection = await CollectionActions.createCollection(context); + + if (createdCollection != null) { + await _loadCollections(); + _navigateToCollection(createdCollection); + } + } + + Future _addInformation() async { + final result = await showInformationAdditionDialog(context); + + if (result != null && mounted) { + switch (result.type) { + case InformationType.physicalDocument: + await _addPhysicalDocument(); + break; + case InformationType.emergencyContact: + await _addEmergencyContact(); + break; + case InformationType.accountCredential: + await _addAccountCredential(); + break; + } + } + } + + Future _addPhysicalDocument() async { + SnackBarUtils.showInfoSnackBar( + context, + "Soon", + ); + } + + Future _addEmergencyContact() async { + SnackBarUtils.showInfoSnackBar( + context, + "Soon", + ); + } + + Future _addAccountCredential() async { + SnackBarUtils.showInfoSnackBar( + context, + "Soon", + ); + } + + Widget _buildEmptyState({ + required IconData icon, + required String title, + required String subtitle, + Widget? action, + }) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, size: 64, color: Colors.grey), + const SizedBox(height: 16), + Text( + title, + style: getEnteTextTheme(context).large.copyWith(color: Colors.grey), + ), + const SizedBox(height: 8), + Text( + subtitle, + style: getEnteTextTheme(context).body.copyWith(color: Colors.grey), + ), + if (action != null) ...[ + const SizedBox(height: 24), + action, + ], + ], + ), + ); + } + + Widget _buildFabLabel(String text) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: getEnteColorScheme(context).fillBase, + borderRadius: BorderRadius.circular(16), + ), + child: Text( + text, + style: getEnteTextTheme(context).small.copyWith( + color: getEnteColorScheme(context).backgroundBase, + ), + ), + ); + } + + Widget _buildFabOption({ + required String label, + required IconData icon, + required VoidCallback onPressed, + required String heroTag, + }) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildFabLabel(label), + const SizedBox(width: 8), + FloatingActionButton( + heroTag: heroTag, + mini: true, + onPressed: onPressed, + backgroundColor: getEnteColorScheme(context).fillBase, + child: Icon(icon), + ), + ], + ); + } + + Widget _buildCollectionsHeader() { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + SnackBarUtils.showWarningSnackBar(context, "Hello"); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const AllCollectionsPage(), + ), + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + context.l10n.collections, + style: getEnteTextTheme(context).h3Bold, + ), + const Icon( + Icons.chevron_right, + color: Colors.grey, + ), + ], + ), + ); + } + + Widget _buildCollectionsGrid() { + return MediaQuery.removePadding( + context: context, + removeBottom: true, + removeTop: true, + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 2.2, + ), + itemCount: min(_displayedCollections.length, 4), + itemBuilder: (context, index) { + final collection = _displayedCollections[index]; + final collectionName = collection.name ?? 'Unnamed Collection'; + + return GestureDetector( + onTap: () => _navigateToCollection(collection), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: getEnteColorScheme(context).fillFaint, + ), + padding: const EdgeInsets.all(12), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + collectionName, + style: getEnteTextTheme(context).body.copyWith( + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + const SizedBox(height: 4), + Text( + context.l10n + .items(_collectionFileCounts[collection.id] ?? 0), + style: getEnteTextTheme(context).small.copyWith( + color: Colors.grey[600], + ), + textAlign: TextAlign.left, + ), + ], + ), + if (collection.type == CollectionType.favorites) + Positioned( + top: 0, + right: 0, + child: Icon( + Icons.star, + color: getEnteColorScheme(context).primary500, + size: 18, + ), + ), + ], + ), + ), + ); + }, + ), + ); + } + + Widget _buildMultiOptionFab() { + return ValueListenableBuilder( + valueListenable: _isFabOpen, + builder: (context, isFabOpen, child) { + return Stack( + children: [ + if (isFabOpen) + Positioned.fill( + child: GestureDetector( + onTap: _toggleFab, + child: Container( + color: Colors.transparent, + ), + ), + ), + Positioned( + right: 0, + bottom: 0, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (isFabOpen) ...[ + ScaleTransition( + scale: _animation, + child: Container( + margin: const EdgeInsets.only(bottom: 16), + child: _buildFabOption( + label: context.l10n.addInformation, + icon: Icons.post_add, + onPressed: () { + _toggleFab(); + _addInformation(); + }, + heroTag: "addInformation", + ), + ), + ), + if (_collections.isNotEmpty) + ScaleTransition( + scale: _animation, + child: Container( + margin: const EdgeInsets.only(bottom: 8), + child: _buildFabOption( + label: context.l10n.uploadDocumentTooltip, + icon: Icons.file_upload, + onPressed: () { + _toggleFab(); + addFile(); + }, + heroTag: "addFile", + ), + ), + ), + ], + FloatingActionButton( + onPressed: _toggleFab, + child: AnimatedRotation( + turns: isFabOpen ? 0.125 : 0.0, // 45 degrees when open + duration: const Duration(milliseconds: 300), + child: const Icon(Icons.add), + ), + ), + ], + ), + ), + ], + ); + }, + ); + } + + void _toggleFab() { + _isFabOpen.value = !_isFabOpen.value; + + if (_isFabOpen.value) { + _animationController.forward(); + } else { + _animationController.reverse(); + } + } + + Future _loadCollectionFileCounts() async { + final counts = {}; + + for (final collection in _displayedCollections.take(4)) { + try { + final files = + await CollectionService.instance.getFilesInCollection(collection); + counts[collection.id] = files.length; + } catch (e) { + counts[collection.id] = 0; + } + } + + if (mounted) { + setState(() { + _collectionFileCounts = counts; + }); + } + } +} diff --git a/mobile/apps/locker/lib/ui/pages/onboarding_page.dart b/mobile/apps/locker/lib/ui/pages/onboarding_page.dart new file mode 100644 index 0000000000..a5654f0e3a --- /dev/null +++ b/mobile/apps/locker/lib/ui/pages/onboarding_page.dart @@ -0,0 +1,227 @@ +import 'package:ente_accounts/pages/email_entry_page.dart'; +import 'package:ente_accounts/pages/login_page.dart'; +import 'package:ente_accounts/pages/password_entry_page.dart'; +import 'package:ente_accounts/pages/password_reentry_page.dart'; +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/gradient_button.dart'; +import 'package:ente_ui/components/developer_settings_widget.dart'; +import "package:ente_ui/pages/developer_settings_page.dart"; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:flutter/material.dart'; +import 'package:locker/l10n/l10n.dart'; +import 'package:locker/services/configuration.dart'; +import 'package:locker/ui/pages/home_page.dart'; + +class OnboardingPage extends StatefulWidget { + const OnboardingPage({super.key}); + + @override + State createState() => _OnboardingPageState(); +} + +class _OnboardingPageState extends State { + static const kDeveloperModeTapCountThreshold = 7; + + int _developerModeTapCount = 0; + + @override + Widget build(BuildContext context) { + debugPrint("Building OnboardingPage"); + final l10n = context.l10n; + return Scaffold( + body: SafeArea( + child: GestureDetector( + onTap: () async { + _developerModeTapCount++; + if (_developerModeTapCount >= kDeveloperModeTapCountThreshold) { + _developerModeTapCount = 0; + final result = await showChoiceDialog( + context, + title: l10n.developerSettings, + firstButtonLabel: l10n.yes, + body: l10n.developerSettingsWarning, + isDismissible: false, + ); + if (result?.action == ButtonAction.first) { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return DeveloperSettingsPage(Configuration.instance); + }, + ), + ); + setState(() {}); + } + } + }, + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + maxWidth: 450, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 40.0, + horizontal: 40, + ), + child: Column( + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "assets/locker.png", + width: 200, + height: 200, + ), + const SizedBox(height: 12), + Text( + "ente", + style: getEnteTextTheme(context).h1.copyWith( + fontWeight: FontWeight.bold, + fontFamily: 'Montserrat', + ), + ), + const SizedBox(height: 4), + Text( + "Locker", + style: Theme.of(context) + .textTheme + .headlineMedium, + ), + const SizedBox(height: 32), + Text( + l10n.onBoardingBody, + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith( + color: Colors.white38, + ), + ), + ], + ), + ), + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 20), + child: GradientButton( + onTap: _navigateToSignUpPage, + text: l10n.newUser, + ), + ), + const SizedBox(height: 16), + Container( + height: 56, + width: double.infinity, + padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), + child: Hero( + tag: "log_in", + child: ElevatedButton( + style: ElevatedButton.styleFrom().copyWith( + shape: WidgetStateProperty.all< + RoundedRectangleBorder>( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(32.0), + ), + ), + ), + onPressed: _navigateToSignInPage, + child: Text( + l10n.existingUser, + style: const TextStyle( + color: Colors.white, // same for both themes + ), + ), + ), + ), + ), + const SizedBox(height: 4), + DeveloperSettingsWidget(Configuration.instance), + ], + ), + ), + ), + ), + ); + }, + ), + ), + ), + ); + } + + void _navigateToSignUpPage() { + Widget page; + if (Configuration.instance.getEncryptedToken() == null) { + page = EmailEntryPage(Configuration.instance); + } else { + // No key + if (Configuration.instance.getKeyAttributes() == null) { + // Never had a key + page = PasswordEntryPage( + Configuration.instance, + PasswordEntryMode.set, + const HomePage(), + ); + } else if (Configuration.instance.getKey() == null) { + // Yet to decrypt the key + page = PasswordReentryPage( + Configuration.instance, + const HomePage(), + ); + } else { + // All is well, user just has not subscribed + page = const HomePage(); + } + } + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return page; + }, + ), + ); + } + + void _navigateToSignInPage() { + Widget page; + if (Configuration.instance.getEncryptedToken() == null) { + page = LoginPage(Configuration.instance); + } else { + // No key + if (Configuration.instance.getKeyAttributes() == null) { + // Never had a key + page = PasswordEntryPage( + Configuration.instance, + PasswordEntryMode.set, + const HomePage(), + ); + } else if (Configuration.instance.getKey() == null) { + // Yet to decrypt the key + page = PasswordReentryPage( + Configuration.instance, + const HomePage(), + ); + } else { + // All is well, user just has not subscribed + // page = getSubscriptionPage(isOnBoarding: true); + page = const HomePage(); + } + } + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return page; + }, + ), + ); + } +} diff --git a/mobile/apps/locker/lib/ui/pages/trash_page.dart b/mobile/apps/locker/lib/ui/pages/trash_page.dart new file mode 100644 index 0000000000..5f4261898d --- /dev/null +++ b/mobile/apps/locker/lib/ui/pages/trash_page.dart @@ -0,0 +1,404 @@ +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:flutter/material.dart'; +import 'package:locker/l10n/l10n.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/files/sync/models/file.dart'; +import 'package:locker/services/trash/models/trash_file.dart'; +import 'package:locker/services/trash/trash_service.dart'; +import 'package:locker/ui/components/item_list_view.dart'; +import 'package:locker/utils/snack_bar_utils.dart'; + +class TrashPage extends StatefulWidget { + final List trashFiles; + + const TrashPage({ + super.key, + required this.trashFiles, + }); + + @override + State createState() => _TrashPageState(); +} + +class _TrashPageState extends State { + List _sortedTrashFiles = []; + List _allTrashFiles = []; + + @override + void initState() { + super.initState(); + _allTrashFiles = List.from(widget.trashFiles); + _sortedTrashFiles = List.from(widget.trashFiles); + } + + List _getFileOverflowActions() { + return [ + OverflowMenuAction( + id: 'restore', + label: context.l10n.restore, + icon: Icons.restore, + onTap: (context, file, collection) { + _restoreFile(context, file!); + }, + ), + OverflowMenuAction( + id: 'delete', + label: context.l10n.delete, + icon: Icons.delete_forever, + onTap: (context, file, collection) { + _deleteFilePermanently(context, file!); + }, + ), + ]; + } + + void _restoreFile(BuildContext context, EnteFile file) async { + final collections = await CollectionService.instance.getCollections(); + + final availableCollections = collections + .where((c) => !c.isDeleted && c.type != CollectionType.uncategorized) + .toList(); + + if (availableCollections.isEmpty) { + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.noCollectionsAvailableForRestore, + ); + return; + } + + final selectedCollection = await _showCollectionPickerDialog( + context, + availableCollections, + file.displayName, + ); + + if (selectedCollection != null) { + await _performRestore(context, file, selectedCollection); + } + } + + void _deleteFilePermanently(BuildContext context, EnteFile file) { + TrashService.instance.deleteFromTrash([file]).then((_) { + setState(() { + _sortedTrashFiles.remove(file); + _allTrashFiles.remove(file); + }); + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.deletedPermanently(file.displayName), + ); + }).catchError((error) { + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.failedToDeleteFile(error.toString()), + ); + }); + } + + Future _showCollectionPickerDialog( + BuildContext context, + List collections, + String fileName, + ) async { + return showDialog( + context: context, + barrierColor: getEnteColorScheme(context).backdropBase, + builder: (context) => _CollectionPickerDialog( + collections: collections, + fileName: fileName, + ), + ); + } + + Future _performRestore( + BuildContext context, + EnteFile file, + Collection targetCollection, + ) async { + final dialog = createProgressDialog( + context, + context.l10n.restoring, + isDismissible: false, + ); + + try { + await dialog.show(); + + await TrashService.instance.restore([file], targetCollection); + + setState(() { + _sortedTrashFiles.remove(file); + _allTrashFiles.remove(file); + }); + + await dialog.hide(); + + SnackBarUtils.showInfoSnackBar( + context, + context.l10n.restoredFileToCollection( + file.displayName, + targetCollection.name ?? 'Unnamed Collection', + ), + ); + } catch (error) { + await dialog.hide(); + + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.failedToRestoreFile(file.displayName, error.toString()), + ); + } + } + + Future _emptyTrash() async { + final result = await showChoiceDialog( + context, + title: context.l10n.emptyTrash, + body: context.l10n.emptyTrashConfirmation, + firstButtonLabel: context.l10n.emptyTrash, + secondButtonLabel: context.l10n.cancel, + firstButtonType: ButtonType.critical, + isCritical: true, + ); + + if (result?.action == ButtonAction.first && context.mounted) { + await _performEmptyTrash(); + } + } + + Future _performEmptyTrash() async { + final dialog = createProgressDialog( + context, + context.l10n.clearingTrash, + isDismissible: false, + ); + await dialog.show(); + try { + await TrashService.instance.emptyTrash(); + setState(() { + _sortedTrashFiles.clear(); + _allTrashFiles.clear(); + }); + SnackBarUtils.showInfoSnackBar( + context, + context.l10n.trashClearedSuccessfully, + ); + Navigator.of(context).pop(); + } catch (error) { + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.failedToClearTrash(error.toString()), + ); + } finally { + await dialog.hide(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.l10n.trash), + centerTitle: false, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + foregroundColor: Theme.of(context).textTheme.bodyLarge?.color, + actions: [ + IconButton( + icon: const Icon(Icons.delete_sweep), + onPressed: _emptyTrash, + tooltip: context.l10n.emptyTrashTooltip, + ), + ], + ), + body: _buildBody(), + ); + } + + Widget _buildBody() { + if (_sortedTrashFiles.isEmpty) { + return Center( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.delete_outline, + size: 64, + color: Colors.grey, + ), + const SizedBox(height: 16), + Text( + context.l10n.trashIsEmpty, + style: getEnteTextTheme(context).large.copyWith( + color: Colors.grey, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } + + return Padding( + padding: const EdgeInsets.all(16.0), + child: ItemListView( + files: _sortedTrashFiles.cast(), + enableSorting: true, + fileOverflowActions: _getFileOverflowActions(), + ), + ); + } +} + +class _CollectionPickerDialog extends StatefulWidget { + final List collections; + final String fileName; + + const _CollectionPickerDialog({ + required this.collections, + required this.fileName, + }); + + @override + State<_CollectionPickerDialog> createState() => + _CollectionPickerDialogState(); +} + +class _CollectionPickerDialogState extends State<_CollectionPickerDialog> { + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + final textTheme = getEnteTextTheme(context); + + return Dialog( + backgroundColor: colorScheme.backgroundElevated, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Container( + width: 400, + constraints: const BoxConstraints(maxHeight: 600), + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon( + Icons.restore, + color: Colors.green, + size: 24, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + context.l10n.restoreFile(widget.fileName), + style: textTheme.largeBold, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 20), + Flexible( + child: Container( + height: 150, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: colorScheme.strokeFaint), + ), + child: widget.collections.isEmpty + ? Container( + padding: const EdgeInsets.all(16), + child: Center( + child: Text( + context.l10n.noCollectionsAvailable, + style: textTheme.body.copyWith( + color: colorScheme.textMuted, + ), + ), + ), + ) + : Scrollbar( + thumbVisibility: true, + thickness: 6, + radius: const Radius.circular(3), + child: GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 3.5, + crossAxisSpacing: 6, + mainAxisSpacing: 6, + ), + padding: const EdgeInsets.all(6), + itemCount: widget.collections.length, + itemBuilder: (context, index) { + final collection = widget.collections[index]; + final collectionName = + collection.name ?? 'Unnamed Collection'; + + return InkWell( + onTap: () => + Navigator.of(context).pop(collection), + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 4, + ), + decoration: BoxDecoration( + color: colorScheme.fillFaint, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: colorScheme.strokeFaint, + width: 1, + ), + ), + child: Center( + child: Text( + collectionName, + style: textTheme.small.copyWith( + color: colorScheme.textBase, + fontWeight: FontWeight.normal, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + ), + ), + ); + }, + ), + ), + ), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: ButtonWidget( + buttonType: ButtonType.secondary, + labelText: context.l10n.cancel, + onTap: () async => Navigator.of(context).pop(), + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/mobile/apps/locker/lib/ui/pages/uploader_page.dart b/mobile/apps/locker/lib/ui/pages/uploader_page.dart new file mode 100644 index 0000000000..ae2e3145fc --- /dev/null +++ b/mobile/apps/locker/lib/ui/pages/uploader_page.dart @@ -0,0 +1,111 @@ +import 'dart:io'; + +import 'package:ente_ui/pages/base_home_page.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/services/files/sync/metadata_updater_service.dart'; +import 'package:locker/services/files/upload/file_upload_service.dart'; +import 'package:locker/ui/components/file_upload_dialog.dart'; +import 'package:logging/logging.dart'; + +/// Abstract base class that provides file upload functionality. +/// Contains common file picking and uploading logic that can be reused +/// across different pages like HomePage and CollectionPage. +abstract class UploaderPage extends BaseHomePage { + const UploaderPage({super.key}); +} + +abstract class UploaderPageState extends State { + final _logger = Logger('UploaderPage'); + + /// Returns the collection that should be pre-selected in the upload dialog. + /// Return null to default to uncategorized collection. + Collection? get selectedCollection => null; + + /// Called after a successful file upload to refresh the UI + void onFileUploadComplete(); + + /// Opens a file picker dialog and uploads the selected file + Future addFile() async { + final FilePickerResult? result = + await FilePicker.platform.pickFiles(type: FileType.any); + + if (result != null && result.files.isNotEmpty) { + final selectedFile = result.files.first; + if (selectedFile.path != null) { + await uploadFile(File(selectedFile.path!)); + } + } + } + + Future uploadFile(File file) async { + final progressDialog = createProgressDialog(context, "Uploading..."); + try { + final List futures = []; + + final collections = await CollectionService.instance.getCollections(); + + final collectionsWithoutUncategorized = collections + .where((c) => c.type != CollectionType.uncategorized) + .toList(); + + final uploadResult = await showFileUploadDialog( + context, + file: file, + collections: collectionsWithoutUncategorized, + selectedCollection: selectedCollection, + ); + + if (uploadResult != null && uploadResult.selectedCollections.isNotEmpty) { + final fileUploadFuture = FileUploader.instance + .upload(file, uploadResult.selectedCollections.first); + futures.add(fileUploadFuture); + futures.add( + fileUploadFuture.then((enteFile) async { + // Add to additional collections if multiple were selected + for (int cIndex = 1; + cIndex < uploadResult.selectedCollections.length; + cIndex++) { + futures.add( + CollectionService.instance.addToCollection( + uploadResult.selectedCollections[cIndex], + enteFile, + ), + ); + } + + if (uploadResult.note.isNotEmpty) { + futures.add( + MetadataUpdaterService.instance + .editFileCaption(enteFile, uploadResult.note), + ); + } + }).catchError((e) { + _logger.severe('File upload failed', e); + }), + ); + } + + if (futures.isNotEmpty) { + await progressDialog.show(); + await Future.wait(futures); + _logger.info('File upload completed successfully'); + await CollectionService.instance.sync(); + onFileUploadComplete(); + } + } catch (e, s) { + _logger.severe('Failed to upload file', e, s); + await showGenericErrorDialog( + context: context, + error: e, + ); + } finally { + if (progressDialog.isShowing()) { + await progressDialog.hide(); + } + } + } +} diff --git a/mobile/apps/locker/lib/utils/collection_actions.dart b/mobile/apps/locker/lib/utils/collection_actions.dart new file mode 100644 index 0000000000..90bdc76473 --- /dev/null +++ b/mobile/apps/locker/lib/utils/collection_actions.dart @@ -0,0 +1,160 @@ +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:flutter/material.dart'; +import 'package:locker/l10n/l10n.dart'; +import 'package:locker/services/collections/collections_service.dart'; +import 'package:locker/services/collections/models/collection.dart'; +import 'package:locker/utils/snack_bar_utils.dart'; +import 'package:logging/logging.dart'; + +/// Utility class for common collection actions like edit and delete +class CollectionActions { + static final _logger = Logger('CollectionActions'); + + /// Shows a dialog to create a new collection + static Future createCollection( + BuildContext context, { + bool autoSelectInParent = false, + }) async { + Collection? createdCollection; + + final nameSuggestion = + await CollectionService.instance.getRandomUnusedCollectionName(); + + final result = await showTextInputDialog( + context, + title: context.l10n.createNewCollection, + submitButtonLabel: context.l10n.create, + hintText: nameSuggestion, + alwaysShowSuccessState: true, + textCapitalization: TextCapitalization.words, + onSubmit: (String text) async { + // indicates user cancelled the creation request + if (text.trim().isEmpty) { + return; + } + + try { + createdCollection = + await CollectionService.instance.createCollection(text.trim()); + } catch (e, s) { + _logger.severe('Failed to create collection', e, s); + rethrow; + } + }, + ); + + if (result is Exception) { + if (context.mounted) { + await showGenericErrorDialog( + context: context, + error: result, + ); + } + return null; + } else if (createdCollection != null) { + return createdCollection; + } + + return null; + } + + // Shows a dialog to edit/rename a collection + static Future editCollection( + BuildContext context, + Collection collection, { + VoidCallback? onSuccess, + }) async { + await showTextInputDialog( + context, + title: context.l10n.renameCollection, + initialValue: collection.name ?? '', + hintText: context.l10n.documentsHint, + submitButtonLabel: context.l10n.save, + onSubmit: (String newName) async { + if (newName.isEmpty || newName == collection.name) return; + + final progressDialog = + createProgressDialog(context, context.l10n.pleaseWait); + await progressDialog.show(); + + try { + await CollectionService.instance.rename(collection, newName); + await progressDialog.hide(); + + SnackBarUtils.showInfoSnackBar( + context, + context.l10n.collectionRenamedSuccessfully, + ); + + // Update the collection name locally + collection.setName(newName); + + // Call success callback if provided + onSuccess?.call(); + } catch (error) { + await progressDialog.hide(); + + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.failedToRenameCollection(error.toString()), + ); + } + }, + ); + } + + /// Shows a confirmation dialog and deletes a collection + static Future deleteCollection( + BuildContext context, + Collection collection, { + VoidCallback? onSuccess, + }) async { + if (!collection.type.canDelete) { + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.collectionCannotBeDeleted, + ); + return; + } + + final collectionName = collection.name ?? 'this collection'; + + final dialogChoice = await showChoiceDialog( + context, + title: context.l10n.deleteCollection, + body: context.l10n.deleteCollectionConfirmation(collectionName), + firstButtonLabel: context.l10n.delete, + secondButtonLabel: context.l10n.cancel, + firstButtonType: ButtonType.critical, + isCritical: true, + ); + + if (dialogChoice?.action != ButtonAction.first) return; + + final progressDialog = + createProgressDialog(context, context.l10n.pleaseWait); + await progressDialog.show(); + + try { + await CollectionService.instance.trashCollection(collection); + await progressDialog.hide(); + + SnackBarUtils.showInfoSnackBar( + context, + context.l10n.collectionDeletedSuccessfully, + ); + + // Call success callback if provided + onSuccess?.call(); + } catch (error) { + await progressDialog.hide(); + + SnackBarUtils.showWarningSnackBar( + context, + context.l10n.failedToDeleteCollection(error.toString()), + ); + } + } +} diff --git a/mobile/apps/locker/lib/utils/collection_sort_util.dart b/mobile/apps/locker/lib/utils/collection_sort_util.dart new file mode 100644 index 0000000000..80e12f9426 --- /dev/null +++ b/mobile/apps/locker/lib/utils/collection_sort_util.dart @@ -0,0 +1,101 @@ +import 'package:locker/services/collections/models/collection.dart'; + +/// Utility class for sorting collections with consistent logic across the app +class CollectionSortUtil { + /// Sorts collections with Important (favorites) collection first, then alphabetically by name + static void sortCollections(List collections) { + collections.sort((a, b) { + // Important collection (favorites) should come first + if (a.type == CollectionType.favorites && + b.type != CollectionType.favorites) { + return -1; + } + if (b.type == CollectionType.favorites && + a.type != CollectionType.favorites) { + return 1; + } + // For other collections, sort alphabetically by name + final nameA = a.name ?? a.name ?? ''; + final nameB = b.name ?? b.name ?? ''; + return nameA.toLowerCase().compareTo(nameB.toLowerCase()); + }); + } + + /// Returns a new sorted list of collections with Important (favorites) first + static List getSortedCollections(List collections) { + final sortedList = List.from(collections); + sortCollections(sortedList); + return sortedList; + } + + /// Filters out uncategorized collections and sorts the remaining ones + static List filterAndSortCollections( + List collections, + ) { + final filtered = collections + .where((c) => c.type != CollectionType.uncategorized) + .toList(); + sortCollections(filtered); + return filtered; + } + + /// Compares two collections for sorting, prioritizing Important collection + /// Returns -1 if a should come before b, 1 if b should come before a, 0 if equal + static int compareCollections(Collection a, Collection b) { + // Important collection (favorites) should come first + if (a.type == CollectionType.favorites && + b.type != CollectionType.favorites) { + return -1; + } + if (b.type == CollectionType.favorites && + a.type != CollectionType.favorites) { + return 1; + } + // For other collections, sort alphabetically by name + final nameA = a.name ?? a.name ?? ''; + final nameB = b.name ?? b.name ?? ''; + return nameA.toLowerCase().compareTo(nameB.toLowerCase()); + } + + /// Compares two collections for sorting with Important always first regardless of sort direction + /// Used for table sorting where Important should always be first + static int compareCollectionsWithFavoritesPriority( + Collection a, + Collection b, + bool ascending, + ) { + // Important collection (favorites) should always come first regardless of sort direction + if (a.type == CollectionType.favorites && + b.type != CollectionType.favorites) { + return -1; + } + if (b.type == CollectionType.favorites && + a.type != CollectionType.favorites) { + return 1; + } + // For other collections, use normal comparison + return ascending ? compareCollections(a, b) : compareCollections(b, a); + } + + /// Compares two collections for date sorting with Important always first regardless of sort direction + /// Used for table sorting where Important should always be first + static int compareCollectionsByDateWithFavoritesPriority( + Collection a, + Collection b, + bool ascending, + ) { + // Important collection (favorites) should always come first regardless of sort direction + if (a.type == CollectionType.favorites && + b.type != CollectionType.favorites) { + return -1; + } + if (b.type == CollectionType.favorites && + a.type != CollectionType.favorites) { + return 1; + } + // For other collections, sort by modification time + final dateA = DateTime.fromMicrosecondsSinceEpoch(a.updationTime); + final dateB = DateTime.fromMicrosecondsSinceEpoch(b.updationTime); + return ascending ? dateA.compareTo(dateB) : dateB.compareTo(dateA); + } +} diff --git a/mobile/apps/locker/lib/utils/crypto_helper.dart b/mobile/apps/locker/lib/utils/crypto_helper.dart new file mode 100644 index 0000000000..8ad4329af7 --- /dev/null +++ b/mobile/apps/locker/lib/utils/crypto_helper.dart @@ -0,0 +1,48 @@ +import "dart:typed_data"; + +import "package:ente_crypto_dart/ente_crypto_dart.dart"; +import "package:locker/services/collections/models/collection.dart"; +import "package:locker/services/configuration.dart"; + +class CryptoHelper { + CryptoHelper._privateConstructor(); + static final CryptoHelper instance = CryptoHelper._privateConstructor(); + + Uint8List getFileKey( + String encryptedKey, + String keyDecryptionNonce, + Uint8List collectionKey, + ) { + final eKey = CryptoUtil.base642bin(encryptedKey); + final nonce = CryptoUtil.base642bin(keyDecryptionNonce); + return CryptoUtil.decryptSync(eKey, collectionKey, nonce); + } + + Uint8List getCollectionKey(Collection collection) { + final encryptedKey = CryptoUtil.base642bin(collection.encryptedKey); + Uint8List? collectionKey; + if (collection.owner.id == Configuration.instance.getUserID()) { + // If the collection is owned by the user, decrypt with the master key + if (Configuration.instance.getKey() == null) { + // Possible during AppStore account migration, where SecureStorage + // would become inaccessible to the new Developer Account + throw Exception("key can not be null"); + } + collectionKey = CryptoUtil.decryptSync( + encryptedKey, + Configuration.instance.getKey()!, + CryptoUtil.base642bin(collection.keyDecryptionNonce!), + ); + } else { + // If owned by a different user, decrypt with the public key + collectionKey = CryptoUtil.openSealSync( + encryptedKey, + CryptoUtil.base642bin( + Configuration.instance.getKeyAttributes()!.publicKey, + ), + Configuration.instance.getSecretKey()!, + ); + } + return collectionKey; + } +} diff --git a/mobile/apps/locker/lib/utils/data_util.dart b/mobile/apps/locker/lib/utils/data_util.dart new file mode 100644 index 0000000000..d324cde35d --- /dev/null +++ b/mobile/apps/locker/lib/utils/data_util.dart @@ -0,0 +1,63 @@ +import 'dart:math'; + +final storageUnits = ["bytes", "KB", "MB", "GB", "TB"]; + +String convertBytesToReadableFormat(int bytes) { + int storageUnitIndex = 0; + while (bytes >= 1024 && storageUnitIndex < storageUnits.length - 1) { + storageUnitIndex++; + bytes = (bytes / 1024).round(); + } + return "$bytes ${storageUnits[storageUnitIndex]}"; +} + +(int, String) convertBytesToNumberAndUnit(int bytes) { + int storageUnitIndex = 0; + while (bytes >= 1024 && storageUnitIndex < storageUnits.length - 1) { + storageUnitIndex++; + bytes = (bytes / 1024).round(); + } + return (bytes, storageUnits[storageUnitIndex]); +} + +String formatBytes(int bytes, [int decimals = 2]) { + if (bytes == 0) return '0 bytes'; + const k = 1024; + final int dm = decimals < 0 ? 0 : decimals; + final int i = (log(bytes) / log(k)).floor(); + return '${(bytes / pow(k, i)).toStringAsFixed(dm)} ${storageUnits[i]}'; +} + +//shows 1st decimal only if less than 10GB & omits decimal if decimal is 0 +num roundBytesUsedToGBs(int usedBytes, int freeSpace) { + const tenGBinBytes = 10737418240; + num bytesInGB = convertBytesToGBs(usedBytes); + if ((usedBytes >= tenGBinBytes && freeSpace >= tenGBinBytes) || + bytesInGB % 1 == 0) { + bytesInGB = bytesInGB.truncate(); + } + return bytesInGB; +} + +//Eg: 0.3 GB, 11.0 GB, 532.3 GB +num convertBytesToGBs(int bytes) { + return num.parse((bytes / (pow(1024, 3))).toStringAsFixed(1)); +} + +int convertBytesToAbsoluteGBs(int bytes) { + return (bytes / pow(1024, 3)).round(); +} + +int convertBytesToMBs(int bytes) { + return (bytes / pow(1024, 2)).round(); +} + +//Eg: 1TB, 1.3TB, 4.9TB, 3TB +num roundGBsToTBs(sizeInGBs) { + final num sizeInTBs = num.parse((sizeInGBs / 1000).toStringAsFixed(1)); + if (sizeInTBs % 1 == 0) { + return sizeInTBs.truncate(); + } else { + return sizeInTBs; + } +} diff --git a/mobile/apps/locker/lib/utils/date_time_util.dart b/mobile/apps/locker/lib/utils/date_time_util.dart new file mode 100644 index 0000000000..3e3ea6850d --- /dev/null +++ b/mobile/apps/locker/lib/utils/date_time_util.dart @@ -0,0 +1,259 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:locker/l10n/l10n.dart'; + +const Set monthWith31Days = {1, 3, 5, 7, 8, 10, 12}; +const Set monthWith30Days = {4, 6, 9, 11}; +Map _months = { + 1: "Jan", + 2: "Feb", + 3: "March", + 4: "April", + 5: "May", + 6: "Jun", + 7: "July", + 8: "Aug", + 9: "Sep", + 10: "Oct", + 11: "Nov", + 12: "Dec", +}; + +Map _fullMonths = { + 1: "January", + 2: "February", + 3: "March", + 4: "April", + 5: "May", + 6: "June", + 7: "July", + 8: "August", + 9: "September", + 10: "October", + 11: "November", + 12: "December", +}; + +Map _days = { + 1: "Mon", + 2: "Tue", + 3: "Wed", + 4: "Thu", + 5: "Fri", + 6: "Sat", + 7: "Sun", +}; + +final currentYear = int.parse(DateTime.now().year.toString()); +const searchStartYear = 1970; + +//Jun 2022 +String getMonthAndYear(DateTime dateTime) { + return "${_months[dateTime.month]!} ${dateTime.year}"; +} + +//Thu, 30 Jun +String getDayAndMonth(DateTime dateTime) { + return "${_days[dateTime.weekday]!}, ${dateTime.day} ${_months[dateTime.month]!}"; +} + +//30 Jun, 2022 +String getDateAndMonthAndYear(DateTime dateTime) { + return "${dateTime.day} ${_months[dateTime.month]!}, ${dateTime.year}"; +} + +String getDay(DateTime dateTime) { + return _days[dateTime.weekday]!; +} + +String getMonth(DateTime dateTime) { + return _months[dateTime.month]!; +} + +String getFullMonth(DateTime dateTime) { + return _fullMonths[dateTime.month]!; +} + +String getAbbreviationOfYear(DateTime dateTime) { + return (dateTime.year % 100).toString(); +} + +//14:32 +String getTime(DateTime dateTime) { + final hours = + dateTime.hour > 9 ? dateTime.hour.toString() : "0${dateTime.hour}"; + final minutes = + dateTime.minute > 9 ? dateTime.minute.toString() : "0${dateTime.minute}"; + return "$hours:$minutes"; +} + +//11:22 AM +String getTimeIn12hrFormat(DateTime dateTime) { + return DateFormat.jm().format(dateTime); +} + +//Thu, Jun 30, 2022 - 14:32 +String getFormattedTime(DateTime dateTime) { + return "${getDay(dateTime)}, ${getMonth(dateTime)} ${dateTime.day}, ${dateTime.year} - ${getTime(dateTime)}"; +} + +//30 Jun'22 +String getFormattedDate(DateTime dateTime) { + return "${dateTime.day} ${getMonth(dateTime)}'${getAbbreviationOfYear(dateTime)}"; +} + +String getFullDate(DateTime dateTime) { + return "${getDay(dateTime)}, ${getMonth(dateTime)} ${dateTime.day} ${dateTime.year}"; +} + +String daysLeft(int futureTime) { + final int daysLeft = ((futureTime - DateTime.now().microsecondsSinceEpoch) / + Duration.microsecondsPerDay) + .ceil(); + return '$daysLeft day${daysLeft <= 1 ? "" : "s"}'; +} + +String formatDuration(Duration position) { + final ms = position.inMilliseconds; + + int seconds = ms ~/ 1000; + final int hours = seconds ~/ 3600; + seconds = seconds % 3600; + final minutes = seconds ~/ 60; + seconds = seconds % 60; + + final hoursString = hours >= 10 + ? '$hours' + : hours == 0 + ? '00' + : '0$hours'; + + final minutesString = minutes >= 10 + ? '$minutes' + : minutes == 0 + ? '00' + : '0$minutes'; + + final secondsString = seconds >= 10 + ? '$seconds' + : seconds == 0 + ? '00' + : '0$seconds'; + + final formattedTime = + '${hoursString == '00' ? '' : '$hoursString:'}$minutesString:$secondsString'; + + return formattedTime; +} + +bool isLeapYear(DateTime dateTime) { + final year = dateTime.year; + if (year % 4 == 0) { + if (year % 100 == 0) { + if (year % 400 == 0) { + return true; + } else { + return false; + } + } else { + return true; + } + } else { + return false; + } +} + +Widget getDayWidget( + BuildContext context, + int timestamp, + bool smallerTodayFont, +) { + return Container( + padding: const EdgeInsets.fromLTRB(4, 14, 0, 8), + alignment: Alignment.centerLeft, + child: Text( + getDayTitle(timestamp), + style: (getDayTitle(timestamp) == "Today" && !smallerTodayFont) + ? Theme.of(context).textTheme.headlineSmall + : Theme.of(context).textTheme.bodySmall?.copyWith( + fontSize: 16, + fontWeight: FontWeight.w600, + fontFamily: 'Inter-SemiBold', + ), + ), + ); +} + +String getDayTitle(int timestamp) { + final date = DateTime.fromMicrosecondsSinceEpoch(timestamp); + final now = DateTime.now(); + var title = getDayAndMonth(date); + if (date.year == now.year && date.month == now.month) { + if (date.day == now.day) { + title = "Today"; + } else if (date.day == now.day - 1) { + title = "Yesterday"; + } + } + if (date.year != DateTime.now().year) { + title += " ${date.year}"; + } + return title; +} + +String secondsToHHMMSS(int value) { + int h, m, s; + h = value ~/ 3600; + m = ((value - h * 3600)) ~/ 60; + s = value - (h * 3600) - (m * 60); + final String hourLeft = h.toString().length < 2 ? "0$h" : h.toString(); + + final String minuteLeft = m.toString().length < 2 ? "0$m" : m.toString(); + + final String secondsLeft = s.toString().length < 2 ? "0$s" : s.toString(); + + final String result = "$hourLeft:$minuteLeft:$secondsLeft"; + + return result; +} + +bool isValidDate({ + required int day, + required int month, + required int year, +}) { + if (day < 0 || day > 31 || month < 0 || month > 12 || year < 0) { + return false; + } + if (monthWith30Days.contains(month) && day > 30) { + return false; + } + if (month == 2) { + if (day > 29) { + return false; + } + if (day == 29 && year % 4 != 0) { + return false; + } + } + return true; +} + +String formatDate(BuildContext context, DateTime date) { + final now = DateTime.now(); + final difference = now.difference(date); + + if (difference.inMinutes < 1) { + return 'Just now'; + } else if (difference.inMinutes < 60) { + return context.l10n.minutesAgo(difference.inMinutes); + } else if (difference.inDays == 0) { + return context.l10n.hoursAgo(difference.inHours); + } else if (difference.inDays == 1) { + return context.l10n.yesterday; + } else if (difference.inDays < 7) { + return context.l10n.daysAgo(difference.inDays); + } else { + return '${date.day}/${date.month}/${date.year}'; + } +} diff --git a/mobile/apps/locker/lib/utils/file_icon_utils.dart b/mobile/apps/locker/lib/utils/file_icon_utils.dart new file mode 100644 index 0000000000..bc1f238200 --- /dev/null +++ b/mobile/apps/locker/lib/utils/file_icon_utils.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; + +class FileIconConfig { + final IconData icon; + final Color color; + final Set extensions; + + const FileIconConfig({ + required this.icon, + required this.color, + required this.extensions, + }); +} + +class FileIconUtils { + // Centralized configuration - change icons and colors here only + static const Map _fileTypeConfigs = { + 'pdf': FileIconConfig( + extensions: {'.pdf'}, + icon: Icons.picture_as_pdf, + color: Colors.red, + ), + 'image': FileIconConfig( + extensions: {'.jpg', '.png', '.heic'}, + icon: Icons.image, + color: Colors.blue, + ), + 'presentation': FileIconConfig( + extensions: {'.pptx'}, + icon: Icons.slideshow, + color: Colors.orange, + ), + 'spreadsheet': FileIconConfig( + extensions: {'.xlsx'}, + icon: Icons.table_chart, + color: Colors.green, + ), + }; + + static const FileIconConfig _defaultConfig = FileIconConfig( + extensions: {}, + icon: Icons.insert_drive_file, + color: Colors.grey, + ); + + static FileIconConfig _getFileConfig(String fileName) { + final lowerFileName = fileName.toLowerCase(); + final lastDotIndex = lowerFileName.lastIndexOf('.'); + + if (lastDotIndex == -1) { + return _defaultConfig; // No extension found + } + + final extension = lowerFileName.substring(lastDotIndex); + + for (final config in _fileTypeConfigs.values) { + if (config.extensions.contains(extension)) { + return config; + } + } + + return _defaultConfig; + } + + static IconData getFileIcon(String fileName) { + return _getFileConfig(fileName).icon; + } + + static Color getFileIconColor(String fileName) { + return _getFileConfig(fileName).color; + } +} diff --git a/mobile/apps/locker/lib/utils/snack_bar_utils.dart b/mobile/apps/locker/lib/utils/snack_bar_utils.dart new file mode 100644 index 0000000000..ab03c69ac1 --- /dev/null +++ b/mobile/apps/locker/lib/utils/snack_bar_utils.dart @@ -0,0 +1,36 @@ +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +class SnackBarUtils { + static void showInfoSnackBar(BuildContext context, String message) { + _showSnackBar( + context, + message, + backgroundColor: getEnteColorScheme(context).primary500, + ); + } + + static void showWarningSnackBar(BuildContext context, String message) { + _showSnackBar( + context, + message, + backgroundColor: getEnteColorScheme(context).warning500, + ); + } + + static void _showSnackBar( + BuildContext context, + String message, { + Color? backgroundColor, + }) { + if (!context.mounted) return; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: + backgroundColor ?? getEnteColorScheme(context).primary500, + ), + ); + } +} diff --git a/mobile/apps/locker/linux/.gitignore b/mobile/apps/locker/linux/.gitignore new file mode 100644 index 0000000000..d3896c9844 --- /dev/null +++ b/mobile/apps/locker/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/mobile/apps/locker/linux/CMakeLists.txt b/mobile/apps/locker/linux/CMakeLists.txt new file mode 100644 index 0000000000..0ce05024f9 --- /dev/null +++ b/mobile/apps/locker/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "locker") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "io.ente.locker") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/mobile/apps/locker/linux/flutter/CMakeLists.txt b/mobile/apps/locker/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000000..d5bd01648a --- /dev/null +++ b/mobile/apps/locker/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/mobile/apps/locker/linux/flutter/generated_plugin_registrant.cc b/mobile/apps/locker/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000000..fafe406725 --- /dev/null +++ b/mobile/apps/locker/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,55 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_saver_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin"); + file_saver_plugin_register_with_registrar(file_saver_registrar); + g_autoptr(FlPluginRegistrar) flutter_local_authentication_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalAuthenticationPlugin"); + flutter_local_authentication_plugin_register_with_registrar(flutter_local_authentication_registrar); + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) gtk_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); + gtk_plugin_register_with_registrar(gtk_registrar); + g_autoptr(FlPluginRegistrar) open_file_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); + open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); + g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); + screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); + 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) sodium_libs_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "SodiumLibsPlugin"); + sodium_libs_plugin_register_with_registrar(sodium_libs_registrar); + g_autoptr(FlPluginRegistrar) tray_manager_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin"); + tray_manager_plugin_register_with_registrar(tray_manager_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); + g_autoptr(FlPluginRegistrar) window_manager_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); + window_manager_plugin_register_with_registrar(window_manager_registrar); +} diff --git a/mobile/apps/locker/linux/flutter/generated_plugin_registrant.h b/mobile/apps/locker/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000000..e0f0a47bc0 --- /dev/null +++ b/mobile/apps/locker/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/mobile/apps/locker/linux/flutter/generated_plugins.cmake b/mobile/apps/locker/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000000..a193fb26ff --- /dev/null +++ b/mobile/apps/locker/linux/flutter/generated_plugins.cmake @@ -0,0 +1,35 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_saver + flutter_local_authentication + flutter_secure_storage_linux + gtk + open_file_linux + screen_retriever_linux + sentry_flutter + sodium_libs + tray_manager + url_launcher_linux + window_manager +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/mobile/apps/locker/linux/main.cc b/mobile/apps/locker/linux/main.cc new file mode 100644 index 0000000000..e7c5c54370 --- /dev/null +++ b/mobile/apps/locker/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/mobile/apps/locker/linux/my_application.cc b/mobile/apps/locker/linux/my_application.cc new file mode 100644 index 0000000000..c16008ea45 --- /dev/null +++ b/mobile/apps/locker/linux/my_application.cc @@ -0,0 +1,124 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "locker"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "locker"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/mobile/apps/locker/linux/my_application.h b/mobile/apps/locker/linux/my_application.h new file mode 100644 index 0000000000..72271d5e41 --- /dev/null +++ b/mobile/apps/locker/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/mobile/apps/locker/macos/.gitignore b/mobile/apps/locker/macos/.gitignore new file mode 100644 index 0000000000..746adbb6b9 --- /dev/null +++ b/mobile/apps/locker/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/mobile/apps/locker/macos/Flutter/Flutter-Debug.xcconfig b/mobile/apps/locker/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000000..4b81f9b2d2 --- /dev/null +++ b/mobile/apps/locker/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/mobile/apps/locker/macos/Flutter/Flutter-Release.xcconfig b/mobile/apps/locker/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000000..5caa9d1579 --- /dev/null +++ b/mobile/apps/locker/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/mobile/apps/locker/macos/Flutter/GeneratedPluginRegistrant.swift b/mobile/apps/locker/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000000..1eca850c4c --- /dev/null +++ b/mobile/apps/locker/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,50 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import app_links +import device_info_plus +import file_picker +import file_saver +import flutter_inappwebview_macos +import flutter_local_authentication +import flutter_secure_storage_macos +import local_auth_darwin +import open_file_mac +import package_info_plus +import path_provider_foundation +import screen_retriever_macos +import sentry_flutter +import share_plus +import shared_preferences_foundation +import sodium_libs +import sqflite_darwin +import tray_manager +import url_launcher_macos +import window_manager + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) + InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) + FlutterLocalAuthenticationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalAuthenticationPlugin")) + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin")) + OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) + SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SodiumLibsPlugin.register(with: registry.registrar(forPlugin: "SodiumLibsPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) +} diff --git a/mobile/apps/locker/macos/Podfile b/mobile/apps/locker/macos/Podfile new file mode 100644 index 0000000000..b52666a103 --- /dev/null +++ b/mobile/apps/locker/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/mobile/apps/locker/macos/Podfile.lock b/mobile/apps/locker/macos/Podfile.lock new file mode 100644 index 0000000000..c6e7122103 --- /dev/null +++ b/mobile/apps/locker/macos/Podfile.lock @@ -0,0 +1,152 @@ +PODS: + - app_links (1.0.0): + - FlutterMacOS + - cupertino_http (0.0.1): + - Flutter + - FlutterMacOS + - device_info_plus (0.0.1): + - FlutterMacOS + - file_picker (0.0.1): + - FlutterMacOS + - file_saver (0.0.1): + - FlutterMacOS + - flutter_inappwebview_macos (0.0.1): + - FlutterMacOS + - OrderedSet (~> 6.0.3) + - flutter_local_authentication (1.2.0): + - FlutterMacOS + - flutter_secure_storage_macos (6.1.3): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - local_auth_darwin (0.0.1): + - Flutter + - FlutterMacOS + - objective_c (0.0.1): + - FlutterMacOS + - OrderedSet (6.0.3) + - package_info_plus (0.0.1): + - FlutterMacOS + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - screen_retriever_macos (0.0.1): + - FlutterMacOS + - Sentry/HybridSDK (8.46.0) + - sentry_flutter (8.14.2): + - Flutter + - FlutterMacOS + - Sentry/HybridSDK (= 8.46.0) + - share_plus (0.0.1): + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sodium_libs (2.2.1): + - FlutterMacOS + - tray_manager (0.0.1): + - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS + - window_manager (0.2.0): + - FlutterMacOS + +DEPENDENCIES: + - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) + - cupertino_http (from `Flutter/ephemeral/.symlinks/plugins/cupertino_http/darwin`) + - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) + - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) + - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) + - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) + - flutter_local_authentication (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_authentication/macos`) + - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`) + - objective_c (from `Flutter/ephemeral/.symlinks/plugins/objective_c/macos`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) + - sentry_flutter (from `Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos`) + - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - sodium_libs (from `Flutter/ephemeral/.symlinks/plugins/sodium_libs/macos`) + - tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) + +SPEC REPOS: + trunk: + - OrderedSet + - Sentry + +EXTERNAL SOURCES: + app_links: + :path: Flutter/ephemeral/.symlinks/plugins/app_links/macos + cupertino_http: + :path: Flutter/ephemeral/.symlinks/plugins/cupertino_http/darwin + device_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos + file_picker: + :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos + file_saver: + :path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos + flutter_inappwebview_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos + flutter_local_authentication: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_authentication/macos + flutter_secure_storage_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos + FlutterMacOS: + :path: Flutter/ephemeral + local_auth_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin + objective_c: + :path: Flutter/ephemeral/.symlinks/plugins/objective_c/macos + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + screen_retriever_macos: + :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos + sentry_flutter: + :path: Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos + share_plus: + :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + sodium_libs: + :path: Flutter/ephemeral/.symlinks/plugins/sodium_libs/macos + tray_manager: + :path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + window_manager: + :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos + +SPEC CHECKSUMS: + app_links: 86a57d95d4dec830373b8c85c21d1c59a4a5dc21 + cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba + device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f + file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af + file_saver: 44e6fbf666677faf097302460e214e977fdd977b + flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b + flutter_local_authentication: 85674893931e1c9cfa7c9e4f5973cb8c56b018b0 + flutter_secure_storage_macos: c2754d3483d20bb207bb9e5a14f1b8e771abcdb9 + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3 + objective_c: e5f8194456e8fc943e034d1af00510a1bc29c067 + OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 + package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161 + Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854 + sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b + share_plus: 1fa619de8392a4398bfaf176d441853922614e89 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sodium_libs: d39bd76697736cb11ce4a0be73b9b4bc64466d6f + tray_manager: 9064e219c56d75c476e46b9a21182087930baf90 + url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 + window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 + +PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 + +COCOAPODS: 1.16.2 diff --git a/mobile/apps/locker/macos/Runner.xcodeproj/project.pbxproj b/mobile/apps/locker/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..ae214f8ff9 --- /dev/null +++ b/mobile/apps/locker/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,809 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 0BE25975B65D6BA73D3A1354 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 525AD39957F290EBEA71D275 /* Pods_Runner.framework */; }; + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + C9A78EA7917BBDA4B497AD6E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 858DFB73D44507E1FE278030 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* locker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = locker.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 525AD39957F290EBEA71D275 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 54856530D3CF926D9FC7D5AB /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 56670D826B6B386CB46D62A4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 6C5BBAB371CF35A643A7437A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 858DFB73D44507E1FE278030 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A7673CE67E05A584D01EA757 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + F74F319179D2B77563D2E697 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + FFAC0A535D20ED9BDF0ADAD2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C9A78EA7917BBDA4B497AD6E /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0BE25975B65D6BA73D3A1354 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + B360C8315A7BC96A51347C06 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* locker.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + B360C8315A7BC96A51347C06 /* Pods */ = { + isa = PBXGroup; + children = ( + 56670D826B6B386CB46D62A4 /* Pods-Runner.debug.xcconfig */, + FFAC0A535D20ED9BDF0ADAD2 /* Pods-Runner.release.xcconfig */, + 6C5BBAB371CF35A643A7437A /* Pods-Runner.profile.xcconfig */, + F74F319179D2B77563D2E697 /* Pods-RunnerTests.debug.xcconfig */, + 54856530D3CF926D9FC7D5AB /* Pods-RunnerTests.release.xcconfig */, + A7673CE67E05A584D01EA757 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 525AD39957F290EBEA71D275 /* Pods_Runner.framework */, + 858DFB73D44507E1FE278030 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 85A4B508E9D300E4FD85782F /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 4C5450204892FC1CE86D7750 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + B035DB2B7066A1CE24DA098C /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* locker.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 4C5450204892FC1CE86D7750 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 85A4B508E9D300E4FD85782F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B035DB2B7066A1CE24DA098C /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F74F319179D2B77563D2E697 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/locker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/locker"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 54856530D3CF926D9FC7D5AB /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/locker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/locker"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A7673CE67E05A584D01EA757 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/locker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/locker"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 6Z68YJY9Q2; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 6Z68YJY9Q2; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 6Z68YJY9Q2; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/mobile/apps/locker/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/apps/locker/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/mobile/apps/locker/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/apps/locker/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/apps/locker/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000..0e55ebac0f --- /dev/null +++ b/mobile/apps/locker/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/locker/macos/Runner.xcworkspace/contents.xcworkspacedata b/mobile/apps/locker/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..21a3cc14c7 --- /dev/null +++ b/mobile/apps/locker/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/mobile/apps/locker/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/apps/locker/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/mobile/apps/locker/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/apps/locker/macos/Runner/AppDelegate.swift b/mobile/apps/locker/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000000..8e02df2888 --- /dev/null +++ b/mobile/apps/locker/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..a2ec33f19f --- /dev/null +++ b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000..82b6f9d9a3 Binary files /dev/null and b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000..13b35eba55 Binary files /dev/null and b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000..0a3f5fa40f Binary files /dev/null and b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000000..bdb57226d5 Binary files /dev/null and b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000000..f083318e09 Binary files /dev/null and b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000000..326c0e72c9 Binary files /dev/null and b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000..2f1632cfdd Binary files /dev/null and b/mobile/apps/locker/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/mobile/apps/locker/macos/Runner/Base.lproj/MainMenu.xib b/mobile/apps/locker/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000000..80e867a4e0 --- /dev/null +++ b/mobile/apps/locker/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/locker/macos/Runner/Configs/AppInfo.xcconfig b/mobile/apps/locker/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000000..1463149715 --- /dev/null +++ b/mobile/apps/locker/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,17 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = locker + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = io.ente.locker + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 Ente Technologies, Inc. All rights reserved. + +// The minimum macOS deployment target +MACOSX_DEPLOYMENT_TARGET = 10.15 diff --git a/mobile/apps/locker/macos/Runner/Configs/Debug.xcconfig b/mobile/apps/locker/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000000..36b0fd9464 --- /dev/null +++ b/mobile/apps/locker/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/mobile/apps/locker/macos/Runner/Configs/Release.xcconfig b/mobile/apps/locker/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000000..dff4f49561 --- /dev/null +++ b/mobile/apps/locker/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/mobile/apps/locker/macos/Runner/Configs/Warnings.xcconfig b/mobile/apps/locker/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000000..42bcbf4780 --- /dev/null +++ b/mobile/apps/locker/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/mobile/apps/locker/macos/Runner/DebugProfile.entitlements b/mobile/apps/locker/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000000..7c098966de --- /dev/null +++ b/mobile/apps/locker/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,18 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + com.apple.security.files.user-selected.read-only + + keychain-access-groups + + + diff --git a/mobile/apps/locker/macos/Runner/Info.plist b/mobile/apps/locker/macos/Runner/Info.plist new file mode 100644 index 0000000000..4789daa6a4 --- /dev/null +++ b/mobile/apps/locker/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/mobile/apps/locker/macos/Runner/MainFlutterWindow.swift b/mobile/apps/locker/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000000..3cc05eb234 --- /dev/null +++ b/mobile/apps/locker/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/mobile/apps/locker/macos/Runner/Release.entitlements b/mobile/apps/locker/macos/Runner/Release.entitlements new file mode 100644 index 0000000000..1de9b453af --- /dev/null +++ b/mobile/apps/locker/macos/Runner/Release.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + keychain-access-groups + + + diff --git a/mobile/apps/locker/macos/RunnerTests/RunnerTests.swift b/mobile/apps/locker/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000000..61f3bd1fc5 --- /dev/null +++ b/mobile/apps/locker/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/mobile/apps/locker/pubspec.lock b/mobile/apps/locker/pubspec.lock new file mode 100644 index 0000000000..a69b237c67 --- /dev/null +++ b/mobile/apps/locker/pubspec.lock @@ -0,0 +1,1471 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + adaptive_theme: + dependency: "direct main" + description: + name: adaptive_theme + sha256: f4ee609b464e5efc68131d9d15ba9aa1de4e3b5ede64be17781c6e19a52d637d + url: "https://pub.dev" + source: hosted + version: "3.6.0" + app_links: + dependency: transitive + description: + name: app_links + sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba" + url: "https://pub.dev" + source: hosted + version: "6.4.0" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + bip39: + dependency: transitive + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: "direct main" + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cronet_http: + dependency: transitive + description: + name: cronet_http + sha256: "5ed075c59b2d4bd43af4e73d906b8082e98ecd2af9c625327370ef28361bf635" + url: "https://pub.dev" + source: hosted + version: "1.3.4" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_http: + dependency: transitive + description: + name: cupertino_http + sha256: "8fb9e2c36d0732d9d96abd76683406b57e78a2514e27c962e0c603dbe6f2e3f8" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + dio: + dependency: "direct main" + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + dotted_border: + dependency: transitive + description: + name: dotted_border + sha256: "99b091ec6891ba0c5331fdc2b502993c7c108f898995739a73c6845d71dad70c" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + email_validator: + dependency: "direct main" + description: + name: email_validator + sha256: b19aa5d92fdd76fbc65112060c94d45ba855105a28bb6e462de7ff03b12fa1fb + url: "https://pub.dev" + source: hosted + version: "3.0.0" + ente_accounts: + dependency: "direct main" + description: + path: "../../packages/accounts" + relative: true + source: path + version: "1.0.0" + ente_base: + dependency: "direct main" + description: + path: "../../packages/base" + relative: true + source: path + version: "1.0.0" + ente_configuration: + dependency: "direct main" + description: + path: "../../packages/configuration" + relative: true + source: path + version: "1.0.0" + ente_crypto_dart: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: f91e1545f8263df127762240c4da54a0c42835b2 + url: "https://github.com/ente-io/ente_crypto_dart.git" + source: git + version: "1.0.0" + ente_events: + dependency: "direct main" + description: + path: "../../packages/events" + relative: true + source: path + version: "1.0.0" + ente_lock_screen: + dependency: "direct main" + description: + path: "../../packages/lock_screen" + relative: true + source: path + version: "1.0.0" + ente_logging: + dependency: "direct main" + description: + path: "../../packages/logging" + relative: true + source: path + version: "1.0.0" + ente_network: + dependency: "direct main" + description: + path: "../../packages/network" + relative: true + source: path + version: "1.0.0" + ente_strings: + dependency: "direct main" + description: + path: "../../packages/strings" + relative: true + source: path + version: "1.0.0" + ente_ui: + dependency: "direct main" + description: + path: "../../packages/ui" + relative: true + source: path + version: "1.0.0" + ente_utils: + dependency: "direct main" + description: + path: "../../packages/utils" + relative: true + source: path + version: "1.0.0" + event_bus: + dependency: "direct main" + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + expandable: + dependency: transitive + description: + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + fast_base58: + dependency: "direct main" + description: + name: fast_base58 + sha256: "611f65633b734f27a850b51371b3eba993a5165650e12e8e7b02959f3768ba06" + url: "https://pub.dev" + source: hosted + version: "0.2.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: ef9908739bdd9c476353d6adff72e88fd00c625f5b959ae23f7567bd5137db0a + url: "https://pub.dev" + source: hosted + version: "10.2.0" + file_saver: + dependency: transitive + description: + name: file_saver + sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_animate: + dependency: transitive + description: + name: flutter_animate + sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" + url: "https://pub.dev" + source: hosted + version: "4.5.2" + flutter_displaymode: + dependency: "direct main" + description: + name: flutter_displaymode + sha256: "42c5e9abd13d28ed74f701b60529d7f8416947e58256e6659c5550db719c57ef" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_email_sender: + dependency: transitive + description: + name: flutter_email_sender + sha256: d39eb5e91358fc19ec4050da69accec21f9d5b2b6bcf188aa246327b6ca2352c + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter_inappwebview: + dependency: transitive + description: + name: flutter_inappwebview + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_windows: + dependency: transitive + description: + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_local_authentication: + dependency: "direct main" + description: + path: "." + ref: "1ac346a04592a05fd75acccf2e01fa3c7e955d96" + resolved-ref: "1ac346a04592a05fd75acccf2e01fa3c7e955d96" + url: "https://github.com/eaceto/flutter_local_authentication" + source: git + version: "1.2.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "1c2b787f99bdca1f3718543f81d38aa1b124817dfeb9fb196201bea85b6134bf" + url: "https://pub.dev" + source: hosted + version: "2.0.26" + flutter_secure_storage: + dependency: transitive + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" + url: "https://pub.dev" + source: hosted + version: "0.1.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: transitive + description: + name: fluttertoast + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + url: "https://pub.dev" + source: hosted + version: "8.2.12" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.dev" + source: hosted + version: "2.1.0" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: "direct main" + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + http_profile: + dependency: transitive + description: + name: http_profile + sha256: "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + io: + dependency: "direct main" + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: "459727a9daf91bdfb39b014cf3c186cf77f0136124a274ac83c186e12262ac4e" + url: "https://pub.dev" + source: hosted + version: "0.12.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + listen_sharing_intent: + dependency: "direct main" + description: + name: listen_sharing_intent + sha256: df12e6c78666018985c39df494fc12412ece6510c38c79fe1230f2abef0965fa + url: "https://pub.dev" + source: hosted + version: "1.9.2" + local_auth: + dependency: transitive + description: + name: local_auth + sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + local_auth_android: + dependency: transitive + description: + name: local_auth_android + sha256: "8bba79f4f0f7bc812fce2ca20915d15618c37721246ba6c3ef2aa7a763a90cf2" + url: "https://pub.dev" + source: hosted + version: "1.0.47" + local_auth_darwin: + dependency: transitive + description: + name: local_auth_darwin + sha256: "630996cd7b7f28f5ab92432c4b35d055dd03a747bc319e5ffbb3c4806a3e50d2" + url: "https://pub.dev" + source: hosted + version: "1.4.3" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" + url: "https://pub.dev" + source: hosted + version: "1.0.10" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + url: "https://pub.dev" + source: hosted + version: "1.0.11" + logging: + dependency: "direct main" + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + menu_base: + dependency: transitive + description: + name: menu_base + sha256: "820368014a171bd1241030278e6c2617354f492f5c703d7b7d4570a6b8b84405" + url: "https://pub.dev" + source: hosted + version: "0.1.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + modal_bottom_sheet: + dependency: transitive + description: + name: modal_bottom_sheet + sha256: eac66ef8cb0461bf069a38c5eb0fa728cee525a531a8304bd3f7b2185407c67e + url: "https://pub.dev" + source: hosted + version: "3.0.0" + native_dio_adapter: + dependency: transitive + description: + name: native_dio_adapter + sha256: "7420bc9517b2abe09810199a19924617b45690a44ecfb0616ac9babc11875c03" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "9f034ba1eeca53ddb339bc8f4813cb07336a849cd735559b60cdc068ecce2dc7" + url: "https://pub.dev" + source: hosted + version: "7.1.0" + open_file: + dependency: "direct main" + description: + name: open_file + sha256: d17e2bddf5b278cb2ae18393d0496aa4f162142ba97d1a9e0c30d476adf99c0e + url: "https://pub.dev" + source: hosted + version: "3.5.10" + open_file_android: + dependency: transitive + description: + name: open_file_android + sha256: "58141fcaece2f453a9684509a7275f231ac0e3d6ceb9a5e6de310a7dff9084aa" + url: "https://pub.dev" + source: hosted + version: "1.0.6" + open_file_ios: + dependency: transitive + description: + name: open_file_ios + sha256: "02996f01e5f6863832068e97f8f3a5ef9b613516db6897f373b43b79849e4d07" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + open_file_linux: + dependency: transitive + description: + name: open_file_linux + sha256: d189f799eecbb139c97f8bc7d303f9e720954fa4e0fa1b0b7294767e5f2d7550 + url: "https://pub.dev" + source: hosted + version: "0.0.5" + open_file_mac: + dependency: transitive + description: + name: open_file_mac + sha256: "1440b1e37ceb0642208cfeb2c659c6cda27b25187a90635c9d1acb7d0584d324" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + open_file_platform_interface: + dependency: transitive + description: + name: open_file_platform_interface + sha256: "101b424ca359632699a7e1213e83d025722ab668b9fd1412338221bf9b0e5757" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + open_file_web: + dependency: transitive + description: + name: open_file_web + sha256: e3dbc9584856283dcb30aef5720558b90f88036360bd078e494ab80a80130c4f + url: "https://pub.dev" + source: hosted + version: "0.0.4" + open_file_windows: + dependency: transitive + description: + name: open_file_windows + sha256: d26c31ddf935a94a1a3aa43a23f4fff8a5ff4eea395fe7a8cb819cf55431c875 + url: "https://pub.dev" + source: hosted + version: "0.0.3" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + password_strength: + dependency: transitive + description: + name: password_strength + sha256: "0e51e3d864e37873a1347e658147f88b66e141ee36c58e19828dc5637961e1ce" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + pinput: + dependency: transitive + description: + name: pinput + sha256: "8a73be426a91fefec90a7f130763ca39772d547e92f19a827cf4aa02e323d35a" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + posix: + dependency: transitive + description: + name: posix + sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + privacy_screen: + dependency: transitive + description: + name: privacy_screen + sha256: "2856e3a3ed082061a5cd2a1518f1ce6367c55916fb75e5db72e5983033a1ca54" + url: "https://pub.dev" + source: hosted + version: "0.0.8" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_linux: + dependency: transitive + description: + name: screen_retriever_linux + sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_macos: + dependency: transitive + description: + name: screen_retriever_macos + sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_platform_interface: + dependency: transitive + description: + name: screen_retriever_platform_interface + sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_windows: + dependency: transitive + description: + name: screen_retriever_windows + sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + sentry: + dependency: transitive + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + sentry_flutter: + dependency: transitive + description: + name: sentry_flutter + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + share_plus: + dependency: transitive + description: + name: share_plus + sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0 + url: "https://pub.dev" + source: hosted + version: "11.0.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shortid: + dependency: transitive + description: + name: shortid + sha256: d0b40e3dbb50497dad107e19c54ca7de0d1a274eb9b4404991e443dadb9ebedb + url: "https://pub.dev" + source: hosted + version: "0.1.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + sodium: + dependency: transitive + description: + name: sodium + sha256: d9830a388e37c82891888e64cfd4c6764fa3ac716bed80ac6eab89ee42c3cd76 + url: "https://pub.dev" + source: hosted + version: "2.3.1+1" + sodium_libs: + dependency: transitive + description: + name: sodium_libs + sha256: aa764acd6ccc6113e119c2d99471aeeb4637a9a501639549b297d3a143ff49b3 + url: "https://pub.dev" + source: hosted + version: "2.2.1+6" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + url: "https://pub.dev" + source: hosted + version: "2.5.5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + step_progress_indicator: + dependency: transitive + description: + name: step_progress_indicator + sha256: b51bb1fcfc78454359f0658c5a2c21548c3825ebf76e826308e9ca10f383bbb8 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + styled_text: + dependency: "direct main" + description: + name: styled_text + sha256: fd624172cf629751b4f171dd0ecf9acf02a06df3f8a81bb56c0caa4f1df706c3 + url: "https://pub.dev" + source: hosted + version: "8.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + tray_manager: + dependency: "direct main" + description: + name: tray_manager + sha256: ad18c4cd73003097d182884bacb0578ad2865f3ab842a0ad00f6d043ed49eaf0 + url: "https://pub.dev" + source: hosted + version: "0.5.0" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + ua_client_hints: + dependency: transitive + description: + name: ua_client_hints + sha256: "1b8759a46bfeab355252881df27f2604c01bded86aa2b578869fb1b638b23118" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + url: "https://pub.dev" + source: hosted + version: "6.3.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + window_manager: + dependency: "direct main" + description: + name: window_manager + sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xmlstream: + dependency: transitive + description: + name: xmlstream + sha256: cfc14e3f256997897df9481ae630d94c2d85ada5187ebeb868bb1aabc2c977b4 + url: "https://pub.dev" + source: hosted + version: "1.1.1" +sdks: + dart: ">=3.7.2 <4.0.0" + flutter: ">=3.24.0" diff --git a/mobile/apps/locker/pubspec.yaml b/mobile/apps/locker/pubspec.yaml new file mode 100644 index 0000000000..1e1197b001 --- /dev/null +++ b/mobile/apps/locker/pubspec.yaml @@ -0,0 +1,87 @@ +name: locker +description: "Ente Locker – Safe space for your important documents" +publish_to: "none" +version: 0.1.0 + +environment: + sdk: ^3.6.0 + +dependencies: + adaptive_theme: ^3.6.0 + collection: ^1.18.0 + dio: ^5.8.0+1 + email_validator: ^3.0.0 + ente_accounts: + path: ../../packages/accounts + ente_base: + path: ../../packages/base + ente_configuration: + path: ../../packages/configuration + ente_crypto_dart: + git: + url: https://github.com/ente-io/ente_crypto_dart.git + ente_events: + path: ../../packages/events + ente_lock_screen: + path: ../../packages/lock_screen + ente_logging: + path: ../../packages/logging + ente_network: + path: ../../packages/network + ente_strings: + path: ../../packages/strings + ente_ui: + path: ../../packages/ui + ente_utils: + path: ../../packages/utils + event_bus: ^2.0.1 + fast_base58: ^0.2.1 + file_picker: ^10.2.0 + flutter: + sdk: flutter + flutter_displaymode: ^0.6.0 + flutter_local_authentication: + git: + url: https://github.com/eaceto/flutter_local_authentication + ref: 1ac346a04592a05fd75acccf2e01fa3c7e955d96 + flutter_localizations: + sdk: flutter + http: ^1.4.0 + intl: ^0.20.2 + io: ^1.0.5 + listen_sharing_intent: ^1.9.2 + logging: ^1.3.0 + open_file: ^3.5.10 + package_info_plus: ^8.3.0 + path: ^1.9.0 + path_provider: ^2.1.5 + shared_preferences: ^2.5.3 + sqflite: ^2.4.1 + styled_text: ^8.1.0 + tray_manager: ^0.5.0 + uuid: ^4.5.1 + window_manager: ^0.5.0 + +dev_dependencies: + flutter_lints: ^4.0.0 + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true + generate: true + + assets: + - assets/ + + fonts: + - family: Inter + fonts: + - asset: assets/fonts/Inter-Regular.ttf + - asset: assets/fonts/Inter-Medium.ttf + - asset: assets/fonts/Inter-Light.ttf + - asset: assets/fonts/Inter-SemiBold.ttf + - asset: assets/fonts/Inter-Bold.ttf + - family: Montserrat + fonts: + - asset: assets/fonts/Montserrat-Bold.ttf diff --git a/mobile/apps/locker/web/favicon.png b/mobile/apps/locker/web/favicon.png new file mode 100644 index 0000000000..8aaa46ac1a Binary files /dev/null and b/mobile/apps/locker/web/favicon.png differ diff --git a/mobile/apps/locker/web/icons/Icon-192.png b/mobile/apps/locker/web/icons/Icon-192.png new file mode 100644 index 0000000000..b749bfef07 Binary files /dev/null and b/mobile/apps/locker/web/icons/Icon-192.png differ diff --git a/mobile/apps/locker/web/icons/Icon-512.png b/mobile/apps/locker/web/icons/Icon-512.png new file mode 100644 index 0000000000..88cfd48dff Binary files /dev/null and b/mobile/apps/locker/web/icons/Icon-512.png differ diff --git a/mobile/apps/locker/web/icons/Icon-maskable-192.png b/mobile/apps/locker/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000..eb9b4d76e5 Binary files /dev/null and b/mobile/apps/locker/web/icons/Icon-maskable-192.png differ diff --git a/mobile/apps/locker/web/icons/Icon-maskable-512.png b/mobile/apps/locker/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000..d69c56691f Binary files /dev/null and b/mobile/apps/locker/web/icons/Icon-maskable-512.png differ diff --git a/mobile/apps/locker/web/index.html b/mobile/apps/locker/web/index.html new file mode 100644 index 0000000000..ec3b5634e6 --- /dev/null +++ b/mobile/apps/locker/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + Ente Locker + + + + + + diff --git a/mobile/apps/locker/web/manifest.json b/mobile/apps/locker/web/manifest.json new file mode 100644 index 0000000000..3edea1b115 --- /dev/null +++ b/mobile/apps/locker/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "Ente Locker", + "short_name": "Ente Locker", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/mobile/apps/locker/windows/.gitignore b/mobile/apps/locker/windows/.gitignore new file mode 100644 index 0000000000..d492d0d98c --- /dev/null +++ b/mobile/apps/locker/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/mobile/apps/locker/windows/CMakeLists.txt b/mobile/apps/locker/windows/CMakeLists.txt new file mode 100644 index 0000000000..5c2121d6e0 --- /dev/null +++ b/mobile/apps/locker/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(locker LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "locker") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/mobile/apps/locker/windows/flutter/CMakeLists.txt b/mobile/apps/locker/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000000..903f4899d6 --- /dev/null +++ b/mobile/apps/locker/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/mobile/apps/locker/windows/flutter/generated_plugin_registrant.cc b/mobile/apps/locker/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000000..725202fc0a --- /dev/null +++ b/mobile/apps/locker/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,50 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); + FileSaverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSaverPlugin")); + FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); + FlutterLocalAuthenticationPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterLocalAuthenticationPluginCApi")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + LocalAuthPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("LocalAuthPlugin")); + ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); + SentryFlutterPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SentryFlutterPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + SodiumLibsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SodiumLibsPluginCApi")); + TrayManagerPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("TrayManagerPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); + WindowManagerPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowManagerPlugin")); +} diff --git a/mobile/apps/locker/windows/flutter/generated_plugin_registrant.h b/mobile/apps/locker/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000000..dc139d85a9 --- /dev/null +++ b/mobile/apps/locker/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/mobile/apps/locker/windows/flutter/generated_plugins.cmake b/mobile/apps/locker/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000000..ddd64f7ebe --- /dev/null +++ b/mobile/apps/locker/windows/flutter/generated_plugins.cmake @@ -0,0 +1,37 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + app_links + file_saver + flutter_inappwebview_windows + flutter_local_authentication + flutter_secure_storage_windows + local_auth_windows + screen_retriever_windows + sentry_flutter + share_plus + sodium_libs + tray_manager + url_launcher_windows + window_manager +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/mobile/apps/locker/windows/runner/CMakeLists.txt b/mobile/apps/locker/windows/runner/CMakeLists.txt new file mode 100644 index 0000000000..394917c053 --- /dev/null +++ b/mobile/apps/locker/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/mobile/apps/locker/windows/runner/Runner.rc b/mobile/apps/locker/windows/runner/Runner.rc new file mode 100644 index 0000000000..793fab1e4e --- /dev/null +++ b/mobile/apps/locker/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "io.ente" "\0" + VALUE "FileDescription", "locker" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "locker" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 Ente Technologies, Inc. All rights reserved." "\0" + VALUE "OriginalFilename", "locker.exe" "\0" + VALUE "ProductName", "locker" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/mobile/apps/locker/windows/runner/flutter_window.cpp b/mobile/apps/locker/windows/runner/flutter_window.cpp new file mode 100644 index 0000000000..955ee3038f --- /dev/null +++ b/mobile/apps/locker/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/mobile/apps/locker/windows/runner/flutter_window.h b/mobile/apps/locker/windows/runner/flutter_window.h new file mode 100644 index 0000000000..6da0652f05 --- /dev/null +++ b/mobile/apps/locker/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/mobile/apps/locker/windows/runner/main.cpp b/mobile/apps/locker/windows/runner/main.cpp new file mode 100644 index 0000000000..aa0a106057 --- /dev/null +++ b/mobile/apps/locker/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"locker", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/mobile/apps/locker/windows/runner/resource.h b/mobile/apps/locker/windows/runner/resource.h new file mode 100644 index 0000000000..66a65d1e4a --- /dev/null +++ b/mobile/apps/locker/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/mobile/apps/locker/windows/runner/resources/app_icon.ico b/mobile/apps/locker/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000..c04e20caf6 Binary files /dev/null and b/mobile/apps/locker/windows/runner/resources/app_icon.ico differ diff --git a/mobile/apps/locker/windows/runner/runner.exe.manifest b/mobile/apps/locker/windows/runner/runner.exe.manifest new file mode 100644 index 0000000000..153653e8d6 --- /dev/null +++ b/mobile/apps/locker/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/mobile/apps/locker/windows/runner/utils.cpp b/mobile/apps/locker/windows/runner/utils.cpp new file mode 100644 index 0000000000..3a0b46511a --- /dev/null +++ b/mobile/apps/locker/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/mobile/apps/locker/windows/runner/utils.h b/mobile/apps/locker/windows/runner/utils.h new file mode 100644 index 0000000000..3879d54755 --- /dev/null +++ b/mobile/apps/locker/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/mobile/apps/locker/windows/runner/win32_window.cpp b/mobile/apps/locker/windows/runner/win32_window.cpp new file mode 100644 index 0000000000..60608d0fe5 --- /dev/null +++ b/mobile/apps/locker/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/mobile/apps/locker/windows/runner/win32_window.h b/mobile/apps/locker/windows/runner/win32_window.h new file mode 100644 index 0000000000..e901dde684 --- /dev/null +++ b/mobile/apps/locker/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/mobile/apps/photos/README.md b/mobile/apps/photos/README.md index 4b5f015838..8558c8ba30 100644 --- a/mobile/apps/photos/README.md +++ b/mobile/apps/photos/README.md @@ -46,7 +46,7 @@ You can alternatively install the build from PlayStore or F-Droid. ## 🧑‍💻 Building from source -1. Install [Flutter v3.32.8](https://flutter.dev/docs/get-started/install) and [Rust v1.85.1](https://www.rust-lang.org/tools/install). +1. Install [Flutter v3.32.8](https://flutter.dev/docs/get-started/install) and [Rust](https://www.rust-lang.org/tools/install). 2. Install [Flutter Rust Bridge](https://cjycode.com/flutter_rust_bridge/) with `cargo install flutter_rust_bridge_codegen` diff --git a/mobile/apps/photos/assets/ml/text_embeddings.json b/mobile/apps/photos/assets/ml/text_embeddings.json new file mode 100644 index 0000000000..5714cbdc67 --- /dev/null +++ b/mobile/apps/photos/assets/ml/text_embeddings.json @@ -0,0 +1,3530 @@ +{ + "version": "1.0.0", + "embeddings": { + "clip_positive": { + "prompt": "Photo of a precious and nostalgic memory radiating warmth, vibrant energy, or quiet beauty — alive with color, light, or emotion", + "vector": [ + -0.00029399772756733, -0.0024989808443933725, 0.0030610354151576757, + -0.04166121035814285, 0.03460526093840599, 0.050619494169950485, + -0.0188677366822958, 0.017285337671637535, -0.00911393016576767, + -0.013402838259935379, 0.0120625551789999, 0.0006917593418620527, + -0.02497251331806183, -0.022732943296432495, 0.029347892850637436, + 0.5122391581535339, -0.01061850693076849, -0.003770088544115424, + 0.012226847000420094, -0.05049843713641167, 0.0034501501359045506, + -0.02298370562493801, -0.03317851200699806, 0.0007177003426477313, + 0.03588501736521721, -0.05409558489918709, 0.038496412336826324, + 0.02857831120491028, -0.01419836189597845, 0.016654107719659805, + -0.010687682777643204, -0.0005015255883336067, 0.011595616117119789, + -0.020847897976636887, -0.011708027683198452, -0.020311785861849785, + -0.007099180947989225, -0.008629699237644672, -0.025595098733901978, + -0.028958778828382492, 0.014060010202229023, -0.020164787769317627, + 0.01764851063489914, -0.006684124935418367, -0.0515793077647686, + -0.034864671528339386, 0.006900300271809101, -0.015357058495283127, + -0.010073745623230934, 0.02983212284743786, 0.02481686696410179, + 0.05888601765036583, 0.008344347588717937, -0.04051980748772621, + -0.023208526894450188, -0.008880460634827614, -0.02613985724747181, + -0.017466925084590912, -0.02899336628615856, 0.007574765477329493, + 0.005715662147849798, 0.0017726332880556583, 0.03105134889483452, + 0.0461922325193882, -0.07244450598955154, 0.03219275176525116, + -0.013783305883407593, -0.008759403601288795, 0.041064564138650894, + 0.1308203488588333, 0.010998974554240704, 0.002741096541285515, + -0.006476596929132938, -0.007246179506182671, -0.003692265832796693, + 0.0019196323119103909, -0.05541857331991196, -0.024730399250984192, + -0.013100193813443184, -0.022196829319000244, -0.028197843581438065, + -0.02845725230872631, 0.05446740612387657, 0.025188688188791275, + 0.00250762770883739, 0.000709053419996053, 0.005534074734896421, + -0.01690487004816532, 0.036066606640815735, -0.02184230275452137, + 0.026598148047924042, -0.004738552030175924, 0.011768556199967861, + 0.0007177003426477313, -0.006554420106112957, 0.012996429577469826, + -0.012676490470767021, 0.008292465470731258, -0.013281780295073986, + -0.014008128084242344, 0.024574752897024155, 0.02172989211976528, + -0.026814322918653488, 0.0041851443238556385, 0.020839251577854156, + -0.004401318728923798, -0.06879547238349915, -0.01034180261194706, + 0.0008992872317321599, 0.010436920449137688, -0.014950649812817574, + -0.020406901836395264, 0.13213469088077545, -0.009822983294725418, + -0.014103244990110397, -0.01761392317712307, 0.018366212025284767, + 0.013108840212225914, -0.0133509561419487, 0.0009944040793925524, + 0.013437426649034023, -0.025984210893511772, -0.011457265354692936, + 0.008110878989100456, 0.02050201967358589, 0.03446691110730171, + 0.021470481529831886, 0.002836213679984212, -0.00470396364107728, + 0.003147505223751068, -0.04847504198551178, 0.002239570952951908, + 0.016213109716773033, -0.013394190929830074, -0.005931836552917957, + -0.033143918961286545, -0.02115054242312908, -0.0012451668735593557, + -0.002792978659272194, 0.007669882383197546, 0.019818907603621483, + -0.007133768871426582, 0.01491606142371893, -0.023805169388651848, + 0.005404370371252298, -0.0260793287307024, -0.019075265154242516, + 0.010350449942052364, -0.009710571728646755, 0.022041182965040207, + -0.031284816563129425, -0.012953193858265877, -0.025223277509212494, + -0.018997443839907646, -0.042716141790151596, -0.05844501778483391, + 0.03895470127463341, -0.01744963228702545, 0.024635281413793564, + 0.023364173248410225, -0.01995725743472576, 0.009010165929794312, + -0.003735500853508711, -0.006571713835000992, -0.039144936949014664, + -0.030774645507335663, 0.02484280988574028, -0.5175830125808716, + -0.00013835188292432576, -0.029460303485393524, -0.04680616781115532, + 0.07913727313280106, 0.001184638007543981, -0.0010376391001045704, + 0.0103677436709404, 0.013930304907262325, -0.03406050056219101, + -0.022041182965040207, -0.0009165811934508383, 0.038513705134391785, + 0.007324002683162689, -0.0007522883242927492, 0.0077649992890655994, + -0.0019109854474663734, -0.013212604448199272, -0.035461317747831345, + 0.014717180281877518, -0.016334168612957, 0.02303558774292469, + -0.028232431039214134, -0.019723789766430855, -0.039118994027376175, + 0.04071004316210747, -0.03521054983139038, 0.03808135166764259, + -0.033066097646951675, 0.022058477625250816, -0.056828033179044724, + -0.03027312271296978, -0.016766518354415894, 0.04006151854991913, + -0.06835447251796722, 0.03892011195421219, 0.0032945042476058006, + -0.014587475918233395, 0.010289921425282955, 0.05095672607421875, + -0.015063060447573662, -0.008396229706704617, -0.017302630469202995, + -0.02613985724747181, 0.023173939436674118, -0.058012671768665314, + 0.038505055010318756, -0.025067631155252457, -0.017795508727431297, + 0.03590231016278267, 0.004807727411389351, 0.022231418639421463, + -0.029304657131433487, -0.021980654448270798, -0.016558989882469177, + -0.005413017235696316, 0.0230528824031353, 0.025819920003414154, + 0.012693784199655056, -0.010921151377260685, 0.0011586970649659634, + 0.026165800169110298, -0.04114238917827606, -0.02717749774456024, + 0.017155632376670837, -0.010549331083893776, -0.019057970494031906, + -0.007254826370626688, -0.024756338447332382, -0.0039170878008008, + -0.022854000329971313, -0.007220238912850618, -0.00874210987240076, + 0.001642928458750248, 0.017216162756085396, -0.03951675444841385, + 0.006787888705730438, -0.08730003982782364, -0.004505082964897156, + -0.04747198894619942, 0.03839264437556267, -0.0714760422706604, + -0.02231788821518421, 0.02241300418972969, -0.01946437917649746, + -0.037977591156959534, 0.05524563416838646, -0.05752843618392944, + -0.0018331623869016767, -0.022577296942472458, -0.02236112207174301, + -0.00878534372895956, 0.0025595095939934254, 0.014976590871810913, + -0.03655948489904404, -0.005629192106425762, 0.002792978659272194, + 0.020303137600421906, -0.03268563002347946, 0.006104776635766029, + -0.03560831397771835, 0.014475065283477306, 0.03400862216949463, + -0.028379429131746292, 0.002991859335452318, -0.007384531665593386, + -0.02546539157629013, 0.028102725744247437, 0.02373599447309971, + 0.019879436120390892, -0.013644954189658165, -0.031820930540561676, + -0.05662915110588074, -0.008214643225073814, -0.03750200942158699, + -0.009260928258299828, 0.005101725459098816, 0.012287376448512077, + -0.0027583905030041933, -0.029373833909630775, 0.013316367752850056, + -0.0017380454810336232, -0.037839241325855255, -0.026969969272613525, + -0.017345866188406944, -0.03754524141550064, -0.026451149955391884, + -0.000432349625043571, 0.020432842895388603, -0.05502945929765701, + 0.12296022474765778, 0.03770088404417038, -0.00823193695396185, + -0.01582399755716324, -0.002083925064653158, -0.003778735874220729, + -0.021487776190042496, 0.033636800944805145, 0.012218199670314789, + 0.026831617578864098, 0.03803811967372894, -0.047506578266620636, + -0.006044247653335333, 0.08365964889526367, 0.02974565327167511, + 0.04988449811935425, -0.009900806471705437, 0.019628673791885376, + -0.041557442396879196, -0.051933836191892624, -0.03379244729876518, + -0.036023370921611786, -0.02920953929424286, 0.0035193259827792645, + 0.03687077388167381, -0.051933836191892624, 0.03700912743806839, + -0.046927228569984436, -0.0038652056828141212, 0.0414709746837616, + 0.012909960001707077, -0.032296519726514816, -0.037899766117334366, + 0.023312291130423546, 0.0060356007888913155, 0.02307017706334591, + 0.04563017934560776, -0.01060121227055788, -0.008672933094203472, + -0.028673425316810608, 0.06280311197042465, 0.016109347343444824, + -0.011050855740904808, 0.007799586746841669, 0.013878422789275646, + 0.026503032073378563, 0.015443528071045876, 0.034890614449977875, + 0.0038652056828141212, -0.01761392317712307, 0.0453534759581089, + -0.022611886262893677, -0.010203450918197632, -0.021435894072055817, + 0.03638654574751854, -0.01030721515417099, -0.008880460634827614, + -0.0024643929209560156, 0.01574617438018322, 0.048907388001680374, + -0.007324002683162689, -0.01999184675514698, -0.0032772100530564785, + 0.006148011423647404, -0.017821451649069786, 0.022888587787747383, + -0.00996998231858015, 0.00530060613527894, -0.0028535074088722467, + 0.012174965813755989, -0.007263473235070705, -0.0059664249420166016, + 0.027722256258130074, -0.03201116621494293, 0.012140377424657345, + -0.012339258566498756, -0.03099946863949299, 0.02184230275452137, + -0.023355526849627495, 0.04307067021727562, 0.019663261249661446, + -0.15298259258270264, 0.009044754318892956, 0.0033896209206432104, + 0.0019369263900443912, -0.026433855295181274, -0.002127160085365176, + 0.0015564586501568556, -0.010557977482676506, -0.0012711079325526953, + 0.00004323495886637829, 0.03380109369754791, -0.06299334019422531, + 0.040390100330114365, -0.05773596838116646, 0.023208526894450188, + 0.027644433081150055, 0.005490840412676334, 0.02726396732032299, + -0.006433362141251564, -0.026814322918653488, -0.015296529047191143, + -0.014207008294761181, 0.008915049023926258, -0.034328557550907135, + 0.0038392646238207817, -0.023441996425390244, -0.0007177003426477313, + 0.030688177794218063, -0.06485243886709213, -0.06524156033992767, + -0.010350449942052364, -0.0582980215549469, 0.02074413374066353, + -0.012166318483650684, -0.010497448965907097, -0.06057218462228775, + 0.01513223722577095, -0.008854520507156849, 0.03753659501671791, + -0.06895976513624191, 0.011431324295699596, 0.004055439494550228, + 0.05822884663939476, -0.0021876890677958727, -0.017743628472089767, + -0.008845873177051544, -0.010142921470105648, -0.0071164751425385475, + 0.03211492672562599, 0.008889107964932919, 0.01885044202208519, + 0.014673946425318718, 0.02731584943830967, 0.016636813059449196, + 0.011647499166429043, -0.008370288647711277, -0.06652131676673889, + 0.03981940075755119, -0.012806195765733719, 0.001418106839992106, + 0.01152644120156765, 0.001720751402899623, 0.008889107964932919, + -0.04371919482946396, -0.013307721354067326, 0.004686669912189245, + 0.0047644926235079765, -0.019144441932439804, -0.00824058335274458, + -0.00530060613527894, -0.00408138008788228, -0.04931379854679108, + 0.04058033600449562, 0.0005620545125566423, 0.02373599447309971, + 0.023874346166849136, 0.0047644926235079765, -0.005854013841599226, + -0.01065309438854456, 0.00815411377698183, -0.0022655120119452477, + -0.003303151112049818, 0.012347905896604061, -0.005646485835313797, + -0.005075784400105476, -0.0066149490885436535, 0.01877262070775032, + -0.05195113271474838, -0.000821464229375124, 0.030852466821670532, + 0.061609819531440735, -0.02860425040125847, -0.003311798209324479, + 0.0008560522692278028, 0.00019023384083993733, -0.03269427642226219, + -0.03091299906373024, -0.01030721515417099, 0.012598667293787003, + -0.024453694000840187, 0.017138339579105377, 0.016775164753198624, + -0.04474818706512451, 0.0442812480032444, 0.045647475868463516, + -0.02374464087188244, -0.07572171837091446, -0.0068311239592731, + -0.16558989882469177, 0.021409953013062477, -0.004202438518404961, + 0.03092164546251297, -0.01999184675514698, 0.046512171626091, + -0.00654577324166894, 0.029901299625635147, 0.007998468354344368, + -0.004193791188299656, -0.022542709484696388, -0.04104727506637573, + -0.010238038375973701, -0.01581534929573536, 0.014379948377609253, + -0.014959297142922878, -0.017890628427267075, 0.0078774094581604, + 0.01431941892951727, 0.002749743638560176, -0.034916553646326065, + 0.03393079712986946, 0.00498931435868144, 0.014258889481425285, + 0.007609352935105562, -0.03220139816403389 + ] + }, + "people_activities": { + "admiring": { + "prompt": "Photo of two people admiring or looking at each other in a loving but non-intimate and non-physical way", + "vector": [ + -0.03514610603451729, -0.02812313660979271, -0.03186924010515213, + 0.0643484964966774, 0.015219029039144516, 0.025284234434366226, + 0.007867596112191677, 0.0054588294588029385, -0.03783641383051872, + -0.061493948101997375, 0.027771208435297012, -0.01790934056043625, + -0.013334247283637524, -0.014499526470899582, 0.025425007566809654, + 0.5175095200538635, 0.023548046126961708, 0.02630092203617096, + -0.013521943241357803, -0.004700224380940199, -0.0021350437309592962, + -0.00952557846903801, -0.008117858320474625, 0.000656936492305249, + -0.0020646576303988695, -0.011770112439990044, -0.0049817683175206184, + -0.021170560270547867, -0.021561594679951668, 0.035865604877471924, + -0.001986450981348753, -0.00030500622233375907, 0.03502097353339195, + -0.04644697532057762, -0.026433873921632767, -0.00983840599656105, + -0.04971601441502571, 0.03028164431452751, -0.013514121994376183, + -0.043123189359903336, 0.010245081037282944, 0.0002502615097910166, + -0.012333200313150883, 0.0380319319665432, -0.011332154273986816, + -0.026715418323874474, -0.016024557873606682, -0.053931355476379395, + -0.011457285843789577, 0.03927541896700859, -0.0016579825896769762, + 0.03470032289624214, 0.005028692539781332, 0.02120966464281082, + 0.018581919372081757, 0.008375939913094044, 0.03340991586446762, + 0.0073123290203511715, -0.033738382160663605, -0.01745574176311493, + -0.023508941754698753, 0.026191432029008865, -0.01151202991604805, + 0.06702316552400589, -0.015672627836465836, 0.0440773107111454, + 0.0014468245208263397, -0.0020020920783281326, 0.011918704956769943, + 0.08666869252920151, -0.033965181559324265, 0.03676498308777809, + 0.02721594087779522, 0.040456339716911316, -0.004653300624340773, + -0.01441350020468235, -0.09551387280225754, 0.03524777293205261, + 0.02456473372876644, -0.0435924269258976, -0.03026600182056427, + -0.03004702553153038, 0.019105903804302216, -0.01288846880197525, + 0.005091257859021425, 0.0017909340094774961, -0.0007586052524857223, + -0.021913524717092514, -0.004708044696599245, 0.03254964202642441, + 0.013279502280056477, 0.02537808194756508, -0.01927795819938183, + 0.0407300628721714, -0.033065807074308395, 0.05871760845184326, + 0.0424584299325943, -0.012638206593692303, 0.0023696639109402895, + 0.020411955192685127, 0.0015719551593065262, -0.02852199412882328, + 0.0037695644423365593, -0.01983322575688362, 0.0449688695371151, + 0.06520094722509384, -0.06771138310432434, 0.036256637424230576, + -0.001133997575379908, -0.007437459193170071, 0.01277115847915411, + -0.008985953405499458, 0.15607717633247375, 0.03256528079509735, + 0.018472427502274513, -0.037468839436769485, 0.005607421975582838, + -0.000656936492305249, -0.010049563832581043, 0.030602293089032173, + 0.007640797644853592, -0.028170062229037285, 0.0023774844594299793, + 0.0002033374912571162, 0.010229440405964851, -0.0038164884317666292, + -0.000383212958695367, -0.018433324992656708, 0.001313872984610498, + -0.04119930416345596, 0.015125180594623089, 0.005896787159144878, + -0.0011496389051899314, 0.0011418182402849197, 0.05330570414662361, + 0.0010870734695345163, 0.0002502615097910166, 0.01318565383553505, + 0.038243088871240616, -0.004215342458337545, 0.00880607683211565, + -0.0019395267590880394, -0.024056388065218925, 0.0031908343080431223, + -0.002705952851101756, -0.03058665059506893, -0.022445330396294594, + 0.03086819313466549, -0.05810759961605072, -0.033957358449697495, + -0.0183003731071949, 0.028185702860355377, -0.04283382371068001, + 0.03940054774284363, 0.03269823268055916, 0.013287322595715523, + 0.02150684967637062, 0.01159023679792881, 0.01299795787781477, + -0.030156515538692474, -0.005013051442801952, 0.011339975520968437, + -0.058287475258111954, 0.024901021271944046, -0.04110545292496681, + 0.031525131314992905, 0.021233126521110535, -0.5212634801864624, + 0.004629838280379772, -0.018581919372081757, -0.012685131281614304, + 0.08338400721549988, -0.0017987547907978296, 0.018628841266036034, + -0.013091806322336197, 0.0011261767940595746, 0.022773798555135727, + -0.034129414707422256, 0.022398406639695168, -0.01181703619658947, + -0.0018143960041925311, 0.014280547387897968, -0.02295367419719696, + -0.027935443446040154, 0.010315467603504658, -0.020005280151963234, + -0.03294849395751953, -0.010722142644226551, 0.01478889212012291, + -0.01216114591807127, -0.025135641917586327, -0.0015250311698764563, + 0.045164383947849274, -0.02295367419719696, -0.01304488256573677, + -0.039541322737932205, 0.013576687313616276, 0.05171810835599899, + -0.03906426206231117, 0.01678316295146942, -0.017354073002934456, + 0.02462729811668396, -0.003417633706703782, 0.03437967598438263, + 0.010440598241984844, -0.026777982711791992, 0.001618879148736596, + -0.031994372606277466, -0.01502351276576519, 0.012434869073331356, + 0.01632174476981163, -0.03318311646580696, -0.055472031235694885, + -0.002095940290018916, -0.0010557908099144697, -0.0758214220404625, + -0.009236213751137257, -0.03192398324608803, -0.007320149801671505, + -0.0029874970205128193, 0.0059046074748039246, -0.024955766275525093, + -0.013709639199078083, 0.051647722721099854, -0.01044841855764389, + -0.03086819313466549, 0.021929167211055756, -0.024134594947099686, + -0.01303706131875515, -0.010683038271963596, -0.03432493284344673, + -0.02806839346885681, 0.007140273693948984, -0.04942664876580238, + -0.019567323848605156, 0.035200849175453186, -0.01064393576234579, + -0.04413987696170807, -0.03332388401031494, -0.02324303798377514, + 0.01822216622531414, 0.0031830137595534325, -0.03575611487030983, + 0.021225305274128914, 0.004246625117957592, 0.006279999855905771, + -0.01353758480399847, -0.007335790432989597, -0.07419472187757492, + -0.04053454473614693, 0.006381668616086245, -0.04254445806145668, + 0.027333252131938934, 0.01059701107442379, 0.02210904285311699, + -0.012520897202193737, -0.005599601659923792, 0.054330214858055115, + -0.02067003771662712, 0.037703461945056915, -0.027638258412480354, + -0.013787846080958843, 0.03139999881386757, 0.061705105006694794, + -0.02394689992070198, -0.0218900628387928, 0.01943437196314335, + 0.027192478999495506, 0.009595965966582298, -0.005404084920883179, + 0.0025182566605508327, -0.014585554599761963, -0.0285845585167408, + -0.031665902584791183, 0.04905908182263374, 0.014202342368662357, + -0.01342027448117733, -0.006068842019885778, -0.0059202490374445915, + -0.033214397728443146, 0.05482291802763939, 0.04771392419934273, + -0.026011556386947632, 0.016220074146986008, 0.05205439776182175, + 0.04306844249367714, -0.02675452083349228, 0.016525082290172577, + -0.04948921501636505, -0.035357262939214706, 0.006913474760949612, + -0.006960398517549038, -0.05734899267554283, -0.018941668793559074, + 0.01866794563829899, 0.004614196717739105, -0.03132961690425873, + 0.015195567160844803, 0.048081494867801666, 0.021240947768092155, + 0.0073279705829918385, 0.002627745969220996, 0.013553226366639137, + 0.026410412043333054, 0.003980722278356552, -0.028248269110918045, + -0.031611159443855286, 0.007492204196751118, -0.0015406724996864796, + 0.03597509488463402, 0.032831184566020966, -0.0025730011984705925, + -0.009658530354499817, -0.03331606462597847, -0.015563137829303741, + 0.004801893141120672, -0.06827446818351746, 0.02409549243748188, + -0.05810759961605072, -0.0327138751745224, 0.04088647663593292, + 0.05888184532523155, 0.014085031114518642, 0.04640787094831467, + -0.0346846841275692, -0.013561046682298183, -0.019864508882164955, + 0.04460911825299263, -0.019262315705418587, -0.0020411955192685127, + 0.020028743892908096, -0.02737235277891159, 0.04531297832727432, + 0.044327571988105774, -0.0480189323425293, -0.005888966377824545, + 0.0035349440295249224, -0.021006327122449875, -0.030696140602231026, + -0.02301623858511448, -0.01360797043889761, -0.013013599440455437, + 0.03806321322917938, 0.010870734229683876, -0.014311830513179302, + -0.016634570434689522, 0.0050208717584609985, -0.005302416160702705, + -0.04763571545481682, -0.0004535990010481328, -0.027028243988752365, + 0.038462068885564804, -0.00024244084488600492, -0.05312582850456238, + 0.016110586002469063, 0.04937972500920296, -0.05849863216280937, + -0.03499750792980194, 0.010815990157425404, 0.029616886749863625, + -0.018636662513017654, -0.019629888236522675, -0.001094894134439528, + 0.01528941374272108, 0.03591252863407135, -0.03255745768547058, + -0.00044577830703929067, -0.037007421255111694, 0.03003138303756714, + 0.006577185820788145, -0.027880696579813957, -0.04398346319794655, + -0.03299541771411896, -0.010033923201262951, 0.04370191693305969, + -0.01159023679792881, 0.03227591514587402, -0.04453872889280319, + -0.07761235535144806, 0.0002971855574287474, -0.049590885639190674, + -0.015680449083447456, -0.029718557372689247, 0.008438506163656712, + 0.02980458363890648, 0.008485429920256138, -0.005411905702203512, + -0.007202839478850365, 0.026402590796351433, -0.04015133157372475, + -0.07478127628564835, 0.007476563099771738, 0.01861320249736309, + -0.011379078961908817, -0.02295367419719696, -0.0015406724996864796, + -0.001290410989895463, -0.031063711270689964, -0.03436403349041939, + -0.11970321834087372, -0.02407984994351864, -0.0426461286842823, + -0.023657534271478653, -0.014968767762184143, -0.030633574351668358, + -0.018511531874537468, 0.020232081413269043, 0.011371258646249771, + 0.006788343656808138, -0.02462729811668396, -0.024431781843304634, + 0.01577429659664631, 0.01730714924633503, 0.011050610803067684, + 0.04086301475763321, 0.05634012445807457, 0.011191382072865963, + -0.028170062229037285, 0.007828493602573872, 0.027489662170410156, + 0.045492853969335556, -0.03988543152809143, -0.01239576656371355, + 0.0060923038981854916, -0.022586101666092873, -0.007562590297311544, + 0.006506799720227718, -0.023892154917120934, 0.04185623675584793, + -0.025659628212451935, -0.02623053640127182, -0.021780572831630707, + 0.0010323288151994348, 0.0032221172004938126, -0.05886620655655861, + 0.024204982444643974, 0.032526176422834396, -0.002189788268879056, + -0.013733101077377796, -0.01262256596237421, -0.002056836849078536, + -0.0275991540402174, 0.024197161197662354, 0.01943437196314335, + -0.0056856293231248856, 0.03224463388323784, -0.008915566839277744, + 0.030477160587906837, -0.0035896888002753258, -0.05702834203839302, + -0.03987761214375496, -0.015602242201566696, 0.010385853238403797, + -0.021084534004330635, 0.006827447097748518, -0.001994271529838443, + 0.02142864279448986, 0.03279989957809448, 0.014929663389921188, + 0.037703461945056915, -0.0015719551593065262, 0.008712229318916798, + -0.027739927172660828, -0.03369927778840065, 0.007468742318451405, + 0.04313100874423981, -0.03158769756555557, 0.0424036867916584, + -0.0005318057374097407, 0.02143646404147148, -0.016603287309408188, + 0.013443736359477043, -0.03951003775000572, 0.02334470860660076, + -0.0018769614398479462, -0.010995865799486637, 0.0017127272440120578, + -0.005333698354661465, -0.008939028717577457, 0.018355118110775948, + 0.018574098125100136, 0.10098834335803986, 0.0284359659999609, + -0.016517261043190956, 0.023070983588695526, -0.026715418323874474, + -0.10452328622341156, -0.018808718770742416, 0.006491158157587051, + 0.002627745969220996, 0.025417186319828033, 0.03117320127785206, + 0.035106997936964035, 0.003480199258774519, 0.006592826917767525, + 0.01292757224291563, 0.021029789000749588, -0.029085082933306694, + 0.009580324403941631, -0.00019551682635210454, -0.021905705332756042, + 0.004966127220541239, 0.0443197526037693, 0.007953624241054058, + 0.0203650314360857, 0.02370445802807808, -0.000766425917390734, + 0.01683790795505047, 0.010628294199705124, 0.002705952851101756, + 0.005896787159144878, -0.011989091522991657 + ] + }, + "embracing": { + "prompt": "Photo of people hugging or embracing each other lovingly, without inappropriately kissing or other intimate actions", + "vector": [ + 0.03190479800105095, 0.012113233096897602, -0.022465744987130165, + 0.020599115639925003, 0.026688827201724052, -0.00448123412206769, + 0.016892332583665848, 0.035790298134088516, -0.07396353781223297, + -0.04633476957678795, 0.010511374101042747, -0.010220126248896122, + 0.014443210326135159, 0.01785212568938732, 0.06136709451675415, + 0.5228084325790405, -0.00482543557882309, -0.001628336263820529, + -0.03713400661945343, -0.01580677554011345, -0.032546866685152054, + -0.02028800919651985, -0.060705170035362244, 0.004487853497266769, + 0.010140695609152317, 0.011874939315021038, -0.04372016713023186, + 0.00482543557882309, -0.009644251316785812, 0.041899871081113815, + -0.039265409111976624, -0.016250265762209892, 0.0030117600690573454, + -0.05602535605430603, -0.022598128765821457, -0.0026146050076931715, + -0.0355321504175663, 0.041357092559337616, 0.016131119802594185, + -0.024047747254371643, 0.029846210032701492, -0.009472151286900043, + -0.01952679641544819, 0.04841983690857887, -0.003971552010625601, + -0.05485374853014946, -0.02879374846816063, -0.05014083907008171, + -0.014535878784954548, 0.012867826968431473, -0.028178159147500992, + 0.08048349618911743, -0.0032235761173069477, 0.0004103936953470111, + -0.029998455196619034, -0.017263010144233704, 0.03011760115623474, + 0.020751357078552246, 0.0015224282396957278, -0.0069435965269804, + -0.026126191020011902, 0.006460390519350767, 0.006824449636042118, + 0.015098516829311848, -0.03204380348324776, 0.024511093273758888, + -0.005977185443043709, 0.01679966412484646, 0.00009266954293707386, + 0.04995550215244293, -0.017514541745185852, 0.030223509296774864, + 0.04506387189030647, 0.009313289076089859, 0.002720512915402651, + -0.026251958683133125, -0.0866791158914566, 0.035022467374801636, + 0.02159862220287323, -0.015065419487655163, -0.047625523060560226, + -0.024173511192202568, -0.004501091782003641, -0.027092603966593742, + -0.0005030632601119578, -0.0006751638138666749, -0.013264983892440796, + -0.0020718262530863285, 0.024703051894903183, 0.000052954022976337, + -0.02320048026740551, 0.00013900430349167436, -0.021863391622900963, + 0.04317738488316536, 0.03924554958939552, 0.05359609052538872, + 0.025940852239727974, -0.01834856905043125, 0.00448123412206769, + 0.006056616548448801, 0.006797972600907087, -0.01000831089913845, + -0.024696432054042816, -0.033784665167331696, 0.033579468727111816, + 0.000105908045952674, -0.061559051275253296, 0.034195058047771454, + -0.0034552502911537886, -0.005672699771821499, -0.020228436216711998, + -0.014595451764762402, 0.1076025739312172, -0.028734175488352776, + -0.012219141237437725, -0.04081431403756142, 0.015131612308323383, + -0.016806283965706825, 0.008889656513929367, 0.025762131437659264, + 0.03790184110403061, -0.006811210885643959, -0.04401141032576561, + -0.013119358569383621, 0.023597635328769684, 0.011120345443487167, + 0.03059418871998787, -0.02412055805325508, 0.02811196632683277, + 0.014039434492588043, -0.016395889222621918, -0.008532216772437096, + 0.0004699669370893389, 0.013364271260797977, 0.09090881794691086, + -0.043428920209407806, -0.03463193029165268, 0.022988665848970413, + 0.03298373892903328, -0.020506445318460464, -0.015171327628195286, + -0.031130345538258553, -0.0036803046241402626, -0.043462011963129044, + -0.04213154315948486, -0.017481446266174316, -0.031805507838726044, + 0.006612633820623159, -0.0343870185315609, -0.0011252729455009103, + -0.04397169500589371, -0.00045010916073806584, -0.05562158301472664, + 0.02931005321443081, 0.002177734160795808, -0.026728542521595955, + 0.007737906649708748, -0.008671221323311329, 0.0023564540315419436, + -0.0002581508597359061, -0.013212029822170734, -0.03402295708656311, + -0.05244433879852295, 0.017329204827547073, -0.023498348891735077, + 0.015482432208955288, 0.031825367361307144, -0.5259922742843628, + 0.005176255479454994, 0.014251251704990864, -0.0660402849316597, + 0.07855729013681412, 0.0002912471245508641, 0.004295895341783762, + -0.016124499961733818, 0.005341737065464258, 0.02462361939251423, + 0.01058418583124876, 0.0008538836264051497, -0.015608198940753937, + 0.0002382930979365483, 0.010994579643011093, -0.005679319147020578, + -0.020420394837856293, 0.018613338470458984, -0.001542285899631679, + -0.01722329668700695, -0.027430184185504913, 0.008280685171484947, + -0.017124006524682045, 0.02515977993607521, -0.025808466598391533, + -0.020374059677124023, -0.03683614358305931, -0.03654489666223526, + 0.0013503276277333498, -0.005626365076750517, 0.03961622714996338, + -0.005308640655130148, 0.03429434821009636, 0.003104429692029953, + 0.009088234044611454, -0.002799944020807743, 0.018785439431667328, + 0.009756778366863728, 0.018434619531035423, -0.005910992622375488, + 0.012801635079085827, -0.03344046697020531, -0.020466729998588562, + 0.011537358164787292, -0.03674347326159477, -0.006725160870701075, + 0.016912192106246948, -0.02935638464987278, -0.007545948028564453, + 0.04967087507247925, -0.0028528980910778046, -0.014959512278437614, + 0.007115696556866169, -0.038292378187179565, -0.01070995070040226, + -0.0024623621720820665, 0.028264211490750313, -0.028806986287236214, + 0.00784381479024887, 0.017263010144233704, 0.011821985244750977, + 0.005771988537162542, 0.006149285938590765, -0.05329160392284393, + -0.02453095093369484, 0.05043208971619606, -0.08943934738636017, + -0.01077614352107048, 0.004527568817138672, 0.007757764775305986, + -0.017600594088435173, -0.028284067288041115, -0.015965638682246208, + 0.020771216601133347, -0.0007943103555589914, -0.051775798201560974, + 0.0035677773412317038, 0.03422153741121292, -0.0027469899505376816, + -0.026927120983600616, 0.02281656488776207, -0.050988104194402695, + -0.07974876463413239, -0.005169636569917202, -0.05307317152619362, + -0.005043870769441128, -0.03931836411356926, -0.024358851835131645, + 0.00539469113573432, -0.03316907584667206, 0.05090205371379852, + -0.013079644180834293, 0.015469194389879704, -0.037478212267160416, + -0.03344046697020531, 0.08413070440292358, 0.0449976809322834, + -0.0311104878783226, -0.038385048508644104, 0.023233577609062195, + 0.053238652646541595, -0.023835929110646248, -0.02187001146376133, + -0.023114431649446487, 0.008863179944455624, -0.01641574688255787, + -0.05323203280568123, 0.012192663736641407, 0.03041546605527401, + -0.0014496163930743933, -0.008221112191677094, -0.010279699228703976, + -0.007188509218394756, 0.047671858221292496, 0.03461207449436188, + -0.03889473155140877, -0.0011252729455009103, 0.04734089598059654, + 0.02732427604496479, 0.008935990743339062, 0.02318062260746956, + -0.02584156207740307, -0.04824111610651016, 0.012278714217245579, + -0.025199495255947113, -0.05007464811205864, -0.002343215513974428, + 0.010273081250488758, 0.012656011618673801, 0.002171115018427372, + 0.030355893075466156, 0.030475042760372162, -0.020499825477600098, + -0.004666573368012905, 0.006215478293597698, 0.0042429412715137005, + -0.00016548130952287465, 0.015992116183042526, -0.008234350942075253, + 0.018653053790330887, -0.014079151675105095, 0.01740863546729088, + 0.008088726550340652, 0.04458728805184364, 0.006791353691369295, + 0.0020916839130222797, 0.017554258927702904, -0.0005229209782555699, + 0.05415872484445572, -0.06349187344312668, -0.0031838605646044016, + -0.02958144061267376, 0.006738399155437946, 0.04008619859814644, + 0.015866348519921303, -0.00421646423637867, 0.047850579023361206, + -0.02380945347249508, -0.014628549106419086, 0.007744526024907827, + 0.006592776160687208, -0.022055350244045258, 0.025351738557219505, + -0.04393859952688217, 0.005540314596146345, 0.03927202895283699, + 0.06421337276697159, -0.02761552296578884, -0.012788397260010242, + 0.032990358769893646, -0.04202563688158989, 0.0013768046628683805, + 0.0024425042793154716, 0.005593268666416407, -0.01525075826793909, + 0.033513277769088745, -0.0014099008403718472, 0.04309795796871185, + 0.026748400181531906, -0.02103598602116108, 0.007850433699786663, + -0.05066376179456711, 0.039821427315473557, 0.027675095945596695, + 0.01834856905043125, 0.0060963318683207035, -0.03812689706683159, + 0.013814380392432213, 0.04603028669953346, -0.03898078203201294, + -0.02425956167280674, 0.011239491403102875, 0.011782269924879074, + 0.004527568817138672, -0.010756285861134529, -0.018163230270147324, + 0.013642280362546444, 0.03339413180947304, 0.008379973471164703, + 0.020844027400016785, -0.010570947080850601, -0.0039847902953624725, + 0.029945500195026398, -0.0016680517001077533, 0.027608904987573624, + -0.01834856905043125, -0.0027469899505376816, -0.012278714217245579, + 0.005076967179775238, 0.022373074665665627, -0.027423566207289696, + -0.07132244855165482, 0.013827620074152946, -0.025292163714766502, + -0.011716078035533428, -0.051193300634622574, -0.0197253730148077, + 0.02865474671125412, -0.019718755036592484, -0.05842152610421181, + -0.01763368956744671, 0.014390256255865097, -0.027966342866420746, + -0.09944765269756317, -0.054483070969581604, 0.01760721392929554, + 0.004534188192337751, -0.03393691033124924, -0.005275544710457325, + 0.006420675665140152, -0.03853728994727135, -0.03293078392744064, + -0.1083240807056427, 0.01916273683309555, -0.04758581146597862, + -0.007353989407420158, -0.04179396107792854, -0.009816352277994156, + -0.00542778754606843, 0.008379973471164703, -0.0049776784144341946, + 0.02719189040362835, -0.03471798077225685, -0.04080107435584068, + -0.013681996613740921, 0.027469897642731667, -0.004606999922543764, + 0.04767847806215286, 0.01767340488731861, -0.009968595579266548, + -0.011318922974169254, -0.018427999690175056, 0.0280259158462286, + 0.11136892437934875, -0.005540314596146345, -0.0007479756022803485, + 0.015184566378593445, 0.022465744987130165, 0.032725587487220764, + 0.022399552166461945, -0.06292261928319931, -0.010637139901518822, + -0.029885927215218544, -0.004501091782003641, -0.008022534660995007, + 0.017501303926110268, -0.02910485491156578, -0.0008274066494777799, + 0.04789029434323311, 0.005639603361487389, 0.0008207873906940222, + 0.03124949149787426, 0.017885221168398857, -0.03352651372551918, + 0.03794817626476288, 0.0012973735574632883, 0.0005692557897418737, + -0.001979156630113721, 0.02885994128882885, -0.02382931113243103, + 0.014529259875416756, -0.04982311651110649, -0.07375171780586243, + -0.026046760380268097, -0.004606999922543764, 0.020665308460593224, + -0.020870503038167953, 0.0, -0.013940147124230862, + 0.020387299358844757, 0.024828817695379257, -0.0035082038957625628, + -0.009074995294213295, -0.00007281178113771603, + -0.0051630171947181225, -0.016309838742017746, -0.005851419642567635, + -0.012404480017721653, 0.0197452325373888, -0.015820013359189034, + 0.009505246765911579, -0.0018335330532863736, 0.012920781038701534, + 0.006208859384059906, 0.006586156319826841, 0.014151962473988533, + -0.0014099008403718472, -0.02750299498438835, -0.015052181668579578, + 0.001542285899631679, 0.015045562759041786, 0.011610169894993305, + 0.025424549356102943, -0.0015224282396957278, 0.08096670359373093, + 0.020519685000181198, 0.014403493143618107, -0.01109386794269085, + -0.05837519094347954, -0.08014591038227081, 0.011603550054132938, + -0.003686923999339342, 0.009551581926643848, 0.021049223840236664, + -0.012841351330280304, 0.013463560491800308, -0.02214140072464943, + 0.016601087525486946, 0.018249280750751495, -0.009617773815989494, + -0.003931836225092411, 0.017931556329131126, 0.007042884826660156, + -0.049743685871362686, 0.006930357776582241, 0.006222097668796778, + 0.009114711545407772, 0.01834856905043125, -0.012656011618673801, + -0.0026410820428282022, 0.05188170447945595, 0.04044363647699356, + 0.02556355483829975, 0.036624327301979065, -0.049227382987737656 + ] + }, + "party": { + "prompt": "Photo of people celebrating together", + "vector": [ + 0.021657669916749, 0.02267414890229702, -0.03314313665032387, + -0.04715124890208244, -0.000511949067004025, -0.0014542320277541876, + -0.0359848253428936, 0.02541196346282959, -0.016916576772928238, + -0.03479769825935364, 0.015351051464676857, -0.030872752889990807, + -0.013614876195788383, 0.027786219492554665, 0.01966923102736473, + 0.5486461520195007, 0.01717626117169857, 0.006358555518090725, + 0.004251402802765369, -0.031265988945961, 0.010513504035770893, + -0.023905795067548752, -0.013889400288462639, 0.008072471246123314, + -0.00276007317006588, 0.0044517312198877335, -0.019639553502202034, + 0.019068246707320213, 0.0030494355596601963, 0.0061804866418242455, + 0.011262878775596619, -0.019105345010757446, 0.011819345876574516, + -0.030338546261191368, 0.026970067992806435, 0.004889484494924545, + -0.01432715356349945, -0.0034723500721156597, 0.032045044004917145, + 0.021961871534585953, 0.050638437271118164, -0.015625575557351112, + 0.0026710384991019964, 0.03608127683401108, -0.01342196948826313, + -0.04062946140766144, -0.04177207499742508, -0.021271854639053345, + 0.007694074884057045, 0.020099565386772156, -0.00014839103096164763, + 0.03710517659783363, 0.03411509469151497, 0.03371443971991539, + -0.005497887264937162, -0.0050081973895430565, -0.003316539339721203, + 0.003598482348024845, -0.00483012804761529, 0.0036429997999221087, + -0.03180761635303497, -0.01874178647994995, -0.010439308360219002, + -0.07591685652732849, -0.05038617178797722, 0.015766547992825508, + 0.008658615872263908, 0.018140802159905434, 0.0036504194140434265, + 0.03667484223842621, 0.02351255901157856, 0.023327069357037544, + -0.020492801442742348, 0.0028416882269084454, 0.03282409533858299, + 0.005416272673755884, -0.05294591560959816, 0.03551739454269409, + 0.038648445159196854, 0.0064624291844666, -0.015907518565654755, + -0.015202660113573074, -0.02860979177057743, 0.03502028435468674, + -0.027934610843658447, -0.0004525926196947694, 0.014750069007277489, + -0.012813565321266651, 0.0009497025748714805, 0.0012910020304843783, + -0.0749448910355568, -0.005668537225574255, -0.01867501251399517, + 0.0038433277513831854, 0.035465456545352936, 0.039842989295721054, + -0.06788889318704605, 0.024388065561652184, -0.021627992391586304, + 0.0019661812111735344, -0.014987492933869362, 0.017035290598869324, + 0.0014097146922722459, -0.03465672582387924, 0.016983352601528168, + 0.01953567937016487, -0.05583954229950905, 0.015009752474725246, + 0.008176345378160477, -0.002255543600767851, 0.006900182459503412, + -0.03729808330535889, 0.06202002987265587, -0.0035836431197822094, + -0.0038878447376191616, -0.07182125747203827, 0.021071525290608406, + -0.02357933484017849, 0.003858166979625821, 0.013926498591899872, + 0.04360470175743103, -0.02247382141649723, 0.004414633382111788, + -0.011218362487852573, 0.008220863528549671, -0.0069966367445886135, + -0.0021590893156826496, -0.05723441764712334, 0.025790361687541008, + -0.0016916577005758882, -0.018734367564320564, 0.03210439905524254, + -0.005178846884518862, 0.009422830305993557, 0.009838324971497059, + 0.003464930457994342, -0.009445088915526867, -0.009044433012604713, + 0.013681653887033463, 0.006299199070781469, -0.02753395587205887, + 0.004214304964989424, 0.008703134022653103, -0.04569701850414276, + -0.00042291442514397204, 0.026851356029510498, -0.030234670266509056, + 0.0006529205129481852, -0.010632217861711979, -0.004333017859607935, + -0.02006988599896431, -0.02068571001291275, -0.07169512659311295, + 0.027934610843658447, -0.020982490852475166, -0.010098009370267391, + -0.009007335640490055, 0.004844967275857925, 0.022303171455860138, + -0.026153918355703354, -0.020166339352726936, -0.023304810747504234, + -0.013763267546892166, 0.0, 0.011366752907633781, + -0.023230616003274918, 0.02135346829891205, -0.5525710582733154, + 0.010120267979800701, -0.026547156274318695, -0.007575361989438534, + 0.054407574236392975, -0.012294196523725986, 0.014431027695536613, + 0.006425331346690655, 0.011099648661911488, 0.026302309706807137, + 0.0069966367445886135, -0.019884398207068443, -0.008027954958379269, + 0.000022258655008045025, 0.007961179129779339, 0.03180019557476044, + 0.010216722264885902, 0.03761712461709976, -0.009504444897174835, + -0.010847384110093117, -0.00271555595099926, 0.03042016178369522, + -0.009556381963193417, 0.03968718275427818, 0.013948756270110607, + -0.004592702724039555, -0.01942438632249832, 0.004889484494924545, + -0.027563635259866714, -0.0013280997518450022, 0.0203518308699131, + -0.014831682667136192, 0.054207246750593185, -0.010290917940437794, + 0.029329488053917885, -0.01600397191941738, -0.02216220088303089, + 0.016946256160736084, 0.029908213764429092, -0.033261850476264954, + -0.008799588307738304, 0.015195241197943687, -0.00010387371730757877, + -0.00605435436591506, 0.003924942575395107, 0.04178691282868385, + -0.049599699676036835, -0.02055215835571289, -0.014193601906299591, + 0.018860500305891037, -0.03188923001289368, -0.037253569811582565, + -0.014816843904554844, -0.02642844244837761, 0.04197240248322487, + 0.0062546818517148495, 0.0545337051153183, 0.0038433277513831854, + 0.02878785878419876, 0.005304979160428047, 0.013607458211481571, + 0.0035094479098916054, 0.0005490467883646488, -0.0031162116210907698, + -0.010980935767292976, 0.006796309258788824, -0.02159089408814907, + -0.016642054542899132, -0.02638392336666584, -0.022110262885689735, + 0.0011055131908506155, -0.025864554569125175, -0.0061433888040483, + -0.024224834516644478, -0.009519284591078758, -0.02364611066877842, + 0.014935556799173355, -0.027571052312850952, 0.046246062964200974, + -0.010357693769037724, 0.02444000169634819, -0.03789164870977402, + -0.00968993455171585, -0.0007122769602574408, -0.018793722614645958, + -0.038336820900440216, 0.009170565754175186, 0.010847384110093117, + -0.009749290533363819, -0.028275910764932632, 0.006506946869194508, + -0.01429747510701418, 0.021538957953453064, -0.027867835015058517, + -0.026242952793836594, 0.018133383244276047, 0.04194272309541702, + 0.02547873929142952, -0.030895013362169266, 0.021895095705986023, + 0.0123832318931818, 0.006685015745460987, -0.01955793797969818, + -0.016211720183491707, 0.003086533397436142, -0.018289193511009216, + -0.03897490352392197, 0.03490899130702019, -0.019832462072372437, + 0.01676076650619507, -0.010060911998152733, 0.0003264602564740926, + 0.006640498526394367, -0.004563024267554283, 0.019565356895327568, + 0.025975851342082024, 0.0015803645364940166, 0.001528427586890757, + 0.0447324737906456, -0.025582613423466682, 0.0214350838214159, + -0.02679941989481449, 0.018519200384616852, -0.03084307350218296, + -0.03813649341464043, -0.03435994312167168, 0.0012539041927084327, + 0.025107761844992638, -0.0014022953109815717, -0.0023223196621984243, + 0.0049191624857485294, 0.025352606549859047, -0.011938057839870453, + 0.003976879641413689, 0.0017584336455911398, 0.025419384241104126, + -0.018400488421320915, 0.02435096725821495, 0.00271555595099926, + 0.01448296383023262, 0.014445867389440536, 0.016901738941669464, + 0.005312399007380009, -0.013043572194874287, 0.017391428351402283, + -0.00848796684294939, 0.011952897533774376, -0.020960234105587006, + -0.0005045294528827071, -0.05082392692565918, 0.004770771600306034, + -0.025419384241104126, 0.007456648629158735, 0.04484377056360245, + 0.0020997331012040377, -0.034582529217004776, 0.010550602339208126, + -0.011099648661911488, 0.01111448835581541, -0.008339575491845608, + 0.048338379710912704, -0.0051046512089669704, 0.009927359409630299, + 0.02717781625688076, -0.005557244177907705, 0.029997248202562332, + 0.0941837877035141, -0.0033536371774971485, 0.0005416272324509919, + 0.019899237900972366, -0.034078001976013184, 0.01092899963259697, + 0.006751792039722204, 0.0010387372458353639, -0.018712108954787254, + 0.09233631193637848, 0.0031458898447453976, -0.012665174901485443, + -0.0008606679039075971, -0.0024336129426956177, -0.011789667420089245, + -0.1166427731513977, -0.007790528703480959, -0.0031681484542787075, + 0.0247664637863636, -0.011307395994663239, -0.05511242896318436, + 0.012820985168218613, 0.01980278268456459, -0.12772756814956665, + 0.014490384608507156, 0.008228282444179058, -0.06526979058980942, + 0.04523700475692749, -0.007894402369856834, -0.04649832844734192, + -0.016048489138484, 0.04359728470444679, 0.027281688526272774, + 0.02774912118911743, 0.0015581058105453849, 0.008154086768627167, + 0.025048404932022095, 0.02400224842131138, 0.034582529217004776, + -0.008027954958379269, 0.008495386689901352, -0.010936419479548931, + 0.017866279929876328, 0.017799504101276398, -0.0154104083776474, + -0.09984490275382996, 0.0003042016178369522, -0.037698738276958466, + -0.005534985102713108, -0.04093366488814354, -0.014260378666222095, + 0.036036763340234756, -0.02932206727564335, -0.031473737210035324, + -0.025842297822237015, 0.033588312566280365, 0.018712108954787254, + -0.08542871475219727, -0.04044397547841072, 0.014156504534184933, + -0.00941541139036417, 0.007130189333111048, 0.007708914112299681, + 0.00834699533879757, -0.077052041888237, 0.020700549706816673, + -0.11950672417879105, 0.043901484459638596, -0.002745233941823244, + -0.05080166831612587, -0.040369778871536255, 0.003962040413171053, + 0.02371288649737835, 0.016901738941669464, 0.030368225648999214, + 0.0167978648096323, -0.04108205810189247, 0.006907602306455374, + 0.006284359842538834, -0.010943838395178318, 0.0029381425119936466, + 0.020084725692868233, 0.01667173206806183, -0.01258355937898159, + -0.0014171343063935637, 0.01907566748559475, 0.004488828592002392, + 0.052448805421590805, -0.048649996519088745, -0.014883620664477348, + 0.00984574481844902, 0.07379485666751862, 0.004614960867911577, + 0.004243983421474695, -0.0037023562472313643, -0.010498665273189545, + -0.027778800576925278, -0.01744336634874344, -0.03314313665032387, + 0.00989026203751564, 0.0028045906219631433, -0.032905708998441696, + 0.051499105989933014, 0.012858081609010696, 0.009118628688156605, + -0.030345967039465904, 0.007070832885801792, -0.02464032918214798, + 0.006173066794872284, -0.05717506259679794, 0.017821762710809708, + 0.03601450473070145, 0.0081911850720644, 0.009229921735823154, + 0.002574584446847439, 0.0038952643517404795, -0.02324545569717884, + -0.026257792487740517, 0.005045294761657715, 0.004362696316093206, + 0.014000694267451763, -0.020989911630749702, 0.012049351818859577, + 0.004703995771706104, 0.027734283357858658, -0.015172983519732952, + -0.00726374052464962, 0.0008458288502879441, -0.011277717538177967, + -0.011351913213729858, -0.010172205045819283, -0.016285916790366173, + 0.05145459249615669, -0.002567164832726121, -0.006314038299024105, + -0.031362444162368774, -0.014534901827573776, 0.007597620598971844, + -0.03720904886722565, 0.032245371490716934, 0.009927359409630299, + -0.006202745251357555, 0.02470710687339306, 0.004941421095281839, + 0.01927599497139454, 0.009422830305993557, 0.018956953659653664, + 0.013926498591899872, -0.0029900791123509407, 0.02242930419743061, + -0.027385564520955086, -0.013956177048385143, -0.047099314630031586, + -0.11699148267507553, 0.0574495866894722, -0.007597620598971844, + 0.03949427232146263, 0.02099733054637909, 0.01011284813284874, + 0.01009058952331543, 0.004674317315220833, 0.01161159761250019, + 0.006729532964527607, -0.012271937914192677, -0.004384954925626516, + 0.010342855006456375, 0.012323874980211258, 0.007545683532953262, + 0.005282720550894737, -0.037542931735515594, 0.012368392199277878, + -0.0074789077043533325, -0.026020366698503494, -0.007627299055457115, + 0.019335351884365082, 0.062405843287706375, -0.004058494698256254, + -0.009370893239974976, -0.06802986562252045 + ] + }, + "hiking": { + "prompt": "Photo of people hiking or walking together in nature", + "vector": [ + 0.002063393360003829, 0.0015020288992673159, -0.013541018590331078, + -0.007646692916750908, 0.026111029088497162, 0.0011834166944026947, + -0.02757512778043747, 0.007176360581070185, 0.019071215763688087, + -0.006736371666193008, -0.005340547300875187, -0.00493848929181695, + 0.0006903264438733459, 0.02783305011689663, 0.05169862136244774, + 0.5473757982254028, 0.010901088826358318, 0.008154954761266708, + 0.010863158851861954, -0.02394143119454384, 0.018168481066823006, + -0.0024426935706287622, -0.02306145429611206, -0.009588710032403469, + 0.01046110037714243, 0.00219235522672534, -0.029919203370809555, + -0.009262511506676674, 0.02251526340842247, 0.007934961467981339, + 0.028439931571483612, -0.0003868862404488027, 0.011454867199063301, + -0.01567268557846546, 0.031299855560064316, -0.016143018379807472, + 0.004331608768552542, -0.03375013545155525, 0.006872919853776693, + 0.0354645736515522, 0.0011682447511702776, -0.002533725695684552, + 0.0012441047001630068, -0.01750091277062893, -0.05769156292080879, + -0.022204237058758736, 0.03236947953701019, -0.047382187098264694, + 0.004976418800652027, 0.022613881155848503, 0.020360836759209633, + 0.008807350881397724, -0.0078970305621624, 0.029555076733231544, + -0.023433169350028038, -0.018456749618053436, -0.004399883095175028, + -0.04163958132266998, -0.007092914544045925, -0.0014640989247709513, + -0.04121476411819458, -0.0076922085136175156, -0.02539035677909851, + 0.026065511628985405, -0.026725493371486664, 0.015361659228801727, + -0.027309618890285492, 0.004134372808039188, 0.03704246133565903, + 0.015437520109117031, 0.004862628877162933, 0.014974773861467838, + -0.00012137607700424269, -0.03316601365804672, -0.03244534134864807, + 0.008905969560146332, -0.07830274105072021, 0.008481153286993504, + 0.06572514027357101, 0.028963366523385048, -0.021453222259879112, + -0.015354073606431484, -0.005780535284429789, -0.0006675684126093984, + 0.022181479260325432, 0.018722258508205414, -0.03245292976498604, + -0.0019192592008039355, 0.010499031282961369, 0.025853104889392853, + 0.005105380900204182, -0.01422375999391079, 0.00827633123844862, + -0.012342429719865322, -0.003967480733990669, 0.019033284857869148, + -0.08297571539878845, 0.03469838574528694, -0.013412055559456348, + 0.017940901219844818, -0.030533669516444206, -0.013503088615834713, + -0.01873743161559105, -0.06481482833623886, 0.0069411941803991795, + 0.020633932203054428, -0.05540059506893158, 0.014603059738874435, + 0.004559189081192017, -0.03791484981775284, -0.01018800400197506, + 0.0024730374570935965, -0.03339359536767006, -0.009528021328151226, + -0.023736609145998955, -0.013707909733057022, 0.009300441481173038, + -0.012926552444696426, 0.035995591431856155, -0.0003793002397287637, + 0.03805140033364296, -0.008162541314959526, 0.002298559295013547, + -0.014542371965944767, -0.012539665214717388, 0.00940664578229189, + 0.02364557795226574, -0.045121558010578156, -0.001008938648737967, + 0.00028068217216059566, 0.0036792121827602386, 0.02761305682361126, + 0.0065012057311832905, 0.020907029509544373, 0.06558859348297119, + 0.0028144079260528088, -0.01451202668249607, -0.005530197639018297, + -0.0006144663784652948, -0.0013199648819863796, 0.00856459978967905, + -0.0002958541736006737, -0.008079095743596554, 0.024707617238163948, + -0.007957719266414642, -0.003110261866822839, -0.002966127824038267, + 0.02645239792764187, -0.02149873785674572, 0.011318319477140903, + -0.04584981128573418, 0.014557542279362679, -0.005317789502441883, + 0.04957453906536102, -0.010324552655220032, 0.030859868973493576, + 0.024131080135703087, -0.002784063806757331, 0.0037702443078160286, + -0.024692445993423462, 0.0030950899235904217, 0.017834696918725967, + 0.009232167154550552, 0.012243811041116714, -0.0712856873869896, + 0.05383029207587242, 0.014056866988539696, -0.551009476184845, + 0.02320558950304985, -0.027984770014882088, -0.02834131382405758, + 0.05802535265684128, 0.0036033522337675095, -0.0293881818652153, + -0.04008444771170616, 0.0006068804068490863, -0.0058260513469576836, + -0.0210663340985775, 0.02102840505540371, 0.04547051340341568, + 0.006994296796619892, 0.03524458035826683, -0.0028523379005491734, + -0.01225898414850235, 0.042170602828264236, -0.015574067831039429, + -0.0283716581761837, -0.015346487052738667, 0.014436166733503342, + -0.012023817747831345, -0.0017220231238752604, 0.025845518335700035, + 0.03774796053767204, -0.050879333168268204, -0.026035169139504433, + -0.006121905520558357, 0.017622290179133415, -0.0962739810347557, + 0.00798806268721819, 0.011826581321656704, 0.04728356748819351, + 0.09593261033296585, 0.04386227950453758, 0.014322377741336823, + 0.03848380222916603, -0.01191002782434225, 0.017265748232603073, + -0.0007661865092813969, -0.013943076133728027, 0.01731126382946968, + 0.02589862048625946, -0.00002275801307405345, 0.0014640989247709513, + -0.009528021328151226, -0.011735549196600914, -0.011477624997496605, + 0.058154311031103134, -0.029744725674390793, -0.004635049030184746, + -0.020163599401712418, -0.02444210648536682, 0.009581124410033226, + 0.0047943550162017345, 0.05439165234565735, 0.020869098603725433, + 0.008041164837777615, 0.011098324321210384, -0.029911616817116737, + -0.023653162643313408, -0.08817972242832184, -0.010347310453653336, + -0.03296118974685669, 0.01143969502300024, -0.03907550871372223, + 0.0034440462477505207, -0.03758106753230095, -0.023251105099916458, + 0.02484416589140892, -0.011758306995034218, -0.034068744629621506, + -0.00598535779863596, 0.0004551603051368147, -0.03244534134864807, + -0.002495795488357544, 0.008503912016749382, 0.07327321916818619, + -0.026505500078201294, 0.013798942789435387, -0.08673837780952454, + -0.018540196120738983, -0.007790826726704836, -0.042034052312374115, + -0.055635757744312286, -0.029524730518460274, -0.0075860051438212395, + 0.007714967243373394, -0.0019799470901489258, -0.06055907532572746, + -0.0005386063130572438, 0.0027157897129654884, -0.04244369640946388, + 0.003686798270791769, 0.014519614167511463, 0.010984535329043865, + -0.0255344919860363, -0.021741488948464394, 0.005355719476938248, + 0.022014586254954338, -0.01018800400197506, -0.012941723689436913, + -0.0000910320522962138, 0.0032923261169344187, -0.0024654516018927097, + 0.02062634751200676, 0.03338600695133209, 0.005014349240809679, + 0.00467297900468111, 0.004202646669000387, 0.02229526825249195, + 0.00722187664359808, 0.014595472253859043, 0.009300441481173038, + 0.009376302361488342, -0.01757677271962166, -0.042360249906778336, + 0.0059019117616117, 0.009072861634194851, 0.009649398736655712, + -0.009679742157459259, 0.008132196962833405, -0.007889444939792156, + -0.005598471499979496, 0.03568456694483757, -0.0014489268651232123, + 0.007009468041360378, 0.028508204966783524, -0.03474390134215355, + -0.10007457435131073, 0.015376831404864788, -0.0013047928223386407, + -0.03398530185222626, 0.013578948564827442, 0.016431285068392754, + 0.017963659018278122, 0.027415819466114044, -0.01756918616592884, + -0.013442400842905045, 0.016363011673092842, 0.013783770613372326, + 0.04548568278551102, 0.05770673602819443, -0.000963422644417733, + -0.018100207671523094, -0.002875095698982477, 0.010051456280052662, + 0.05289721488952637, -0.03294602036476135, 0.016939548775553703, + -0.05000694468617439, -0.030321259051561356, 0.04122234880924225, + 0.008208057843148708, 0.03441770374774933, -0.008746663108468056, + 0.003239224199205637, -0.009725257754325867, -0.043528493493795395, + 0.0073811826296150684, 0.002905439818277955, 0.021946312859654427, + 0.020679449662566185, 0.0052874451503157616, -0.03149709105491638, + 0.05969427153468132, 0.0024882094003260136, -0.01367756724357605, + 0.024798648431897163, -0.026027580723166466, -0.018380889669060707, + -0.06441276520490646, 0.007593590300530195, -0.002723375800997019, + 0.026975832879543304, -0.010309380479156971, 0.004232990555465221, + -0.0185477826744318, 0.010787298902869225, 0.006175007671117783, + 0.00040964424260891974, 0.01695472002029419, -0.00923975370824337, + 0.002556483494117856, 0.02156701311469078, -0.07915237545967102, + 0.01619611866772175, 0.004278506617993116, -0.019078800454735756, + 0.02157459780573845, -0.0014792709844186902, -0.0033302560914307833, + 0.020641518756747246, -0.019526375457644463, -0.0314212329685688, + -0.013078272342681885, -0.005461923312395811, -0.00219235522672534, + -0.027734432369470596, -0.02193872444331646, 0.01945810206234455, + 0.020239459350705147, -0.008344604633748531, 0.019518790766596794, + -0.04682840779423714, 0.006834989879280329, 0.007237048353999853, + -0.01417065691202879, 0.028690271079540253, -0.014921671710908413, + -0.061385948210954666, -0.015703029930591583, 0.003117847954854369, + -0.007358424365520477, -0.02138494700193405, -0.007343252655118704, + 0.05872325971722603, -0.01699265092611313, 0.020307734608650208, + -0.016605764627456665, 0.02196907065808773, 0.006599824409931898, + 0.02062634751200676, 0.0405016764998436, -0.0021771832834929228, + 0.04533396288752556, 0.015134080313146114, -0.020535314455628395, + -0.006250868085771799, -0.015217525884509087, 0.02823510952293873, + -0.1144576445221901, 0.023304205387830734, 0.03866586834192276, + 0.0056212292984128, 0.012175537645816803, -0.02510209009051323, + -0.012592768296599388, -0.002336489502340555, 0.06109009310603142, + -0.004953661002218723, 0.009482505731284618, -0.03818794712424278, + -0.008177713491022587, 0.00503710750490427, -0.0011758307227864861, + 0.014542371965944767, 0.0022378715220838785, -0.0035274922847747803, + 0.0026854455936700106, 0.023380065336823463, 0.02768133208155632, + 0.11998025327920914, -0.07625452429056168, -0.06897195428609848, + 0.02488209493458271, 0.015012702904641628, -0.020057396963238716, + -0.010756954550743103, 0.01931396685540676, 0.03437977284193039, + -0.002108909422531724, -0.004710908979177475, 0.012069333344697952, + 0.008147369138896465, -0.027150310575962067, -0.06366175413131714, + 0.01267621386796236, 0.023304205387830734, 0.03529009595513344, + -0.016309909522533417, -0.0025792415253818035, -0.004415054805576801, + 0.00925492588430643, 0.007745311129838228, -0.026497915387153625, + 0.0015247869305312634, 0.020679449662566185, -0.019943606108427048, + 0.022644223645329475, 0.0021013233345001936, -0.0057729496620595455, + -0.057418469339609146, 0.016567833721637726, -0.040615469217300415, + 0.021111849695444107, -0.016051985323429108, -0.017409881576895714, + -0.027529612183570862, 0.00851149670779705, -0.016271980479359627, + -0.01257000956684351, 0.01160658709704876, -0.03044263832271099, + -0.011447281576693058, 0.03321152925491333, 0.010286622680723667, + 0.05867016315460205, -0.0005006763385608792, 0.005651573650538921, + 0.02270491234958172, 0.019670508801937103, 0.009103205054998398, + 0.0033909440971910954, 0.007828757166862488, 0.012486563995480537, + -0.06490585952997208, 0.014314791187644005, -0.02128632925450802, + 0.01449685450643301, 0.02833372727036476, -0.006053632125258446, + -0.001084798714146018, 0.03882517293095589, 0.021870451048016548, + -0.016522318124771118, 0.0028826817870140076, -0.049089036881923676, + -0.06481482833623886, 0.0018206412205472589, 0.023296620696783066, + 0.019404999911785126, 0.02746892161667347, 0.031079862266778946, + 0.01899535581469536, -0.040478918701410294, 0.04027410224080086, + 0.01311620231717825, -0.001866157166659832, -0.055635757744312286, + -0.016234049573540688, 0.0015323730185627937, -0.021407704800367355, + -0.009050103835761547, -0.007494972553104162, 0.011614173650741577, + -0.00951285008341074, -0.018889151513576508, -0.01571820117533207, + 0.012501736171543598, 0.06539894640445709, -0.006281211972236633, + 0.011727963574230671, -0.04220853000879288 + ] + }, + "feast": { + "prompt": "Photo of people having a big feast together", + "vector": [ + -0.029508370906114578, 0.006410595495253801, -0.022999616339802742, + -0.04990297555923462, 0.017683882266283035, -0.010110467672348022, + -0.023958561941981316, 0.004115164279937744, -0.03147157281637192, + -0.0791093111038208, -0.01019352674484253, 0.03028610162436962, + -0.005285532213747501, 0.016543716192245483, 0.01726858876645565, + 0.5275111794471741, -0.016181280836462975, -0.005934897810220718, + -0.017427153885364532, -0.01871078461408615, -0.007905646227300167, + -0.03548102453351021, 0.024102026596665382, 0.005323286168277264, + -0.00561021501198411, -0.002605012385174632, -0.01120532862842083, + -0.006410595495253801, 0.030542826279997826, 0.03679485619068146, + -0.009483755566179752, -0.035435717552900314, 0.0168155450373888, + 0.012134072370827198, -0.004001902882009745, 0.04106858745217323, + -0.013289338909089565, -0.009597016498446465, -0.026314400136470795, + 0.014791940338909626, 0.07266851514577866, -0.017094921320676804, + -0.00034733497886918485, 0.020719286054372787, -0.03684015944600105, + -0.051322516053915024, -0.026714589446783066, -0.0372932069003582, + -0.004938197322189808, 0.017162878066301346, 0.013908501714468002, + 0.011575316078960896, 0.03738381713628769, -0.004032106138765812, + -0.01874098740518093, -0.011099617928266525, -0.01941300556063652, + 0.003224174724891782, -0.03239276260137558, 0.008056661114096642, + -0.029923664405941963, 0.008396445773541927, -0.012330392375588417, + -0.0709695965051651, -0.04053248465061188, 0.006886293180286884, + -0.005647968500852585, -0.04843812808394432, 0.004855139181017876, + 0.009838640689849854, 0.00040774105582386255, -0.025453614071011543, + -0.017955707386136055, 0.02265983261168003, 0.029281847178936005, + -0.01099390722811222, -0.06056464836001396, 0.009732929989695549, + 0.09662707895040512, 0.01938280090689659, 0.00030203041387721896, + -0.02985570766031742, -0.011424301192164421, -0.007709326688200235, + 0.019480960443615913, 0.047864269465208054, 0.0035111038014292717, + -0.006252029910683632, 0.0017517763189971447, -0.007497905287891626, + -0.016649426892399788, 0.015033564530313015, -0.02813413366675377, + 0.0017895301571115851, -0.012141622602939606, 0.022909006103873253, + -0.13033367693424225, 0.0323852114379406, -0.02679009921848774, + 0.015373348258435726, -0.0010118018835783005, 0.024849552661180496, + -0.010050062090158463, 0.05398038774728775, 0.021149680018424988, + 0.008532359264791012, -0.04978971555829048, 0.012164275161921978, + 0.03536776453256607, -0.009438450448215008, 0.006908946204930544, + -0.028670238330960274, 0.04793977737426758, -0.03107893094420433, + -0.00043039332376793027, -0.09445246309041977, 0.041287556290626526, + -0.03239276260137558, 0.013568716123700142, 0.031305454671382904, + -0.010352092795073986, -0.00006795684748794883, -0.019397905096411705, + -0.045115794986486435, 0.005270430818200111, -0.006206724792718887, + 0.04941217601299286, -0.031645238399505615, 0.0459916815161705, + 0.006365290842950344, -0.017872650176286697, 0.029508370906114578, + -0.00622937735170126, 0.005776331759989262, -0.014505011029541492, + -0.014248285442590714, -0.009763133712112904, -0.009702727198600769, + 0.01652861386537552, 0.03707423433661461, -0.00672017689794302, + 0.025831151753664017, -0.009242130443453789, -0.044255007058382034, + 0.006395494099706411, -0.018363449722528458, 0.00662201689556241, + 0.030399363487958908, -0.015713132917881012, 0.018167128786444664, + -0.015833944082260132, 0.0032543777488172054, -0.06430982798337936, + 0.02250126749277115, 0.014172777533531189, 0.03633425757288933, + 0.02984815463423729, -0.01769143156707287, 0.0005814085598103702, + -0.01925443857908249, -0.037919919937849045, -0.012254884466528893, + -0.008396445773541927, 0.009959452785551548, 0.057582102715969086, + -0.04155183583498001, 0.013508310541510582, -0.5311808586120605, + -0.0006191623397171497, 0.002084009815007448, -0.008177473209798336, + 0.04822670668363571, -0.02510627917945385, 0.027484769001603127, + 0.003480900777503848, -0.0014119921252131462, 0.040977977216243744, + -0.010004756972193718, -0.01550926174968481, -0.002846636576578021, + 0.023565921932458878, 0.016400251537561417, 0.014384198933839798, + 0.009068462997674942, 0.04809079319238663, -0.013712181709706783, + 0.0087739834561944, -0.02348286472260952, -0.010978804901242256, + -0.02327144332230091, 0.024630580097436905, 0.004923095460981131, + -0.0450025349855423, -0.018431406468153, -0.02188965491950512, + -0.029259197413921356, -0.020983561873435974, 0.023807547986507416, + -0.02648051828145981, 0.05487137287855148, 0.0007550760637968779, + 0.003443146590143442, -0.00040019029984250665, -0.02216148190200329, + -0.008992955088615417, -0.01047290489077568, -0.0007324237376451492, + -0.027545172721147537, 0.024086926132440567, -0.008917448110878468, + 0.01448235847055912, 0.0016838194569572806, -0.004462499171495438, + -0.013025062158703804, 0.022674933075904846, 0.023867953568696976, + -0.09708012640476227, -0.013727283105254173, -0.031886860728263855, + 0.008034009486436844, -0.0067352778278291225, 0.05508279800415039, + 0.010352092795073986, 0.02363388054072857, 0.004024555440992117, + -0.010676775127649307, 0.0008456851937808096, -0.016800440847873688, + 0.00014346445095725358, -0.0004152918409090489, 0.01708737015724182, + 0.03860703855752945, -0.011877345852553844, -0.02216903120279312, + -0.02170843631029129, 0.056842122226953506, 0.003428045427426696, + -0.005051458720117807, -0.0659709945321083, -0.0037753803189843893, + -0.004175570793449879, 0.011620620265603065, -0.0223502516746521, + 0.011507358402013779, -0.07293279469013214, 0.0331176333129406, + 0.006810786202549934, -0.00946110300719738, -0.03183400630950928, + 0.0026276647113263607, -0.021610276773571968, -0.04296382516622543, + -0.059968139976263046, 0.02210862748324871, -0.022448411211371422, + 0.00127607851754874, -0.030225694179534912, -0.02345266193151474, + 0.008215227164328098, 0.03343476727604866, -0.016249235719442368, + -0.030784450471401215, 0.017955707386136055, 0.014965606853365898, + 0.009159073233604431, -0.015214783139526844, 0.044353168457746506, + -0.012813640758395195, -0.011673475615680218, 0.004084961488842964, + 0.010276584886014462, 0.03187175840139389, 0.006093463860452175, + 0.0021972714457660913, 0.004681471269577742, -0.02412468008697033, + -0.005330836866050959, 0.008260532282292843, 0.013968906365334988, + 0.030852409079670906, 0.012488958425819874, 0.03156217932701111, + 0.028957165777683258, -0.0005889593157917261, 0.01711002178490162, + 0.026835402473807335, 0.019216684624552727, 0.004704123828560114, + -0.029644286260008812, -0.006319986190646887, -0.041868966072797775, + -0.017321443185210228, -0.015086418949067593, -0.04332626610994339, + 0.011439401656389236, 0.0019707484170794487, -0.012889147736132145, + -0.047192253172397614, 0.017525315284729004, 0.016543716192245483, + 0.030225694179534912, -0.014301139861345291, 0.02335450053215027, + -0.005693273153156042, 0.039082735776901245, -0.011937752366065979, + 0.02835310623049736, 0.019110973924398422, -0.002778679830953479, + -0.010654122568666935, -0.05033336952328682, -0.018997713923454285, + -0.03303457424044609, 0.0017593271331861615, 0.015071317553520203, + 0.05412385240197182, -0.0390600822865963, 0.01020862814038992, + -0.04464764520525932, 0.005149618722498417, 0.021353550255298615, + 0.014633373357355595, -0.008940099738538265, 0.050348468124866486, + 0.003941496834158897, 0.003722524968907237, -0.026095427572727203, + 0.006168971303850412, 0.018514463678002357, 0.009989656507968903, + 0.003216624027118087, -0.0018952408572658896, 0.010231280699372292, + 0.09087339788675308, -0.020960910245776176, -0.004696573130786419, + 0.01754041574895382, -0.02033419720828533, -0.027296001091599464, + -0.018869351595640182, -0.036387115716934204, 0.028859006240963936, + 0.058525945991277695, -0.015373348258435726, 0.00786789320409298, + -0.03333660960197449, 0.024056723341345787, -0.004628615919500589, + -0.06604650616645813, 0.022116176784038544, -0.010404948145151138, + 0.0031713193748146296, 0.019692381843924522, -0.08060436695814133, + 0.015637625008821487, 0.017261039465665817, -0.11375976353883743, + 0.008041559718549252, 0.00019631977193057537, -0.05997568741440773, + 0.041045933961868286, -0.005315735470503569, -0.002378489589318633, + 0.01956401951611042, 0.05740842968225479, 0.005942448507994413, + 0.022486163303256035, 0.002099111443385482, 0.02795291505753994, + -0.03366883844137192, -0.014572967775166035, -0.0296065304428339, + 0.018793843686580658, -0.016490861773490906, -0.0028390861116349697, + 0.021549871191382408, 0.04615779593586922, -0.019088322296738625, + -0.12028361111879349, -0.010571064427495003, -0.032709892839193344, + -0.026865605264902115, -0.02486465498805046, 0.006221826653927565, + 0.013802791014313698, -0.0009060912416316569, 0.01171122957020998, + 0.0067352778278291225, 0.05201718956232071, 0.01818978041410446, + -0.09494326263666153, -0.004756979178637266, -0.014452156610786915, + 0.014799490571022034, 0.005647968500852585, 0.058926135301589966, + 0.006916496902704239, 0.014051965437829494, -0.012874046340584755, + -0.06671097129583359, 0.023679183796048164, 0.028405960649251938, + -0.01400666031986475, -0.03927905485033989, -0.015849046409130096, + 0.005126966163516045, 0.04285811632871628, 0.04158203676342964, + 0.014505011029541492, -0.053074296563863754, -0.00996700394898653, + 0.021210087463259697, 0.008940099738538265, 0.02851167321205139, + 0.01449745986610651, 0.008185024373233318, 0.00274092610925436, + 0.022425759583711624, 0.009408247657120228, -0.007052409928292036, + 0.03719504550099373, -0.02506852336227894, 0.016490861773490906, + -0.011046762578189373, 0.052311670035123825, 0.018756089732050896, + -0.009415797889232635, 0.0008683374035172164, 0.022403106093406677, + -0.03511858731508255, 0.0030807103030383587, -0.01668718084692955, + -0.00672017689794302, -0.015562117099761963, -0.005376141518354416, + 0.021595174446702003, 0.027409261092543602, -0.00037753803189843893, + -0.004605963826179504, 0.016294540837407112, -0.030135085806250572, + 0.0011477156076580286, -0.03647017478942871, -0.044413574039936066, + 0.010835341177880764, 0.02777169644832611, -0.00030203041387721896, + 0.04723755642771721, -0.004636167082935572, -0.0037602786906063557, + -0.06221826747059822, 0.003178870305418968, -0.011628171429038048, + 0.004175570793449879, -0.02207842469215393, -0.02427569404244423, + 0.007361991330981255, 0.01446725707501173, 0.02302226796746254, + -0.004930646624416113, -0.0018423855071887374, -0.008653171360492706, + 0.0005436547799035907, 0.0017593271331861615, 0.012300188653171062, + 0.026533372700214386, -0.03156972676515579, 0.021655581891536713, + -0.05373876169323921, -0.005013705231249332, -0.009204376488924026, + 0.00019631977193057537, 0.04528946056962013, 0.02777169644832611, + 0.022116176784038544, -0.024577725678682327, -0.02348286472260952, + -0.004855139181017876, 0.007445049937814474, 0.010246382094919682, + -0.0012911800295114517, -0.0016309642232954502, 0.0021897205151617527, + -0.044776007533073425, -0.06371331959962845, -0.00727138202637434, + -0.09274598956108093, 0.03334415704011917, 0.030724044889211655, + -0.006418146193027496, 0.02210862748324871, -0.019345048815011978, + -0.019178932532668114, -0.041159193962812424, -0.009159073233604431, + 0.008290735073387623, -0.014746634289622307, 0.000823032867629081, + 0.023520618677139282, -0.008502155542373657, 0.01005761232227087, + -0.0026654184330254793, 0.0026427661068737507, 0.006297334562987089, + 0.02388305403292179, -0.011371445842087269, -0.014286038465797901, + 0.016279440373182297, 0.045025184750556946, 0.00844930112361908, + 0.024653233587741852, -0.13621573150157928 + ] + }, + "selfies": { + "prompt": "Happy and nostalgic selfie with people, clearly taken from the front camera of a phone", + "vector": [ + 0.015917416661977768, 0.050739727914333344, -0.02502211555838585, + -0.087443046271801, 0.03248291090130806, 0.0026871508453041315, + -0.00765837961807847, 0.007144659757614136, 0.03526490181684494, + 0.001857295399531722, -0.003943788819015026, -0.007460794877260923, + -0.03395294025540352, -0.0075793457217514515, -0.004188793711364269, + 0.4699036478996277, 0.030467547476291656, -0.014897879213094711, + 0.0670839250087738, -0.05165652185678482, -0.015071753412485123, + -0.02868928574025631, 0.025488415732979774, -0.010851346887648106, + 0.017822131514549255, 0.01167329866439104, -0.01135716401040554, + -0.009808100759983063, 0.06773991137742996, 0.01942651905119419, + 0.02820718102157116, -0.041832614690065384, -0.005722050555050373, + 0.01917361095547676, 0.0193316787481308, -0.03930353373289108, 0.0, + 0.0002133913803845644, 0.0028136048931628466, -0.049388252198696136, + -0.035889267921447754, 0.004259924404323101, -0.04313667118549347, + 0.04614786058664322, 0.04571317881345749, -0.05501545965671539, + -0.0005216234130784869, -0.025361960753798485, 0.03127369284629822, + -0.012866709381341934, -0.056959692388772964, 0.07071158289909363, + 0.015150788240134716, -0.027337808161973953, -0.055307887494564056, + -0.046590451151132584, 0.00572995375841856, 0.042409561574459076, + -0.03867916390299797, 0.011823463253676891, -0.01588580198585987, + 0.006188350263983011, -0.03316260129213333, -0.004797354806214571, + -0.08805950731039047, 0.010337627492845058, 0.017490191385149956, + 0.008749046362936497, 0.025891486555337906, 0.049546319991350174, + 0.014629164710640907, 0.054177701473236084, -0.0517592653632164, + 0.015150788240134716, -0.008662109263241291, -0.001414705766364932, + -0.069162517786026, -0.012455734424293041, 0.06672036647796631, + 0.008843887597322464, 0.048005156219005585, -0.04151647910475731, + 0.019766364246606827, 0.020738480612635612, 0.014992720447480679, + 0.04920647293329239, -0.0025211796164512634, 0.019797977060079575, + 0.035960398614406586, 0.0034300689585506916, 0.0030586100183427334, + 0.027100706472992897, 0.024808725342154503, 0.012179115787148476, + -0.013111715205013752, 0.013894150033593178, -0.003643460338935256, + 0.033249542117118835, -0.04278102144598961, -0.020232664421200752, + -0.007176273502409458, 0.01348317414522171, -0.043500229716300964, + -0.01609129086136818, -0.0025369864888489246, 0.0075872489251196384, + -0.042788922786712646, 0.04044952243566513, -0.01958458684384823, + 0.003667170647531748, -0.01606758125126362, -0.023465149104595184, + 0.015032238326966763, -0.059915561228990555, 0.008970341645181179, + 0.03035689890384674, 0.010685376822948456, -0.05084247514605522, + -0.016533881425857544, 0.005737856961786747, 0.03273581713438034, + -0.010369240306317806, 0.014692391268908978, -0.0033431316260248423, + 0.020959775894880295, 0.0011143771698698401, 0.050478916615247726, + -0.04680384323000908, -0.01737954281270504, -0.012890420854091644, + -0.021394461393356323, 0.014945301227271557, -0.023710153996944427, + -0.009657936170697212, 0.008053549565374851, 0.027456358075141907, + 0.0069075580686330795, 0.02016153372824192, 0.0330914705991745, + -0.07644153386354446, -0.02246141992509365, -0.00044258954585529864, + -0.0005216234130784869, -0.026428917422890663, -0.0035091026220470667, + -0.0034853925462812185, -0.0336921289563179, -0.022382386028766632, + 0.031281597912311554, -0.08310408890247345, 0.009657936170697212, + 0.02995382808148861, -0.017901165410876274, 0.0012171212583780289, + 0.022777553647756577, -0.0060223788022994995, -0.0018414886435493827, + -0.01603596843779087, -0.0593465156853199, -0.04250440001487732, + 0.027709266170859337, 0.03439553081989288, -0.03536764532327652, + 0.011199096217751503, 0.03919288516044617, -0.008986148051917553, + 0.017110828310251236, -0.4741714596748352, 0.05406705290079117, + 0.017442768439650536, -0.004923808388411999, 0.05942555144429207, + 0.027914753183722496, -0.009246960282325745, 0.0069154612720012665, + -0.04851888120174408, 0.02397886849939823, 0.0008693723357282579, + 0.022121572867035866, 0.13283218443393707, -0.015245628543198109, + -0.006488678976893425, 0.01965571753680706, -0.012218632735311985, + 0.04632173851132393, 0.057386476546525955, 0.002450049389153719, + -0.02909236028790474, 0.004354765173047781, -0.059259574860334396, + 0.026958445087075233, -0.019473940134048462, 0.0340556837618351, + -0.03817334771156311, 0.004117663484066725, -0.018738925457000732, + 0.0018731020390987396, -0.018154073506593704, -0.03981725126504898, + 0.0010432468261569738, 0.026326173916459084, 0.04975971207022667, + -0.059109412133693695, -0.029021229594945908, -0.02719554677605629, + -0.012756062671542168, -0.02954285219311714, 0.03445085510611534, + 0.050146978348493576, 0.05016278102993965, -0.008511945605278015, + -0.006512389052659273, 0.0041097598150372505, -0.018936509266495705, + -0.024397749453783035, 0.004615576937794685, 0.032064031809568405, + 0.039959512650966644, 0.02782781794667244, 0.0013198652304708958, + 0.015972739085555077, 0.015332565642893314, 0.016280973330140114, + 0.011570555157959461, 0.022659003734588623, 0.010179559700191021, + -0.011910400353372097, 0.015672411769628525, 0.01436835341155529, + -0.017316315323114395, -0.02405790239572525, -0.006046089343726635, + 0.0014779329067096114, -0.07849641144275665, 0.0059828623197972775, + 0.03033319115638733, 0.016968566924333572, -0.03571539372205734, + -0.001509546535089612, -0.020951872691512108, -0.012171212583780289, + 0.004180890507996082, -0.01209217868745327, 0.02031169831752777, + -0.041706159710884094, 0.049570027738809586, 0.015538053587079048, + -0.05229669809341431, -0.019845400005578995, -0.04116082563996315, + 0.011025221087038517, -0.08250343799591064, 0.008551462553441525, + 0.003888465464115143, -0.02114945650100708, -0.025899391621351242, + -0.03126579150557518, -0.061259131878614426, -0.04042581096291542, + 0.015893707051873207, -0.07852012664079666, -0.01596483774483204, + 0.013467367738485336, 0.009444545023143291, 0.040868401527404785, + -0.028807837516069412, -0.027914753183722496, -0.035280708223581314, + 0.016842111945152283, 0.01143619790673256, -0.0288157407194376, + 0.022295447066426277, 0.016178227961063385, -0.031811121851205826, + -0.02838895656168461, 0.025899391621351242, 0.0017466479912400246, + 0.008662109263241291, -0.00922324974089861, 0.001983749447390437, + 0.027764590457081795, -0.026247140020132065, 0.000545333547051996, + -0.061132680624723434, -0.014905783347785473, 0.03196919336915016, + -0.0200824998319149, 0.00834597460925579, -0.02823088876903057, + 0.004931711591780186, -0.03958805650472641, -0.03649783134460449, + -0.021916085854172707, 0.03401616960763931, 0.002126010600477457, + 0.014921589754521847, -0.033557772636413574, 0.08977454900741577, + 0.03425326943397522, 0.003366841934621334, 0.005113489460200071, + -0.015617088414728642, -0.025306636467576027, 0.03321792557835579, + -0.007460794877260923, 0.0021655273158103228, 0.006828524172306061, + 0.020201051607728004, -0.016612913459539413, -0.04017290472984314, + -0.04703304171562195, -0.018146172165870667, -0.008290650323033333, + 0.0038252382073551416, 0.018960220739245415, 0.04915114864706993, + -0.049799226224422455, 0.07689203321933746, -0.027377326041460037, + 0.015553861856460571, 0.022303352132439613, -0.06472081691026688, + -0.022706422954797745, 0.036545250564813614, 0.09203491359949112, + -0.020849129185080528, 0.007049818988889456, 0.06066638231277466, + -0.02196350507438183, 0.008496138267219067, 0.03311518207192421, + -0.00007903384539531544, 0.021694790571928024, 0.04487541690468788, + 0.007895481772720814, -0.03597620874643326, -0.0013751889346167445, + 0.015648702159523964, -0.0345061756670475, 0.01453432347625494, + -0.01897602714598179, -0.031155141070485115, 0.026492144912481308, + -0.0359208844602108, 0.008164196275174618, -0.042354241013526917, + -0.021268006414175034, -0.014613358303904533, -0.13114877045154572, + 0.018509726971387863, 0.004489122424274683, -0.013570111244916916, + 0.01238460373133421, -0.030949654057621956, 0.025646483525633812, + 0.015846285969018936, -0.09424786269664764, 0.024674367159605026, + -0.044029753655195236, 0.01856505125761032, -0.01834375597536564, + 0.02671344019472599, -0.005263654049485922, 0.0069075580686330795, + 0.020398635417222977, 0.013720275834202766, 0.0563037134706974, + 0.044614605605602264, -0.000987923122011125, -0.008369684219360352, + 0.036972030997276306, -0.03273581713438034, 0.01824101060628891, + -0.03617379069328308, 0.014423677697777748, -0.02711651287972927, + 0.07519280165433884, -0.03147917985916138, 0.05080295726656914, + 0.0008061452535912395, -0.021220587193965912, -0.0074449884705245495, + 0.007531925570219755, -0.0281518567353487, 0.03056238777935505, + -0.03332066908478737, -0.017387446016073227, -0.004196696914732456, + 0.06400161236524582, -0.10398483276367188, -0.061053644865751266, + -0.07527974247932434, -0.008646302856504917, 0.022255931049585342, + 0.01205266173928976, 0.026223430410027504, -0.03162934258580208, + -0.031613539904356, 0.0017071310430765152, -0.08489025384187698, + 0.0036592669785022736, -0.07139917463064194, 0.0009009858476929367, + -0.0031139336060732603, 0.043626684695482254, 0.003651363542303443, + 0.019900722429156303, 0.03483021631836891, -0.007500311825424433, + 0.02757490798830986, -0.04568946361541748, 0.028302019461989403, + -0.015119175426661968, -0.008203713223338127, -0.012835096567869186, + 0.042615048587322235, -0.039279818534851074, 0.02332288771867752, + -0.0243898443877697, -0.03396874666213989, 0.052881546318531036, + -0.07279808074235916, -0.03365261107683182, 0.02646053023636341, + 0.039121754467487335, 0.015893707051873207, 0.007421278394758701, + 0.025788743048906326, 0.020675254985690117, 0.010637955740094185, + 0.011657492257654667, -0.03491715341806412, -0.03268839791417122, + 0.0022682715207338333, 0.019813785329461098, 0.01329349260777235, + -0.004133470356464386, 0.0031139336060732603, 0.012961551547050476, + -0.023204337805509567, -0.032166775315999985, -0.06231028214097023, + -0.08763272315263748, -0.0237259604036808, -0.0020074595231562853, + -0.034245364367961884, -0.04824225977063179, -0.017703581601381302, + -0.015640798956155777, -0.023836607113480568, -0.019015545025467873, + -0.014605454169213772, 0.06023959815502167, -0.028136050328612328, + -0.0054454319179058075, 0.009989878162741661, 0.0352570004761219, + -0.0196320079267025, 0.023267565295100212, 0.017624547705054283, + -0.00889130774885416, -0.020422345027327538, 0.010313916951417923, + 0.011823463253676891, -0.06701279431581497, 0.0280333049595356, + -0.03893997520208359, -0.01917361095547676, -0.027179740369319916, + -0.043421193957328796, -0.02129962295293808, -0.027582813054323196, + -0.005413818638771772, -0.0025923100765794516, 0.006164640188217163, + -0.02005879022181034, 0.03063351847231388, 0.05219395086169243, + -0.021995117887854576, 0.04875598102807999, -0.02876041643321514, + -0.005168813746422529, 0.04773644357919693, 0.015134981833398342, + 0.000940502795856446, 0.02046186290681362, -0.08118356764316559, + 0.013554304838180542, -0.005737856961786747, 0.02631036750972271, + 0.04408508166670799, 0.027203449979424477, -0.02306997962296009, + 0.003619750263169408, -0.03498038277029991, 0.014012700878083706, + -0.0035011994186788797, -0.008021934889256954, + -0.000023710153982392512, 0.0044337990693748, 0.012487347237765789, + -0.01917361095547676, 0.015142884105443954, -0.02519599162042141, + 0.005413818638771772, -0.0033826485741883516, 0.0006796910893172026, + 0.047341275960206985, 0.039556439965963364, -0.018130365759134293, + -0.027353614568710327, -0.05560821294784546 + ] + }, + "posing": { + "prompt": "Photo of people posing together in a funny manner for the camera", + "vector": [ + 0.010862172581255436, -0.002259705914184451, -0.019589310511946678, + -0.02353210747241974, 0.011734886094927788, -0.021841226145625114, + 0.0055947196669876575, 0.027217766270041466, 0.024350278079509735, + -0.04710317775607109, 0.01493743434548378, -0.04109547659754753, + 0.028659304603934288, 0.005968740209937096, -0.009934913367033005, + 0.568651020526886, 0.019573727622628212, -0.05230829492211342, + 0.009732319042086601, -0.033996883779764175, -0.004534995649009943, + -0.017352983355522156, -0.023680157959461212, -0.019028281792998314, + 0.007347939535975456, 0.011236192658543587, -0.006218086928129196, + -0.007293395232409239, 0.0030700829811394215, -0.0027506074402481318, + 0.003638905705884099, -0.00935829896479845, -0.007698584347963333, + 0.03275015205144882, 0.013737453147768974, -0.009693358093500137, + -0.007371315732598305, 0.004075262695550919, 0.0011064766440540552, + 0.02505156584084034, 0.02505156584084034, 0.003989549353718758, + -0.0027817757800221443, -0.013114085420966148, 0.01835816167294979, + -0.022285373881459236, -0.02734243869781494, -0.021069807931780815, + 0.038516294211149216, -0.008438833057880402, -0.03540725260972977, + 0.041056517511606216, 0.008960902690887451, -0.00017921804101206362, + -0.014890681952238083, -0.04561488702893257, 0.01084658782929182, + -0.00024155476421583444, -0.0013480314519256353, + -0.0008415456395596266, -0.010643993504345417, 0.0029609936755150557, + -0.037277352064847946, -0.016129624098539352, 0.0026804786175489426, + 0.002703854814171791, -0.009732319042086601, -0.0028441124595701694, + -0.00356877688318491, 0.07399367541074753, 0.011415409855544567, + 0.015599762089550495, -0.0187165979295969, 0.012638768181204796, + -0.009264794178307056, 0.01648026891052723, -0.05869780480861664, + -0.005649264436215162, 0.03279690444469452, 0.028955401852726936, + -0.011197231709957123, -0.027981389313936234, 0.004449282772839069, + -0.010519320145249367, -0.01870880462229252, -0.007340147625654936, + -0.0010207636514678597, -0.0014804968377575278, -0.008002475835382938, + 0.005890819244086742, 0.005602512042969465, -0.00751157384365797, + 0.02130357176065445, -0.015358206816017628, 0.02491910010576248, + 0.022565890103578568, -0.020968511700630188, -0.010106339119374752, + 0.0006467433995567262, 0.006779117044061422, -0.01658935658633709, + 0.0009506348869763315, -0.019472431391477585, 0.01172709371894598, + -0.0008415456395596266, 0.03326442837715149, -0.038874730467796326, + -0.008968694135546684, -0.00034285191213712096, + -0.0005688224919140339, -0.020898383110761642, -0.02009579725563526, + 0.08457533270120621, -0.007698584347963333, 0.00875051598995924, + -0.0446486696600914, 0.011633588932454586, -0.01245955005288124, + -0.00289086508564651, 0.004145391285419464, 0.05244075879454613, + -0.011571251787245274, 0.015389375388622284, -0.00446486659348011, + -0.01843608170747757, -0.017368566244840622, 0.02174772135913372, + -0.013371225446462631, 0.0169244185090065, -0.012412797659635544, + -0.01758674345910549, 0.005228491500020027, -0.03711371868848801, + 0.015623139217495918, 0.05841728672385216, 0.001667506992816925, + -0.026095706969499588, 0.0027272312436252832, 0.002477884292602539, + -0.022908741608262062, -0.023773664608597755, 0.022191869094967842, + 0.0031480039469897747, -0.013238758780062199, 0.019519183784723282, + 0.02527753636240959, 0.005290828179568052, -0.014056929387152195, + 0.0027272312436252832, -0.0545835867524147, -0.009023238904774189, + 0.028893064707517624, -0.04298895597457886, 0.0009194665472023189, + -0.027482695877552032, -0.008134940639138222, -0.0074726128950715065, + -0.011220607906579971, -0.037963055074214935, -0.029111245647072792, + -0.020578907802700996, 0.019550351426005363, -0.02579960599541664, + 0.006779117044061422, -0.02009579725563526, 0.037822797894477844, + -0.018038686364889145, -0.5727107524871826, 0.025674933567643166, + 0.01623092219233513, -0.028682678937911987, 0.05233946070075035, + 0.015288078226149082, 0.0012545263161882758, 0.0124439662322402, + -0.015132236294448376, 0.012771233916282654, 0.0011454371269792318, + -0.004254480358213186, 0.03370078653097153, -0.003444103291258216, + 0.038072146475315094, 0.020789293572306633, -0.02684374526143074, + 0.021202275529503822, 0.008197277784347534, -0.022978870198130608, + -0.024404821917414665, 0.027825549244880676, 0.0008415456395596266, + 0.02039968967437744, -0.015420544892549515, 0.054692670702934265, + -0.044461656361818314, -0.005851858761161566, 0.007231058552861214, + -0.021326947957277298, 0.00887518934905529, 0.0011376449838280678, + 0.0008882981492206454, 0.05418618768453598, 0.03594490885734558, + -0.05126415193080902, -0.014914058148860931, 0.006646651774644852, + -0.009334922768175602, -0.025900904089212418, -0.006108997855335474, + 0.025791816413402557, 0.03403584286570549, -0.0022674978245049715, + -0.0010129715083166957, 0.01477380096912384, -0.026391804218292236, + -0.022830819711089134, 0.04309804365038872, -0.012825778685510159, + 0.020984094589948654, 0.014984187670052052, 0.012217995710670948, + 0.002127240179106593, 0.03755787014961243, -0.006911583244800568, + 0.021342530846595764, 0.0182179044932127, 0.00843103975057602, + -0.0045583718456327915, 0.0017532199854031205, -0.009038823656737804, + -0.03983315825462341, -0.02408534660935402, -0.03221249580383301, + 0.006171334534883499, -0.016293257474899292, -0.0228230282664299, + 0.01149333082139492, -0.007223266176879406, 0.007893386296927929, + -0.015116652473807335, -0.0013012788258492947, -0.002251913771033287, + 0.0013090709690004587, -0.06694962829351425, 0.014999771490693092, + -0.05226153880357742, 0.015124444849789143, -0.0295398086309433, + -0.009015446528792381, -0.01918412186205387, -0.02356327697634697, + 0.017758170142769814, -0.048490170389413834, 0.0064206812530756, + 0.01907503418624401, -0.018062062561511993, -0.002470092149451375, + -0.03808772936463356, 0.011158271692693233, -0.04685383290052414, + -0.006560939364135265, -0.014025759883224964, 0.0011376449838280678, + -0.005859650671482086, 0.01603611931204796, 0.027334649115800858, + 0.03127744421362877, -0.0018934777472168207, 0.015747811645269394, + 0.01681532710790634, 0.0050960262306034565, -0.01601274311542511, + -0.005072650033980608, -0.014329652301967144, -0.029080074280500412, + 0.006864830385893583, -0.017204932868480682, 0.03066966123878956, + 0.00846220925450325, -0.01464912761002779, -0.04285648837685585, + 0.021599670872092247, -0.023687949404120445, -0.00855571310967207, + -0.07308200001716614, -0.01865426078438759, 0.01531145442277193, + 0.0014181601582095027, 0.009810240007936954, -0.04577852413058281, + 0.02196589857339859, 0.002251913771033287, -0.0351189449429512, + 0.0013480314519256353, 0.02700738050043583, 0.021623047068715096, + -0.012997204437851906, -0.012864738702774048, 0.04727460443973541, + 0.014477700926363468, -0.012927074916660786, 0.00814273301512003, + -0.005610303953289986, 0.00518173910677433, 0.011072558350861073, + -0.005750561598688364, -0.009693358093500137, -0.009428427554666996, + -0.003638905705884099, 0.0159036535769701, -0.005485630594193935, + -0.003701242385432124, -0.019558142870664597, -0.004316817037761211, + 0.022994454950094223, 0.03232158347964287, 0.04605124890804291, + -0.04007471352815628, 0.01918412186205387, -0.036615025252103806, + -0.01489847432821989, 0.05593161657452583, -0.018155567348003387, + -0.03229821100831032, 0.02337626740336418, 0.005649264436215162, + 0.0005610304069705307, -0.041601963341236115, 0.05302516743540764, + 0.027381401509046555, 0.017127012833952904, 0.0003506439970806241, + -0.010527112521231174, 0.020703580230474472, 0.13976670801639557, + -0.013464730232954025, 0.0022129532881081104, -0.002766191493719816, + -0.03004629537463188, -0.0017610121285542846, -0.014049137011170387, + 0.00998945813626051, -0.019215291365981102, 0.04412660002708435, + -0.02331393025815487, 0.014158225618302822, 0.007277811411768198, + -0.012763441540300846, -0.01570885069668293, -0.05846404284238815, + 0.0004207728197798133, -0.011968648061156273, 0.011337489821016788, + -0.03463583439588547, -0.06321722269058228, 0.017376359552145004, + 0.01678415946662426, -0.08861164003610611, -0.00014804970123805106, + 0.005111610516905785, 0.014345236122608185, 0.015093276277184486, + 0.011267360299825668, -0.005158362910151482, 0.002189577091485262, + 0.03795526549220085, 0.0333501435816288, 0.0015194573206827044, + 0.05777055025100708, -0.005859650671482086, -0.02086721360683441, + 0.041407160460948944, 0.027981389313936234, 0.011392033658921719, + -0.011734886094927788, 0.009997250512242317, -0.0025713893119245768, + 0.0315345823764801, -0.006451849360018969, -0.03194756433367729, + -0.025815190747380257, -0.02221524529159069, -0.010262181051075459, + -0.021093184128403664, -0.00021817849483340979, 0.023103544488549232, + -0.021311363205313683, -0.014127057045698166, -0.03778383880853653, + 0.035095568746328354, -0.002080487785860896, -0.09577256441116333, + -0.01047256775200367, 0.018724389374256134, 0.024069763720035553, + -0.00024934683460742235, -0.006475226022303104, -0.0117972232401371, + -0.019885409623384476, 0.02056332305073738, -0.09992574900388718, + -0.013223174959421158, -0.0048233033157885075, 0.0032882613595575094, + -0.006498602218925953, 0.029781363904476166, 0.0034129349514842033, + 0.004301233217120171, 0.030957968905568123, -0.02788788452744484, + 0.003864876227453351, -0.049043409526348114, 0.020298391580581665, + 0.02144382894039154, -0.02499702200293541, -0.006895998492836952, + 0.011142686940729618, -0.022838613018393517, 0.016682861372828484, + -0.0020259430166333914, -0.020711371675133705, 0.07982216030359268, + -0.059983499348163605, 0.0027350231539458036, 0.018584132194519043, + 0.04217857867479324, 0.014945225790143013, -0.04072924703359604, + 0.025784023106098175, 0.004909015726298094, 0.012779026292264462, + -0.02458404004573822, 0.006584315560758114, 0.0033739744685590267, + -0.006740157026797533, 0.003498647827655077, 0.016745198518037796, + 0.005431086290627718, 0.0233684740960598, -0.01979190669953823, + 0.004199936054646969, -0.0400669202208519, 0.003405143041163683, + -0.02759178727865219, 0.017454279586672783, -0.013456937856972218, + -0.015132236294448376, -0.021342530846595764, 0.027404775843024254, + -0.04580968990921974, -0.016667278483510017, -0.026446349918842316, + -0.005220699589699507, 0.003296053735539317, -0.008384287357330322, + -0.047781091183423996, -0.01192189659923315, -0.03818902745842934, + 0.02091396600008011, 0.001558417803607881, 0.0072466423735022545, + 0.010301141999661922, -0.02191135287284851, 0.0022207454312592745, + 0.005220699589699507, 0.007644039113074541, 0.08062474429607391, + -0.016916625201702118, 0.002470092149451375, -0.01870880462229252, + -0.016971170902252197, -0.02560480497777462, -0.014602375216782093, + 0.0215295422822237, -0.005625888239592314, 0.01019984483718872, + 0.006327176000922918, 0.009498557075858116, -0.021326947957277298, + 0.01393225509673357, 0.005353164859116077, 0.007036256603896618, + 0.005555759649723768, 0.03422285243868828, 0.01172709371894598, + 0.05642251297831535, -0.026228170841932297, -0.1293564736843109, + 0.006514186505228281, -0.007121969014406204, 0.023243801668286324, + 0.028394371271133423, 0.014197185635566711, 0.009646606631577015, + -0.012038777582347393, 0.014656919054687023, 0.020321767777204514, + -0.014960811473429203, 0.004262272734194994, 0.051965437829494476, + 0.014298483729362488, -0.0017298436723649502, 0.0006779117393307388, + -0.005119402427226305, -0.02624375745654106, -0.011937480419874191, + -0.014797177165746689, -0.0035921530798077583, 0.039622772485017776, + 0.05270569026470184, 0.01697896234691143, 0.01007517147809267, + -0.0711028128862381 + ] + }, + "background": { + "prompt": "Photo of people with an absolutely stunning or interesting background", + "vector": [ + -0.014705854468047619, 0.001741824671626091, 0.005927403923124075, + 0.010000327602028847, -0.0043155658058822155, -0.010771583765745163, + 0.02611004002392292, -0.00355297583155334, 0.030156968161463737, + 0.03655232489109039, 0.005528777372092009, 0.025919392704963684, + 0.02458486147224903, 0.0015945063205435872, 0.0063086990267038345, + 0.5186651945114136, -0.012028124183416367, -0.009879006072878838, + -0.0039082737639546394, -0.02218443527817726, 0.004506213590502739, + -0.02672531269490719, 0.0031110206618905067, -0.01181147899478674, + 0.005953401327133179, 0.01045094896107912, -0.007365925703197718, + 0.006889307405799627, 0.04576406255364418, 0.03521779179573059, + -0.025555429980158806, 0.0023137673269957304, 0.018926095217466354, + 0.006438686046749353, -0.0043068998493254185, 0.0019931329879909754, + -0.020711250603199005, 0.014021256938576698, 0.0008059189422056079, + -0.021716482937335968, -0.00024264225794468075, -0.009047090075910091, + 0.021248528733849525, -0.03074624016880989, -0.003864944912493229, + -0.020468607544898987, 0.018232833594083786, -0.01973201520740986, + 0.021499838680028915, 0.009463047608733177, 0.012669392861425877, + 0.033042676746845245, 0.02818116545677185, 0.005927403923124075, + -0.018068183213472366, -0.02570274844765663, 0.07050491124391556, + -0.010112982243299484, -0.02271305024623871, 0.008076520636677742, + 0.05238473042845726, -0.010745585896074772, -0.03836347535252571, + 0.014974493533372879, -0.029784338548779488, 0.0220631156116724, + 0.0046448661014437675, -0.035989049822092056, 0.0028250492177903652, + 0.06036592647433281, -0.00710595166310668, 0.026664651930332184, + -0.026629988104104996, 0.023718280717730522, -0.024810170754790306, + 0.011768150143325329, -0.08320896327495575, 0.0017331590643152595, + 0.10377290099859238, 0.04410889744758606, -0.01850147359073162, + -0.07739421725273132, 0.008449150249361992, 0.0003206344263162464, + 0.029567692428827286, 0.024463539943099022, -0.003795618424192071, + 0.00887377467006445, -0.03964601084589958, -0.009168410673737526, + -0.018683454021811485, 0.018024854362010956, -0.01592773012816906, + -0.00891710352152586, -0.00811118446290493, 0.01965402439236641, + 0.026803303509950638, 0.007989862933754921, 0.017738882452249527, + -0.01795552857220173, 0.009437051601707935, -0.03222808986902237, + -0.024402879178524017, 0.06099853292107582, 0.035859059542417526, + 0.03632701188325882, -0.05632766708731651, -0.006845978554338217, + -0.009428384713828564, 0.016369687393307686, 0.0005632766988128424, + -0.015329791232943535, 0.10203107446432114, -0.046647973358631134, + -0.009272401221096516, 0.02673397958278656, -0.01079758070409298, + 0.013042021542787552, -0.006118051242083311, 0.027340583503246307, + 0.000372629176126793, -0.031101539731025696, 0.04186445474624634, + 0.012964029796421528, 0.021673154085874557, 0.01696762628853321, + 0.09339127689599991, -0.022669721394777298, -0.003986265975981951, + -0.012808045372366905, -0.016291694715619087, 0.021621158346533775, + -0.017314258962869644, 0.03532177954912186, 0.026335351169109344, + 0.004714192356914282, -0.02821582928299904, 0.02479284070432186, + 0.002209777943789959, -0.0157284177839756, 0.01334532443434, + -0.017392251640558243, -0.004956834949553013, 0.004835513886064291, + 0.013267332687973976, 0.0009359058458358049, -0.012868705205619335, + -0.010650262236595154, -0.030269624665379524, -0.03364061564207077, + 0.015269131399691105, -0.007556573487818241, -0.06311298906803131, + -0.013631295412778854, -0.04714192450046539, 0.012028124183416367, + -0.006499346345663071, -0.006663996260613203, 0.023865601047873497, + -0.00870912428945303, -0.05077289417386055, 0.025720080360770226, + 0.007816547527909279, 0.012392086908221245, -0.025538098067045212, + 0.01365729235112667, 0.004142250400036573, -0.5228767395019531, + -0.021447842940688133, -0.0018544801278039813, -0.0165516696870327, + 0.07093819975852966, 0.03947269544005394, -0.024229563772678375, + 0.010849575512111187, 0.008041857741773129, -0.0050954874604940414, + -0.016976293176412582, -0.0007019293843768537, 0.05097220838069916, + -0.004497547633945942, 0.028293821960687637, -0.01125686801970005, + -0.01136085670441389, 0.005468116607517004, -0.003431654768064618, + -0.019350720569491386, -0.021612493321299553, -0.0026084042619913816, + 0.014610530808568, -0.030962886288762093, -0.030148301273584366, + 0.04546942561864853, -0.10783714801073074, -0.003925605211406946, + -0.03533044829964638, 0.021629825234413147, 0.003284336533397436, + -0.01100555993616581, -0.02330232411623001, 0.024844834581017494, + 0.012591400183737278, -0.009965664707124233, -0.004722858313471079, + 0.02870977856218815, -0.013908601365983486, 0.00006932636460987851, + -0.017886200919747353, -0.005277469288557768, -0.016083715483546257, + 0.004956834949553013, 0.027409909293055534, -0.020061315968632698, + -0.003535644384101033, 0.004705526866018772, 0.04721991717815399, + -0.0024437541142106056, -0.03354529291391373, 0.010606933385133743, + -0.015347123146057129, 0.022565731778740883, 0.03150016441941261, + 0.02127452753484249, 0.03058158978819847, -0.021586496382951736, + -0.01185480784624815, 0.018388817086815834, 0.005173479672521353, + -0.0035789732355624437, -0.0014471877366304398, -0.003864944912493229, + 0.022739047184586525, -0.008249836973845959, 0.027461905032396317, + -0.022028449922800064, 0.017825540155172348, -0.020061315968632698, + -0.00854447390884161, -0.007027959916740656, -0.02036461792886257, + -0.034524526447057724, 0.0005632766988128424, -0.09423185139894485, + -0.012756050564348698, -0.07407521456480026, -0.0036482997238636017, + -0.03629234805703163, -0.06251505017280579, -0.04469817131757736, + -0.013189340010285378, -0.005156148225069046, -0.02992299012839794, + 0.0010918901534751058, 0.006984631065279245, -0.01935938559472561, + -0.023137671872973442, -0.008189177140593529, -0.04037394002079964, + -0.03426455333828926, 0.011204873211681843, -0.007617233786731958, + -0.004523545037955046, 0.0020711252000182867, -0.0011958797695115209, + -0.016040386632084846, 0.019541367888450623, -0.007088620215654373, + 0.00893443450331688, -0.018258830532431602, 0.020130641758441925, + 0.028805101290345192, -0.009827012196183205, -0.02884843200445175, + -0.02470618113875389, -0.007989862933754921, 0.017149608582258224, + -0.0020711252000182867, -0.0030936887487769127, -0.028432471677660942, + -0.027990518137812614, -0.03516579791903496, -0.002495748922228813, + -0.002781720133498311, -0.059612005949020386, 0.01388260442763567, + -0.004428221378475428, 0.005797416903078556, 0.006404022686183453, + -0.013024689629673958, -0.013908601365983486, 0.013267332687973976, + -0.00565876392647624, -0.014428549446165562, 0.009073087014257908, + 0.006057390943169594, -0.013995259068906307, -0.04328564926981926, + 0.06733322888612747, 0.012903369031846523, 0.012669392861425877, + 0.006430020090192556, -0.002686396474018693, -0.003934271167963743, + 0.005780085455626249, -0.008674461394548416, 0.002036461839452386, + 0.006741988472640514, 0.013068018481135368, 0.0038736106362193823, + 0.0034143230877816677, 0.0636935904622078, 0.01019964087754488, + -0.028623120859265327, -0.003682962851598859, 0.027115274220705032, + 0.06764519959688187, -0.05486314743757248, -0.017036952078342438, + -0.04303433746099472, 0.003241007449105382, 0.03711559996008873, + 0.006499346345663071, 0.0020451275631785393, 0.03806883841753006, + 0.030390942469239235, 0.018215501680970192, -0.0263180211186409, + 0.05684761330485344, 0.005026161205023527, -0.02772187814116478, + 0.0043155658058822155, 0.00480085052549839, 0.009376389905810356, + 0.09580903500318527, 0.019628025591373444, 0.006586004514247179, + -0.03949002921581268, -0.018146174028515816, -0.02420356497168541, + -0.030217627063393593, -0.005303466692566872, 0.02084990404546261, + 0.002625735942274332, -0.005953401327133179, 0.01721893437206745, + -0.026785971596837044, -0.022123774513602257, -0.021257195621728897, + -0.07881540805101395, -0.013891268521547318, 0.007747221272438765, + -0.014610530808568, -0.00005199477163841948, -0.07542707771062851, + 0.036396339535713196, 0.029706347733736038, -0.007383257150650024, + 0.001325866673141718, -0.021040551364421844, -0.017738882452249527, + 0.003145683789625764, -0.012071453034877777, 0.03554708883166313, + 0.003596305148676038, 0.029117072001099586, 0.006915304809808731, + -0.01483584102243185, -0.011187541298568249, -0.008163179270923138, + 0.008267167955636978, -0.014740517362952232, 0.003466318128630519, + -0.011343525722622871, -0.02442021109163761, 0.054941143840551376, + -0.040573254227638245, 0.01577174663543701, -0.020017987117171288, + -0.061795786023139954, -0.004280902910977602, -0.010520275682210922, + -0.04650065675377846, -0.021031884476542473, 0.0007885873783379793, + 0.022938359528779984, 0.013293329626321793, -0.001325866673141718, + -0.05081622302532196, 0.02681197039783001, -0.03442053869366646, + -0.10379889607429504, -0.03584172949194908, 0.035191792994737625, + 0.0017678221920505166, 0.009922335855662823, -0.008969098329544067, + -0.0127473846077919, -0.06911838054656982, 0.01127419900149107, + -0.12100916355848312, -0.021031884476542473, -0.00002599738581920974, + 0.01791219785809517, -0.0077992151491343975, -0.027990518137812614, + 0.008882440626621246, 0.00002599738581920974, 0.043432965874671936, + -0.013778614811599255, -0.045339442789554596, 0.006447351537644863, + -0.004584205336868763, 0.02495748922228813, -0.0038909418508410454, + 0.0056500984355807304, 0.005494114011526108, -0.012418084777891636, + 0.035191792994737625, -0.012634729035198689, -0.01898675598204136, + 0.09869474172592163, -0.08138915151357651, -0.03569440916180611, + 0.01558110024780035, -0.015147809870541096, 0.0030936887487769127, + -0.014064585790038109, 0.028276490047574043, 0.008631131611764431, + -0.018154840916395187, -0.013206670992076397, -0.0013691956410184503, + -0.02682063728570938, 0.0019498037872835994, -0.01621370203793049, + 0.010095651261508465, 0.009740353561937809, 0.0278865285217762, + -0.01866612210869789, 0.016569001600146294, -0.034082572907209396, + -0.03928204998373985, -0.03595438227057457, 0.04257505014538765, + 0.03925605118274689, -0.007686560042202473, -0.007209941744804382, + 0.04557341709733009, -0.013024689629673958, -0.005364127457141876, + -0.02342364378273487, 0.004688194952905178, -0.018423479050397873, + -0.011846141889691353, -0.03744490072131157, 0.02342364378273487, + -0.0050954874604940414, 0.02503548189997673, 0.02118786983191967, + 0.019350720569491386, 0.006178712006658316, -0.047159258276224136, + 0.006005396135151386, -0.01779954321682453, 0.030962886288762093, + 0.1288430541753769, -0.017946861684322357, 0.011066220700740814, + 0.013475310988724232, -0.0294550359249115, -0.0018804774153977633, + 0.002530412282794714, 0.008041857741773129, -0.027583226561546326, + -0.04444686323404312, 0.009151079691946507, -0.041769132018089294, + -0.011724821291863918, -0.004904840141534805, 0.010320961475372314, + -0.013033355586230755, -0.013293329626321793, 0.04311233386397362, + -0.02163849025964737, 0.08099918812513351, -0.023241661489009857, + -0.18601128458976746, 0.027739210054278374, 0.013232668861746788, + 0.043337639421224594, 0.020009320229291916, -0.03266138210892677, + -0.0010138980578631163, 0.0072446041740477085, 0.011724821291863918, + 0.0009359058458358049, -0.034663181751966476, -0.02565941959619522, + 0.021534500643610954, -0.031352847814559937, 0.017444245517253876, + -0.007071288768202066, 0.009047090075910091, -0.020667921751737595, + -0.015468443743884563, -0.0004766187339555472, -0.017262263223528862, + 0.021993787959218025, 0.012357424013316631, -0.00497416639700532, + -0.022149773314595222, -0.017392251640558243 + ] + }, + "sports": { + "prompt": "Photo of people joyfully playing sports together", + "vector": [ + -0.005494717042893171, 0.014157154597342014, -0.007176160346716642, + -0.06306163221597672, -0.02043253928422928, 0.015883635729551315, + -0.017932895570993423, 0.008880123496055603, -0.03749468922615051, + -0.036038439720869064, -0.010418944992125034, -0.015861116349697113, + 0.010839304886758327, 0.020199840888381004, 0.01848086528480053, + 0.5462063550949097, 0.013301420025527477, 0.017745234072208405, + 0.022181542590260506, -0.061507806181907654, 0.010126193054020405, + -0.0285244882106781, 0.01592867448925972, -0.021588532254099846, + 0.024313373491168022, -0.03855309635400772, -0.03058875910937786, + 0.00425615394487977, -0.030160890892148018, 0.02802155539393425, + 0.0383354127407074, -0.011372262611985207, 0.027969012036919594, + -0.0023945558350533247, 0.012610825709998608, -0.004083505365997553, + 0.009570715948939323, -0.010929382406175137, 0.011529898270964622, + 0.02537928707897663, 0.013316432014107704, -0.01292609702795744, + 0.0003377899993211031, 0.012032830156385899, -0.011297198012471199, + -0.025033991783857346, 0.0018315722700208426, -0.008910148404538631, + -0.006342945154756308, 0.019621845334768295, 0.01180013082921505, + 0.03567812964320183, 0.004196102265268564, 0.008797552436590195, + 0.008519813418388367, -0.010914369486272335, 0.01219797134399414, + -0.01037390623241663, 0.0065831514075398445, 0.02144591137766838, + -0.045661699026823044, -0.0022969720885157585, -0.03618856891989708, + -0.029072457924485207, -0.01758759841322899, 0.010741720907390118, + 0.005885052029043436, 0.0196593776345253, 0.010156218893826008, + 0.026677902787923813, 0.02429835870862007, 0.034169334918260574, + -0.012858538888394833, -0.03154958412051201, -0.019306574016809464, + 0.009345523081719875, -0.058467693626880646, 0.06812848895788193, + 0.048529159277677536, 0.023134861141443253, -0.0144949434325099, + -0.024658668786287308, -0.015253094024956226, 0.02557445503771305, + -0.01504291407763958, 0.05085615813732147, -0.012265530414879322, + 0.02958289533853531, 0.030363567173480988, 0.010148712433874607, + -0.04953502491116524, 0.0010358892614021897, -0.018240658566355705, + -0.0057799615897238255, 0.019937116652727127, 0.04628473520278931, + -0.09602243453264236, -0.0007431379635818303, -0.013699259608983994, + 0.029042433947324753, -0.0016814435366541147, 0.03136942908167839, + 0.014419878832995892, -0.03038608655333519, 0.039776645600795746, + 0.0373820923268795, -0.0472155325114727, 0.043604932725429535, + -0.0056523522362113, 0.0011259665479883552, 0.0028824745677411556, + -0.0006980992620810866, 0.06918689608573914, -0.0010208763414993882, + 0.013166302815079689, -0.07102597504854202, -0.008249581791460514, + -0.015883635729551315, 0.007619041018188, 0.04771095886826515, + 0.07880265265703201, -0.03757726028561592, 0.006523100193589926, + -0.01739243045449257, -0.0012460697907954454, -0.013383990153670311, + 0.04241140931844711, -0.008865110576152802, 0.0324128232896328, + 0.023127354681491852, -0.058385126292705536, 0.008444749750196934, + -0.005592301022261381, -0.009713338688015938, 0.012881058268249035, + -0.007011018693447113, -0.00820454303175211, -0.008925162255764008, + 0.027075743302702904, 0.02810412459075451, 0.021903803572058678, + 0.019404157996177673, -0.009540691040456295, -0.05063847079873085, + 0.012866045348346233, 0.010906863026320934, -0.015906155109405518, + 0.016048777848482132, -0.026189984753727913, -0.0010358892614021897, + 0.004714047070592642, 0.019839532673358917, -0.04477594047784805, + 0.06558380275964737, -0.00998357031494379, 0.02143840305507183, + 0.03393663465976715, -0.007829220965504646, 0.014900291338562965, + -0.021986374631524086, -0.017752740532159805, -0.01670183800160885, + -0.031609635800123215, 0.03666897863149643, -0.026017336174845695, + 0.007341302931308746, 0.017880350351333618, -0.5495617985725403, + -0.010741720907390118, -0.027683766558766365, -0.014374840073287487, + 0.054324135184288025, 0.03813273459672928, 0.028156671673059464, + -0.016273971647024155, 0.012858538888394833, -0.007371328305453062, + 0.03422938287258148, -0.04352236166596413, 0.013203836046159267, + 0.014637566171586514, 0.0077091180719435215, 0.02092796564102173, + -0.0005930090555921197, -0.010366398841142654, -0.012798487208783627, + -0.040970172733068466, -0.038800809532403946, 0.021490950137376785, + 0.004714047070592642, 0.024628642946481705, 0.011927739717066288, + 0.06314420700073242, -0.013368976302444935, -0.02664787508547306, + -0.002281959168612957, 0.0007281251018866897, 0.0177001953125, + -0.03793756663799286, 0.058355093002319336, 0.025011472404003143, + 0.016641788184642792, -0.008737500756978989, 0.00806192122399807, + 0.02567203901708126, 0.018345749005675316, -0.004308698698878288, + -0.020237373188138008, 0.014284763485193253, 0.019899582490324974, + -0.025296716019511223, 0.013871908187866211, -0.015163017436861992, + -0.015478287823498249, -0.011650001630187035, -0.0025521910283714533, + -0.002664787694811821, 0.009916013106703758, 0.009383055381476879, + 0.0029575389344245195, 0.0007731637451797724, 0.05487960949540138, + -0.03497252240777016, 0.0012085374910384417, -0.025131573900580406, + 0.0034754835069179535, 0.019877063110470772, 0.002056765602901578, + -0.0093380156904459, -0.07027532905340195, 0.004045973066240549, + -0.03432697057723999, 0.015313146635890007, -0.04633728042244911, + 0.009345523081719875, -0.07399101555347443, -0.03498753532767296, + -0.002154349349439144, -0.01072670891880989, -0.024463500827550888, + -0.01210038736462593, -0.012460697442293167, -0.0177077017724514, + 0.0077916886657476425, -0.04800371080636978, 0.018630994483828545, + 0.00602016830816865, -0.004000934772193432, -0.028171684592962265, + -0.0006755799986422062, 0.0035730674862861633, -0.06694997847080231, + -0.004083505365997553, -0.01709968037903309, -0.01528311986476183, + -0.01997464708983898, 0.01439735945314169, -0.016446620225906372, + -0.029470300301909447, 0.016101323068141937, 0.0025822166353464127, + 0.003452964359894395, -0.01640908606350422, 0.004473840352147818, + 0.014254736714065075, -0.019726933911442757, 0.0068533835001289845, + -0.019509248435497284, 0.01494533009827137, 0.007836727425456047, + -0.03067133203148842, 0.020522618666291237, -0.0002927513269241899, + -0.019103901460766792, 0.011026966385543346, 0.011815142817795277, + 0.031331900507211685, 0.020605187863111496, -0.009593235328793526, + -0.0004503866657614708, -0.0006380477570928633, 0.0030476164538413286, + 0.02152848243713379, -0.012468203902244568, 0.0038207799661904573, + 0.0017640143632888794, 0.006733280140906572, 0.02990567311644554, + -0.03845551237463951, -0.006800837814807892, -0.013759312219917774, + -0.05141913890838623, -0.03612851724028587, 0.021798713132739067, + 0.011214627884328365, 0.017354898154735565, -0.00008257088484242558, + -0.03302835300564766, 0.043011926114559174, 0.00012010310456389561, + -0.02448602020740509, 0.03386157006025314, 0.013399002142250538, + 0.04048225283622742, 0.028764694929122925, -0.019546780735254288, + 0.03656388819217682, 0.02310483530163765, 0.010749228298664093, + 0.00043537377496249974, 0.012956122867763042, 0.012610825709998608, + -0.027608701959252357, 0.0018315722700208426, 0.008902642875909805, + 0.00983344204723835, -0.038305383175611496, -0.0017715208232402802, + -0.02928263694047928, -0.018360761925578117, 0.013691754080355167, + -0.0030250968411564827, -0.050210606306791306, 0.056485991925001144, + -0.02546936459839344, 0.01750502735376358, -0.0015463274903595448, + 0.06896920502185822, 0.011199614964425564, 0.0015313145704567432, + -0.019524261355400085, -0.01533566601574421, 0.05636589229106903, + 0.08704472333192825, -0.023119846358895302, -0.0022594397887587547, + 0.003993428312242031, 0.0024771266616880894, -0.016897005960345268, + -0.03360635042190552, -0.007191173732280731, -0.021956348791718483, + 0.0373445600271225, 0.02390802465379238, 0.010486502200365067, + -0.03097158670425415, -0.010809279978275299, -0.024088179692626, + -0.04976021870970726, 0.017152225598692894, -0.010741720907390118, + -0.004226128105074167, -0.004976772237569094, -0.08818570524454117, + 0.0011935245711356401, 0.039288729429244995, -0.051389116793870926, + -0.007123615127056837, 0.017377417534589767, -0.018218139186501503, + -0.0009533183765597641, -0.018345749005675316, -0.03401920571923256, + 0.013901934027671814, 0.03311843052506447, 0.0007131121819838881, + 0.015808571130037308, -0.01238563284277916, 0.01092187687754631, + 0.015418236143887043, 0.03311843052506447, -0.039093561470508575, + 0.006673228461295366, -0.0020342464558780193, 0.0029275130946189165, + -0.03431195393204689, 0.029192563146352768, -0.0120553495362401, + -0.06441279500722885, 0.016183892264962196, -0.03513015806674957, + -0.020124776288866997, -0.030558735132217407, -0.004383763298392296, + 0.01282851304858923, -0.01631150208413601, 0.008872617036104202, + -0.033185988664627075, 0.02941024675965309, 0.06830864399671555, + -0.031121715903282166, 0.012265530414879322, 0.013008668087422848, + -0.01798544079065323, 0.0006455541588366032, -0.012212984263896942, + -0.02881723828613758, -0.09747868776321411, 0.09067033976316452, + -0.13611434400081635, 0.0013511599972844124, -0.03847803175449371, + -0.00820454303175211, -0.009112823754549026, 0.03943134844303131, + -0.013016173616051674, 0.01494533009827137, 0.012340594083070755, + 0.01140979491174221, 0.01633402146399021, -0.022046426311135292, + 0.017362404614686966, -0.009127836674451828, -0.012535762041807175, + 0.0019141433294862509, -0.01858595572412014, -0.011019459925591946, + 0.037922557443380356, 0.0324353463947773, -0.006763305980712175, + 0.0619506798684597, -0.023855479434132576, 0.0074914307333528996, + 0.01439735945314169, 0.05958615615963936, 0.019839532673358917, + -0.00004503866512095556, -0.01229555532336235, 0.009593235328793526, + -0.013931960798799992, -0.010809279978275299, -0.02095799148082733, + -0.006402996834367514, -0.027360988780856133, -0.03969407454133034, + 0.039086051285266876, -0.002116817282512784, 0.04214868322014809, + -0.006950967013835907, -0.0016814435366541147, -0.061410218477249146, + 0.030904032289981842, -0.03679658845067024, -0.032773133367300034, + 0.017857830971479416, 0.0015988725936040282, 0.009525677189230919, + 0.007041044998914003, -0.01429977547377348, -0.022241592407226562, + -0.003385406220331788, 0.010201257653534412, 0.010846812278032303, + 0.028967367485165596, -0.02282709628343582, -0.004413789138197899, + -0.04995538666844368, -0.009323003701865673, 0.0046014501713216305, + -0.04801872372627258, -0.015425742603838444, -0.018661020323634148, + -0.021581025794148445, 0.01170254684984684, -0.011777611449360847, + 0.014337308704853058, -0.013263886794447899, 0.007123615127056837, + -0.026272553950548172, 0.01780528575181961, -0.034747328609228134, + -0.019103901460766792, 0.05255261808633804, 0.017264820635318756, + -0.0270532239228487, 0.024230802431702614, 0.02319491095840931, + -0.009855961427092552, -0.008024388924241066, -0.02516160160303116, + 0.008677449077367783, -0.0028224231209605932, 0.028449421748518944, + 0.00840721745043993, 0.08323144912719727, -0.009713338688015938, + -0.04470087215304375, -0.017940400168299675, 0.011529898270964622, + 0.006215335801243782, 0.016641788184642792, 0.0046915276907384396, + 0.008887629956007004, 0.02921508252620697, -0.008437243290245533, + -0.013113757595419884, 0.013301420025527477, 0.010148712433874607, + -0.004211115185171366, 0.009548196569085121, 0.004623969551175833, + 0.0022969720885157585, -0.00997606385499239, 0.017362404614686966, + -0.016138853505253792, -0.017947906628251076, -0.0021768687292933464, + 0.028246749192476273, 0.03518270328640938, -0.0038508058059960604, + 0.0022294139489531517, -0.10991685837507248 + ] + }, + "roadtrip": { + "prompt": "Photo of people on a road trip together", + "vector": [ + -0.004912007600069046, 0.0011731297709047794, 0.0019375563133507967, + 0.02452976442873478, 0.015061472542583942, -0.02907847985625267, + -0.0028987659607082605, 0.01586374267935753, -0.020556261762976646, + -0.016181621700525284, -0.03766882047057152, -0.02740582451224327, + -0.009059589356184006, 0.021070925518870354, -0.002868491690605879, + 0.5225347280502319, -0.007871323265135288, 0.010830637067556381, + 0.017354751005768776, -0.0008022694382816553, 0.032658420503139496, + -0.018527882173657417, -0.0190047025680542, 0.0072355614975094795, + 0.025120114907622337, 0.00825731921941042, -0.0001665087474975735, + 0.018641412258148193, 0.03140203654766083, 0.03611725941300392, + 0.0012261098017916083, 0.00574455177411437, -0.02322797104716301, + -0.0031031176913529634, 0.007129601668566465, -0.007651833817362785, + -0.030705727636814117, -0.002891197334975004, -0.010179739445447922, + -0.01948152296245098, 0.031053880229592323, 0.012442744337022305, + -0.017438005656003952, 0.01353261899203062, -0.043587446212768555, + -0.051943160593509674, -0.04392046853899956, -0.008302731439471245, + 0.011148517020046711, 0.04066597670316696, 0.0070009357295930386, + -0.009914839640259743, 0.012972544878721237, 0.01281360536813736, + -0.007311247289180756, -0.046047236770391464, -0.02214566245675087, + -0.0011201497400179505, -0.009771035984158516, -0.007818342186510563, + -0.0008249751408584416, 0.052904367446899414, -0.030183492228388786, + 0.04566124081611633, -0.08333005756139755, 0.01936042681336403, + -0.02107849344611168, 0.0004238404508214444, 0.03165179863572121, + 0.03735850751399994, -0.012041609734296799, 0.02590724639594555, + -0.020253518596291542, -0.005517494399100542, -0.050595950335264206, + 0.022032134234905243, -0.06305383890867233, 0.0010066210525110364, + 0.11184089630842209, 0.06904814392328262, -0.016052957624197006, + -0.011193929240107536, 0.02701982855796814, 0.01279089879244566, + 0.0417180098593235, 0.0484994575381279, -0.018777644261717796, + 0.0058126687072217464, -0.023439889773726463, 0.026868455111980438, + -0.014274340122938156, -0.013456934131681919, 0.02573316916823387, + -0.025786150246858597, 0.005313142668455839, 0.03297629952430725, + -0.055485256016254425, 0.04085519164800644, 0.015462607145309448, + 0.05586368218064308, -0.034747347235679626, -0.0027246885001659393, + -0.002020810730755329, -0.07925816625356674, -0.011322595179080963, + 0.03522416949272156, -0.06007181107997894, -0.009831584990024567, + -0.012041609734296799, 0.014047283679246902, 0.020450301468372345, + -0.013441797345876694, 0.05773312598466873, -0.02776155062019825, + -0.02318255975842476, -0.03757042810320854, 0.0023386909160763025, + -0.0450330451130867, -0.00011352869478287175, 0.007364227436482906, + 0.0449119508266449, -0.057589318603277206, -0.012828742153942585, + -0.02518066205084324, -0.025543956086039543, -0.04224780946969986, + 0.048923294991254807, -0.026096461340785027, 0.000628192035946995, + -0.0224711112678051, -0.017475849017500877, 0.03426295891404152, + 0.004927145317196846, 0.050686776638031006, 0.030804118141531944, + 0.004283815622329712, -0.024257296696305275, -0.02782209776341915, + -0.005721846129745245, -0.004450324922800064, 0.01995077356696129, + 0.07054673135280609, 0.03744933009147644, -0.0414758138358593, + -0.025665052235126495, -0.038879793137311935, -0.006849563680589199, + 0.00522231962531805, -0.047856125980615616, -0.04316360875964165, + -0.016022682189941406, 0.03517875447869301, 0.0066981930285692215, + 0.0018088903743773699, 0.042686786502599716, 0.029086049646139145, + -0.04600939154624939, 0.020359478890895844, 0.0003557232266757637, + -0.008075674064457417, -0.019640464335680008, -0.020972533151507378, + 0.0026868456043303013, 0.005434240214526653, 0.055190082639455795, + -0.032923322170972824, 0.017248792573809624, -0.5268185138702393, + 0.0002573317033238709, 0.03440675884485245, 0.021328255534172058, + 0.04710683599114418, -0.008976335637271404, 0.008590337820351124, + 0.0345429964363575, -0.020654652267694473, -0.020215675234794617, + 0.01556099858134985, 0.006645212881267071, 0.02641434222459793, + 0.019859952852129936, -0.0069706616923213005, 0.033248770982027054, + -0.023031188175082207, 0.07042562961578369, -0.010823068208992481, + 0.030138082802295685, 0.0007114464533515275, -0.010807931423187256, + -0.007462618872523308, 0.014092694967985153, -0.03826673701405525, + 0.0121475700289011, 0.03548149764537811, 0.014796572737395763, + -0.01946638524532318, 0.017218517139554024, -0.09220042824745178, + -0.012639527209103107, 0.010104053653776646, 0.02154017798602581, + 0.02644461579620838, -0.01799808256328106, -0.02150990255177021, + 0.0053434171713888645, -0.007099327631294727, -0.0258769728243351, + 0.01148153468966484, 0.026391636580228806, -0.006350038107484579, + -0.030713295564055443, -0.018558157607913017, 0.024552471935749054, + -0.017074715346097946, 0.005146633833646774, -0.012510862201452255, + 0.00226300535723567, -0.018671685829758644, 0.02938879281282425, + -0.002633865689858794, -0.0011428555008023977, -0.0028609230648726225, + -0.03088737092912197, 0.04949093982577324, 0.031000901013612747, + 0.004018915817141533, 0.005456945393234491, 0.0007568579749204218, + -0.016227034851908684, -0.04377666488289833, -0.024143768474459648, + -0.034611113369464874, -0.014766298234462738, -0.04628186300396919, + -0.001967830816283822, -0.0005600748700089753, -0.019973481073975563, + 0.027352845296263695, 0.007409639656543732, 0.009483429603278637, + -0.02866221033036709, 0.016022682189941406, -0.017839141190052032, + 0.0030804118141531944, -0.056567560881376266, 0.05664324760437012, + -0.02036704681813717, 0.04857514426112175, -0.03882681205868721, + 0.01077765692025423, -0.042936552315950394, -0.042611103504896164, + -0.03981829434633255, 0.06430265307426453, 0.001150424126535654, + 0.002346259541809559, -0.04794694855809212, -0.06702733784914017, + 0.013419090770184994, -0.00125638407189399, 0.008582768961787224, + -0.03512577712535858, -0.008151359856128693, 0.010368953458964825, + 0.03081168793141842, -0.005948903504759073, 0.01629515178501606, + -0.007091759238392115, 0.014122968539595604, -0.03186371922492981, + -0.03385425731539726, 0.01768776960670948, -0.03588263317942619, + -0.06287219375371933, -0.011875101365149021, -0.015333942137658596, + -0.00028003743500448763, -0.018020788207650185, 0.020806023851037025, + -0.008787120692431927, 0.009876996278762817, 0.014804141595959663, + 0.05313899368047714, -0.04797722399234772, -0.002611159812659025, + -0.0019753992091864347, -0.0010293268132954836, 0.006675486918538809, + -0.025279054418206215, -0.011965923942625523, -0.015470176003873348, + -0.028942245990037918, -0.029517458751797676, 0.007878891192376614, + 0.02733014151453972, 0.01634056307375431, -0.018020788207650185, + -0.10486266762018204, 0.01142855454236269, 0.02041245810687542, + -0.009990525431931019, -0.029070913791656494, 0.02555152401328087, + 0.018807919695973396, 0.020344341173768044, -0.02085900492966175, + 0.004647107794880867, -0.005517494399100542, 0.03657137602567673, + 0.0006963092600926757, 0.03387695923447609, 0.007379364687949419, + -0.01353261899203062, -0.02150990255177021, -0.01656005159020424, + 0.041203346103429794, -0.04540390521287918, 0.020927121862769127, + -0.04470759630203247, -0.05180692300200462, 0.022758718580007553, + 0.06918437778949738, -0.0031182547099888325, -0.006864701863378286, + 0.010800362564623356, -0.020314067602157593, -0.03897061571478844, + 0.0869629755616188, 0.002891197334975004, -0.0037615839391946793, + 0.02598293125629425, -0.013956460170447826, 0.00941531267017126, + 0.051958296447992325, -0.0029063343536108732, 0.0208741407841444, + 0.02943420596420765, -0.034656524658203125, -0.03898575156927109, + 0.006569527089595795, -0.021305551752448082, 0.014440849423408508, + 0.07355902343988419, -0.0035875067114830017, 0.010626285336911678, + -0.03583722561597824, -0.01316175889223814, -0.01621946506202221, + -0.0011958355316892266, 0.008620611391961575, -0.02355341799557209, + 0.01207945216447115, 0.040537308901548386, -0.07472458481788635, + -0.02253166027367115, 0.011171223595738411, -0.048226986080408096, + -0.020450301468372345, 0.002346259541809559, 0.018921447917819023, + -0.0008855237392708659, -0.030539218336343765, -0.012162706814706326, + -0.011125811375677586, 0.007326385006308556, 0.006804152857512236, + 0.026331087574362755, 0.009899700991809368, 0.007553441915661097, + -0.011958355084061623, 0.008310300298035145, 0.004889302421361208, + -0.03930363059043884, 0.02355341799557209, 0.019738854840397835, + -0.022493818774819374, 0.02474168688058853, -0.013267719186842442, + -0.08479836583137512, 0.030357571318745613, -0.01624973863363266, + -0.03583722561597824, 0.008045399561524391, -0.025309329852461815, + 0.034997113049030304, -0.0174228698015213, 0.009286646731197834, + -0.027042534202337265, 0.02798103727400303, -0.043292272835969925, + -0.05818723887205124, 0.007606422062963247, -0.019352857023477554, + 0.021971585229039192, -0.005926197394728661, 0.02698955498635769, + -0.017074715346097946, -0.008151359856128693, 0.07210585474967957, + -0.08330735564231873, 0.03763097524642944, 0.008211908861994743, + -0.029948867857456207, -0.0052980054169893265, 0.023931847885251045, + -0.001922419061884284, 0.03935661166906357, 0.09029315412044525, + -0.03508793190121651, 0.015053904615342617, -0.02816268429160118, + 0.004964988213032484, -0.0021192021667957306, 0.002020810730755329, + 0.02364424243569374, 0.04837078973650932, -0.005161771085113287, + 0.01001323014497757, -0.010959303006529808, 0.02909361943602562, + 0.10385604947805405, -0.07229506969451904, -0.026853321120142937, + 0.0004314090183470398, 0.007167445030063391, -0.046864643692970276, + -0.037547722458839417, 0.0433603897690773, 0.0007492893491871655, + -0.008590337820351124, -0.0056158858351409435, 0.000983915408141911, + 0.01420622318983078, 0.02777668461203575, -0.03675302118062973, + 0.007318815682083368, -0.005721846129745245, 0.022387858480215073, + -0.028185389935970306, 0.00484389066696167, -0.020480575039982796, + 0.011875101365149021, -0.0156291164457798, 0.011345300823450089, + 0.01621946506202221, 0.004480598960071802, 0.013752108439803123, + 0.0014683044282719493, -0.005494788289070129, -0.021116336807608604, + -0.071303591132164, 0.0020662222523242235, -0.02361396700143814, + -0.008726571686565876, 0.0031560976058244705, -0.0024900624994188547, + 0.0008855237392708659, 0.03772936761379242, 0.009089863859117031, + 0.0033982922323048115, 0.04201318323612213, -0.02085900492966175, + 0.002278142375871539, 0.0008552494109608233, 0.035383109003305435, + 0.03050137497484684, 0.020049167796969414, 0.011262046173214912, + -0.03053164854645729, -0.055840980261564255, 0.016317857429385185, + 0.01570480316877365, 0.04692519083619118, 0.024340549483895302, + -0.00008325437374878675, 0.003148528980091214, -0.00414001289755106, + 0.019246896728873253, -0.003254489041864872, -0.004026484210044146, + 0.0005222319741733372, 0.006191097665578127, 0.047871265560388565, + -0.021146610379219055, 0.013055799528956413, -0.02017783187329769, + -0.07355145364999771, -0.019935637712478638, 0.00869629718363285, + -0.0010520325740799308, 0.015886448323726654, 0.04725820943713188, + 0.03541338071227074, 0.005631023086607456, 0.004034052602946758, + -0.010361384600400925, 0.007515599485486746, -0.015825899317860603, + 0.009082295000553131, -0.005911060608923435, -0.009967818856239319, + 0.01814945414662361, -0.010247856378555298, -0.021562881767749786, + -0.0366092175245285, 0.004238404333591461, 0.04177099093794823, + 0.0060851373709738255, 0.04944552853703499, -0.007666971068829298, + 0.022804129868745804, -0.055992353707551956 + ] + } + }, + "clip_memory_types": { + "sunrise": { + "prompt": "Photo of an absolutely stunning sunrise or sunset", + "vector": [ + 0.015730585902929306, 0.01055071223527193, 0.01939469203352928, + -0.019281307235360146, 0.012806463055312634, 0.006988056469708681, + -0.018081819638609886, 0.04163592681288719, 0.0742190033197403, + 0.01209631934762001, -0.002148335101082921, -0.03029748983681202, + -0.03369305282831192, -0.011308596469461918, -0.01473399717360735, + 0.5191869139671326, -0.023709263652563095, -0.020820945501327515, + 0.014399810694158077, -0.007214825134724379, -0.005866148043423891, + -0.00647484278306365, -0.003377659944817424, 0.02588743530213833, + 0.011099730618298054, -0.04701869562268257, 0.031532783061265945, + 0.013230162672698498, 0.009476544335484505, -0.030768930912017822, + -0.000035805580409942195, -0.012388731352984905, -0.01604686863720417, + 0.015098019503057003, -0.03659927472472191, 0.034164492040872574, + 0.004624887835234404, 0.0031270210165530443, -0.04144496098160744, + 0.025583088397979736, 0.041230130940675735, -0.016422826796770096, + 0.004636823199689388, -0.023184115067124367, -0.006450972519814968, + -0.03528043255209923, 0.0026615483220666647, 0.041188355535268784, + -0.007155148778110743, 0.019484205171465874, 0.006892574951052666, + 0.042525097727775574, -0.047812387347221375, 0.034898508340120316, + -0.014883186668157578, 0.01724635623395443, 0.04145689681172371, + 0.0011517462553456426, -0.014799641445279121, 0.013128713704645634, + 0.018666643649339676, -0.0030434743966907263, 0.005376805085688829, + 0.014960765838623047, 0.007292403373867273, 0.028608661144971848, + 0.04778851941227913, -0.008903655223548412, 0.04611162468791008, + 0.023643620312213898, 0.013701601885259151, -0.024174736812710762, + -0.04187462851405144, -0.025075843557715416, 0.019740810617804527, + 0.030040884390473366, 0.0019454365829005837, 0.04633242264389992, + 0.0022915571462363005, -0.012394699268043041, -0.012448407709598541, + -0.025970982387661934, 0.031562622636556625, -0.010216525755822659, + -0.02288573607802391, -0.045198578387498856, 0.015903646126389503, + -0.022080110386013985, 0.010950540192425251, 0.023184115067124367, + 0.030351197347044945, 0.011887453496456146, -0.02117900364100933, + 0.02244413271546364, -0.002118496922776103, -0.027277885004878044, + -0.03205793350934982, -0.00811593234539032, 0.03711845353245735, + 0.006695643533021212, -0.013528542593121529, -0.009279613383114338, + -0.019621457904577255, 0.02236655354499817, 0.05651910975575447, + -0.002482520416378975, -0.047746747732162476, 0.03063167631626129, + -0.014268524944782257, -0.03448674455285072, 0.022312846034765244, + -0.012018740177154541, 0.04589679092168808, -0.01978258416056633, + -0.04169560223817825, -0.03168197348713875, -0.001808181987144053, + -0.017144907265901566, -0.02877575345337391, -0.011195212602615356, + 0.01444755308330059, -0.045335836708545685, -0.002822673413902521, + 0.012991459108889103, 0.029909595847129822, 0.01564704068005085, + 0.03973226249217987, 0.010467165149748325, 0.01981242187321186, + 0.010109109804034233, 0.014184977859258652, -0.0038550677709281445, + -0.02150125242769718, 0.027779165655374527, -0.06068449467420578, + -0.041063033044338226, -0.05965806543827057, 0.01097441092133522, + 0.006158560514450073, -0.02341088280081749, 0.0131645193323493, + 0.016536211594939232, -0.005472286604344845, -0.05319515988230705, + 0.05447222664952278, -0.05522414296865463, -0.015760423615574837, + -0.0232557263225317, -0.03621137887239456, 0.0044100540690124035, + -0.0027212242130190134, -0.02476552687585354, 0.024353764951229095, + -0.03384821116924286, -0.02725401520729065, -0.04609968513250351, + 0.009279613383114338, -0.07156343013048172, -0.034164492040872574, + -0.008682853542268276, 0.006588227115571499, -0.0029181549325585365, + 0.057515699416399, 0.00784739013761282, -0.022121882066130638, + -0.07184987515211105, 0.004069901071488857, -0.5235790610313416, + 0.027546430006623268, 0.005042619537562132, -0.02073740027844906, + 0.04766916483640671, -0.019621457904577255, -0.046302586793899536, + -0.024711819365620613, 0.018756156787276268, -0.0039863549172878265, + -0.023005086928606033, -0.015849938616156578, -0.012663241475820541, + 0.02015257440507412, 0.015885744243860245, 0.050837960094213486, + 0.04939976707100868, 0.010592484846711159, -0.004505535587668419, + -0.0480152890086174, -0.029109938070178032, 0.016178155317902565, + 0.03152681514620781, -0.004929235205054283, -0.015277048572897911, + -0.0026973539497703314, -0.07340145111083984, -0.004702466540038586, + -0.021184969693422318, -0.005663249641656876, -0.0696597620844841, + -0.020940298214554787, -0.028447536751627922, 0.058261651545763016, + -0.06172285974025726, 0.03253534063696861, 0.02317814715206623, + 0.016792817041277885, 0.025672603398561478, 0.0007161116227507591, + 0.013242097571492195, -0.027337562292814255, 0.0019693071953952312, + 0.013838857412338257, 0.02570243924856186, -0.013677733018994331, + 0.01089086476713419, 0.004135544877499342, -0.021877212449908257, + -0.02387038804590702, -0.0032583081629127264, 0.02525487169623375, + -0.020098866894841194, -0.03658733889460564, 0.007525139953941107, + 0.02305879443883896, -0.03377063199877739, 0.005030684173107147, + -0.034039173275232315, -0.002482520416378975, 0.004923267289996147, + 0.056495241820812225, -0.016333313658833504, 0.00896929856389761, + 0.0319385789334774, -0.030315391719341278, -0.02822076715528965, + -0.02798803150653839, 0.0642889216542244, 0.017359739169478416, + 0.030237814411520958, 0.04572969675064087, 0.015551558695733547, + 0.0075370753183960915, 0.015581395477056503, -0.04787803068757057, + 0.024819236248731613, -0.07242872565984726, 0.017019586637616158, + -0.05419175326824188, -0.028584789484739304, -0.0478004552423954, + -0.025833727791905403, 0.027904484421014786, -0.02554728277027607, + -0.0031270210165530443, 0.06221219524741173, -0.03958307206630707, + 0.02147141471505165, 0.012155994772911072, -0.026895960792899132, + -0.012388731352984905, 0.009267677552998066, 0.0232557263225317, + 0.01873825490474701, -0.024150865152478218, -0.012490181252360344, + 0.013128713704645634, 0.04901784285902977, -0.008772367611527443, + 0.03884309157729149, -0.0010204591089859605, 0.04531793296337128, + 0.02846544049680233, -0.02359587885439396, 0.00020289829990360886, + -0.010616355575621128, 0.004069901071488857, 0.060063865035772324, + 0.011982935480773449, -0.014059659093618393, -0.008676886558532715, + -0.01345096342265606, -0.01842794008553028, -0.023607812821865082, + 0.027993997558951378, -0.0009488479699939489, -0.006242106202989817, + 0.017532799392938614, -0.03568026423454285, 0.052837107330560684, + 0.0027391270268708467, 0.012931782752275467, -0.028196895495057106, + -0.0020707561634480953, 0.013940306380391121, 0.023106535896658897, + 0.007017894182354212, 0.04989507794380188, -0.032821785658597946, + 0.08213800936937332, 0.002106561791151762, 0.0013606121065095067, + -0.033329032361507416, -0.014757867902517319, -0.026525970548391342, + -0.015945419669151306, -0.019203728064894676, 0.020928362384438515, + 0.022927507758140564, 0.035328175872564316, -0.04256686940789223, + 0.009589928202331066, 0.1263817697763443, -0.014907057397067547, + 0.007602719124406576, 0.00035805581137537956, 0.022491874173283577, + 0.03591896593570709, -0.03532220795750618, 0.015599298290908337, + -0.07282259315252304, 0.00899913627654314, 0.015396400354802608, + -0.03627702593803406, 0.010562647134065628, 0.026794511824846268, + -0.04384990409016609, 0.015306886285543442, 0.020647887140512466, + 0.07979274541139603, -0.015378497540950775, 0.06526760756969452, + -0.015736553817987442, -0.007471431512385607, -0.04404086619615555, + 0.06075610592961311, -0.021214807406067848, 0.04585501551628113, + -0.017174743115901947, -0.0015873807715252042, 0.056328147649765015, + 0.017234420403838158, -0.017634248360991478, 0.022098012268543243, + 0.027248049154877663, -0.027242079377174377, 0.005257453303784132, + 0.008921558037400246, 0.022038336843252182, 0.013176454231142998, + 0.004380216356366873, 0.03504173085093498, -0.016643628478050232, + 0.0297246016561985, 0.011791972443461418, -0.001861890428699553, + 0.016166219487786293, -0.03747054189443588, 0.057521671056747437, + -0.0038431326393038034, -0.013594185933470726, 0.02111932635307312, + -0.0290801003575325, 0.011768100783228874, 0.01912018097937107, + -0.01493092905730009, 0.014662385918200016, 0.039523396641016006, + 0.02468794956803322, 0.0033120163716375828, 0.01601702906191349, + -0.010031530633568764, -0.015020442195236683, 0.013051135465502739, + -0.01444755308330059, -0.023816680535674095, 0.02502213604748249, + 0.012454375624656677, 0.025863565504550934, -0.006773222703486681, + -0.06847818195819855, -0.03054216131567955, 0.001545607578009367, + -0.03384821116924286, -0.0265438724309206, -0.003395562758669257, + 0.0016351215308532119, 0.018421972170472145, 0.005895986221730709, + -0.03442706912755966, -0.003222502302378416, 0.014190945774316788, + 0.04217897355556488, -0.05589848384261131, 0.07648072391748428, + -0.03790617734193802, -0.018111657351255417, -0.03603235259652138, + 0.004421989433467388, -0.0255771204829216, 0.02341088280081749, + -0.028089478611946106, 0.002977831056341529, -0.006874672137200832, + -0.021835438907146454, -0.02285589650273323, -0.007948839105665684, + 0.024371666833758354, -0.05104682594537735, 0.004875526763498783, + -0.033102262765169144, -0.012639370746910572, -0.07154552638530731, + -0.025404062122106552, 0.02471778728067875, -0.035035762935876846, + 0.02645435929298401, 0.023452656343579292, 0.04376635700464249, + 0.010747642256319523, -0.021053681150078773, -0.01824294403195381, + 0.031085213646292686, -0.03658733889460564, -0.006558389402925968, + 0.012239541858434677, 0.030560065060853958, -0.00466069346293807, + -0.03072715923190117, 0.019818389788269997, 0.0128422686830163, + 0.009774924255907536, -0.0230408925563097, 0.04861801490187645, + -0.03312016278505325, -0.008855913765728474, -0.071014404296875, + 0.019925806671380997, 0.008121899329125881, 0.017240388318896294, + -0.016118479892611504, -0.02033757045865059, -0.008867849595844746, + -0.024276185780763626, -0.038496967405080795, -0.0337885357439518, + 0.03429577872157097, -0.02477746270596981, 0.015945419669151306, + 0.07845599949359894, 0.0008473987691104412, -0.034838832914829254, + 0.012925814837217331, 0.031610362231731415, 0.010437327437102795, + 0.02356604114174843, 0.007173051591962576, -0.02039724588394165, + 0.029366547241806984, 0.07196921855211258, 0.03641427680850029, + 0.010377652011811733, -0.05049780756235123, -0.017210550606250763, + -0.018732286989688873, 0.03511931002140045, 0.008265122771263123, + 0.016804754734039307, -0.05049780756235123, 0.012018740177154541, + -0.009028973989188671, 0.0064629074186086655, -0.02353620156645775, + 0.02248590625822544, 0.01842794008553028, -0.00395054928958416, + 0.0018260846845805645, 0.03070925548672676, -0.0533025786280632, + 0.019424527883529663, 0.0476512610912323, -0.0015635105082765222, + -0.00694031547755003, -0.02583969570696354, 0.010789415799081326, + 0.014358039014041424, 0.02073740027844906, -0.043706681579351425, + -0.1288284808397293, 0.023858454078435898, -0.023721197620034218, + 0.007256597746163607, -0.030583932995796204, 0.011236985214054585, + -0.03825826570391655, -0.00011338433978380635, 0.02393006533384323, + 0.016649596393108368, -0.006325652822852135, 0.0010741675505414605, + -0.02848334237933159, -0.05317725986242294, -0.005633411929011345, + -0.0004117641947232187, -0.016655564308166504, -0.0066240327432751656, + -0.018947120755910873, -0.032887428998947144, -0.009339289739727974, + -0.00281073828227818, -0.0724346935749054, 0.006809028331190348, + 0.028817525133490562, 0.011236985214054585 + ] + }, + "mountains": { + "prompt": "Photo of a beautiful mountain range", + "vector": [ + 0.028191275894641876, 0.017811166122555733, 0.016408821567893028, + -0.01227052602916956, -0.007121717091649771, 0.0009211487486027181, + 0.01934412308037281, 0.01587950438261032, 0.060747694224119186, + 0.03486616536974907, -0.007527296897023916, -0.034934911876916885, + 0.016594424843788147, -0.015742018818855286, 0.016945011913776398, + 0.5609795451164246, -0.02234816737473011, 0.01763930916786194, + 0.022746874019503593, 0.007767895702272654, -0.02878246083855629, + -0.020210279151797295, -0.026953911408782005, 0.0035608585458248854, + 0.009871414862573147, -0.014834619127213955, 0.007898506708443165, + 0.005911849904805422, -0.019674086943268776, 0.06762880831956863, + 0.012765471823513508, 0.006894866470247507, -0.030885977670550346, + 0.023221196606755257, -0.004729480016976595, 0.04720543324947357, + -0.0018697944469749928, -0.016367575153708458, -0.026534583419561386, + -0.014552775770425797, 0.05517268180847168, 0.012256776914000511, + -0.013940966688096523, -0.0313534289598465, -0.017467455938458443, + -0.023915493860840797, -0.009713307023048401, -0.015418929047882557, + 0.03967126086354256, -0.010758192278444767, 0.015714522451162338, + 0.019323501735925674, -0.03575294464826584, 0.01787990890443325, + -0.010187629610300064, -0.022086946293711662, -0.01875981315970421, + 0.014277804642915726, -0.012524873949587345, 0.05602509155869484, + -0.006626770831644535, 0.01602386310696602, 0.005052569787949324, + -0.004777600057423115, 0.011748083867132664, 0.038976967334747314, + -0.006778004579246044, -0.011775580234825611, 0.03699030727148056, + 0.0200384221971035, 0.02092519775032997, 0.05463649332523346, + -0.029153671115636826, -0.008297212421894073, 0.006503034848719835, + 0.018780436366796494, -0.024458561092615128, -0.0017666807398200035, + 0.05913224816322327, 0.04763163626194, 0.014085326343774796, + -0.019818445667624474, 0.04759039357304573, 0.006757382303476334, + -0.0011342503130435944, -0.04072989523410797, 0.006331178825348616, + -0.007616662420332432, -0.027950676158070564, -0.00705984840169549, + 0.04116297513246536, 0.040283069014549255, -0.0158382598310709, + -0.00376708572730422, -0.01117752119898796, 0.02269187942147255, + -0.009974528104066849, 0.03630288317799568, 0.002378488425165415, + 0.009369594044983387, -0.022526897490024567, -0.009837043471634388, + 0.03181400150060654, -0.03449495509266853, 0.03248080238699913, + -0.001739183790050447, -0.07973435521125793, 0.02408735267817974, + 0.028761839494109154, -0.0025847158394753933, 0.002447230974212289, + -0.011748083867132664, 0.011225640773773193, -0.016511933878064156, + 0.006723010912537575, -0.007499800529330969, 0.009665187448263168, + 0.010256372392177582, -0.022224431857466698, 0.01949535682797432, + -0.012545496225357056, -0.03436434641480446, 0.02019652910530567, + 0.012353016994893551, 0.00017185609613079578, 0.006846747361123562, + 0.08900771290063858, -0.05629318580031395, 0.03059038706123829, + 0.015460175462067127, 0.016078857704997063, -0.007726650685071945, + -0.021275784820318222, 0.0033958766143769026, -0.0042620315216481686, + -0.015858881175518036, -0.035587962716817856, 0.010373233817517757, + -0.038317035883665085, -0.005774364806711674, 0.011074407026171684, + -0.005361910443753004, 0.03155965358018875, -0.030576638877391815, + 0.008214721456170082, -0.061325132846832275, 0.016003241762518883, + 0.007149213925004005, -0.03575294464826584, -0.006083706393837929, + -0.03567732870578766, 0.004674485884606838, 0.031195318326354027, + 0.002089770045131445, -0.0007630411419086158, -0.024018609896302223, + 0.0002337243058718741, 0.020258398726582527, -0.014724631793797016, + -0.024836644530296326, 0.0029971706680953503, 0.013466645032167435, + 0.029263656586408615, -0.02874808944761753, -0.046380527317523956, + -0.0888427272439003, -0.01563890650868416, -0.5659977793693542, + 0.007719775661826134, -0.0003299637173768133, -0.021715737879276276, + 0.07681968063116074, -0.004983827006071806, -0.008393452502787113, + -0.0009898912394419312, -0.013535386882722378, -0.009802672080695629, + -0.002502224873751402, -0.003457744838669896, -0.034274980425834656, + 0.003457744838669896, 0.008757786825299263, -0.027648210525512695, + 0.015006475150585175, -0.018711691722273827, 0.01304044108837843, + -0.021667618304491043, -0.003925193566828966, -0.0010723820887506008, + 0.021970083937048912, -0.026108378544449806, -0.014133445918560028, + -0.02007966674864292, -0.08935829997062683, -0.006619896739721298, + -0.03663284704089165, 0.005801862105727196, -0.04136919975280762, + -0.0002337243058718741, 0.03229519724845886, 0.016120102256536484, + -0.01900041103363037, 0.040063098073005676, 0.00628993334248662, + 0.01516458299010992, 0.017556820064783096, 0.015006475150585175, + 0.01216053869575262, 0.006152448244392872, 0.02019652910530567, + 0.011218766681849957, -0.003004044760018587, 0.027263253927230835, + -0.015515169128775597, -0.011363125406205654, 0.0010036396561190486, + 0.016285084187984467, -0.020141534507274628, 0.008297212421894073, + -0.0020760218612849712, 0.03229519724845886, -0.001251112436875701, + 0.01255924440920353, 0.02153700776398182, -0.007980997674167156, + -0.004798222333192825, 0.04441449046134949, -0.012380514293909073, + -0.04238659143447876, -0.12834900617599487, -0.01211929228156805, + 0.022018203511834145, 0.0024884764570742846, -0.04711607098579407, + -0.022169437259435654, -0.04123859107494354, -0.026768306270241737, + -0.02793005295097828, 0.022685006260871887, 0.001230489695444703, + 0.018966039642691612, 0.006475538015365601, -0.04930895194411278, + -0.01402345858514309, -0.016890017315745354, 0.04635303094983101, + -0.02617024816572666, -0.020175905898213387, -0.09306351840496063, + 0.025744043290615082, 0.0014779624762013555, -0.013769110664725304, + -0.031119702383875847, 0.0618063323199749, -0.02091832458972931, + -0.019447235390543938, 0.004681359976530075, -0.037313397973775864, + 0.019055403769016266, 0.009005260653793812, 0.011493736878037453, + 0.0022135067265480757, 0.00628993334248662, 0.029682986438274384, + -0.009142744354903698, 0.0007767896167933941, -0.0029078051447868347, + 0.03637849912047386, 0.005781239364296198, -0.017591191455721855, + 0.005788113921880722, 0.03366317227482796, -0.008194099180400372, + 0.014112822711467743, 0.012263651937246323, 0.07155400514602661, + 0.014820870012044907, 0.015212701633572578, 0.023399926722049713, + -0.039863742887973785, -0.007905380800366402, -0.011919938959181309, + -0.00044682587031275034, -0.008104734122753143, 0.01003639679402113, + -0.00793975219130516, 0.014188440516591072, 0.017268100753426552, + 0.029717355966567993, -0.007334818597882986, -0.018010521307587624, + -0.0061180773191154, -0.00934209767729044, -0.01255924440920353, + -0.014167816378176212, 0.005836233496665955, 0.010778814554214478, + 0.024025484919548035, 0.022300047799944878, -0.0062211910262703896, + 0.004825719632208347, 0.013452896848320961, 0.006468663923442364, + 0.006908615585416555, -0.0013336034025996923, -0.006303681991994381, + -0.001196118537336588, -0.008785284124314785, 0.0030796611681580544, + -0.009314601309597492, 0.12067047506570816, 0.002900931052863598, + 0.005478772800415754, 0.000006874244263599394, 0.03638537600636482, + 0.03177963197231293, -0.05441651493310928, 0.019323501735925674, + -0.05815610662102699, 0.01652568206191063, -0.01169308926910162, + 0.0001099879082175903, -0.005547515116631985, 0.03231582045555115, + -0.048649027943611145, 0.013315411284565926, -0.04281279072165489, + 0.02011403813958168, 0.009280229918658733, 0.006186820100992918, + -0.012724226340651512, 0.021688241511583328, -0.01447028387337923, + 0.03657785430550575, -0.02612212672829628, 0.03006106987595558, + -0.011143149808049202, -0.007589165586978197, 0.008352206088602543, + 0.0020004052203148603, 0.01792115531861782, -0.0023234945256263018, + 0.010118887759745121, -0.005774364806711674, 0.0017666807398200035, + 0.00853781122714281, 0.007974122650921345, 0.0260465107858181, + -0.025668427348136902, -0.00188354286365211, -0.0027565720956772566, + -0.02824626863002777, 0.0030590386595577, -0.02823939360678196, + 0.017164988443255424, -0.008874649181962013, 0.011129401624202728, + 0.049879513680934906, 0.023399926722049713, 0.018347356468439102, + 0.03853701055049896, -0.011012539267539978, -0.01439466793090105, + -0.020712098106741905, -0.025070369243621826, 0.015975743532180786, + 0.02597089484333992, -0.008393452502787113, -0.0273594930768013, + 0.013665997423231602, -0.008056613616645336, 0.0016566927079111338, + -0.01768055558204651, -0.01298544742166996, 0.03688719496130943, + -0.032879509031772614, 0.02878246083855629, 0.023255567997694016, + -0.11043473333120346, 0.015996364876627922, -0.030157307162880898, + 0.015267697162926197, 0.0006599274347536266, -0.00775414751842618, + 0.007486051879823208, -0.010187629610300064, -0.016429442912340164, + -0.022671258077025414, 0.0036433495115488768, 0.022588767111301422, + -0.08365267515182495, 0.016553180292248726, -0.004014558624476194, + 0.0021310157608240843, 0.010118887759745121, -0.014710881747305393, + -0.010703197680413723, -0.008702793158590794, -0.0023166202008724213, + 0.006049334537237883, 0.007369189988821745, 0.004853216465562582, + 0.01109503023326397, 0.004097049590200186, 0.03899071365594864, + 0.015398306772112846, -0.05756492167711258, 0.024266080930829048, + -0.014676511287689209, 0.005547515116631985, -0.03958189859986305, + -0.07155400514602661, -0.00853781122714281, -0.013975338079035282, + 0.011961184442043304, -0.006503034848719835, 0.017206232994794846, + -0.010297617875039577, 0.01186494529247284, 0.02049212157726288, + 0.03522362560033798, 0.0033408827148377895, -0.07305946946144104, + 0.016346951946616173, -0.014772751368582249, -0.022685006260871887, + -0.010029522702097893, 0.014140320010483265, -0.028528112918138504, + 0.04158230498433113, -0.0033958766143769026, 0.021055810153484344, + -0.01737808994948864, 0.024568548426032066, -0.016250712797045708, + 0.0015192078426480293, -0.03339507803320885, 0.026672067120671272, + -0.02617024816572666, -0.008585930801928043, 0.0057124970480799675, + 0.023839877918362617, 0.020835833624005318, 0.02408047765493393, + 0.05790863186120987, 0.01695876009762287, 0.04110110551118851, + -0.03364942595362663, 0.0041520437225699425, -0.008895271457731724, + -0.046909842640161514, -0.025902152061462402, -0.0009142744820564985, + -0.03301699459552765, 0.0013817230938002467, -0.018979787826538086, + 0.018773561343550682, 0.06643269956111908, 0.02223818004131317, + -0.011088155210018158, 0.005485646892338991, -0.009135870262980461, + 0.0015879502752795815, 0.018670447170734406, 0.03186212107539177, + 0.07482614368200302, 0.016044486314058304, 0.031380925327539444, + 0.019275380298495293, 0.003258391749113798, -0.019426614046096802, + 0.04843592271208763, 0.000653053168207407, 0.002935302210971713, + -0.02835625782608986, 0.022018203511834145, -0.012284274213016033, + 0.0032927629072219133, 0.005623131524771452, 0.01645006611943245, + -0.01730247400701046, -0.015027097426354885, 0.009727055206894875, + 0.04178165644407272, -0.003045290242880583, 0.0011686214711517096, + -0.060933299362659454, -0.013129807077348232, 0.01575576700270176, + -0.009424588643014431, -0.02685767039656639, 0.003175900550559163, + -0.0057606166228652, -0.0051900544203817844, -0.01298544742166996, + -0.004502630326896906, -0.05713871866464615, -0.0021378900855779648, + -0.00451637851074338, 0.0018560459138825536, -0.007444805931299925, + -0.011954310350120068, 0.001340477610938251, -0.020251523703336716, + -0.006063083186745644, -0.02438981831073761, 0.010318240150809288, + -0.02720825746655464, 0.0004399516328703612, -0.014387793838977814, + 0.01875981315970421, -0.018704818561673164 + ] + }, + "greenery": { + "prompt": "Photo of lush greenery", + "vector": [ + -0.0024188209790736437, 0.01026985514909029, -0.003033661050722003, + -0.026080025359988213, -0.0058240885846316814, 0.01385079137980938, + -0.00429712375625968, 0.006621353793889284, -0.027606990188360214, + -0.01551288552582264, 0.02874883823096752, -0.009891491383314133, + 0.050639841705560684, 0.016256099566817284, 0.01855330355465412, + 0.5217559337615967, -0.01582368277013302, 0.004567382857203484, + -0.0029998787213116884, -0.03520803526043892, -0.025161145254969597, + -0.03884977847337723, -0.025248978286981583, -0.023296354338526726, + -0.011242789216339588, -0.006371363997459412, 0.015810171142220497, + -0.049180444329977036, -0.019958652555942535, 0.043504998087882996, + -0.018661407753825188, -0.01766820438206196, 0.03996460139751434, + 0.00032431120052933693, 0.03405267745256424, 0.017087146639823914, + 0.01268867589533329, 0.01987757533788681, -0.032836511731147766, + 0.015175062231719494, 0.012208965606987476, -0.013506210409104824, + 0.013877816498279572, -0.0033444594591856003, 0.06924044340848923, + -0.012499494478106499, 0.024073351174592972, -0.01617502234876156, + 0.016803374513983727, 0.04507249966263771, -0.009715823456645012, + 0.030127162113785744, -0.013742687180638313, -0.036971479654312134, + -0.04819399490952492, -0.0032431120052933693, -0.012877857312560081, + -0.0001891815336421132, -0.021620746701955795, 0.01589125022292137, + -0.02408010885119438, -0.022796375676989555, -0.002952583134174347, + -0.0651325061917305, 0.04060646519064903, 0.027938058599829674, + -0.026154346764087677, -0.009945543482899666, 0.001378322602249682, + 0.06817292422056198, 0.0241882111877203, -0.009175305254757404, + -0.0001486426335759461, 0.014904802665114403, 0.022316664457321167, + -0.01551964320242405, -0.11554938554763794, 0.008310474455356598, + -0.06501764059066772, -0.010364445857703686, -0.03290407359600067, + 0.000945907726418227, 0.044180646538734436, 0.02203965000808239, + -0.0035809362307190895, 0.02187073789536953, -0.008040214888751507, + 0.0019053284777328372, -0.0007229437469504774, 0.04820075258612633, + 0.07287543267011642, 0.027829956263303757, 0.01050633192062378, + -0.004222802352160215, -0.02354634366929531, 0.022451795637607574, + -0.11383998394012451, 0.024654408916831017, 0.028411012142896652, + 0.0018242505611851811, -0.00024323341494891793, 0.005445725750178099, + 0.011060363613069057, -0.08840858936309814, 0.009080713614821434, + -0.019992433488368988, -0.0661730021238327, -0.012965692207217216, + -0.00035133716301061213, -0.007580774370580912, -0.042998261749744415, + -0.037802524864673615, 0.02318825200200081, 0.030681191012263298, + -0.0008648298680782318, 0.029519077390432358, -0.012708946131169796, + -0.007553748786449432, -0.000020269450033083558, + 0.0035539104137569666, -0.0003378241672180593, -0.03653906285762787, + 0.009688797406852245, -0.0014256180729717016, -0.000493223313242197, + 0.005526803433895111, 0.03970109671354294, 0.046153537929058075, + 0.025924628600478172, 0.037998463958501816, -0.03274191915988922, + 0.018303314223885536, -0.03343108296394348, 0.0015607477398589253, + 0.06490277498960495, -0.007168628741055727, 0.0011215762933716178, + -0.021816685795783997, 0.011594126001000404, -0.07243625819683075, + 0.005648420192301273, 0.003675527172163129, 0.0022499088663607836, + -0.0335054025053978, -0.011431969702243805, -0.027282679453492165, + 0.033451348543167114, 0.0036417446099221706, 0.012567059136927128, + 0.01567504182457924, 0.013479184359312057, -0.008580734021961689, + 0.024309827014803886, 0.015620989724993706, 0.020796455442905426, + -0.017560100182890892, -0.011479265987873077, 0.0016620948445051908, + 0.014148076064884663, -0.04395768418908119, -0.061193469911813736, + -0.004128211177885532, -0.010053647682070732, -0.0012567059602588415, + 0.04771428927779198, -0.00859424751251936, -0.018499253317713737, + -0.5263165235519409, 0.0033444594591856003, -0.04816021770238876, + -0.004574139136821032, 0.07895626127719879, 0.005851114634424448, + -0.015526398085057735, 0.006472711451351643, 0.0330527164041996, + -0.004364688415080309, -0.0242219939827919, 0.02620839886367321, + 0.07151062041521072, 0.0031350082717835903, -0.018789781257510185, + 0.017310110852122307, -0.014188614673912525, -0.03284326568245888, + -0.01953299343585968, -0.040653761476278305, -0.03721471130847931, + -0.00692539568990469, -0.0022499088663607836, -0.008763158693909645, + -0.027154305949807167, -0.008472629822790623, -0.028451552614569664, + -0.016499333083629608, -0.026823241263628006, 0.023303112015128136, + -0.0004391714173834771, -0.016762835904955864, -0.009182061068713665, + 0.03387025371193886, -0.03335675969719887, 0.0240598376840353, + 0.022296395152807236, 0.011067119427025318, 0.006114617455750704, + 0.020776188001036644, 0.010323906317353249, -0.0014458874939009547, + -0.030167698860168457, 0.03322163224220276, 0.018411418423056602, + -0.011573855765163898, 0.003628231817856431, -0.010026621632277966, + -0.025181414559483528, 0.10141481459140778, -0.01784387417137623, + 0.007533479016274214, -0.025181414559483528, 0.00885774940252304, + -0.0226950291544199, 0.020303232595324516, -0.027255654335021973, + 0.0032160861883312464, -0.024073351174592972, -0.005979487672448158, + -0.023911194875836372, -0.028958288952708244, -0.12885290384292603, + -0.006067322101444006, 0.004905207082629204, -0.028329934924840927, + -0.02904612384736538, 0.00012837319809477776, -0.05159250646829605, + -0.025431403890252113, 0.005729498341679573, -0.009587449952960014, + 0.019505968317389488, 0.0365593321621418, -0.011965733021497726, + -0.04020783305168152, -0.01583719812333584, -0.0029390703421086073, + 0.053862687200307846, -0.02485034614801407, -0.04088348150253296, + -0.1935124397277832, -0.03687012940645218, 0.013898086734116077, + -0.0021350488532334566, -0.008040214888751507, -0.06161237135529518, + -0.0033849983010441065, 0.028910990804433823, 0.004648460540920496, + -0.1203937828540802, -0.01955326274037361, 0.03089739941060543, + -0.00010810373350977898, -0.03483642637729645, -0.00107428093906492, + -0.004526843782514334, 0.004243071656674147, -0.0019931625574827194, + 0.0381808876991272, -0.024809807538986206, 0.01412105094641447, + -0.00003378242035978474, -0.0023917951621115208, -0.00417550653219223, + -0.0034255371429026127, -0.019411375746130943, 0.013404862955212593, + -0.004513331223279238, 0.020404580980539322, 0.003418780630454421, + 0.02103968895971775, -0.017256058752536774, 0.016073673963546753, + -0.07371999323368073, -0.006222721189260483, -0.011040094308555126, + -0.00810778047889471, 0.024316584691405296, 0.009830683469772339, + 0.00512817082926631, 0.062058303505182266, -0.009290164336562157, + -0.04211992025375366, -0.012445442378520966, -0.012540033087134361, + -0.01118198037147522, 0.048356153070926666, 0.010938746854662895, + -0.02669486589729786, 0.024627381935715675, 0.04824129119515419, + -0.007965893484652042, -0.026674598455429077, 0.020343773066997528, + 0.017107415944337845, 0.004371444694697857, -0.013621071353554726, + -0.0003716066130436957, -0.005344378296285868, 0.011175223626196384, + -0.0035539104137569666, 0.006580814719200134, 0.004459279123693705, + -0.01477643009275198, 0.05755172669887543, -0.022093700245022774, + 0.016647975891828537, 0.010546870529651642, -0.027275923639535904, + 0.02605975791811943, 0.021080227568745613, -0.019505968317389488, + -0.035120200365781784, -0.005965975113213062, -0.016492577269673347, + 0.01226301770657301, -0.11921814829111099, 0.0170128270983696, + -0.010060404427349567, -0.04658595100045204, 0.03541748598217964, + -0.040309179574251175, -0.016729053109884262, -0.0359107106924057, + 0.030937936156988144, 0.009384755976498127, -0.01067524403333664, + 0.025627342984080315, -0.014452118426561356, -0.00844560470432043, + 0.050329048186540604, -0.053862687200307846, -0.0006148400134406984, + 0.01387106068432331, 0.00809426698833704, 0.013681878335773945, + 0.03153926506638527, -0.029667718335986137, 0.010094186291098595, + 0.005884896963834763, -0.028275884687900543, -0.006013270001858473, + 0.018107375130057335, 0.0240598376840353, -0.0007905085803940892, + 0.034295909106731415, 0.003891734639182687, 0.02855965495109558, + 0.00927665177732706, 0.02070862241089344, 0.02420172281563282, + -0.06411226838827133, -0.015107497572898865, -0.0016147996066138148, + -0.026593517512083054, 0.01109414640814066, -0.0091888178139925, + 0.02204640582203865, -0.01387106068432331, 0.017303355038166046, + -0.0324040949344635, 0.027302948758006096, 0.04524141550064087, + 0.008803698234260082, -0.0058173323050141335, -0.024005787447094917, + 0.010540113784372807, 0.037829551845788956, 0.03985649719834328, + 0.00512817082926631, -0.02721511386334896, -0.018445199355483055, + -0.01351296715438366, 0.03370809555053711, 0.00960096251219511, + 0.003080956405028701, 0.007202411536127329, -0.004655216820538044, + -0.025154387578368187, 0.007864546962082386, 0.0020134320948272943, + 0.011290083639323711, -0.02401929907500744, -0.001033741980791092, + 0.02604624256491661, 0.0003378241672180593, 0.015039931982755661, + -0.01285083219408989, 0.024316584691405296, -0.014073754660785198, + -0.022255856543779373, 0.011195492930710316, -0.016965530812740326, + 0.011918436735868454, -0.007188898511230946, -0.041951004415750504, + -0.03319460153579712, 0.00554707320407033, -0.012661649845540524, + -0.024303071200847626, -0.008790184743702412, -0.022485578432679176, + -0.018066836521029472, -0.0007769956137053668, 0.023654447868466377, + -0.01251976378262043, 0.001331027247942984, 0.016769591718912125, + -0.0048849377781152725, -0.019978921860456467, -0.021262653172016144, + -0.004830885678529739, 0.03161358833312988, 0.029438000172376633, + -0.02486385963857174, 0.031005503609776497, -0.002533681457862258, + -0.007790225557982922, -0.015769632533192635, 0.005743010900914669, + 0.0035403973888605833, -0.001141845714300871, -0.027174577116966248, + 0.017316866666078568, 0.002533681457862258, 0.015303434804081917, + -0.013992678374052048, 0.016810130327939987, -0.04673459380865097, + 0.015154791995882988, -0.028323179110884666, -0.022086946293711662, + -0.027755632996559143, -0.02791779115796089, 0.05092361569404602, + 0.05913950130343437, 0.008830724284052849, -0.02551248110830784, + 0.002256665611639619, -0.0037498483434319496, 0.01850600726902485, + 0.018397903069853783, -0.046849459409713745, -0.018735729157924652, + -0.03820791468024254, 0.002466116566210985, 0.008425334468483925, + -0.029140714555978775, -0.022086946293711662, 0.058990854769945145, + 0.00925638247281313, 0.01327649038285017, -0.0157155804336071, + 0.04408605396747589, -0.0026958370581269264, 0.022749079391360283, + 0.029269086197018623, 0.037322815507650375, 0.03983622416853905, + -0.018641138449311256, 0.011668447405099869, 0.006871343590319157, + -0.008310474455356598, 0.048998016864061356, -0.005040336865931749, + -0.00425658468157053, 0.0035876925103366375, 0.010067160241305828, + -0.014046729542315006, -0.014843993820250034, 0.007655095774680376, + -0.01109414640814066, 0.005324108991771936, -0.0329243466258049, + 0.03744443133473396, -0.010560383088886738, 0.023167982697486877, + -0.01920192502439022, -0.09798252582550049, 0.04036998748779297, + -0.011472509242594242, -0.0017972246278077364, -0.03458644077181816, + -0.019411375746130943, 0.016587166115641594, -0.008067241869866848, + -0.037457942962646484, 0.0022228830493986607, -0.029242059215903282, + -0.044153619557619095, 0.010648217983543873, -0.011398187838494778, + -0.022147752344608307, 0.014594004489481449, 0.004871424287557602, + -0.0006486224010586739, 0.0050065540708601475, -0.01685742661356926, + -0.033640533685684204, -0.006567301694303751, -0.020323501899838448, + 0.026296233758330345, 0.028992071747779846, 0.04254557564854622 + ] + }, + "beach": { + "prompt": "Photo of a beautiful beach", + "vector": [ + -0.014337359927594662, 0.008085873909294605, 0.004238295368850231, + -0.04446237161755562, -0.008224942721426487, 0.03859497979283333, + 0.022509323433041573, 0.008933532051742077, 0.003609173698350787, + 0.029283974319696426, 0.0250787902623415, -0.01219172216951847, + -0.04351537674665451, 0.011549355462193489, -0.0035694397520273924, + 0.5811630487442017, 0.0009138825116679072, 0.005066087935119867, + -0.02306560054421425, 0.028462804853916168, -0.015257864259183407, + -0.03599240258336067, 0.03354213759303093, -0.04589279368519783, + 0.017860442399978638, -0.0337805412709713, 0.03242295980453491, + -0.018317384645342827, 0.015886986628174782, 0.015211507678031921, + -0.011284462176263332, -0.02511190064251423, 0.021277567371726036, + -0.027005890384316444, -0.0029005836695432663, 0.00967523455619812, + -0.007960048504173756, -0.016794247552752495, -0.037469182163476944, + -0.022012649103999138, 0.006648826412856579, 0.018112091347575188, + -0.003728376002982259, -0.03072764351963997, 0.02964157983660698, + 0.014211534522473812, -0.0009072601678781211, -0.003980024252086878, + 0.018098846077919006, -0.00018542543693911284, 0.02858862839639187, + 0.05057479068636894, 0.013999620452523232, 0.010562627576291561, + 0.006728294305503368, -0.02783368155360222, -0.0021125255152583122, + 0.016271082684397697, -0.030118387192487717, 0.030005808919668198, + -0.000715212372597307, -0.02084711566567421, -0.01541017834097147, + 0.0136089026927948, -0.004278029780834913, 0.028621740639209747, + 0.00102646229788661, -0.030999161303043365, 0.01017853245139122, + -0.03440966084599495, 0.04332995042204857, 0.0114698875695467, + -0.004337630700320005, -0.002880716696381569, 0.03437655046582222, + 0.02301924303174019, -0.05721036717295647, 0.006158773321658373, + 0.04152205213904381, 0.026813842356204987, 0.004033003468066454, + -0.035277191549539566, 0.04628351330757141, 0.023575520142912865, + 0.007695155218243599, 0.002331062685698271, -0.008575926534831524, + 0.015383689664304256, -0.016853848472237587, -0.009536165744066238, + 0.027442965656518936, 0.02937006577849388, 0.029072057455778122, + -0.006390554830431938, -0.028582006692886353, 0.010105686262249947, + -0.05311776325106621, 0.010145420208573341, 0.007761379238218069, + -0.001927100121974945, -0.02185371145606041, -0.012906935065984726, + 0.0002119147829944268, 0.006741539109498262, -0.033409688621759415, + -0.03160179406404495, -0.0758257582783699, 0.02498607710003853, + 0.01603267714381218, -0.023515919223427773, -0.024443047121167183, + 0.00582103431224823, 0.008443479426205158, -0.04087306559085846, + 0.006907097529619932, -0.026899931952357292, -0.0013046003878116608, + 0.006681937724351883, -0.04248891398310661, 0.008714995346963406, + -0.02203913778066635, 0.0157479178160429, 0.02273448370397091, + 0.013747971504926682, 0.0025628444273024797, 0.010734807699918747, + 0.062263209372758865, -0.02872769720852375, -0.008344144560396671, + 0.009390474297106266, -0.0065031349658966064, 0.016449885442852974, + 0.009642122313380241, -0.010681829415261745, -0.024760916829109192, + -0.013589034788310528, -0.026330411434173584, 0.004072736948728561, + -0.0035628171171993017, 0.005973347928375006, 0.006417044438421726, + 0.015886986628174782, 0.0069137196987867355, -0.04590604081749916, + 0.006152151618152857, -0.01846969872713089, -0.012642040848731995, + 0.013456588611006737, -0.03586657717823982, 0.00468861497938633, + 0.004417098592966795, -0.00984079297631979, 0.03658178821206093, + -0.040555190294981, -0.009370607323944569, -0.0043641203083097935, + 0.012165232561528683, 0.006079305429011583, 0.021754376590251923, + -0.017310788854956627, -0.02872769720852375, 0.0004238295659888536, + 0.020304085686802864, 0.00770177785307169, -0.03493282571434975, + -0.055217042565345764, 0.019814031198620796, -0.5854675769805908, + 0.01785382069647312, -0.03142298758029938, -0.011244728229939938, + 0.0694948062300682, -0.009244781918823719, 0.008522948250174522, + 0.00046356357051990926, 0.01653597503900528, -0.025747647508978844, + 0.012787732295691967, -0.017727995291352272, 0.014509540051221848, + 0.012721509672701359, 0.01629757136106491, 0.03692615032196045, + 0.010701696388423443, 0.002225105185061693, 0.006364066153764725, + -0.008264676667749882, 0.01925775595009327, -0.009211670607328415, + 0.032257404178380966, 0.027409851551055908, -0.0293568205088377, + 0.05248864367604256, -0.03565466031432152, -0.0049733747728168964, + -0.04603186249732971, -0.00925140455365181, -0.07718995958566666, + -0.003615796100348234, 0.0067746504209935665, 0.023946372792124748, + 0.024979455396533012, 0.04461468383669853, 0.020747780799865723, + 0.013681747950613499, 0.017330655828118324, 0.007840846665203571, + 0.0059137470088899136, 0.02589995786547661, 0.0298601184040308, + 0.025058923289179802, -0.010589116252958775, -0.006079305429011583, + -0.015032704919576645, 0.011099036782979965, 0.005119066219776869, + -0.025025812909007072, -0.011979807168245316, 0.006324331741780043, + -0.0008277921588160098, -0.019184909760951996, -0.00837725680321455, + 0.002331062685698271, -0.0025495998561382294, -0.016734644770622253, + -0.011046057567000389, 0.03548910468816757, 0.00333765777759254, + 0.004721726290881634, -0.11840076744556427, -0.02340996079146862, + 0.052018459886312485, -0.035422880202531815, -0.06260757148265839, + -0.0006357443635351956, 0.015900231897830963, -0.02202589437365532, + 0.005675342865288258, -0.003993269056081772, -0.0024039081763476133, + -0.01565520465373993, 0.0036422854755073786, -0.06417044252157211, + 0.029144903644919395, -0.05456143245100975, 0.0021323924884200096, + 0.014939992688596249, 0.019516026601195335, -0.10220915079116821, + -0.007814357057213783, 0.007979915477335453, -0.021780867129564285, + -0.010748052969574928, 0.03157530352473259, 0.010489782318472862, + -0.01360228005796671, 0.0098341703414917, -0.08183883875608444, + -0.011363930068910122, 0.00719847995787859, 0.04788611829280853, + -0.01499959360808134, 0.002337685087695718, 0.008642150089144707, + 0.006039571017026901, 0.008940154686570168, -0.0023111954797059298, + 0.034244101494550705, -0.0005231646355241537, -0.019244510680437088, + 0.006880608387291431, -0.009019623510539532, -0.014045977033674717, + 0.02710522525012493, -0.005874013062566519, 0.039998915046453476, + 0.031442854553461075, 0.010701696388423443, 0.0038144660647958517, + -0.005827656481415033, 0.016734644770622253, -0.018098846077919006, + -0.015377067029476166, 0.01251621637493372, 0.011516244150698185, + 0.00788058154284954, -0.0015496269334107637, 0.014542651362717152, + 0.028078708797693253, 0.001377446111291647, -0.030767379328608513, + -0.008383878506720066, -0.0006291220197454095, 0.0060130818746984005, + -0.004278029780834913, 0.020853739231824875, -0.025933071970939636, + -0.016277704387903214, 0.02117823250591755, -0.00002648934787430335, + 0.047144416719675064, -0.015939965844154358, 0.0052647581323981285, + 0.010754674673080444, 0.017251187935471535, 0.0027615143917500973, + 0.027284028008580208, -0.011264595203101635, 0.015608848072588444, + 0.017191587015986443, 0.12382446229457855, 0.007834224961698055, + 0.01678762398660183, 0.014423450455069542, 0.052773404866456985, + 0.02674761787056923, -0.04421072080731392, 0.002198615809902549, + -0.037575140595436096, 0.008469969034194946, -0.010211643762886524, + -0.015112172812223434, -0.02072129212319851, -0.01097321230918169, + -0.043634578585624695, 0.009483186528086662, -0.03866782784461975, + -0.0001059573914972134, -0.00037747318856418133, -0.01737038977444172, + -0.007887203246355057, 0.016588954254984856, -0.01361552532762289, + -0.004311141092330217, -0.011873849667608738, 0.016747890040278435, + 0.0027615143917500973, 0.001258244039490819, -0.0365486778318882, + 0.03971415385603905, 0.027118470519781113, 0.009536165744066238, + 0.022727860137820244, -0.004562790039926767, -0.02871445193886757, + -0.018198180943727493, -0.011430153623223305, 0.015165151096880436, + -0.03127729892730713, -0.00659584766253829, -0.00333765777759254, + 0.005330981221050024, -0.004648880567401648, -0.023688098415732384, + 0.00942358560860157, -0.015641959384083748, 0.032118331640958786, + -0.01956900581717491, 0.03055546246469021, 0.0069137196987867355, + 0.017946533858776093, -0.005701832007616758, -0.0027350252494215965, + 0.007748133968561888, -0.004423721227794886, 0.01821804977953434, + 0.004940263461321592, -0.026946287602186203, -0.021277567371726036, + 0.0175094585865736, 0.011297707445919514, -0.003609173698350787, + -0.02441655658185482, -0.015489645302295685, -0.02902570180594921, + -0.05147542431950569, 0.017708130180835724, -0.013231429271399975, + -0.03809168189764023, -0.021376904100179672, -0.022237807512283325, + -0.015834007412195206, -0.00043045193888247013, -0.023217912763357162, + 0.03719104453921318, -0.009774569422006607, -0.009046112187206745, + 0.0021390148904174566, 0.004907151684165001, 0.02403908409178257, + -0.022251052781939507, -0.0006357443635351956, 0.01812533661723137, + 0.005357470363378525, 0.02306560054421425, 0.01927100121974945, + -0.010211643762886524, -0.039939314126968384, 0.01617174781858921, + -0.013112226501107216, -0.024363579228520393, 0.009158692322671413, + 0.0050131091848015785, -0.029634958133101463, 0.013648636639118195, + 0.0024039081763476133, 0.0124764833599329, 0.01138379704207182, + 0.0015628715045750141, -0.008569303900003433, 0.01697305031120777, + -0.03660827875137329, 0.01063547283411026, -0.002880716696381569, + -0.0035760619211941957, 0.014343982562422752, 0.005681965034455061, + -0.0039998916909098625, -0.010132175870239735, -0.005973347928375006, + 0.07218346744775772, -0.028707832098007202, -0.04050883278250694, + 0.010820899158716202, 0.01771475002169609, 0.0035429501440376043, + -0.016555842012166977, 0.0013906907988712192, 0.007092522922903299, + 0.014151934534311295, -0.009880526922643185, 0.02850916050374508, + 0.01268839742988348, 0.018979618325829506, -0.018429962918162346, + -0.012761243619024754, 0.00421180622652173, 0.013238051906228065, + -0.0040992265567183495, -0.0006092549883760512, 0.010463292710483074, + 0.008814330212771893, -0.013502945192158222, 0.01138379704207182, + 0.027277406305074692, 0.007860713638365269, 0.013787705451250076, + -0.022217940539121628, 0.013622147962450981, -0.02139677107334137, + -0.05273367092013359, 0.030330302193760872, -0.006655448582023382, + -0.00335090234875679, -0.03293950483202934, -0.001927100121974945, + -0.03336995840072632, 0.02331724762916565, 0.002284706337377429, + -0.024913230910897255, -0.008145473897457123, -0.029648203402757645, + -0.0158737413585186, 0.03517785295844078, 0.04156840965151787, + 0.06958751380443573, 0.024350333958864212, 0.014291003346443176, + -0.014973104000091553, -0.02298613078892231, -0.003072764491662383, + 0.028734320774674416, 0.034634821116924286, 0.004953507799655199, + 0.0064038001000881195, 0.001430424745194614, -0.04593915119767189, + 0.009648744948208332, 0.022330520674586296, -0.0023045733105391264, + -0.02199278026819229, 0.006549491547048092, 0.020747780799865723, + 0.006132283713668585, 0.022549057379364967, -0.012582440860569477, + -0.10984470695257187, -0.00737066101282835, 0.006854118779301643, + 0.028151554986834526, -0.029807137325406075, 0.007483240682631731, + 0.02718469314277172, 0.01227119006216526, -0.0034237480722367764, + -0.00837725680321455, -0.024476157501339912, 0.006834251806139946, + -0.0037879766896367073, -0.003827710635960102, 0.012456615455448627, + -0.04129689559340477, 0.011277839541435242, -0.00726470397785306, + 0.008728240616619587, -0.017986267805099487, 0.013675126247107983, + 0.022820573300123215, 0.014423450455069542, -0.008238187991082668, + 0.02880716510117054, -0.06673328578472137 + ] + }, + "city": { + "prompt": "Beautiful photo showing a metropolitan city", + "vector": [ + -0.02022508904337883, 0.0024308236315846443, -0.024840475991368294, + -0.01621343567967415, -0.018501268699765205, 0.014394289813935757, + -0.004027541261166334, 0.010938706807792187, 0.018445663154125214, + 0.0036859549582004547, 0.0027088590431958437, -0.01409242209047079, + -0.05214355140924454, 0.004583612084388733, 0.031870801001787186, + 0.5370293259620667, -0.007220976520329714, -0.005624258890748024, + 0.015101294033229351, -0.03704225644469261, -0.0075943381525576115, + -0.006609298288822174, -0.03983850032091141, 0.004742489196360111, + 0.011693374253809452, -0.02916988544166088, 0.017015766352415085, + -0.018954068422317505, -0.041300173848867416, 0.004543892573565245, + -0.0018429774791002274, 0.006267712451517582, 0.03637497127056122, + 0.04597910866141319, -0.03643852472305298, 0.003113996470347047, + 0.007316302508115768, -0.012535424903035164, -0.04145904630422592, + -0.01633259281516075, 0.024808701127767563, -0.03063950128853321, + -0.0009453203529119492, -0.043651554733514786, 0.0011359731433913112, + -0.06291543692350388, -0.010231702588498592, -0.005346223711967468, + 0.016547076404094696, 0.0009691519662737846, 0.04871974512934685, + 0.023418523371219635, -0.0007943868404254317, 0.05904677137732506, + -0.012170006521046162, -0.010048992931842804, -0.009111616760492325, + -0.024419451132416725, 0.0007864429499022663, 0.049530018121004105, + 0.020924149081110954, 0.013679341413080692, 0.021392837166786194, + -0.024562440812587738, -0.07822327315807343, 0.024403564631938934, + 0.023760110139846802, 0.003431751159951091, -0.0020177424885332584, + 0.037240855395793915, 0.027882976457476616, 0.0327446274459362, + -0.044167909771203995, 0.025261500850319862, -0.006069115363061428, + 0.004488285630941391, -0.02721569500863552, 0.0396399050951004, + 0.07103406637907028, 0.08251295983791351, 0.014576999470591545, + -0.003503246232867241, 0.03790019452571869, 0.02272740751504898, + 0.03453994169831276, -0.00006355094956234097, -0.05225476622581482, + -0.008023306727409363, 0.006402757950127125, -0.011296181008219719, + 0.02713625505566597, 0.031886689364910126, -0.0186760351061821, + 0.005568651482462883, 0.0008182184537872672, 0.013655510731041431, + -0.05238186940550804, -0.03210911527276039, -0.015625588595867157, + 0.0030583892948925495, -0.01081160455942154, 0.00936582125723362, + 0.025404492393136024, 0.0222031120210886, 0.011423283256590366, + -0.009977499023079872, -0.055519696325063705, 0.010906931944191456, + 0.026516633108258247, 0.011177022941410542, 0.01776248961687088, + -0.05265196040272713, -0.0033920318819582462, -0.05242953449487686, + 0.014608773402869701, 0.02619093470275402, -0.004813984502106905, + -0.0286058709025383, -0.0017079317476600409, -0.0365656279027462, + -0.02855820581316948, 0.012440097518265247, 0.03320537135004997, + 0.02806568518280983, 0.059038832783699036, 0.06320936232805252, + 0.01823117770254612, -0.026921769604086876, 0.00046074436977505684, + 0.0030425016302615404, -0.016960158944129944, -0.005465381313115358, + 0.0365656279027462, 0.016983991488814354, 0.006982659921050072, + -0.032752569764852524, -0.014719988219439983, -0.0045200614258646965, + -0.03741561993956566, -0.04035485163331032, 0.014457839541137218, + 0.00039719342021271586, 0.0050523001700639725, -0.011661598458886147, + -0.007729384116828442, -0.015403159894049168, 0.04833843931555748, + 0.01579241082072258, -0.0315609872341156, 0.0256110318005085, + 0.02179797552525997, -0.022997498512268066, 0.06011919677257538, + -0.024292349815368652, -0.006188273895531893, -0.058022018522024155, + -0.013401305302977562, 0.0030742769595235586, 0.01505363080650568, + 0.020582562312483788, -0.03634319826960564, -0.002605588873848319, + 0.03014698065817356, -0.020963868126273155, -0.009373764507472515, + -0.02958296611905098, -0.020940037444233894, -0.5408344864845276, + -0.026413362473249435, -0.008031250908970833, -0.04113335162401199, + 0.052993547171354294, 0.007681720890104771, 0.015959231182932854, + 0.03058389574289322, -0.041284285485744476, -0.023926932364702225, + 0.00962796900421381, -0.004385015461593866, 0.004933142103254795, + 0.03788430988788605, -0.015371385030448437, -0.00006355094956234097, + 0.021972740069031715, 0.042150165885686874, 0.024530665948987007, + -0.004964917898178101, -0.022830678150057793, -0.02320403978228569, + 0.017031654715538025, -0.009945723228156567, -0.0012710189912468195, + 0.00988217256963253, -0.06859530508518219, -0.026786724105477333, + -0.06705419719219208, -0.0025499816983938217, -0.0145690543577075, + 0.03293528035283089, 0.018080245703458786, 0.03054417483508587, + 0.0023196095135062933, -0.003821000689640641, 0.010891043581068516, + -0.0018112020334228873, 0.021964795887470245, -0.007729384116828442, + -0.014998022466897964, 0.029980158433318138, -0.063360296189785, + 0.016038671135902405, 0.027970358729362488, -0.047059476375579834, + 0.010001330636441708, 0.008960683830082417, -0.009786846116185188, + -0.06726867705583572, -0.03569180145859718, 0.004861647263169289, + -0.020113874226808548, 0.040672607719898224, 0.010954594239592552, + 0.015093350782990456, -0.020415741950273514, -0.02970212511718273, + 0.010334973223507404, 0.011026089079678059, -0.005536876618862152, + -0.027509616687893867, -0.08001858741044998, -0.0019462477648630738, + 0.06487756967544556, -0.021980684250593185, -0.03148949518799782, + -0.01865220256149769, -0.033689945936203, -0.014497559517621994, + -0.012559256516397, 0.0024546554777771235, -0.031441833823919296, + -0.05378793179988861, 0.0027882978320121765, -0.01620549149811268, + -0.021933021023869514, -0.060548167675733566, -0.0010247590253129601, + -0.051984675228595734, -0.016626516357064247, -0.06469486653804779, + 0.005656034220010042, 0.003034557681530714, -0.03561235964298248, + 0.0037415619008243084, 0.053057096898555756, -0.016904551535844803, + -0.008936851285398006, 0.006672849413007498, -0.09151336550712585, + 0.00577519228681922, 0.017873702570796013, 0.013663453981280327, + 0.016435863450169563, -0.027040928602218628, 0.026270372793078423, + -0.02910633571445942, 0.03319742530584335, -0.02999604493379593, + -0.03689132630825043, 0.021392837166786194, 0.01628493145108223, + 0.04634452611207962, 0.037510946393013, -0.018135851249098778, + -0.0022322270087897778, 0.022965723648667336, 0.04292071983218193, + -0.010620951652526855, 0.018985845148563385, 0.005894350353628397, + -0.018374167382717133, -0.04233287647366524, -0.008523771539330482, + -0.0110816964879632, -0.02910633571445942, 0.03639880567789078, + -0.03484180569648743, -0.004369127564132214, 0.02375216595828533, + 0.0009294326300732791, 0.006045283749699593, -0.012432154268026352, + -0.003963990602642298, 0.022910118103027344, -0.014672324992716312, + 0.003868664149194956, -0.0008817694033496082, -0.0002780354116111994, + 0.0354217104613781, 0.03109230101108551, -0.013369530439376831, + -0.02374422363936901, -0.013981208205223083, 0.04003709554672241, + 0.009810677729547024, -0.001469615614041686, 0.004067260771989822, + 0.001199524151161313, -0.0004130811430513859, -0.00815040897578001, + -0.006100891157984734, 0.058482762426137924, -0.0038051127921789885, + -0.015887737274169922, -0.007848542183637619, -0.0013425137149170041, + -0.0055527640506625175, -0.04345295950770378, 0.03109230101108551, + -0.04988749325275421, 0.003821000689640641, 0.05859397351741791, + 0.025380657985806465, 0.013925601728260517, 0.04548659175634384, + -0.05572623386979103, 0.029511472210288048, -0.0542169027030468, + 0.036287590861320496, 0.01865220256149769, -0.028296060860157013, + 0.01213028747588396, -0.010994314216077328, 0.043262310326099396, + 0.032760512083768845, -0.004837815649807453, 0.04880712926387787, + -0.01974051259458065, -0.014918585307896137, -0.02765260450541973, + 0.05876079201698303, -0.014434007927775383, -0.004281744826585054, + -0.010184039361774921, -0.01478353887796402, 0.011439170688390732, + -0.023005442693829536, -0.005187346134334803, 0.037828702479600906, + -0.019518084824085236, -0.004043429158627987, -0.01927182450890541, + -0.0241334717720747, -0.013957376591861248, -0.004575667902827263, + 0.016562964767217636, -0.03171192482113838, -0.013480745255947113, + -0.01922416314482689, -0.024300292134284973, -0.051079075783491135, + -0.028367552906274796, -0.003407919779419899, 0.015919512137770653, + -0.007348078303039074, -0.03191052004694939, 0.021829750388860703, + 0.00914339255541563, -0.008420499972999096, -0.001835033530369401, + -0.02218722365796566, -0.001850921311415732, 0.02117040939629078, + -0.014505504630506039, -0.001517278840765357, 0.04524827376008034, + 0.01832650601863861, 0.010422355495393276, -0.00021448444749694318, + -0.06717334687709808, 0.034690871834754944, 0.007975644432008266, + -0.01573680341243744, 0.002859792672097683, -0.0015887736808508635, + -0.011184967122972012, 0.026747005060315132, -0.04824311286211014, + -0.053875312209129333, -0.001835033530369401, -0.020383967086672783, + 0.0008023307309485972, -0.014195693656802177, 0.0006513972184620798, + 0.011296181008219719, 0.03794785961508751, 0.023378804326057434, + -0.03809085115790367, -0.04352445527911186, 0.023950763046741486, + -0.034253958612680435, 0.04487491399049759, 0.04095064103603363, + 0.0024784868583083153, -0.016928382217884064, 0.006227992940694094, + -0.019931165501475334, 0.010009273886680603, 0.01927976869046688, + 0.02655635215342045, -0.05090431123971939, 0.02217928133904934, + -0.004369127564132214, 0.027255410328507423, -0.017651276662945747, + 0.029868947342038155, 0.01428307592868805, -0.01884285733103752, + 0.04993515834212303, 0.020042380318045616, -0.001644380739890039, + 0.012575143948197365, -0.038750190287828445, -0.032323598861694336, + 0.031251177191734314, -0.03403947502374649, -0.026349812746047974, + 0.001644380739890039, 0.04008476063609123, -0.010295253247022629, + 0.0118204765021801, 0.0033920318819582462, 0.007578450720757246, + -0.001437840168364346, 0.03484180569648743, -0.0423567034304142, + 0.01086721196770668, -0.016110165044665337, -0.0004686882020905614, + 0.006672849413007498, -0.020431628450751305, -0.03305443376302719, + -0.00284390477463603, -0.03195818141102791, 0.03586656600236893, + 0.024276461452245712, 0.02707270160317421, 0.04344501718878746, + 0.01232888363301754, -0.001549054286442697, -0.0779770165681839, + -0.03746328502893448, -0.015299891121685505, -0.015363441780209541, + -0.008237791247665882, 0.010795717127621174, 0.014028871431946754, + -0.007228919770568609, 0.005187346134334803, 0.0533113032579422, + 0.013155045919120312, 0.00529856001958251, -0.06317758560180664, + -0.012519536539912224, 0.0317198671400547, 0.04698003828525543, + 0.08506294339895248, 0.020074155181646347, 0.0237203910946846, + -0.03108435869216919, 0.01625315472483635, -0.004607443697750568, + 0.05777575448155403, -0.01980406418442726, -0.04331791400909424, + -0.031322672963142395, 0.018866688013076782, -0.012908786535263062, + 0.013552239164710045, 0.002859792672097683, 0.038758132606744766, + -0.0037177305202931166, 0.001294850604608655, 0.0073798540979623795, + 0.022004514932632446, 0.027779707685112953, 0.008086858317255974, + -0.12062764167785645, -0.04426323622465134, 0.021035363897681236, + 0.04329408332705498, -0.025285333395004272, 0.030250251293182373, + -0.0031696034129709005, 0.0241334717720747, -0.02515028789639473, + 0.004528005141764879, -0.06219254434108734, 0.01232888363301754, + -0.008023306727409363, 0.02171853743493557, 0.03409508615732193, + 0.02372833527624607, -0.00884946994483471, -0.011065809056162834, + -0.00729247136041522, -0.04056933522224426, -0.005838742945343256, + 0.001421952387318015, -0.023307310417294502, -0.017389127984642982, + 0.014998022466897964, -0.024014314636588097 + ] + }, + "moon": { + "prompt": "Photo of a beautiful moon", + "vector": [ + 0.02025710977613926, 0.018633542582392693, 0.006263218354433775, + -0.03596823289990425, 0.01744708977639675, -0.014811917208135128, + 0.025377586483955383, 0.027844157069921494, 0.0332893468439579, + 0.03457571193575859, -0.019520258530974388, -0.03809135779738426, + -0.03466938063502312, 0.030654177069664, 0.018358785659074783, + 0.5304065346717834, -0.03515645116567612, 0.039383962750434875, + -0.004383628722280264, -0.007081246003508568, 0.0023042147513478994, + 0.026564037427306175, -0.028431138023734093, -0.023691575974225998, + 0.0031971761491149664, -0.05539482459425926, -0.001954523613676429, + -0.022355254739522934, 0.00783682893961668, 0.0347505584359169, + -0.004302449990063906, -0.02996104024350643, -0.03302083536982536, + 0.014168735593557358, -0.017503291368484497, 0.011108938604593277, + -0.00493314303457737, 0.021318672224879265, -0.002660150406882167, + -0.03516269475221634, 0.03971492499113083, 0.05899789184331894, + -0.04307445511221886, -0.0264141708612442, 0.018964501097798347, + -0.017497045919299126, 0.0006494264234788716, -0.012607615441083908, + 0.019201790913939476, -0.0244658924639225, 0.026663949713110924, + -0.008835946209728718, -0.015923436731100082, -0.011358717456459999, + -0.0240787323564291, 0.01980126090347767, 0.0007743160822428763, + 0.010103576816618443, 0.018927032127976418, 0.055344872176647186, + 0.0006993822753429413, -0.003534378483891487, -0.015892215073108673, + -0.0073934695683419704, -0.021100115031003952, 0.03495662659406662, + 0.037054769694805145, 0.007218623999506235, -0.01709739863872528, + -0.027057351544499397, 0.03955256566405296, 0.05627529323101044, + -0.01544885616749525, -0.010003664530813694, 0.016173215582966805, + 0.0055888136848807335, -0.01970134861767292, 0.030410639941692352, + 0.0495624765753746, 0.02790660224854946, -0.03941518813371658, + 0.027250930666923523, 0.012588880956172943, 0.010559423826634884, + -0.012207968160510063, 0.014861873351037502, 0.0033720219507813454, + 0.03432593494653702, -0.04916282743215561, -0.0025227719452232122, + -0.012726260349154472, 0.07261711359024048, 0.003134731436148286, + 0.033969998359680176, -0.01705993339419365, 0.019058167934417725, + -0.039565056562423706, -0.03470684587955475, -0.003278354648500681, + 0.007356002926826477, -0.011421162635087967, 0.026051988825201988, + -0.03685494884848595, -0.05667494237422943, -0.0015111652901396155, + -0.009273060597479343, -0.05809244140982628, 0.0107717365026474, + 0.020725445821881294, -0.024522092193365097, -0.018358785659074783, + -0.006744043901562691, -0.018227651715278625, -0.029786191880702972, + 0.04217524826526642, 0.0341198667883873, 0.03138478100299835, + 0.03781035542488098, -0.03432593494653702, 0.06708449870347977, + 0.03763550892472267, -0.005395234562456608, 0.01049073413014412, + 0.017034955322742462, 0.025102829560637474, 0.049081649631261826, + 0.0868607833981514, 0.02744450978934765, 0.02859349548816681, + 0.04921278357505798, -0.004989343229681253, 0.028019003570079803, + -0.002722595352679491, 0.02356044016778469, -0.0471833273768425, + -0.0012801194097846746, -0.011015270836651325, -0.02095649018883705, + -0.0052765896543860435, 0.016391772776842117, 0.006344396620988846, + -0.013800310902297497, -0.0002560238935984671, -0.037747908383607864, + -0.034457068890333176, 0.005901038181036711, -0.007918006740510464, + -0.027400799095630646, -0.06263842433691025, -0.03289594501256943, + 0.0012613859726116061, 0.0014362315414473414, -0.0148181626573205, + -0.022605035454034805, 0.012951061129570007, 0.034488290548324585, + 0.009497861377894878, 0.011833298951387405, 0.015105409547686577, + -0.014249914325773716, 0.005120477639138699, -0.03333306312561035, + 0.03558731824159622, -0.005526368971914053, -0.02112509310245514, + -0.06097739562392235, -0.003890313906595111, -0.5348338484764099, + -0.01283866073936224, -0.0047832755371928215, -0.025621119886636734, + 0.05942251905798912, 0.0257397647947073, -0.02598954737186432, + -0.012314124032855034, 0.01257639192044735, -0.026695171371102333, + -0.038309913128614426, 0.020163441076874733, 0.005008076783269644, + 0.016597840934991837, 0.026076968759298325, 0.011596008203923702, + 0.015142875723540783, -0.02873087301850319, -0.013787822797894478, + -0.012145522981882095, -0.033826377242803574, 0.011596008203923702, + 0.04476671293377876, 0.022286565974354744, -0.0026289280503988266, + 0.05146079882979393, -0.07879915088415146, -0.0283187385648489, + -0.03071662038564682, 0.0016610330203548074, -0.025077851489186287, + -0.008298920467495918, -0.007468403782695532, 0.03574967756867409, + -0.017940403893589973, 0.059191472828388214, 0.02376650832593441, + 0.0012176745804026723, 0.04235009849071503, 0.03725459426641464, + -0.027669312432408333, -0.049668632447719574, 0.022505123168230057, + 0.03690490499138832, 0.02529640682041645, -0.0446043536067009, + 0.014786939136683941, -0.015261520631611347, 0.01775307022035122, + 0.022018054500222206, -0.032989609986543655, 0.016010858118534088, + -0.03268988057971001, 0.0076120272278785706, -0.03703603893518448, + 0.0003684246039483696, -0.024853048846125603, 0.020937757566571236, + -0.027544422075152397, -0.025658588856458664, -0.022111721336841583, + -0.017084911465644836, -0.037017304450273514, -0.005195411387830973, + 0.05747423693537712, -0.042481228709220886, -0.011614741757512093, + -0.04056417569518089, -0.01143365167081356, -0.010852914303541183, + 0.03895933926105499, 0.013575509190559387, -0.010721780359745026, + 0.0158859696239233, -0.0040464261546730995, 0.00031846872298046947, + -0.0031971761491149664, -0.005083010531961918, -0.011458629742264748, + -0.018683498725295067, -0.039733655750751495, -0.10297779738903046, + -0.013163373805582523, -0.01376284472644329, -0.04125106707215309, + 0.02078164555132389, 0.10747382044792175, -0.0015611212002113461, + 0.02255507931113243, 0.007786872796714306, -0.025589898228645325, + 0.015536277554929256, 0.019538993015885353, -0.011439896188676357, + 0.006381863262504339, 0.0032596210949122906, 0.014568382874131203, + 0.005876060109585524, -0.011508584953844547, 0.018777165561914444, + -0.008967080153524876, 0.012089322321116924, 0.007849317975342274, + -0.0010365844937041402, 0.008342632092535496, -0.011221338994801044, + -0.033514149487018585, -0.02644539251923561, 0.045822031795978546, + 0.01873345486819744, 0.0034407111816108227, -0.04611552134156227, + -0.027506954967975616, -0.006256973836570978, 0.03975863382220268, + -0.007774383760988712, 0.002535260980948806, 0.014549649320542812, + -0.014768206514418125, 0.047795288264751434, -0.024172401055693626, + -0.014649561606347561, 0.011290028691291809, 0.00993497483432293, + 0.011908232234418392, 0.01636679470539093, 0.004002714529633522, + 0.005164189264178276, 0.0002872463082894683, -0.02679508365690708, + 0.10626864433288574, 0.06183912977576256, 0.03245258703827858, + 0.06080879643559456, -0.007705694064497948, -0.0011427407152950764, + -0.0320279635488987, -0.014268646948039532, -0.011271295137703419, + -0.039839811623096466, -0.054364487528800964, 0.02366659790277481, + 0.006981334183365107, 0.054327018558979034, 0.021874429658055305, + -0.03973989933729172, 0.00262268353253603, 0.011302517727017403, + -0.012982283718883991, -0.054183389991521835, 0.016141993924975395, + -0.0916752815246582, -0.013007261790335178, -0.0034094888251274824, + 0.00017484556883573532, -0.06137079745531082, 0.014193713665008545, + -0.008823457174003124, 0.009791351854801178, 0.005189166869968176, + 0.024478379637002945, 0.016323082149028778, 0.018720965832471848, + -0.04423592984676361, 0.006556709297001362, 0.0031222424004226923, + 0.057836420834064484, 0.0016235660295933485, 0.03446955606341362, + 0.005638769827783108, -0.008929613046348095, 0.019538993015885353, + 0.03247756510972977, 0.048176199197769165, 0.003877825103700161, + 0.049949631094932556, 0.004121359903365374, 0.020269596949219704, + -0.027269665151834488, 0.003521889680996537, 0.003290843451395631, + -0.06808985769748688, 0.0010990293230861425, -0.034525755792856216, + 0.014736983925104141, -0.00008117830293485895, -0.0181339830160141, + 0.018995722755789757, -0.001049073413014412, 0.03494413569569588, + -0.025277674198150635, 0.02605823427438736, 0.01771560311317444, + -0.055544693022966385, -0.006893911398947239, -0.004327428061515093, + -0.02199932001531124, 0.03206542879343033, -0.0031472202390432358, + 0.05089879408478737, -0.003315821522846818, -0.06154564023017883, + -0.020163441076874733, -0.0001998235093196854, -0.023429308086633682, + -0.010084843263030052, -0.0064318194054067135, 0.05909780412912369, + -0.015130387619137764, 0.026332993060350418, -0.008698566816747189, + -0.03749188780784607, 0.022018054500222206, 0.014274892397224903, + 0.042031627148389816, -0.026070723310112953, -0.04804506525397301, + -0.03628045693039894, -0.0060633947141468525, -0.025939591228961945, + -0.0007743160822428763, -0.0039402698166668415, -0.008642367087304592, + 0.05587564781308174, 0.02682630717754364, 0.02716975286602974, + -0.015592479147017002, -0.020200908184051514, 0.003771668765693903, + -0.015117897652089596, 0.0006619153427891433, -0.06335654109716415, + -0.00936672743409872, -0.001161474152468145, -0.015642434358596802, + 0.04652765393257141, -0.03859715908765793, 0.033514149487018585, + -0.039871037006378174, -0.06976962834596634, 0.04631534591317177, + -0.042268916964530945, -0.0018421229906380177, 0.019620170816779137, + -0.047620441764593124, 0.017284734174609184, 0.03624923527240753, + -0.00670657679438591, 0.0035281339660286903, -0.02095649018883705, + 0.011683430522680283, 0.006793999578803778, -0.004820742178708315, + -0.028749607503414154, 0.004777031019330025, -0.04409230500459671, + -0.005563836079090834, 0.0033345548436045647, -0.011240072548389435, + 0.007337269838899374, 0.014905585907399654, 0.022911015897989273, + -0.004008959047496319, -0.005513879936188459, 0.0387907400727272, + -0.03369523957371712, 0.002728839870542288, -0.011427407152950764, + 0.017041198909282684, 0.0006931378156878054, -0.04941260814666748, + -0.014318603090941906, -0.03224651888012886, -0.01539265550673008, + -0.02693246304988861, -0.008623633533716202, -0.027456998825073242, + -0.010409556329250336, 0.005969727877527475, 0.031022600829601288, + 0.009460395202040672, -0.056375205516815186, 0.01032213307917118, + -0.009404193609952927, -0.011558541096746922, -0.03071662038564682, + -0.0077993618324398994, -0.02721346542239189, 0.014968030154705048, + -0.0036904902663081884, 0.013144641183316708, -0.01914559118449688, + -0.03774166852235794, -0.05164188891649246, -0.027456998825073242, + -0.030810287222266197, -0.004283716902136803, 0.004964365623891354, + -0.012451502494513988, -0.021137580275535583, 0.013550532050430775, + -0.025770988315343857, 0.01650417409837246, 0.005757415201514959, + 0.06817728281021118, -0.02665146067738533, 0.008592410944402218, + -0.027007395401597023, 0.049050427973270416, -0.027919093146920204, + 0.002316703787073493, 0.028830785304307938, -0.036124344915151596, + -0.008823457174003124, -0.012239189818501472, 0.015386410057544708, + -0.04643398895859718, 0.02435348927974701, -0.020625533536076546, + -0.11875761300325394, 0.026626484468579292, 0.010403310880064964, + 0.020625533536076546, -0.025521209463477135, -0.01702870987355709, + 0.018052807077765465, -0.009091969579458237, 0.008061629720032215, + 0.018571097403764725, -0.01370664406567812, -0.010059865191578865, + 0.006606664974242449, 0.023860175162553787, 0.006987578235566616, + 0.014043846167623997, 0.002360415179282427, -0.01977003924548626, + -0.03860964998602867, -0.020413219928741455, 0.05174804478883743, + 0.012345346622169018, 0.0019482792122289538, 0.024259822443127632, + -0.005020565818995237, -0.01080920360982418 + ] + }, + "onTheRoad": { + "prompt": "Photo of a nostalgic road trip", + "vector": [ + -0.028973080217838287, 0.012647041119635105, -0.008081350475549698, + -0.004977925214916468, 0.04188457131385803, -0.005273489281535149, + 0.004659026395529509, -0.015703797340393066, -0.002621188759803772, + 0.03169538080692291, -0.007015763316303492, -0.02328735589981079, + 0.0030256451573222876, 0.031788717955350876, -0.004262348171323538, + 0.5301334857940674, 0.015089335851371288, 0.018737221136689186, + 0.020642833784222603, -0.030870914459228516, 0.018169425427913666, + -0.0017811637371778488, -0.04613914340734482, -0.020533941686153412, + 0.011495895683765411, -0.019196122884750366, 0.008594698272645473, + 0.011052549816668034, 0.017586076632142067, 0.03496992215514183, + 0.004534578416496515, 0.011340335942804813, -0.028241947293281555, + -0.0016411596443504095, -0.019297238439321518, 0.007552445400506258, + -0.03332098573446274, -0.017827194184064865, -0.017189396545290947, + -0.0345110185444355, 0.028420841321349144, 0.016100475564599037, + -0.03083980083465576, 0.0034534353762865067, -0.037661112844944, + -0.05464828386902809, -0.028024161234498024, 0.027215249836444855, + -0.023940708488225937, 0.014544875361025333, -0.014015969820320606, + -0.025667425245046616, 0.01144922710955143, 0.03073868528008461, + 0.03635440766811371, -0.03593439608812332, -0.040157854557037354, + 0.009139158762991428, -0.022081764414906502, 0.008921375498175621, + -0.013292615301907063, 0.03142315149307251, -0.01682383008301258, + 0.05030815303325653, -0.07767118513584137, 0.010313638485968113, + -0.03102647140622139, -0.006440190598368645, 0.04801364243030548, + 0.06619862467050552, 0.016644936054944992, 0.04493355005979538, + -0.05138152092695236, -0.0172905120998621, -0.04947590455412865, + 0.0029789770487695932, -0.0670386478304863, 0.022462885826826096, + 0.06652530282735825, 0.05179375410079956, 0.0031112032011151314, + -0.012755932286381721, 0.03672775253653526, 0.009388054721057415, + 0.041814569383859634, 0.011145885102450848, -0.02598632499575615, + 0.021902868524193764, -0.03352321311831474, -0.011729235760867596, + 0.05675612390041351, 0.02020726352930069, 0.018114980310201645, + -0.037062209099531174, -0.0000077780077845091, 0.006541304290294647, + -0.04171345755457878, 0.012118136510252953, 0.015524904243648052, + 0.02827305719256401, -0.01118477527052164, -0.00269119068980217, + 0.019258346408605576, -0.06276852637529373, -0.029486428946256638, + 0.03668108582496643, -0.06087847054004669, -0.021941760554909706, + -0.03342209756374359, 0.037871118634939194, -0.014202643185853958, + -0.002006725873798132, 0.07768674194812775, -0.018791666254401207, + -0.022750671952962875, -0.011425893753767014, 0.01854277029633522, + -0.026235220953822136, -0.041231222450733185, 0.012017021887004375, + 0.03198316693305969, -0.03062201477587223, -0.02393293008208275, + -0.009224717505276203, -0.02770526520907879, -0.025216301903128624, + 0.06107291951775551, 0.003430101554840803, -0.028747517615556717, + -0.013035940937697887, -0.012009243480861187, 0.0052034868858754635, + 0.0017578297993168235, 0.039854515343904495, 0.03901448845863342, + -0.012864825315773487, -0.02987532690167427, -0.027619706466794014, + -0.038158904761075974, -0.017368290573358536, 0.04195457324385643, + 0.08762703835964203, 0.03662663698196411, -0.019266124814748764, + 0.0031189811415970325, -0.04515133425593376, -0.012195915915071964, + 0.010196967981755733, -0.022245101630687714, -0.05293712019920349, + -0.001345595344901085, 0.020075038075447083, 0.02769748494029045, + -0.0164349302649498, 0.027339696884155273, -0.0029400868806988, + -0.03755221888422966, 0.011254777200520039, 0.010725872591137886, + 0.008291356265544891, 0.0013844853965565562, -0.007311326917260885, + -0.0008711368427611887, 0.005172375123947859, 0.05788393318653107, + -0.015532681718468666, 0.03155537694692612, -0.5350958108901978, + 0.005615721456706524, 0.03021756000816822, 0.00598128791898489, + 0.047414734959602356, -0.05283600836992264, 0.041231222450733185, + 0.01253814809024334, -0.02467184141278267, -0.03131425753235817, + 0.019903922453522682, 0.0127870449796319, 0.034487687051296234, + 0.028234168887138367, -0.009108047001063824, 0.024228494614362717, + -0.018698330968618393, 0.04494910687208176, -0.014887107536196709, + 0.02439183183014393, -0.01256148237735033, -0.025340748950839043, + -0.013432620093226433, 0.031399816274642944, -0.05150596797466278, + 0.012499258853495121, 0.06501636654138565, 0.025885211303830147, + -0.03263652324676514, 0.03205316886305809, -0.11994465440511703, + -0.022525111213326454, 0.02953309379518032, 0.02495962753891945, + -0.001703383750282228, -0.02562853693962097, -0.006650196388363838, + -0.010593647137284279, 0.0019678359385579824, -0.006984651554375887, + 0.03988562524318695, 0.019157234579324722, -0.03468991443514824, + -0.03913893550634384, 0.007435775361955166, 0.021739531308412552, + -0.02215954288840294, 0.00023334022262133658, 0.0072335475124418736, + 0.007435775361955166, 0.001571157481521368, 0.01637270487844944, + -0.0014933774946257472, 0.008128018118441105, -0.010632536374032497, + -0.03667330741882324, 0.01813831366598606, 0.034378793090581894, + 0.0035934397019445896, 0.0032200952991843224, 0.0061446260660886765, + 0.006525748409330845, -0.06574749946594238, -0.038151130080223083, + -0.011488117277622223, -0.030147558078169823, -0.08026126027107239, + -0.02649189531803131, -0.04459131881594658, -0.029851993545889854, + 0.016956057399511337, 0.009325831197202206, 0.013689294457435608, + -0.030139779672026634, 0.03228650987148285, -0.0043556843884289265, + 0.023847371339797974, -0.10854987800121307, 0.008586919866502285, + -0.011721457354724407, 0.04104454815387726, -0.06776978075504303, + 0.03004644252359867, -0.03847780451178551, -0.026764124631881714, + -0.04989591985940933, 0.04401574656367302, -0.0460146926343441, + -0.0022245103027671576, -0.0211250688880682, -0.07163545489311218, + 0.012491480447351933, -0.004977925214916468, 0.00010889210534514859, + -0.01981058530509472, -0.05246266350150108, -0.016691604629158974, + 0.03320431336760521, -0.0081824641674757, -0.0058179497718811035, + -0.02873195894062519, 0.014568207785487175, -0.011091439053416252, + -0.04647359624505043, -0.006300186738371849, -0.02443072386085987, + -0.03892115131020546, -0.008859150111675262, 0.026258554309606552, + 0.00787134375423193, -0.028607511892914772, -0.02695857547223568, + -0.007490221876651049, 0.01284926850348711, -0.03438657149672508, + 0.04619358852505684, -0.014474872499704361, 0.02334180288016796, + 0.006665752734988928, -0.029463093727827072, 0.018293874338269234, + -0.034254346042871475, -0.007311326917260885, -0.03447213023900986, + -0.004806808661669493, -0.03904559835791588, -0.023940708488225937, + 0.013728183694183826, 0.020557275041937828, -0.0432768352329731, + -0.05826505646109581, 0.025791874155402184, 0.010578090324997902, + 0.00437901820987463, -0.01825498417019844, 0.034767694771289825, + 0.026826348155736923, 0.022112876176834106, 0.004254570230841637, + 0.014093750156462193, 0.0049701472744345665, 0.01739940233528614, + 0.006463524419814348, 0.06456524133682251, 0.009730287827551365, + 0.005825727712363005, -0.018457211554050446, 0.0001866721868282184, + -0.005887951701879501, -0.03931005299091339, 0.030015332624316216, + -0.017889417707920074, -0.034425463527441025, -0.03558438643813133, + 0.018962783738970757, -0.029890885576605797, 0.013098164461553097, + 0.014871550723910332, -0.032815415412187576, -0.0028934190049767494, + 0.05481162294745445, -0.01762496493756771, 0.0022011762484908104, + 0.03169538080692291, -0.009123602882027626, 0.010998102836310863, + 0.042390141636133194, 0.006160182412713766, 0.02678745985031128, + 0.012281473726034164, -0.016893833875656128, -0.0006844646413810551, + 0.052851561456918716, -0.018986117094755173, 0.020230598747730255, + 0.04578135535120964, 0.008151352405548096, 0.027977492660284042, + -0.024578504264354706, -0.02076728083193302, -0.017531629651784897, + 0.011527007445693016, 0.0008711368427611887, -0.025846319273114204, + 0.004386796150356531, 0.04838698357343674, -0.008423582650721073, + -0.005374603439122438, 0.004410130437463522, -0.014599321410059929, + 0.007692449726164341, -0.00984695740044117, 0.016279369592666626, + -0.03129870444536209, -0.021591750904917717, -0.008485806174576283, + -0.030482012778520584, -0.016341593116521835, 0.026172997429966927, + 0.016932722181081772, -0.024111824110150337, 0.0011200332082808018, + -0.009551393799483776, -0.0358099490404129, 0.010896989144384861, + -0.047080282121896744, -0.007357995491474867, 0.00798801425844431, + 0.000575572601519525, 0.016046030446887016, -0.00489236693829298, + -0.10168967396020889, 0.02358292043209076, 0.0017811637371778488, + -0.02100062184035778, 0.014350424520671368, -0.05210487172007561, + 0.028871964663267136, 0.009131381288170815, -0.015066001564264297, + -0.004589024931192398, 0.039691176265478134, -0.0756644606590271, + 0.04063231125473976, 0.004775696899741888, -0.02403404377400875, + 0.01830943115055561, -0.004013451747596264, 0.01505044475197792, + 0.015066001564264297, -0.02248622104525566, 0.05625055357813835, + -0.010313638485968113, 0.04159678518772125, -0.0014467095024883747, + -0.02043282613158226, -0.008820260874927044, 0.020572829991579056, + 0.015447122976183891, 0.0064324126578867435, 0.06067623943090439, + -0.04028230160474777, 0.02981310337781906, 0.004526800476014614, + 0.0035623274743556976, -0.01305149681866169, -0.010313638485968113, + 0.01038364041596651, 0.030474234372377396, -0.018986117094755173, + 0.005584609694778919, -0.013261503539979458, 0.015509347431361675, + 0.04229680448770523, -0.01998170092701912, -0.028716403990983963, + -0.006556860636919737, -0.04452909529209137, -0.05336491018533707, + -0.03118981048464775, 0.05527830123901367, -0.006113514304161072, + -0.011744791641831398, 0.0033523214515298605, -0.010391417890787125, + 0.018970560282468796, 0.019266124814748764, -0.03332098573446274, + 0.013152611441910267, -0.030808690935373306, -0.015641573816537857, + -0.0258152075111866, -0.007357995491474867, -0.005989065859466791, + 0.02151397056877613, -0.010461420752108097, 0.023668477311730385, + 0.014124861918389797, 0.014319312758743763, 0.020176151767373085, + -0.023334022611379623, 0.01225036196410656, -0.04085009917616844, + -0.01138700358569622, -0.01356484554708004, -0.011239221319556236, + -0.02076728083193302, 0.007101321592926979, -0.030233116820454597, + 0.007505777757614851, 0.03353099152445793, 0.01745384931564331, + 0.00538238137960434, 0.026141883805394173, -0.03178093954920769, + 0.01402374729514122, 0.03107313998043537, 0.03692220151424408, + -0.03624551743268967, -0.008081350475549698, 0.026414114981889725, + -0.03165648877620697, -0.031500931829214096, -0.005716835614293814, + 0.05883285030722618, 0.06266740709543228, 0.00887470692396164, + 0.008913597092032433, -0.0015089336084201932, -0.005623499397188425, + 0.029377536848187447, 0.003305653342977166, 0.023995155468583107, + -0.019787251949310303, -0.001804497791454196, 0.0464969277381897, + -0.009528059512376785, 0.02586965449154377, 0.013572623021900654, + -0.09108047187328339, 0.0117603475227952, -0.003056757152080536, + -0.016092699021100998, -0.0019211678300052881, 0.033056534826755524, + 0.028241947293281555, 0.026188552379608154, -0.020518384873867035, + -0.02048727311193943, -0.0033134312834590673, -0.014474872499704361, + 0.01888500340282917, -0.028514178469777107, 0.0027689707931131124, + 0.00927138514816761, -0.024119602516293526, -0.005856839939951897, + -0.017267178744077682, 0.0059268418699502945, 0.04030563682317734, + -0.015556016005575657, 0.021249517798423767, -0.017407182604074478, + 0.01837943308055401, 0.00439457455649972 + ] + }, + "food": { + "prompt": "Photo of delicious looking food", + "vector": [ + -0.03325869143009186, 0.019341137260198593, -0.013895140029489994, + -0.03983273357152939, -0.03497690334916115, 0.013626201078295708, + 0.01961754634976387, -0.015852412208914757, -0.01781715452671051, + -0.032451875507831573, 0.02271033637225628, 0.03895868360996246, + 0.0020319772884249687, 0.021567348390817642, 0.0059166401624679565, + 0.5388176441192627, -0.009412836283445358, 0.0056551722809672356, + -0.003092789091169834, -0.05278659239411354, 0.026774289086461067, + -0.007037215866148472, 0.01334232185035944, -0.018474560230970383, + -0.022807452827692032, -0.0056551722809672356, -0.03127153590321541, + 0.024988839402794838, 0.009196192026138306, 0.0060585797764360905, + -0.014388193376362324, -0.006036167964339256, 0.041065365076065063, + -0.011175875551998615, -0.030292898416519165, 0.0198715440928936, + 0.006290165241807699, -0.0056925248354673386, 0.016285700723528862, + 0.0009114016429521143, 0.05141201615333557, -0.029986606910824776, + -0.01359631959348917, 0.022740216925740242, -0.039907436817884445, + -0.04615277796983719, -0.021769052371382713, 0.0008964606677182019, + -0.02143287844955921, -0.023248212411999702, 0.038293808698654175, + 0.016278231516480446, 0.01688334159553051, -0.0026819114573299885, + -0.03274322301149368, 0.010608118027448654, -0.04260428994894028, + -0.010877056047320366, -0.005221882835030556, 0.05776941776275635, + -0.016225937753915787, -0.0020618592388927937, -0.0018526853527873755, + -0.025810595601797104, -0.08053204417228699, 0.03278804570436478, + 0.016659226268529892, -0.014141666702926159, 0.058658406138420105, + 0.038801804184913635, 0.04038555175065994, -0.02264310047030449, + 0.006320047192275524, -0.015307065099477768, 0.005162118934094906, + 0.015986880287528038, -0.046683184802532196, 0.005931580904871225, + 0.01092934887856245, 0.0175482165068388, -0.01203498337417841, + -0.02484690025448799, 0.02037953771650791, 0.04440468177199364, + -0.010764997452497482, 0.02353956177830696, -0.003369197715073824, + 0.02812645211815834, -0.003204846754670143, -0.02384585328400135, + 0.016173643991351128, 0.05445251241326332, -0.030449779704213142, + 0.019453195855021477, 0.002510089660063386, -0.013842846266925335, + -0.11064565181732178, 0.017436157912015915, -0.015172596089541912, + -0.010899467393755913, -0.019371019676327705, -0.006260283291339874, + 0.022531043738126755, 0.07672955840826035, 0.009509953670203686, + 0.010451236739754677, -0.0635366439819336, 0.02775292657315731, + 0.007963558658957481, 0.008501434698700905, -0.006006286013871431, + -0.008972076699137688, 0.10791891813278198, -0.06624843925237656, + 0.012453332543373108, -0.021500114351511, 0.00781414844095707, + 0.005924110766500235, -0.05336181819438934, 0.0006275224150158465, + -0.019079670310020447, -0.021888580173254013, 0.01606905646622181, + -0.02204546146094799, -0.013730787672102451, 0.03783810883760452, + 0.06955787539482117, -0.004198423586785793, 0.002838792046532035, + 0.00573734799399972, -0.014881246723234653, 0.009076663292944431, + 0.007784266024827957, 0.01649487391114235, -0.014156606048345566, + -0.02323327027261257, 0.008352024480700493, 0.026975994929671288, + 0.008172732777893543, 0.019796838983893394, -0.009495011530816555, + -0.007067097816616297, -0.0028238508384674788, -0.04082631319761276, + 0.004952944815158844, 0.005550585221499205, 0.0071791550144553185, + 0.022822393104434013, -0.018713615834712982, -0.018907848745584488, + -0.012879150919616222, 0.044188037514686584, 0.054788682609796524, + -0.021507585421204567, 0.007433152757585049, -0.0055729965679347515, + 0.02680417336523533, -0.005162118934094906, 0.020237598568201065, + -0.01447036862373352, 0.015045597217977047, 0.008202614262700081, + 0.03884662687778473, 0.03740482032299042, 0.02749892883002758, + -0.08252666890621185, -0.020626064389944077, -0.5434120297431946, + 0.004355304408818483, 0.022000636905431747, 0.013252676464617252, + 0.06556862592697144, 0.007866442203521729, 0.005595408380031586, + 0.004317952319979668, 0.003795016324147582, -0.008150320500135422, + 0.03689682483673096, 0.001180339721031487, 0.0028238508384674788, + -0.00079187355004251, 0.027416754513978958, 0.0027342047542333603, + -0.002689381828531623, -0.0005154648097231984, 0.033213865011930466, + -0.04651883617043495, -0.02916485257446766, 0.0355297215282917, + -0.0036381359677761793, -0.013118207454681396, -0.06837005913257599, + -0.04045278578996658, -0.01098164264112711, -0.002308386145159602, + -0.041267070919275284, -0.0024577961303293705, 0.030001547187566757, + 0.019341137260198593, 0.025960005819797516, 0.03824898600578308, + -0.003966838121414185, 0.01657705195248127, -0.00017929212481249124, + 0.01197521947324276, 0.023569442331790924, 0.045973487198352814, + -0.019333666190505028, 0.02383091114461422, -0.034999314695596695, + -0.002046918496489525, 0.004452420864254236, 0.005259235389530659, + 0.00938295479863882, -0.015971940010786057, 0.011459754779934883, + -0.07136573642492294, -0.00008217555296141654, 0.003354256972670555, + 0.011960278265178204, -0.006925158202648163, 0.022015579044818878, + -0.00782161857932806, -0.005565526429563761, 0.0022785039618611336, + -0.02211269550025463, 0.027215048670768738, -0.030121076852083206, + -0.002756616333499551, -0.05776941776275635, -0.034961964935064316, + 0.05590179190039635, -0.01955031231045723, -0.051270075142383575, + -0.029695259407162666, 0.004639183636754751, -0.016539698466658592, + -0.011616635136306286, 0.015165125951170921, -0.012124629691243172, + -0.01593458652496338, -0.003697899868711829, -0.024772195145487785, + 0.039907436817884445, 0.019206669181585312, 0.0040639545768499374, + 0.03631412610411644, -0.015419123694300652, -0.03551478311419487, + 0.01601676270365715, -0.00938295479863882, -0.02476472407579422, + -0.014948480762541294, 0.05316758155822754, -0.031839292496442795, + -0.042066413909196854, -0.00469147739931941, 0.006529221311211586, + 0.005535644479095936, -0.0032870222348719835, -0.001830273657105863, + -0.0017032751347869635, -0.006387282162904739, 0.010794879868626595, + -0.020954767242074013, -0.015419123694300652, 0.011960278265178204, + -0.01377561129629612, 0.014276135712862015, -0.028201157227158546, + -0.002046918496489525, -0.00322725810110569, 0.015874823555350304, + 0.021798934787511826, -0.010705234482884407, -0.0036754885222762823, + 0.007194096688181162, 0.0313163585960865, 0.003062907140702009, + -0.015314536169171333, 0.02692370116710663, 0.042962875217199326, + 0.00938295479863882, -0.04484543949365616, 0.046578601002693176, + 0.026236414909362793, -0.018355030566453934, -0.00047811231343075633, + 0.019214140251278877, -0.00611834367737174, -0.0389288030564785, + -0.002793968887999654, -0.012931443750858307, -0.010107593610882759, + -0.0041909534484148026, 0.011840750463306904, -0.0026968521997332573, + -0.031346239149570465, 0.03468555584549904, 0.003197376150637865, + 0.023547032848000526, 0.0007246389868669212, 0.028118979185819626, + 0.007134332321584225, 0.017346512526273727, -0.01574782468378544, + 0.027827631682157516, -0.002913497155532241, 0.0016360405134037137, + -0.011198286898434162, 0.023255683481693268, -0.006745866034179926, + 0.032481756061315536, 0.018355030566453934, 0.016173643991351128, + -0.013140617869794369, -0.05315264314413071, -0.0056103491224348545, + -0.06217701733112335, -0.008479023352265358, -0.043373752385377884, + 0.02527271769940853, -0.039264973253011703, 0.048737574368715286, + -0.016517287120223045, -0.0036007834132760763, -0.008411789312958717, + -0.0382564552128315, 0.017660275101661682, 0.015703001990914345, + -0.0026072063483297825, -0.004138659685850143, -0.01969972252845764, + 0.08213820308446884, -0.03620953857898712, -0.012998678721487522, + -0.011594223789870739, 0.01292397454380989, -0.05907675623893738, + 0.009218603372573853, -0.02578071318566799, 0.020521478727459908, + 0.032758165150880814, -0.010764997452497482, 0.009315719828009605, + -0.017129868268966675, 0.0033841386903077364, -0.011280463077127934, + -0.04391910135746002, 0.025534186512231827, -0.006880335509777069, + 0.007440623361617327, -0.019468136131763458, 0.025713477283716202, + -0.012460802681744099, -0.0057672299444675446, 0.050388555973768234, + -0.0032571402844041586, 0.012251628562808037, -0.0466383621096611, + -0.005998815875500441, 0.0018078621942549944, -0.011116111651062965, + 0.004960415419191122, 0.019610077142715454, -0.006842982489615679, + -0.006566573400050402, 0.01104887668043375, 0.0034588437993079424, + 0.015486356802284718, -0.010137475095689297, -0.005094884429126978, + -0.014007197692990303, -0.01934860832989216, 0.029493553563952446, + -0.05791882425546646, 0.01922907866537571, -0.0032944928389042616, + -0.09949218481779099, -0.02040194906294346, -0.010144946165382862, + -0.022770099341869354, 0.0027192640118300915, -0.012849269434809685, + 0.05047820508480072, 0.032011114060878754, -0.008359495550394058, + -0.010578235611319542, 0.02046918496489525, 0.04862551763653755, + -0.045801665633916855, 0.03130141645669937, 0.03137611970305443, + -0.019811779260635376, 0.02223222330212593, -0.017757391557097435, + 0.01899002306163311, 0.025115838274359703, -0.009599599055945873, + 0.07309142500162125, 0.01588229462504387, 0.008404318243265152, + 0.034775201231241226, -0.027760395780205727, 0.004056484438478947, + -0.0008217555587179959, 0.005326470360159874, 0.0442926250398159, + 0.007119391579180956, -0.03139106184244156, -0.022441396489739418, + 0.008635904639959335, -0.005498291924595833, 0.00540117546916008, + 0.0027491459622979164, -0.02805921621620655, -0.033333394676446915, + -0.03194388002157211, -0.017249396070837975, 0.004758711438626051, + 0.07516822218894958, -0.009278367273509502, 0.020715711638331413, + -0.019953718408942223, 0.01878831908106804, 0.014313487336039543, + -0.022740216925740242, 0.0008740490884520113, 0.019707193598151207, + 0.01625581830739975, -0.0039967200718820095, 0.04726588353514671, + -0.0011653987457975745, -0.015590944327414036, -0.01895267143845558, + 0.02831321209669113, 0.006222930736839771, 0.04343351721763611, + -0.001942331320606172, 0.005498291924595833, -0.022822393104434013, + -0.017503393813967705, 0.00837443582713604, -0.0072239781729876995, + 0.016412699595093727, -0.008366965688765049, 0.027028288692235947, + 0.03430455923080444, 0.025145720690488815, 0.021320821717381477, + -0.09243255853652954, 0.00502764992415905, -0.07131344079971313, + 0.009778891690075397, -0.02291204035282135, -0.00046317133819684386, + -0.004325422458350658, 0.01776486076414585, -0.018743496388196945, + -0.02123117446899414, -0.02457796223461628, -0.03160770609974861, + -0.012879150919616222, -0.027640867978334427, 0.010189768858253956, + 0.06280454248189926, -0.0057224067859351635, 0.010839702561497688, + -0.02601976878941059, 0.00540117546916008, -0.006207989528775215, + 0.05945027619600296, 0.0041909534484148026, -0.02009565941989422, + 0.02253851294517517, 0.015755295753479004, -0.028597094118595123, + -0.004796063993126154, 0.03963850066065788, -0.021036941558122635, + 0.00008217555296141654, -0.0003735252539627254, 0.02180640399456024, + -0.044240329414606094, -0.1282835155725479, -0.027775337919592857, + -0.13690447807312012, 0.026587529107928276, 0.003234728705137968, + -0.00243538455106318, -0.026064591482281685, 0.04545802250504494, + -0.017085043713450432, 0.026161709800362587, -0.005035120528191328, + -0.016076527535915375, -0.020274950191378593, -0.008142850361764431, + 0.016860930249094963, -0.025332482531666756, 0.0396459698677063, + -0.047856055200099945, 0.012565389275550842, 0.0017854507314041257, + -0.010712703689932823, -0.03399079665541649, -0.009808773174881935, + 0.018810732290148735, -0.016412699595093727, -0.0073808589950203896, + 0.0021515055559575558, -0.045577552169561386 + ] + }, + "pets": { + "prompt": "Photo of cute pets", + "vector": [ + -0.018412098288536072, 0.015965646132826805, -0.006039677653461695, + -0.006294516380876303, 0.0009683871758170426, 0.0048355646431446075, + -0.01478064525872469, 0.0024400807451456785, 0.019100161269307137, + -0.009696613065898418, 0.016220485791563988, 0.0063645970076322556, + -0.07371847331523895, -0.009652016684412956, -0.012397903949022293, + 0.5433799028396606, -0.030102822929620743, -0.007033549249172211, + 0.0409589558839798, -0.03465806692838669, 0.02250226028263569, + 0.04019443690776825, 0.03446056693792343, -0.03670952096581459, + 0.011556935496628284, -0.05243943631649017, -0.019686290994286537, + 0.023438790813088417, 0.029172662645578384, 0.004740000236779451, + 0.007995565421879292, -0.03016653284430504, 0.01606758125126362, + 0.015060968697071075, 0.006772339344024658, -0.04906919598579407, + -0.004141129087656736, -0.006090645678341389, -0.016711048781871796, + 0.004548871424049139, 0.05233750119805336, -0.028446370735764503, + -0.00853709690272808, -0.039780326187610626, -0.017061451449990273, + -0.024241533130407333, 0.01281838770955801, 0.004765484016388655, + 0.025745080783963203, -0.011263871565461159, 0.061562661081552505, + 0.016252340748906136, 0.002796855056658387, -0.0019240323454141617, + 0.03179113194346428, 0.022610565647482872, -0.04005427658557892, + -0.02130451612174511, 0.0006753226043656468, -0.01755838841199875, + -0.030956534668803215, 0.019405968487262726, -0.019246693700551987, + 0.04778863117098808, -0.049668069928884506, 0.02699379250407219, + 0.0064537907019257545, 0.04073597118258476, 0.015022742561995983, + 0.01813814602792263, 0.002898790407925844, 0.044456616044044495, + -0.003644193522632122, -0.025394678115844727, -0.0014079839456826448, + -0.006358225829899311, -0.09420750290155411, 0.002554758219048381, + -0.01680024154484272, 0.008728226646780968, 0.0413985475897789, + 0.0026120967231690884, 0.019533388316631317, 0.009550080634653568, + -0.029631372541189194, 0.02370637282729149, -0.0016118548810482025, + 0.014251855202019215, -0.03925790637731552, -0.01651354879140854, + 0.027656372636556625, 0.013888711109757423, -0.004982097074389458, + 0.029771532863378525, -0.03363233804702759, 0.00021661292703356594, + -0.07534944266080856, -0.01572354882955551, 0.0008154839160852134, + -0.036212582141160965, -0.0546119399368763, 0.018373873084783554, + -0.015678953379392624, 0.03827040269970894, -0.006937984377145767, + -0.021221693605184555, -0.06097016483545303, 0.005115887615829706, + 0.003408468095585704, -0.013156048953533173, 0.002592983888462186, + -0.009314354509115219, 0.10348363220691681, -0.01737362891435623, + -0.010684113018214703, -0.00946088694036007, -0.014194516465067863, + 0.0004905645619146526, 0.010199920274317265, -0.0007772581302560866, + -0.009989677928388119, -0.02426701784133911, 0.06381798535585403, + -0.009492741897702217, 0.02280169539153576, -0.003121774410828948, + 0.0017456453060731292, 0.00788088794797659, 0.028548305854201317, + 0.02487863041460514, 0.024279760196805, 0.050165001302957535, + 0.006613065022975206, -0.007555968128144741, -0.024540970101952553, + -0.03653113171458244, -0.02502516098320484, 0.011346694082021713, + -0.007849032990634441, 0.007097258232533932, -0.0324983075261116, + -0.0029242741875350475, 0.02594895288348198, 0.0070526618510484695, + 0.004994838964194059, 0.020590970292687416, 0.021954355761408806, + 0.024916857481002808, -0.0013251613127067685, -0.00966475810855627, + 0.01769217848777771, -0.008530725724995136, -0.007262903265655041, + -0.032918792217969894, -0.00833322573453188, -0.020043065771460533, + -0.009702984243631363, 0.0010766936466097832, 0.015156532637774944, + -0.009104114025831223, -0.058128710836172104, 0.03610427677631378, + 0.005963225848972797, 0.02354072593152523, 0.03976758196949959, + 0.02101145125925541, -0.00396911334246397, -0.5477248430252075, + 0.0277455672621727, -0.005084032658487558, -0.017813226208090782, + 0.06590129435062408, 0.015819113701581955, 0.03328193724155426, + 0.029057985171675682, 0.05862564593553543, -0.004217580892145634, + 0.0317656472325325, 0.003930887207388878, -0.027325082570314407, + -0.01633516140282154, 0.013882339932024479, 0.027847502380609512, + 0.005950483959168196, -0.000700806500390172, 0.003962742164731026, + 0.03163185715675354, 0.007619677577167749, -0.018787983804941177, + -0.0214319359511137, 0.007817178033292294, -0.0603075847029686, + -0.0012805645819753408, -0.012984032742679119, -0.022565970197319984, + -0.03361322730779648, 0.014239112846553326, 0.010633145458996296, + 0.02683451771736145, 0.007785323075950146, 0.061683714389801025, + -0.021183468401432037, -0.012461613863706589, -0.021323630586266518, + -0.028860483318567276, 0.01320701651275158, -0.018214598298072815, + 0.028707580640912056, -0.042660001665353775, 0.020329760387539864, + -0.0006753226043656468, -0.010849758982658386, 0.005351613275706768, + -0.04668645188212395, -0.014258225448429585, 0.01813177578151226, + -0.04093347117304802, -0.03286782279610634, 0.017144273966550827, + -0.00764516182243824, 0.012391532771289349, 0.04016895592212677, + -0.0038990324828773737, 0.007237419486045837, -0.02127903327345848, + -0.01976911351084709, 0.008129355497658253, 0.021846048533916473, + -0.011652500368654728, -0.07947145402431488, -0.0050521777011454105, + 0.00004459677802515216, -0.022846290841698647, -0.06710540503263474, + -0.006294516380876303, -0.015405001118779182, -0.030497824773192406, + -0.013385403901338577, 0.037703387439250946, -0.032326292246580124, + -0.012041130103170872, -0.03310991823673248, -0.015303065069019794, + 0.002389112953096628, -0.03407830744981766, -0.007345725782215595, + 0.0293892752379179, -0.03459435701370239, -0.08546654134988785, + -0.006415564566850662, 0.013009516522288322, -0.04607484117150307, + -0.010849758982658386, 0.0468011312186718, -0.013308952562510967, + -0.005848548375070095, -0.010868871584534645, -0.049559760838747025, + -0.007466774433851242, 0.020801210775971413, -0.012920322827994823, + -0.00475911283865571, 0.02626113034784794, 0.020438065752387047, + 0.006396451964974403, 0.026509597897529602, 0.03451153263449669, + -0.0045934682711958885, 0.0024400807451456785, 0.011671612970530987, + -0.012251371517777443, 0.0025675001088529825, -0.010467500425875187, + 0.01026363018900156, 0.05339508131146431, 0.030045485123991966, + 0.04553968086838722, 0.025088870897889137, -0.0013633872149512172, + -0.042672742158174515, 0.015596129931509495, -0.012404275126755238, + -0.01755201630294323, -0.02296096831560135, 0.030472340062260628, + 0.029886210337281227, -0.0198200810700655, 0.04711968079209328, + 0.006218065042048693, -0.004077419172972441, -0.06715000420808792, + 0.007606935687363148, -0.00536435516551137, 0.029344677925109863, + 0.009938710369169712, 0.008581694215536118, 0.012780161574482918, + 0.02591072767972946, 0.016889436170458794, -0.04043653607368469, + -0.02471298538148403, -0.0017265323549509048, 0.003605967853218317, + 0.02171863056719303, 0.02534371055662632, -0.014455726370215416, + 0.0554911345243454, -0.01784508116543293, 0.0418381467461586, + -0.004599838983267546, 0.04076782613992691, 0.03344758227467537, + 0.04495355114340782, -0.00823766179382801, 0.011805403977632523, + -0.023285888135433197, -0.05006306618452072, 0.0018539517186582088, + -0.05221645534038544, -0.01827193610370159, 0.019278548657894135, + 0.03916871175169945, -0.002223467919975519, 0.016112178564071655, + -0.07009975612163544, -0.023763710632920265, -0.000860080705024302, + -0.011862742714583874, 0.014143547974526882, 0.024993307888507843, + -0.010008790530264378, -0.001688306569121778, -0.010633145458996296, + 0.0553446002304554, -0.007396693807095289, 0.024228790774941444, + -0.02114524319767952, -0.02173137106001377, 0.02101145125925541, + 0.023808307945728302, -0.01453854888677597, 0.00426854845136404, + -0.010798790492117405, -0.013079597614705563, -0.005001210141927004, + 0.0015991129912436008, 0.015131048858165741, -0.013920566067099571, + 0.03167008236050606, 0.00898943655192852, -0.011130081489682198, + 0.0063263713382184505, -0.02939564548432827, -0.053458791226148605, + 0.03219250217080116, 0.011875484138727188, 0.02474484033882618, + -0.04773129150271416, -0.033078063279390335, 0.04755927622318268, + 0.014825242571532726, 0.00277137104421854, -0.028102342039346695, + 0.00401371018961072, 0.039347097277641296, 0.032033227384090424, + -0.005969597026705742, -0.016711048781871796, -0.004899274557828903, + 0.02397395297884941, 0.015264839865267277, -0.02385290339589119, + 0.03400822728872299, -0.027452502399683, 0.02171863056719303, + -0.06312992423772812, 0.01647532358765602, -0.013977904804050922, + -0.05996992439031601, -0.004567984025925398, -0.00021661292703356594, + 0.010301855392754078, -0.007078145164996386, -0.024470888078212738, + 0.030676212161779404, 0.009957822971045971, -0.0411054864525795, + -0.033065322786569595, 0.035607341676950455, 0.010894355364143848, + -0.07190274447202682, -0.03331379219889641, 0.022540485486388206, + 0.008887500502169132, 0.005485403351485729, -0.01666645146906376, + -0.0017010484589263797, 0.038002822548151016, -0.028733065351843834, + -0.07052024453878403, 0.033078063279390335, -0.0019877420272678137, + -0.005663790740072727, -0.027675487101078033, 0.024407178163528442, + 0.02864387072622776, 0.009473629295825958, -0.043851371854543686, + 0.05092315003275871, 0.015398629941046238, -0.02924911305308342, + -0.023126613348722458, 0.03863992169499397, -0.00818669330328703, + 0.00558733893558383, 0.02818516455590725, -0.02622290514409542, + -0.02606363035738468, -0.011894597671926022, -0.01845669560134411, + 0.05558669567108154, -0.06443597376346588, 0.07635605335235596, + 0.010945322923362255, 0.028134196996688843, -0.016163146123290062, + -0.005517258308827877, 0.02263605035841465, 0.011773549020290375, + 0.0020896773785352707, -0.02352161332964897, 0.03303983807563782, + 0.008970323018729687, -0.025076130405068398, -0.04618314653635025, + -0.015252097509801388, -0.010958065278828144, 0.03614887222647667, + -0.018647823482751846, 0.02625476010143757, -0.03058064728975296, + 0.015080081298947334, 0.023171210661530495, -0.032587502151727676, + 0.03588129207491875, -0.019527016207575798, 0.009014920331537724, + -0.0513245165348053, 0.040130726993083954, 0.010212661698460579, + -0.07171798497438431, 0.04889081045985222, -0.018832581117749214, + 0.017915163189172745, -0.010187177918851376, -0.039359841495752335, + -0.05723040550947189, -0.022871773689985275, -0.017335403710603714, + -0.0351995974779129, -0.023725485429167747, -0.027255002409219742, + -0.01102177519351244, 0.019858308136463165, 0.02341330796480179, + 0.02624838799238205, 0.054662905633449554, -0.009282500483095646, + -0.020622823387384415, 0.04590919613838196, -0.05874669924378395, + 0.008250403217971325, 0.019450565800070763, -0.015347662381827831, + 0.01154419407248497, 0.011518710292875767, 0.013614758849143982, + 0.023158468306064606, 0.015226613730192184, -0.03371516242623329, + -0.029478468000888824, 0.018647823482751846, 0.010633145458996296, + 0.0005479032406583428, -0.025146210566163063, -0.03495113179087639, + -0.07967532426118851, 0.01184362918138504, 0.015245726332068443, + 0.006307258270680904, 0.02591709792613983, 0.023298630490899086, + 0.021533871069550514, 0.006549355108290911, -0.010798790492117405, + 0.011047258973121643, -0.019527016207575798, -0.0348237119615078, + -0.009620161727070808, -0.009913226589560509, -0.016322419047355652, + -0.015564274974167347, -0.009199677966535091, -0.023336855694651604, + -0.026057260110974312, -0.0160293560475111, -0.02277621068060398, + 0.005351613275706768, 0.022438550367951393, 0.003586854785680771, + 0.01966717839241028, -0.05842814967036247 + ] + } + } + } +} diff --git a/mobile/apps/photos/fastlane/metadata/android/id/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/id/full_description.txt index 7b3deb7985..1f79440d32 100644 --- a/mobile/apps/photos/fastlane/metadata/android/id/full_description.txt +++ b/mobile/apps/photos/fastlane/metadata/android/id/full_description.txt @@ -16,7 +16,7 @@ FITUR - Collaborative albums, so you can pool together photos after a trip - Shared folders, in case you want your partner to enjoy your "Camera" clicks - Link album, yang bisa dilindungi dengan sandi -- Ability to free up space, by removing files that have been safely backed up +Kemampuan untuk membebaskan kapasitas, dengan menghilangkan files yang sudah di back-up dengan aman - Human support, because you're worth it - Descriptions, so you can caption your memories and find them easily - Editor gambar, untuk menyempurnakan fotomu @@ -33,4 +33,4 @@ HARGA Kami tidak menyediakan paket yang gratis seumur hidup, karena penting bagi kami untuk tetap berdiri dan bertahan hingga masa depan. Namun, kami menyediakan paket yang terjangkau, yang bisa kamu bagikan dengan keluargamu. Kamu bisa menemukan informasi lebih lanjut di ente.io. DUKUNGAN -We take pride in offering human support. Jika kamu adalah pelanggan berbayar, kamu bisa menghubungi team@ente.io dan menunggu balasan dari tim kami dalam 24 jam. +Kita bangga untuk menyediakan support berbasis manusia. Jika kamu adalah pelanggan berbayar, kamu bisa menghubungi team@ente.io dan menunggu balasan dari tim kami dalam 24 jam. diff --git a/mobile/apps/photos/ios/Podfile.lock b/mobile/apps/photos/ios/Podfile.lock index a152ef181c..3a8b2d74df 100644 --- a/mobile/apps/photos/ios/Podfile.lock +++ b/mobile/apps/photos/ios/Podfile.lock @@ -12,6 +12,8 @@ PODS: - Flutter - device_info_plus (0.0.1): - Flutter + - emoji_picker_flutter (0.0.1): + - Flutter - ffmpeg_kit_custom (6.0.3) - ffmpeg_kit_flutter (6.0.3): - ffmpeg_kit_custom @@ -179,6 +181,8 @@ PODS: - PromisesObjC (2.4.0) - receive_sharing_intent (1.8.1): - Flutter + - rust_lib_photos (0.0.1): + - Flutter - SDWebImage (5.21.1): - SDWebImage/Core (= 5.21.1) - SDWebImage/Core (5.21.1) @@ -228,6 +232,8 @@ PODS: - Flutter - url_launcher_ios (0.0.1): - Flutter + - vibration (1.7.5): + - Flutter - video_player_avfoundation (0.0.1): - Flutter - FlutterMacOS @@ -248,6 +254,7 @@ DEPENDENCIES: - cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`) - dart_ui_isolate (from `.symlinks/plugins/dart_ui_isolate/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) + - emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`) - ffmpeg_kit_flutter (from `.symlinks/plugins/ffmpeg_kit_flutter/ios`) - file_saver (from `.symlinks/plugins/file_saver/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) @@ -294,6 +301,7 @@ DEPENDENCIES: - thermal (from `.symlinks/plugins/thermal/ios`) - ua_client_hints (from `.symlinks/plugins/ua_client_hints/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - vibration (from `.symlinks/plugins/vibration/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`) - volume_controller (from `.symlinks/plugins/volume_controller/ios`) @@ -336,6 +344,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/dart_ui_isolate/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" + emoji_picker_flutter: + :path: ".symlinks/plugins/emoji_picker_flutter/ios" ffmpeg_kit_flutter: :path: ".symlinks/plugins/ffmpeg_kit_flutter/ios" file_saver: @@ -428,6 +438,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/ua_client_hints/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" + vibration: + :path: ".symlinks/plugins/vibration/ios" video_player_avfoundation: :path: ".symlinks/plugins/video_player_avfoundation/darwin" video_thumbnail: @@ -446,6 +458,7 @@ SPEC CHECKSUMS: cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c dart_ui_isolate: 46f6714abe6891313267153ef6f9748d8ecfcab1 device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + emoji_picker_flutter: ed468d9746c21711e66b2788880519a9de5de211 ffmpeg_kit_custom: 682b4f2f1ff1f8abae5a92f6c3540f2441d5be99 ffmpeg_kit_flutter: 915b345acc97d4142e8a9a8549d177ff10f043f5 file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 @@ -497,6 +510,7 @@ SPEC CHECKSUMS: privacy_screen: 3159a541f5d3a31bea916cfd4e58f9dc722b3fd4 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00 + rust_lib_photos: 04d3901908d2972192944083310b65abf410774c SDWebImage: f29024626962457f3470184232766516dee8dfea SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854 @@ -510,6 +524,7 @@ SPEC CHECKSUMS: thermal: d4c48be750d1ddbab36b0e2dcb2471531bc8df41 ua_client_hints: 92fe0d139619b73ec9fcb46cc7e079a26178f586 url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + vibration: 8e2f50fc35bb736f9eecb7dd9f7047fbb6a6e888 video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140 volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12 diff --git a/mobile/apps/photos/ios/Runner.xcodeproj/project.pbxproj b/mobile/apps/photos/ios/Runner.xcodeproj/project.pbxproj index 56ba9d2b02..5471f4f021 100644 --- a/mobile/apps/photos/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/apps/photos/ios/Runner.xcodeproj/project.pbxproj @@ -532,6 +532,7 @@ "${BUILT_PRODUCTS_DIR}/cupertino_http/cupertino_http.framework", "${BUILT_PRODUCTS_DIR}/dart_ui_isolate/dart_ui_isolate.framework", "${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework", + "${BUILT_PRODUCTS_DIR}/emoji_picker_flutter/emoji_picker_flutter.framework", "${BUILT_PRODUCTS_DIR}/file_saver/file_saver.framework", "${BUILT_PRODUCTS_DIR}/flutter_email_sender/flutter_email_sender.framework", "${BUILT_PRODUCTS_DIR}/flutter_image_compress_common/flutter_image_compress_common.framework", @@ -575,6 +576,7 @@ "${BUILT_PRODUCTS_DIR}/thermal/thermal.framework", "${BUILT_PRODUCTS_DIR}/ua_client_hints/ua_client_hints.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework", + "${BUILT_PRODUCTS_DIR}/vibration/vibration.framework", "${BUILT_PRODUCTS_DIR}/video_player_avfoundation/video_player_avfoundation.framework", "${BUILT_PRODUCTS_DIR}/video_thumbnail/video_thumbnail.framework", "${BUILT_PRODUCTS_DIR}/volume_controller/volume_controller.framework", @@ -627,6 +629,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cupertino_http.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/dart_ui_isolate.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/emoji_picker_flutter.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_saver.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_email_sender.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_image_compress_common.framework", @@ -670,6 +673,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/thermal.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ua_client_hints.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/vibration.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player_avfoundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_thumbnail.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/volume_controller.framework", diff --git a/mobile/apps/photos/ios/Runner/AppDelegate.swift b/mobile/apps/photos/ios/Runner/AppDelegate.swift index cbf287bc52..8673b08bc4 100644 --- a/mobile/apps/photos/ios/Runner/AppDelegate.swift +++ b/mobile/apps/photos/ios/Runner/AppDelegate.swift @@ -27,9 +27,12 @@ import workmanager // Retrieve the link from parameters if let url = AppLinks.shared.getLink(launchOptions: launchOptions) { - // We have a link, propagate it to your Flutter app or not - AppLinks.shared.handleLink(url: url) - return true // Returning true will stop the propagation to other packages + // only accept non-homewidget urls for AppLinks + if !url.absoluteString.contains("&homeWidget") { + AppLinks.shared.handleLink(url: url) + // link is handled, stop propagation + return true + } } return super.application(application, didFinishLaunchingWithOptions: launchOptions) diff --git a/mobile/apps/photos/lib/app.dart b/mobile/apps/photos/lib/app.dart index 45bcb63019..a872a704df 100644 --- a/mobile/apps/photos/lib/app.dart +++ b/mobile/apps/photos/lib/app.dart @@ -89,8 +89,9 @@ class _EnteAppState extends State with WidgetsBindingObserver { _checkForWidgetLaunch(); } - void _checkForWidgetLaunch() { - hw.HomeWidget.initiallyLaunchedFromHomeWidget().then( + Future _checkForWidgetLaunch() async { + await HomeWidgetService.instance.setAppGroup(); + await hw.HomeWidget.initiallyLaunchedFromHomeWidget().then( (uri) => HomeWidgetService.instance.onLaunchFromWidget(uri, context), ); hw.HomeWidget.widgetClicked.listen( diff --git a/mobile/apps/photos/lib/l10n/intl_de.arb b/mobile/apps/photos/lib/l10n/intl_de.arb index afba351302..8849255874 100644 --- a/mobile/apps/photos/lib/l10n/intl_de.arb +++ b/mobile/apps/photos/lib/l10n/intl_de.arb @@ -1776,6 +1776,14 @@ "same": "Gleich", "different": "Verschieden", "sameperson": "Dieselbe Person?", + "cLTitle1": "Erweiterte Bildbearbeitung", + "cLDesc1": "Wir veröffentlichen eine neue und erweiterte Bildbearbeitung, die mehr Bildzuschnitte ermöglicht, vordefinierte Filter für schnelleres Bearbeiten bietet, sowie die Feinabstimmung von Sättigung, Kontrast, Helligkeit und vielem mehr erlaubt. Der neue Editor erlaubt außerdem das Zeichnen auf den Fotos oder das Hinzufügen von Emojis als Sticker.", + "cLTitle2": "Intelligente Alben", + "cLDesc2": "Du kannst jetzt automatisch Fotos von ausgewählten Personen zu jedem Album hinzufügen. Öffne einfach das Album und wähle \"Personen automatisch hinzufügen\" aus dem Menü. Zusammen mit einem geteilten Album kannst Du Fotos mit null Klicks teilen.", + "cLTitle3": "Verbesserte Galerie", + "cLDesc3": "Wir haben die Möglichkeit hinzugefügt, Alben nach Wochen, Monaten und Jahren zu gruppieren. Du kannst jetzt die Galerie mit diesen neuen Gruppierungs-Optionen so anpassen, dass sie genau so aussieht, wie Du möchtest, zusammen mit angepassten Rastern", + "cLTitle4": "Schnelleres Scrollen", + "cLDesc4": "Zusammen mit einem Schwung Änderungen unter der Haube, um das Erlebnis beim Scrollen der Galerie zu verbessern, haben wir außerdem den Scrollbalken mit Markern neu gestaltet, um es Dir zu ermöglichen, schnell in der Zeitleiste zu springen.", "indexingPausedStatusDescription": "Die Indizierung ist pausiert. Sie wird automatisch fortgesetzt, wenn das Gerät bereit ist. Das Gerät wird als bereit angesehen, wenn sich der Akkustand, die Akkugesundheit und der thermische Zustand in einem gesunden Bereich befinden.", "thisWeek": "Diese Woche", "lastWeek": "Letzte Woche", diff --git a/mobile/apps/photos/lib/l10n/intl_fr.arb b/mobile/apps/photos/lib/l10n/intl_fr.arb index 8f73c4baa1..7bdb63cbd4 100644 --- a/mobile/apps/photos/lib/l10n/intl_fr.arb +++ b/mobile/apps/photos/lib/l10n/intl_fr.arb @@ -1758,7 +1758,7 @@ "wishThemAHappyBirthday": "Souhaitez à {name} un joyeux anniversaire ! 🎉", "areYouSureRemoveThisFaceFromPerson": "Êtes-vous sûr de vouloir retirer ce visage de cette personne ?", "otherDetectedFaces": "Autres visages détectés", - "areThey": "Vraiment", + "areThey": "S'agit-il de ", "questionmark": "?", "saveAsAnotherPerson": "Enregistrer comme une autre personne", "showLessFaces": "Afficher moins de visages", @@ -1776,7 +1776,56 @@ "same": "Identique", "different": "Différent(e)", "sameperson": "Même personne ?", + "cLTitle1": "Éditeur d'image avancé", + "cLDesc1": "Nous déployons un nouvel éditeur d'image avancé qui ajoute plus d'options de rognage, des filtres, des préréglages pour des modifications rapides ainsi que des options de réglage fin (la saturation, le contraste, la luminosité, la température et beaucoup plus). Le nouvel éditeur inclut également la possibilité de dessiner sur vos photos et d'ajouter des emojis en tant qu'autocollants.", + "cLTitle2": "Albums Intelligents", + "cLDesc2": "Vous pouvez maintenant ajouter automatiquement des photos de personnes sélectionnées à n'importe quel album. Allez simplement à l'album et sélectionnez \"Ajouter automatiquement des personnes\" dans le menu déroulant. Couplé avec un album partagé, vous pouvez partager des photos en zéro clic.", + "cLTitle3": "Galerie améliorée", + "cLDesc3": "Nous avons ajouté la possibilité de regrouper votre galerie par semaines, mois et années. Vous pouvez maintenant personnaliser votre galerie pour qu'elle soit exactement comme vous le souhaitez avec ces nouvelles options de regroupement, ainsi que des grilles personnalisées", + "cLTitle4": "Défilement plus rapide", + "cLDesc4": "En plus des quelques améliorations pour améliorer l'expérience de défilement de la galerie, nous avons également redessiné la barre de défilement pour afficher des marqueurs, ce qui vous permet de sauter rapidement dans la chronologie.", "indexingPausedStatusDescription": "L'indexation est en pause. Elle reprendra automatiquement lorsque l'appareil sera prêt. Celui-ci est considéré comme prêt lorsque le niveau de batterie, sa santé et son état thermique sont dans une plage saine.", + "thisWeek": "Cette semaine", + "lastWeek": "La semaine dernière", + "thisMonth": "Ce mois", + "thisYear": "Cette année", + "groupBy": "Grouper par", "faceThumbnailGenerationFailed": "Impossible de créer des miniatures de visage", - "fileAnalysisFailed": "Impossible d'analyser le fichier" + "fileAnalysisFailed": "Impossible d'analyser le fichier", + "editAutoAddPeople": "Modifier les personnes auto-ajoutées", + "autoAddPeople": "Ajouter automatiquement des personnes", + "autoAddToAlbum": "Ajouter automatiquement à l'album", + "shouldRemoveFilesSmartAlbumsDesc": "Les fichiers liés à la personne qui ont été précédemment sélectionnés dans les albums intelligents doivent-ils être supprimés ?", + "addingPhotos": "Ajout des photos", + "gettingReady": "Préparation", + "addSomePhotosDesc1": "Ajoutez quelques photos ou choisissez des ", + "addSomePhotosDesc2": "visages familiers", + "addSomePhotosDesc3": "\npour commencer", + "ignorePerson": "Ignorer la personne", + "mixedGrouping": "Plusieurs personnes?", + "analysis": "Analyse", + "doesGroupContainMultiplePeople": "Ce groupement contient-il plusieurs personnes ?", + "automaticallyAnalyzeAndSplitGrouping": "Nous analyserons automatiquement le regroupement pour déterminer s'il y a plusieurs personnes présentes et nous les séparerons à nouveau si tel est le cas. Cela peut prendre quelques secondes.", + "layout": "Disposition", + "day": "Jour", + "peopleAutoAddDesc": "Sélectionnez les personnes que vous souhaitez ajouter automatiquement à l'album", + "undo": "Annuler", + "redo": "Rétablir", + "filter": "Filtres", + "adjust": "Ajuster", + "draw": "Dessiner", + "sticker": "Autocollant", + "brushColor": "Couleur du pinceau", + "font": "Police", + "background": "Arrière-plan", + "align": "Aligner", + "addedToAlbums": "{count, plural, one {}=1{Ajouté avec succès à 1 album} other{Ajouté avec succès à {count} albums}}", + "@addedToAlbums": { + "description": "Message shown when items are added to albums", + "placeholders": { + "count": { + "type": "int" + } + } + } } \ No newline at end of file diff --git a/mobile/apps/photos/lib/l10n/intl_no.arb b/mobile/apps/photos/lib/l10n/intl_no.arb index 00306d6d95..4dbf2967c0 100644 --- a/mobile/apps/photos/lib/l10n/intl_no.arb +++ b/mobile/apps/photos/lib/l10n/intl_no.arb @@ -1403,6 +1403,16 @@ "enableMachineLearningBanner": "Aktiver maskinlæring for magisk søk og ansiktsgjenkjenning", "searchDiscoverEmptySection": "Bilder vil vises her når behandlingen og synkronisering er fullført", "searchPersonsEmptySection": "Folk vil vises her når behandling og synkronisering er fullført", + "viewersSuccessfullyAdded": "{count, plural, =0 {La til 0 tilskuere} =1 {La til 1 tilskuer} other {La til {count} tilskuer}}", + "@viewersSuccessfullyAdded": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + }, + "description": "Number of viewers that were successfully added to an album." + }, "collaboratorsSuccessfullyAdded": "{count, plural, =0 {La til 0 samarbeidspartner} =1 {La til 1 samarbeidspartner} other {Lagt til {count} samarbeidspartnere}}", "@collaboratorsSuccessfullyAdded": { "placeholders": { @@ -1478,6 +1488,15 @@ }, "currentlyRunning": "Kjører for øyeblikket", "ignored": "ignorert", + "photosCount": "{count, plural, =0 {0 bilder} =1 {1 bilde} other {{count} bilder}}", + "@photosCount": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + } + }, "file": "Fil", "searchSectionsLengthMismatch": "Uoverensstemmelse i seksjonslengde: {snapshotLength} != {searchLength}", "@searchSectionsLengthMismatch": { @@ -1643,6 +1662,7 @@ "@linkPersonCaption": { "description": "Caption for the 'Link person' title. It should be a continuation of the 'Link person' title. Just like how 'Link person' + 'for better sharing experience' forms a proper sentence in English, the combination of these two strings should also be a proper sentence in other languages." }, + "videoStreaming": "Strømbare videoer", "processingVideos": "Behandler videoer", "streamDetails": "Strømmedetaljer", "processing": "Behandler", @@ -1708,5 +1728,13 @@ "moon": "I månelyset", "onTheRoad": "På veien igjen", "food": "Kulinær glede", - "pets": "Pelsvenner" + "pets": "Pelsvenner", + "curatedMemories": "Kuraterte minner", + "widgets": "Moduler", + "memories": "Minner", + "peopleWidgetDesc": "Velg folkene du ønsker å se på din hjemskjerm.", + "albumsWidgetDesc": "Velg albumene du ønsker å se på din hjemskjerm.", + "memoriesWidgetDesc": "Velg typen minner du ønsker å se på din hjemskjerm.", + "smartMemories": "Smarte minner", + "pastYearsMemories": "Tidligere års minner" } \ No newline at end of file diff --git a/mobile/apps/photos/lib/l10n/intl_sr.arb b/mobile/apps/photos/lib/l10n/intl_sr.arb index d516e7cff7..52f1bd951d 100644 --- a/mobile/apps/photos/lib/l10n/intl_sr.arb +++ b/mobile/apps/photos/lib/l10n/intl_sr.arb @@ -141,7 +141,14 @@ "enterThe6digitCodeFromnyourAuthenticatorApp": "Унесите 6-цифрени кôд из\nапликације за аутентификацију", "confirm": "Потврди", "setupComplete": "Постављање завршено", + "saveYourRecoveryKeyIfYouHaventAlready": "Уколико већ нисте, сачувајте кључ за опоравак", + "thisCanBeUsedToRecoverYourAccountIfYou": "Може се користити за опоравак рачуна уколико заборавите други ниво провере идентитета", + "twofactorAuthenticationPageTitle": "Двострука провера идентитета", "lostDevice": "Изгубили сте уређај?", + "verifyingRecoveryKey": "Проверавам кључ за опоравак...", + "recoveryKeyVerified": "Кључ за опоравак је потврђен", + "recoveryKeySuccessBody": "Сјајно! Ваш кључ за опоравак је исправан. Хвала за потврду.\n\nНе заборавите да овај кључ чувате на сигурном месту.", + "invalidRecoveryKey": "Кључ за опоравак који сте унели није исправан. Постарајте се да садржи 24 речи, и проверите да ли сте их правилно уписали.\n\nУколико сте унели стари формат кључа, постарајте се да садржи 64 карактера и додатно проверите да ли је сваки исправно уписан.", "invalidKey": "Неисправан кључ", "tryAgain": "Покушај поново", "viewRecoveryKey": "Погледај кључ за опоравак", @@ -186,10 +193,13 @@ "disableDownloadWarningTitle": "Молим, обратите пажњу", "disableDownloadWarningBody": "Људи и даље могу да направе снимак екрана или да скину ваше фотографије користећи друге апликације", "allowDownloads": "Дозволи преузимање", + "linkExpiry": "Рок важења линка", "linkExpired": "Истекао", "linkEnabled": "Омогућено", "linkNeverExpires": "Никада", + "expiredLinkInfo": "Линк је истекао. Поставите ново време за престанак важности линка или онемогућите његово истицање.", "setAPassword": "Постави лозинку", + "lockButtonLabel": "Закључај", "enterPassword": "Унеси лозинку", "removeLink": "Уклони везу", "manageLink": "Управљај везом", @@ -269,11 +279,19 @@ "codeChangeLimitReached": "Жао нам је, достигли сте максимум броја промена кôда.", "onlyFamilyAdminCanChangeCode": "Молимо вас да контактирате {familyAdminEmail} да бисте променили свој код.", "storageInGB": "{storageAmountInGB} ГБ", + "claimed": "Освојено", + "@claimed": { + "description": "Used to indicate storage claimed, like 10GB Claimed" + }, "details": "Више детаља", "claimMore": "Освоји још!", + "theyAlsoGetXGb": "Они ће такође добити {storageAmountInGB} ГБ", + "freeStorageOnReferralSuccess": "{storageAmountInGB} ГБ сваки пут када се неко претплати на неку од наших понуда и при том унесе ваш код", "claimFreeStorage": "Освоји бесплатан простор на диску", "inviteYourFriends": "Позови своје пријатеље", "referralStep1": "1. Дај овај код својим пријатељима", + "referralStep2": "2. Они се претплате на понуду која се плаћа", + "referralStep3": "3. Обоје добијате {storageInGB} ГБ* бесплатно", "referralsAreCurrentlyPaused": "Препоручивање је тренутно паузирано", "youCanAtMaxDoubleYourStorage": "* У најбољем случају можете удвостручити ваш простор", "faq": "Честа питања", @@ -296,10 +314,13 @@ "deleteAlbum": "Избриши албум", "deleteSharedAlbumDialogBody": "Албум ће бити обрисан за све", "yesRemove": "Да, уклони", + "creatingLink": "Креирам линк...", "removeWithQuestionMark": "Уклони?", + "removeParticipantBody": "{userEmail} ће бити уклоњен из овог дељеног албума.\n\nСве фотографије које су они додали ће такође бити уклоњене", "keepPhotos": "Задржи фотографије", "deletePhotos": "Обриши фотгорафије", "inviteToEnte": "Позови у Енте", + "disableLinkMessage": "Ово ће уклонити јавни линк за приступ \"{albumName}\".", "sharing": "Делим...", "youCannotShareWithYourself": "Не можеш делити сам са собом", "archive": "Архивирај", @@ -397,6 +418,7 @@ "discover_sunset": "Заласци сунца", "discover_hills": "Брда", "discover_greenery": "Зеленило", + "waitingForWifi": "Чекам на бежичну мрежу...", "status": "Статус", "indexedItems": "Индексиране ставке", "pendingItems": "На чекању", @@ -406,6 +428,28 @@ "unselectAll": "Деселектуј све", "selectAll": "Означи све", "skip": "Прескочи", + "updatingFolderSelection": "Освежавам избор фолдера...", + "duplicateItemsGroup": "{count} фајлова, {formattedSize} сваки", + "@duplicateItemsGroup": { + "description": "Display the number of duplicate files and their size", + "type": "text", + "placeholders": { + "count": { + "example": "12", + "type": "int" + }, + "formattedSize": { + "example": "2.3 MB", + "type": "String" + } + } + }, + "showMemories": "Прикажи успомене", + "yearsAgo": "{count, plural, one{{count} година уназад} few {{count} године уназад} other{{count} година уназад}}", + "backupStatus": "Статус резервних копија", + "backupOverMobileData": "Копирај користећи мобилни интернет", + "backupVideos": "Копирај видео снимке", + "disableAutoLock": "Онемогући закључавање екрана", "about": "О нама", "privacy": "Приватност", "terms": "Услови", @@ -413,17 +457,67 @@ "checkStatus": "Провери статус", "checking": "Проверавам...", "youAreOnTheLatestVersion": "Користите најновију верзију", + "account": "Рачун", "manageSubscription": "Управљај претплатом", "changePassword": "Измени лозинку", + "authToChangeYourPassword": "Потврдите идентитет како бисте променили лозинку", + "emailVerificationToggle": "Провера имејла", + "exportYourData": "Извези своје податке", + "logout": "Одјави се", + "authToInitiateAccountDeletion": "Потврдите идентитет како бисте покренули брисање рачуна", + "areYouSureYouWantToLogout": "Да ли сигурно желите да се одјавите?", + "yesLogout": "Да, одјави ме", "aNewVersionOfEnteIsAvailable": "Нова Енте верзија је доступна.", + "update": "Ажурирај", + "installManually": "Инсталирај ручно", + "criticalUpdateAvailable": "Критично ажурирање је доступно", + "updateAvailable": "Ажурирање доступно", + "ignoreUpdate": "Игнориши", + "cannotDeleteSharedFiles": "Не могу да обришем дељене фајлове", "retry": "Покушај поново", "freeUpDeviceSpace": "Ослободи простор на уређају", + "allClear": "✨ Све је чисто", "noDeviceThatCanBeDeleted": "Не постоје фајлови на овом уређају који би могли бити обрисани", "removeDuplicates": "Уклони дупликате", + "removeDuplicatesDesc": "Прегледај и уклони фајлове који су дупликати.", "viewLargeFiles": "Велики фајлови", + "viewLargeFilesDesc": "Погледај фајлове који заузимају највише простора.", + "noDuplicates": "✨ Нема дупликата", "youveNoDuplicateFilesThatCanBeCleared": "Немаш дупликата које бисмо могли да обришемо", "rateUs": "Оцени нас", + "youHaveSuccessfullyFreedUp": "Успешно сте ослободили {storageSaved}!", + "@youHaveSuccessfullyFreedUp": { + "description": "The text to display when the user has successfully freed up storage", + "type": "text", + "placeholders": { + "storageSaved": { + "example": "1.2 GB", + "type": "String" + } + } + }, + "duplicateFileCountWithStorageSaved": "Обрисали сте {count, plural, one{{count} дупликат} few {{count} дупликата} other{{count} дупликата}}, ослобађам ({storageSaved}!)", + "@duplicateFileCountWithStorageSaved": { + "description": "The text to display when the user has successfully cleaned up duplicate files", + "type": "text", + "placeholders": { + "count": { + "example": "1", + "type": "int" + }, + "storageSaved": { + "example": "1.2 GB", + "type": "String" + } + } + }, + "familyPlans": "Породичне понуде", + "referrals": "Препоруке", "notifications": "Обавештења", + "sharedPhotoNotifications": "Нове дељене фотографије", + "sharedPhotoNotificationsExplanation": "Добиј обавештење када неко дода фотографију у албум у којем ви учествујете", + "advanced": "Напредно", + "general": "Опште", "security": "Сигурност", "no": "Не", "yes": "Да", @@ -435,14 +529,19 @@ "matrix": "Матрикс", "discord": "Дискорд", "reddit": "Редит", + "yourStorageDetailsCouldNotBeFetched": "Подаци о вашем простору на диску нису могли бити освежени", "reportABug": "Пријави грешку", "reportBug": "Пријави грешку", + "suggestFeatures": "Предложи нову функционалност", "support": "Подршка", "theme": "Тема", "lightTheme": "Светла", "darkTheme": "Тамна", "systemTheme": "Системска", "freeTrial": "Бесплатна проба", + "selectYourPlan": "Изабери пакет", + "enteSubscriptionPitch": "Енте чува твоје успомене тако да су увек са тобом, чак и ако изгубиш уређај.", + "enteSubscriptionShareWithFamily": "Можеш додати и породицу у оквиру овог пакета.", "currentUsageIs": "Тренутно искоришћено", "@currentUsageIs": { "description": "This text is followed by storage usage", @@ -452,7 +551,12 @@ "type": "text" }, "faqs": "Често постављана питања", + "renewsOn": "Претплата се обнавља {endDate}", "freeTrialValidTill": "Бесплатан пробни период важи до {endDate}", + "validTill": "Важи до {endDate}", + "addOnValidTill": "Ваш {storageAmount} додатак важи до {endDate}", + "playStoreFreeTrialValidTill": "Бесплатни пробни период важи до {endDate}.\nМожете изабрати неки од планова након тога.", + "subWillBeCancelledOn": "Ваша претплата ће бити отказана {endDate}", "subscription": "Претплата", "paymentDetails": "Детаљи плаћања", "manageFamily": "Управљај породицом", @@ -462,12 +566,119 @@ "yesRenew": "Да, обнови претплату", "areYouSureYouWantToCancel": "Да ли сте сигурни да желите да откажете?", "yesCancel": "Да, откажи", + "failedToCancel": "Неуспешно отказивање", + "twoMonthsFreeOnYearlyPlans": "2 месеца гратис уз годишњу претплату", + "monthly": "Месечно", + "@monthly": { + "description": "The text to display for monthly plans", + "type": "text" + }, + "yearly": "Годишње", + "@yearly": { + "description": "The text to display for yearly plans", + "type": "text" + }, + "confirmPlanChange": "Потврди промену пакета", + "areYouSureYouWantToChangeYourPlan": "Да ли сигурно желиш да промениш пакет?", + "youCannotDowngradeToThisPlan": "Не можете изабрати овај пакет", + "cancelOtherSubscription": "Молимо, прво откажите претплау на {paymentProvider}", + "@cancelOtherSubscription": { + "description": "The text to display when the user has an existing subscription from a different payment provider", + "type": "text", + "placeholders": { + "paymentProvider": { + "example": "Apple", + "type": "String" + } + } + }, + "optionalAsShortAsYouLike": "Опционо, коико год кратко желите...", + "send": "Пошаљи", + "askCancelReason": "Отказали сте претплату. Да ли бисте нам рекли разлог?", + "thankYouForSubscribing": "Хвала што сте се претплатили!", + "yourPurchaseWasSuccessful": "Куповина успешно закључена", + "yourPlanWasSuccessfullyUpgraded": "Успешно сте прешли на јачи пакет", + "yourPlanWasSuccessfullyDowngraded": "Успешно сте прешли на слабији пакет", + "yourSubscriptionWasUpdatedSuccessfully": "Претплата успешно ажурирана", + "googlePlayId": "Гугл плеј идентификација", + "appleId": "Епл идентификација", + "playstoreSubscription": "Претплата путем Гугл продавнице", + "appstoreSubscription": "Претплата путем Епл продавнице", + "visitWebToManage": "Претплатом можете управљати на страници web.ente.io", + "couldNotUpdateSubscription": "Неуспешно ажурирање претплате", + "pleaseContactSupportAndWeWillBeHappyToHelp": "Контактирајте подршку на support@ente.io и радо ћемо вам помоћи!", + "paymentFailed": "Неуспела уплата", + "continueOnFreeTrial": "Настави бесплатни пробни период", + "areYouSureYouWantToExit": "Сигурно желите да изађете?", + "thankYou": "Хвала вам", + "pleaseWaitForSometimeBeforeRetrying": "Мало сачекајте пре него што покушате поново", + "youAreOnAFamilyPlan": "Ви сте на породичном пакету!", + "leaveFamily": "Напусти породицу", + "areYouSureThatYouWantToLeaveTheFamily": "Да ли заиста желиш да напустиш породични пакет?", + "leave": "Напусти", + "rateTheApp": "Оцените апликацију", + "startBackup": "Започни сигурносно копирање", + "preserveMore": "Сачувај још", + "grantFullAccessPrompt": "Дозволите приступ свим фотографијама у подешавањима уређаја", + "allowPermTitle": "Дозволи приступ фотографијама", "openSettings": "Отвори подешавања", "selectMorePhotos": "Изабери још фотографија", "existingUser": "Постојећи корисник", + "privateBackups": "Приватни бекапи", "forYourMemories": "за твоје успомене", + "safelyStored": "Сигурно похрањено", "atAFalloutShelter": "у подземном бункеру", + "designedToOutlive": "Направљено да надживи", + "available": "Доступно", + "everywhere": "свуда", + "newToEnte": "Нови на Енте", + "pleaseLoginAgain": "Молимо, улогујте се поново", + "autoLogoutMessage": "Излоговани сте услед техничке грешке. Извињавамо се за непријатност.", + "yourSubscriptionHasExpired": "Ваша претплата је истекла", + "storageLimitExceeded": "Расположив простор је прекорачен", + "upgrade": "Ажурирај", + "raiseTicket": "Пошаљи упит", + "@raiseTicket": { + "description": "Button text for raising a support tickets in case of unhandled errors during backup", + "type": "text" + }, + "backupFailed": "Неуспешна израда резервне копије", + "sorryBackupFailedDesc": "Извините, нисмо успели да копирамо овај фајл тренутно. Покушаћемо касније.", + "couldNotBackUpTryLater": "Нисмо могли да бекапујемо ваше податке.\nПокушаћемо касније.", + "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "Енте може да екриптује и сачува фајлове једино ако дозволите приступ њима", + "pleaseGrantPermissions": "Молимо, дозволите приступ", + "grantPermission": "Дозволите приступ", + "privateSharing": "Приватно дељење", + "shareOnlyWithThePeopleYouWant": "Дели само са људима са којима желиш", + "usePublicLinksForPeopleNotOnEnte": "Користи јавне линкове за људима који нису на Енте", + "allowPeopleToAddPhotos": "Дозволи људима да додају фотографије", + "shareAnAlbumNow": "Подели албум сада", + "collectEventPhotos": "Прикупи фотографије са догађаја", + "@onDevice": { + "description": "The text displayed above folders/albums stored on device", + "type": "text" + }, + "onDevice": "На уређају", + "@onEnte": { + "description": "The text displayed above albums backed up to Ente", + "type": "text" + }, + "onEnte": "У Енте", + "name": "Име", + "newest": "Најновије", + "lastUpdated": "Последње ажурирано", "deleteEmptyAlbums": "Избриши празне албуме", + "deleteEmptyAlbumsWithQuestionMark": "Обриши празне албуме?", + "permanentlyDelete": "Трајно избриши", + "canOnlyCreateLinkForFilesOwnedByYou": "Можете направити линк само за фајлове који су ваше власништво", + "linkCopiedToClipboard": "Линк је копиран у меморију", + "restore": "Поврати", + "@restore": { + "description": "Display text for an action which triggers a restore of item from trash", + "type": "text" + }, + "moveToAlbum": "Премести у албум", + "unarchive": "Врати из архиве", "createCollage": "Направи колаж", "saveCollage": "Сачувај колаж", "addToEnte": "Додај у Енте", @@ -475,6 +686,22 @@ "delete": "Обриши", "hide": "Сакриј", "share": "Подели", + "searchByAlbumNameHint": "Име албума", + "albumTitle": "Наслов албума", + "enterAlbumName": "Унесите име албума", + "movingFilesToAlbum": "Премештам фајлове у албум...", + "addedSuccessfullyTo": "Успешно додато у {albumName}", + "movedSuccessfullyTo": "Успешно премештено у {albumName}", + "thisAlbumAlreadyHDACollaborativeLink": "Овај албум већ има линк за сарадњу", + "collaborativeLinkCreatedFor": "Линк за сарадњу креиран за {albumName}", + "askYourLovedOnesToShare": "Замолите ваше вољене да деле", + "invite": "Позови", + "shareYourFirstAlbum": "Подели твој први албум", + "sharedWith": "Подељено са {emailIDs}", + "sharedWithMe": "Подељено са мном", + "sharedByMe": "Ја поделио", + "doubleYourStorage": "Удвостручи простор", + "referFriendsAnd2xYourPlan": "Препоручи пријатељима и удвостручи тренутни пакет", "rename": "Преименуј", "thisEmailIsAlreadyInUse": "Имејл је већ у употреби", "yourVerificationCodeHasExpired": "Ваш код ѕа верификацију је истекао", diff --git a/mobile/apps/photos/lib/l10n/intl_vi.arb b/mobile/apps/photos/lib/l10n/intl_vi.arb index d609d51994..6392a8957d 100644 --- a/mobile/apps/photos/lib/l10n/intl_vi.arb +++ b/mobile/apps/photos/lib/l10n/intl_vi.arb @@ -241,7 +241,7 @@ "linkHasExpired": "Liên kết đã hết hạn", "publicLinkEnabled": "Liên kết công khai đã được bật", "shareALink": "Chia sẻ một liên kết", - "sharedAlbumSectionDescription": "Tạo album chia sẻ và cộng tác với người dùng Ente khác, bao gồm cả người dùng các gói miễn phí.", + "sharedAlbumSectionDescription": "Tạo album chia sẻ và cộng tác với người dùng Ente khác, bao gồm người dùng gói miễn phí.", "shareWithPeopleSectionTitle": "{numberOfPeople, plural, =0 {Chia sẻ với những người cụ thể} =1 {Chia sẻ với 1 người} other {Chia sẻ với {numberOfPeople} người}}", "@shareWithPeopleSectionTitle": { "placeholders": { @@ -293,7 +293,7 @@ "theyAlsoGetXGb": "Họ cũng nhận được {storageAmountInGB} GB", "freeStorageOnReferralSuccess": "{storageAmountInGB} GB mỗi khi ai đó đăng ký gói trả phí và áp dụng mã của bạn", "shareTextReferralCode": "Mã giới thiệu Ente: {referralCode} \n\nÁp dụng nó trong Cài đặt → Chung → Giới thiệu để nhận thêm {referralStorageInGB} GB miễn phí sau khi bạn đăng ký gói trả phí\n\nhttps://ente.io", - "claimFreeStorage": "Nhận thêm dung lượng miễn phí", + "claimFreeStorage": "Nhận thêm dung lượng", "inviteYourFriends": "Mời bạn bè của bạn", "failedToFetchReferralDetails": "Không thể lấy thông tin giới thiệu. Vui lòng thử lại sau.", "referralStep1": "1. Đưa mã này cho bạn bè của bạn", diff --git a/mobile/apps/photos/lib/l10n/intl_zh.arb b/mobile/apps/photos/lib/l10n/intl_zh.arb index bb6219ced3..5ddb76d98f 100644 --- a/mobile/apps/photos/lib/l10n/intl_zh.arb +++ b/mobile/apps/photos/lib/l10n/intl_zh.arb @@ -1776,7 +1776,56 @@ "same": "相同", "different": "不同", "sameperson": "是同一个人?", + "cLTitle1": "高级图像编辑器", + "cLDesc1": "我们正在发布一款全新且高级的图像编辑器,新增更多裁剪框架、快速编辑的滤镜预设,以及包括饱和度、对比度、亮度、色温等在内的精细调整选项。新的编辑器还支持在照片上绘制和添加表情符号作为贴纸。", + "cLTitle2": "智能相册", + "cLDesc2": "您现在可以将所选人物的照片自动添加到任何相册。只需进入相册,从溢出菜单中选择“自动添加人物”。如果与共享相册一起使用,您可以零点击分享照片。", + "cLTitle3": "改进的相册", + "cLDesc3": "我们新增了按周、月、年对图库进行分组的功能。您现在可以通过这些新的分组选项以及自定义网格,定制图库的外观,完全按照您的喜好进行设置", + "cLTitle4": "更快滚动", + "cLDesc4": "除了多项后台改进以提升图库滚动体验外,我们还重新设计了滚动条,添加了标记功能,让您可以快速跳转到时间轴上的不同位置。", "indexingPausedStatusDescription": "索引已暂停。待设备准备就绪后,索引将自动恢复。当设备的电池电量、电池健康度和温度状态处于健康范围内时,设备即被视为准备就绪。", + "thisWeek": "本周", + "lastWeek": "上周", + "thisMonth": "本月", + "thisYear": "今年", + "groupBy": "分组依据", "faceThumbnailGenerationFailed": "无法生成人脸缩略图", - "fileAnalysisFailed": "无法分析文件" + "fileAnalysisFailed": "无法分析文件", + "editAutoAddPeople": "编辑自动添加人物", + "autoAddPeople": "自动添加人物", + "autoAddToAlbum": "自动添加到相册", + "shouldRemoveFilesSmartAlbumsDesc": "应该移除之前在智能相册中选择的与该人相关的文件吗?", + "addingPhotos": "正在添加照片", + "gettingReady": "准备就绪", + "addSomePhotosDesc1": "添加一些照片或者选择 ", + "addSomePhotosDesc2": "熟悉的面孔", + "addSomePhotosDesc3": "首先", + "ignorePerson": "忽略此人", + "mixedGrouping": "混合分组?", + "analysis": "分析", + "doesGroupContainMultiplePeople": "这个分组包含多个人吗?", + "automaticallyAnalyzeAndSplitGrouping": "我们将自动分析分组以确定是否有多人在场,并再次将他们分开。这可能需要几秒钟。", + "layout": "布局", + "day": "日", + "peopleAutoAddDesc": "选择您想要自动添加到相册的人", + "undo": "撤销", + "redo": "重做", + "filter": "筛选", + "adjust": "调整", + "draw": "绘制", + "sticker": "贴纸", + "brushColor": "笔刷颜色", + "font": "字体", + "background": "背景", + "align": "对齐", + "addedToAlbums": "{count, plural, =1{已成功添加到 1 个相册} other{已成功添加到 {count} 个相册}}", + "@addedToAlbums": { + "description": "Message shown when items are added to albums", + "placeholders": { + "count": { + "type": "int" + } + } + } } \ No newline at end of file diff --git a/mobile/apps/photos/lib/main.dart b/mobile/apps/photos/lib/main.dart index db7aa5d1b8..585170bc32 100644 --- a/mobile/apps/photos/lib/main.dart +++ b/mobile/apps/photos/lib/main.dart @@ -174,7 +174,6 @@ Future _runMinimally(String taskId, TimeLogger tlog) async { // Misc Services await UserService.instance.init(); NotificationService.instance.init(prefs); - if (Platform.isAndroid) HomeWidgetService.instance.init(prefs); // Begin Execution // only runs for android @@ -269,8 +268,6 @@ Future _init(bool isBackground, {String via = ''}) async { await SyncService.instance.init(preferences); _logger.info("SyncService init done $tlog"); - HomeWidgetService.instance.init(preferences); - if (!isBackground) { await _scheduleFGHomeWidgetSync(); } diff --git a/mobile/apps/photos/lib/models/memories/memories_cache.dart b/mobile/apps/photos/lib/models/memories/memories_cache.dart index f45282d30c..f49a1a1014 100644 --- a/mobile/apps/photos/lib/models/memories/memories_cache.dart +++ b/mobile/apps/photos/lib/models/memories/memories_cache.dart @@ -9,14 +9,12 @@ import "package:photos/models/memories/smart_memory.dart"; import "package:photos/models/memories/smart_memory_constants.dart"; import "package:photos/models/memories/trip_memory.dart"; -const kPersonShowTimeout = Duration(days: 7 * 10); -const kPersonAndTypeShowTimeout = Duration(days: 7 * 26); -const kClipShowTimeout = Duration(days: 3 * 10); -const kTripShowTimeout = Duration(days: 7 * 25); +const kPersonShowTimeout = Duration(days: 16 * kMemoriesUpdateFrequencyDays); +const kClipShowTimeout = Duration(days: 10 * kMemoriesUpdateFrequencyDays); +const kTripShowTimeout = Duration(days: 50 * kMemoriesUpdateFrequencyDays); final maxShowTimeout = [ kPersonShowTimeout, - kPersonAndTypeShowTimeout, kTripShowTimeout, ].reduce((value, element) => value > element ? value : element) * 3; diff --git a/mobile/apps/photos/lib/models/memories/smart_memory_constants.dart b/mobile/apps/photos/lib/models/memories/smart_memory_constants.dart index 2d9885f90e..35a8b4130c 100644 --- a/mobile/apps/photos/lib/models/memories/smart_memory_constants.dart +++ b/mobile/apps/photos/lib/models/memories/smart_memory_constants.dart @@ -1,4 +1,5 @@ // Constants for computing smart memories -const kMemoriesUpdateFrequency = Duration(days: 3); +const kMemoriesUpdateFrequencyDays = 3; +const kMemoriesUpdateFrequency = Duration(days: kMemoriesUpdateFrequencyDays); const kMemoriesMargin = Duration(days: 1); const kDayItself = Duration(days: 1); diff --git a/mobile/apps/photos/lib/services/album_home_widget_service.dart b/mobile/apps/photos/lib/services/album_home_widget_service.dart index 9513427e1f..7cf07d9e13 100644 --- a/mobile/apps/photos/lib/services/album_home_widget_service.dart +++ b/mobile/apps/photos/lib/services/album_home_widget_service.dart @@ -39,12 +39,7 @@ class AlbumHomeWidgetService { // Properties final Logger _logger = Logger((AlbumHomeWidgetService).toString()); - late final SharedPreferences _prefs; - - // Initialization - void init(SharedPreferences prefs) { - _prefs = prefs; - } + SharedPreferences get _prefs => ServiceLocator.instance.prefs; // Public methods List? getSelectedAlbumIds() { diff --git a/mobile/apps/photos/lib/services/home_widget_service.dart b/mobile/apps/photos/lib/services/home_widget_service.dart index 3c86fbe51b..f6f9e057b2 100644 --- a/mobile/apps/photos/lib/services/home_widget_service.dart +++ b/mobile/apps/photos/lib/services/home_widget_service.dart @@ -55,23 +55,22 @@ class HomeWidgetService { final Logger _logger = Logger((HomeWidgetService).toString()); final computeLock = Lock(); + bool _isAppGroupSet = false; - void init(SharedPreferences prefs) { - setAppGroupID(iOSGroupIDMemory); - _initializeWidgetServices(prefs); - } - - void _initializeWidgetServices(SharedPreferences prefs) { - AlbumHomeWidgetService.instance.init(prefs); - PeopleHomeWidgetService.instance.init(prefs); - MemoryHomeWidgetService.instance.init(prefs); - } - - void setAppGroupID(String id) { - hw.HomeWidget.setAppGroupId(id).ignore(); + Future setAppGroup({String id = iOSGroupIDMemory}) async { + if (!Platform.isIOS || _isAppGroupSet) return; + _logger.info("Setting app group id"); + await hw.HomeWidget.setAppGroupId(id).catchError( + (error) { + _logger.severe("Failed to set app group ID: $error"); + return null; + }, + ); + _isAppGroupSet = true; } Future initHomeWidget([bool isBg = false]) async { + await setAppGroup(); await AlbumHomeWidgetService.instance.initAlbumHomeWidget(isBg); await PeopleHomeWidgetService.instance.initPeopleHomeWidget(); await MemoryHomeWidgetService.instance.initMemoryHomeWidget(); @@ -218,7 +217,7 @@ class HomeWidgetService { Future clearWidget(bool autoLogout) async { if (autoLogout) { - setAppGroupID(iOSGroupIDMemory); + await setAppGroup(); } await Future.wait([ diff --git a/mobile/apps/photos/lib/services/machine_learning/semantic_search/semantic_search_service.dart b/mobile/apps/photos/lib/services/machine_learning/semantic_search/semantic_search_service.dart index 040c87af77..032c21b777 100644 --- a/mobile/apps/photos/lib/services/machine_learning/semantic_search/semantic_search_service.dart +++ b/mobile/apps/photos/lib/services/machine_learning/semantic_search/semantic_search_service.dart @@ -265,6 +265,7 @@ class SemanticSearchService { required Map minimumSimilarityMap, }) async { final startTime = DateTime.now(); + // Uncomment if needed for debugging: print query embeddings // if (kDebugMode) { // for (final queryText in textQueryToEmbeddingMap.keys) { // final embedding = textQueryToEmbeddingMap[queryText]!; diff --git a/mobile/apps/photos/lib/services/memory_home_widget_service.dart b/mobile/apps/photos/lib/services/memory_home_widget_service.dart index ca69b75eb4..a78a650a9d 100644 --- a/mobile/apps/photos/lib/services/memory_home_widget_service.dart +++ b/mobile/apps/photos/lib/services/memory_home_widget_service.dart @@ -31,12 +31,7 @@ class MemoryHomeWidgetService { // Properties final Logger _logger = Logger((MemoryHomeWidgetService).toString()); - late final SharedPreferences _prefs; - - // Initialization - void init(SharedPreferences prefs) { - _prefs = prefs; - } + SharedPreferences get _prefs => ServiceLocator.instance.prefs; // Preference getters and setters bool? hasLastYearMemoriesSelected() { diff --git a/mobile/apps/photos/lib/services/people_home_widget_service.dart b/mobile/apps/photos/lib/services/people_home_widget_service.dart index c204e140b1..323bac5351 100644 --- a/mobile/apps/photos/lib/services/people_home_widget_service.dart +++ b/mobile/apps/photos/lib/services/people_home_widget_service.dart @@ -35,14 +35,9 @@ class PeopleHomeWidgetService { // Properties final Logger _logger = Logger((PeopleHomeWidgetService).toString()); - late final SharedPreferences _prefs; + SharedPreferences get _prefs => ServiceLocator.instance.prefs; final peopleChangedLock = Lock(); - // Initialization - void init(SharedPreferences prefs) { - _prefs = prefs; - } - // Public methods List? getSelectedPeople() { return _prefs.getStringList(SELECTED_PEOPLE_KEY); diff --git a/mobile/apps/photos/lib/services/smart_memories_service.dart b/mobile/apps/photos/lib/services/smart_memories_service.dart index ce20f14cd2..117f334253 100644 --- a/mobile/apps/photos/lib/services/smart_memories_service.dart +++ b/mobile/apps/photos/lib/services/smart_memories_service.dart @@ -35,9 +35,9 @@ import "package:photos/services/collections_service.dart"; import "package:photos/services/language_service.dart"; import "package:photos/services/location_service.dart"; import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; -import "package:photos/services/machine_learning/ml_computer.dart"; import "package:photos/services/machine_learning/ml_result.dart"; import "package:photos/services/search_service.dart"; +import "package:photos/utils/text_embeddings_util.dart"; class MemoriesResult { final List memories; @@ -103,24 +103,18 @@ class SmartMemoriesService { 'allImageEmbeddings has ${allImageEmbeddings.length} entries $t', ); - const String clipPositiveQuery = - 'Photo of a precious and nostalgic memory radiating warmth, vibrant energy, or quiet beauty — alive with color, light, or emotion'; - final clipPositiveTextVector = Vector.fromList( - await MLComputer.instance.runClipText(clipPositiveQuery), - ); - final Map clipPeopleActivityVectors = {}; - for (final peopleActivity in PeopleActivity.values) { - clipPeopleActivityVectors[peopleActivity] ??= Vector.fromList( - await MLComputer.instance.runClipText(activityQuery(peopleActivity)), + // Load pre-computed text embeddings from assets + final textEmbeddings = await loadTextEmbeddingsFromAssets(); + if (textEmbeddings == null) { + _logger.severe('Failed to load pre-computed text embeddings'); + throw Exception( + 'Failed to load pre-computed text embeddings', ); } - final Map clipMemoryTypeVectors = {}; - for (final clipMemoryType in ClipMemoryType.values) { - clipMemoryTypeVectors[clipMemoryType] ??= Vector.fromList( - await MLComputer.instance.runClipText(clipQuery(clipMemoryType)), - ); - } - _logger.info('clipPositiveTextVector and clipPeopleActivityVectors $t'); + _logger.info('Using pre-computed text embeddings from assets'); + final clipPositiveTextVector = textEmbeddings.clipPositiveVector; + final clipPeopleActivityVectors = textEmbeddings.peopleActivityVectors; + final clipMemoryTypeVectors = textEmbeddings.clipMemoryTypeVectors; final local = await getLocale(); final languageCode = local?.languageCode ?? "en"; @@ -422,6 +416,7 @@ class SmartMemoriesService { .map((p) => p.remoteID) .toList(); orderedImportantPersonsID.shuffle(Random()); + final amountOfPersons = orderedImportantPersonsID.length; w?.log('orderedImportantPersonsID setup'); // Check if the user has assignmed "me" @@ -709,6 +704,14 @@ class SmartMemoriesService { w?.log('relevancy setup'); // Loop through the people (and memory types) and add based on rotation + final shownPersonTimeout = Duration( + days: min( + kPersonShowTimeout.inDays, + max(1, amountOfPersons) * kMemoriesUpdateFrequencyDays, + ), + ); + final shownPersonAndTypeTimeout = + Duration(days: shownPersonTimeout.inDays * 2); peopleRotationLoop: for (final personID in orderedImportantPersonsID) { for (final memory in memoryResults) { @@ -721,11 +724,13 @@ class SmartMemoriesService { final shownDate = DateTime.fromMicrosecondsSinceEpoch(shownLog.lastTimeShown); final bool seenPersonRecently = - currentTime.difference(shownDate) < kPersonShowTimeout; + currentTime.difference(shownDate) < shownPersonTimeout; if (seenPersonRecently) continue peopleRotationLoop; } if (personToMemories[personID] == null) continue peopleRotationLoop; int added = 0; + final amountOfMemoryTypesForPerson = personToMemories[personID]!.length; + final bool manyMemoryTypes = amountOfMemoryTypesForPerson > 2; potentialMemoryLoop: for (final memoriesForCategory in personToMemories[personID]!.values) { PeopleMemory potentialMemory = memoriesForCategory.first; @@ -748,8 +753,10 @@ class SmartMemoriesService { final shownTypeDate = DateTime.fromMicrosecondsSinceEpoch(shownLog.lastTimeShown); final bool seenPersonTypeRecently = - currentTime.difference(shownTypeDate) < kPersonAndTypeShowTimeout; - if (seenPersonTypeRecently) continue potentialMemoryLoop; + currentTime.difference(shownTypeDate) < shownPersonAndTypeTimeout; + if (manyMemoryTypes && seenPersonTypeRecently) { + continue potentialMemoryLoop; + } } memoryResults.add(potentialMemory); added++; diff --git a/mobile/apps/photos/lib/ui/viewer/people/person_gallery_suggestion.dart b/mobile/apps/photos/lib/ui/viewer/people/person_gallery_suggestion.dart index 7311e81de5..59aa4a869e 100644 --- a/mobile/apps/photos/lib/ui/viewer/people/person_gallery_suggestion.dart +++ b/mobile/apps/photos/lib/ui/viewer/people/person_gallery_suggestion.dart @@ -101,7 +101,9 @@ class _PersonGallerySuggestionState extends State } else { suggestions = await ClusterFeedbackService.instance .getAllLargePersonSuggestions(); - person = suggestions.first.person; + if (suggestions.isNotEmpty) { + person = suggestions.first.person; + } } if (suggestions.isNotEmpty && mounted) { @@ -122,8 +124,8 @@ class _PersonGallerySuggestionState extends State } if (mounted && _fadeController != null && _slideController != null) { - unawaited(_fadeController!.forward()); - unawaited(_slideController!.forward()); + unawaited(_fadeController?.forward()); + unawaited(_slideController?.forward()); } unawaited(_precomputeNextSuggestions()); @@ -383,8 +385,8 @@ class _PersonGallerySuggestionState extends State Future _animateIn() async { if (mounted && _fadeController != null && _slideController != null) { - _slideController!.reset(); - _fadeController!.reset(); + _slideController?.reset(); + _fadeController?.reset(); await Future.wait([ _fadeController!.forward(), _slideController!.forward(), diff --git a/mobile/apps/photos/lib/utils/text_embeddings_util.dart b/mobile/apps/photos/lib/utils/text_embeddings_util.dart new file mode 100644 index 0000000000..349c284ed4 --- /dev/null +++ b/mobile/apps/photos/lib/utils/text_embeddings_util.dart @@ -0,0 +1,172 @@ +import 'dart:convert'; +import "dart:developer" as dev show log; +import "dart:io" show File; + +import 'package:flutter/services.dart'; +import 'package:logging/logging.dart'; +import 'package:ml_linalg/vector.dart'; +import "package:path_provider/path_provider.dart" + show getExternalStorageDirectory; +import 'package:photos/models/memories/clip_memory.dart'; +import 'package:photos/models/memories/people_memory.dart'; +import "package:photos/services/machine_learning/ml_computer.dart" + show MLComputer; + +final _logger = Logger('TextEmbeddingsUtil'); + +/// Loads pre-computed text embeddings from assets +Future loadTextEmbeddingsFromAssets() async { + try { + _logger.info('Loading text embeddings from assets'); + final jsonString = + await rootBundle.loadString('assets/ml/text_embeddings.json'); + final data = json.decode(jsonString) as Map; + + final embeddings = data['embeddings'] as Map; + + // Parse clip positive embedding + Vector? clipPositiveVector; + final clipPositive = embeddings['clip_positive'] as Map; + final clipPositiveVectorData = + (clipPositive['vector'] as List).cast(); + if (clipPositiveVectorData.isNotEmpty) { + clipPositiveVector = Vector.fromList(clipPositiveVectorData); + } + + // Parse people activities embeddings + final Map peopleActivityVectors = {}; + final peopleActivities = + embeddings['people_activities'] as Map; + for (final activity in PeopleActivity.values) { + final activityName = activity.toString().split('.').last; + if (peopleActivities.containsKey(activityName)) { + final activityData = + peopleActivities[activityName] as Map; + final vector = (activityData['vector'] as List).cast(); + if (vector.isNotEmpty) { + peopleActivityVectors[activity] = Vector.fromList(vector); + } + } + } + + // Parse clip memory types embeddings + final Map clipMemoryTypeVectors = {}; + final clipMemoryTypes = + embeddings['clip_memory_types'] as Map; + for (final memoryType in ClipMemoryType.values) { + final typeName = memoryType.toString().split('.').last; + if (clipMemoryTypes.containsKey(typeName)) { + final typeData = clipMemoryTypes[typeName] as Map; + final vector = (typeData['vector'] as List).cast(); + if (vector.isNotEmpty) { + clipMemoryTypeVectors[memoryType] = Vector.fromList(vector); + } + } + } + + // Check if we have all required embeddings + if (clipPositiveVector == null) { + _logger.severe('Clip positive vector is missing'); + throw Exception('Clip positive vector is missing'); + } + + if (peopleActivityVectors.length != PeopleActivity.values.length) { + _logger.severe('Some people activity vectors are missing'); + throw Exception('Some people activity vectors are missing'); + } + + if (clipMemoryTypeVectors.length != ClipMemoryType.values.length) { + _logger.severe('Some clip memory type vectors are missing'); + throw Exception('Some clip memory type vectors are missing'); + } + + _logger.info('Text embeddings loaded successfully from JSON assets'); + return TextEmbeddings( + clipPositiveVector: clipPositiveVector, + peopleActivityVectors: peopleActivityVectors, + clipMemoryTypeVectors: clipMemoryTypeVectors, + ); + } catch (e, stackTrace) { + _logger.severe('Failed to load text embeddings from JSON', e, stackTrace); + return null; + } +} + +class TextEmbeddings { + final Vector clipPositiveVector; + final Map peopleActivityVectors; + final Map clipMemoryTypeVectors; + + const TextEmbeddings({ + required this.clipPositiveVector, + required this.peopleActivityVectors, + required this.clipMemoryTypeVectors, + }); +} + +/// Helper function to generate text embeddings and save them to a JSON file +/// Run this once to generate the embeddings, then copy the output +/// to assets/ml/text_embeddings.json +Future generateAndSaveTextEmbeddings() async { + final Map embeddingsData = { + 'version': '1.0.0', + 'embeddings': { + 'clip_positive': {}, + 'people_activities': {}, + 'clip_memory_types': {}, + }, + }; + + // Generate clip positive embedding + const String clipPositiveQuery = + 'Photo of a precious and nostalgic memory radiating warmth, vibrant energy, or quiet beauty — alive with color, light, or emotion'; + final clipPositiveVector = + await MLComputer.instance.runClipText(clipPositiveQuery); + embeddingsData['embeddings']['clip_positive'] = { + 'prompt': clipPositiveQuery, + 'vector': clipPositiveVector, + }; + + // Generate people activity embeddings + final peopleActivities = {}; + for (final activity in PeopleActivity.values) { + final activityName = activity.toString().split('.').last; + final prompt = activityQuery(activity); + final vector = await MLComputer.instance.runClipText(prompt); + peopleActivities[activityName] = { + 'prompt': prompt, + 'vector': vector, + }; + } + embeddingsData['embeddings']['people_activities'] = peopleActivities; + + // Generate clip memory type embeddings + final clipMemoryTypes = {}; + for (final memoryType in ClipMemoryType.values) { + final typeName = memoryType.toString().split('.').last; + final prompt = clipQuery(memoryType); + final vector = await MLComputer.instance.runClipText(prompt); + clipMemoryTypes[typeName] = { + 'prompt': prompt, + 'vector': vector, + }; + } + embeddingsData['embeddings']['clip_memory_types'] = clipMemoryTypes; + + // Convert to JSON and log it + final jsonString = const JsonEncoder.withIndent(' ').convert(embeddingsData); + dev.log( + '_generateAndSaveTextEmbeddings: Generated text embeddings JSON', + ); + + final tempDir = await getExternalStorageDirectory(); + final file = File('${tempDir!.path}/text_embeddings.json'); + await file.writeAsString(jsonString); + dev.log( + '_generateAndSaveTextEmbeddings: Saved text embeddings to ${file.path}', + ); + + dev.log( + '_generateAndSaveTextEmbeddings: Text embeddings generation complete! Copy the JSON output above to assets/ml/text_embeddings.json', + ); +} diff --git a/mobile/apps/photos/pubspec.yaml b/mobile/apps/photos/pubspec.yaml index 710c69eb0a..63db0d4076 100644 --- a/mobile/apps/photos/pubspec.yaml +++ b/mobile/apps/photos/pubspec.yaml @@ -347,6 +347,7 @@ flutter: - assets/image-editor/ - assets/icons/ - assets/launcher_icon/ + - assets/ml/ fonts: - family: Inter fonts: diff --git a/mobile/apps/photos/scripts/internal_changes.txt b/mobile/apps/photos/scripts/internal_changes.txt index ca54d7c400..dca921f6ab 100644 --- a/mobile/apps/photos/scripts/internal_changes.txt +++ b/mobile/apps/photos/scripts/internal_changes.txt @@ -1,3 +1,4 @@ +- (prtk) Fix widget initial launch on iOS - Similar images debug screen (Settings > Backup > Free up space > Similar images) - (prtk) Upgrade Flutter version to 3.32.8 - (prtk) Run FFMpeg in an isolate diff --git a/mobile/packages/.gitignore b/mobile/packages/.gitignore new file mode 100644 index 0000000000..009c454944 --- /dev/null +++ b/mobile/packages/.gitignore @@ -0,0 +1,2 @@ +.dart_tool/ +.flutter-plugins-dependencies diff --git a/mobile/packages/accounts/analysis_options.yaml b/mobile/packages/accounts/analysis_options.yaml new file mode 100644 index 0000000000..1bd78bc1b0 --- /dev/null +++ b/mobile/packages/accounts/analysis_options.yaml @@ -0,0 +1,72 @@ +# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html +# or https://pub.dev/packages/lint (Effective dart) +# use "flutter analyze ." or "dart analyze ." for running lint checks + +include: package:flutter_lints/flutter.yaml +linter: + rules: + # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml + # Ref https://dart-lang.github.io/linter/lints/ + - avoid_print + - avoid_unnecessary_containers + - avoid_web_libraries_in_flutter + - no_logic_in_create_state + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_final_locals + - require_trailing_commas + - sized_box_for_whitespace + - use_full_hex_values_for_flutter_colors + - use_key_in_widget_constructors + - cancel_subscriptions + + + - avoid_empty_else + - exhaustive_cases + + # just style suggestions + - sort_pub_dependencies + - use_rethrow_when_possible + - prefer_double_quotes + - directives_ordering + - always_use_package_imports + - sort_child_properties_last + - unawaited_futures + +analyzer: + errors: + avoid_empty_else: error + exhaustive_cases: error + curly_braces_in_flow_control_structures: error + directives_ordering: error + require_trailing_commas: error + always_use_package_imports: warning + prefer_final_fields: error + unused_import: error + camel_case_types: error + prefer_is_empty: warning + use_rethrow_when_possible: info + unused_field: warning + use_key_in_widget_constructors: warning + sort_child_properties_last: warning + sort_pub_dependencies: warning + library_private_types_in_public_api: warning + constant_identifier_names: ignore + prefer_const_constructors: warning + prefer_const_declarations: warning + prefer_const_constructors_in_immutables: warning + prefer_final_locals: warning + unnecessary_const: error + cancel_subscriptions: error + unrelated_type_equality_checks: error + unnecessary_cast: info + + + unawaited_futures: warning # convert to warning after fixing existing issues + invalid_dependency: info + use_build_context_synchronously: ignore # experimental lint, requires many changes + prefer_interpolation_to_compose_strings: ignore # later too many warnings + prefer_double_quotes: ignore # too many warnings + avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides diff --git a/mobile/packages/accounts/lib/ente_accounts.dart b/mobile/packages/accounts/lib/ente_accounts.dart new file mode 100644 index 0000000000..9fcaf2bcf6 --- /dev/null +++ b/mobile/packages/accounts/lib/ente_accounts.dart @@ -0,0 +1,28 @@ +export 'models/bonus.dart'; +export 'models/delete_account.dart'; +export 'models/sessions.dart'; +export 'models/set_keys_request.dart'; +export 'models/set_recovery_key_request.dart'; +export 'models/srp.dart'; +export 'models/subscription.dart'; +export 'models/two_factor.dart'; +export 'models/user_details.dart'; + +export 'pages/change_email_dialog.dart'; +export 'pages/delete_account_page.dart'; +export 'pages/email_entry_page.dart'; +export 'pages/login_page.dart'; +export 'pages/login_pwd_verification_page.dart'; +export 'pages/ott_verification_page.dart'; +export 'pages/passkey_page.dart'; +export 'pages/password_entry_page.dart'; +export 'pages/password_reentry_page.dart'; +export 'pages/recovery_key_page.dart'; +export 'pages/recovery_page.dart'; +export 'pages/request_pwd_verification_page.dart'; +export 'pages/sessions_page.dart'; +export 'pages/two_factor_authentication_page.dart'; +export 'pages/two_factor_recovery_page.dart'; + +export 'services/passkey_service.dart'; +export 'services/user_service.dart'; diff --git a/mobile/packages/accounts/lib/models/bonus.dart b/mobile/packages/accounts/lib/models/bonus.dart new file mode 100644 index 0000000000..a41ea62499 --- /dev/null +++ b/mobile/packages/accounts/lib/models/bonus.dart @@ -0,0 +1,60 @@ +class Bonus { + int storage; + String type; + int validTill; + bool isRevoked; + + Bonus(this.storage, this.type, this.validTill, this.isRevoked); + + factory Bonus.fromJson(Map json) { + return Bonus( + json['storage'], + json['type'], + json['validTill'], + json['isRevoked'], + ); + } + + Map toJson() { + return { + 'storage': storage, + 'type': type, + 'validTill': validTill, + 'isRevoked': isRevoked, + }; + } +} + +class BonusData { + static Set signUpBonusTypes = {'SIGN_UP', 'REFERRAL'}; + final List storageBonuses; + + BonusData(this.storageBonuses); + + List getAddOnBonuses() { + return storageBonuses + .where((b) => !signUpBonusTypes.contains(b.type)) + .toList(); + } + + int totalAddOnBonus() { + return getAddOnBonuses().fold(0, (sum, bonus) => sum + bonus.storage); + } + + factory BonusData.fromJson(Map? json) { + if (json == null || json['storageBonuses'] == null) { + return BonusData([]); + } + return BonusData( + (json['storageBonuses'] as List) + .map((bonus) => Bonus.fromJson(bonus)) + .toList(), + ); + } + + Map toJson() { + return { + 'storageBonuses': storageBonuses.map((bonus) => bonus.toJson()).toList(), + }; + } +} diff --git a/mobile/apps/auth/lib/models/delete_account.dart b/mobile/packages/accounts/lib/models/delete_account.dart similarity index 100% rename from mobile/apps/auth/lib/models/delete_account.dart rename to mobile/packages/accounts/lib/models/delete_account.dart diff --git a/mobile/packages/accounts/lib/models/errors.dart b/mobile/packages/accounts/lib/models/errors.dart new file mode 100644 index 0000000000..dab8c14e1e --- /dev/null +++ b/mobile/packages/accounts/lib/models/errors.dart @@ -0,0 +1,7 @@ +class UnauthorizedError extends Error {} + +class PassKeySessionNotVerifiedError extends Error {} + +class PassKeySessionExpiredError extends Error {} + +class SrpSetupNotCompleteError extends Error {} diff --git a/mobile/apps/auth/lib/models/sessions.dart b/mobile/packages/accounts/lib/models/sessions.dart similarity index 100% rename from mobile/apps/auth/lib/models/sessions.dart rename to mobile/packages/accounts/lib/models/sessions.dart diff --git a/mobile/apps/auth/lib/models/set_keys_request.dart b/mobile/packages/accounts/lib/models/set_keys_request.dart similarity index 100% rename from mobile/apps/auth/lib/models/set_keys_request.dart rename to mobile/packages/accounts/lib/models/set_keys_request.dart diff --git a/mobile/apps/auth/lib/models/set_recovery_key_request.dart b/mobile/packages/accounts/lib/models/set_recovery_key_request.dart similarity index 100% rename from mobile/apps/auth/lib/models/set_recovery_key_request.dart rename to mobile/packages/accounts/lib/models/set_recovery_key_request.dart diff --git a/mobile/apps/auth/lib/models/api/user/srp.dart b/mobile/packages/accounts/lib/models/srp.dart similarity index 99% rename from mobile/apps/auth/lib/models/api/user/srp.dart rename to mobile/packages/accounts/lib/models/srp.dart index baee0335ff..56b19c1317 100644 --- a/mobile/apps/auth/lib/models/api/user/srp.dart +++ b/mobile/packages/accounts/lib/models/srp.dart @@ -81,6 +81,7 @@ class CompleteSRPSetupRequest { ); } } + class SrpAttributes { final String srpUserID; final String srpSalt; diff --git a/mobile/packages/accounts/lib/models/subscription.dart b/mobile/packages/accounts/lib/models/subscription.dart new file mode 100644 index 0000000000..50735a7c48 --- /dev/null +++ b/mobile/packages/accounts/lib/models/subscription.dart @@ -0,0 +1,117 @@ +import 'dart:convert'; + +const freeProductID = "free"; +const popularProductIDs = ["200gb_yearly", "200gb_monthly"]; +const stripe = "stripe"; +const appStore = "appstore"; +const playStore = "playstore"; + +class Subscription { + final String productID; + final int storage; + final String originalTransactionID; + final String paymentProvider; + final int expiryTime; + final String price; + final String period; + final Attributes? attributes; + + Subscription({ + required this.productID, + required this.storage, + required this.originalTransactionID, + required this.paymentProvider, + required this.expiryTime, + required this.price, + required this.period, + this.attributes, + }); + + bool isValid() { + return expiryTime > DateTime.now().microsecondsSinceEpoch; + } + + bool isCancelled() { + return attributes?.isCancelled ?? false; + } + + bool isPastDue() { + return !isCancelled() && + expiryTime < DateTime.now().microsecondsSinceEpoch && + expiryTime >= + DateTime.now() + .subtract(const Duration(days: 30)) + .microsecondsSinceEpoch; + } + + bool isYearlyPlan() { + return 'year' == period; + } + + bool isFreePlan() { + return productID == freeProductID; + } + + static fromMap(Map? map) { + if (map == null) return null; + return Subscription( + productID: map['productID'], + storage: map['storage'], + originalTransactionID: map['originalTransactionID'], + paymentProvider: map['paymentProvider'], + expiryTime: map['expiryTime'], + price: map['price'], + period: map['period'], + attributes: map["attributes"] != null + ? Attributes.fromMap(map["attributes"]) + : null, + ); + } + + Map toMap() { + return { + 'productID': productID, + 'storage': storage, + 'originalTransactionID': originalTransactionID, + 'paymentProvider': paymentProvider, + 'expiryTime': expiryTime, + 'price': price, + 'period': period, + 'attributes': attributes?.toMap(), + }; + } + + String toJson() => json.encode(toMap()); + + factory Subscription.fromJson(String source) => + Subscription.fromMap(json.decode(source)); +} + +class Attributes { + bool? isCancelled; + String? customerID; + + Attributes({ + this.isCancelled, + this.customerID, + }); + + Map toMap() { + return { + 'isCancelled': isCancelled, + 'customerID': customerID, + }; + } + + factory Attributes.fromMap(Map map) { + return Attributes( + isCancelled: map['isCancelled'], + customerID: map['customerID'], + ); + } + + String toJson() => json.encode(toMap()); + + factory Attributes.fromJson(String source) => + Attributes.fromMap(json.decode(source)); +} diff --git a/mobile/apps/auth/lib/models/account/two_factor.dart b/mobile/packages/accounts/lib/models/two_factor.dart similarity index 100% rename from mobile/apps/auth/lib/models/account/two_factor.dart rename to mobile/packages/accounts/lib/models/two_factor.dart diff --git a/mobile/packages/accounts/lib/models/user_details.dart b/mobile/packages/accounts/lib/models/user_details.dart new file mode 100644 index 0000000000..7fe5f8e60f --- /dev/null +++ b/mobile/packages/accounts/lib/models/user_details.dart @@ -0,0 +1,249 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:ente_accounts/models/bonus.dart'; +import 'package:ente_accounts/models/subscription.dart'; + +class UserDetails { + final String email; + final int usage; + final int fileCount; + final int storageBonus; + final int sharedCollectionsCount; + final Subscription subscription; + final FamilyData? familyData; + final ProfileData? profileData; + final BonusData? bonusData; + + const UserDetails( + this.email, + this.usage, + this.fileCount, + this.storageBonus, + this.sharedCollectionsCount, + this.subscription, + this.familyData, + this.profileData, + this.bonusData, + ); + + bool isPartOfFamily() { + return familyData?.members?.isNotEmpty ?? false; + } + + bool hasPaidAddon() { + return bonusData?.getAddOnBonuses().isNotEmpty ?? false; + } + + bool isFamilyAdmin() { + assert(isPartOfFamily(), "verify user is part of family before calling"); + final FamilyMember currentUserMember = familyData!.members! + .firstWhere((element) => element.email.trim() == email.trim()); + return currentUserMember.isAdmin; + } + + // getFamilyOrPersonalUsage will return total usage for family if user + // belong to family group. Otherwise, it will return storage consumed by + // current user + int getFamilyOrPersonalUsage() { + return isPartOfFamily() ? familyData!.getTotalUsage() : usage; + } + + int getFreeStorage() { + final int? memberLimit = familyMemberStorageLimit(); + if (memberLimit != null) { + return max(memberLimit - usage, 0); + } + return max(getTotalStorage() - getFamilyOrPersonalUsage(), 0); + } + + // getTotalStorage will return total storage available including the + // storage bonus + int getTotalStorage() { + return (isPartOfFamily() ? familyData!.storage : subscription.storage) + + storageBonus; + } + + // return the member storage limit if user is part of family and the admin + // has set the storage limit for the user. + int? familyMemberStorageLimit() { + if (isPartOfFamily()) { + try { + final FamilyMember currentUserMember = familyData!.members! + .firstWhere((element) => element.email.trim() == email.trim()); + return currentUserMember.storageLimit; + } catch (e) { + return null; + } + } + return null; + } + + // This is the total storage for which user has paid for. + int getPlanPlusAddonStorage() { + return (isPartOfFamily() ? familyData!.storage : subscription.storage) + + bonusData!.totalAddOnBonus(); + } + + factory UserDetails.fromMap(Map map) { + return UserDetails( + map['email'] as String, + map['usage'] as int, + (map['fileCount'] ?? 0) as int, + (map['storageBonus'] ?? 0) as int, + (map['sharedCollectionsCount'] ?? 0) as int, + Subscription.fromMap(map['subscription']), + FamilyData.fromMap(map['familyData']), + ProfileData.fromJson(map['profileData']), + BonusData.fromJson(map['bonusData']), + ); + } + + Map toMap() { + return { + 'email': email, + 'usage': usage, + 'fileCount': fileCount, + 'storageBonus': storageBonus, + 'sharedCollectionsCount': sharedCollectionsCount, + 'subscription': subscription.toMap(), + 'familyData': familyData?.toMap(), + 'profileData': profileData?.toJson(), + 'bonusData': bonusData?.toJson(), + }; + } + + String toJson() => json.encode(toMap()); + + factory UserDetails.fromJson(String source) => + UserDetails.fromMap(json.decode(source)); +} + +class FamilyMember { + final String email; + final int usage; + final String id; + final bool isAdmin; + final int? storageLimit; + + FamilyMember( + this.email, + this.usage, + this.id, + this.isAdmin, + this.storageLimit, + ); + + factory FamilyMember.fromMap(Map map) { + return FamilyMember( + (map['email'] ?? '') as String, + map['usage'] as int, + map['id'] as String, + map['isAdmin'] as bool, + map['storageLimit'] as int?, + ); + } + + Map toMap() { + return { + 'email': email, + 'usage': usage, + 'id': id, + 'isAdmin': isAdmin, + 'storageLimit': storageLimit, + }; + } + + String toJson() => json.encode(toMap()); + + factory FamilyMember.fromJson(String source) => + FamilyMember.fromMap(json.decode(source)); +} + +class ProfileData { + bool canDisableEmailMFA; + bool isEmailMFAEnabled; + bool isTwoFactorEnabled; + + // Constructor with default values + ProfileData({ + this.canDisableEmailMFA = false, + this.isEmailMFAEnabled = false, + this.isTwoFactorEnabled = false, + }); + + // Factory method to create ProfileData instance from JSON + factory ProfileData.fromJson(Map? json) { + return ProfileData( + canDisableEmailMFA: json?['canDisableEmailMFA'] ?? false, + isEmailMFAEnabled: json?['isEmailMFAEnabled'] ?? false, + isTwoFactorEnabled: json?['isTwoFactorEnabled'] ?? false, + ); + } + + // Method to convert ProfileData instance to JSON + Map toJson() { + return { + 'canDisableEmailMFA': canDisableEmailMFA, + 'isEmailMFAEnabled': isEmailMFAEnabled, + 'isTwoFactorEnabled': isTwoFactorEnabled, + }; + } + + String toJsonString() => json.encode(toJson()); +} + +class FamilyData { + final List? members; + + // Storage available based on the family plan + final int storage; + final int expiryTime; + + FamilyData( + this.members, + this.storage, + this.expiryTime, + ); + + int getTotalUsage() { + return members! + .map((e) => e.usage) + .toList() + .fold(0, (sum, usage) => sum + usage); + } + + FamilyMember? getMemberByID(String id) { + try { + return members!.firstWhere((element) => element.id == id); + } catch (e) { + return null; + } + } + + static fromMap(Map? map) { + if (map == null) return null; + assert(map['members'] != null && map['members'].length >= 0); + final members = List.from( + map['members'].map((x) => FamilyMember.fromMap(x)), + ); + return FamilyData( + members, + map['storage'] as int, + map['expiryTime'] as int, + ); + } + + Map toMap() { + return { + 'members': members?.map((x) => x.toMap()).toList(), + 'storage': storage, + 'expiryTime': expiryTime, + }; + } + + String toJson() => json.encode(toMap()); + + factory FamilyData.fromJson(String source) => + FamilyData.fromMap(json.decode(source)); +} diff --git a/mobile/apps/auth/lib/ui/account/change_email_dialog.dart b/mobile/packages/accounts/lib/pages/change_email_dialog.dart similarity index 79% rename from mobile/apps/auth/lib/ui/account/change_email_dialog.dart rename to mobile/packages/accounts/lib/pages/change_email_dialog.dart index 5277e0fcb6..860b627918 100644 --- a/mobile/apps/auth/lib/ui/account/change_email_dialog.dart +++ b/mobile/packages/accounts/lib/pages/change_email_dialog.dart @@ -1,7 +1,7 @@ -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/email_util.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_utils/email_util.dart'; import 'package:flutter/material.dart'; class ChangeEmailDialog extends StatefulWidget { @@ -16,9 +16,8 @@ class _ChangeEmailDialogState extends State { @override Widget build(BuildContext context) { - final l10n = context.l10n; return AlertDialog( - title: Text(l10n.enterNewEmailHint), + title: Text(context.strings.enterNewEmailHint), content: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -26,7 +25,7 @@ class _ChangeEmailDialogState extends State { children: [ TextFormField( decoration: InputDecoration( - hintText: l10n.email, + hintText: context.strings.email, hintStyle: const TextStyle( color: Colors.white30, ), @@ -48,7 +47,7 @@ class _ChangeEmailDialogState extends State { actions: [ TextButton( child: Text( - l10n.cancel, + context.strings.cancel, style: const TextStyle( color: Colors.redAccent, ), @@ -59,7 +58,7 @@ class _ChangeEmailDialogState extends State { ), TextButton( child: Text( - l10n.verify, + context.strings.verify, style: const TextStyle( color: Colors.purple, ), @@ -68,8 +67,8 @@ class _ChangeEmailDialogState extends State { if (!isValidEmail(_email)) { showErrorDialog( context, - l10n.invalidEmailTitle, - l10n.invalidEmailMessage, + context.strings.invalidEmailTitle, + context.strings.invalidEmailMessage, ); return; } diff --git a/mobile/apps/auth/lib/ui/account/delete_account_page.dart b/mobile/packages/accounts/lib/pages/delete_account_page.dart similarity index 82% rename from mobile/apps/auth/lib/ui/account/delete_account_page.dart rename to mobile/packages/accounts/lib/pages/delete_account_page.dart index b8779e8cbb..f79e32cac8 100644 --- a/mobile/apps/auth/lib/ui/account/delete_account_page.dart +++ b/mobile/packages/accounts/lib/pages/delete_account_page.dart @@ -1,29 +1,31 @@ import 'dart:convert'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/delete_account.dart'; -import 'package:ente_auth/services/local_authentication_service.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/common/dialogs.dart'; -import 'package:ente_auth/ui/common/gradient_button.dart'; -import 'package:ente_auth/utils/email_util.dart'; -import 'package:ente_auth/utils/platform_util.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_configuration/base_configuration.dart'; import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_lock_screen/local_authentication_service.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/buttons/gradient_button.dart'; +import 'package:ente_ui/components/dialogs.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_utils/email_util.dart'; +import 'package:ente_utils/platform_util.dart'; import 'package:flutter/material.dart'; class DeleteAccountPage extends StatelessWidget { - const DeleteAccountPage({ + final BaseConfiguration config; + + const DeleteAccountPage( + this.config, { super.key, }); @override Widget build(BuildContext context) { - final l10n = context.l10n; return Scaffold( appBar: AppBar( elevation: 0, - title: Text(l10n.deleteAccount), + title: Text(context.strings.deleteAccount), leading: IconButton( icon: const Icon(Icons.arrow_back), color: Theme.of(context).iconTheme.color, @@ -47,7 +49,7 @@ class DeleteAccountPage extends StatelessWidget { ), Center( child: Text( - l10n.deleteAccountQuery, + context.strings.deleteAccountQuery, style: Theme.of(context).textTheme.titleMedium, ), ), @@ -74,7 +76,7 @@ class DeleteAccountPage extends StatelessWidget { height: 24, ), GradientButton( - text: l10n.yesSendFeedbackAction, + text: context.strings.yesSendFeedbackAction, iconData: Icons.check, onTap: () async { await sendEmail( @@ -105,7 +107,7 @@ class DeleteAccountPage extends StatelessWidget { backgroundColor: Colors.white, ), label: Text( - l10n.noDeleteAccountAction, + context.strings.noDeleteAccountAction, style: const TextStyle( color: Colors.redAccent, // same for both themes ), @@ -143,11 +145,10 @@ class DeleteAccountPage extends StatelessWidget { BuildContext context, DeleteChallengeResponse response, ) async { - final l10n = context.l10n; final hasAuthenticated = await LocalAuthenticationService.instance.requestLocalAuthentication( context, - l10n.initiateAccountDeleteTitle, + context.strings.initiateAccountDeleteTitle, ); await PlatformUtil.refocusWindows(); @@ -155,11 +156,11 @@ class DeleteAccountPage extends StatelessWidget { if (hasAuthenticated) { final choice = await showChoiceDialogOld( context, - l10n.confirmAccountDeleteTitle, - l10n.confirmAccountDeleteMessage, - firstAction: l10n.cancel, - secondAction: l10n.delete, - firstActionColor: Theme.of(context).colorScheme.onSurface, + context.strings.confirmAccountDeleteTitle, + context.strings.confirmAccountDeleteMessage, + firstAction: context.strings.cancel, + secondAction: context.strings.delete, + firstActionColor: getEnteColorScheme(context).surface, secondActionColor: Colors.red, ); if (choice != DialogUserChoice.secondChoice) { @@ -168,9 +169,9 @@ class DeleteAccountPage extends StatelessWidget { final decryptChallenge = CryptoUtil.openSealSync( CryptoUtil.base642bin(response.encryptedChallenge), CryptoUtil.base642bin( - Configuration.instance.getKeyAttributes()!.publicKey, + config.getKeyAttributes()!.publicKey, ), - Configuration.instance.getSecretKey()!, + config.getSecretKey()!, ); final challengeResponseStr = utf8.decode(decryptChallenge); await UserService.instance.deleteAccount(context, challengeResponseStr); @@ -178,10 +179,9 @@ class DeleteAccountPage extends StatelessWidget { } Future _requestEmailForDeletion(BuildContext context) async { - final l10n = context.l10n; final AlertDialog alert = AlertDialog( title: Text( - l10n.deleteAccount, + context.strings.deleteAccount, style: const TextStyle( color: Colors.red, ), @@ -204,7 +204,7 @@ class DeleteAccountPage extends StatelessWidget { ), ], style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, + color: getEnteColorScheme(context).surface, height: 1.5, fontSize: 16, ), @@ -213,7 +213,7 @@ class DeleteAccountPage extends StatelessWidget { actions: [ TextButton( child: Text( - l10n.sendEmail, + context.strings.sendEmail, style: const TextStyle( color: Colors.red, ), @@ -229,9 +229,9 @@ class DeleteAccountPage extends StatelessWidget { ), TextButton( child: Text( - l10n.ok, + context.strings.ok, style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, + color: getEnteColorScheme(context).surface, ), ), onPressed: () { diff --git a/mobile/apps/auth/lib/ui/account/email_entry_page.dart b/mobile/packages/accounts/lib/pages/email_entry_page.dart similarity index 86% rename from mobile/apps/auth/lib/ui/account/email_entry_page.dart rename to mobile/packages/accounts/lib/pages/email_entry_page.dart index 2283f4ba09..62c49e2922 100644 --- a/mobile/apps/auth/lib/ui/account/email_entry_page.dart +++ b/mobile/packages/accounts/lib/pages/email_entry_page.dart @@ -1,20 +1,20 @@ import 'package:email_validator/email_validator.dart'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/ente_theme_data.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/theme/ente_theme.dart'; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import 'package:ente_auth/utils/platform_util.dart'; -import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_configuration/base_configuration.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/buttons/dynamic_fab.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_utils/platform_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:password_strength/password_strength.dart'; -import 'package:step_progress_indicator/step_progress_indicator.dart'; +import "package:step_progress_indicator/step_progress_indicator.dart"; import "package:styled_text/styled_text.dart"; class EmailEntryPage extends StatefulWidget { - const EmailEntryPage({super.key}); + final BaseConfiguration config; + + const EmailEntryPage(this.config, {super.key}); @override State createState() => _EmailEntryPageState(); @@ -24,10 +24,9 @@ class _EmailEntryPageState extends State { static const kMildPasswordStrengthThreshold = 0.4; static const kStrongPasswordStrengthThreshold = 0.7; - final _config = Configuration.instance; final _passwordController1 = TextEditingController(); final _passwordController2 = TextEditingController(); - final Color _validFieldValueColor = const Color.fromARGB(51, 157, 45, 194); + Color? _validFieldValueColor; String? _email; String? _password; @@ -49,7 +48,7 @@ class _EmailEntryPageState extends State { @override void initState() { - _email = _config.getEmail(); + _email = widget.config.getEmail(); _password1FocusNode.addListener(() { setState(() { _password1InFocus = _password1FocusNode.hasFocus; @@ -67,6 +66,10 @@ class _EmailEntryPageState extends State { Widget build(BuildContext context) { final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100; + // Initialize theme-aware color + final colorScheme = getEnteColorScheme(context); + _validFieldValueColor = colorScheme.primary300.withOpacity(0.2); + FloatingActionButtonLocation? fabLocation() { if (isKeypadOpen) { return null; @@ -89,10 +92,10 @@ class _EmailEntryPageState extends State { child: StepProgressIndicator( totalSteps: 4, currentStep: 1, - selectedColor: Theme.of(context).colorScheme.alternativeColor, + selectedColor: getEnteColorScheme(context).alternativeColor, roundedEdges: const Radius.circular(10), unselectedColor: - Theme.of(context).colorScheme.stepProgressUnselectedColor, + getEnteColorScheme(context).stepProgressUnselectedColor, ), ), ); @@ -103,10 +106,10 @@ class _EmailEntryPageState extends State { floatingActionButton: DynamicFAB( isKeypadOpen: isKeypadOpen, isFormValid: _isFormValid(), - buttonText: context.l10n.createAccount, + buttonText: context.strings.createAccount, onPressedFunction: () { UserService.instance.setEmail(_email!); - _config.setVolatilePassword(_passwordController1.text); + widget.config.setVolatilePassword(_passwordController1.text); UserService.instance.setRefSource(_referralSource); UserService.instance.sendOtt( context, @@ -123,13 +126,13 @@ class _EmailEntryPageState extends State { } Widget _getBody() { - var passwordStrengthText = context.l10n.weakStrength; + var passwordStrengthText = context.strings.weakStrength; var passwordStrengthColor = Colors.redAccent; if (_passwordStrength > kStrongPasswordStrengthThreshold) { - passwordStrengthText = context.l10n.strongStrength; + passwordStrengthText = context.strings.strongStrength; passwordStrengthColor = Colors.greenAccent; } else if (_passwordStrength > kMildPasswordStrengthThreshold) { - passwordStrengthText = context.l10n.moderateStrength; + passwordStrengthText = context.strings.moderateStrength; passwordStrengthColor = Colors.orangeAccent; } return Column( @@ -142,7 +145,7 @@ class _EmailEntryPageState extends State { padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), child: Text( - context.l10n.createNewAccount, + context.strings.createNewAccount, style: Theme.of(context).textTheme.headlineMedium, ), ), @@ -154,7 +157,7 @@ class _EmailEntryPageState extends State { decoration: InputDecoration( fillColor: _emailIsValid ? _validFieldValueColor : null, filled: true, - hintText: context.l10n.email, + hintText: context.strings.email, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, @@ -167,11 +170,7 @@ class _EmailEntryPageState extends State { ? Icon( Icons.check, size: 20, - color: Theme.of(context) - .inputDecorationTheme - .focusedBorder! - .borderSide - .color, + color: getEnteColorScheme(context).primary300, ) : null, ), @@ -203,7 +202,7 @@ class _EmailEntryPageState extends State { fillColor: _passwordIsValid ? _validFieldValueColor : null, filled: true, - hintText: context.l10n.password, + hintText: context.strings.password, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, @@ -226,11 +225,7 @@ class _EmailEntryPageState extends State { : _passwordIsValid ? Icon( Icons.check, - color: Theme.of(context) - .inputDecorationTheme - .focusedBorder! - .borderSide - .color, + color: getEnteColorScheme(context).primary300, ) : null, border: UnderlineInputBorder( @@ -272,7 +267,7 @@ class _EmailEntryPageState extends State { ? _validFieldValueColor : null, filled: true, - hintText: context.l10n.confirmPassword, + hintText: context.strings.confirmPassword, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, @@ -295,11 +290,7 @@ class _EmailEntryPageState extends State { : _passwordsMatch ? Icon( Icons.check, - color: Theme.of(context) - .inputDecorationTheme - .focusedBorder! - .borderSide - .color, + color: getEnteColorScheme(context).primary300, ) : null, border: UnderlineInputBorder( @@ -324,7 +315,7 @@ class _EmailEntryPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), child: Text( - context.l10n.passwordStrength(passwordStrengthText), + context.strings.passwordStrength(passwordStrengthText), style: TextStyle( color: passwordStrengthColor, fontWeight: FontWeight.w500, @@ -338,7 +329,7 @@ class _EmailEntryPageState extends State { padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20), child: Text( - context.l10n.hearUsWhereTitle, + context.strings.hearUsWhereTitle, style: getEnteTextTheme(context).smallFaint, ), ), @@ -362,7 +353,7 @@ class _EmailEntryPageState extends State { onTap: () { showToast( context, - context.l10n.hearUsExplanation, + context.strings.hearUsExplanation, ); }, child: Icon( @@ -424,7 +415,7 @@ class _EmailEntryPageState extends State { ), Expanded( child: StyledText( - text: context.l10n.signUpTerms, + text: context.strings.signUpTerms, style: Theme.of(context) .textTheme .titleMedium! @@ -434,7 +425,7 @@ class _EmailEntryPageState extends State { (String? text, Map attrs) => PlatformUtil.openWebView( context, - context.l10n.termsOfServicesTitle, + context.strings.termsOfServicesTitle, "https://ente.io/terms", ), style: const TextStyle( @@ -445,7 +436,7 @@ class _EmailEntryPageState extends State { (String? text, Map attrs) => PlatformUtil.openWebView( context, - context.l10n.privacyPolicyTitle, + context.strings.privacyPolicyTitle, "https://ente.io/privacy", ), style: const TextStyle( @@ -481,7 +472,7 @@ class _EmailEntryPageState extends State { ), Expanded( child: StyledText( - text: context.l10n.ackPasswordLostWarning, + text: context.strings.ackPasswordLostWarning, style: Theme.of(context) .textTheme .titleMedium! @@ -491,7 +482,7 @@ class _EmailEntryPageState extends State { (String? text, Map attrs) => PlatformUtil.openWebView( context, - context.l10n.encryption, + context.strings.encryption, "https://ente.io/architecture", ), style: const TextStyle( @@ -513,4 +504,6 @@ class _EmailEntryPageState extends State { _hasAgreedToE2E && _passwordIsValid; } + + void showToast(BuildContext context, String hearUsExplanation) {} } diff --git a/mobile/apps/auth/lib/ui/account/login_page.dart b/mobile/packages/accounts/lib/pages/login_page.dart similarity index 79% rename from mobile/apps/auth/lib/ui/account/login_page.dart rename to mobile/packages/accounts/lib/pages/login_page.dart index fef05707ac..3caeebea2c 100644 --- a/mobile/apps/auth/lib/ui/account/login_page.dart +++ b/mobile/packages/accounts/lib/pages/login_page.dart @@ -1,25 +1,26 @@ -import 'package:email_validator/email_validator.dart'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/errors.dart'; -import "package:ente_auth/l10n/l10n.dart"; -import 'package:ente_auth/models/api/user/srp.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/account/login_pwd_verification_page.dart'; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import 'package:ente_auth/utils/platform_util.dart'; +import "package:email_validator/email_validator.dart"; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_accounts/models/errors.dart'; +import 'package:ente_configuration/base_configuration.dart'; +import "package:ente_strings/ente_strings.dart"; +import 'package:ente_ui/components/buttons/dynamic_fab.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_utils/platform_util.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; -import "package:styled_text/styled_text.dart"; +import "package:styled_text/tags/styled_text_tag_action.dart"; +import "package:styled_text/widgets/styled_text.dart"; class LoginPage extends StatefulWidget { - const LoginPage({super.key}); + final BaseConfiguration config; + + const LoginPage(this.config, {super.key}); @override State createState() => _LoginPageState(); } class _LoginPageState extends State { - final _config = Configuration.instance; bool _emailIsValid = false; String? _email; Color? _emailInputFieldColor; @@ -27,7 +28,7 @@ class _LoginPageState extends State { Future onPressed() async { await UserService.instance.setEmail(_email!); - Configuration.instance.resetVolatilePassword(); + widget.config.resetVolatilePassword(); SrpAttributes? attr; bool isEmailVerificationEnabled = true; try { @@ -43,7 +44,8 @@ class _LoginPageState extends State { MaterialPageRoute( builder: (BuildContext context) { return LoginPasswordVerificationPage( - srpAttributes: attr!, + widget.config, + attr!, ); }, ), @@ -61,7 +63,7 @@ class _LoginPageState extends State { @override void initState() { - _email = _config.getEmail(); + _email = widget.config.getEmail(); super.initState(); } @@ -93,7 +95,7 @@ class _LoginPageState extends State { floatingActionButton: DynamicFAB( isKeypadOpen: isKeypadOpen, isFormValid: _emailIsValid, - buttonText: context.l10n.logInLabel, + buttonText: context.strings.logInLabel, onPressedFunction: onPressed, ), floatingActionButtonLocation: fabLocation(), @@ -102,7 +104,6 @@ class _LoginPageState extends State { } Widget _getBody() { - final l10n = context.l10n; return Column( children: [ Expanded( @@ -113,7 +114,7 @@ class _LoginPageState extends State { padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), child: Text( - l10n.welcomeBack, + context.strings.welcomeBack, style: Theme.of(context).textTheme.headlineMedium, ), ), @@ -126,7 +127,7 @@ class _LoginPageState extends State { decoration: InputDecoration( fillColor: _emailInputFieldColor, filled: true, - hintText: l10n.email, + hintText: context.strings.email, contentPadding: const EdgeInsets.symmetric( horizontal: 15, vertical: 15, @@ -139,11 +140,7 @@ class _LoginPageState extends State { ? Icon( Icons.check, size: 20, - color: Theme.of(context) - .inputDecorationTheme - .focusedBorder! - .borderSide - .color, + color: getEnteColorScheme(context).primary300, ) : null, ), @@ -152,8 +149,9 @@ class _LoginPageState extends State { _email = value.trim(); _emailIsValid = EmailValidator.validate(_email!); if (_emailIsValid) { - _emailInputFieldColor = - const Color.fromARGB(51, 157, 45, 194); + _emailInputFieldColor = getEnteColorScheme(context) + .primary300 + .withOpacity(0.2); } else { _emailInputFieldColor = null; } @@ -178,17 +176,14 @@ class _LoginPageState extends State { Expanded( flex: 5, child: StyledText( - text: context.l10n.loginTerms, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(fontSize: 12), + text: context.strings.loginTerms, + style: getEnteTextTheme(context).small, tags: { 'u-terms': StyledTextActionTag( (String? text, Map attrs) => PlatformUtil.openWebView( context, - context.l10n.termsOfServicesTitle, + context.strings.termsOfServicesTitle, "https://ente.io/terms", ), style: const TextStyle( @@ -199,7 +194,7 @@ class _LoginPageState extends State { (String? text, Map attrs) => PlatformUtil.openWebView( context, - context.l10n.privacyPolicyTitle, + context.strings.privacyPolicyTitle, "https://ente.io/privacy", ), style: const TextStyle( diff --git a/mobile/apps/auth/lib/ui/account/login_pwd_verification_page.dart b/mobile/packages/accounts/lib/pages/login_pwd_verification_page.dart similarity index 85% rename from mobile/apps/auth/lib/ui/account/login_pwd_verification_page.dart rename to mobile/packages/accounts/lib/pages/login_pwd_verification_page.dart index 6d43988e2c..e23064d389 100644 --- a/mobile/apps/auth/lib/ui/account/login_pwd_verification_page.dart +++ b/mobile/packages/accounts/lib/pages/login_pwd_verification_page.dart @@ -1,14 +1,13 @@ import "package:dio/dio.dart"; -import 'package:ente_auth/core/configuration.dart'; -import "package:ente_auth/l10n/l10n.dart"; -import "package:ente_auth/models/api/user/srp.dart"; -import "package:ente_auth/services/user_service.dart"; -import "package:ente_auth/theme/ente_theme.dart"; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import "package:ente_auth/ui/components/buttons/button_widget.dart"; -import "package:ente_auth/utils/dialog_util.dart"; -import "package:ente_auth/utils/email_util.dart"; +import "package:ente_accounts/ente_accounts.dart"; +import "package:ente_configuration/base_configuration.dart"; import "package:ente_crypto_dart/ente_crypto_dart.dart"; +import "package:ente_strings/ente_strings.dart"; +import "package:ente_ui/components/buttons/button_widget.dart"; +import "package:ente_ui/components/buttons/dynamic_fab.dart"; +import "package:ente_ui/theme/ente_theme.dart"; +import "package:ente_ui/utils/dialog_util.dart"; +import "package:ente_utils/email_util.dart"; import 'package:flutter/material.dart'; import "package:logging/logging.dart"; @@ -18,8 +17,13 @@ import "package:logging/logging.dart"; // In the PasswordReentryPage, the password is auto-filled based on the // volatile password. class LoginPasswordVerificationPage extends StatefulWidget { + final BaseConfiguration config; final SrpAttributes srpAttributes; - const LoginPasswordVerificationPage({super.key, required this.srpAttributes}); + const LoginPasswordVerificationPage( + this.config, + this.srpAttributes, { + super.key, + }); @override State createState() => @@ -43,7 +47,7 @@ class _LoginPasswordVerificationPageState @override void initState() { super.initState(); - email = Configuration.instance.getEmail(); + email = widget.config.getEmail(); _passwordFocusNode.addListener(() { setState(() { _passwordInFocus = _passwordFocusNode.hasFocus; @@ -80,7 +84,7 @@ class _LoginPasswordVerificationPageState key: const ValueKey("verifyPasswordButton"), isKeypadOpen: isKeypadOpen, isFormValid: _passwordController.text.isNotEmpty, - buttonText: context.l10n.logInLabel, + buttonText: context.strings.logInLabel, onPressedFunction: onPressed, ), floatingActionButtonLocation: fabLocation(), @@ -91,7 +95,7 @@ class _LoginPasswordVerificationPageState Future verifyPassword(BuildContext context, String password) async { final dialog = createProgressDialog( context, - context.l10n.pleaseWait, + context.strings.pleaseWait, isDismissible: true, ); await dialog.show(); @@ -108,22 +112,22 @@ class _LoginPasswordVerificationPageState _logger.severe('server reject, failed verify SRP login', e, s); await _showContactSupportDialog( context, - context.l10n.incorrectPasswordTitle, - context.l10n.pleaseTryAgain, + context.strings.incorrectPasswordTitle, + context.strings.pleaseTryAgain, ); } else { _logger.severe('API failure during SRP login', e, s); if (e.type == DioExceptionType.connectionError) { await _showContactSupportDialog( context, - context.l10n.noInternetConnection, - context.l10n.pleaseCheckYourInternetConnectionAndTryAgain, + context.strings.noInternetConnection, + context.strings.pleaseCheckYourInternetConnectionAndTryAgain, ); } else { await _showContactSupportDialog( context, - context.l10n.oops, - context.l10n.verificationFailedPleaseTryAgain, + context.strings.oops, + context.strings.verificationFailedPleaseTryAgain, ); } } @@ -143,9 +147,9 @@ class _LoginPasswordVerificationPageState // device is not powerful enough to perform derive key final dialogChoice = await showChoiceDialog( context, - title: context.l10n.recreatePasswordTitle, - body: context.l10n.recreatePasswordBody, - firstButtonLabel: context.l10n.useRecoveryKey, + title: context.strings.recreatePasswordTitle, + body: context.strings.recreatePasswordBody, + firstButtonLabel: context.strings.useRecoveryKey, ); if (dialogChoice!.action == ButtonAction.first) { await UserService.instance.sendOtt( @@ -159,8 +163,8 @@ class _LoginPasswordVerificationPageState _logger.severe('unexpected error while verifying password', e, s); await _showContactSupportDialog( context, - context.l10n.oops, - context.l10n.verificationFailedPleaseTryAgain, + context.strings.oops, + context.strings.verificationFailedPleaseTryAgain, ); } } @@ -175,13 +179,13 @@ class _LoginPasswordVerificationPageState context, title: title, body: message, - firstButtonLabel: context.l10n.contactSupport, - secondButtonLabel: context.l10n.ok, + firstButtonLabel: context.strings.contactSupport, + secondButtonLabel: context.strings.ok, ); if (dialogChoice!.action == ButtonAction.first) { await sendLogs( context, - context.l10n.contactSupport, + context.strings.contactSupport, postShare: () {}, ); } @@ -197,7 +201,7 @@ class _LoginPasswordVerificationPageState Padding( padding: const EdgeInsets.only(top: 30, left: 20, right: 20), child: Text( - context.l10n.enterPassword, + context.strings.enterPassword, style: Theme.of(context).textTheme.headlineMedium, ), ), @@ -235,7 +239,7 @@ class _LoginPasswordVerificationPageState key: const ValueKey("passwordInputField"), autofillHints: const [AutofillHints.password], decoration: InputDecoration( - hintText: context.l10n.enterYourPasswordHint, + hintText: context.strings.enterYourPasswordHint, filled: true, contentPadding: const EdgeInsets.all(20), border: UnderlineInputBorder( @@ -295,7 +299,7 @@ class _LoginPasswordVerificationPageState }, child: Center( child: Text( - context.l10n.forgotPassword, + context.strings.forgotPassword, style: Theme.of(context) .textTheme .titleMedium! @@ -311,17 +315,17 @@ class _LoginPasswordVerificationPageState onTap: () async { final dialog = createProgressDialog( context, - context.l10n.pleaseWait, + context.strings.pleaseWait, ); await dialog.show(); - await Configuration.instance.logout(); + await widget.config.logout(); await dialog.hide(); Navigator.of(context) .popUntil((route) => route.isFirst); }, child: Center( child: Text( - context.l10n.changeEmail, + context.strings.changeEmail, style: Theme.of(context) .textTheme .titleMedium! diff --git a/mobile/apps/auth/lib/ui/account/ott_verification_page.dart b/mobile/packages/accounts/lib/pages/ott_verification_page.dart similarity index 88% rename from mobile/apps/auth/lib/ui/account/ott_verification_page.dart rename to mobile/packages/accounts/lib/pages/ott_verification_page.dart index cc9661defc..cc49d1f88d 100644 --- a/mobile/apps/auth/lib/ui/account/ott_verification_page.dart +++ b/mobile/packages/accounts/lib/pages/ott_verification_page.dart @@ -1,7 +1,7 @@ -import 'package:ente_auth/ente_theme_data.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/buttons/dynamic_fab.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; import 'package:flutter/material.dart'; import 'package:step_progress_indicator/step_progress_indicator.dart'; import 'package:styled_text/styled_text.dart'; @@ -46,7 +46,6 @@ class _OTTVerificationPageState extends State { @override Widget build(BuildContext context) { - final l10n = context.l10n; final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100; FloatingActionButtonLocation? fabLocation() { @@ -74,10 +73,10 @@ class _OTTVerificationPageState extends State { child: StepProgressIndicator( totalSteps: 4, currentStep: 2, - selectedColor: Theme.of(context).colorScheme.alternativeColor, + selectedColor: getEnteColorScheme(context).alternativeColor, roundedEdges: const Radius.circular(10), unselectedColor: - Theme.of(context).colorScheme.stepProgressUnselectedColor, + getEnteColorScheme(context).stepProgressUnselectedColor, ), ) : null, @@ -86,7 +85,7 @@ class _OTTVerificationPageState extends State { floatingActionButton: DynamicFAB( isKeypadOpen: isKeypadOpen, isFormValid: _verificationCodeController.text.isNotEmpty, - buttonText: l10n.verify, + buttonText: context.strings.verify, onPressedFunction: onPressed, ), floatingActionButtonLocation: fabLocation(), @@ -95,7 +94,6 @@ class _OTTVerificationPageState extends State { } Widget _getBody() { - final l10n = context.l10n; return ListView( children: [ Column( @@ -104,7 +102,7 @@ class _OTTVerificationPageState extends State { Padding( padding: const EdgeInsets.fromLTRB(20, 30, 20, 15), child: Text( - l10n.verifyEmail, + context.strings.verifyEmail, style: Theme.of(context).textTheme.headlineMedium, ), ), @@ -119,7 +117,8 @@ class _OTTVerificationPageState extends State { Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 12), child: StyledText( - text: l10n.weHaveSendEmailTo(widget.email), + text: + context.strings.weHaveSendEmailTo(widget.email), style: Theme.of(context) .textTheme .titleMedium! @@ -127,8 +126,7 @@ class _OTTVerificationPageState extends State { tags: { 'green': StyledTextTag( style: TextStyle( - color: Theme.of(context) - .colorScheme + color: getEnteColorScheme(context) .alternativeColor, ), ), @@ -137,14 +135,14 @@ class _OTTVerificationPageState extends State { ), widget.isResetPasswordScreen ? Text( - l10n.toResetVerifyEmail, + context.strings.toResetVerifyEmail, style: Theme.of(context) .textTheme .titleMedium! .copyWith(fontSize: 14), ) : Text( - l10n.checkInboxAndSpamFolder, + context.strings.checkInboxAndSpamFolder, style: Theme.of(context) .textTheme .titleMedium! @@ -169,7 +167,7 @@ class _OTTVerificationPageState extends State { : null, decoration: InputDecoration( filled: true, - hintText: l10n.tapToEnterCode, + hintText: context.strings.tapToEnterCode, contentPadding: const EdgeInsets.all(15), border: UnderlineInputBorder( borderSide: BorderSide.none, @@ -204,7 +202,7 @@ class _OTTVerificationPageState extends State { ); }, child: Text( - l10n.resendEmail, + context.strings.resendEmail, style: Theme.of(context).textTheme.titleMedium!.copyWith( fontSize: 14, decoration: TextDecoration.underline, diff --git a/mobile/apps/auth/lib/ui/passkey_page.dart b/mobile/packages/accounts/lib/pages/passkey_page.dart similarity index 82% rename from mobile/apps/auth/lib/ui/passkey_page.dart rename to mobile/packages/accounts/lib/pages/passkey_page.dart index 906d2122df..f7470939da 100644 --- a/mobile/apps/auth/lib/ui/passkey_page.dart +++ b/mobile/packages/accounts/lib/pages/passkey_page.dart @@ -1,27 +1,27 @@ import 'dart:convert'; import 'package:app_links/app_links.dart'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/errors.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/account/two_factor.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/components/buttons/button_widget.dart'; -import 'package:ente_auth/ui/components/models/button_type.dart'; -import 'package:ente_auth/ui/two_factor_authentication_page.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/navigation_util.dart'; -import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_accounts/models/errors.dart'; +import 'package:ente_configuration/base_configuration.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_ui/utils/toast_util.dart'; +import 'package:ente_utils/navigation_util.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:url_launcher/url_launcher_string.dart'; class PasskeyPage extends StatefulWidget { + final BaseConfiguration config; final String sessionID; final String totp2FASessionID; final String accountsUrl; const PasskeyPage( + this.config, this.sessionID, { required this.totp2FASessionID, required this.accountsUrl, @@ -63,13 +63,13 @@ class _PasskeyPageState extends State { response = await UserService.instance .getTokenForPasskeySession(widget.sessionID); } on PassKeySessionNotVerifiedError { - showToast(context, context.l10n.passKeyPendingVerification); + showToast(context, context.strings.passKeyPendingVerification); return; } on PassKeySessionExpiredError { await showErrorDialog( context, - context.l10n.loginSessionExpired, - context.l10n.loginSessionExpiredDetails, + context.strings.loginSessionExpired, + context.strings.loginSessionExpiredDetails, ); Navigator.of(context).pop(); return; @@ -83,16 +83,16 @@ class _PasskeyPageState extends State { Future _handleDeeplink(String? link) async { if (!context.mounted || - Configuration.instance.hasConfiguredAccount() || + widget.config.hasConfiguredAccount() || link == null) { _logger.warning( - 'ignored deeplink: contextMounted ${context.mounted} hasConfiguredAccount ${Configuration.instance.hasConfiguredAccount()}', + 'ignored deeplink: contextMounted ${context.mounted}', ); return; } try { if (mounted && link.toLowerCase().startsWith("enteauth://passkey")) { - if (Configuration.instance.isLoggedIn()) { + if (widget.config.isLoggedIn()) { _logger.info('ignored deeplink: already configured'); showToast(context, 'Account is already configured.'); return; @@ -135,11 +135,10 @@ class _PasskeyPageState extends State { @override Widget build(BuildContext context) { - final l10n = context.l10n; return Scaffold( appBar: AppBar( title: Text( - l10n.passkeyAuthTitle, + context.strings.passkeyAuthTitle, ), ), body: _getBody(), @@ -154,7 +153,7 @@ class _PasskeyPageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - context.l10n.waitingForVerification, + context.strings.waitingForVerification, style: const TextStyle( height: 1.4, fontSize: 16, @@ -163,13 +162,13 @@ class _PasskeyPageState extends State { const SizedBox(height: 16), ButtonWidget( buttonType: ButtonType.primary, - labelText: context.l10n.tryAgain, + labelText: context.strings.tryAgain, onTap: () => launchPasskey(), ), const SizedBox(height: 16), ButtonWidget( buttonType: ButtonType.secondary, - labelText: context.l10n.checkStatus, + labelText: context.strings.checkStatus, onTap: () async { try { await checkStatus(); @@ -196,7 +195,7 @@ class _PasskeyPageState extends State { padding: const EdgeInsets.all(10), child: Center( child: Text( - context.l10n.loginWithTOTP, + context.strings.loginWithTOTP, style: const TextStyle( decoration: TextDecoration.underline, fontSize: 12, @@ -218,7 +217,7 @@ class _PasskeyPageState extends State { padding: const EdgeInsets.all(10), child: Center( child: Text( - context.l10n.recoverAccount, + context.strings.recoverAccount, style: const TextStyle( decoration: TextDecoration.underline, fontSize: 12, diff --git a/mobile/apps/auth/lib/ui/account/password_entry_page.dart b/mobile/packages/accounts/lib/pages/password_entry_page.dart similarity index 81% rename from mobile/apps/auth/lib/ui/account/password_entry_page.dart rename to mobile/packages/accounts/lib/pages/password_entry_page.dart index b7e3e14694..85b6f63b25 100644 --- a/mobile/apps/auth/lib/ui/account/password_entry_page.dart +++ b/mobile/packages/accounts/lib/pages/password_entry_page.dart @@ -1,15 +1,14 @@ -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/key_gen_result.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/account/recovery_key_page.dart'; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import 'package:ente_auth/ui/components/models/button_type.dart'; -import 'package:ente_auth/ui/home_page.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/navigation_util.dart'; -import 'package:ente_auth/utils/platform_util.dart'; -import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_configuration/base_configuration.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/buttons/dynamic_fab.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/pages/base_home_page.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_ui/utils/toast_util.dart'; +import 'package:ente_utils/navigation_util.dart'; +import 'package:ente_utils/platform_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:logging/logging.dart'; @@ -23,9 +22,16 @@ enum PasswordEntryMode { } class PasswordEntryPage extends StatefulWidget { + final BaseConfiguration config; final PasswordEntryMode mode; + final BaseHomePage homePage; - const PasswordEntryPage({required this.mode, super.key}); + const PasswordEntryPage( + this.config, + this.mode, + this.homePage, { + super.key, + }); @override State createState() => _PasswordEntryPageState(); @@ -38,7 +44,7 @@ class _PasswordEntryPageState extends State { final _logger = Logger((_PasswordEntryPageState).toString()); final _passwordController1 = TextEditingController(), _passwordController2 = TextEditingController(); - final Color _validFieldValueColor = const Color.fromRGBO(45, 194, 98, 0.2); + Color? _validFieldValueColor; String? _volatilePassword; String _passwordInInputBox = ''; String _passwordInInputConfirmationBox = ''; @@ -56,7 +62,7 @@ class _PasswordEntryPageState extends State { @override void initState() { super.initState(); - _volatilePassword = Configuration.instance.getVolatilePassword(); + _volatilePassword = widget.config.getVolatilePassword(); if (_volatilePassword != null) { Future.delayed( Duration.zero, @@ -81,6 +87,7 @@ class _PasswordEntryPageState extends State { @override Widget build(BuildContext context) { final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100; + _validFieldValueColor = const Color.fromRGBO(45, 194, 98, 0.2); FloatingActionButtonLocation? fabLocation() { if (isKeypadOpen) { @@ -90,13 +97,13 @@ class _PasswordEntryPageState extends State { } } - String title = context.l10n.setPasswordTitle; + String title = context.strings.setPasswordTitle; if (widget.mode == PasswordEntryMode.update) { - title = context.l10n.changePasswordTitle; + title = context.strings.changePasswordTitle; } else if (widget.mode == PasswordEntryMode.reset) { - title = context.l10n.resetPasswordTitle; + title = context.strings.resetPasswordTitle; } else if (_volatilePassword != null) { - title = context.l10n.encryptionKeys; + title = context.strings.encryptionKeys; } return Scaffold( resizeToAvoidBottomInset: isKeypadOpen, @@ -132,14 +139,14 @@ class _PasswordEntryPageState extends State { } Widget _getBody(String buttonTextAndHeading) { - final email = Configuration.instance.getEmail(); - var passwordStrengthText = context.l10n.weakStrength; + final email = widget.config.getEmail(); + var passwordStrengthText = context.strings.weakStrength; var passwordStrengthColor = Colors.redAccent; if (_passwordStrength > kStrongPasswordStrengthThreshold) { - passwordStrengthText = context.l10n.strongStrength; + passwordStrengthText = context.strings.strongStrength; passwordStrengthColor = Colors.greenAccent; } else if (_passwordStrength > kMildPasswordStrengthThreshold) { - passwordStrengthText = context.l10n.moderateStrength; + passwordStrengthText = context.strings.moderateStrength; passwordStrengthColor = Colors.orangeAccent; } if (_volatilePassword != null) { @@ -167,8 +174,8 @@ class _PasswordEntryPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 20), child: Text( widget.mode == PasswordEntryMode.set - ? context.l10n.enterPasswordToEncrypt - : context.l10n.enterNewPasswordToEncrypt, + ? context.strings.enterPasswordToEncrypt + : context.strings.enterNewPasswordToEncrypt, textAlign: TextAlign.start, style: Theme.of(context) .textTheme @@ -180,7 +187,7 @@ class _PasswordEntryPageState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: StyledText( - text: context.l10n.passwordWarning, + text: context.strings.passwordWarning, style: Theme.of(context) .textTheme .titleMedium! @@ -222,10 +229,11 @@ class _PasswordEntryPageState extends State { null); }, decoration: InputDecoration( - fillColor: - _isPasswordValid ? _validFieldValueColor : null, + fillColor: _isPasswordValid + ? _validFieldValueColor + : getEnteColorScheme(context).fillFaint, filled: true, - hintText: context.l10n.password, + hintText: context.strings.password, contentPadding: const EdgeInsets.all(20), border: UnderlineInputBorder( borderSide: BorderSide.none, @@ -249,11 +257,8 @@ class _PasswordEntryPageState extends State { : _isPasswordValid ? Icon( Icons.check, - color: Theme.of(context) - .inputDecorationTheme - .focusedBorder! - .borderSide - .color, + color: + getEnteColorScheme(context).primary300, ) : null, ), @@ -288,10 +293,11 @@ class _PasswordEntryPageState extends State { onEditingComplete: () => TextInput.finishAutofillContext(), decoration: InputDecoration( - fillColor: - _passwordsMatch ? _validFieldValueColor : null, + fillColor: _passwordsMatch + ? _validFieldValueColor + : getEnteColorScheme(context).fillFaint, filled: true, - hintText: context.l10n.confirmPassword, + hintText: context.strings.confirmPassword, contentPadding: const EdgeInsets.symmetric( horizontal: 20, vertical: 20, @@ -314,11 +320,8 @@ class _PasswordEntryPageState extends State { : _passwordsMatch ? Icon( Icons.check, - color: Theme.of(context) - .inputDecorationTheme - .focusedBorder! - .borderSide - .color, + color: + getEnteColorScheme(context).primary300, ) : null, border: UnderlineInputBorder( @@ -348,7 +351,7 @@ class _PasswordEntryPageState extends State { vertical: 8, ), child: Text( - context.l10n.passwordStrength(passwordStrengthText), + context.strings.passwordStrength(passwordStrengthText), style: TextStyle( color: passwordStrengthColor, ), @@ -361,7 +364,7 @@ class _PasswordEntryPageState extends State { onTap: () { PlatformUtil.openWebView( context, - context.l10n.howItWorks, + context.strings.howItWorks, "https://ente.io/architecture", ); }, @@ -369,7 +372,7 @@ class _PasswordEntryPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 20), child: RichText( text: TextSpan( - text: context.l10n.howItWorks, + text: context.strings.howItWorks, style: Theme.of(context).textTheme.titleMedium!.copyWith( fontSize: 14, @@ -392,10 +395,10 @@ class _PasswordEntryPageState extends State { void _updatePassword() async { final logOutFromOthers = await logOutFromOtherDevices(context); final dialog = - createProgressDialog(context, context.l10n.generatingEncryptionKeys); + createProgressDialog(context, context.strings.generatingEncryptionKeys); await dialog.show(); try { - final result = await Configuration.instance + final result = await widget.config .getAttributesForNewPassword(_passwordController1.text); await UserService.instance.updateKeyAttributes( result.item1, @@ -403,7 +406,7 @@ class _PasswordEntryPageState extends State { logoutOtherDevices: logOutFromOthers, ); await dialog.hide(); - showShortToast(context, context.l10n.passwordChangedSuccessfully); + showShortToast(context, context.strings.passwordChangedSuccessfully); Navigator.of(context).pop(); if (widget.mode == PasswordEntryMode.reset) { Navigator.of(context).popUntil((route) => route.isFirst); @@ -423,15 +426,15 @@ class _PasswordEntryPageState extends State { bool logOutFromOther = true; await showChoiceDialog( context, - title: context.l10n.signOutFromOtherDevices, - body: context.l10n.signOutOtherBody, + title: context.strings.signOutFromOtherDevices, + body: context.strings.signOutOtherBody, isDismissible: false, - firstButtonLabel: context.l10n.signOutOtherDevices, + firstButtonLabel: context.strings.signOutOtherDevices, firstButtonType: ButtonType.critical, firstButtonOnTap: () async { logOutFromOther = true; }, - secondButtonLabel: context.l10n.doNotSignOut, + secondButtonLabel: context.strings.doNotSignOut, secondButtonOnTap: () async { logOutFromOther = false; }, @@ -443,30 +446,31 @@ class _PasswordEntryPageState extends State { String password, { bool usingVolatilePassword = false, }) async { - final l10n = context.l10n; - final dialog = - createProgressDialog(context, l10n.generatingEncryptionKeysTitle); + final dialog = createProgressDialog( + context, + context.strings.generatingEncryptionKeysTitle, + ); await dialog.show(); try { if (usingVolatilePassword) { _logger.info('Using volatile password'); } - final KeyGenResult result = - await Configuration.instance.generateKey(password); - Configuration.instance.resetVolatilePassword(); + final result = await widget.config.generateKey(password); + widget.config.resetVolatilePassword(); await dialog.hide(); onDone() async { - final dialog = createProgressDialog(context, l10n.pleaseWait); + final dialog = + createProgressDialog(context, context.strings.pleaseWait); await dialog.show(); try { await UserService.instance.setAttributes(result); await dialog.hide(); - Configuration.instance.resetVolatilePassword(); + widget.config.resetVolatilePassword(); // ignore: unawaited_futures Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (BuildContext context) { - return const HomePage(); + return widget.homePage; }, ), (route) => route.isFirst, @@ -486,8 +490,9 @@ class _PasswordEntryPageState extends State { routeToPage( context, RecoveryKeyPage( + widget.config, result.privateKeyAttributes.recoveryKey, - context.l10n.continueLabel, + context.strings.continueLabel, showAppBar: false, isDismissible: false, onDone: onDone, @@ -501,8 +506,8 @@ class _PasswordEntryPageState extends State { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.insecureDevice, - context.l10n.sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease, + context.strings.insecureDevice, + context.strings.sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease, ); } else { // ignore: unawaited_futures diff --git a/mobile/apps/auth/lib/ui/account/password_reentry_page.dart b/mobile/packages/accounts/lib/pages/password_reentry_page.dart similarity index 82% rename from mobile/apps/auth/lib/ui/account/password_reentry_page.dart rename to mobile/packages/accounts/lib/pages/password_reentry_page.dart index 600d6ada98..f684fc1379 100644 --- a/mobile/apps/auth/lib/ui/account/password_reentry_page.dart +++ b/mobile/packages/accounts/lib/pages/password_reentry_page.dart @@ -1,22 +1,28 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/errors.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/account/recovery_page.dart'; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import 'package:ente_auth/ui/components/buttons/button_widget.dart'; -import 'package:ente_auth/ui/home_page.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/email_util.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_accounts/models/errors.dart'; +import 'package:ente_configuration/base_configuration.dart'; import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/dynamic_fab.dart'; +import 'package:ente_ui/pages/base_home_page.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_utils/email_util.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; class PasswordReentryPage extends StatefulWidget { - const PasswordReentryPage({super.key}); + final BaseConfiguration config; + final BaseHomePage homePage; + + const PasswordReentryPage( + this.config, + this.homePage, { + super.key, + }); @override State createState() => _PasswordReentryPageState(); @@ -34,8 +40,8 @@ class _PasswordReentryPageState extends State { @override void initState() { super.initState(); - email = Configuration.instance.getEmail(); - _volatilePassword = Configuration.instance.getVolatilePassword(); + email = widget.config.getEmail(); + _volatilePassword = widget.config.getVolatilePassword(); if (_volatilePassword != null) { _passwordController.text = _volatilePassword!; Future.delayed( @@ -79,7 +85,7 @@ class _PasswordReentryPageState extends State { key: const ValueKey("verifyPasswordButton"), isKeypadOpen: isKeypadOpen, isFormValid: _passwordController.text.isNotEmpty, - buttonText: context.l10n.verifyPassword, + buttonText: context.strings.verifyPassword, onPressedFunction: () async { FocusScope.of(context).unfocus(); await verifyPassword(_passwordController.text); @@ -95,15 +101,15 @@ class _PasswordReentryPageState extends State { bool usingVolatilePassword = false, }) async { FocusScope.of(context).unfocus(); - final dialog = createProgressDialog(context, context.l10n.pleaseWait); + final dialog = createProgressDialog(context, context.strings.pleaseWait); await dialog.show(); if (usingVolatilePassword) { _logger.info("Using volatile password"); } try { - final kek = await Configuration.instance.decryptSecretsAndGetKeyEncKey( + final kek = await widget.config.decryptSecretsAndGetKeyEncKey( password, - Configuration.instance.getKeyAttributes()!, + widget.config.getKeyAttributes()!, ); _registerSRPForExistingUsers(kek).ignore(); } on KeyDerivationError catch (e, s) { @@ -111,16 +117,19 @@ class _PasswordReentryPageState extends State { await dialog.hide(); final dialogChoice = await showChoiceDialog( context, - title: context.l10n.recreatePasswordTitle, - body: context.l10n.recreatePasswordBody, - firstButtonLabel: context.l10n.useRecoveryKey, + title: context.strings.recreatePasswordTitle, + body: context.strings.recreatePasswordBody, + firstButtonLabel: context.strings.useRecoveryKey, ); if (dialogChoice!.action == ButtonAction.first) { // ignore: unawaited_futures Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) { - return const RecoveryPage(); + return RecoveryPage( + widget.config, + widget.homePage, + ); }, ), ); @@ -131,27 +140,27 @@ class _PasswordReentryPageState extends State { await dialog.hide(); final dialogChoice = await showChoiceDialog( context, - title: context.l10n.incorrectPasswordTitle, - body: context.l10n.pleaseTryAgain, - firstButtonLabel: context.l10n.contactSupport, - secondButtonLabel: context.l10n.ok, + title: context.strings.incorrectPasswordTitle, + body: context.strings.pleaseTryAgain, + firstButtonLabel: context.strings.contactSupport, + secondButtonLabel: context.strings.ok, ); if (dialogChoice!.action == ButtonAction.first) { await sendLogs( context, - context.l10n.contactSupport, + context.strings.contactSupport, postShare: () {}, ); } return; } - Configuration.instance.resetVolatilePassword(); + widget.config.resetVolatilePassword(); await dialog.hide(); unawaited( Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (BuildContext context) { - return const HomePage(); + return widget.homePage; }, ), (route) => false, @@ -190,7 +199,7 @@ class _PasswordReentryPageState extends State { padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), child: Text( - context.l10n.welcomeBack, + context.strings.welcomeBack, style: Theme.of(context).textTheme.headlineMedium, ), ), @@ -214,7 +223,7 @@ class _PasswordReentryPageState extends State { key: const ValueKey("passwordInputField"), autofillHints: const [AutofillHints.password], decoration: InputDecoration( - hintText: context.l10n.enterYourPasswordHint, + hintText: context.strings.enterYourPasswordHint, filled: true, contentPadding: const EdgeInsets.all(20), border: UnderlineInputBorder( @@ -269,14 +278,17 @@ class _PasswordReentryPageState extends State { Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) { - return const RecoveryPage(); + return RecoveryPage( + widget.config, + widget.homePage, + ); }, ), ); }, child: Center( child: Text( - context.l10n.forgotPassword, + context.strings.forgotPassword, style: Theme.of(context) .textTheme .titleMedium! @@ -292,17 +304,17 @@ class _PasswordReentryPageState extends State { onTap: () async { final dialog = createProgressDialog( context, - context.l10n.pleaseWait, + context.strings.pleaseWait, ); await dialog.show(); - await Configuration.instance.logout(); + await widget.config.logout(); await dialog.hide(); Navigator.of(context) .popUntil((route) => route.isFirst); }, child: Center( child: Text( - context.l10n.changeEmail, + context.strings.changeEmail, style: Theme.of(context) .textTheme .titleMedium! diff --git a/mobile/apps/auth/lib/ui/account/recovery_key_page.dart b/mobile/packages/accounts/lib/pages/recovery_key_page.dart similarity index 86% rename from mobile/apps/auth/lib/ui/account/recovery_key_page.dart rename to mobile/packages/accounts/lib/pages/recovery_key_page.dart index e82d3dbca9..416d8c4399 100644 --- a/mobile/apps/auth/lib/ui/account/recovery_key_page.dart +++ b/mobile/packages/accounts/lib/pages/recovery_key_page.dart @@ -3,14 +3,15 @@ import 'dart:io' as io; import 'package:bip39/bip39.dart' as bip39; import 'package:dotted_border/dotted_border.dart'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/constants.dart'; -import 'package:ente_auth/ente_theme_data.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/ui/common/gradient_button.dart'; -import 'package:ente_auth/utils/platform_util.dart'; -import 'package:ente_auth/utils/share_utils.dart'; -import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_configuration/base_configuration.dart'; +import 'package:ente_configuration/constants.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/buttons/gradient_button.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import "package:ente_ui/theme/ente_theme_data.dart"; +import 'package:ente_ui/utils/toast_util.dart'; +import 'package:ente_utils/platform_util.dart'; +import 'package:ente_utils/share_utils.dart'; import 'package:file_saver/file_saver.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -18,6 +19,7 @@ import 'package:share_plus/share_plus.dart'; import 'package:step_progress_indicator/step_progress_indicator.dart'; class RecoveryKeyPage extends StatefulWidget { + final BaseConfiguration config; final bool? showAppBar; final String recoveryKey; final String doneText; @@ -29,6 +31,7 @@ class RecoveryKeyPage extends StatefulWidget { final bool showProgressBar; const RecoveryKeyPage( + this.config, this.recoveryKey, this.doneText, { super.key, @@ -47,9 +50,15 @@ class RecoveryKeyPage extends StatefulWidget { class _RecoveryKeyPageState extends State { bool _hasTriedToSave = false; - final _recoveryKeyFile = io.File( - "${Configuration.instance.getTempDirectory()}ente-recovery-key.txt", - ); + late final io.File _recoveryKeyFile; + + @override + void initState() { + super.initState(); + _recoveryKeyFile = io.File( + "${widget.config.getTempDirectory()}ente-recovery-key.txt", + ); + } @override Widget build(BuildContext context) { @@ -73,7 +82,7 @@ class _RecoveryKeyPageState extends State { ); showShortToast( context, - context.l10n.recoveryKeyCopiedToClipboard, + context.strings.recoveryKeyCopiedToClipboard, ); setState(() { _hasTriedToSave = true; @@ -90,17 +99,17 @@ class _RecoveryKeyPageState extends State { child: StepProgressIndicator( totalSteps: 4, currentStep: 3, - selectedColor: Theme.of(context).colorScheme.alternativeColor, + selectedColor: getEnteColorScheme(context).alternativeColor, roundedEdges: const Radius.circular(10), unselectedColor: - Theme.of(context).colorScheme.stepProgressUnselectedColor, + getEnteColorScheme(context).stepProgressUnselectedColor, ), ), ) : widget.showAppBar! ? AppBar( elevation: 0, - title: Text(widget.title ?? context.l10n.recoveryKey), + title: Text(widget.title ?? context.strings.recoveryKey), ) : null, body: Padding( @@ -121,14 +130,15 @@ class _RecoveryKeyPageState extends State { widget.showAppBar! ? const SizedBox.shrink() : Text( - widget.title ?? context.l10n.recoveryKey, + widget.title ?? context.strings.recoveryKey, style: Theme.of(context).textTheme.headlineMedium, ), Padding( padding: EdgeInsets.all(widget.showAppBar! ? 0 : 12), ), Text( - widget.text ?? context.l10n.recoveryKeyOnForgotPassword, + widget.text ?? + context.strings.recoveryKeyOnForgotPassword, style: Theme.of(context).textTheme.titleMedium, ), const Padding(padding: EdgeInsets.only(top: 24)), @@ -136,14 +146,14 @@ class _RecoveryKeyPageState extends State { padding: const EdgeInsets.all(1), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), - gradient: const LinearGradient( + gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Color(0x8E9610D6), - Color(0x8E9F4FC6), + getEnteColorScheme(context).primary700, + getEnteColorScheme(context).primary300, ], - stops: [0.0, 0.9753], + stops: const [0.0, 0.9753], ), ), child: DottedBorder( @@ -207,7 +217,7 @@ class _RecoveryKeyPageState extends State { padding: const EdgeInsets.symmetric(vertical: 20), child: Text( widget.subText ?? - context.l10n.recoveryKeySaveDescription, + context.strings.recoveryKeySaveDescription, style: Theme.of(context).textTheme.bodyLarge, ), ), @@ -245,7 +255,7 @@ class _RecoveryKeyPageState extends State { onPressed: () async { await _saveKeys(); }, - child: Text(context.l10n.doThisLater), + child: Text(context.strings.doThisLater), ), ), ); @@ -257,7 +267,7 @@ class _RecoveryKeyPageState extends State { onTap: () async { await shareDialog( context, - context.l10n.recoveryKey, + context.strings.recoveryKey, saveAction: () async { await _saveRecoveryKey(recoveryKey); }, @@ -266,7 +276,7 @@ class _RecoveryKeyPageState extends State { }, ); }, - text: context.l10n.saveKey, + text: context.strings.saveKey, ), ); @@ -302,7 +312,7 @@ class _RecoveryKeyPageState extends State { if (mounted) { showToast( context, - context.l10n.recoveryKeySaved, + context.strings.recoveryKeySaved, ); setState(() { _hasTriedToSave = true; diff --git a/mobile/apps/auth/lib/ui/account/recovery_page.dart b/mobile/packages/accounts/lib/pages/recovery_page.dart similarity index 85% rename from mobile/apps/auth/lib/ui/account/recovery_page.dart rename to mobile/packages/accounts/lib/pages/recovery_page.dart index 137b8ce437..93d22991bf 100644 --- a/mobile/apps/auth/lib/ui/account/recovery_page.dart +++ b/mobile/packages/accounts/lib/pages/recovery_page.dart @@ -1,13 +1,17 @@ -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/ui/account/password_entry_page.dart'; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_configuration/base_configuration.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/buttons/dynamic_fab.dart'; +import 'package:ente_ui/pages/base_home_page.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_ui/utils/toast_util.dart'; import 'package:flutter/material.dart'; class RecoveryPage extends StatefulWidget { - const RecoveryPage({super.key}); + final BaseConfiguration config; + final BaseHomePage homePage; + + const RecoveryPage(this.config, this.homePage, {super.key}); @override State createState() => _RecoveryPageState(); @@ -21,16 +25,18 @@ class _RecoveryPageState extends State { final dialog = createProgressDialog(context, "Decrypting..."); await dialog.show(); try { - await Configuration.instance.recover(_recoveryKey.text.trim()); + await widget.config.recover(_recoveryKey.text.trim()); await dialog.hide(); showToast(context, "Recovery successful!"); await Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (BuildContext context) { - return const PopScope( + return PopScope( canPop: false, child: PasswordEntryPage( - mode: PasswordEntryMode.reset, + widget.config, + PasswordEntryMode.reset, + widget.homePage, ), ); }, @@ -86,7 +92,7 @@ class _RecoveryPageState extends State { padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), child: Text( - context.l10n.forgotPassword, + context.strings.forgotPassword, style: Theme.of(context).textTheme.headlineMedium, ), ), @@ -137,7 +143,7 @@ class _RecoveryPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 20), child: Center( child: Text( - context.l10n.noRecoveryKeyTitle, + context.strings.noRecoveryKeyTitle, style: Theme.of(context) .textTheme .titleMedium! diff --git a/mobile/apps/auth/lib/ui/account/request_pwd_verification_page.dart b/mobile/packages/accounts/lib/pages/request_pwd_verification_page.dart similarity index 89% rename from mobile/apps/auth/lib/ui/account/request_pwd_verification_page.dart rename to mobile/packages/accounts/lib/pages/request_pwd_verification_page.dart index 5901d3bd45..d3d23a1096 100644 --- a/mobile/apps/auth/lib/ui/account/request_pwd_verification_page.dart +++ b/mobile/packages/accounts/lib/pages/request_pwd_verification_page.dart @@ -1,22 +1,24 @@ import "dart:convert"; import "dart:typed_data"; -import 'package:ente_auth/core/configuration.dart'; -import "package:ente_auth/l10n/l10n.dart"; -import "package:ente_auth/theme/ente_theme.dart"; -import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import "package:ente_auth/utils/dialog_util.dart"; +import "package:ente_configuration/base_configuration.dart"; import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import "package:ente_strings/ente_strings.dart"; +import "package:ente_ui/components/buttons/dynamic_fab.dart"; +import "package:ente_ui/theme/ente_theme.dart"; +import "package:ente_ui/utils/dialog_util.dart"; import 'package:flutter/material.dart'; import "package:logging/logging.dart"; typedef OnPasswordVerifiedFn = Future Function(Uint8List bytes); class RequestPasswordVerificationPage extends StatefulWidget { + final BaseConfiguration config; final OnPasswordVerifiedFn onPasswordVerified; final Function? onPasswordError; - const RequestPasswordVerificationPage({ + const RequestPasswordVerificationPage( + this.config, { super.key, required this.onPasswordVerified, this.onPasswordError, @@ -39,7 +41,7 @@ class _RequestPasswordVerificationPageState @override void initState() { super.initState(); - email = Configuration.instance.getEmail(); + email = widget.config.getEmail(); _passwordFocusNode.addListener(() { setState(() { _passwordInFocus = _passwordFocusNode.hasFocus; @@ -76,13 +78,14 @@ class _RequestPasswordVerificationPageState key: const ValueKey("verifyPasswordButton"), isKeypadOpen: isKeypadOpen, isFormValid: _passwordController.text.isNotEmpty, - buttonText: context.l10n.verifyPassword, + buttonText: context.strings.verifyPassword, onPressedFunction: () async { FocusScope.of(context).unfocus(); - final dialog = createProgressDialog(context, context.l10n.pleaseWait); + final dialog = + createProgressDialog(context, context.strings.pleaseWait); await dialog.show(); try { - final attributes = Configuration.instance.getKeyAttributes()!; + final attributes = widget.config.getKeyAttributes()!; final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey( utf8.encode(_passwordController.text), CryptoUtil.base642bin(attributes.kekSalt), @@ -108,8 +111,8 @@ class _RequestPasswordVerificationPageState // ignore: unawaited_futures showErrorDialog( context, - context.l10n.incorrectPasswordTitle, - context.l10n.pleaseTryAgain, + context.strings.incorrectPasswordTitle, + context.strings.pleaseTryAgain, ); } } @@ -130,7 +133,7 @@ class _RequestPasswordVerificationPageState Padding( padding: const EdgeInsets.only(top: 30, left: 20, right: 20), child: Text( - context.l10n.enterPassword, + context.strings.enterPassword, style: Theme.of(context).textTheme.headlineMedium, ), ), @@ -165,7 +168,7 @@ class _RequestPasswordVerificationPageState key: const ValueKey("passwordInputField"), autofillHints: const [AutofillHints.password], decoration: InputDecoration( - hintText: context.l10n.enterYourPasswordHint, + hintText: context.strings.enterYourPasswordHint, filled: true, contentPadding: const EdgeInsets.all(20), border: UnderlineInputBorder( diff --git a/mobile/apps/auth/lib/ui/account/sessions_page.dart b/mobile/packages/accounts/lib/pages/sessions_page.dart similarity index 80% rename from mobile/apps/auth/lib/ui/account/sessions_page.dart rename to mobile/packages/accounts/lib/pages/sessions_page.dart index 1aae822af4..1e9409f7c3 100644 --- a/mobile/apps/auth/lib/ui/account/sessions_page.dart +++ b/mobile/packages/accounts/lib/pages/sessions_page.dart @@ -1,17 +1,20 @@ -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/ente_theme_data.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/sessions.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/common/loading_widget.dart'; -import 'package:ente_auth/utils/date_time_util.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_configuration/base_configuration.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/loading_widget.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_ui/utils/toast_util.dart'; +import 'package:ente_utils/date_time_util.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; class SessionsPage extends StatefulWidget { - const SessionsPage({super.key}); + final BaseConfiguration config; + const SessionsPage( + this.config, { + super.key, + }); @override State createState() => _SessionsPageState(); @@ -34,7 +37,7 @@ class _SessionsPageState extends State { return Scaffold( appBar: AppBar( elevation: 0, - title: Text(context.l10n.activeSessions), + title: Text(context.strings.activeSessions), ), body: _getBody(), ); @@ -112,7 +115,7 @@ class _SessionsPageState extends State { } Future _terminateSession(Session session) async { - final dialog = createProgressDialog(context, context.l10n.pleaseWait); + final dialog = createProgressDialog(context, context.strings.pleaseWait); await dialog.show(); try { await UserService.instance.terminateSession(session.token); @@ -124,8 +127,8 @@ class _SessionsPageState extends State { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.oops, - context.l10n.somethingWentWrongPleaseTryAgain, + context.strings.oops, + context.strings.somethingWentWrongPleaseTryAgain, ); } } @@ -147,18 +150,18 @@ class _SessionsPageState extends State { void _showSessionTerminationDialog(Session session) { final isLoggingOutFromThisDevice = - session.token == Configuration.instance.getToken(); + session.token == widget.config.getToken(); Widget text; if (isLoggingOutFromThisDevice) { text = Text( - context.l10n.thisWillLogYouOutOfThisDevice, + context.strings.thisWillLogYouOutOfThisDevice, ); } else { text = SingleChildScrollView( child: Column( children: [ Text( - context.l10n.thisWillLogYouOutOfTheFollowingDevice, + context.strings.thisWillLogYouOutOfTheFollowingDevice, ), const Padding(padding: EdgeInsets.all(8)), Text( @@ -170,12 +173,12 @@ class _SessionsPageState extends State { ); } final AlertDialog alert = AlertDialog( - title: Text(context.l10n.terminateSession), + title: Text(context.strings.terminateSession), content: text, actions: [ TextButton( child: Text( - context.l10n.terminate, + context.strings.terminate, style: const TextStyle( color: Colors.red, ), @@ -191,11 +194,11 @@ class _SessionsPageState extends State { ), TextButton( child: Text( - context.l10n.cancel, + context.strings.cancel, style: TextStyle( color: isLoggingOutFromThisDevice - ? Theme.of(context).colorScheme.alternativeColor - : Theme.of(context).colorScheme.defaultTextColor, + ? getEnteColorScheme(context).alternativeColor + : getEnteColorScheme(context).textBase, ), ), onPressed: () { @@ -214,12 +217,12 @@ class _SessionsPageState extends State { } Widget _getUAWidget(Session session) { - if (session.token == Configuration.instance.getToken()) { + if (session.token == widget.config.getToken()) { return Text( - context.l10n.thisDevice, + context.strings.thisDevice, style: TextStyle( fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.alternativeColor, + color: getEnteColorScheme(context).alternativeColor, ), ); } diff --git a/mobile/apps/auth/lib/ui/two_factor_authentication_page.dart b/mobile/packages/accounts/lib/pages/two_factor_authentication_page.dart similarity index 77% rename from mobile/apps/auth/lib/ui/two_factor_authentication_page.dart rename to mobile/packages/accounts/lib/pages/two_factor_authentication_page.dart index 86dfa503e9..f1fa4f327e 100644 --- a/mobile/apps/auth/lib/ui/two_factor_authentication_page.dart +++ b/mobile/packages/accounts/lib/pages/two_factor_authentication_page.dart @@ -1,7 +1,7 @@ -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/account/two_factor.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/ui/lifecycle_event_handler.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/lifecycle_event_handler.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:pinput/pinput.dart'; @@ -19,14 +19,6 @@ class TwoFactorAuthenticationPage extends StatefulWidget { class _TwoFactorAuthenticationPageState extends State { final _pinController = TextEditingController(); - final _pinPutDecoration = PinTheme( - height: 45, - width: 45, - decoration: BoxDecoration( - border: Border.all(color: const Color.fromRGBO(45, 194, 98, 1.0)), - borderRadius: BorderRadius.circular(15.0), - ), - ); String _code = ""; late LifecycleEventHandler _lifecycleEventHandler; @@ -54,26 +46,36 @@ class _TwoFactorAuthenticationPageState @override Widget build(BuildContext context) { - final l10n = context.l10n; + final colorScheme = getEnteColorScheme(context); + + final pinPutDecoration = PinTheme( + height: 45, + width: 45, + decoration: BoxDecoration( + border: Border.all(color: colorScheme.primary500), + borderRadius: BorderRadius.circular(15.0), + ), + ); + return Scaffold( appBar: AppBar( title: Text( - l10n.twoFactorAuthTitle, + context.strings.twoFactorAuthTitle, ), ), - body: _getBody(), + body: _getBody(pinPutDecoration), ); } - Widget _getBody() { - final l10n = context.l10n; + Widget _getBody(PinTheme pinPutDecoration) { + final colorScheme = getEnteColorScheme(context); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ Text( - l10n.enterCodeHint, + context.strings.enterCodeHint, style: const TextStyle( height: 1.4, fontSize: 16, @@ -94,20 +96,20 @@ class _TwoFactorAuthenticationPageState }); }, controller: _pinController, - submittedPinTheme: _pinPutDecoration.copyWith( + submittedPinTheme: pinPutDecoration.copyWith( decoration: BoxDecoration( borderRadius: BorderRadius.circular(20.0), border: Border.all( - color: const Color.fromRGBO(45, 194, 98, 0.5), + color: colorScheme.primary500.withOpacity(0.5), ), ), ), - defaultPinTheme: _pinPutDecoration, - followingPinTheme: _pinPutDecoration.copyWith( + defaultPinTheme: pinPutDecoration, + followingPinTheme: pinPutDecoration.copyWith( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), border: Border.all( - color: const Color.fromRGBO(45, 194, 98, 0.5), + color: colorScheme.primary500.withOpacity(0.5), ), ), ), @@ -125,7 +127,7 @@ class _TwoFactorAuthenticationPageState await _verifyTwoFactorCode(_code); } : null, - child: Text(l10n.verify), + child: Text(context.strings.verify), ), ), const Padding(padding: EdgeInsets.all(30)), @@ -142,7 +144,7 @@ class _TwoFactorAuthenticationPageState padding: const EdgeInsets.all(10), child: Center( child: Text( - l10n.lostDeviceTitle, + context.strings.lostDeviceTitle, style: const TextStyle( decoration: TextDecoration.underline, fontSize: 12, diff --git a/mobile/apps/auth/lib/ui/two_factor_recovery_page.dart b/mobile/packages/accounts/lib/pages/two_factor_recovery_page.dart similarity index 87% rename from mobile/apps/auth/lib/ui/two_factor_recovery_page.dart rename to mobile/packages/accounts/lib/pages/two_factor_recovery_page.dart index a00245471f..c4ef3eec76 100644 --- a/mobile/apps/auth/lib/ui/two_factor_recovery_page.dart +++ b/mobile/packages/accounts/lib/pages/two_factor_recovery_page.dart @@ -1,8 +1,7 @@ -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/account/two_factor.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/theme/ente_theme.dart'; -import 'package:ente_auth/utils/email_util.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_utils/email_util.dart'; import 'package:flutter/material.dart'; class TwoFactorRecoveryPage extends StatefulWidget { @@ -28,11 +27,10 @@ class _TwoFactorRecoveryPageState extends State { @override Widget build(BuildContext context) { - final l10n = context.l10n; return Scaffold( appBar: AppBar( title: Text( - l10n.recoverAccount, + context.strings.recoverAccount, style: const TextStyle( fontSize: 18, ), @@ -47,7 +45,7 @@ class _TwoFactorRecoveryPageState extends State { padding: const EdgeInsets.fromLTRB(60, 0, 60, 0), child: TextFormField( decoration: InputDecoration( - hintText: l10n.enterRecoveryKeyHint, + hintText: context.strings.enterRecoveryKeyHint, contentPadding: const EdgeInsets.all(20), ), style: const TextStyle( @@ -82,7 +80,7 @@ class _TwoFactorRecoveryPageState extends State { ); } : null, - child: Text(l10n.recover), + child: Text(context.strings.recover), ), ), GestureDetector( @@ -94,7 +92,7 @@ class _TwoFactorRecoveryPageState extends State { padding: const EdgeInsets.all(40), child: Center( child: Text( - l10n.noRecoveryKeyTitle, + context.strings.noRecoveryKeyTitle, style: TextStyle( decoration: TextDecoration.underline, fontSize: 12, diff --git a/mobile/apps/auth/lib/services/passkey_service.dart b/mobile/packages/accounts/lib/services/passkey_service.dart similarity index 91% rename from mobile/apps/auth/lib/services/passkey_service.dart rename to mobile/packages/accounts/lib/services/passkey_service.dart index dd5a4e5f7a..427272a743 100644 --- a/mobile/apps/auth/lib/services/passkey_service.dart +++ b/mobile/packages/accounts/lib/services/passkey_service.dart @@ -1,6 +1,6 @@ -import 'package:ente_auth/core/constants.dart'; -import 'package:ente_auth/core/network.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; +import 'package:ente_accounts/services/user_service.dart'; +import 'package:ente_network/network.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; import 'package:url_launcher/url_launcher_string.dart'; diff --git a/mobile/apps/auth/lib/services/user_service.dart b/mobile/packages/accounts/lib/services/user_service.dart similarity index 82% rename from mobile/apps/auth/lib/services/user_service.dart rename to mobile/packages/accounts/lib/services/user_service.dart index 0326023efa..7bc0a54c64 100644 --- a/mobile/apps/auth/lib/services/user_service.dart +++ b/mobile/packages/accounts/lib/services/user_service.dart @@ -4,35 +4,35 @@ import "dart:math"; import 'package:bip39/bip39.dart' as bip39; import 'package:dio/dio.dart'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/constants.dart'; -import 'package:ente_auth/core/errors.dart'; -import 'package:ente_auth/core/event_bus.dart'; -import 'package:ente_auth/core/network.dart'; -import 'package:ente_auth/events/user_details_changed_event.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/models/account/two_factor.dart'; -import 'package:ente_auth/models/api/user/srp.dart'; -import 'package:ente_auth/models/delete_account.dart'; -import 'package:ente_auth/models/key_attributes.dart'; -import 'package:ente_auth/models/key_gen_result.dart'; -import 'package:ente_auth/models/sessions.dart'; -import 'package:ente_auth/models/set_keys_request.dart'; -import 'package:ente_auth/models/set_recovery_key_request.dart'; -import 'package:ente_auth/models/user_details.dart'; -import 'package:ente_auth/ui/account/login_page.dart'; -import 'package:ente_auth/ui/account/ott_verification_page.dart'; -import 'package:ente_auth/ui/account/password_entry_page.dart'; -import 'package:ente_auth/ui/account/password_reentry_page.dart'; -import 'package:ente_auth/ui/account/recovery_page.dart'; -import 'package:ente_auth/ui/common/progress_dialog.dart'; -import 'package:ente_auth/ui/home_page.dart'; -import 'package:ente_auth/ui/passkey_page.dart'; -import 'package:ente_auth/ui/two_factor_authentication_page.dart'; -import 'package:ente_auth/ui/two_factor_recovery_page.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_accounts/models/delete_account.dart'; +import 'package:ente_accounts/models/errors.dart'; +import 'package:ente_accounts/models/sessions.dart'; +import 'package:ente_accounts/models/set_keys_request.dart'; +import 'package:ente_accounts/models/set_recovery_key_request.dart'; +import 'package:ente_accounts/models/srp.dart'; +import 'package:ente_accounts/models/two_factor.dart'; +import 'package:ente_accounts/models/user_details.dart'; +import 'package:ente_accounts/pages/login_page.dart'; +import 'package:ente_accounts/pages/ott_verification_page.dart'; +import 'package:ente_accounts/pages/passkey_page.dart'; +import 'package:ente_accounts/pages/password_entry_page.dart'; +import 'package:ente_accounts/pages/password_reentry_page.dart'; +import 'package:ente_accounts/pages/recovery_page.dart'; +import 'package:ente_accounts/pages/two_factor_authentication_page.dart'; +import 'package:ente_accounts/pages/two_factor_recovery_page.dart'; +import 'package:ente_base/models/key_attributes.dart'; +import 'package:ente_base/models/key_gen_result.dart'; +import 'package:ente_configuration/base_configuration.dart'; +import 'package:ente_configuration/constants.dart'; import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_events/event_bus.dart'; +import 'package:ente_events/models/user_details_changed_event.dart'; +import 'package:ente_network/network.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/progress_dialog.dart'; +import 'package:ente_ui/pages/base_home_page.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_ui/utils/toast_util.dart'; import "package:flutter/foundation.dart"; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; @@ -44,6 +44,8 @@ import "package:pointycastle/srp/srp6_verifier_generator.dart"; import 'package:shared_preferences/shared_preferences.dart'; import "package:uuid/uuid.dart"; +const String kAccountsUrl = "https://accounts.ente.io"; + class UserService { static const keyHasEnabledTwoFactor = "has_enabled_two_factor"; static const keyUserDetails = "user_details"; @@ -54,18 +56,19 @@ class UserService { final _dio = Network.instance.getDio(); final _enteDio = Network.instance.enteDio; final _logger = Logger((UserService).toString()); - final _config = Configuration.instance; late SharedPreferences _preferences; - late ValueNotifier emailValueNotifier; + late BaseConfiguration _config; + late BaseHomePage _homePage; UserService._privateConstructor(); static final UserService instance = UserService._privateConstructor(); - Future init() async { - emailValueNotifier = - ValueNotifier(Configuration.instance.getEmail()); + Future init(BaseConfiguration config, BaseHomePage homePage) async { + _config = config; + _homePage = homePage; + emailValueNotifier = ValueNotifier(config.getEmail()); _preferences = await SharedPreferences.getInstance(); } @@ -77,7 +80,7 @@ class UserService { bool isResetPasswordScreen = false, String? purpose, }) async { - final dialog = createProgressDialog(context, context.l10n.pleaseWait); + final dialog = createProgressDialog(context, context.strings.pleaseWait); await dialog.show(); try { final response = await _dio.post( @@ -114,24 +117,24 @@ class UserService { unawaited( showErrorDialog( context, - context.l10n.oops, - context.l10n.emailAlreadyRegistered, + context.strings.oops, + context.strings.emailAlreadyRegistered, ), ); } else if (enteErrCode != null && enteErrCode == "USER_NOT_REGISTERED") { unawaited( showErrorDialog( context, - context.l10n.oops, - context.l10n.emailNotRegistered, + context.strings.oops, + context.strings.emailNotRegistered, ), ); } else if (e.response != null && e.response!.statusCode == 403) { unawaited( showErrorDialog( context, - context.l10n.oops, - context.l10n.thisEmailIsAlreadyInUse, + context.strings.oops, + context.strings.thisEmailIsAlreadyInUse, ), ); } else { @@ -194,6 +197,13 @@ class UserService { } } + UserDetails? getCachedUserDetails() { + if (_preferences.containsKey(keyUserDetails)) { + return UserDetails.fromJson(_preferences.getString(keyUserDetails)!); + } + return null; + } + Future getActiveSessions() async { try { final response = await _enteDio.get("/users/sessions"); @@ -231,7 +241,7 @@ class UserService { try { final response = await _enteDio.post("/users/logout"); if (response.statusCode == 200) { - await Configuration.instance.logout(); + await _config.logout(); Navigator.of(context).popUntil((route) => route.isFirst); } else { throw Exception("Log out action failed"); @@ -240,7 +250,7 @@ class UserService { _logger.severe(e); // check if token is already invalid if (e is DioException && e.response?.statusCode == 401) { - await Configuration.instance.logout(); + await _config.logout(); Navigator.of(context).popUntil((route) => route.isFirst); return; } @@ -290,7 +300,7 @@ class UserService { ); if (response.statusCode == 200) { // clear data - await Configuration.instance.logout(); + await _config.logout(); } else { throw Exception("delete action failed"); } @@ -327,7 +337,7 @@ class UserService { Future onPassKeyVerified(BuildContext context, Map response) async { final ProgressDialog dialog = - createProgressDialog(context, context.l10n.pleaseWait); + createProgressDialog(context, context.strings.pleaseWait); await dialog.show(); try { final userPassword = _config.getVolatilePassword(); @@ -338,7 +348,10 @@ class UserService { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (BuildContext context) { - return const PasswordReentryPage(); + return PasswordReentryPage( + _config, + _homePage, + ); }, ), (route) => route.isFirst, @@ -351,7 +364,7 @@ class UserService { _config.getKeyAttributes()!, ); _config.resetVolatilePassword(); - page = const HomePage(); + page = _homePage; } else { throw Exception("unexpected response during passkey verification"); } @@ -379,7 +392,7 @@ class UserService { String ott, { bool isResettingPasswordScreen = false, }) async { - final dialog = createProgressDialog(context, context.l10n.pleaseWait); + final dialog = createProgressDialog(context, context.strings.pleaseWait); await dialog.show(); final verifyData = { "email": _config.getEmail(), @@ -405,6 +418,7 @@ class UserService { } if (passkeySessionID.isNotEmpty) { page = PasskeyPage( + _config, passkeySessionID, totp2FASessionID: twoFASessionID, accountsUrl: accountsUrl, @@ -413,15 +427,23 @@ class UserService { page = TwoFactorAuthenticationPage(twoFASessionID); } else { await _saveConfiguration(response); - if (Configuration.instance.getEncryptedToken() != null) { + if (_config.getEncryptedToken() != null) { if (isResettingPasswordScreen) { - page = const RecoveryPage(); + page = RecoveryPage( + _config, + _homePage, + ); } else { - page = const PasswordReentryPage(); + page = PasswordReentryPage( + _config, + _homePage, + ); } } else { - page = const PasswordEntryPage( - mode: PasswordEntryMode.set, + page = PasswordEntryPage( + _config, + PasswordEntryMode.set, + _homePage, ); } } @@ -444,16 +466,16 @@ class UserService { if (e.response != null && e.response!.statusCode == 410) { await showErrorDialog( context, - context.l10n.oops, - context.l10n.yourVerificationCodeHasExpired, + context.strings.oops, + context.strings.yourVerificationCodeHasExpired, ); Navigator.of(context).pop(); } else { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.incorrectCode, - context.l10n.sorryTheCodeYouveEnteredIsIncorrect, + context.strings.incorrectCode, + context.strings.sorryTheCodeYouveEnteredIsIncorrect, ); } } catch (e) { @@ -462,8 +484,8 @@ class UserService { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.oops, - context.l10n.verificationFailedPleaseTryAgain, + context.strings.oops, + context.strings.verificationFailedPleaseTryAgain, ); } } @@ -478,7 +500,7 @@ class UserService { String email, String ott, ) async { - final dialog = createProgressDialog(context, context.l10n.pleaseWait); + final dialog = createProgressDialog(context, context.strings.pleaseWait); await dialog.show(); try { final response = await _enteDio.post( @@ -490,7 +512,7 @@ class UserService { ); await dialog.hide(); if (response.statusCode == 200) { - showShortToast(context, context.l10n.emailChangedTo(email)); + showShortToast(context, context.strings.emailChangedTo(email)); await setEmail(email); Navigator.of(context).popUntil((route) => route.isFirst); Bus.instance.fire(UserDetailsChangedEvent()); @@ -499,8 +521,8 @@ class UserService { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.oops, - context.l10n.verificationFailedPleaseTryAgain, + context.strings.oops, + context.strings.verificationFailedPleaseTryAgain, ); } on DioException catch (e) { await dialog.hide(); @@ -508,15 +530,15 @@ class UserService { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.oops, - context.l10n.thisEmailIsAlreadyInUse, + context.strings.oops, + context.strings.thisEmailIsAlreadyInUse, ); } else { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.incorrectCode, - context.l10n.authenticationFailedPleaseTryAgain, + context.strings.incorrectCode, + context.strings.authenticationFailedPleaseTryAgain, ); } } catch (e) { @@ -525,8 +547,8 @@ class UserService { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.oops, - context.l10n.verificationFailedPleaseTryAgain, + context.strings.oops, + context.strings.verificationFailedPleaseTryAgain, ); } } @@ -718,9 +740,10 @@ class UserService { response.data["twoFactorSessionIDV2"] != null) { twoFASessionID = response.data["twoFactorSessionIDV2"]; } - Configuration.instance.setVolatilePassword(userPassword); + _config.setVolatilePassword(userPassword); if (passkeySessionID.isNotEmpty) { page = PasskeyPage( + _config, passkeySessionID, totp2FASessionID: twoFASessionID, accountsUrl: accountsUrl, @@ -729,13 +752,13 @@ class UserService { page = TwoFactorAuthenticationPage(twoFASessionID); } else { await _saveConfiguration(response); - if (Configuration.instance.getEncryptedToken() != null) { - await Configuration.instance.decryptSecretsAndGetKeyEncKey( + if (_config.getEncryptedToken() != null) { + await _config.decryptSecretsAndGetKeyEncKey( userPassword, - Configuration.instance.getKeyAttributes()!, + _config.getKeyAttributes()!, keyEncryptionKey: keyEncryptionKey, ); - page = const HomePage(); + page = _homePage; } else { throw Exception("unexpected response during email verification"); } @@ -805,7 +828,7 @@ class UserService { String sessionID, String code, ) async { - final dialog = createProgressDialog(context, context.l10n.pleaseWait); + final dialog = createProgressDialog(context, context.strings.pleaseWait); await dialog.show(); try { final response = await _dio.post( @@ -817,13 +840,16 @@ class UserService { ); await dialog.hide(); if (response.statusCode == 200) { - showShortToast(context, context.l10n.authenticationSuccessful); + showShortToast(context, context.strings.authenticationSuccessful); await _saveConfiguration(response); // ignore: unawaited_futures Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (BuildContext context) { - return const PasswordReentryPage(); + return PasswordReentryPage( + _config, + _homePage, + ); }, ), (route) => route.isFirst, @@ -838,7 +864,7 @@ class UserService { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (BuildContext context) { - return const LoginPage(); + return LoginPage(_config); }, ), (route) => route.isFirst, @@ -847,8 +873,8 @@ class UserService { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.incorrectCode, - context.l10n.authenticationFailedPleaseTryAgain, + context.strings.incorrectCode, + context.strings.authenticationFailedPleaseTryAgain, ); } } catch (e) { @@ -857,8 +883,8 @@ class UserService { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.oops, - context.l10n.authenticationFailedPleaseTryAgain, + context.strings.oops, + context.strings.authenticationFailedPleaseTryAgain, ); } } @@ -868,7 +894,7 @@ class UserService { String sessionID, TwoFactorType type, ) async { - final dialog = createProgressDialog(context, context.l10n.pleaseWait); + final dialog = createProgressDialog(context, context.strings.pleaseWait); await dialog.show(); try { final response = await _dio.get( @@ -899,12 +925,12 @@ class UserService { await dialog.hide(); _logger.severe(e); if (e.response != null && e.response!.statusCode == 404) { - showToast(context, context.l10n.sessionExpired); + showToast(context, context.strings.sessionExpired); // ignore: unawaited_futures Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (BuildContext context) { - return const LoginPage(); + return LoginPage(_config); }, ), (route) => route.isFirst, @@ -913,8 +939,8 @@ class UserService { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.oops, - context.l10n.somethingWentWrongPleaseTryAgain, + context.strings.oops, + context.strings.somethingWentWrongPleaseTryAgain, ); } } catch (e) { @@ -923,8 +949,8 @@ class UserService { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.oops, - context.l10n.somethingWentWrongPleaseTryAgain, + context.strings.oops, + context.strings.somethingWentWrongPleaseTryAgain, ); } finally { await dialog.hide(); @@ -939,7 +965,7 @@ class UserService { String encryptedSecret, String secretDecryptionNonce, ) async { - final dialog = createProgressDialog(context, context.l10n.pleaseWait); + final dialog = createProgressDialog(context, context.strings.pleaseWait); await dialog.show(); String secret; try { @@ -962,8 +988,8 @@ class UserService { await dialog.hide(); await showErrorDialog( context, - context.l10n.incorrectRecoveryKey, - context.l10n.theRecoveryKeyYouEnteredIsIncorrect, + context.strings.incorrectRecoveryKey, + context.strings.theRecoveryKeyYouEnteredIsIncorrect, ); return; } @@ -980,14 +1006,17 @@ class UserService { if (response.statusCode == 200) { showShortToast( context, - context.l10n.twofactorAuthenticationSuccessfullyReset, + context.strings.twofactorAuthenticationSuccessfullyReset, ); await _saveConfiguration(response); // ignore: unawaited_futures Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (BuildContext context) { - return const PasswordReentryPage(); + return PasswordReentryPage( + _config, + _homePage, + ); }, ), (route) => route.isFirst, @@ -1002,7 +1031,7 @@ class UserService { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (BuildContext context) { - return const LoginPage(); + return LoginPage(_config); }, ), (route) => route.isFirst, @@ -1011,8 +1040,8 @@ class UserService { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.oops, - context.l10n.somethingWentWrongPleaseTryAgain, + context.strings.oops, + context.strings.somethingWentWrongPleaseTryAgain, ); } } catch (e) { @@ -1021,8 +1050,8 @@ class UserService { // ignore: unawaited_futures showErrorDialog( context, - context.l10n.oops, - context.l10n.somethingWentWrongPleaseTryAgain, + context.strings.oops, + context.strings.somethingWentWrongPleaseTryAgain, ); } finally { await dialog.hide(); @@ -1033,15 +1062,14 @@ class UserService { final responseData = response is Map ? response : response.data as Map?; if (responseData == null) return; - await Configuration.instance.setUserID(responseData["id"]); + await _config.setUserID(responseData["id"]); if (responseData["encryptedToken"] != null) { - await Configuration.instance - .setEncryptedToken(responseData["encryptedToken"]); - await Configuration.instance.setKeyAttributes( + await _config.setEncryptedToken(responseData["encryptedToken"]); + await _config.setKeyAttributes( KeyAttributes.fromMap(responseData["keyAttributes"]), ); } else { - await Configuration.instance.setToken(responseData["token"]); + await _config.setToken(responseData["token"]); } } diff --git a/mobile/packages/accounts/pubspec.lock b/mobile/packages/accounts/pubspec.lock new file mode 100644 index 0000000000..641615d91f --- /dev/null +++ b/mobile/packages/accounts/pubspec.lock @@ -0,0 +1,1288 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + app_links: + dependency: "direct main" + description: + name: app_links + sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba" + url: "https://pub.dev" + source: hosted + version: "6.4.0" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + bip39: + dependency: "direct main" + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: "direct main" + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cronet_http: + dependency: transitive + description: + name: cronet_http + sha256: df26af0de7c4eff46c53c190b5590e22457bfce6ea679aedb1e6326197f27d6f + url: "https://pub.dev" + source: hosted + version: "1.4.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_http: + dependency: transitive + description: + name: cupertino_http + sha256: "8fb9e2c36d0732d9d96abd76683406b57e78a2514e27c962e0c603dbe6f2e3f8" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + dio: + dependency: "direct main" + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + dotted_border: + dependency: "direct main" + description: + name: dotted_border + sha256: "99b091ec6891ba0c5331fdc2b502993c7c108f898995739a73c6845d71dad70c" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + email_validator: + dependency: "direct main" + description: + name: email_validator + sha256: b19aa5d92fdd76fbc65112060c94d45ba855105a28bb6e462de7ff03b12fa1fb + url: "https://pub.dev" + source: hosted + version: "3.0.0" + ente_base: + dependency: "direct main" + description: + path: "../base" + relative: true + source: path + version: "1.0.0" + ente_configuration: + dependency: "direct main" + description: + path: "../configuration" + relative: true + source: path + version: "1.0.0" + ente_crypto_dart: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: f91e1545f8263df127762240c4da54a0c42835b2 + url: "https://github.com/ente-io/ente_crypto_dart.git" + source: git + version: "1.0.0" + ente_events: + dependency: "direct main" + description: + path: "../events" + relative: true + source: path + version: "1.0.0" + ente_lock_screen: + dependency: "direct main" + description: + path: "../lock_screen" + relative: true + source: path + version: "1.0.0" + ente_logging: + dependency: transitive + description: + path: "../logging" + relative: true + source: path + version: "1.0.0" + ente_network: + dependency: "direct main" + description: + path: "../network" + relative: true + source: path + version: "1.0.0" + ente_strings: + dependency: "direct main" + description: + path: "../strings" + relative: true + source: path + version: "1.0.0" + ente_ui: + dependency: "direct main" + description: + path: "../ui" + relative: true + source: path + version: "1.0.0" + ente_utils: + dependency: "direct main" + description: + path: "../utils" + relative: true + source: path + version: "1.0.0" + event_bus: + dependency: transitive + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + expandable: + dependency: transitive + description: + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_saver: + dependency: "direct main" + description: + name: file_saver + sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_animate: + dependency: transitive + description: + name: flutter_animate + sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" + url: "https://pub.dev" + source: hosted + version: "4.5.2" + flutter_email_sender: + dependency: transitive + description: + name: flutter_email_sender + sha256: d39eb5e91358fc19ec4050da69accec21f9d5b2b6bcf188aa246327b6ca2352c + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter_inappwebview: + dependency: transitive + description: + name: flutter_inappwebview + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_windows: + dependency: transitive + description: + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_local_authentication: + dependency: transitive + description: + path: "." + ref: "1ac346a04592a05fd75acccf2e01fa3c7e955d96" + resolved-ref: "1ac346a04592a05fd75acccf2e01fa3c7e955d96" + url: "https://github.com/eaceto/flutter_local_authentication" + source: git + version: "1.2.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "1c2b787f99bdca1f3718543f81d38aa1b124817dfeb9fb196201bea85b6134bf" + url: "https://pub.dev" + source: hosted + version: "2.0.26" + flutter_secure_storage: + dependency: transitive + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" + url: "https://pub.dev" + source: hosted + version: "0.1.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: transitive + description: + name: fluttertoast + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + url: "https://pub.dev" + source: hosted + version: "8.2.12" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.dev" + source: hosted + version: "2.1.0" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + http_profile: + dependency: transitive + description: + name: http_profile + sha256: "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + jni: + dependency: transitive + description: + name: jni + sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1 + url: "https://pub.dev" + source: hosted + version: "0.14.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + local_auth: + dependency: transitive + description: + name: local_auth + sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + local_auth_android: + dependency: transitive + description: + name: local_auth_android + sha256: "8bba79f4f0f7bc812fce2ca20915d15618c37721246ba6c3ef2aa7a763a90cf2" + url: "https://pub.dev" + source: hosted + version: "1.0.47" + local_auth_darwin: + dependency: transitive + description: + name: local_auth_darwin + sha256: "630996cd7b7f28f5ab92432c4b35d055dd03a747bc319e5ffbb3c4806a3e50d2" + url: "https://pub.dev" + source: hosted + version: "1.4.3" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" + url: "https://pub.dev" + source: hosted + version: "1.0.10" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + url: "https://pub.dev" + source: hosted + version: "1.0.11" + logging: + dependency: "direct main" + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + modal_bottom_sheet: + dependency: transitive + description: + name: modal_bottom_sheet + sha256: eac66ef8cb0461bf069a38c5eb0fa728cee525a531a8304bd3f7b2185407c67e + url: "https://pub.dev" + source: hosted + version: "3.0.0" + native_dio_adapter: + dependency: transitive + description: + name: native_dio_adapter + sha256: "7420bc9517b2abe09810199a19924617b45690a44ecfb0616ac9babc11875c03" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "9f034ba1eeca53ddb339bc8f4813cb07336a849cd735559b60cdc068ecce2dc7" + url: "https://pub.dev" + source: hosted + version: "7.1.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + password_strength: + dependency: "direct main" + description: + name: password_strength + sha256: "0e51e3d864e37873a1347e658147f88b66e141ee36c58e19828dc5637961e1ce" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + pinput: + dependency: "direct main" + description: + name: pinput + sha256: "8a73be426a91fefec90a7f130763ca39772d547e92f19a827cf4aa02e323d35a" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: "direct main" + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + privacy_screen: + dependency: transitive + description: + name: privacy_screen + sha256: "2856e3a3ed082061a5cd2a1518f1ce6367c55916fb75e5db72e5983033a1ca54" + url: "https://pub.dev" + source: hosted + version: "0.0.8" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_linux: + dependency: transitive + description: + name: screen_retriever_linux + sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_macos: + dependency: transitive + description: + name: screen_retriever_macos + sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_platform_interface: + dependency: transitive + description: + name: screen_retriever_platform_interface + sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_windows: + dependency: transitive + description: + name: screen_retriever_windows + sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + sentry: + dependency: transitive + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + sentry_flutter: + dependency: transitive + description: + name: sentry_flutter + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 + url: "https://pub.dev" + source: hosted + version: "11.1.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + sodium: + dependency: transitive + description: + name: sodium + sha256: d9830a388e37c82891888e64cfd4c6764fa3ac716bed80ac6eab89ee42c3cd76 + url: "https://pub.dev" + source: hosted + version: "2.3.1+1" + sodium_libs: + dependency: transitive + description: + name: sodium_libs + sha256: aa764acd6ccc6113e119c2d99471aeeb4637a9a501639549b297d3a143ff49b3 + url: "https://pub.dev" + source: hosted + version: "2.2.1+6" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + step_progress_indicator: + dependency: "direct main" + description: + name: step_progress_indicator + sha256: b51bb1fcfc78454359f0658c5a2c21548c3825ebf76e826308e9ca10f383bbb8 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + styled_text: + dependency: "direct main" + description: + name: styled_text + sha256: fd624172cf629751b4f171dd0ecf9acf02a06df3f8a81bb56c0caa4f1df706c3 + url: "https://pub.dev" + source: hosted + version: "8.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + ua_client_hints: + dependency: transitive + description: + name: ua_client_hints + sha256: "1b8759a46bfeab355252881df27f2604c01bded86aa2b578869fb1b638b23118" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: "direct main" + description: + name: url_launcher_ios + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + url: "https://pub.dev" + source: hosted + version: "6.3.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + window_manager: + dependency: transitive + description: + name: window_manager + sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xmlstream: + dependency: transitive + description: + name: xmlstream + sha256: cfc14e3f256997897df9481ae630d94c2d85ada5187ebeb868bb1aabc2c977b4 + url: "https://pub.dev" + source: hosted + version: "1.1.1" +sdks: + dart: ">=3.7.2 <4.0.0" + flutter: ">=3.24.0" diff --git a/mobile/packages/accounts/pubspec.yaml b/mobile/packages/accounts/pubspec.yaml new file mode 100644 index 0000000000..1d4064ac75 --- /dev/null +++ b/mobile/packages/accounts/pubspec.yaml @@ -0,0 +1,58 @@ +name: ente_accounts +description: A Flutter package containing account-related models, pages, and services for Ente apps +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + app_links: ^6.3.3 + bip39: ^1.0.6 + collection: ^1.18.0 + dio: ^5.4.0 + dotted_border: ^3.1.0 + email_validator: ^3.0.0 + ente_base: + path: ../base + ente_configuration: + path: ../configuration + ente_crypto_dart: + git: + url: https://github.com/ente-io/ente_crypto_dart.git + ente_events: + path: ../events + ente_lock_screen: + path: ../lock_screen + ente_network: + path: ../network + ente_strings: + path: ../strings + ente_ui: + path: ../ui + ente_utils: + path: ../utils + file_saver: ^0.3.0 + flutter: + sdk: flutter + logging: ^1.2.0 + password_strength: ^0.2.0 + pinput: ^5.0.1 + pointycastle: ^3.7.3 + share_plus: ^11.0.0 + shared_preferences: ^2.2.2 + step_progress_indicator: ^1.0.2 + styled_text: ^8.1.0 + url_launcher: ^6.3.1 + url_launcher_ios: ^6.3.1 + uuid: ^4.2.1 + +dev_dependencies: + flutter_lints: ^5.0.0 + flutter_test: + sdk: flutter + +flutter: + +# This package is not meant to be published +publish_to: none diff --git a/mobile/packages/base/analysis_options.yaml b/mobile/packages/base/analysis_options.yaml new file mode 100644 index 0000000000..1bd78bc1b0 --- /dev/null +++ b/mobile/packages/base/analysis_options.yaml @@ -0,0 +1,72 @@ +# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html +# or https://pub.dev/packages/lint (Effective dart) +# use "flutter analyze ." or "dart analyze ." for running lint checks + +include: package:flutter_lints/flutter.yaml +linter: + rules: + # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml + # Ref https://dart-lang.github.io/linter/lints/ + - avoid_print + - avoid_unnecessary_containers + - avoid_web_libraries_in_flutter + - no_logic_in_create_state + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_final_locals + - require_trailing_commas + - sized_box_for_whitespace + - use_full_hex_values_for_flutter_colors + - use_key_in_widget_constructors + - cancel_subscriptions + + + - avoid_empty_else + - exhaustive_cases + + # just style suggestions + - sort_pub_dependencies + - use_rethrow_when_possible + - prefer_double_quotes + - directives_ordering + - always_use_package_imports + - sort_child_properties_last + - unawaited_futures + +analyzer: + errors: + avoid_empty_else: error + exhaustive_cases: error + curly_braces_in_flow_control_structures: error + directives_ordering: error + require_trailing_commas: error + always_use_package_imports: warning + prefer_final_fields: error + unused_import: error + camel_case_types: error + prefer_is_empty: warning + use_rethrow_when_possible: info + unused_field: warning + use_key_in_widget_constructors: warning + sort_child_properties_last: warning + sort_pub_dependencies: warning + library_private_types_in_public_api: warning + constant_identifier_names: ignore + prefer_const_constructors: warning + prefer_const_declarations: warning + prefer_const_constructors_in_immutables: warning + prefer_final_locals: warning + unnecessary_const: error + cancel_subscriptions: error + unrelated_type_equality_checks: error + unnecessary_cast: info + + + unawaited_futures: warning # convert to warning after fixing existing issues + invalid_dependency: info + use_build_context_synchronously: ignore # experimental lint, requires many changes + prefer_interpolation_to_compose_strings: ignore # later too many warnings + prefer_double_quotes: ignore # too many warnings + avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides diff --git a/mobile/packages/base/lib/ente_base.dart b/mobile/packages/base/lib/ente_base.dart new file mode 100644 index 0000000000..6bb3610d5d --- /dev/null +++ b/mobile/packages/base/lib/ente_base.dart @@ -0,0 +1,6 @@ +export 'models/database.dart'; +export 'models/key_attributes.dart'; +export 'models/key_gen_result.dart'; +export 'models/private_key_attributes.dart'; + +export 'typedefs.dart'; diff --git a/mobile/packages/base/lib/models/database.dart b/mobile/packages/base/lib/models/database.dart new file mode 100644 index 0000000000..61216d45e1 --- /dev/null +++ b/mobile/packages/base/lib/models/database.dart @@ -0,0 +1,3 @@ +abstract class EnteBaseDatabase { + Future clearTable(); +} diff --git a/mobile/apps/auth/lib/models/key_attributes.dart b/mobile/packages/base/lib/models/key_attributes.dart similarity index 100% rename from mobile/apps/auth/lib/models/key_attributes.dart rename to mobile/packages/base/lib/models/key_attributes.dart diff --git a/mobile/apps/auth/lib/models/key_gen_result.dart b/mobile/packages/base/lib/models/key_gen_result.dart similarity index 60% rename from mobile/apps/auth/lib/models/key_gen_result.dart rename to mobile/packages/base/lib/models/key_gen_result.dart index 5b581b0ab1..758b8dd690 100644 --- a/mobile/apps/auth/lib/models/key_gen_result.dart +++ b/mobile/packages/base/lib/models/key_gen_result.dart @@ -1,7 +1,7 @@ -import 'dart:typed_data'; +import "dart:typed_data"; -import 'package:ente_auth/models/key_attributes.dart'; -import 'package:ente_auth/models/private_key_attributes.dart'; +import 'package:ente_base/models/key_attributes.dart'; +import 'package:ente_base/models/private_key_attributes.dart'; class KeyGenResult { final KeyAttributes keyAttributes; diff --git a/mobile/apps/auth/lib/models/private_key_attributes.dart b/mobile/packages/base/lib/models/private_key_attributes.dart similarity index 100% rename from mobile/apps/auth/lib/models/private_key_attributes.dart rename to mobile/packages/base/lib/models/private_key_attributes.dart diff --git a/mobile/apps/auth/lib/models/typedefs.dart b/mobile/packages/base/lib/typedefs.dart similarity index 100% rename from mobile/apps/auth/lib/models/typedefs.dart rename to mobile/packages/base/lib/typedefs.dart diff --git a/mobile/packages/base/pubspec.lock b/mobile/packages/base/pubspec.lock new file mode 100644 index 0000000000..eccd0a04eb --- /dev/null +++ b/mobile/packages/base/pubspec.lock @@ -0,0 +1,205 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/mobile/packages/base/pubspec.yaml b/mobile/packages/base/pubspec.yaml new file mode 100644 index 0000000000..3148d560e1 --- /dev/null +++ b/mobile/packages/base/pubspec.yaml @@ -0,0 +1,18 @@ +name: ente_base +description: A base Flutter package containing shared models and types for ente apps +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_lints: ^5.0.0 + flutter_test: + sdk: flutter + +flutter: diff --git a/mobile/packages/configuration/.flutter-plugins b/mobile/packages/configuration/.flutter-plugins new file mode 100644 index 0000000000..a788946466 --- /dev/null +++ b/mobile/packages/configuration/.flutter-plugins @@ -0,0 +1,21 @@ +# This is a generated file; do not edit or check into version control. +device_info_plus=/Users/amanraj/.pub-cache/hosted/pub.dev/device_info_plus-9.1.2/ +flutter_secure_storage=/Users/amanraj/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/ +flutter_secure_storage_linux=/Users/amanraj/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/ +flutter_secure_storage_macos=/Users/amanraj/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/ +flutter_secure_storage_web=/Users/amanraj/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/ +flutter_secure_storage_windows=/Users/amanraj/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/ +package_info_plus=/Users/amanraj/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/ +path_provider=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider-2.1.5/ +path_provider_android=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_android-2.2.15/ +path_provider_foundation=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/ +path_provider_linux=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ +path_provider_windows=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/ +sentry_flutter=/Users/amanraj/.pub-cache/hosted/pub.dev/sentry_flutter-8.14.2/ +shared_preferences=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences-2.5.3/ +shared_preferences_android=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.7/ +shared_preferences_foundation=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/ +shared_preferences_linux=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/ +shared_preferences_web=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/ +shared_preferences_windows=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/ +sodium_libs=/Users/amanraj/.pub-cache/hosted/pub.dev/sodium_libs-2.2.1+6/ diff --git a/mobile/packages/configuration/analysis_options.yml b/mobile/packages/configuration/analysis_options.yml new file mode 100644 index 0000000000..1bd78bc1b0 --- /dev/null +++ b/mobile/packages/configuration/analysis_options.yml @@ -0,0 +1,72 @@ +# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html +# or https://pub.dev/packages/lint (Effective dart) +# use "flutter analyze ." or "dart analyze ." for running lint checks + +include: package:flutter_lints/flutter.yaml +linter: + rules: + # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml + # Ref https://dart-lang.github.io/linter/lints/ + - avoid_print + - avoid_unnecessary_containers + - avoid_web_libraries_in_flutter + - no_logic_in_create_state + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_final_locals + - require_trailing_commas + - sized_box_for_whitespace + - use_full_hex_values_for_flutter_colors + - use_key_in_widget_constructors + - cancel_subscriptions + + + - avoid_empty_else + - exhaustive_cases + + # just style suggestions + - sort_pub_dependencies + - use_rethrow_when_possible + - prefer_double_quotes + - directives_ordering + - always_use_package_imports + - sort_child_properties_last + - unawaited_futures + +analyzer: + errors: + avoid_empty_else: error + exhaustive_cases: error + curly_braces_in_flow_control_structures: error + directives_ordering: error + require_trailing_commas: error + always_use_package_imports: warning + prefer_final_fields: error + unused_import: error + camel_case_types: error + prefer_is_empty: warning + use_rethrow_when_possible: info + unused_field: warning + use_key_in_widget_constructors: warning + sort_child_properties_last: warning + sort_pub_dependencies: warning + library_private_types_in_public_api: warning + constant_identifier_names: ignore + prefer_const_constructors: warning + prefer_const_declarations: warning + prefer_const_constructors_in_immutables: warning + prefer_final_locals: warning + unnecessary_const: error + cancel_subscriptions: error + unrelated_type_equality_checks: error + unnecessary_cast: info + + + unawaited_futures: warning # convert to warning after fixing existing issues + invalid_dependency: info + use_build_context_synchronously: ignore # experimental lint, requires many changes + prefer_interpolation_to_compose_strings: ignore # later too many warnings + prefer_double_quotes: ignore # too many warnings + avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides diff --git a/mobile/packages/configuration/devtools_options.yaml b/mobile/packages/configuration/devtools_options.yaml new file mode 100644 index 0000000000..fa0b357c4f --- /dev/null +++ b/mobile/packages/configuration/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/mobile/packages/configuration/lib/base_configuration.dart b/mobile/packages/configuration/lib/base_configuration.dart new file mode 100644 index 0000000000..5dfc722834 --- /dev/null +++ b/mobile/packages/configuration/lib/base_configuration.dart @@ -0,0 +1,438 @@ +library configuration; + +import 'dart:convert'; +import 'dart:io' as io; + +import 'package:bip39/bip39.dart' as bip39; +import 'package:ente_configuration/constants.dart'; +import 'package:ente_base/models/database.dart'; +import 'package:ente_base/models/key_attributes.dart'; +import 'package:ente_base/models/key_gen_result.dart'; +import 'package:ente_base/models/private_key_attributes.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; +import 'package:ente_events/event_bus.dart'; +import 'package:ente_events/models/endpoint_updated_event.dart'; +import 'package:ente_events/models/signed_in_event.dart'; +import 'package:ente_events/models/signed_out_event.dart'; +import 'package:ente_logging/logging.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tuple/tuple.dart'; + +class BaseConfiguration { + static const endpoint = String.fromEnvironment( + "endpoint", + defaultValue: kDefaultProductionEndpoint, + ); + static const emailKey = "email"; + static const keyAttributesKey = "key_attributes"; + static const keyKey = "key"; + static const secretKeyKey = "secret_key"; + static const tokenKey = "token"; + static const encryptedTokenKey = "encrypted_token"; + static const userIDKey = "user_id"; + static const endPointKey = "endpoint"; + static const lastTempFolderClearTimeKey = "last_temp_folder_clear_time"; + + final kTempFolderDeletionTimeBuffer = const Duration(days: 1).inMicroseconds; + + static final _logger = Logger("Configuration"); + + String? _cachedToken; + late SharedPreferences _preferences; + String? _key; + String? _secretKey; + late FlutterSecureStorage _secureStorage; + late String _documentsDirectory; + late String _cacheDirectory; + late String _tempDocumentsDirPath; + late List _databases; + + String? _volatilePassword; + + Future init(List dbs) async { + _databases = dbs; + _documentsDirectory = (await getApplicationDocumentsDirectory()).path; + _tempDocumentsDirPath = "$_documentsDirectory/temp/"; + _preferences = await SharedPreferences.getInstance(); + _secureStorage = const FlutterSecureStorage( + iOptions: IOSOptions( + accessibility: KeychainAccessibility.first_unlock_this_device, + ), + ); + _setupKeys(); + _setupFolders(); + } + + Future logout({bool autoLogout = false}) async { + await _preferences.clear(); + _secureStorage.deleteAll(); + for (final db in _databases) { + await db.clearTable(); + } + _key = null; + _cachedToken = null; + _secretKey = null; + Bus.instance.fire(SignedOutEvent()); + } + + Future generateKey(String password) async { + // Create a master key + final masterKey = CryptoUtil.generateKey(); + + // Create a recovery key + final recoveryKey = CryptoUtil.generateKey(); + + // Encrypt master key and recovery key with each other + final encryptedMasterKey = CryptoUtil.encryptSync(masterKey, recoveryKey); + final encryptedRecoveryKey = CryptoUtil.encryptSync(recoveryKey, masterKey); + + // Derive a key from the password that will be used to encrypt and + // decrypt the master key + final kekSalt = CryptoUtil.getSaltToDeriveKey(); + final derivedKeyResult = await CryptoUtil.deriveSensitiveKey( + utf8.encode(password), + kekSalt, + ); + final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key); + + // Encrypt the key with this derived key + final encryptedKeyData = + CryptoUtil.encryptSync(masterKey, derivedKeyResult.key); + + // Generate a public-private keypair and encrypt the latter + final keyPair = CryptoUtil.generateKeyPair(); + final encryptedSecretKeyData = + CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey); + + final attributes = KeyAttributes( + CryptoUtil.bin2base64(kekSalt), + CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), + CryptoUtil.bin2base64(encryptedKeyData.nonce!), + CryptoUtil.bin2base64(keyPair.publicKey), + CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!), + CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!), + derivedKeyResult.memLimit, + derivedKeyResult.opsLimit, + CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!), + CryptoUtil.bin2base64(encryptedMasterKey.nonce!), + CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!), + CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!), + ); + final privateAttributes = PrivateKeyAttributes( + CryptoUtil.bin2base64(masterKey), + CryptoUtil.bin2hex(recoveryKey), + CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()), + ); + return KeyGenResult(attributes, privateAttributes, loginKey); + } + + Future> getAttributesForNewPassword( + String password, + ) async { + // Get master key + final masterKey = getKey(); + + // Derive a key from the password that will be used to encrypt and + // decrypt the master key + final kekSalt = CryptoUtil.getSaltToDeriveKey(); + final derivedKeyResult = await CryptoUtil.deriveSensitiveKey( + utf8.encode(password), + kekSalt, + ); + final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key); + + // Encrypt the key with this derived key + final encryptedKeyData = + CryptoUtil.encryptSync(masterKey!, derivedKeyResult.key); + + final existingAttributes = getKeyAttributes(); + + final updatedAttributes = existingAttributes!.copyWith( + kekSalt: CryptoUtil.bin2base64(kekSalt), + encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), + keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!), + memLimit: derivedKeyResult.memLimit, + opsLimit: derivedKeyResult.opsLimit, + ); + return Tuple2(updatedAttributes, loginKey); + } + + // decryptSecretsAndGetLoginKey decrypts the master key and recovery key + // with the given password and save them in local secure storage. + // This method also returns the keyEncKey that can be used for performing + // SRP setup for existing users. + Future decryptSecretsAndGetKeyEncKey( + String password, + KeyAttributes attributes, { + Uint8List? keyEncryptionKey, + }) async { + _logger.info('Start decryptAndSaveSecrets'); + keyEncryptionKey ??= await CryptoUtil.deriveKey( + utf8.encode(password), + CryptoUtil.base642bin(attributes.kekSalt), + attributes.memLimit, + attributes.opsLimit, + ); + + _logger.info('user-key done'); + Uint8List key; + try { + key = CryptoUtil.decryptSync( + CryptoUtil.base642bin(attributes.encryptedKey), + keyEncryptionKey, + CryptoUtil.base642bin(attributes.keyDecryptionNonce), + ); + } catch (e) { + _logger.severe('master-key failed, incorrect password?', e); + throw Exception("Incorrect password"); + } + _logger.info("master-key done"); + await setKey(CryptoUtil.bin2base64(key)); + final secretKey = CryptoUtil.decryptSync( + CryptoUtil.base642bin(attributes.encryptedSecretKey), + key, + CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce), + ); + _logger.info("secret-key done"); + await setSecretKey(CryptoUtil.bin2base64(secretKey)); + final token = CryptoUtil.openSealSync( + CryptoUtil.base642bin(getEncryptedToken()!), + CryptoUtil.base642bin(attributes.publicKey), + secretKey, + ); + _logger.info('appToken done'); + await setToken( + CryptoUtil.bin2base64(token, urlSafe: true), + ); + return keyEncryptionKey; + } + + Future recover(String recoveryKey) async { + // check if user has entered mnemonic code + if (recoveryKey.contains(' ')) { + final split = recoveryKey.split(' '); + if (split.length != mnemonicKeyWordCount) { + String wordThatIsFollowedByEmptySpaceInSplit = ''; + for (int i = 0; i < split.length; i++) { + String word = split[i]; + if (word.isEmpty) { + wordThatIsFollowedByEmptySpaceInSplit = + '\n\nExtra space after word at position $i'; + break; + } + } + throw AssertionError( + '\nRecovery code should have $mnemonicKeyWordCount words, ' + 'found ${split.length} words instead.$wordThatIsFollowedByEmptySpaceInSplit', + ); + } + recoveryKey = bip39.mnemonicToEntropy(recoveryKey); + } + final attributes = getKeyAttributes(); + Uint8List masterKey; + try { + masterKey = await CryptoUtil.decrypt( + CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey), + CryptoUtil.hex2bin(recoveryKey), + CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce), + ); + } catch (e) { + _logger.severe(e); + rethrow; + } + await setKey(CryptoUtil.bin2base64(masterKey)); + final secretKey = CryptoUtil.decryptSync( + CryptoUtil.base642bin(attributes.encryptedSecretKey), + masterKey, + CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce), + ); + await setSecretKey(CryptoUtil.bin2base64(secretKey)); + final token = CryptoUtil.openSealSync( + CryptoUtil.base642bin(getEncryptedToken()!), + CryptoUtil.base642bin(attributes.publicKey), + secretKey, + ); + await setToken( + CryptoUtil.bin2base64(token, urlSafe: true), + ); + } + + String getHttpEndpoint() { + return _preferences.getString(endPointKey) ?? endpoint; + } + + Future setHttpEndpoint(String endpoint) async { + await _preferences.setString(endPointKey, endpoint); + Bus.instance.fire(EndpointUpdatedEvent()); + } + + String? getToken() { + _cachedToken ??= _preferences.getString(tokenKey); + return _cachedToken; + } + + bool isLoggedIn() { + return getToken() != null; + } + + Future setToken(String token) async { + _cachedToken = token; + await _preferences.setString(tokenKey, token); + Bus.instance.fire(SignedInEvent()); + } + + Future setEncryptedToken(String encryptedToken) async { + await _preferences.setString(encryptedTokenKey, encryptedToken); + } + + String? getEncryptedToken() { + return _preferences.getString(encryptedTokenKey); + } + + String? getEmail() { + return _preferences.getString(emailKey); + } + + Future setEmail(String email) async { + await _preferences.setString(emailKey, email); + } + + int? getUserID() { + return _preferences.getInt(userIDKey); + } + + Future setUserID(int userID) async { + await _preferences.setInt(userIDKey, userID); + } + + Future setKeyAttributes(KeyAttributes attributes) async { + await _preferences.setString(keyAttributesKey, attributes.toJson()); + } + + KeyAttributes? getKeyAttributes() { + final jsonValue = _preferences.getString(keyAttributesKey); + if (jsonValue == null) { + return null; + } else { + return KeyAttributes.fromJson(jsonValue); + } + } + + Future setKey(String key) async { + _key = key; + await _secureStorage.write( + key: keyKey, + value: key, + ); + } + + Future setSecretKey(String? secretKey) async { + _secretKey = secretKey; + await _secureStorage.write( + key: secretKeyKey, + value: secretKey, + ); + } + + Uint8List? getKey() { + return _key == null ? null : CryptoUtil.base642bin(_key!); + } + + Uint8List? getSecretKey() { + return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!); + } + + Uint8List getRecoveryKey() { + final keyAttributes = getKeyAttributes()!; + return CryptoUtil.decryptSync( + CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey), + getKey()!, + CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce), + ); + } + + // Caution: This directory is cleared on app start + String getTempDirectory() { + return _tempDocumentsDirPath; + } + + String getCacheDirectory() { + return _cacheDirectory; + } + + bool hasConfiguredAccount() { + return getToken() != null && _key != null; + } + + void setVolatilePassword(String volatilePassword) { + _volatilePassword = volatilePassword; + } + + void resetVolatilePassword() { + _volatilePassword = null; + } + + String? getVolatilePassword() { + return _volatilePassword; + } + + Future _setupKeys() async { + try { + if (!_preferences.containsKey(tokenKey)) { + await _secureStorage.deleteAll(); + return; + } + _key = await _secureStorage.read(key: keyKey); + if (_key == null) { + _logger.warning("No key found in secure storage"); + await logout(autoLogout: true); + } + _secretKey = await _secureStorage.read(key: secretKeyKey); + } catch (e, s) { + _logger.severe("Configuration init failed", e, s); + /* + Check if it's a known is related to reading secret from secure storage + on android https://github.com/mogol/flutter_secure_storage/issues/541 + */ + if (e is PlatformException) { + final PlatformException error = e; + final bool isBadPaddingError = + error.toString().contains('BadPaddingException') || + (error.message ?? '').contains('BadPaddingException'); + if (isBadPaddingError) { + await logout(autoLogout: true); + return; + } + } else { + rethrow; + } + } + } + + Future _setupFolders() async { + final tempDirectory = io.Directory(_tempDocumentsDirPath); + try { + final currentTime = DateTime.now().microsecondsSinceEpoch; + if (tempDirectory.existsSync() && + (_preferences.getInt(lastTempFolderClearTimeKey) ?? 0) < + (currentTime - kTempFolderDeletionTimeBuffer)) { + await tempDirectory.delete(recursive: true); + await _preferences.setInt(lastTempFolderClearTimeKey, currentTime); + _logger.info("Cleared temp folder"); + } else { + _logger.info("Skipping temp folder clear"); + } + } catch (e) { + _logger.warning(e); + } + tempDirectory.createSync(recursive: true); + + _cacheDirectory = "$_documentsDirectory/cache/"; + if (!io.Directory(_cacheDirectory).existsSync()) { + io.Directory(_cacheDirectory).createSync(recursive: true); + } + } +} diff --git a/mobile/packages/configuration/lib/constants.dart b/mobile/packages/configuration/lib/constants.dart new file mode 100644 index 0000000000..7b28375d9f --- /dev/null +++ b/mobile/packages/configuration/lib/constants.dart @@ -0,0 +1,5 @@ +const kDefaultProductionEndpoint = 'https://api.ente.io'; + +// 256 bit key maps to 24 words +// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#Generating_the_mnemonic +const mnemonicKeyWordCount = 24; diff --git a/mobile/packages/configuration/pubspec.lock b/mobile/packages/configuration/pubspec.lock new file mode 100644 index 0000000000..62eeff5e74 --- /dev/null +++ b/mobile/packages/configuration/pubspec.lock @@ -0,0 +1,696 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + bip39: + dependency: "direct main" + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + ente_base: + dependency: "direct main" + description: + path: "../base" + relative: true + source: path + version: "1.0.0" + ente_crypto_dart: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: f91e1545f8263df127762240c4da54a0c42835b2 + url: "https://github.com/ente-io/ente_crypto_dart.git" + source: git + version: "1.0.0" + ente_events: + dependency: "direct main" + description: + path: "../events" + relative: true + source: path + version: "1.0.0" + ente_logging: + dependency: "direct main" + description: + path: "../logging" + relative: true + source: path + version: "1.0.0" + event_bus: + dependency: transitive + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + sentry: + dependency: transitive + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + sentry_flutter: + dependency: transitive + description: + name: sentry_flutter + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + sodium: + dependency: transitive + description: + name: sodium + sha256: d9830a388e37c82891888e64cfd4c6764fa3ac716bed80ac6eab89ee42c3cd76 + url: "https://pub.dev" + source: hosted + version: "2.3.1+1" + sodium_libs: + dependency: transitive + description: + name: sodium_libs + sha256: aa764acd6ccc6113e119c2d99471aeeb4637a9a501639549b297d3a143ff49b3 + url: "https://pub.dev" + source: hosted + version: "2.2.1+6" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + tuple: + dependency: "direct main" + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.24.0" diff --git a/mobile/packages/configuration/pubspec.yaml b/mobile/packages/configuration/pubspec.yaml new file mode 100644 index 0000000000..919439c9cc --- /dev/null +++ b/mobile/packages/configuration/pubspec.yaml @@ -0,0 +1,33 @@ +name: ente_configuration +description: A Flutter package for shared configuration across ente apps +version: 1.0.0 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + bip39: ^1.0.6 + ente_base: + path: ../../packages/base + ente_crypto_dart: + git: + url: https://github.com/ente-io/ente_crypto_dart.git + ente_events: + path: ../../packages/events + ente_logging: + path: ../../packages/logging + flutter_secure_storage: ^9.0.0 + path_provider: ^2.1.5 + shared_preferences: ^2.5.3 + tuple: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: diff --git a/mobile/packages/events/analysis_options.yaml b/mobile/packages/events/analysis_options.yaml new file mode 100644 index 0000000000..1bd78bc1b0 --- /dev/null +++ b/mobile/packages/events/analysis_options.yaml @@ -0,0 +1,72 @@ +# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html +# or https://pub.dev/packages/lint (Effective dart) +# use "flutter analyze ." or "dart analyze ." for running lint checks + +include: package:flutter_lints/flutter.yaml +linter: + rules: + # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml + # Ref https://dart-lang.github.io/linter/lints/ + - avoid_print + - avoid_unnecessary_containers + - avoid_web_libraries_in_flutter + - no_logic_in_create_state + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_final_locals + - require_trailing_commas + - sized_box_for_whitespace + - use_full_hex_values_for_flutter_colors + - use_key_in_widget_constructors + - cancel_subscriptions + + + - avoid_empty_else + - exhaustive_cases + + # just style suggestions + - sort_pub_dependencies + - use_rethrow_when_possible + - prefer_double_quotes + - directives_ordering + - always_use_package_imports + - sort_child_properties_last + - unawaited_futures + +analyzer: + errors: + avoid_empty_else: error + exhaustive_cases: error + curly_braces_in_flow_control_structures: error + directives_ordering: error + require_trailing_commas: error + always_use_package_imports: warning + prefer_final_fields: error + unused_import: error + camel_case_types: error + prefer_is_empty: warning + use_rethrow_when_possible: info + unused_field: warning + use_key_in_widget_constructors: warning + sort_child_properties_last: warning + sort_pub_dependencies: warning + library_private_types_in_public_api: warning + constant_identifier_names: ignore + prefer_const_constructors: warning + prefer_const_declarations: warning + prefer_const_constructors_in_immutables: warning + prefer_final_locals: warning + unnecessary_const: error + cancel_subscriptions: error + unrelated_type_equality_checks: error + unnecessary_cast: info + + + unawaited_futures: warning # convert to warning after fixing existing issues + invalid_dependency: info + use_build_context_synchronously: ignore # experimental lint, requires many changes + prefer_interpolation_to_compose_strings: ignore # later too many warnings + prefer_double_quotes: ignore # too many warnings + avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides diff --git a/mobile/packages/events/lib/ente_events.dart b/mobile/packages/events/lib/ente_events.dart new file mode 100644 index 0000000000..a4a94dd52d --- /dev/null +++ b/mobile/packages/events/lib/ente_events.dart @@ -0,0 +1,8 @@ +export 'event_bus.dart'; +export 'models/collections_updated_event.dart'; +export 'models/endpoint_updated_event.dart'; +export 'models/event.dart'; +export 'models/signed_in_event.dart'; +export 'models/signed_out_event.dart'; +export 'models/trigger_logout_event.dart'; +export 'models/user_details_changed_event.dart'; diff --git a/mobile/apps/auth/lib/core/event_bus.dart b/mobile/packages/events/lib/event_bus.dart similarity index 100% rename from mobile/apps/auth/lib/core/event_bus.dart rename to mobile/packages/events/lib/event_bus.dart diff --git a/mobile/packages/events/lib/models/collections_updated_event.dart b/mobile/packages/events/lib/models/collections_updated_event.dart new file mode 100644 index 0000000000..d35e3a15cf --- /dev/null +++ b/mobile/packages/events/lib/models/collections_updated_event.dart @@ -0,0 +1,3 @@ +import 'package:ente_events/models/event.dart'; + +class CollectionsUpdatedEvent extends Event {} diff --git a/mobile/packages/events/lib/models/endpoint_updated_event.dart b/mobile/packages/events/lib/models/endpoint_updated_event.dart new file mode 100644 index 0000000000..ccdf0daa4f --- /dev/null +++ b/mobile/packages/events/lib/models/endpoint_updated_event.dart @@ -0,0 +1,3 @@ +import 'package:ente_events/models/event.dart'; + +class EndpointUpdatedEvent extends Event {} diff --git a/mobile/apps/auth/lib/events/event.dart b/mobile/packages/events/lib/models/event.dart similarity index 88% rename from mobile/apps/auth/lib/events/event.dart rename to mobile/packages/events/lib/models/event.dart index bd21892935..61a42e7107 100644 --- a/mobile/apps/auth/lib/events/event.dart +++ b/mobile/packages/events/lib/models/event.dart @@ -1,3 +1 @@ - - class Event {} diff --git a/mobile/packages/events/lib/models/signed_in_event.dart b/mobile/packages/events/lib/models/signed_in_event.dart new file mode 100644 index 0000000000..db30d50c84 --- /dev/null +++ b/mobile/packages/events/lib/models/signed_in_event.dart @@ -0,0 +1,3 @@ +import 'package:ente_events/models/event.dart'; + +class SignedInEvent extends Event {} diff --git a/mobile/packages/events/lib/models/signed_out_event.dart b/mobile/packages/events/lib/models/signed_out_event.dart new file mode 100644 index 0000000000..8fe1055197 --- /dev/null +++ b/mobile/packages/events/lib/models/signed_out_event.dart @@ -0,0 +1,3 @@ +import 'package:ente_events/models/event.dart'; + +class SignedOutEvent extends Event {} diff --git a/mobile/packages/events/lib/models/trigger_logout_event.dart b/mobile/packages/events/lib/models/trigger_logout_event.dart new file mode 100644 index 0000000000..a6c9b724f5 --- /dev/null +++ b/mobile/packages/events/lib/models/trigger_logout_event.dart @@ -0,0 +1,3 @@ +import 'package:ente_events/models/event.dart'; + +class TriggerLogoutEvent extends Event {} diff --git a/mobile/apps/auth/lib/events/user_details_changed_event.dart b/mobile/packages/events/lib/models/user_details_changed_event.dart similarity index 50% rename from mobile/apps/auth/lib/events/user_details_changed_event.dart rename to mobile/packages/events/lib/models/user_details_changed_event.dart index 066999f473..d96a038a69 100644 --- a/mobile/apps/auth/lib/events/user_details_changed_event.dart +++ b/mobile/packages/events/lib/models/user_details_changed_event.dart @@ -1,3 +1,3 @@ -import 'package:ente_auth/events/event.dart'; +import 'package:ente_events/models/event.dart'; class UserDetailsChangedEvent extends Event {} diff --git a/mobile/packages/events/pubspec.lock b/mobile/packages/events/pubspec.lock new file mode 100644 index 0000000000..91728750e8 --- /dev/null +++ b/mobile/packages/events/pubspec.lock @@ -0,0 +1,213 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + event_bus: + dependency: "direct main" + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/mobile/packages/events/pubspec.yaml b/mobile/packages/events/pubspec.yaml new file mode 100644 index 0000000000..a68822fcfe --- /dev/null +++ b/mobile/packages/events/pubspec.yaml @@ -0,0 +1,19 @@ +name: ente_events +description: A Flutter package for event management using event_bus, extracted from the surprise app +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + event_bus: ^2.0.1 + flutter: + sdk: flutter + +dev_dependencies: + flutter_lints: ^5.0.0 + flutter_test: + sdk: flutter + +flutter: diff --git a/mobile/packages/lock_screen/analysis_options.yaml b/mobile/packages/lock_screen/analysis_options.yaml new file mode 100644 index 0000000000..1bd78bc1b0 --- /dev/null +++ b/mobile/packages/lock_screen/analysis_options.yaml @@ -0,0 +1,72 @@ +# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html +# or https://pub.dev/packages/lint (Effective dart) +# use "flutter analyze ." or "dart analyze ." for running lint checks + +include: package:flutter_lints/flutter.yaml +linter: + rules: + # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml + # Ref https://dart-lang.github.io/linter/lints/ + - avoid_print + - avoid_unnecessary_containers + - avoid_web_libraries_in_flutter + - no_logic_in_create_state + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_final_locals + - require_trailing_commas + - sized_box_for_whitespace + - use_full_hex_values_for_flutter_colors + - use_key_in_widget_constructors + - cancel_subscriptions + + + - avoid_empty_else + - exhaustive_cases + + # just style suggestions + - sort_pub_dependencies + - use_rethrow_when_possible + - prefer_double_quotes + - directives_ordering + - always_use_package_imports + - sort_child_properties_last + - unawaited_futures + +analyzer: + errors: + avoid_empty_else: error + exhaustive_cases: error + curly_braces_in_flow_control_structures: error + directives_ordering: error + require_trailing_commas: error + always_use_package_imports: warning + prefer_final_fields: error + unused_import: error + camel_case_types: error + prefer_is_empty: warning + use_rethrow_when_possible: info + unused_field: warning + use_key_in_widget_constructors: warning + sort_child_properties_last: warning + sort_pub_dependencies: warning + library_private_types_in_public_api: warning + constant_identifier_names: ignore + prefer_const_constructors: warning + prefer_const_declarations: warning + prefer_const_constructors_in_immutables: warning + prefer_final_locals: warning + unnecessary_const: error + cancel_subscriptions: error + unrelated_type_equality_checks: error + unnecessary_cast: info + + + unawaited_futures: warning # convert to warning after fixing existing issues + invalid_dependency: info + use_build_context_synchronously: ignore # experimental lint, requires many changes + prefer_interpolation_to_compose_strings: ignore # later too many warnings + prefer_double_quotes: ignore # too many warnings + avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides diff --git a/mobile/packages/lock_screen/devtools_options.yaml b/mobile/packages/lock_screen/devtools_options.yaml new file mode 100644 index 0000000000..fa0b357c4f --- /dev/null +++ b/mobile/packages/lock_screen/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/mobile/apps/auth/lib/utils/auth_util.dart b/mobile/packages/lock_screen/lib/auth_util.dart similarity index 90% rename from mobile/apps/auth/lib/utils/auth_util.dart rename to mobile/packages/lock_screen/lib/auth_util.dart index 6c797a4328..f4071b0712 100644 --- a/mobile/apps/auth/lib/utils/auth_util.dart +++ b/mobile/packages/lock_screen/lib/auth_util.dart @@ -1,8 +1,8 @@ import 'dart:io'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/services/local_authentication_service.dart'; -import 'package:ente_auth/utils/lock_screen_settings.dart'; +import 'package:ente_lock_screen/local_authentication_service.dart'; +import 'package:ente_lock_screen/lock_screen_settings.dart'; +import 'package:ente_strings/ente_strings.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_local_authentication/flutter_local_authentication.dart'; import 'package:local_auth/local_auth.dart'; @@ -34,7 +34,7 @@ Future requestAuthentication( return await FlutterLocalAuthentication().authenticate(); } else { await LocalAuthentication().stopAuthentication(); - final l10n = context.l10n; + final l10n = context.strings; return await LocalAuthentication().authenticate( localizedReason: reason, authMessages: [ @@ -53,7 +53,6 @@ Future requestAuthentication( signInTitle: l10n.androidSignInTitle, ), IOSAuthMessages( - localizedFallbackTitle: l10n.enterPassword, goToSettingsButton: l10n.goToSettings, goToSettingsDescription: l10n.goToSettings, lockOut: l10n.iOSLockOut, diff --git a/mobile/apps/auth/lib/services/local_authentication_service.dart b/mobile/packages/lock_screen/lib/local_authentication_service.dart similarity index 90% rename from mobile/apps/auth/lib/services/local_authentication_service.dart rename to mobile/packages/lock_screen/lib/local_authentication_service.dart index 1502b2a36e..eb1a65cf05 100644 --- a/mobile/apps/auth/lib/services/local_authentication_service.dart +++ b/mobile/packages/lock_screen/lib/local_authentication_service.dart @@ -1,12 +1,12 @@ import 'dart:io'; -import 'package:ente_auth/ui/settings/lock_screen/lock_screen_password.dart'; -import 'package:ente_auth/ui/settings/lock_screen/lock_screen_pin.dart'; -import 'package:ente_auth/ui/tools/app_lock.dart'; -import 'package:ente_auth/utils/auth_util.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/lock_screen_settings.dart'; -import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_lock_screen/auth_util.dart'; +import 'package:ente_lock_screen/lock_screen_settings.dart'; +import 'package:ente_lock_screen/ui/app_lock.dart'; +import 'package:ente_lock_screen/ui/lock_screen_password.dart'; +import 'package:ente_lock_screen/ui/lock_screen_pin.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_ui/utils/toast_util.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/mobile/apps/auth/lib/utils/lock_screen_settings.dart b/mobile/packages/lock_screen/lib/lock_screen_settings.dart similarity index 91% rename from mobile/apps/auth/lib/utils/lock_screen_settings.dart rename to mobile/packages/lock_screen/lib/lock_screen_settings.dart index 247f7ae4b2..33e230cfc0 100644 --- a/mobile/apps/auth/lib/utils/lock_screen_settings.dart +++ b/mobile/packages/lock_screen/lib/lock_screen_settings.dart @@ -2,11 +2,11 @@ import "dart:convert"; import "dart:io"; import "dart:typed_data"; -import "package:ente_auth/core/configuration.dart"; -import "package:ente_auth/core/event_bus.dart"; -import "package:ente_auth/events/signed_out_event.dart"; -import "package:ente_auth/utils/platform_util.dart"; +import "package:ente_configuration/base_configuration.dart"; import "package:ente_crypto_dart/ente_crypto_dart.dart"; +import "package:ente_events/event_bus.dart"; +import "package:ente_events/models/signed_out_event.dart"; +import "package:ente_utils/platform_util.dart"; import "package:flutter/material.dart"; import "package:flutter_secure_storage/flutter_secure_storage.dart"; import "package:privacy_screen/privacy_screen.dart"; @@ -40,10 +40,12 @@ class LockScreenSettings { Duration(minutes: 30), ]; + late BaseConfiguration _config; late SharedPreferences _preferences; late FlutterSecureStorage _secureStorage; - Future init() async { + Future init(BaseConfiguration config) async { + _config = config; _secureStorage = const FlutterSecureStorage(); _preferences = await SharedPreferences.getInstance(); @@ -93,7 +95,6 @@ class LockScreenSettings { Future setHideAppContent(bool hideContent) async { if (PlatformUtil.isDesktop()) return; - final bool isLightMode = _preferences.getBool(kIsLightMode) ?? true; !hideContent ? PrivacyScreen.instance.disable() : await PrivacyScreen.instance.enable( @@ -103,11 +104,8 @@ class LockScreenSettings { androidOptions: const PrivacyAndroidOptions( enableSecure: true, ), - backgroundColor: - isLightMode ? const Color(0xffffffff) : const Color(0xff000000), - blurEffect: isLightMode - ? PrivacyBlurEffect.extraLight - : PrivacyBlurEffect.extraLight, + backgroundColor: const Color(0xff000000), + blurEffect: PrivacyBlurEffect.extraLight, ); await _preferences.setBool(keyHideAppContent, hideContent); } @@ -242,9 +240,7 @@ class LockScreenSettings { // backups), keychain items of the app persist in the keychain. To avoid using // old keychain items, we delete them on reinstall. Future _clearLsDataInKeychainIfFreshInstall() async { - if ((Platform.isIOS || Platform.isMacOS) && - !Configuration.instance.isLoggedIn() && - !Configuration.instance.hasOptedForOfflineMode()) { + if ((Platform.isIOS || Platform.isMacOS) && !_config.isLoggedIn()) { await _secureStorage.delete(key: password); await _secureStorage.delete(key: pin); await _secureStorage.delete(key: saltKey); diff --git a/mobile/apps/auth/lib/ui/tools/app_lock.dart b/mobile/packages/lock_screen/lib/ui/app_lock.dart similarity index 91% rename from mobile/apps/auth/lib/ui/tools/app_lock.dart rename to mobile/packages/lock_screen/lib/ui/app_lock.dart index a90c67ef20..7a2169700f 100644 --- a/mobile/apps/auth/lib/ui/tools/app_lock.dart +++ b/mobile/packages/lock_screen/lib/ui/app_lock.dart @@ -1,8 +1,6 @@ import 'dart:async'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/locale.dart'; -import 'package:ente_auth/utils/lock_screen_settings.dart'; +import "package:ente_lock_screen/lock_screen_settings.dart"; import 'package:flutter/material.dart'; /// A widget which handles app lifecycle events for showing and hiding a lock screen. @@ -36,12 +34,18 @@ class AppLock extends StatefulWidget { final ThemeData? lightTheme; final ThemeMode savedThemeMode; final Locale? locale; + final List? supportedLocales; + final List> localizationsDelegates; + final LocaleListResolutionCallback? localeListResolutionCallback; const AppLock({ super.key, required this.builder, required this.lockScreen, required this.savedThemeMode, + required this.supportedLocales, + required this.localizationsDelegates, + required this.localeListResolutionCallback, this.enabled = true, this.locale, this.backgroundLockLatency = const Duration(seconds: 0), @@ -117,11 +121,10 @@ class _AppLockState extends State with WidgetsBindingObserver { theme: widget.lightTheme, darkTheme: widget.darkTheme, locale: widget.locale, - supportedLocales: appSupportedLocales, - localeListResolutionCallback: localResolutionCallBack, - localizationsDelegates: const [ - ...AppLocalizations.localizationsDelegates, - ], + supportedLocales: + widget.supportedLocales ?? const [Locale('en', 'US')], + localeListResolutionCallback: widget.localeListResolutionCallback, + localizationsDelegates: widget.localizationsDelegates, onGenerateRoute: (settings) { switch (settings.name) { case '/lock-screen': @@ -141,8 +144,8 @@ class _AppLockState extends State with WidgetsBindingObserver { Widget get _lockScreen { return PopScope( - child: this.widget.lockScreen, canPop: false, + child: this.widget.lockScreen, ); } diff --git a/mobile/apps/auth/lib/ui/settings/lock_screen/custom_pin_keypad.dart b/mobile/packages/lock_screen/lib/ui/custom_pin_keypad.dart similarity index 99% rename from mobile/apps/auth/lib/ui/settings/lock_screen/custom_pin_keypad.dart rename to mobile/packages/lock_screen/lib/ui/custom_pin_keypad.dart index f8ad367fe0..f6fdf9c3e0 100644 --- a/mobile/apps/auth/lib/ui/settings/lock_screen/custom_pin_keypad.dart +++ b/mobile/packages/lock_screen/lib/ui/custom_pin_keypad.dart @@ -1,4 +1,4 @@ -import "package:ente_auth/theme/ente_theme.dart"; +import "package:ente_ui/theme/ente_theme.dart"; import "package:flutter/material.dart"; class CustomPinKeypad extends StatelessWidget { diff --git a/mobile/apps/auth/lib/ui/tools/lock_screen.dart b/mobile/packages/lock_screen/lib/ui/lock_screen.dart similarity index 92% rename from mobile/apps/auth/lib/ui/tools/lock_screen.dart rename to mobile/packages/lock_screen/lib/ui/lock_screen.dart index 35fa268042..b4f38cc51c 100644 --- a/mobile/apps/auth/lib/ui/tools/lock_screen.dart +++ b/mobile/packages/lock_screen/lib/ui/lock_screen.dart @@ -1,21 +1,26 @@ import 'dart:io'; import 'dart:math'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/services/user_service.dart'; -import 'package:ente_auth/theme/ente_theme.dart'; -import 'package:ente_auth/ui/tools/app_lock.dart'; -import 'package:ente_auth/utils/auth_util.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:ente_auth/utils/lock_screen_settings.dart'; +import 'package:ente_accounts/ente_accounts.dart'; +import 'package:ente_configuration/base_configuration.dart'; +import 'package:ente_lock_screen/auth_util.dart'; +import 'package:ente_lock_screen/lock_screen_settings.dart'; +import 'package:ente_lock_screen/ui/app_lock.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:logging/logging.dart'; class LockScreen extends StatefulWidget { - const LockScreen({super.key}); + final BaseConfiguration config; + + const LockScreen( + this.config, { + super.key, + }); @override State createState() => _LockScreenState(); @@ -33,7 +38,6 @@ class _LockScreenState extends State with WidgetsBindingObserver { int remainingTimeInSeconds = 0; final _lockscreenSetting = LockScreenSettings.instance; late Brightness _platformBrightness; - final bool isLoggedIn = Configuration.instance.isLoggedIn(); @override void initState() { @@ -55,7 +59,7 @@ class _LockScreenState extends State with WidgetsBindingObserver { return Scaffold( appBar: AppBar( elevation: 0, - leading: isLoggedIn + leading: widget.config.isLoggedIn() ? IconButton( icon: const Icon(Icons.logout_outlined), color: Theme.of(context).iconTheme.color, @@ -149,7 +153,7 @@ class _LockScreenState extends State with WidgetsBindingObserver { alignment: Alignment.center, children: [ Text( - context.l10n.tooManyIncorrectAttempts, + context.strings.tooManyIncorrectAttempts, style: textTheme.small, ) .animate( @@ -175,7 +179,7 @@ class _LockScreenState extends State with WidgetsBindingObserver { : GestureDetector( onTap: () => _showLockScreen(source: "tap"), child: Text( - context.l10n.tapToUnlock, + context.strings.tapToUnlock, style: textTheme.small, ), ), @@ -193,8 +197,8 @@ class _LockScreenState extends State with WidgetsBindingObserver { void _onLogoutTapped(BuildContext context) { showChoiceActionSheet( context, - title: context.l10n.areYouSureYouWantToLogout, - firstButtonLabel: context.l10n.yesLogout, + title: context.strings.areYouSureYouWantToLogout, + firstButtonLabel: context.strings.yesLogout, isCritical: true, firstButtonOnTap: () async { await UserService.instance.logout(context); @@ -297,9 +301,9 @@ class _LockScreenState extends State with WidgetsBindingObserver { _logger.info("Auto logout on max invalid attempts"); Navigator.of(context, rootNavigator: true).pop('dialog'); Navigator.of(context).popUntil((route) => route.isFirst); - final dialog = createProgressDialog(context, "Logging out ..."); + final dialog = createProgressDialog(context, context.strings.loggingOut); await dialog.show(); - await Configuration.instance.logout(); + await widget.config.logout(); await dialog.hide(); } @@ -321,7 +325,7 @@ class _LockScreenState extends State with WidgetsBindingObserver { ? false : await requestAuthentication( context, - context.l10n.authToViewSecrets, + context.strings.authToViewSecrets, isOpeningApp: true, ); _logger.finest("LockScreen Result $result $currentTimestamp"); diff --git a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart b/mobile/packages/lock_screen/lib/ui/lock_screen_auto_lock.dart similarity index 85% rename from mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart rename to mobile/packages/lock_screen/lib/ui/lock_screen_auto_lock.dart index 869bb1e40a..16b5f45ffe 100644 --- a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart +++ b/mobile/packages/lock_screen/lib/ui/lock_screen_auto_lock.dart @@ -1,12 +1,12 @@ -import 'package:ente_auth/l10n/l10n.dart'; -import 'package:ente_auth/theme/ente_theme.dart'; -import 'package:ente_auth/ui/components/captioned_text_widget.dart'; -import 'package:ente_auth/ui/components/divider_widget.dart'; -import 'package:ente_auth/ui/components/menu_item_widget.dart'; -import 'package:ente_auth/ui/components/separators.dart'; -import 'package:ente_auth/ui/components/title_bar_title_widget.dart'; -import 'package:ente_auth/ui/components/title_bar_widget.dart'; -import 'package:ente_auth/utils/lock_screen_settings.dart'; +import 'package:ente_lock_screen/lock_screen_settings.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/captioned_text_widget.dart'; +import 'package:ente_ui/components/divider_widget.dart'; +import 'package:ente_ui/components/menu_item_widget.dart'; +import 'package:ente_ui/components/separators.dart'; +import 'package:ente_ui/components/title_bar_title_widget.dart'; +import 'package:ente_ui/components/title_bar_widget.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; import 'package:flutter/material.dart'; class LockScreenAutoLock extends StatefulWidget { @@ -25,7 +25,7 @@ class _LockScreenAutoLockState extends State { slivers: [ TitleBarWidget( flexibleSpaceTitle: TitleBarTitleWidget( - title: context.l10n.autoLock, + title: context.strings.autoLock, ), ), SliverList( @@ -137,7 +137,7 @@ class _AutoLockItemsState extends State { } else if (duration.inSeconds != 0) { return "${duration.inSeconds}s"; } else { - return context.l10n.immediately; + return context.strings.immediately; } } } diff --git a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart b/mobile/packages/lock_screen/lib/ui/lock_screen_confirm_password.dart similarity index 92% rename from mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart rename to mobile/packages/lock_screen/lib/ui/lock_screen_confirm_password.dart index 196a29f9d7..32d2d7e671 100644 --- a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart +++ b/mobile/packages/lock_screen/lib/ui/lock_screen_confirm_password.dart @@ -1,9 +1,9 @@ -import "package:ente_auth/l10n/l10n.dart"; -import "package:ente_auth/theme/ente_theme.dart"; -import "package:ente_auth/ui/common/dynamic_fab.dart"; -import "package:ente_auth/ui/components/buttons/icon_button_widget.dart"; -import "package:ente_auth/ui/components/text_input_widget.dart"; -import "package:ente_auth/utils/lock_screen_settings.dart"; +import "package:ente_lock_screen/lock_screen_settings.dart"; +import "package:ente_strings/ente_strings.dart"; +import "package:ente_ui/components/buttons/dynamic_fab.dart"; +import "package:ente_ui/components/buttons/icon_button_widget.dart"; +import "package:ente_ui/components/text_input_widget.dart"; +import "package:ente_ui/theme/ente_theme.dart"; import "package:flutter/material.dart"; import "package:flutter/services.dart"; @@ -88,7 +88,7 @@ class _LockScreenConfirmPasswordState extends State { builder: (context, isFormValid, child) { return DynamicFAB( isKeypadOpen: isKeypadOpen, - buttonText: context.l10n.confirm, + buttonText: context.strings.confirm, isFormValid: isFormValid, onPressedFunction: () async { _submitNotifier.value = !_submitNotifier.value; @@ -153,14 +153,14 @@ class _LockScreenConfirmPasswordState extends State { ), ), Text( - context.l10n.reEnterPassword, + context.strings.reEnterPassword, style: textTheme.bodyBold, ), const Padding(padding: EdgeInsets.all(12)), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: TextInputWidget( - hintText: context.l10n.confirmPassword, + hintText: context.strings.confirmPassword, autoFocus: true, textCapitalization: TextCapitalization.none, isPasswordInput: true, diff --git a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart b/mobile/packages/lock_screen/lib/ui/lock_screen_confirm_pin.dart similarity index 86% rename from mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart rename to mobile/packages/lock_screen/lib/ui/lock_screen_confirm_pin.dart index e6fbd01ecc..1553a194f9 100644 --- a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart +++ b/mobile/packages/lock_screen/lib/ui/lock_screen_confirm_pin.dart @@ -1,11 +1,9 @@ import "dart:io"; -import "package:ente_auth/l10n/l10n.dart"; -import "package:ente_auth/theme/colors.dart"; -import "package:ente_auth/theme/ente_theme.dart"; -import "package:ente_auth/theme/text_style.dart"; -import "package:ente_auth/ui/settings/lock_screen/custom_pin_keypad.dart"; -import "package:ente_auth/utils/lock_screen_settings.dart"; +import "package:ente_lock_screen/lock_screen_settings.dart"; +import "package:ente_lock_screen/ui/custom_pin_keypad.dart"; +import "package:ente_strings/ente_strings.dart"; +import "package:ente_ui/theme/ente_theme.dart"; import "package:flutter/material.dart"; import "package:flutter/services.dart"; import "package:pinput/pinput.dart"; @@ -22,15 +20,6 @@ class _LockScreenConfirmPinState extends State { bool isConfirmPinValid = false; bool isPlatformDesktop = false; final LockScreenSettings _lockscreenSetting = LockScreenSettings.instance; - final _pinPutDecoration = PinTheme( - height: 48, - width: 48, - padding: const EdgeInsets.only(top: 6.0), - decoration: BoxDecoration( - border: Border.all(color: const Color.fromRGBO(45, 194, 98, 1.0)), - borderRadius: BorderRadius.circular(15.0), - ), - ); @override void initState() { @@ -91,7 +80,17 @@ class _LockScreenConfirmPinState extends State { ); } - Widget _getBody(EnteColorScheme colorTheme, EnteTextTheme textTheme) { + Widget _getBody(colorTheme, textTheme) { + final pinPutDecoration = PinTheme( + height: 48, + width: 48, + padding: const EdgeInsets.only(top: 6.0), + decoration: BoxDecoration( + border: Border.all(color: const Color.fromRGBO(45, 194, 98, 1.0)), + borderRadius: BorderRadius.circular(15.0), + ), + ); + return Center( child: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -161,7 +160,7 @@ class _LockScreenConfirmPinState extends State { ), ), Text( - context.l10n.reEnterPin, + context.strings.reEnterPin, style: textTheme.bodyBold, ), const Padding(padding: EdgeInsets.all(12)), @@ -171,8 +170,8 @@ class _LockScreenConfirmPinState extends State { useNativeKeyboard: isPlatformDesktop, autofocus: true, controller: _confirmPinController, - defaultPinTheme: _pinPutDecoration, - submittedPinTheme: _pinPutDecoration.copyWith( + defaultPinTheme: pinPutDecoration, + submittedPinTheme: pinPutDecoration.copyWith( textStyle: textTheme.h3Bold, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), @@ -181,7 +180,7 @@ class _LockScreenConfirmPinState extends State { ), ), ), - followingPinTheme: _pinPutDecoration.copyWith( + followingPinTheme: pinPutDecoration.copyWith( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), border: Border.all( @@ -189,8 +188,8 @@ class _LockScreenConfirmPinState extends State { ), ), ), - focusedPinTheme: _pinPutDecoration, - errorPinTheme: _pinPutDecoration.copyWith( + focusedPinTheme: pinPutDecoration, + errorPinTheme: pinPutDecoration.copyWith( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), border: Border.all( diff --git a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_options.dart b/mobile/packages/lock_screen/lib/ui/lock_screen_options.dart similarity index 88% rename from mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_options.dart rename to mobile/packages/lock_screen/lib/ui/lock_screen_options.dart index 7f48bca84d..176172af95 100644 --- a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_options.dart +++ b/mobile/packages/lock_screen/lib/ui/lock_screen_options.dart @@ -1,25 +1,24 @@ import "dart:async"; import "dart:io"; -import "package:ente_auth/l10n/l10n.dart"; -import "package:ente_auth/services/local_authentication_service.dart"; -import "package:ente_auth/theme/ente_theme.dart"; -import "package:ente_auth/ui/components/buttons/button_widget.dart"; -import "package:ente_auth/ui/components/captioned_text_widget.dart"; -import "package:ente_auth/ui/components/dialog_widget.dart"; -import "package:ente_auth/ui/components/divider_widget.dart"; -import "package:ente_auth/ui/components/menu_item_widget.dart"; -import "package:ente_auth/ui/components/models/button_type.dart"; -import "package:ente_auth/ui/components/title_bar_title_widget.dart"; -import "package:ente_auth/ui/components/title_bar_widget.dart"; -import "package:ente_auth/ui/components/toggle_switch_widget.dart"; -import "package:ente_auth/ui/settings/lock_screen/lock_screen_auto_lock.dart"; -import "package:ente_auth/ui/settings/lock_screen/lock_screen_password.dart"; -import "package:ente_auth/ui/settings/lock_screen/lock_screen_pin.dart"; -import "package:ente_auth/ui/tools/app_lock.dart"; -import "package:ente_auth/utils/lock_screen_settings.dart"; -import "package:ente_auth/utils/navigation_util.dart"; -import "package:ente_auth/utils/platform_util.dart"; +import "package:ente_lock_screen/local_authentication_service.dart"; +import "package:ente_lock_screen/lock_screen_settings.dart"; +import "package:ente_lock_screen/ui/app_lock.dart"; +import "package:ente_lock_screen/ui/lock_screen_auto_lock.dart"; +import "package:ente_lock_screen/ui/lock_screen_password.dart"; +import "package:ente_lock_screen/ui/lock_screen_pin.dart"; +import "package:ente_strings/ente_strings.dart"; +import "package:ente_ui/components/buttons/button_widget.dart"; +import "package:ente_ui/components/buttons/models/button_type.dart"; +import "package:ente_ui/components/captioned_text_widget.dart"; +import "package:ente_ui/components/dialog_widget.dart"; +import "package:ente_ui/components/divider_widget.dart"; +import "package:ente_ui/components/menu_item_widget.dart"; +import "package:ente_ui/components/title_bar_title_widget.dart"; +import "package:ente_ui/components/title_bar_widget.dart"; +import "package:ente_ui/components/toggle_switch_widget.dart"; +import "package:ente_ui/theme/ente_theme.dart"; +import "package:ente_utils/platform_util.dart"; import "package:flutter/material.dart"; class LockScreenOptions extends StatefulWidget { @@ -52,7 +51,8 @@ class _LockScreenOptionsState extends State { final bool pinEnabled = await _lockScreenSettings.isPinSet(); final bool shouldHideAppContent = _lockScreenSettings.getShouldHideAppContent(); - final bool systemLockEnabled = _lockScreenSettings.shouldShowSystemLockScreen(); + final bool systemLockEnabled = + _lockScreenSettings.shouldShowSystemLockScreen(); setState(() { isPasswordEnabled = passwordEnabled; isPinEnabled = pinEnabled; @@ -69,13 +69,13 @@ class _LockScreenOptionsState extends State { } else { await showDialogWidget( context: context, - title: context.l10n.noSystemLockFound, - body: context.l10n.deviceLockEnablePreSteps, + title: context.strings.noSystemLockFound, + body: context.strings.deviceLockEnablePreSteps, isDismissible: true, - buttons: const [ + buttons: [ ButtonWidget( buttonType: ButtonType.secondary, - labelText: "OK", + labelText: context.strings.ok, isInAlert: true, ), ], @@ -171,7 +171,7 @@ class _LockScreenOptionsState extends State { } else if (duration.inSeconds != 0) { return "in ${duration.inSeconds} second${duration.inSeconds > 1 ? 's' : ''}"; } else { - return context.l10n.immediately; + return context.strings.immediately; } } @@ -185,7 +185,7 @@ class _LockScreenOptionsState extends State { slivers: [ TitleBarWidget( flexibleSpaceTitle: TitleBarTitleWidget( - title: context.l10n.appLock, + title: context.strings.appLock, ), ), SliverList( @@ -203,7 +203,7 @@ class _LockScreenOptionsState extends State { children: [ MenuItemWidget( captionedTextWidget: CaptionedTextWidget( - title: context.l10n.appLock, + title: context.strings.appLock, ), alignCaptionedTextToLeft: true, singleBorderRadius: 8, @@ -225,7 +225,7 @@ class _LockScreenOptionsState extends State { right: 12, ), child: Text( - context.l10n.appLockDescription, + context.strings.appLockDescription, style: textTheme.miniFaint, textAlign: TextAlign.left, ), @@ -247,7 +247,7 @@ class _LockScreenOptionsState extends State { children: [ MenuItemWidget( captionedTextWidget: CaptionedTextWidget( - title: context.l10n.deviceLock, + title: context.strings.deviceLock, ), surfaceExecutionStates: false, alignCaptionedTextToLeft: true, @@ -266,7 +266,7 @@ class _LockScreenOptionsState extends State { ), MenuItemWidget( captionedTextWidget: CaptionedTextWidget( - title: context.l10n.pinLock, + title: context.strings.pinLock, ), surfaceExecutionStates: false, alignCaptionedTextToLeft: true, @@ -284,7 +284,7 @@ class _LockScreenOptionsState extends State { ), MenuItemWidget( captionedTextWidget: CaptionedTextWidget( - title: context.l10n.password, + title: context.strings.password, ), surfaceExecutionStates: false, alignCaptionedTextToLeft: true, @@ -304,7 +304,7 @@ class _LockScreenOptionsState extends State { ? MenuItemWidget( captionedTextWidget: CaptionedTextWidget( - title: context.l10n.autoLock, + title: context.strings.autoLock, subTitle: _formatTime( Duration( milliseconds: @@ -329,7 +329,7 @@ class _LockScreenOptionsState extends State { right: 12, ), child: Text( - context.l10n + context.strings .autoLockFeatureDescription, style: textTheme.miniFaint, textAlign: TextAlign.left, @@ -345,8 +345,8 @@ class _LockScreenOptionsState extends State { MenuItemWidget( captionedTextWidget: CaptionedTextWidget( - title: - context.l10n.hideContent, + title: context + .strings.hideContent, ), alignCaptionedTextToLeft: true, singleBorderRadius: 8, @@ -367,9 +367,9 @@ class _LockScreenOptionsState extends State { ), child: Text( Platform.isAndroid - ? context.l10n + ? context.strings .hideContentDescriptionAndroid - : context.l10n + : context.strings .hideContentDescriptioniOS, style: textTheme.miniFaint, textAlign: TextAlign.left, @@ -394,4 +394,6 @@ class _LockScreenOptionsState extends State { ), ); } + + routeToPage(BuildContext context, LockScreenAutoLock lockScreenAutoLock) {} } diff --git a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_password.dart b/mobile/packages/lock_screen/lib/ui/lock_screen_password.dart similarity index 92% rename from mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_password.dart rename to mobile/packages/lock_screen/lib/ui/lock_screen_password.dart index e9443b2486..85a1cb4d0b 100644 --- a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_password.dart +++ b/mobile/packages/lock_screen/lib/ui/lock_screen_password.dart @@ -1,14 +1,14 @@ import "dart:convert"; -import "package:ente_auth/l10n/l10n.dart"; -import "package:ente_auth/theme/ente_theme.dart"; -import "package:ente_auth/ui/common/dynamic_fab.dart"; -import "package:ente_auth/ui/components/buttons/icon_button_widget.dart"; -import "package:ente_auth/ui/components/text_input_widget.dart"; -import "package:ente_auth/ui/settings/lock_screen/lock_screen_confirm_password.dart"; -import "package:ente_auth/ui/settings/lock_screen/lock_screen_options.dart"; -import "package:ente_auth/utils/lock_screen_settings.dart"; import "package:ente_crypto_dart/ente_crypto_dart.dart"; +import "package:ente_lock_screen/lock_screen_settings.dart"; +import "package:ente_lock_screen/ui/lock_screen_confirm_password.dart"; +import "package:ente_lock_screen/ui/lock_screen_options.dart"; +import "package:ente_strings/ente_strings.dart"; +import "package:ente_ui/components/buttons/dynamic_fab.dart"; +import "package:ente_ui/components/buttons/icon_button_widget.dart"; +import "package:ente_ui/components/text_input_widget.dart"; +import "package:ente_ui/theme/ente_theme.dart"; import "package:flutter/material.dart"; import "package:flutter/services.dart"; @@ -99,7 +99,7 @@ class _LockScreenPasswordState extends State { builder: (context, isFormValid, child) { return DynamicFAB( isKeypadOpen: isKeypadOpen, - buttonText: context.l10n.next, + buttonText: context.strings.next, isFormValid: isFormValid, onPressedFunction: () async { _submitNotifier.value = !_submitNotifier.value; @@ -165,8 +165,8 @@ class _LockScreenPasswordState extends State { ), Text( widget.isChangingLockScreenSettings - ? context.l10n.enterPassword - : context.l10n.setNewPassword, + ? context.strings.enterPassword + : context.strings.setNewPassword, textAlign: TextAlign.center, style: textTheme.bodyBold, ), @@ -174,7 +174,7 @@ class _LockScreenPasswordState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: TextInputWidget( - hintText: context.l10n.password, + hintText: context.strings.password, autoFocus: true, textCapitalization: TextCapitalization.none, isPasswordInput: true, diff --git a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_pin.dart b/mobile/packages/lock_screen/lib/ui/lock_screen_pin.dart similarity index 87% rename from mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_pin.dart rename to mobile/packages/lock_screen/lib/ui/lock_screen_pin.dart index 4361622920..a5f1950bb8 100644 --- a/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_pin.dart +++ b/mobile/packages/lock_screen/lib/ui/lock_screen_pin.dart @@ -1,15 +1,15 @@ import "dart:convert"; import "dart:io"; -import "package:ente_auth/l10n/l10n.dart"; -import "package:ente_auth/theme/colors.dart"; -import "package:ente_auth/theme/ente_theme.dart"; -import "package:ente_auth/theme/text_style.dart"; -import "package:ente_auth/ui/settings/lock_screen/custom_pin_keypad.dart"; -import "package:ente_auth/ui/settings/lock_screen/lock_screen_confirm_pin.dart"; -import "package:ente_auth/ui/settings/lock_screen/lock_screen_options.dart"; -import "package:ente_auth/utils/lock_screen_settings.dart"; import "package:ente_crypto_dart/ente_crypto_dart.dart"; +import "package:ente_lock_screen/lock_screen_settings.dart"; +import "package:ente_lock_screen/ui/custom_pin_keypad.dart"; +import "package:ente_lock_screen/ui/lock_screen_confirm_pin.dart"; +import "package:ente_lock_screen/ui/lock_screen_options.dart"; +import "package:ente_strings/ente_strings.dart"; +import "package:ente_ui/theme/colors.dart"; +import "package:ente_ui/theme/ente_theme.dart"; +import "package:ente_ui/theme/text_style.dart"; import "package:flutter/material.dart"; import "package:flutter/services.dart"; import 'package:pinput/pinput.dart'; @@ -120,20 +120,20 @@ class _LockScreenPinState extends State { } } - final _pinPutDecoration = PinTheme( - height: 48, - width: 48, - padding: const EdgeInsets.only(top: 6.0), - decoration: BoxDecoration( - border: Border.all(color: const Color.fromRGBO(45, 194, 98, 1.0)), - borderRadius: BorderRadius.circular(15.0), - ), - ); - @override Widget build(BuildContext context) { final colorTheme = getEnteColorScheme(context); final textTheme = getEnteTextTheme(context); + + final pinPutDecoration = PinTheme( + height: 48, + width: 48, + padding: const EdgeInsets.only(top: 6.0), + decoration: BoxDecoration( + border: Border.all(color: const Color.fromRGBO(45, 194, 98, 1.0)), + borderRadius: BorderRadius.circular(15.0), + ), + ); return Scaffold( appBar: AppBar( elevation: 0, @@ -152,7 +152,7 @@ class _LockScreenPinState extends State { : CustomPinKeypad(controller: _pinController), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, body: SingleChildScrollView( - child: _getBody(colorTheme, textTheme), + child: _getBody(colorTheme, textTheme, pinPutDecoration), ), ); } @@ -160,6 +160,7 @@ class _LockScreenPinState extends State { Widget _getBody( EnteColorScheme colorTheme, EnteTextTheme textTheme, + PinTheme pinPutDecoration, ) { return Center( child: Column( @@ -231,8 +232,8 @@ class _LockScreenPinState extends State { ), Text( widget.isChangingLockScreenSettings - ? context.l10n.enterPin - : context.l10n.setNewPin, + ? context.strings.enterPin + : context.strings.setNewPin, style: textTheme.bodyBold, ), const Padding(padding: EdgeInsets.all(12)), @@ -242,8 +243,8 @@ class _LockScreenPinState extends State { useNativeKeyboard: isPlatformDesktop, controller: _pinController, autofocus: true, - defaultPinTheme: _pinPutDecoration, - submittedPinTheme: _pinPutDecoration.copyWith( + defaultPinTheme: pinPutDecoration, + submittedPinTheme: pinPutDecoration.copyWith( textStyle: textTheme.h3Bold, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), @@ -252,7 +253,7 @@ class _LockScreenPinState extends State { ), ), ), - followingPinTheme: _pinPutDecoration.copyWith( + followingPinTheme: pinPutDecoration.copyWith( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), border: Border.all( @@ -260,8 +261,8 @@ class _LockScreenPinState extends State { ), ), ), - focusedPinTheme: _pinPutDecoration, - errorPinTheme: _pinPutDecoration.copyWith( + focusedPinTheme: pinPutDecoration, + errorPinTheme: pinPutDecoration.copyWith( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), border: Border.all( diff --git a/mobile/packages/lock_screen/pubspec.lock b/mobile/packages/lock_screen/pubspec.lock new file mode 100644 index 0000000000..4b6d02e6f3 --- /dev/null +++ b/mobile/packages/lock_screen/pubspec.lock @@ -0,0 +1,1288 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + app_links: + dependency: transitive + description: + name: app_links + sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba" + url: "https://pub.dev" + source: hosted + version: "6.4.0" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + bip39: + dependency: transitive + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cronet_http: + dependency: transitive + description: + name: cronet_http + sha256: df26af0de7c4eff46c53c190b5590e22457bfce6ea679aedb1e6326197f27d6f + url: "https://pub.dev" + source: hosted + version: "1.4.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_http: + dependency: transitive + description: + name: cupertino_http + sha256: "8fb9e2c36d0732d9d96abd76683406b57e78a2514e27c962e0c603dbe6f2e3f8" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + dio: + dependency: transitive + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + dotted_border: + dependency: transitive + description: + name: dotted_border + sha256: "99b091ec6891ba0c5331fdc2b502993c7c108f898995739a73c6845d71dad70c" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + email_validator: + dependency: transitive + description: + name: email_validator + sha256: b19aa5d92fdd76fbc65112060c94d45ba855105a28bb6e462de7ff03b12fa1fb + url: "https://pub.dev" + source: hosted + version: "3.0.0" + ente_accounts: + dependency: "direct main" + description: + path: "../accounts" + relative: true + source: path + version: "1.0.0" + ente_base: + dependency: transitive + description: + path: "../base" + relative: true + source: path + version: "1.0.0" + ente_configuration: + dependency: "direct main" + description: + path: "../configuration" + relative: true + source: path + version: "1.0.0" + ente_crypto_dart: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: f91e1545f8263df127762240c4da54a0c42835b2 + url: "https://github.com/ente-io/ente_crypto_dart.git" + source: git + version: "1.0.0" + ente_events: + dependency: "direct main" + description: + path: "../events" + relative: true + source: path + version: "1.0.0" + ente_logging: + dependency: transitive + description: + path: "../logging" + relative: true + source: path + version: "1.0.0" + ente_network: + dependency: transitive + description: + path: "../network" + relative: true + source: path + version: "1.0.0" + ente_strings: + dependency: "direct main" + description: + path: "../strings" + relative: true + source: path + version: "1.0.0" + ente_ui: + dependency: "direct main" + description: + path: "../ui" + relative: true + source: path + version: "1.0.0" + ente_utils: + dependency: "direct main" + description: + path: "../utils" + relative: true + source: path + version: "1.0.0" + event_bus: + dependency: transitive + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + expandable: + dependency: transitive + description: + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_saver: + dependency: transitive + description: + name: file_saver + sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_animate: + dependency: "direct main" + description: + name: flutter_animate + sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" + url: "https://pub.dev" + source: hosted + version: "4.5.2" + flutter_email_sender: + dependency: transitive + description: + name: flutter_email_sender + sha256: d39eb5e91358fc19ec4050da69accec21f9d5b2b6bcf188aa246327b6ca2352c + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter_inappwebview: + dependency: transitive + description: + name: flutter_inappwebview + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_windows: + dependency: transitive + description: + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_local_authentication: + dependency: "direct main" + description: + path: "." + ref: "1ac346a04592a05fd75acccf2e01fa3c7e955d96" + resolved-ref: "1ac346a04592a05fd75acccf2e01fa3c7e955d96" + url: "https://github.com/eaceto/flutter_local_authentication" + source: git + version: "1.2.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "1c2b787f99bdca1f3718543f81d38aa1b124817dfeb9fb196201bea85b6134bf" + url: "https://pub.dev" + source: hosted + version: "2.0.26" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" + url: "https://pub.dev" + source: hosted + version: "0.1.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: transitive + description: + name: fluttertoast + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + url: "https://pub.dev" + source: hosted + version: "8.2.12" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.dev" + source: hosted + version: "2.1.0" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + http_profile: + dependency: transitive + description: + name: http_profile + sha256: "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + jni: + dependency: transitive + description: + name: jni + sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1 + url: "https://pub.dev" + source: hosted + version: "0.14.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + local_auth: + dependency: "direct main" + description: + name: local_auth + sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + local_auth_android: + dependency: transitive + description: + name: local_auth_android + sha256: "8bba79f4f0f7bc812fce2ca20915d15618c37721246ba6c3ef2aa7a763a90cf2" + url: "https://pub.dev" + source: hosted + version: "1.0.47" + local_auth_darwin: + dependency: transitive + description: + name: local_auth_darwin + sha256: "630996cd7b7f28f5ab92432c4b35d055dd03a747bc319e5ffbb3c4806a3e50d2" + url: "https://pub.dev" + source: hosted + version: "1.4.3" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" + url: "https://pub.dev" + source: hosted + version: "1.0.10" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + url: "https://pub.dev" + source: hosted + version: "1.0.11" + logging: + dependency: "direct main" + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + modal_bottom_sheet: + dependency: transitive + description: + name: modal_bottom_sheet + sha256: eac66ef8cb0461bf069a38c5eb0fa728cee525a531a8304bd3f7b2185407c67e + url: "https://pub.dev" + source: hosted + version: "3.0.0" + native_dio_adapter: + dependency: transitive + description: + name: native_dio_adapter + sha256: "7420bc9517b2abe09810199a19924617b45690a44ecfb0616ac9babc11875c03" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "9f034ba1eeca53ddb339bc8f4813cb07336a849cd735559b60cdc068ecce2dc7" + url: "https://pub.dev" + source: hosted + version: "7.1.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + password_strength: + dependency: transitive + description: + name: password_strength + sha256: "0e51e3d864e37873a1347e658147f88b66e141ee36c58e19828dc5637961e1ce" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + pinput: + dependency: "direct main" + description: + name: pinput + sha256: "8a73be426a91fefec90a7f130763ca39772d547e92f19a827cf4aa02e323d35a" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + privacy_screen: + dependency: "direct main" + description: + name: privacy_screen + sha256: "2856e3a3ed082061a5cd2a1518f1ce6367c55916fb75e5db72e5983033a1ca54" + url: "https://pub.dev" + source: hosted + version: "0.0.8" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_linux: + dependency: transitive + description: + name: screen_retriever_linux + sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_macos: + dependency: transitive + description: + name: screen_retriever_macos + sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_platform_interface: + dependency: transitive + description: + name: screen_retriever_platform_interface + sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_windows: + dependency: transitive + description: + name: screen_retriever_windows + sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + sentry: + dependency: transitive + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + sentry_flutter: + dependency: transitive + description: + name: sentry_flutter + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + share_plus: + dependency: transitive + description: + name: share_plus + sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 + url: "https://pub.dev" + source: hosted + version: "11.1.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + sodium: + dependency: transitive + description: + name: sodium + sha256: d9830a388e37c82891888e64cfd4c6764fa3ac716bed80ac6eab89ee42c3cd76 + url: "https://pub.dev" + source: hosted + version: "2.3.1+1" + sodium_libs: + dependency: transitive + description: + name: sodium_libs + sha256: aa764acd6ccc6113e119c2d99471aeeb4637a9a501639549b297d3a143ff49b3 + url: "https://pub.dev" + source: hosted + version: "2.2.1+6" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + step_progress_indicator: + dependency: transitive + description: + name: step_progress_indicator + sha256: b51bb1fcfc78454359f0658c5a2c21548c3825ebf76e826308e9ca10f383bbb8 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + styled_text: + dependency: transitive + description: + name: styled_text + sha256: fd624172cf629751b4f171dd0ecf9acf02a06df3f8a81bb56c0caa4f1df706c3 + url: "https://pub.dev" + source: hosted + version: "8.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + ua_client_hints: + dependency: transitive + description: + name: ua_client_hints + sha256: "1b8759a46bfeab355252881df27f2604c01bded86aa2b578869fb1b638b23118" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + url: "https://pub.dev" + source: hosted + version: "6.3.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + window_manager: + dependency: transitive + description: + name: window_manager + sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xmlstream: + dependency: transitive + description: + name: xmlstream + sha256: cfc14e3f256997897df9481ae630d94c2d85ada5187ebeb868bb1aabc2c977b4 + url: "https://pub.dev" + source: hosted + version: "1.1.1" +sdks: + dart: ">=3.7.2 <4.0.0" + flutter: ">=3.24.0" diff --git a/mobile/packages/lock_screen/pubspec.yaml b/mobile/packages/lock_screen/pubspec.yaml new file mode 100644 index 0000000000..cbe75137e2 --- /dev/null +++ b/mobile/packages/lock_screen/pubspec.yaml @@ -0,0 +1,45 @@ +name: ente_lock_screen +description: A Flutter package containing lock screen UI components and services for Ente apps +version: 1.0.0 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + ente_accounts: + path: ../accounts + ente_configuration: + path: ../configuration + ente_crypto_dart: + git: + url: https://github.com/ente-io/ente_crypto_dart.git + ente_events: + path: ../events + ente_strings: + path: ../strings + ente_ui: + path: ../ui + ente_utils: + path: ../utils + flutter: + sdk: flutter + flutter_animate: ^4.1.0 + flutter_local_authentication: + git: + url: https://github.com/eaceto/flutter_local_authentication + ref: 1ac346a04592a05fd75acccf2e01fa3c7e955d96 + flutter_secure_storage: ^9.0.0 + local_auth: ^2.3.0 + logging: ^1.1.1 + pinput: ^5.0.0 + privacy_screen: ^0.0.8 + shared_preferences: ^2.5.3 + +dev_dependencies: + flutter_lints: ^5.0.0 + flutter_test: + sdk: flutter + +flutter: diff --git a/mobile/packages/logging/.flutter-plugins b/mobile/packages/logging/.flutter-plugins new file mode 100644 index 0000000000..9b8a33aaa7 --- /dev/null +++ b/mobile/packages/logging/.flutter-plugins @@ -0,0 +1,14 @@ +# This is a generated file; do not edit or check into version control. +package_info_plus=/Users/amanraj/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/ +path_provider=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider-2.1.5/ +path_provider_android=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_android-2.2.15/ +path_provider_foundation=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/ +path_provider_linux=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ +path_provider_windows=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/ +sentry_flutter=/Users/amanraj/.pub-cache/hosted/pub.dev/sentry_flutter-8.14.2/ +shared_preferences=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences-2.5.3/ +shared_preferences_android=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.7/ +shared_preferences_foundation=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/ +shared_preferences_linux=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/ +shared_preferences_web=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/ +shared_preferences_windows=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/ diff --git a/mobile/packages/logging/analysis_options.yaml b/mobile/packages/logging/analysis_options.yaml new file mode 100644 index 0000000000..1bd78bc1b0 --- /dev/null +++ b/mobile/packages/logging/analysis_options.yaml @@ -0,0 +1,72 @@ +# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html +# or https://pub.dev/packages/lint (Effective dart) +# use "flutter analyze ." or "dart analyze ." for running lint checks + +include: package:flutter_lints/flutter.yaml +linter: + rules: + # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml + # Ref https://dart-lang.github.io/linter/lints/ + - avoid_print + - avoid_unnecessary_containers + - avoid_web_libraries_in_flutter + - no_logic_in_create_state + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_final_locals + - require_trailing_commas + - sized_box_for_whitespace + - use_full_hex_values_for_flutter_colors + - use_key_in_widget_constructors + - cancel_subscriptions + + + - avoid_empty_else + - exhaustive_cases + + # just style suggestions + - sort_pub_dependencies + - use_rethrow_when_possible + - prefer_double_quotes + - directives_ordering + - always_use_package_imports + - sort_child_properties_last + - unawaited_futures + +analyzer: + errors: + avoid_empty_else: error + exhaustive_cases: error + curly_braces_in_flow_control_structures: error + directives_ordering: error + require_trailing_commas: error + always_use_package_imports: warning + prefer_final_fields: error + unused_import: error + camel_case_types: error + prefer_is_empty: warning + use_rethrow_when_possible: info + unused_field: warning + use_key_in_widget_constructors: warning + sort_child_properties_last: warning + sort_pub_dependencies: warning + library_private_types_in_public_api: warning + constant_identifier_names: ignore + prefer_const_constructors: warning + prefer_const_declarations: warning + prefer_const_constructors_in_immutables: warning + prefer_final_locals: warning + unnecessary_const: error + cancel_subscriptions: error + unrelated_type_equality_checks: error + unnecessary_cast: info + + + unawaited_futures: warning # convert to warning after fixing existing issues + invalid_dependency: info + use_build_context_synchronously: ignore # experimental lint, requires many changes + prefer_interpolation_to_compose_strings: ignore # later too many warnings + prefer_double_quotes: ignore # too many warnings + avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides diff --git a/mobile/packages/logging/lib/logging.dart b/mobile/packages/logging/lib/logging.dart new file mode 100644 index 0000000000..c3d827a240 --- /dev/null +++ b/mobile/packages/logging/lib/logging.dart @@ -0,0 +1,4 @@ +export 'package:logging/logging.dart'; + +export 'super_logging.dart'; +export 'tunneled_transport.dart'; diff --git a/mobile/apps/auth/lib/core/logging/super_logging.dart b/mobile/packages/logging/lib/super_logging.dart similarity index 95% rename from mobile/apps/auth/lib/core/logging/super_logging.dart rename to mobile/packages/logging/lib/super_logging.dart index 1c5370c8e3..51af75d39a 100644 --- a/mobile/apps/auth/lib/core/logging/super_logging.dart +++ b/mobile/packages/logging/lib/super_logging.dart @@ -3,13 +3,12 @@ import 'dart:collection'; import 'dart:core'; import 'dart:io'; -import 'package:ente_auth/core/logging/tunneled_transport.dart'; -import 'package:ente_auth/models/typedefs.dart'; +import 'package:ente_logging/tunneled_transport.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; -import 'package:logging/logging.dart'; +import 'package:logging/logging.dart' as log; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; @@ -17,6 +16,9 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:uuid/uuid.dart'; +/// Type definition for functions that may return a value or Future +typedef FutureOrVoidCallback = dynamic Function(); + extension SuperString on String { Iterable chunked(int chunkSize) sync* { var start = 0; @@ -34,7 +36,7 @@ extension SuperString on String { } } -extension SuperLogRecord on LogRecord { +extension SuperLogRecord on log.LogRecord { String toPrettyString([String? extraLines]) { final header = "[$loggerName] [$level] [$time]"; @@ -136,7 +138,7 @@ class LogConfig { class SuperLogging { /// The logger for SuperLogging - static final $ = Logger('ente_logging'); + static final $ = log.Logger('ente_logging'); /// The current super logging configuration static late LogConfig config; @@ -168,8 +170,8 @@ class SuperLogging { setupSentry().ignore(); } - Logger.root.level = Level.ALL; - Logger.root.onRecord.listen(onLogRecord); + log.Logger.root.level = log.Level.ALL; + log.Logger.root.onRecord.listen(onLogRecord); if (!enable) { $.info("detected debug mode; sentry & file logging disabled."); @@ -224,7 +226,7 @@ class SuperLogging { static String _lastExtraLines = ''; - static Future onLogRecord(LogRecord rec) async { + static Future onLogRecord(log.LogRecord rec) async { // log misc info if it changed String? extraLines = "app version: '$appVersion'\n"; if (extraLines != _lastExtraLines) { diff --git a/mobile/apps/auth/lib/core/logging/tunneled_transport.dart b/mobile/packages/logging/lib/tunneled_transport.dart similarity index 100% rename from mobile/apps/auth/lib/core/logging/tunneled_transport.dart rename to mobile/packages/logging/lib/tunneled_transport.dart diff --git a/mobile/packages/logging/pubspec.lock b/mobile/packages/logging/pubspec.lock new file mode 100644 index 0000000000..464fd2afb7 --- /dev/null +++ b/mobile/packages/logging/pubspec.lock @@ -0,0 +1,474 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + logging: + dependency: "direct main" + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sentry: + dependency: "direct main" + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + sentry_flutter: + dependency: "direct main" + description: + name: sentry_flutter + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.24.0" diff --git a/mobile/packages/logging/pubspec.yaml b/mobile/packages/logging/pubspec.yaml new file mode 100644 index 0000000000..aed0a718ed --- /dev/null +++ b/mobile/packages/logging/pubspec.yaml @@ -0,0 +1,28 @@ +name: ente_logging +description: A Flutter package for logging functionality across ente apps +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + http: ^1.4.0 + intl: ^0.20.2 + logging: ^1.3.0 + package_info_plus: ^8.3.0 + path: ^1.9.0 + path_provider: ^2.1.5 + sentry: ^8.14.2 + sentry_flutter: ^8.14.2 + shared_preferences: ^2.5.3 + uuid: ^4.5.1 + +dev_dependencies: + flutter_lints: ^5.0.0 + flutter_test: + sdk: flutter + +flutter: diff --git a/mobile/packages/network/.flutter-plugins b/mobile/packages/network/.flutter-plugins new file mode 100644 index 0000000000..1cea5815b1 --- /dev/null +++ b/mobile/packages/network/.flutter-plugins @@ -0,0 +1,26 @@ +# This is a generated file; do not edit or check into version control. +cronet_http=/Users/amanraj/.pub-cache/hosted/pub.dev/cronet_http-1.4.0/ +cupertino_http=/Users/amanraj/.pub-cache/hosted/pub.dev/cupertino_http-2.2.0/ +device_info_plus=/Users/amanraj/.pub-cache/hosted/pub.dev/device_info_plus-9.1.2/ +flutter_secure_storage=/Users/amanraj/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/ +flutter_secure_storage_linux=/Users/amanraj/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/ +flutter_secure_storage_macos=/Users/amanraj/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/ +flutter_secure_storage_web=/Users/amanraj/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/ +flutter_secure_storage_windows=/Users/amanraj/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/ +jni=/Users/amanraj/.pub-cache/hosted/pub.dev/jni-0.14.2/ +objective_c=/Users/amanraj/.pub-cache/hosted/pub.dev/objective_c-7.1.0/ +package_info_plus=/Users/amanraj/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/ +path_provider=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider-2.1.5/ +path_provider_android=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_android-2.2.15/ +path_provider_foundation=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/ +path_provider_linux=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ +path_provider_windows=/Users/amanraj/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/ +sentry_flutter=/Users/amanraj/.pub-cache/hosted/pub.dev/sentry_flutter-8.14.2/ +shared_preferences=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences-2.5.3/ +shared_preferences_android=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.7/ +shared_preferences_foundation=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/ +shared_preferences_linux=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/ +shared_preferences_web=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/ +shared_preferences_windows=/Users/amanraj/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/ +sodium_libs=/Users/amanraj/.pub-cache/hosted/pub.dev/sodium_libs-2.2.1+6/ +ua_client_hints=/Users/amanraj/.pub-cache/hosted/pub.dev/ua_client_hints-1.4.1/ diff --git a/mobile/packages/network/analysis_options.yaml b/mobile/packages/network/analysis_options.yaml new file mode 100644 index 0000000000..1bd78bc1b0 --- /dev/null +++ b/mobile/packages/network/analysis_options.yaml @@ -0,0 +1,72 @@ +# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html +# or https://pub.dev/packages/lint (Effective dart) +# use "flutter analyze ." or "dart analyze ." for running lint checks + +include: package:flutter_lints/flutter.yaml +linter: + rules: + # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml + # Ref https://dart-lang.github.io/linter/lints/ + - avoid_print + - avoid_unnecessary_containers + - avoid_web_libraries_in_flutter + - no_logic_in_create_state + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_final_locals + - require_trailing_commas + - sized_box_for_whitespace + - use_full_hex_values_for_flutter_colors + - use_key_in_widget_constructors + - cancel_subscriptions + + + - avoid_empty_else + - exhaustive_cases + + # just style suggestions + - sort_pub_dependencies + - use_rethrow_when_possible + - prefer_double_quotes + - directives_ordering + - always_use_package_imports + - sort_child_properties_last + - unawaited_futures + +analyzer: + errors: + avoid_empty_else: error + exhaustive_cases: error + curly_braces_in_flow_control_structures: error + directives_ordering: error + require_trailing_commas: error + always_use_package_imports: warning + prefer_final_fields: error + unused_import: error + camel_case_types: error + prefer_is_empty: warning + use_rethrow_when_possible: info + unused_field: warning + use_key_in_widget_constructors: warning + sort_child_properties_last: warning + sort_pub_dependencies: warning + library_private_types_in_public_api: warning + constant_identifier_names: ignore + prefer_const_constructors: warning + prefer_const_declarations: warning + prefer_const_constructors_in_immutables: warning + prefer_final_locals: warning + unnecessary_const: error + cancel_subscriptions: error + unrelated_type_equality_checks: error + unnecessary_cast: info + + + unawaited_futures: warning # convert to warning after fixing existing issues + invalid_dependency: info + use_build_context_synchronously: ignore # experimental lint, requires many changes + prefer_interpolation_to_compose_strings: ignore # later too many warnings + prefer_double_quotes: ignore # too many warnings + avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides diff --git a/mobile/packages/network/devtools_options.yaml b/mobile/packages/network/devtools_options.yaml new file mode 100644 index 0000000000..fa0b357c4f --- /dev/null +++ b/mobile/packages/network/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/mobile/packages/network/lib/ente_network.dart b/mobile/packages/network/lib/ente_network.dart new file mode 100644 index 0000000000..418fd5eba1 --- /dev/null +++ b/mobile/packages/network/lib/ente_network.dart @@ -0,0 +1 @@ +export 'network.dart'; diff --git a/mobile/apps/auth/lib/core/network.dart b/mobile/packages/network/lib/network.dart similarity index 61% rename from mobile/apps/auth/lib/core/network.dart rename to mobile/packages/network/lib/network.dart index 0f5ae3555d..b95c4b5a8f 100644 --- a/mobile/apps/auth/lib/core/network.dart +++ b/mobile/packages/network/lib/network.dart @@ -1,14 +1,13 @@ import 'dart:io'; import 'package:dio/dio.dart'; -import 'package:ente_auth/core/configuration.dart'; -import 'package:ente_auth/core/event_bus.dart'; -import 'package:ente_auth/events/endpoint_updated_event.dart'; -import 'package:ente_auth/utils/package_info_util.dart'; -import 'package:ente_auth/utils/platform_util.dart'; -import 'package:fk_user_agent/fk_user_agent.dart'; +import 'package:ente_configuration/base_configuration.dart'; +import 'package:ente_events/event_bus.dart'; +import 'package:ente_events/models/endpoint_updated_event.dart'; import 'package:flutter/foundation.dart'; import 'package:native_dio_adapter/native_dio_adapter.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:ua_client_hints/ua_client_hints.dart'; import 'package:uuid/uuid.dart'; int kConnectTimeout = 15000; @@ -17,20 +16,19 @@ class Network { late Dio _dio; late Dio _enteDio; - Future init() async { - if (PlatformUtil.isMobile()) await FkUserAgent.init(); - final packageInfo = await PackageInfoUtil().getPackageInfo(); - final version = PackageInfoUtil().getVersion(packageInfo); - final packageName = PackageInfoUtil().getPackageName(packageInfo); - final endpoint = Configuration.instance.getHttpEndpoint(); + Future init(BaseConfiguration configuration) async { + final String ua = await userAgent(); + final packageInfo = await PackageInfo.fromPlatform(); + final version = packageInfo.version; + final packageName = packageInfo.packageName; + final endpoint = configuration.getHttpEndpoint(); + final isMobile = Platform.isAndroid || Platform.isIOS; _dio = Dio( BaseOptions( connectTimeout: Duration(milliseconds: kConnectTimeout), headers: { - HttpHeaders.userAgentHeader: PlatformUtil.isMobile() - ? FkUserAgent.userAgent - : Platform.operatingSystem, + HttpHeaders.userAgentHeader: isMobile ? ua : Platform.operatingSystem, 'X-Client-Version': version, 'X-Client-Package': packageName, }, @@ -42,8 +40,8 @@ class Network { baseUrl: endpoint, connectTimeout: Duration(milliseconds: kConnectTimeout), headers: { - if (PlatformUtil.isMobile()) - HttpHeaders.userAgentHeader: FkUserAgent.userAgent + if (isMobile) + HttpHeaders.userAgentHeader: ua else HttpHeaders.userAgentHeader: Platform.operatingSystem, 'X-Client-Version': version, @@ -55,12 +53,12 @@ class Network { _dio.httpClientAdapter = NativeAdapter(); _enteDio.httpClientAdapter = NativeAdapter(); - _setupInterceptors(endpoint); + _setupInterceptors(configuration); Bus.instance.on().listen((event) { - final endpoint = Configuration.instance.getHttpEndpoint(); + final endpoint = configuration.getHttpEndpoint(); _enteDio.options.baseUrl = endpoint; - _setupInterceptors(endpoint); + _setupInterceptors(configuration); }); } @@ -71,12 +69,12 @@ class Network { Dio getDio() => _dio; Dio get enteDio => _enteDio; - void _setupInterceptors(String endpoint) { + void _setupInterceptors(BaseConfiguration configuration) { _dio.interceptors.clear(); _dio.interceptors.add(RequestIdInterceptor()); _enteDio.interceptors.clear(); - _enteDio.interceptors.add(EnteRequestInterceptor(endpoint)); + _enteDio.interceptors.add(EnteRequestInterceptor(configuration)); } } @@ -90,21 +88,21 @@ class RequestIdInterceptor extends Interceptor { } class EnteRequestInterceptor extends Interceptor { - final String endpoint; + final BaseConfiguration configuration; - EnteRequestInterceptor(this.endpoint); + EnteRequestInterceptor(this.configuration); @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { if (kDebugMode) { assert( - options.baseUrl == endpoint, + options.baseUrl == configuration.getHttpEndpoint(), "interceptor should only be used for API endpoint", ); } options.headers .putIfAbsent("x-request-id", () => const Uuid().v4().toString()); - final String? tokenValue = Configuration.instance.getToken(); + final String? tokenValue = configuration.getToken(); if (tokenValue != null) { options.headers.putIfAbsent("X-Auth-Token", () => tokenValue); } diff --git a/mobile/packages/network/pubspec.lock b/mobile/packages/network/pubspec.lock new file mode 100644 index 0000000000..abf8393667 --- /dev/null +++ b/mobile/packages/network/pubspec.lock @@ -0,0 +1,799 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + bip39: + dependency: transitive + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cronet_http: + dependency: transitive + description: + name: cronet_http + sha256: df26af0de7c4eff46c53c190b5590e22457bfce6ea679aedb1e6326197f27d6f + url: "https://pub.dev" + source: hosted + version: "1.4.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + cupertino_http: + dependency: transitive + description: + name: cupertino_http + sha256: "8fb9e2c36d0732d9d96abd76683406b57e78a2514e27c962e0c603dbe6f2e3f8" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + dio: + dependency: "direct main" + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + ente_base: + dependency: transitive + description: + path: "../base" + relative: true + source: path + version: "1.0.0" + ente_configuration: + dependency: "direct main" + description: + path: "../configuration" + relative: true + source: path + version: "1.0.0" + ente_crypto_dart: + dependency: transitive + description: + path: "." + ref: HEAD + resolved-ref: f91e1545f8263df127762240c4da54a0c42835b2 + url: "https://github.com/ente-io/ente_crypto_dart.git" + source: git + version: "1.0.0" + ente_events: + dependency: "direct main" + description: + path: "../events" + relative: true + source: path + version: "1.0.0" + ente_logging: + dependency: transitive + description: + path: "../logging" + relative: true + source: path + version: "1.0.0" + event_bus: + dependency: transitive + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_secure_storage: + dependency: transitive + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + http_profile: + dependency: transitive + description: + name: http_profile + sha256: "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + jni: + dependency: transitive + description: + name: jni + sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1 + url: "https://pub.dev" + source: hosted + version: "0.14.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + native_dio_adapter: + dependency: "direct main" + description: + name: native_dio_adapter + sha256: "7420bc9517b2abe09810199a19924617b45690a44ecfb0616ac9babc11875c03" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "9f034ba1eeca53ddb339bc8f4813cb07336a849cd735559b60cdc068ecce2dc7" + url: "https://pub.dev" + source: hosted + version: "7.1.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + sentry: + dependency: transitive + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + sentry_flutter: + dependency: transitive + description: + name: sentry_flutter + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + sodium: + dependency: transitive + description: + name: sodium + sha256: d9830a388e37c82891888e64cfd4c6764fa3ac716bed80ac6eab89ee42c3cd76 + url: "https://pub.dev" + source: hosted + version: "2.3.1+1" + sodium_libs: + dependency: transitive + description: + name: sodium_libs + sha256: aa764acd6ccc6113e119c2d99471aeeb4637a9a501639549b297d3a143ff49b3 + url: "https://pub.dev" + source: hosted + version: "2.2.1+6" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + ua_client_hints: + dependency: "direct main" + description: + name: ua_client_hints + sha256: "1b8759a46bfeab355252881df27f2604c01bded86aa2b578869fb1b638b23118" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.24.0" diff --git a/mobile/packages/network/pubspec.yaml b/mobile/packages/network/pubspec.yaml new file mode 100644 index 0000000000..1e2e9b13d9 --- /dev/null +++ b/mobile/packages/network/pubspec.yaml @@ -0,0 +1,28 @@ +name: ente_network +description: A Flutter package for network management and HTTP client configuration +version: 1.0.0 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + dio: ^5.8.0+1 + ente_configuration: + path: ../../packages/configuration + ente_events: + path: ../../packages/events + flutter: + sdk: flutter + native_dio_adapter: ^1.4.0 + package_info_plus: ^8.3.0 + ua_client_hints: ^1.4.1 + uuid: ^4.5.1 + +dev_dependencies: + flutter_lints: ^5.0.0 + flutter_test: + sdk: flutter + +flutter: diff --git a/mobile/packages/strings/analysis_options.yaml b/mobile/packages/strings/analysis_options.yaml new file mode 100644 index 0000000000..1bd78bc1b0 --- /dev/null +++ b/mobile/packages/strings/analysis_options.yaml @@ -0,0 +1,72 @@ +# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html +# or https://pub.dev/packages/lint (Effective dart) +# use "flutter analyze ." or "dart analyze ." for running lint checks + +include: package:flutter_lints/flutter.yaml +linter: + rules: + # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml + # Ref https://dart-lang.github.io/linter/lints/ + - avoid_print + - avoid_unnecessary_containers + - avoid_web_libraries_in_flutter + - no_logic_in_create_state + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_final_locals + - require_trailing_commas + - sized_box_for_whitespace + - use_full_hex_values_for_flutter_colors + - use_key_in_widget_constructors + - cancel_subscriptions + + + - avoid_empty_else + - exhaustive_cases + + # just style suggestions + - sort_pub_dependencies + - use_rethrow_when_possible + - prefer_double_quotes + - directives_ordering + - always_use_package_imports + - sort_child_properties_last + - unawaited_futures + +analyzer: + errors: + avoid_empty_else: error + exhaustive_cases: error + curly_braces_in_flow_control_structures: error + directives_ordering: error + require_trailing_commas: error + always_use_package_imports: warning + prefer_final_fields: error + unused_import: error + camel_case_types: error + prefer_is_empty: warning + use_rethrow_when_possible: info + unused_field: warning + use_key_in_widget_constructors: warning + sort_child_properties_last: warning + sort_pub_dependencies: warning + library_private_types_in_public_api: warning + constant_identifier_names: ignore + prefer_const_constructors: warning + prefer_const_declarations: warning + prefer_const_constructors_in_immutables: warning + prefer_final_locals: warning + unnecessary_const: error + cancel_subscriptions: error + unrelated_type_equality_checks: error + unnecessary_cast: info + + + unawaited_futures: warning # convert to warning after fixing existing issues + invalid_dependency: info + use_build_context_synchronously: ignore # experimental lint, requires many changes + prefer_interpolation_to_compose_strings: ignore # later too many warnings + prefer_double_quotes: ignore # too many warnings + avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides diff --git a/mobile/packages/strings/build/.last_build_id b/mobile/packages/strings/build/.last_build_id new file mode 100644 index 0000000000..3887ace2f7 --- /dev/null +++ b/mobile/packages/strings/build/.last_build_id @@ -0,0 +1 @@ +2edd7a931624573eff7b4280d22a7f4e \ No newline at end of file diff --git a/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/.filecache b/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/.filecache new file mode 100644 index 0000000000..956c754b17 --- /dev/null +++ b/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/.filecache @@ -0,0 +1 @@ +{"version":2,"files":[{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart","hash":"e1c2dd402ece615704eaf45f3e2729a6"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb","hash":"45c460142089a4ea87608b06ff2628b1"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb","hash":"25dad41ccf2f92d2c497c7800a1ce45c"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart","hash":"b4d03024c1c40f7d5353ebced85205a2"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart","hash":"ac39db85fddd532a36bc0ad2ff7a65da"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb","hash":"4f2fe3f8c87899e023e72b5a170061aa"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart","hash":"ceed420aa54a45d5cf8b74721098e1cd"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart","hash":"249fa76b027a367d4d3175dc97a91fca"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb","hash":"09e4f926e05cee727e7f9a32cc98b812"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart","hash":"712abca07c4b95d9ff1d4d49e5a0ba93"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb","hash":"9dedf57678d81d80efcd3a706a4d7a30"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb","hash":"5a8a246b080e3003cf8f728d2b10b6f9"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb","hash":"24b4e737f1bef65c324059937e559819"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_id.dart","hash":"498d474db7361219be2b1a84a382d4df"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_el.dart","hash":"2d9ed54e233b37087ec75a58e2cb5e1b"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_en.dart","hash":"aace294470f29d70e75ae5e6e2cbd6c3"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart","hash":"b18042dbed118bcf827f7e76bad1f86f"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart","hash":"bcc9f15b7bb9e2853491d54d0452def2"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations.dart","hash":"5d9ebe82abf371f5e62696bae72eefaa"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart","hash":"5c0867a5c13989b8603cc22375d5464c"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb","hash":"68c77aa51f2ba7ce1cc24a7ed22a648b"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb","hash":"ee2dfa2a8e23ad82d27e32d499083189"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb","hash":"ebfcd08d4c861cc0c5d2d5371e430e56"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart","hash":"5329b0b8d1335418b8e7c28eb6b97acb"},{"path":"/Users/vishnu/tools/flutter/packages/flutter_tools/lib/src/build_system/targets/localizations.dart","hash":"5ff2d90544438cc086555a06fe71334b"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/l10n.yaml","hash":"7f1fe83b9f37bd55ea049a6a2e5d1abe"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart","hash":"bada8a24323a9032a1d48f99d364f7f3"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb","hash":"f5e6b9da5f1fce53cb7a13ed0ac928d4"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_da.dart","hash":"97848131d81cb0c62ef4500104178d3a"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb","hash":"72a20184b7c14aa3bebc1829168ec249"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb","hash":"b8b1ab1d8d851e1efdaac119ca9aed51"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart","hash":"e281bb4412dfed8559e8bc670ae9b7f7"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_es.dart","hash":"9ad9b82614ff0ab5763c9cae9644b8d6"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb","hash":"a8421a85bad5a6adbff705117f683290"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart","hash":"3837a1da249e7377d67ca12fddf61708"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb","hash":"05da370179c31437bac685fae12ba5e3"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb","hash":"faab6eda68e6fc4782603410686f468a"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb","hash":"b344d11d93750b02904d1ee4b1e40d9e"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart","hash":"3fa1f5ae77f6ee3ae37a2af7ba5c4e17"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb","hash":"55ad5a67f7709ca095b64d46c68feaf7"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart","hash":"f66aa0ae4b9b07a3611fda4e3312be41"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb","hash":"125a748e9305bf2ff330e307b52ac99f"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb","hash":"5867a832ae87b5e70284d8987d79b4b3"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb","hash":"91fa72e09a0f06350440c87d81ae500c"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb","hash":"1783f60bcbf1764090536df245866c22"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart","hash":"7e6e759e326dd57ef8fcf50e7b09709b"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart","hash":"39dae1cf59fe1386ca5813e7f9d99a89"},{"path":"/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb","hash":"fc62522a963025f3b7fc2c6502083fe7"}]} \ No newline at end of file diff --git a/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/gen_l10n_inputs_and_outputs.json b/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/gen_l10n_inputs_and_outputs.json new file mode 100644 index 0000000000..b980c23ac8 --- /dev/null +++ b/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/gen_l10n_inputs_and_outputs.json @@ -0,0 +1 @@ +{"inputs":["/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb"],"outputs":["/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_da.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_el.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_en.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_es.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_id.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations.dart"]} \ No newline at end of file diff --git a/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/gen_localizations.d b/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/gen_localizations.d new file mode 100644 index 0000000000..fef0e6d5cf --- /dev/null +++ b/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/gen_localizations.d @@ -0,0 +1 @@ + /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_da.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_el.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_en.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_es.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_id.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations.dart: /Users/vishnu/work/ente/mobile/packages/strings/l10n.yaml /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb /Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb \ No newline at end of file diff --git a/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/gen_localizations.stamp b/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/gen_localizations.stamp new file mode 100644 index 0000000000..118ea0a01b --- /dev/null +++ b/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/gen_localizations.stamp @@ -0,0 +1 @@ +{"inputs":["/Users/vishnu/tools/flutter/packages/flutter_tools/lib/src/build_system/targets/localizations.dart","/Users/vishnu/work/ente/mobile/packages/strings/l10n.yaml","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb"],"outputs":["/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_da.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_el.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_en.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_es.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_id.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations.dart"]} \ No newline at end of file diff --git a/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/outputs.json b/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/outputs.json new file mode 100644 index 0000000000..bded8f773a --- /dev/null +++ b/mobile/packages/strings/build/1bb2a21ff13ffb469b0dfcac2f14d315/outputs.json @@ -0,0 +1 @@ +["/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_da.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_el.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_en.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_es.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_id.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart","/Users/vishnu/work/ente/mobile/packages/strings/lib/l10n/strings_localizations.dart"] \ No newline at end of file diff --git a/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/.filecache b/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/.filecache new file mode 100644 index 0000000000..db85c060dd --- /dev/null +++ b/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/.filecache @@ -0,0 +1 @@ +{"version":2,"files":[{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ka.arb","hash":"9cd3b583ef2a0ba24dce579b0e551f9c"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hi.arb","hash":"9ed088070b8a4e1abc6ca573f571d461"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart","hash":"71288b898ed43b69a51837c923aaa7a5"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_et.dart","hash":"60542a14aa2ff8fe19e32ffa1bb282f6"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_km.arb","hash":"23fb911d60d6124067af7c81c92d17d0"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart","hash":"91ebe148f2fe4044e92215b97e3a1a50"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_da.dart","hash":"f354d37885a430fa2eeb66ce5f6cfce6"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart","hash":"46e3b4b91b3940dbea272ce56b3dc1e7"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_uk.dart","hash":"419261354218dae8bb17669e3bdae814"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_hu.dart","hash":"6ba60fecfba0b8ea0c003878d510dfb3"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_he.dart","hash":"959f5fc63a790fc864d6cde960de1b94"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_be.arb","hash":"891655dc1aace6917ee027b4703e5038"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sl.dart","hash":"e63ab6e23fd144b7a1b3580efed7eaf7"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb","hash":"682b375aaded1c742fb9b7f2048ecef3"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb","hash":"2f63b5f8cc2603056e8afae563d5ebeb"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_uk.arb","hash":"18734cbdc4ca08381dbf05edbb04e764"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart","hash":"a263d0cc8ddd8719e4282655afd5c2e6"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ti.dart","hash":"43564bc74f962cb121962c53bb1a76e4"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_it.arb","hash":"e1330022bdc52a31cdec5ed56ec3509e"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart","hash":"67887a2f596bfe05edf79f0fa35d3a8e"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_de.arb","hash":"d36cae88dcd2083fd934b7973a71bc6c"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb","hash":"640c06b0f22964611cf9b6b47939e8aa"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb","hash":"032ae47fe9c19834e14411871a4f6137"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ca.dart","hash":"ae89b93da6d535782205755312719076"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_be.dart","hash":"3181396325d10f07b08ec0237e00a708"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb","hash":"8891455a6ec548c14b849b66380846a6"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ro.arb","hash":"7fbb2929c42db782e4dfe30c7d8a6375"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_km.dart","hash":"31f85c81278f493df05ed0cbe1ca57f4"},{"path":"/Users/amanraj/development/flutter/packages/flutter_tools/lib/src/build_system/targets/localizations.dart","hash":"5ff2d90544438cc086555a06fe71334b"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart","hash":"31fed56051556c56495eb8457836a802"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_lv.dart","hash":"0a4ab9207ec94fa011613567e6cb34ba"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb","hash":"dc7e80ad8cea661ea613b0f5b36c9662"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart","hash":"4a4efa317f1bc41410356ea33cd97f00"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sl.arb","hash":"e45f59e1bdbe6f47b870919182a6ea12"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ro.dart","hash":"15ae6e78a97cbaf91eac549c1e7b200e"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hu.arb","hash":"ab0f09b5bd538dd9541f42baec4be64a"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb","hash":"9e5f514aa7b96c42dd74b6df60c15deb"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_en.dart","hash":"be57fdd02f5dc23a2fe7a3917bae183f"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart","hash":"a249efb9d117a249579550bb56515d22"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb","hash":"95433370dc0f99c299e3b668e291e97b"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_et.arb","hash":"9d958d6ee4550c5346d8202428663869"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb","hash":"ca78debc0d042bd2883ae9371dd74f75"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/l10n.yaml","hash":"7f1fe83b9f37bd55ea049a6a2e5d1abe"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ti.arb","hash":"1946b5c8fbefe5af85a74af8995a29bd"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_gu.arb","hash":"93ee531a43623d62697085e9a5d9ccbe"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ml.arb","hash":"92a50c05f4e3ae0f369b72479a732c9f"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb","hash":"de6e7fcb10b5297abfebea313aaaf9d1"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_it.dart","hash":"819b2d3e9e24c1e45d6e47fafb391b3b"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb","hash":"d1c0176b94b78c6d17ce12eb0085e6e6"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb","hash":"8bf38e1bde975e58ccf64b9ba9d6b626"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lv.arb","hash":"a3c8ba63da0a61f93e23029d0badc25e"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart","hash":"c802ea7f8ee49c9c883c5757a63ad9a6"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ca.arb","hash":"993f8367aff09ed68fee0048e74b0564"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb","hash":"a9f5860ba6b6218b0fe1254c0effed45"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb","hash":"71030f741e1952b92b63200a2170f120"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_hi.dart","hash":"2b049168cb55faef8d7fe218e1ddcd47"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fa.arb","hash":"e55f440d7bdd2c3cd692c0ea457065ff"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_el.dart","hash":"2a357d2f0405dbdfa004b633beea4952"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_es.dart","hash":"24496779b44fa7443de3bb7fe6476139"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fi.arb","hash":"fa18867ceb7220ba33ca4cfa957bd379"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ml.dart","hash":"7a66bd9232b0fb069fafd189c7854c65"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_id.dart","hash":"8861fd036ca6ffeb75bcdc9ddec987ea"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb","hash":"18534f8448f44f96912e582465b59b49"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fa.dart","hash":"d7ce5ee304988ad28fc4919a26c722df"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart","hash":"1030bb01fb2719117b60f1806c39aeaa"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_gu.dart","hash":"969c9b7055e9e76526f8818572230021"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations.dart","hash":"9231605cc77608c6d0f450924e5e6489"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart","hash":"2ca8be7e1ca33d343e4592259beaa9ce"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_he.arb","hash":"2b18231e6b963c0debe48ba59b44f7bd"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb","hash":"4a5d7a9141bf629facd0e7a8f43eabc1"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb","hash":"4b3c05b1759ce7dd457c340e207cbcc3"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb","hash":"3efca77cf8ac8d7533002055969db806"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb","hash":"5f197f457e206aee5bfcf073af2f4f32"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart","hash":"63488baac0de5001f20bfd6f34d28b1a"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart","hash":"03148830ecd9c8e40799e8e06d582224"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart","hash":"19b4ecb469f4236359ddc743d105130e"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fi.dart","hash":"ad3a5b9cd6e423c1b84c6d4a4cd5c2fa"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_CN.arb","hash":"c34e7122476d72931cab5f4ca6997886"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_de.dart","hash":"c2e1b43cc34e0f62ee96abf76a809580"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb","hash":"2ebf7d21d3c24e81374e2292b0f90fa5"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ka.dart","hash":"7dcdcc4fbdecf888ae33e65a72601212"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb","hash":"0349a95439507fd0ba360e296879dfa5"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart","hash":"4160d2429d4e1029cd3148729741791b"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart","hash":"d17419d0fb18199b4251031846c704d3"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart","hash":"5b1ea8f21d95657409d4abe973f4838f"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb","hash":"67a52094e4e350e731a3bd433edf2716"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb","hash":"d6f114efef8606608fdaa1c188b4398d"}]} \ No newline at end of file diff --git a/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/gen_l10n_inputs_and_outputs.json b/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/gen_l10n_inputs_and_outputs.json new file mode 100644 index 0000000000..1ec8a0af9b --- /dev/null +++ b/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/gen_l10n_inputs_and_outputs.json @@ -0,0 +1 @@ +{"inputs":["/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_be.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ca.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_de.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_et.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fa.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_gu.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_he.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hu.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_it.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ka.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_km.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lv.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ml.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ro.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ti.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_uk.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_CN.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb"],"outputs":["/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_be.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ca.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_da.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_de.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_el.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_en.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_es.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_et.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fa.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fi.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_gu.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_he.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_hi.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_hu.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_id.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_it.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ka.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_km.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_lv.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ml.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ro.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sl.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ti.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_uk.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations.dart"]} \ No newline at end of file diff --git a/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/gen_localizations.d b/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/gen_localizations.d new file mode 100644 index 0000000000..cf69ed661e --- /dev/null +++ b/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/gen_localizations.d @@ -0,0 +1 @@ + /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_be.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ca.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_da.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_de.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_el.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_en.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_es.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_et.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fa.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fi.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_gu.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_he.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_hi.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_hu.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_id.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_it.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ka.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_km.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_lv.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ml.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ro.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sl.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ti.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_uk.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations.dart: /Users/amanraj/development/ente/mobile/packages/strings/l10n.yaml /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_be.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ca.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_de.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_et.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fa.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fi.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_gu.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_he.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hi.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hu.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_it.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ka.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_km.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lv.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ml.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ro.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sl.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ti.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_uk.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_CN.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb \ No newline at end of file diff --git a/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/gen_localizations.stamp b/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/gen_localizations.stamp new file mode 100644 index 0000000000..04a589a372 --- /dev/null +++ b/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/gen_localizations.stamp @@ -0,0 +1 @@ +{"inputs":["/Users/amanraj/development/flutter/packages/flutter_tools/lib/src/build_system/targets/localizations.dart","/Users/amanraj/development/ente/mobile/packages/strings/l10n.yaml","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_be.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ca.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_de.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_et.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fa.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_gu.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_he.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hu.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_it.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ka.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_km.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lv.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ml.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ro.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ti.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_uk.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_CN.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb"],"outputs":["/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_be.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ca.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_da.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_de.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_el.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_en.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_es.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_et.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fa.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fi.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_gu.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_he.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_hi.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_hu.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_id.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_it.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ka.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_km.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_lv.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ml.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ro.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sl.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ti.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_uk.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations.dart"]} \ No newline at end of file diff --git a/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/outputs.json b/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/outputs.json new file mode 100644 index 0000000000..4b4e470109 --- /dev/null +++ b/mobile/packages/strings/build/2edd7a931624573eff7b4280d22a7f4e/outputs.json @@ -0,0 +1 @@ +["/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_be.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ca.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_da.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_de.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_el.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_en.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_es.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_et.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fa.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fi.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_gu.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_he.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_hi.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_hu.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_id.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_it.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ka.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_km.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_lv.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ml.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ro.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sl.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_ti.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_uk.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/strings_localizations.dart"] \ No newline at end of file diff --git a/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/.filecache b/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/.filecache new file mode 100644 index 0000000000..07fb61f785 --- /dev/null +++ b/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/.filecache @@ -0,0 +1 @@ +{"version":2,"files":[{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_zh.dart","hash":"7f7cc8995f142b71e60d866712f5cc79"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ka.arb","hash":"9cd3b583ef2a0ba24dce579b0e551f9c"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hi.arb","hash":"9ed088070b8a4e1abc6ca573f571d461"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ka.dart","hash":"3efbe57fcc84fa800b418582502f4d8b"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_uk.dart","hash":"9616766d449b01fa1d5bbcec48678b8c"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_pl.dart","hash":"e706eba44092f662d3e682b6002c6b6e"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_tr.dart","hash":"a94045bf44762c3e6119b5aeb71765fe"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sk.dart","hash":"4bb0e6197457be6fac2ba4b61fcc6634"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_km.arb","hash":"23fb911d60d6124067af7c81c92d17d0"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_id.dart","hash":"ec966ec5ec5ca5cd0db226b4afbb5002"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ja.dart","hash":"b1089f8a4306c1483aa6c4fa71a94e09"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_hi.dart","hash":"995674e33c2b7ccf4548eb486fbebcfa"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_be.arb","hash":"891655dc1aace6917ee027b4703e5038"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sl.dart","hash":"e393b9257b22b332702c09e6bc9c0cf4"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb","hash":"682b375aaded1c742fb9b7f2048ecef3"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ar.dart","hash":"149edbe6638853ef73513e2d10808837"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb","hash":"2f63b5f8cc2603056e8afae563d5ebeb"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_uk.arb","hash":"18734cbdc4ca08381dbf05edbb04e764"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_it.arb","hash":"e1330022bdc52a31cdec5ed56ec3509e"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_de.arb","hash":"d36cae88dcd2083fd934b7973a71bc6c"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb","hash":"640c06b0f22964611cf9b6b47939e8aa"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_he.dart","hash":"8869673f63a885119d38639078278205"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb","hash":"032ae47fe9c19834e14411871a4f6137"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb","hash":"8891455a6ec548c14b849b66380846a6"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ro.arb","hash":"7fbb2929c42db782e4dfe30c7d8a6375"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sv.dart","hash":"8df8d70c68d748a6a340201bdd1e3d28"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_et.dart","hash":"fc25800930f961de8615f0443e55111d"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_en.dart","hash":"2ed9eae7543bd1af982f9e44b2a9a8ac"},{"path":"/Users/amanraj/development/flutter/packages/flutter_tools/lib/src/build_system/targets/localizations.dart","hash":"d9155a79b0ac6660fef528508997f61d"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb","hash":"dc7e80ad8cea661ea613b0f5b36c9662"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sl.arb","hash":"e45f59e1bdbe6f47b870919182a6ea12"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hu.arb","hash":"ab0f09b5bd538dd9541f42baec4be64a"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_da.dart","hash":"c1d662b7aa3d509c0654aaf6745dee32"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb","hash":"9e5f514aa7b96c42dd74b6df60c15deb"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ko.dart","hash":"328e781dfb8415bcee4b5593c2f062c4"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb","hash":"95433370dc0f99c299e3b668e291e97b"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_et.arb","hash":"9d958d6ee4550c5346d8202428663869"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_lt.dart","hash":"655745a258c77086c7c270cbe1b7f73c"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ca.dart","hash":"b2b1dee28e0e6bcaf937f7674aa44bab"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_gu.dart","hash":"74520d69bf0ada9fad06fcc721a433cc"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb","hash":"ca78debc0d042bd2883ae9371dd74f75"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_es.dart","hash":"9b20c833aca1505b3fb2e35cec22874d"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/l10n.yaml","hash":"7f1fe83b9f37bd55ea049a6a2e5d1abe"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ti.arb","hash":"1946b5c8fbefe5af85a74af8995a29bd"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_be.dart","hash":"78a73dadfd1cfea583ff650180498367"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_gu.arb","hash":"93ee531a43623d62697085e9a5d9ccbe"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ml.arb","hash":"92a50c05f4e3ae0f369b72479a732c9f"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb","hash":"de6e7fcb10b5297abfebea313aaaf9d1"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations.dart","hash":"2786bede1a68cdbdbdb9b7fe99750d12"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb","hash":"d1c0176b94b78c6d17ce12eb0085e6e6"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb","hash":"8bf38e1bde975e58ccf64b9ba9d6b626"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_el.dart","hash":"788cb4e18371523c3fd7c2e7f892fa33"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lv.arb","hash":"a3c8ba63da0a61f93e23029d0badc25e"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fi.dart","hash":"3140c370adc7afcca2a69867ad9eb25f"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ml.dart","hash":"9b8007a4960c931d5889d1a6ac8ffd1c"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_it.dart","hash":"f81c45df665770564270999d90347c18"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ca.arb","hash":"993f8367aff09ed68fee0048e74b0564"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_bg.dart","hash":"d5efbe5faa1ce67d32a37a2c3991213f"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fa.dart","hash":"92dce41248d0c1af2abdfd1eba8918b7"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_nl.dart","hash":"ed130f58c9c441a64edda2923a91fd74"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb","hash":"a9f5860ba6b6218b0fe1254c0effed45"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb","hash":"71030f741e1952b92b63200a2170f120"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_km.dart","hash":"aa68d9181e72b527dde2b629cd40260b"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fa.arb","hash":"e55f440d7bdd2c3cd692c0ea457065ff"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fi.arb","hash":"fa18867ceb7220ba33ca4cfa957bd379"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_vi.dart","hash":"11f04682cabeb10efac5c389b459511b"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_lv.dart","hash":"24d83f73d369c974b5f696588fa19dbc"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sr.dart","hash":"9d5a35e934eed834f49f6d624201a9c0"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb","hash":"18534f8448f44f96912e582465b59b49"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_de.dart","hash":"5592fa613b1359b7a9e934dd3fa11818"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fr.dart","hash":"51b2114781d4deea0e8581cdad739f4c"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ro.dart","hash":"eeae7228eec576809a8422db24b68e56"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_he.arb","hash":"2b18231e6b963c0debe48ba59b44f7bd"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb","hash":"4a5d7a9141bf629facd0e7a8f43eabc1"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb","hash":"4b3c05b1759ce7dd457c340e207cbcc3"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ti.dart","hash":"d74b061d22f07198eab740961c9faea5"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb","hash":"3efca77cf8ac8d7533002055969db806"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb","hash":"5f197f457e206aee5bfcf073af2f4f32"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_CN.arb","hash":"c34e7122476d72931cab5f4ca6997886"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb","hash":"2ebf7d21d3c24e81374e2292b0f90fa5"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_hu.dart","hash":"1bdfc1fca4ab0337df08c9927035f5fd"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ru.dart","hash":"8fd88edab7eb3a41ece8c0c07fd3d557"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb","hash":"0349a95439507fd0ba360e296879dfa5"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb","hash":"67a52094e4e350e731a3bd433edf2716"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb","hash":"d6f114efef8606608fdaa1c188b4398d"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_pt.dart","hash":"72b194e8ad05bdf5f535087a85bb40ee"},{"path":"/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_cs.dart","hash":"e588ad1f68ce5ff0a0591a307398104e"}]} \ No newline at end of file diff --git a/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/gen_l10n_inputs_and_outputs.json b/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/gen_l10n_inputs_and_outputs.json new file mode 100644 index 0000000000..5ce7d8cba6 --- /dev/null +++ b/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/gen_l10n_inputs_and_outputs.json @@ -0,0 +1 @@ +{"inputs":["/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_be.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ca.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_de.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_et.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fa.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_gu.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_he.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hu.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_it.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ka.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_km.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lv.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ml.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ro.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ti.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_uk.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_CN.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb"],"outputs":["/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ar.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_be.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_bg.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ca.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_cs.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_da.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_de.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_el.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_en.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_es.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_et.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fa.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fi.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fr.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_gu.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_he.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_hi.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_hu.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_id.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_it.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ja.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ka.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_km.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ko.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_lt.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_lv.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ml.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_nl.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_pl.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_pt.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ro.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ru.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sk.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sl.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sr.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sv.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ti.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_tr.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_uk.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_vi.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_zh.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations.dart"]} \ No newline at end of file diff --git a/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/gen_localizations.d b/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/gen_localizations.d new file mode 100644 index 0000000000..7d24383260 --- /dev/null +++ b/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/gen_localizations.d @@ -0,0 +1 @@ + /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ar.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_be.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_bg.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ca.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_cs.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_da.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_de.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_el.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_en.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_es.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_et.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fa.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fi.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fr.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_gu.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_he.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_hi.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_hu.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_id.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_it.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ja.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ka.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_km.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ko.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_lt.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_lv.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ml.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_nl.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_pl.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_pt.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ro.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ru.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sk.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sl.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sr.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sv.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ti.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_tr.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_uk.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_vi.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_zh.dart /Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations.dart: /Users/amanraj/development/ente/mobile/packages/strings/l10n.yaml /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_be.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ca.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_de.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_et.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fa.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fi.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_gu.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_he.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hi.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hu.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_it.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ka.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_km.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lv.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ml.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ro.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sl.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ti.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_uk.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_CN.arb /Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb \ No newline at end of file diff --git a/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/gen_localizations.stamp b/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/gen_localizations.stamp new file mode 100644 index 0000000000..5279ec26f9 --- /dev/null +++ b/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/gen_localizations.stamp @@ -0,0 +1 @@ +{"inputs":["/Users/amanraj/development/flutter/packages/flutter_tools/lib/src/build_system/targets/localizations.dart","/Users/amanraj/development/ente/mobile/packages/strings/l10n.yaml","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ar.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_be.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_bg.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ca.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_cs.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_da.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_de.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_el.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_en.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_es.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_et.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fa.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_fr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_gu.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_he.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_hu.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_id.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_it.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ja.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ka.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_km.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ko.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lt.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_lv.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ml.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_nl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_pt.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ro.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ru.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sk.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sl.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_sv.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_ti.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_tr.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_uk.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_vi.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_CN.arb","/Users/amanraj/development/ente/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb"],"outputs":["/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ar.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_be.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_bg.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ca.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_cs.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_da.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_de.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_el.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_en.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_es.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_et.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fa.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fi.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_fr.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_gu.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_he.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_hi.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_hu.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_id.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_it.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ja.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ka.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_km.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ko.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_lt.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_lv.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ml.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_nl.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_pl.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_pt.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ro.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ru.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sk.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sl.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sr.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_sv.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_ti.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_tr.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_uk.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_vi.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations_zh.dart","/Users/amanraj/development/ente/mobile/packages/strings/.dart_tool/flutter_gen/gen_l10n/strings_localizations.dart"]} \ No newline at end of file diff --git a/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/outputs.json b/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/outputs.json new file mode 100644 index 0000000000..0637a088a0 --- /dev/null +++ b/mobile/packages/strings/build/3d559b893a35c16b02d8e7441d0112a4/outputs.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/mobile/packages/strings/l10n.yaml b/mobile/packages/strings/l10n.yaml new file mode 100644 index 0000000000..38319d1632 --- /dev/null +++ b/mobile/packages/strings/l10n.yaml @@ -0,0 +1,6 @@ +arb-dir: lib/l10n/arb +template-arb-file: strings_en.arb +output-localization-file: strings_localizations.dart +output-class: StringsLocalizations +output-dir: lib/l10n +nullable-getter: false diff --git a/mobile/packages/strings/lib/ente_strings.dart b/mobile/packages/strings/lib/ente_strings.dart new file mode 100644 index 0000000000..171d58bdb5 --- /dev/null +++ b/mobile/packages/strings/lib/ente_strings.dart @@ -0,0 +1,2 @@ +export 'extensions.dart'; +export 'l10n/strings_localizations.dart'; diff --git a/mobile/packages/strings/lib/extensions.dart b/mobile/packages/strings/lib/extensions.dart new file mode 100644 index 0000000000..e60e135535 --- /dev/null +++ b/mobile/packages/strings/lib/extensions.dart @@ -0,0 +1,11 @@ +import 'package:ente_strings/l10n/strings_localizations.dart'; +import 'package:flutter/widgets.dart'; + +// Re-export the localizations for convenience +export 'l10n/strings_localizations.dart'; + +/// Extension to easily access shared strings from any BuildContext +extension EnteStringsExtension on BuildContext { + /// Get the shared strings localizations for the current locale + StringsLocalizations get strings => StringsLocalizations.of(this); +} diff --git a/mobile/packages/strings/lib/l10n/arb/strings_ar.arb b/mobile/packages/strings/lib/l10n/arb/strings_ar.arb new file mode 100644 index 0000000000..916cf68982 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_ar.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "تعذر الاتصال بـEnte، فضلا تحقق من إعدادات الشبكة الخاصة بك وتواصل مع الدعم إذا استمر الخطأ.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "تعذر الإتصال بـEnte، فضلا أعد المحاولة لاحقا. إذا استمر الخطأ، فضلا تواصل مع الدعم.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "يبدو أنه حدث خطأ ما. الرجاء إعادة المحاولة لاحقا. إذا استمر الخطأ، يرجى الاتصال بفريق الدعم.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "خطأ", + "@error": { + "description": "Generic error title" + }, + "ok": "حسناً", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "الأسئلة الأكثر شيوعاً", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "الاتصال بالدعم", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "إرسال السجلات عبر البريد الإلكتروني", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "الرجاء إرسال السجلات إلى {toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "نسخ عنوان البريد الإلكتروني", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "تصدير السجلات", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "إلغاء", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "ألإبلاغ عن خلل تقني", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "متصل بـ{endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "حفظ", + "@save": { + "description": "Label for save button" + }, + "send": "إرسال", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "هل تريد حفظه إلى السعة التخزينية الخاصة بك (مجلد التنزيلات افتراضيا) أم إرساله إلى تطبيقات أخرى؟", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "هل تريد حفظه إلى السعة التخزينية الخاصة بك (مجلد التنزيلات افتراضيا)؟", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "أدخل عنوان بريدك الإلكتروني الجديد", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "البريد الإلكتروني", + "@email": { + "description": "Email field label" + }, + "verify": "التحقق", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "عنوان البريد الإلكتروني غير صالح", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "الرجاء إدخال بريد إلكتروني صالح.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "انتظر قليلاً...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "التحقق من كلمة المرور", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "كلمة المرور غير صحيحة", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "يرجى المحاولة مرة أخرى", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "أدخل كلمة المرور", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "أدخل كلمة المرور الخاصة بك", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "الجلسات النشطة", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "عذرًا", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "حدث خطأ ما، يرجى المحاولة مرة أخرى", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "سيؤدي هذا إلى تسجيل خروجك من هذا الجهاز!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "سيؤدي هذا إلى تسجيل خروجك من هذا الجهاز:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "إنهاء الجلسة؟", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "إنهاء", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "هذا الجهاز", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "إنشاء حساب", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "ضعيف", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "متوسط", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "قوي", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "إزالة الحساب", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "سوف نأسف لرؤيتك تذهب. هل تواجه بعض المشاكل؟", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "نعم، ارسل الملاحظات", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "لا، حذف الحساب", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "الرجاء المصادقة لبدء حذف الحساب", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "تأكيد حذف الحساب", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "هذا الحساب مرتبط بتطبيقات Ente أخرى، إذا كنت تستخدم أحدها.\n\nسنضع موعدا لحذف بياناتك المرفوعة عبر كل تطبيقات Ente، وسيتم حذف حسابك بصورة دائمة.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "حذف", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "إنشاء حساب جديد", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "كلمة المرور", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "تأكيد كلمة المرور", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "قوة كلمة المرور: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "كيف سمعت عن Ente؟ (اختياري)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "نحن لا نتتبع تثبيت التطبيق. سيكون من المفيد إذا أخبرتنا أين وجدتنا!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "أوافق على شروط الخدمة وسياسة الخصوصية", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "الشروط", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "سياسة الخصوصية", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "أنا أفهم أنه إذا فقدت كلمة المرور الخاصة بي، قد أفقد بياناتي لأن بياناتي هي مشفرة من الند للند.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "التشفير", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "تسجيل الدخول", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "مرحبًا مجددًا!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "بالنقر على تسجيل الدخول، أوافق على شروط الخدمة و سياسة الخصوصية", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "لا يوجد اتصال بالإنترنت", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "يرجى التحقق من اتصالك بالإنترنت ثم المحاولة من جديد.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "فشل في المصادقة ، يرجى المحاولة مرة أخرى في وقت لاحق", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "إعادة كتابة كلمة المرور", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "الجهاز الحالي ليس قويًا بما يكفي للتحقق من كلمة المرور الخاصة بك، لذا نحتاج إلى إعادة إنشائها مرة واحدة بطريقة تعمل مع جميع الأجهزة.\n\nالرجاء تسجيل الدخول باستخدام مفتاح الاسترداد وإعادة إنشاء كلمة المرور الخاصة بك (يمكنك استخدام نفس كلمة المرور مرة أخرى إذا كنت ترغب في ذلك).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "استخدم مفتاح الاسترداد", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "هل نسيت كلمة المرور", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "غير البريد الإلكتروني", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "تأكيد البريد الإلكتروني", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "لقد أرسلنا رسالة إلى {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "لإعادة تعيين كلمة المرور الخاصة بك، يرجى التحقق من بريدك الإلكتروني أولاً.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "الرجاء التحقق من صندوق الوارد (والرسائل غير المرغوب فيها) لإكمال التحقق", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "انقر لإدخال الرمز", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "إرسال بريد إلكتروني", + "resendEmail": "إعادة إرسال البريد الإلكتروني", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "التحقق ما زال جارٍ", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "انتهت صلاحية الجلسة", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "انتهت صلاحية جلستك. فضلا أعد تسجيل الدخول.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "التحقق من مفتاح المرور", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "بانتظار التحقق...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "حاول مرة أخرى", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "تحقق من الحالة", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "إسترجاع الحساب", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "تعيين كلمة المرور", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "تغيير كلمة المرور", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "إعادة تعيين كلمة المرور", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "مفاتيح التشفير", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "أدخل كلمة المرور التي يمكننا استخدامها لتشفير بياناتك", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "أدخل كلمة مرور جديدة يمكننا استخدامها لتشفير بياناتك", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "نحن لا نقوم بتخزين كلمة المرور هذه، لذا إذا نسيتها، لا يمكننا فك تشفير بياناتك", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "كيف يعمل", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "توليد مفاتيح التشفير...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "تم تغيير كلمة المرور بنجاح", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "تسجيل الخروج من الأجهزة الأخرى", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "إذا كنت تعتقد أن شخصا ما يعرف كلمة المرور الخاصة بك، يمكنك إجبار جميع الأجهزة الأخرى الستخدمة حاليا لحسابك على تسجيل الخروج.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "تسجيل الخروج من الأجهزة الأخرى", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "لا تقم بتسجيل الخروج", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "توليد مفاتيح التشفير...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "المتابعة", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "جهاز غير آمن", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "عذرًا، لم نتمكن من إنشاء مفاتيح آمنة على هذا الجهاز.\n\nيرجى التسجيل من جهاز مختلف.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "تم نسخ عبارة الاسترداد للحافظة", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "مفتاح الاسترداد", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "إذا نسيت كلمة المرور الخاصة بك، فالطريقة الوحيدة التي يمكنك بها استرداد بياناتك هي بهذا المفتاح.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "نحن لا نخزن هذا المفتاح، يرجى حفظ مفتاح الـ 24 كلمة هذا في مكان آمن.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "قم بهذا لاحقاً", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "حفظ المفتاح", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "حُفِظ مفتاح الاستعادة في مجلد التنزيلات!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "لا يوجد مفتاح استرجاع؟", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "المصادقة الثنائية", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "أدخل الرمز المكون من 6 أرقام من\nتطبيق المصادقة", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "جهاز مفقود ؟", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "أدخل رمز الاسترداد", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "استرداد", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "جاري تسجيل الخروج...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "فورًا", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "قُفْل التطبيق", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "قفل تلقائي", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "لا يوجد قفل نظام", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "لتفعيل قُفْل الجهاز، اضبط رمز مرور أو قُفْل الشاشة من الإعدادات", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "اختر نوع قُفْل الشاشة: افتراضي أو مخصص.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "قفل الجهاز", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "قفل رقم التعريف الشخصي", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "الوقت الذي بعده ينقفل التطبيق بعدما يوضع في الخلفية", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "أخفِ المحتوى", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "يخفي محتوى التطبيق في مبدل التطبيقات ويمنع لقطات الشاشة", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "يخفي محتوى التطبيق في مبدل التطبيقات", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "محاولات خاطئة أكثر من المسموح", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "المس لإلغاء القفل", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "هل أنت متأكد من أنك تريد تسجيل الخروج؟", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "نعم، تسجيل الخروج", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "الرجاء المصادقة لعرض مفتاح الاسترداد الخاص بك", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "التالي", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "عين كلمة مرور جديدة", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "أدخل رقم التعريف الشخصي", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "عين رقم تعريف شخصي جديد", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "تأكيد", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "أعد إدخال كلمة المرور", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "أعد إدخال رقم التعريف الشخصي", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "التحقق من الهوية", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "لم يتم التعرف عليه. حاول مرة أخرى.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "تم بنجاح", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "إلغاء", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "المصادقة مطلوبة", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "البيومترية مطلوبة", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "بيانات اعتماد الجهاز مطلوبة", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "بيانات اعتماد الجهاز مطلوبة", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "الانتقال إلى الإعدادات", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "لم يتم إعداد المصادقة الحيوية على جهازك. انتقل إلى 'الإعدادات > الأمن' لإضافة المصادقة البيومترية.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "المصادقة البيومترية معطلة. الرجاء قفل الشاشة وفتح القفل لتفعيلها.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "حسناً", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "البريد الإلكتروني مُسَجَّل من قبل.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "البريد الإلكتروني غير مُسَجَّل.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "هذا البريد مستخدم مسبقاً", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "تم تغيير البريد الإلكتروني إلى {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "فشلت المصادقة. الرجاء المحاولة مرة أخرى", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "تمت المصادقة بنجاح!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "انتهت صَلاحِيَة الجِلسة", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "مفتاح الاسترداد غير صحيح", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "مفتاح الاسترداد الذي أدخلته غير صحيح", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "تم تحديث المصادقة الثنائية بنجاح", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "انتهت صلاحية رمز التحقق", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "رمز غير صحيح", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "عذراً، الرمز الذي أدخلته غير صحيح", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "اعدادات المطور", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "نقطة طرف الخادم", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "نقطة طرف غير صالحة", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "عذرا، نقطة الطرف التي أدخلتها غير صالحة. فضلا أدخل نقطة طرف صالحة وأعد المحاولة.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "حُدِّثَت نقطة الطرف بنجاح", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_be.arb b/mobile/packages/strings/lib/l10n/arb/strings_be.arb new file mode 100644 index 0000000000..b8b9d319d9 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_be.arb @@ -0,0 +1,331 @@ +{ + "error": "Памылка", + "@error": { + "description": "Generic error title" + }, + "ok": "OK", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Частыя пытанні", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Звярнуцца ў службу падтрымкі", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Адправіць журналы", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "exportLogs": "Экспартаваць журналы", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Скасаваць", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Паведаміць аб памылцы", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "save": "Захаваць", + "@save": { + "description": "Label for save button" + }, + "send": "Адправіць", + "@send": { + "description": "Label for send button" + }, + "email": "Электронная пошта", + "@email": { + "description": "Email field label" + }, + "verify": "Праверыць", + "@verify": { + "description": "Verify button label" + }, + "pleaseWait": "Пачакайце...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Праверыць пароль", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Няправільны пароль", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Калі ласка, паспрабуйце яшчэ раз", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Увядзіце пароль", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Увядзіце ваш пароль", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Актыўныя сеансы", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Вой", + "@oops": { + "description": "Oops error title" + }, + "terminate": "Перарваць", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Гэта прылада", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Стварыць уліковы запіс", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Ненадзейны", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Умераная", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Надзейны", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Выдаліць уліковы запіс", + "@deleteAccount": { + "description": "Delete account button label" + }, + "noDeleteAccountAction": "Не, выдаліць уліковы запіс", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "delete": "Выдаліць", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Стварыць новы ўліковы запіс", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Пароль", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Пацвердзіць пароль", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "termsOfServicesTitle": "Умовы", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Палітыка прыватнасці", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "encryption": "Шыфраванне", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Увайсці", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "З вяртаннем!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "useRecoveryKey": "Выкарыстоўваць ключ аднаўлення", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Забылі пароль", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Змяніць адрас электроннай пошты", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Праверыць электронную пошту", + "@verifyEmail": { + "description": "Verify email title" + }, + "sendEmail": "Адправіць ліст", + "resendEmail": "Адправіць ліст яшчэ раз", + "@resendEmail": { + "description": "Resend email button label" + }, + "loginSessionExpired": "Сеанс завяршыўся", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "tryAgain": "Паспрабуйце яшчэ раз", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Праверыць статус", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Увайсці з TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Аднавіць уліковы запіс", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Задаць пароль", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Змяніць пароль", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Скінуць пароль", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Ключы шыфравання", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "howItWorks": "Як гэта працуе", + "@howItWorks": { + "description": "How it works button label" + }, + "doNotSignOut": "Не выходзіць", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Генерацыя ключоў шыфравання...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Працягнуць", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Небяспечная прылада", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "recoveryKey": "Ключ аднаўлення", + "@recoveryKey": { + "description": "Recovery key label" + }, + "doThisLater": "Зрабіць гэта пазней", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Захаваць ключ", + "@saveKey": { + "description": "Save key button label" + }, + "lostDeviceTitle": "Згубілі прыладу?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "recover": "Аднавіць", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Выхад...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Адразу", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Блакіроўка праграмы", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Аўтаблакіроўка", + "@autoLock": { + "description": "Auto lock setting title" + }, + "deviceLock": "Блакіроўка прылады", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Блакіроўка PIN'ам", + "@pinLock": { + "description": "PIN lock option title" + }, + "hideContent": "Схаваць змест", + "@hideContent": { + "description": "Hide content setting title" + }, + "tapToUnlock": "Націсніце для разблакіроўкі", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "yesLogout": "Так, выйсці", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "next": "Далей", + "@next": { + "description": "Next button label" + }, + "enterPin": "Увядзіце PIN-код", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Задаць новы PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Пацвердзіць", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPin": "Увядзіце PIN-код яшчэ раз", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Праверыць ідэнтыфікацыю", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Паспяхова", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Скасаваць", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "goToSettings": "Перайсці ў налады", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "sessionExpired": "Сеанс завяршыўся", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectCode": "Няправільны код", + "@incorrectCode": { + "description": "Error message when code is incorrect" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_bg.arb b/mobile/packages/strings/lib/l10n/arb/strings_bg.arb new file mode 100644 index 0000000000..725cc4f991 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_bg.arb @@ -0,0 +1,720 @@ +{ + "networkHostLookUpErr": "Не може да се свърже с Ente, моля, проверете мрежовите си настройки и се свържете с поддръжката, ако проблемът продължава.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Не може да се свърже с Ente, моля, опитайте отново след известно време. Ако проблемът продължава, моля, свържете се с поддръжката.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Изглежда нещо се обърка. Моля, опитайте отново след известно време. Ако грешката продължава, моля, свържете се с нашия екип за поддръжка.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Грешка", + "@error": { + "description": "Generic error title" + }, + "ok": "Ок", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "ЧЗВ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Свържете се с поддръжката", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Изпратете Вашата история на действията на имейл", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Моля, изпратете историята на действията на \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Копиране на имейл адрес", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Експорт на файловете с историята", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Отказ", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Докладване на проблем", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Свързан към {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Запазване", + "@save": { + "description": "Label for save button" + }, + "send": "Изпращане", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Искате ли да запазите това в хранилището си (папка за Изтегляния по подразбиране) или да го изпратите на други приложения?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Искате ли да запазите това в хранилището си (папка за Изтегляния по подразбиране)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "Имейл", + "@email": { + "description": "Email field label" + }, + "verify": "Потвърждаване", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Невалиден имейл адрес", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Моля, въведете валиден имейл адрес.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Моля изчакайте...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Потвърдете паролата", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Грешна парола", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Опитайте отново", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Въведете парола", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Въведете паролата си", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Активни сесии", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Опа", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Нещо се обърка, моля опитайте отново", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Това ще Ви изкара от профила на това устройство!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Това ще Ви изкара от профила на следното устройство:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Прекратяване на сесията?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Прекратяване", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Това устройство", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Създаване на акаунт", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Слаба", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Умерена", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Силна", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Изтриване на акаунта", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Ще съжаляваме да си тръгнете. Изправени ли сте пред някакъв проблем?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Да, изпращане на обратна връзка", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Не, изтриване на акаунта", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Моля, удостоверете се, за да инициирате изтриването на акаунта", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Потвърдете изтриването на акаунта", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Този акаунт е свързан с други приложения на Ente, ако използвате такива.\n\nВашите качени данни във всички приложения на Ente ще бъдат планирани за изтриване и акаунтът Ви ще бъде изтрит за постоянно.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Изтриване", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Създаване на нов акаунт", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Парола", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Потвърждаване на паролата", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Сила на паролата: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Как научихте за Ente? (по избор)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Ние не проследяваме инсталиранията на приложения. Ще помогне, ако ни кажете къде ни намерихте!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Съгласявам се с условията за ползване и политиката за поверителност", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Условия", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Политика за поверителност", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Разбирам, че ако загубя паролата си, може да загубя данните си, тъй като данните ми са шифровани от край до край.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Шифроване", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Вход", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Добре дошли отново!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "С натискането на вход, се съгласявам с условията за ползване и политиката за поверителност", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Няма връзка с интернет", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Моля, проверете интернет връзката си и опитайте отново.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Неуспешно проверка, моля опитайте отново", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Създайте отново парола", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Текущото устройство не е достатъчно мощно, за да потвърди паролата Ви, но можем да я регенерираме по начин, който работи с всички устройства.\n\nМоля, влезте с Вашия ключ за възстановяване и генерирайте отново паролата си (можете да използвате същата отново, ако желаете).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Използвайте ключ за възстановяване", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Забравена парола", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Промяна на имейл", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Потвърдете имейла", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Изпратихме имейл до {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "За да нулирате паролата си, моля, първо потвърдете своя имейл.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Моля, проверете входящата си поща (и спама), за да завършите проверката", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Докоснете, за да въведете код", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Изпратете имейл", + "resendEmail": "Повторно изпращане на имейл", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Потвърждението все още се изчаква", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Сесията изтече", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Вашата сесия изтече. Моля влезте отново.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Удостоверяване с ключ за парола", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Изчаква се потвърждение...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Опитайте отново", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Проверка на състоянието", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Влизане с еднократен код", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Възстановяване на акаунт", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Задаване на парола", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Промяна на паролата", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Нулиране на паролата", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Ключове за шифроване", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Въведете парола, която да използваме за шифроване на Вашите данни", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Въведете нова парола, която да използваме за шифроване на Вашите данни", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Ние не съхраняваме тази парола, така че ако я забравите, не можем да дешифрираме Вашите данни", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Как работи", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Генериране на ключове за шифроване...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Паролата е променена успешно", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Излизане от други устройства", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Ако смятате, че някой може да знае паролата Ви, можете да принудите всички други устройства, използващи Вашия акаунт, да излязат.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Излизане от други устройства", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Не излизайте", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Генерират се ключове за шифроване...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Продължете", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Несигурно устройство", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "За съжаление не можахме да генерираме защитени ключове на това устройство.\n\nМоля, регистрирайте се от друго устройство.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Ключът за възстановяване е копиран в буферната памет", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Ключ за възстановяване", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Ако забравите паролата си, единственият начин да възстановите данните си е с този ключ.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Ние не съхраняваме този ключ, моля, запазете този ключ от 24 думи на сигурно място.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Направете това по-късно", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Запазване на ключа", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Ключът за възстановяване е запазен в папка за Изтегляния!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Няма ключ за възстановяване?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Двуфакторно удостоверяване", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Въведете 6-цифрения код от\nВашето приложение за удостоверяване", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Загубено устройство?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Въведете Вашия ключ за възстановяване", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Възстановяване", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Излизане от профила...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Незабавно", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Заключване на приложението", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Автоматично заключване", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Не е намерено заключване на системата", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "За да активирате заключването на устройството, моля, задайте парола за устройството или заключване на екрана в системните настройки.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Изберете между заключен екран по подразбиране на Вашето устройство и персонализиран заключен екран с ПИН код или парола.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Заключване на устройството", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Заключване с ПИН код", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Време, след което приложението се заключва, след като е поставено на заден план", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Скриване на съдържанието", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Скрива съдържанието на приложението в превключвателя на приложения и деактивира екранните снимки", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Скрива съдържанието на приложението в превключвателя на приложения", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Твърде много неуспешни опити", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Докоснете, за да отключите", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Наистина ли искате да излезете от профила си?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Да, излез", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Моля, удостоверете се, за да видите Вашите кодове", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Следващ", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Задаване на нова парола", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Въведете ПИН код", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Задаване на нов ПИН код", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Потвърждаване", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Въведете отново паролата", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Въведете отново ПИН кода", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Потвърждаване на самоличността", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Не е разпознат. Опитайте отново.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Успешно", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Отказ", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Необходимо е удостоверяване", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Изискват се биометрични данни", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Изискват се идентификационни данни за устройството", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Изискват се идентификационни данни за устройството", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Отваряне на настройките", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Биометричното удостоверяване не е настроено на Вашето устройство. Отидете на „Настройки > Сигурност“, за да добавите биометрично удостоверяване.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Биометричното удостоверяване е деактивирано. Моля, заключете и отключете екрана си, за да го активирате.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "ОК", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "Имейлът вече е регистриран.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "Имейлът не е регистриран.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Този имейл вече се използва", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Имейлът е променен на {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Неуспешно удостоверяване, моля опитайте отново", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Успешно удостоверяване!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Сесията е изтекла", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Неправилен ключ за възстановяване", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Въведеният от Вас ключ за възстановяване е неправилен", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Двуфакторното удостоверяване бе успешно нулирано", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Вашият код за потвърждение е изтекъл", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Неправилен код", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "За съжаление кодът, който сте въвели, е неправилен", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Настройки за програмисти", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Крайна точка на сървъра", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Невалидна крайна точка", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "За съжаление въведената от Вас крайна точка е невалидна. Моля, въведете валидна крайна точка и опитайте отново.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Крайната точка е актуализирана успешно", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_ca.arb b/mobile/packages/strings/lib/l10n/arb/strings_ca.arb new file mode 100644 index 0000000000..d277c5ae2c --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_ca.arb @@ -0,0 +1,720 @@ +{ + "networkHostLookUpErr": "No s'ha pogut connectar a Ente, si us plau, comprova la configuració de la xarxa i contacta amb suport si l'error persisteix.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "No s'ha pogut connectar a Ente, si us plau, torna-ho a intentar després d'un temps. Si l'error persisteix, contacta amb suport.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Sembla que alguna cosa ha anat malament. Si us plau, torna-ho a intentar després d'un temps. Si l'error persisteix, contacta amb el nostre equip de suport.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Error", + "@error": { + "description": "Generic error title" + }, + "ok": "D'acord", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "FAQ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Contacta amb suport", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Envia els teus registres per correu", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Si us plau, envia els registres a \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Copia l'adreça de correu", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Exporta els registres", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Cancel·la", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Informa d'un error", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Connectat a {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Guarda", + "@save": { + "description": "Label for save button" + }, + "send": "Envia", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Vols guardar-ho al teu emmagatzematge (per defecte, a la carpeta Descàrregues) o enviar-ho a altres aplicacions?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Vols guardar-ho al teu emmagatzematge (per defecte, a la carpeta Descàrregues)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "Correu electrònic", + "@email": { + "description": "Email field label" + }, + "verify": "Verifica", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Adreça de correu electrònic no vàlida", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Si us plau, introdueix una adreça de correu electrònic vàlida.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Si us plau, espera...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Verifica la contrasenya", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Contrasenya incorrecta", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Si us plau, intenta-ho de nou", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Introdueix la contrasenya", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Introdueix la teva contrasenya", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Sessions actives", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Ups", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "S'ha produït un error, si us plau, intenta-ho de nou", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Això tancarà la sessió en aquest dispositiu!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Això tancarà la sessió en el següent dispositiu:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Finalitzar sessió?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Finalitzar", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Aquest dispositiu", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Crea un compte", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Feble", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Moderada", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Forta", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Elimina el compte", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Ens sabrà greu veure't marxar. Tens algun problema?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Sí, envia comentaris", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "No, elimina el compte", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Si us plau, autentica't per iniciar l'eliminació del compte", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Confirma la supressió del compte", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Aquest compte està vinculat a altres apps d'Ente, si en fas ús.\n\nLes dades pujades, a través de totes les apps d'Ente, es programaran per a la supressió, i el teu compte s'eliminarà permanentment.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Elimina", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Crea un nou compte", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Contrasenya", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Confirma la contrasenya", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Força de la contrasenya: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Com vas conèixer Ente? (opcional)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "No fem seguiment de les instal·lacions de l'app. Ens ajudaria saber on ens has trobat!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Estic d'acord amb els termes del servei i la política de privacitat", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Termes", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Política de privacitat", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Entenc que si perdo la meva contrasenya, puc perdre les meves dades ja que les meves dades estan xifrades d'extrem a extrem.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Xifratge", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Inicia sessió", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Benvingut de nou!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "En fer clic a iniciar sessió, estic d'acord amb els termes del servei i la política de privacitat", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Sense connexió a Internet", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Comprova la connexió a Internet i torna-ho a intentar.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "La verificació ha fallat, intenta-ho de nou", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Recrea la contrasenya", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "El dispositiu actual no és prou potent per verificar la teva contrasenya, però podem regenerar-la d'una manera que funcioni amb tots els dispositius.\n\nSi us plau, inicia sessió utilitzant la teva clau de recuperació i regenera la teva contrasenya (pots tornar a utilitzar la mateixa si ho desitges).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Usa la clau de recuperació", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Has oblidat la contrasenya", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Canvia el correu electrònic", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Verifica el correu electrònic", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Hem enviat un correu a {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Per restablir la teva contrasenya, si us plau verifica primer el teu correu electrònic.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Comprova la teva safata d'entrada (i el correu no desitjat) per completar la verificació", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Toca per introduir el codi", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Envia correu electrònic", + "resendEmail": "Reenviar correu electrònic", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "La verificació encara està pendent", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Sessió caducada", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "La teva sessió ha caducat. Torna a iniciar sessió.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Verificació per passkey", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Esperant verificació...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Intenta-ho de nou", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Comprova l'estat", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Inici de sessió amb TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Recupera el compte", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Configura la contrasenya", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Canvia la contrasenya", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Restableix la contrasenya", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Claus de xifratge", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Introdueix una contrasenya que puguem utilitzar per xifrar les teves dades", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Introdueix una nova contrasenya que puguem utilitzar per xifrar les teves dades", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "No guardem aquesta contrasenya, per tant, si l'oblides, no podrem desxifrar les teves dades", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Com funciona", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Generant claus de xifratge...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "La contrasenya s'ha canviat amb èxit", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Tanca sessió en altres dispositius", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Si creus que algú pot saber la teva contrasenya, pots forçar tots els altres dispositius que usen el teu compte a tancar sessió.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Tancar sessió en altres dispositius", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "No tancar sessió", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Generant claus d'encriptació...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Continua", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Dispositiu no segur", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Ho sentim, no hem pogut generar claus segures en aquest dispositiu.\n\nSi us plau, registra't des d'un altre dispositiu.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "La clau de recuperació s'ha copiat al porta-retalls", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Clau de recuperació", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Si oblides la teva contrasenya, l'única manera de recuperar les teves dades és amb aquesta clau.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "No guardem aquesta clau, si us plau, guarda aquesta clau de 24 paraules en un lloc segur.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Fes-ho més tard", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Guarda la clau", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Clau de recuperació guardada a la carpeta Descàrregues!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "No tens clau de recuperació?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Autenticació de dos factors", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Introdueix el codi de 6 dígits de\nl'aplicació d'autenticació", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Dispositiu perdut?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Introdueix la teva clau de recuperació", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Recupera", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Tancant sessió...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Immediatament", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Bloqueig de l'aplicació", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Bloqueig automàtic", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "No s'ha trobat cap bloqueig del sistema", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Per habilitar el bloqueig de dispositiu, configura un codi o bloqueig de pantalla en la configuració del sistema.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Tria entre el bloqueig predeterminat del dispositiu o un bloqueig personalitzat amb PIN o contrasenya.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Bloqueig del dispositiu", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Bloqueig amb PIN", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Temps després del qual l'app es bloqueja quan es posa en segon pla", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Amaga el contingut", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Amaga el contingut d'aquesta app en el commutador d'apps del sistema i desactiva les captures de pantalla", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Amaga el contingut d'aquesta app en el commutador d'apps del sistema", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Massa intents incorrectes", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Toca per desbloquejar", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Segur que vols tancar la sessió?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Sí, tanca la sessió", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Si us plau, autentica't per veure els teus secrets", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Següent", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Estableix una nova contrasenya", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Introdueix el PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Estableix un nou PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Confirma", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Torna a introduir la contrasenya", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Torna a introduir el PIN", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Verifica la identitat", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "No reconegut. Torna-ho a provar.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Correcte", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Cancel·la", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Es requereix autenticació", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biometria necessària", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Credencials del dispositiu requerides", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Es requereixen credencials del dispositiu", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Ves a configuració", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "L'autenticació biomètrica no està configurada al teu dispositiu. Ves a 'Configuració > Seguretat' per afegir autenticació biomètrica.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "L'autenticació biomètrica està desactivada. Bloqueja i desbloqueja la pantalla per activar-la.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "D'acord", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "El correu electrònic ja està registrat.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "El correu electrònic no està registrat.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Aquest correu electrònic ja està en ús", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Correu electrònic canviat a {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Autenticació fallida, intenta-ho de nou", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Autenticació amb èxit!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "La sessió ha caducat", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Clau de recuperació incorrecta", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "La clau de recuperació que has introduït és incorrecta", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Autenticació de dos factors restablerta amb èxit", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "El teu codi de verificació ha expirat", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Codi incorrecte", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Ho sentim, el codi que has introduït és incorrecte", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Configuració de desenvolupador", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Endpoint del servidor", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Endpoint no vàlid", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Ho sentim, l'endpoint que has introduït no és vàlid. Introdueix un endpoint vàlid i torna-ho a intentar.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Extrem actualitzat correctament", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_cs.arb b/mobile/packages/strings/lib/l10n/arb/strings_cs.arb new file mode 100644 index 0000000000..398127b655 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_cs.arb @@ -0,0 +1,716 @@ +{ + "networkHostLookUpErr": "Nelze se připojit k Ente, zkontrolujte, prosím, nastavení své sítě a kontaktujte podporu, pokud chyba přetrvává", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Nepodařilo se připojit k Ente, zkuste to po nějaké době znovu. Pokud chyba přetrvává, kontaktujte, prosím, podporu.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Vypadá to, že se něco pokazilo. Zkuste to prosím znovu po nějaké době. Pokud chyba přetrvává, kontaktujte prosím naši podporu.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Chyba", + "@error": { + "description": "Generic error title" + }, + "ok": "Ok", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Často kladené dotazy (FAQ)", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Kontaktovat podporu", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Zašlete své logy e-mailem", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Pošlete prosím logy na \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Kopírovat e-mailovou adresu", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Exportovat logy", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Zrušit", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Nahlásit chybu", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Připojeno k {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Uložit", + "@save": { + "description": "Label for save button" + }, + "send": "Odeslat", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Chcete toto uložit do paměti zařízení (ve výchozím nastavení do složky Stažené soubory), nebo odeslat do jiných aplikací?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Chcete toto uložit do paměti zařízení (ve výchozím nastavení do složky Stažené soubory)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "E-mail", + "@email": { + "description": "Email field label" + }, + "verify": "Ověřit", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Neplatná e-mailová adresa", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Prosím, zadejte platnou e-mailovou adresu.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Čekejte prosím...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Ověření hesla", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Nesprávné heslo", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Zkuste to prosím znovu", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Zadejte heslo", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Zadejte své heslo", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Aktivní relace", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Jejda", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Něco se pokazilo. Zkuste to, prosím, znovu", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Tato akce Vás odhlásí z tohoto zařízení!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Toto Vás odhlásí z následujícího zařízení:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Ukončit relaci?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Ukončit", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Toto zařízení", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Vytvořit účet", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Slabé", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Střední", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Silné", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Odstranit účet", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Mrzí nás, že odcházíte. Máte nějaké problémy s aplikací?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Ano, poslat zpětnou vazbu", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Ne, odstranit účet", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Pro zahájení odstranění účtu se, prosím, ověřte", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Potvrdit odstranění účtu", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": " ", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Smazat", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Vytvořit nový účet", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Heslo", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Potvrzení hesla", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Síla hesla: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Jak jste se dozvěděli o Ente? (volitelné)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Ne sledujeme instalace aplikace. Pomůže nám, když nám sdělíte, kde jste nás našli!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Souhlasím s podmínkami služby a zásadami ochrany osobních údajů", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Podmínky", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Podmínky ochrany osobních údajů", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Rozumím, že při zapomenutí hesla mohu ztratit svá data, protože jsou zabezpečena koncovým šifrováním.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Šifrování", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Přihlásit se", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Vítejte zpět!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Kliknutím na přihlášení souhlasím s podmínkami služby a zásadami ochrany osobních údajů", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Žádné připojení k internetu", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Zkontrolujte, prosím, své připojení k internetu a zkuste to znovu.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Ověření selhalo, přihlaste se, prosím, znovu", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Resetovat heslo", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Aktzální zařízení není dostatečně výkonné pro ověření Vašeho hesla, ale můžeme ho regenerovat způsobem, který funguje ve všech zařízením.\n\nPřihlašte se pomocí obnovovacího klíče a znovu si vygenerujte své heslo (můžete použít opět stejné, pokud chcete).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Použít obnovovací klíč", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Zapomenuté heslo", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Změnit e-mail", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Ověřit e-mail", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Odeslali jsme e-mail na {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Pro obnovení hesla obnovte, prosím, nejprve svůj e-mail.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Pro dokončení ověření prosím zkontrolujte, prosím, svou doručenou poštu (a spamy)", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Klepnutím zadejte kód", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Odeslat e-mail", + "resendEmail": "Odeslat e-mail znovu", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Ověřování stále probíhá", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Relace vypršela", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Vaše relace vypršela. Přihlaste se, prosím, znovu.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "waitingForVerification": "Čekání na ověření...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Zkusit znovu", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Zkontrolovat stav", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Přihlášení s TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Obnovit účet", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Nastavit heslo", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Změnit heslo", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Obnovit heslo", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Šifrovací klíče", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Zadejte heslo, kterým můžeme zašifrovat Vaše data", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Zadejte nové heslo, kterým můžeme šifrovat Vaše data", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Vaše heslo neuchováváme. Pokud ho zapomenete, nemůžeme Vaše data dešifrovat", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Jak to funguje", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Generování šifrovacích klíčů...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Heslo úspěšně změněno", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Odhlásit z ostatních zařízení", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Pokud si myslíte, že by někdo mohl znát Vaše heslo, můžete vynutit odhlášení ostatních zařízení používajících Váš účet.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Odhlásit z ostatních zařízení", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Neodhlašovat", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Generování šifrovacích klíčů...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Pokračovat", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Nezabezpečené zařízení", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Omlouváme se, na tomto zařízení nemůžeme vygenerovat bezpečné klíče.\n\nprosím přihlaste se z jiného zařízení.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Obnovovací klíč byl zkopírován", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Obnovovací klíč", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Tento klíč je jedinou cestou pro obnovení Vašich dat, pokud zapomenete heslo.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Tento 24místný klíč neuchováváme, uschovejte ho, prosím, na bezpečném místě.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Udělat později", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Uložit klíč", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Obnovovací klíč uložen do složky Stažené soubory!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Nemáte obnovovací klíč?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Dvoufaktorové ověření", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Zadejte 6místný kód ze své autentizační aplikace", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Ztratili jste zařízení?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Zadejte svůj obnovovací klíč", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Obnovit", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Odhlašování...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Ihned", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Zámek aplikace", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Automatické zamykání", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Zámek systému nenalezen", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Pro aktivaci zámku zařízení si nastavte přístupový kód zařízení nebo zámek obrazovky v nastavení systému.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Vyberte si mezi zámkem obrazovky svého zařízení a vlastním zámkem obrazovky s PIN kódem nebo heslem.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Zámek zařízení", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Uzamčení na PIN", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Interval, po kterém se aplikace běžící na pozadí uzamkne", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Skrýt obsah", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Skryje obsah aplikace ve ", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Skryje obsah aplikace při přepínání úloh", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Příliš mnoho neúspěšných pokusů", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Pro odemčení klepněte", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Opravdu se chcete odhlásit?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Ano, odhlásit se", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Pro zobrazení svých tajných údajů se musíte ověřit", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Další", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Nastavit nové heslo", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Zadejte PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Nadra", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Potvrdit", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Zadejte heslo znovu", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Zadejte PIN znovu", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Ověřte svou identitu", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Nerozpoznáno. Zkuste znovu.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Úspěch", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Zrušit", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Je požadováno ověření", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Je požadováno biometrické ověření", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Jsou vyžadovány přihlašovací údaje zařízení", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Jsou vyžadovány přihlašovací údaje zařízení", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Jít do nastavení", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Na Vašem zařízení není nastaveno biometrické ověřování. Pro aktivaci běžte do 'Nastavení > Zabezpečení'.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biometrické ověřování není povoleno. Pro povolení zamkněte a odemkněte obrazovku.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "E-mail je již registrován.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "E-mail není registrován.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Tento e-mail je již používán", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "E-mail změněn na {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Ověření selhalo, zkuste to, prosím, znovu", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Ověření bylo úspěšné!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Relace vypršela", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Nesprávný obnovovací klíč", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Vámi zadaný obnovovací klíč je nesprávný", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Dvoufázové ověření bylo úspěšně obnoveno", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Váš ověřovací kód vypršel", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Nesprávný kód", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Omlouváme se, zadaný kód je nesprávný", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Nastavení pro vývojáře", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Koncový bod serveru", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Neplatný koncový bod", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Zadaný koncový bod je neplatný. Zadejte prosím platný koncový bod a zkuste to znovu.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Koncový bod byl úspěšně aktualizován", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_da.arb b/mobile/packages/strings/lib/l10n/arb/strings_da.arb new file mode 100644 index 0000000000..23eff3fdf5 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_da.arb @@ -0,0 +1,708 @@ +{ + "networkHostLookUpErr": "Ude af stand til at forbinde til Ente. Tjek venligst dine netværksindstillinger og kontakt support hvis fejlen varer ved.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Ude af stand til at forbinde til Ente. Forsøg igen efter et stykke tid. Hvis fejlen varer ved, kontakt da venligst support.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Det ser ud til at noget gik galt. Forsøg venligst igen efter lidt tid. Hvis fejlen varer ved, kontakt da venligst support.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Fejl", + "@error": { + "description": "Generic error title" + }, + "ok": "OK", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "FAQ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Kontakt support", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Email dine logs", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Send venligst logs til {toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Kopier email adresse", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Eksporter logs", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Afbryd", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Rapporter en fejl", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Forbindelse oprettet til {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Gem", + "@save": { + "description": "Label for save button" + }, + "send": "Send", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Vil du gemme på din enhed (Downloads mappe som udgangspunkt) eller sende til andre apps?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Vil du gemme på din enhed (Downloads mappe som udgangspunkt)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "Email", + "@email": { + "description": "Email field label" + }, + "verify": "Bekræft", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Ugyldig email adresse", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Indtast en gyldig email adresse.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Vent venligst...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Bekræft adgangskode", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Forkert adgangskode", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Forsøg venligst igen", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Indtast adgangskode", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Indtast adgangskode", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Aktive sessioner", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Ups", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Noget gik galt, forsøg venligst igen", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Dette vil logge dig ud af denne enhed!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Dette vil logge dig ud af den følgende enhed:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Afslut session?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Afslut", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Denne enhed", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Opret konto", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Svagt", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Middel", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Stærkt", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Slet konto", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Vi er kede af at se dig gå. Er du stødt på et problem?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Ja, send feedback", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Nej, slet konto", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Bekræft venligst for at påbegynde sletning af konto", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Bekræft sletning af konto", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Denne konto er forbundet til andre Ente apps, hvis du benytter nogle.\n\nDine uploadede data for alle Ente apps vil blive slettet, og din konto vil blive slettet permanent.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Slet", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Opret konto", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Kodeord", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Bekræft kodeord", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Kodeordets styrke: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Hvordan hørte du om Ente? (valgfrit)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Vi tracker ikke app installeringer. Det ville hjælpe os at vide hvordan du fandt os!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Jeg er enig i betingelser for brug og privatlivspolitik", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Betingelser", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Privatlivspolitik", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Jeg forstår at hvis jeg mister min adgangskode kan jeg miste mine data, da mine data er end-to-end krypteret.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Kryptering", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Log ind", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Velkommen tilbage!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Ved at logge ind godkender jeg Ente's betingelser for brug og privatlivspolitik.", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Ingen internetforbindelse", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Tjek venligst din internetforbindelse og forsøg igen.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Bekræftelse fejlede, forsøg venligst igen", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Gendan adgangskode", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Denne enhed er ikke kraftfuld nok til at bekræfte adgangskoden, men vi kan gendanne den på en måde der fungerer for alle enheder.\n\nLog venligst ind med din gendannelsesnøgle og gendan din adgangskode (du kan bruge den samme adgangskode igen hvis du ønsker).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Brug gendannelsesnøgle", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Glemt adgangskode", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Skift email adresse", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Bekræft email adresse", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Vi har sendt en email til {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "For at nulstille din adgangskode, bekræft venligst din email adresse.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Tjek venligst din indboks (og spam) for at færdiggøre verificeringen", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Tryk for at indtaste kode", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Send email", + "resendEmail": "Send email igen", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Bekræftelse afventes stadig", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Session udløbet", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Din session er udløbet. Log venligst på igen.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Bekræftelse af adgangskode", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Venter på bekræftelse...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Forsøg igen", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Tjek status", + "@checkStatus": { + "description": "Check status button label" + }, + "recoverAccount": "Gendan konto", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Angiv adgangskode", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Skift adgangskode", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Nulstil adgangskode", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Krypteringsnøgler", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Indtast en adgangskode vi kan bruge til at kryptere dine data", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Indtast en ny adgangskode vi kan bruge til at kryptere dine data", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Vi gemmer ikke denne adgangskode, så hvis du glemmer den kan vi ikke dekryptere dine data", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Sådan fungerer det", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Genererer krypteringsnøgler...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Adgangskoden er blevet ændret", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Log ud af andre enheder", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Hvis du mistænker at nogen kender din adgangskode kan du tvinge alle enheder der benytter din konto til at logge ud.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Log ud af andre enheder", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Log ikke ud", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Genererer krypteringsnøgler...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Fortsæt", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Usikker enhed", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Beklager, vi kunne ikke generere sikre krypteringsnøgler på denne enhed.\n\nForsøg venligst at oprette en konto fra en anden enhed.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Gendannelsesnøgle kopieret til udklipsholderen", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Gendannelsesnøgle", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Hvis du glemmer dit kodeord er gendannelsesnøglen den eneste mulighed for at få adgang til dine data.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Vi gemmer ikke denne nøgle, gem venligst denne 24-ords nøgle et sikkert sted.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Gør det senere", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Gem nøgle", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Gendannelsesnøgle gemt i din Downloads mappe!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Ingen gendannelsesnøgle?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Tofaktorgodkendelse", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Indtast den 6-cifrede kode fra din authenticator app", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Mistet enhed?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Indtast din gendannelsesnøgle", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Gendan", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Logger ud...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Med det samme", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Låsning af app", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Automatisk lås", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Ingen systemlås fundet", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "For at aktivere enhedslås, indstil venligst kode eller skærmlås på din enhed i dine systemindstillinger.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Vælg mellem din enheds standard skærmlås eller skærmlås med pinkode eller adgangskode.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Enhedslås", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Låsning med pinkode", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Tid til låsning af app efter at være blevet placeret i baggrunden", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Skjul indhold", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Skjul app indhold i app-vælger og deaktiver screenshots", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Skjul app indhold i app-vælger", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "For mange forkerte forsøg", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Tryk for at låse op", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Er du sikker på at du vil logge ud?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Ja, log ud", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Bekræft venligst din identitet for at se dine hemmeligheder", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Næste", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Indstil ny adgangskode", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Indtast pinkode", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Indstil ny pinkode", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Bekræft", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Indtast adgangskode igen", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Indtast pinkode igen", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Bekræft identitet", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Ikke genkendt. Forsøg igen.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Succes", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Afbryd", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Godkendelse påkrævet", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biometri påkrævet", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Enhedsoplysninger påkrævet", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Enhedsoplysninger påkrævet", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Gå til indstillinger", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Biometrisk godkendelse er ikke indstillet på din enhed. Gå til \"Indstillinger > Sikkerhed\" for at indstille biometrisk godkendelse.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biometrisk godkendelse er slået fra. Lås din skærm, og lås den derefter op for at aktivere det.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "thisEmailIsAlreadyInUse": "Denne email adresse er allerede i brug", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Email adresse ændret til {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Bekræftelse af identitet fejlede, forsøg venligst igen", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Bekræftelse af identitet lykkedes!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Session udløbet", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Forkert gendannelsesnøgle", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Den indtastede gendannelsesnøgle er ikke korrekt", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Tofaktorgodkendelse nulstillet", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Din bekræftelseskode er udløbet", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Forkert kode", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Beklager, den indtastede kode er forkert", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Udvikler-indstillinger", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Server endpoint", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Ugyldigt endpoint", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Beklager, det indtastede endpoint er ugyldigt. Indtast venligst et gyldigt endpoint og forsøg igen.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Endpoint opdateret", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_de.arb b/mobile/packages/strings/lib/l10n/arb/strings_de.arb new file mode 100644 index 0000000000..7b4a3d0344 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_de.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "Ente ist im Moment nicht erreichbar. Bitte überprüfen Sie Ihre Netzwerkeinstellungen. Sollte das Problem bestehen bleiben, wenden Sie sich bitte an den Support.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Ente ist im Moment nicht erreichbar. Bitte versuchen Sie es später erneut. Sollte das Problem bestehen bleiben, wenden Sie sich bitte an den Support.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Etwas ist schiefgelaufen. Bitte versuchen Sie es später noch einmal. Sollte der Fehler weiter bestehen, kontaktieren Sie unser Supportteam.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Fehler", + "@error": { + "description": "Generic error title" + }, + "ok": "Ok", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "FAQ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Support kontaktieren", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "E-Mail mit Logs senden", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Bitte Logs an {toEmail} senden", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "E-Mail-Adresse kopieren", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Logs exportieren", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Abbrechen", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Einen Fehler melden", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Mit {endpoint} verbunden", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Speichern", + "@save": { + "description": "Label for save button" + }, + "send": "Senden", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Möchtest du dies in deinem Speicher (standardmäßig im Ordner Downloads) speichern oder an andere Apps senden?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Möchtest du dies in deinem Speicher (standardmäßig im Ordner Downloads) speichern?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Gib deine neue E-Mail-Adresse ein", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "E-Mail", + "@email": { + "description": "Email field label" + }, + "verify": "Verifizieren", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Ungültige E-Mail-Adresse", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Bitte geben Sie eine gültige E-Mail-Adresse ein.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Bitte warten...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Passwort überprüfen", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Falsches Passwort", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Bitte versuchen Sie es erneut", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Passwort eingeben", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Geben Sie Ihr Passwort ein", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Aktive Sitzungen", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Hoppla", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Ein Fehler ist aufgetreten, bitte erneut versuchen", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Dadurch werden Sie von diesem Gerät abgemeldet!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Dadurch werden Sie vom folgendem Gerät abgemeldet:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Sitzung beenden?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Beenden", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Dieses Gerät", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Konto erstellen", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Schwach", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Mittel", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Stark", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Konto löschen", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Es tut uns leid, dass Sie gehen. Haben Sie ein Problem?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Ja, Feedback senden", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Nein, Konto löschen", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Bitte authentifizieren Sie sich, um die Kontolöschung einzuleiten", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Kontolöschung bestätigen", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Dieses Konto ist mit anderen Ente-Apps verknüpft, falls Sie welche verwenden.\n\nIhre hochgeladenen Daten werden in allen Ente-Apps zur Löschung vorgemerkt und Ihr Konto wird endgültig gelöscht.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Löschen", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Neues Konto erstellen", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Passwort", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Bestätigen Sie das Passwort", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Passwortstärke: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Wie hast du von Ente erfahren? (optional)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Wir tracken keine App-Installationen. Es würde uns jedoch helfen, wenn du uns mitteilst, wie du von uns erfahren hast!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Ich stimme den Nutzerbedingungen und Datenschutzbestimmungen zu", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Bedingungen", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Datenschutzbestimmungen", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Ich verstehe, dass der Verlust meines Passworts zum Verlust meiner Daten führen kann, denn diese sind Ende-zu-Ende verschlüsselt.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Verschlüsselung", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Einloggen", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Willkommen zurück!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Durch das Klicken auf den Login-Button, stimme ich den Nutzungsbedingungen und den Datenschutzbestimmungen zu", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Keine Internetverbindung", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie erneut.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Verifizierung fehlgeschlagen, bitte versuchen Sie es erneut", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Neues Passwort erstellen", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Das benutzte Gerät ist nicht leistungsfähig genug das Passwort zu prüfen. Wir können es aber neu erstellen damit es auf jedem Gerät funktioniert. \n\nBitte loggen sie sich mit ihrem Wiederherstellungsschlüssel ein und erstellen sie ein neues Passwort (Sie können das selbe Passwort wieder verwenden, wenn sie möchten).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Wiederherstellungsschlüssel verwenden", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Passwort vergessen", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "E-Mail ändern", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "E-Mail-Adresse verifizieren", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Wir haben eine E-Mail an {email} gesendet", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Um Ihr Passwort zurückzusetzen, verifizieren Sie bitte zuerst Ihre E-Mail-Adresse.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Bitte überprüfe deinen E-Mail-Posteingang (und Spam), um die Verifizierung abzuschließen", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Antippen, um den Code einzugeben", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "E-Mail senden", + "resendEmail": "E-Mail erneut senden", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Verifizierung steht noch aus", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Sitzung abgelaufen", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Passkey Authentifizierung", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Warte auf Bestätigung...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Noch einmal versuchen", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Status überprüfen", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Mit TOTP anmelden", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Konto wiederherstellen", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Passwort setzen", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Passwort ändern", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Passwort zurücksetzen", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Verschlüsselungsschlüssel", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Geben Sie ein Passwort ein, mit dem wir Ihre Daten verschlüsseln können", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Geben Sie ein neues Passwort ein, mit dem wir Ihre Daten verschlüsseln können", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Wir speichern dieses Passwort nicht. Wenn Sie es vergessen, können wir Ihre Daten nicht entschlüsseln", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "So funktioniert's", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Generierung von Verschlüsselungsschlüsseln...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Passwort erfolgreich geändert", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Von anderen Geräten abmelden", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Falls Sie denken, dass jemand Ihr Passwort kennen könnte, können Sie alle anderen Geräte forcieren, sich von Ihrem Konto abzumelden.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Andere Geräte abmelden", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Nicht abmelden", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Generierung von Verschlüsselungsschlüsseln...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Weiter", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Unsicheres Gerät", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Es tut uns leid, wir konnten keine sicheren Schlüssel auf diesem Gerät generieren.\n\nBitte registrieren Sie sich auf einem anderen Gerät.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Wiederherstellungsschlüssel in die Zwischenablage kopiert", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Wiederherstellungsschlüssel", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Sollten sie ihr Passwort vergessen, dann ist dieser Schlüssel die einzige Möglichkeit ihre Daten wiederherzustellen.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Wir speichern diesen Schlüssel nicht. Sichern Sie diesen Schlüssel bestehend aus 24 Wörtern an einem sicheren Platz.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Auf später verschieben", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Schlüssel speichern", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Wiederherstellungsschlüssel im Downloads-Ordner gespeichert!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Kein Wiederherstellungsschlüssel?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Zwei-Faktor-Authentifizierung", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Geben Sie den 6-stelligen Code \naus Ihrer Authentifikator-App ein.", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Gerät verloren?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Geben Sie Ihren Wiederherstellungsschlüssel ein", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Wiederherstellen", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Wird abgemeldet...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Sofort", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "App-Sperre", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Automatisches Sperren", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Keine Systemsperre gefunden", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Um die Gerätesperre zu aktivieren, richte bitte einen Gerätepasscode oder eine Bildschirmsperre in den Systemeinstellungen ein.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Wähle zwischen dem Standard-Sperrbildschirm deines Gerätes und einem eigenen Sperrbildschirm mit PIN oder Passwort.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Gerätesperre", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "PIN-Sperre", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Zeit, nach der die App gesperrt wird, nachdem sie in den Hintergrund verschoben wurde", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Inhalte verstecken", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Versteckt Inhalte der App beim Wechseln zwischen Apps und deaktiviert Screenshots", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Versteckt Inhalte der App beim Wechseln zwischen Apps", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Zu viele fehlerhafte Versuche", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Zum Entsperren antippen", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Sind sie sicher, dass sie sich ausloggen möchten?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Ja ausloggen", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Bitte authentifizieren, um Ihren Wiederherstellungscode anzuzeigen", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Weiter", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Neues Passwort festlegen", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "PIN eingeben", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Neue PIN festlegen", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Bestätigen", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Passwort erneut eingeben", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "PIN erneut eingeben", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Identität bestätigen", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Nicht erkannt. Versuchen Sie es erneut.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Erfolgreich", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Abbrechen", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Authentifizierung erforderlich", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biometrie erforderlich", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Geräteanmeldeinformationen erforderlich", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Geräteanmeldeinformationen erforderlich", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Zu den Einstellungen", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Auf Ihrem Gerät ist keine biometrische Authentifizierung eingerichtet. Gehen Sie zu 'Einstellungen > Sicherheit', um die biometrische Authentifizierung hinzuzufügen.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Die biometrische Authentifizierung ist deaktiviert. Bitte sperren und entsperren Sie Ihren Bildschirm, um sie wieder zu aktivieren.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "E-Mail ist bereits registriert.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "E-Mail-Adresse nicht registriert.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Diese E-Mail-Adresse wird bereits verwendet", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "E-Mail-Adresse geändert zu {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Authentifizierung fehlgeschlagen, bitte erneut versuchen", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Authentifizierung erfolgreich!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Sitzung abgelaufen", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Falscher Wiederherstellungs-Schlüssel", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Der eingegebene Wiederherstellungs-Schlüssel ist ungültig", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Zwei-Faktor-Authentifizierung (2FA) erfolgreich zurückgesetzt", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Ihr Bestätigungscode ist abgelaufen", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Falscher Code", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Leider ist der eingegebene Code falsch", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Entwicklereinstellungen", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Server Endpunkt", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Ungültiger Endpunkt", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Der eingegebene Endpunkt ist ungültig. Bitte geben Sie einen gültigen Endpunkt ein und versuchen Sie es erneut.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Endpunkt erfolgreich aktualisiert", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_el.arb b/mobile/packages/strings/lib/l10n/arb/strings_el.arb new file mode 100644 index 0000000000..bd290f6998 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_el.arb @@ -0,0 +1,712 @@ +{ + "networkHostLookUpErr": "Δεν είναι δυνατή η σύνδεση με το Ente, ελέγξτε τις ρυθμίσεις του δικτύου σας και επικοινωνήστε με την υποστήριξη αν το σφάλμα παραμένει.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Δεν είναι δυνατή η σύνδεση με το Ente, παρακαλώ προσπαθήστε ξανά μετά από λίγο. Εάν το σφάλμα παραμένει, παρακαλούμε επικοινωνήστε με την υποστήριξη.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Φαίνεται ότι κάτι πήγε στραβά. Παρακαλώ προσπαθήστε ξανά μετά από λίγο. Αν το σφάλμα παραμένει, παρακαλούμε επικοινωνήστε με την ομάδα υποστήριξης μας.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Σφάλμα", + "@error": { + "description": "Generic error title" + }, + "ok": "Οκ", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Συχνές Ερωτήσεις", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Επικοινωνήστε με την υποστήριξη", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Στείλτε με email τα αρχεία καταγραφής σας", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Παρακαλώ στείλτε τα αρχεία καταγραφής σας στο \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Αντιγραφή διεύθυνσης email", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Εξαγωγή αρχείων καταγραφής", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Ακύρωση", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Αναφορά Σφάλματος", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Συνδεδεμένο στο {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Αποθήκευση", + "@save": { + "description": "Label for save button" + }, + "send": "Αποστολή", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Θέλετε να το αποθηκεύσετε στον αποθηκευτικό σας χώρο (φάκελος Λήψεις από προεπιλογή) ή να το στείλετε σε άλλες εφαρμογές;", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Θέλετε να το αποθηκεύσετε στον αποθηκευτικό σας χώρο (φάκελος Λήψεις από προεπιλογή);", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "Email", + "@email": { + "description": "Email field label" + }, + "verify": "Επαλήθευση", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Μη έγκυρη διεύθυνση email", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Παρακαλούμε εισάγετε μια έγκυρη διεύθυνση email.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Παρακαλώ περιμένετε…", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Επαλήθευση κωδικού πρόσβασης", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Λάθος κωδικός πρόσβασης", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Παρακαλώ δοκιμάστε ξανά", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Εισάγετε κωδικό πρόσβασης", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Εισάγετε τον κωδικό πρόσβασης σας", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Ενεργές συνεδρίες", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Ουπς", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Κάτι πήγε στραβά, παρακαλώ προσπαθήστε ξανά", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Αυτό θα σας αποσυνδέσει από αυτή τη συσκευή!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Αυτό θα σας αποσυνδέσει από την ακόλουθη συσκευή:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Τερματισμός συνεδρίας;", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Τερματισμός", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Αυτή η συσκευή", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Δημιουργία λογαριασμού", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Αδύναμος", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Μέτριος", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Δυνατός", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Διαγραφή λογαριασμού", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Λυπόμαστε που σας βλέπουμε να φεύγετε. Αντιμετωπίζετε κάποιο πρόβλημα;", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Ναι, αποστολή σχολίων", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Όχι, διαγραφή λογαριασμού", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να ξεκινήσετε τη διαγραφή λογαριασμού", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Επιβεβαίωση διαγραφής λογαριασμού", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Αυτός ο λογαριασμός είναι συνδεδεμένος με άλλες εφαρμογές Ente, εάν χρησιμοποιείτε κάποια.\n\nΤα δεδομένα σας, σε όλες τις εφαρμογές Ente, θα προγραμματιστούν για διαγραφή και ο λογαριασμός σας θα διαγραφεί οριστικά.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Διαγραφή", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Δημιουργία νέου λογαριασμού", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Κωδικόs πρόσβασης", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Επιβεβαίωση κωδικού πρόσβασης", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Ισχύς κωδικού πρόσβασης: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Πώς ακούσατε για το Ente; (προαιρετικό)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Δεν παρακολουθούμε εγκαταστάσεις εφαρμογών. Θα βοηθούσε αν μας είπατε πού μας βρήκατε!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Συμφωνώ με τους όρους χρήσης και την πολιτική απορρήτου", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Όροι", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Πολιτική Απορρήτου", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Καταλαβαίνω ότι αν χάσω τον κωδικό μου μπορεί να χάσω τα δεδομένα μου αφού τα δεδομένα μου είναι από άκρο-σε-άκρο κρυπτογραφημένα.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Kρυπτογράφηση", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Σύνδεση", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Καλωσορίσατε και πάλι!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Κάνοντας κλικ στη σύνδεση, συμφωνώ με τους όρους χρήσης και την πολιτική απορρήτου", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Χωρίς σύνδεση στο διαδίκτυο", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Παρακαλούμε ελέγξτε τη σύνδεσή σας στο διαδίκτυο και προσπαθήστε ξανά.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Η επαλήθευση απέτυχε, παρακαλώ προσπαθήστε ξανά", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Επαναδημιουργία κωδικού πρόσβασης", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Η τρέχουσα συσκευή δεν είναι αρκετά ισχυρή για να επαληθεύσει τον κωδικό πρόσβασής σας, αλλά μπορούμε να τον αναδημιουργήσουμε με έναν τρόπο που λειτουργεί με όλες τις συσκευές.\n\nΠαρακαλούμε συνδεθείτε χρησιμοποιώντας το κλειδί ανάκτησης και αναδημιουργήστε τον κωδικό πρόσβασής σας (μπορείτε να χρησιμοποιήσετε ξανά τον ίδιο αν το επιθυμείτε).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Χρήση κλειδιού ανάκτησης", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Ξέχασα τον κωδικό πρόσβασης σας", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Αλλαγή email", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Επαλήθευση email", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Έχουμε στείλει ένα μήνυμα στο {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Για να επαναφέρετε τον κωδικό πρόσβασής σας, επαληθεύστε πρώτα το email σας.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Παρακαλώ ελέγξτε τα εισερχόμενά σας (και τα ανεπιθύμητα) για να ολοκληρώσετε την επαλήθευση", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Πατήστε για να εισάγετε τον κωδικό", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Αποστολή email", + "resendEmail": "Επανάληψη αποστολής email", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Η επαλήθευση εξακολουθεί να εκκρεμεί", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Η συνεδρία έληξε", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Η συνεδρία σας έληξε. Παρακαλώ συνδεθείτε ξανά.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Επιβεβαίωση κλειδιού πρόσβασης", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Αναμονή για επαλήθευση...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Προσπαθήστε ξανά", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Έλεγχος κατάστασης", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Είσοδος με TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Ανάκτηση λογαριασμού", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Ορισμός κωδικού πρόσβασης", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Αλλαγή κωδικού πρόσβασής", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Επαναφορά κωδικού πρόσβασης", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Κλειδιά κρυπτογράφησης", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Εισάγετε έναν κωδικό πρόσβασης που μπορούμε να χρησιμοποιήσουμε για την κρυπτογράφηση των δεδομένων σας", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Εισάγετε ένα νέο κωδικό πρόσβασης που μπορούμε να χρησιμοποιήσουμε για να κρυπτογραφήσουμε τα δεδομένα σας", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Δεν αποθηκεύουμε αυτόν τον κωδικό πρόσβασης, οπότε αν τον ξεχάσετε δεν μπορούμε να αποκρυπτογραφήσουμε τα δεδομένα σας", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Πώς λειτουργεί", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Δημιουργία κλειδιών κρυπτογράφησης...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Ο κωδικός πρόσβασης άλλαξε επιτυχώς", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Αποσύνδεση από άλλες συσκευές", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Αν νομίζετε ότι κάποιος μπορεί να γνωρίζει τον κωδικό πρόσβασής σας, μπορείτε να αναγκάσετε όλες τις άλλες συσκευές που χρησιμοποιούν το λογαριασμό σας να αποσυνδεθούν.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Αποσύνδεση άλλων συσκευών", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Μην αποσυνδεθείτε", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Δημιουργία κλειδιών κρυπτογράφησης…", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Συνέχεια", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Μη ασφαλής συσκευή", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Συγγνώμη, δεν μπορέσαμε να δημιουργήσουμε ασφαλή κλειδιά σε αυτήν τη συσκευή.\n\nπαρακαλώ εγγραφείτε από μια διαφορετική συσκευή.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Το κλειδί ανάκτησης αντιγράφηκε στο πρόχειρο", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Κλειδί ανάκτησης", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Εάν ξεχάσετε τον κωδικό πρόσβασής σας, ο μόνος τρόπος για να ανακτήσετε τα δεδομένα σας είναι με αυτό το κλειδί.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Δεν αποθηκεύουμε αυτό το κλειδί, παρακαλώ αποθηκεύστε αυτό το κλειδί 24 λέξεων σε μια ασφαλή τοποθεσία.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Κάντε το αργότερα", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Αποθήκευση κλειδιού", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Το κλειδί ανάκτησης αποθηκεύτηκε στο φάκελο Λήψεις!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Χωρίς κλειδί ανάκτησης;", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Αυθεντικοποίηση δύο παραγόντων", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Εισάγετε τον 6ψήφιο κωδικό από \nτην εφαρμογή αυθεντικοποίησης", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Χαμένη συσκευή;", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Εισάγετε το κλειδί ανάκτησης σας", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Ανάκτηση", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Αποσύνδεση…", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Άμεσα", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Κλείδωμα εφαρμογής", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Αυτόματο κλείδωμα", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Δεν βρέθηκε κλείδωμα συστήματος", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Για να ενεργοποιήσετε το κλείδωμα της συσκευής, παρακαλώ ρυθμίστε τον κωδικό πρόσβασης της συσκευής ή το κλείδωμα οθόνης στις ρυθμίσεις του συστήματός σας.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Επιλέξτε ανάμεσα στην προεπιλεγμένη οθόνη κλειδώματος της συσκευής σας και σε μια προσαρμοσμένη οθόνη κλειδώματος με ένα PIN ή έναν κωδικό πρόσβασης.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Κλείδωμα συσκευής", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Κλείδωμα καρφιτσωμάτων", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Χρόνος μετά τον οποίο η εφαρμογή κλειδώνει μετά την τοποθέτηση στο παρασκήνιο", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Απόκρυψη περιεχομένου", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Απόκρυψη περιεχομένου εφαρμογής στην εναλλαγή εφαρμογών και απενεργοποίηση στιγμιότυπων οθόνης", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Απόκρυψη περιεχομένου εφαρμογών στην εναλλαγή εφαρμογών", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Πάρα πολλές εσφαλμένες προσπάθειες", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Πατήστε για ξεκλείδωμα", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Ναι, αποσύνδεση", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να δείτε τα μυστικά σας", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Επόμενο", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Ορίστε νέο κωδικό πρόσβασης", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Εισαγωγή PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Ορίστε νέο PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Επιβεβαίωση", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Πληκτρολογήστε ξανά τον κωδικό πρόσβασης", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Πληκτρολογήστε ξανά το PIN", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Επαλήθευση ταυτότητας", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Δεν αναγνωρίζεται. Δοκιμάστε ξανά.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Επιτυχία", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Ακύρωση", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Απαιτείται έλεγχος ταυτότητας", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Απαιτούνται βιομετρικά", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Απαιτούνται στοιχεία συσκευής", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Απαιτούνται στοιχεία συσκευής", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Μετάβαση στις ρυθμίσεις", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Η βιομετρική πιστοποίηση δεν έχει ρυθμιστεί στη συσκευή σας. Μεταβείτε στις 'Ρυθμίσεις > Ασφάλεια' για να προσθέσετε βιομετρική ταυτοποίηση.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Η βιομετρική ταυτοποίηση είναι απενεργοποιημένη. Παρακαλώ κλειδώστε και ξεκλειδώστε την οθόνη σας για να την ενεργοποιήσετε.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "ΟΚ", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "thisEmailIsAlreadyInUse": "Αυτό το email είναι ήδη σε χρήση", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Το email άλλαξε σε {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Αποτυχία ελέγχου ταυτότητας, παρακαλώ προσπαθήστε ξανά", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Επιτυχής έλεγχος ταυτότητας!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Η συνεδρία έληξε", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Εσφαλμένο κλειδί ανάκτησης", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Το κλειδί ανάκτησης που εισάγατε είναι εσφαλμένο", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Η αυθεντικοποίηση δύο παραγόντων επαναφέρθηκε επιτυχώς", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Ο κωδικός επαλήθευσης σας έχει λήξει", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Εσφαλμένος κωδικός", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Λυπούμαστε, ο κωδικός που εισαγάγατε είναι εσφαλμένος", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Ρυθμίσεις προγραμματιστή", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Τερματικό σημείο διακομιστή", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Μη έγκυρο τερματικό σημείο", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Λυπούμαστε, το τερματικό σημείο που εισάγατε δεν είναι έγκυρο. Παρακαλώ εισάγετε ένα έγκυρο τερματικό σημείο και προσπαθήστε ξανά.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Το τερματκό σημείο ενημερώθηκε επιτυχώς", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_en.arb b/mobile/packages/strings/lib/l10n/arb/strings_en.arb new file mode 100644 index 0000000000..6db1039109 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_en.arb @@ -0,0 +1,783 @@ +{ + "networkHostLookUpErr": "Unable to connect to Ente, please check your network settings and contact support if the error persists.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Error", + "@error": { + "description": "Generic error title" + }, + "ok": "Ok", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "FAQ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Contact support", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Email your logs", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Please send the logs to \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Copy email address", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Export logs", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Cancel", + "@cancel": { + "description": "Cancel button label" + }, + "pleaseEmailUsAt": "Email us at {toEmail}", + "@pleaseEmailUsAt": { + "description": "Message showing email address for support", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Support email address" + } + } + }, + "emailAddressCopied": "Email address copied", + "@emailAddressCopied": { + "description": "Snackbar message when email address is copied" + }, + "supportEmailSubject": "[Support]", + "@supportEmailSubject": { + "description": "Default subject for support emails" + }, + "clientDebugInfoLabel": "Following information can help us in debugging if you are facing any issue", + "@clientDebugInfoLabel": { + "description": "Label for debug information in emails" + }, + "registeredEmailLabel": "Registered email:", + "@registeredEmailLabel": { + "description": "Label for registered email in debug info" + }, + "clientLabel": "Client:", + "@clientLabel": { + "description": "Label for client information in debug info" + }, + "versionLabel": "Version :", + "@versionLabel": { + "description": "Label for version information in debug info" + }, + "notAvailable": "N/A", + "@notAvailable": { + "description": "Not available text" + }, + + "reportABug": "Report a bug", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "logsDialogBody": "This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.", + "@logsDialogBody": { + "description": "Body text for the logs dialog explaining what will be sent" + }, + "viewLogs": "View logs", + "@viewLogs": { + "description": "Button to view logs" + }, + "customEndpoint": "Connected to {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Save", + "@save": { + "description": "Label for save button" + }, + "send": "Send", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Do you want to save this to your storage (Downloads folder by default) or send it to other apps?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Do you want to save this to your storage (Downloads folder by default)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Enter your new email address", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "Email", + "@email": { + "description": "Email field label" + }, + "verify": "Verify", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Invalid email address", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Please enter a valid email address.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Please wait...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Verify password", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Incorrect password", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Please try again", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Enter password", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Enter your password", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Active sessions", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Oops", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Something went wrong, please try again", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "This will log you out of this device!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "This will log you out of the following device:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Terminate session?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Terminate", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "This device", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Create account", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Weak", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Moderate", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Strong", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Delete account", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "We'll be sorry to see you go. Are you facing some issue?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Yes, send feedback", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "No, delete account", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Please authenticate to initiate account deletion", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Confirm account deletion", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "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": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Delete", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Create new account", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Password", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Confirm password", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Password strength: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "How did you hear about Ente? (optional)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "I agree to the terms of service and privacy policy", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Terms", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Privacy Policy", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Encryption", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Log in", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Welcome back!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "By clicking log in, I agree to the terms of service and privacy policy", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "No internet connection", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Please check your internet connection and try again.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Verification failed, please try again", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Recreate password", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Use recovery key", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Forgot password", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Change email", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Verify email", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "We have sent a mail to {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "To reset your password, please verify your email first.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Please check your inbox (and spam) to complete verification", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Tap to enter code", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Send email", + "resendEmail": "Resend email", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Verification is still pending", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Session expired", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Your session has expired. Please login again.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Passkey verification", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Waiting for verification...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Try again", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Check status", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Login with TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Recover account", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Set password", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Change password", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Reset password", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Encryption keys", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Enter a password we can use to encrypt your data", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Enter a new password we can use to encrypt your data", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "We don't store this password, so if you forget, we cannot decrypt your data", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "How it works", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Generating encryption keys...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Password changed successfully", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Sign out from other devices", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "If you think someone might know your password, you can force all other devices using your account to sign out.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Sign out other devices", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Do not sign out", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Generating encryption keys...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Continue", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Insecure device", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Recovery key copied to clipboard", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Recovery key", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "If you forget your password, the only way you can recover your data is with this key.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Do this later", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Save key", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Recovery key saved in Downloads folder!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "No recovery key?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Two-factor authentication", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Enter the 6-digit code from\nyour authenticator app", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Lost device?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Enter your recovery key", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Recover", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Logging out...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Immediately", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "App lock", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Auto lock", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "No system lock found", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "To enable device lock, please setup device passcode or screen lock in your system settings.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Choose between your device's default lock screen and a custom lock screen with a PIN or password.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Device lock", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Pin lock", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Time after which the app locks after being put in the background", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Hide content", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Hides app content in the app switcher and disables screenshots", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Hides app content in the app switcher", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Too many incorrect attempts", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Tap to unlock", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Are you sure you want to logout?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Yes, logout", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Please authenticate to view your secrets", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Next", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Set new password", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Enter PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Set new PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Confirm", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Re-enter password", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Re-enter PIN", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Verify identity", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Not recognized. Try again.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Success", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Cancel", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Authentication required", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biometric required", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Device credentials required", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Device credentials required", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Go to settings", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Biometric authentication is not set up on your device. Go to 'Settings > Security' to add biometric authentication.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biometric authentication is disabled. Please lock and unlock your screen to enable it.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "Email already registered.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "Email not registered.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "This email is already in use", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Email changed to {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Authentication failed, please try again", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Authentication successful!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Session expired", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Incorrect recovery key", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "The recovery key you entered is incorrect", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Two-factor authentication successfully reset", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "noRecoveryKey": "No recovery key", + "@noRecoveryKey": { + "description": "Error message when no recovery key is found" + }, + "yourAccountHasBeenDeleted": "Your account has been deleted", + "@yourAccountHasBeenDeleted": { + "description": "Confirmation message when account has been deleted" + }, + "verificationId": "Verification ID", + "@verificationId": { + "description": "Label for verification ID" + }, + "yourVerificationCodeHasExpired": "Your verification code has expired", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Incorrect code", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Sorry, the code you've entered is incorrect", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Developer settings", + "@developerSettings": { + "description": "Label for developer settings" + }, + "serverEndpoint": "Server endpoint", + "@serverEndpoint": { + "description": "Label for server endpoint setting" + }, + "invalidEndpoint": "Invalid endpoint", + "@invalidEndpoint": { + "description": "Error message when endpoint is invalid" + }, + "invalidEndpointMessage": "Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.", + "@invalidEndpointMessage": { + "description": "Detailed error message when endpoint is invalid" + }, + "endpointUpdatedMessage": "Endpoint updated successfully", + "@endpointUpdatedMessage": { + "description": "Success message when endpoint is updated" + } +} diff --git a/mobile/packages/strings/lib/l10n/arb/strings_es.arb b/mobile/packages/strings/lib/l10n/arb/strings_es.arb new file mode 100644 index 0000000000..dca0601187 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_es.arb @@ -0,0 +1,720 @@ +{ + "networkHostLookUpErr": "No se puede conectar a Ente. Por favor, comprueba tu configuración de red y ponte en contacto con el soporte técnico si el error persiste.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "No se puede conectar a Ente. Por favor, vuelve a intentarlo pasado un tiempo. Si el error persiste, ponte en contacto con el soporte técnico.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Parece que algo salió mal. Por favor, vuelve a intentarlo pasado un tiempo. Si el error persiste, ponte en contacto con nuestro equipo de soporte.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Error", + "@error": { + "description": "Generic error title" + }, + "ok": "Ok", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Preguntas Frecuentes", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Ponerse en contacto con el equipo de soporte", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Envíe sus registros por correo electrónico", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Por favor, envíe los registros a {toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Copiar dirección de correo electrónico", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Exportar registros", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Cancelar", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Reportar un error", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Conectado a {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Guardar", + "@save": { + "description": "Label for save button" + }, + "send": "Enviar", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "¿Desea guardar el archivo en el almacenamiento (carpeta Descargas por defecto) o enviarlo a otras aplicaciones?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "¿Desea guardar el archivo en el almacenamiento (carpeta Descargas por defecto)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "Correo electrónico", + "@email": { + "description": "Email field label" + }, + "verify": "Verificar", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Dirección de correo electrónico no válida", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Por favor, introduce una dirección de correo electrónico válida.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Por favor, espere...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Verificar contraseña", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Contraseña incorrecta", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Por favor, inténtalo de nuevo", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Introduzca la contraseña", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Introduce tu contraseña", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Sesiones activas", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Ups", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Algo ha ido mal, por favor, inténtelo de nuevo", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "¡Esto cerrará la sesión de este dispositivo!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Esto cerrará la sesión del siguiente dispositivo:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "¿Terminar sesión?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Terminar", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Este dispositivo", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Crear cuenta", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Poco segura", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Moderada", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Segura", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Eliminar cuenta", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Lamentamos que te vayas. ¿Estás teniendo algún problema?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Sí, enviar comentarios", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "No, eliminar cuenta", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Por favor, autentícate para iniciar la eliminación de la cuenta", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Confirmar eliminación de la cuenta", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Esta cuenta está vinculada a otras aplicaciones de Ente, si utilizas alguna. \n\nSe programará la eliminación de los datos cargados en todas las aplicaciones de Ente, y tu cuenta se eliminará permanentemente.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Borrar", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Crear cuenta nueva", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Contraseña", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Confirmar contraseña", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Fortaleza de la contraseña: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "¿Cómo conoció Ente? (opcional)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "No rastreamos la instalación de las aplicaciones. ¡Nos ayudaría si nos dijera dónde nos encontró!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Estoy de acuerdo con los términos del servicio y la política de privacidad", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Términos", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Política de Privacidad", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Entiendo que si pierdo mi contraseña podría perder mis datos, ya que mis datos están cifrados de extremo a extremo.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Cifrado", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Iniciar sesión", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "¡Te damos la bienvenida otra vez!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Al hacer clic en iniciar sesión, acepto los términos de servicio y la política de privacidad", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "No hay conexión a Internet", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Compruebe su conexión a Internet e inténtelo de nuevo.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Verificación fallida, por favor inténtalo de nuevo", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Recrear contraseña", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "El dispositivo actual no es lo suficientemente potente para verificar su contraseña, pero podemos regenerarla de manera que funcione con todos los dispositivos.\n\nPor favor inicie sesión usando su clave de recuperación y regenere su contraseña (puede volver a utilizar la misma si lo desea).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Usar clave de recuperación", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Olvidé mi contraseña", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Cambiar correo electrónico", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Verificar correo electrónico", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Hemos enviado un correo electrónico a {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Para restablecer tu contraseña, por favor verifica tu correo electrónico primero.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Por favor revisa tu bandeja de entrada (y spam) para completar la verificación", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Toca para introducir el código", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Enviar correo electrónico", + "resendEmail": "Reenviar correo electrónico", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "La verificación todavía está pendiente", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "La sesión ha expirado", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Tu sesión ha expirado. Por favor, vuelve a iniciar sesión.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Verificación de clave de acceso", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Esperando verificación...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Inténtelo de nuevo", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Comprobar estado", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Inicio de sesión con TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Recuperar cuenta", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Establecer contraseña", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Cambiar contraseña", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Restablecer contraseña", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Claves de cifrado", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Introduzca una contraseña que podamos usar para cifrar sus datos", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Introduzca una contraseña nueva que podamos usar para cifrar sus datos", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "No almacenamos esta contraseña, así que si la olvidas, no podremos descifrar tus datos", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Cómo funciona", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Generando claves de cifrado...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Contraseña cambiada correctamente", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Cerrar sesión en otros dispositivos", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Si crees que alguien puede conocer tu contraseña, puedes forzar a todos los demás dispositivos que usen tu cuenta a cerrar la sesión.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Cerrar la sesión en otros dispositivos", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "No cerrar la sesión", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Generando claves de cifrado...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Continuar", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Dispositivo inseguro", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Lo sentimos, no hemos podido generar claves seguras en este dispositivo.\n\nRegístrate desde un dispositivo diferente.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Clave de recuperación copiada al portapapeles", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Clave de recuperación", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Si olvidas tu contraseña, la única forma de recuperar tus datos es con esta clave.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Nosotros no almacenamos esta clave, por favor guarda esta clave de 24 palabras en un lugar seguro.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Hacer esto más tarde", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Guardar clave", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "¡Clave de recuperación guardada en la carpeta Descargas!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "¿No tienes la clave de recuperación?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Autenticación de dos factores", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Ingrese el código de seis dígitos de su aplicación de autenticación", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "¿Dispositivo perdido?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Introduce tu clave de recuperación", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Recuperar", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Cerrando sesión...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Inmediatamente", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Bloqueo de aplicación", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Bloqueo automático", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Bloqueo del sistema no encontrado", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Para activar el bloqueo de la aplicación, por favor configura el código de acceso del dispositivo o el bloqueo de pantalla en los ajustes de tu sistema.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Elija entre la pantalla de bloqueo por defecto de su dispositivo y una pantalla de bloqueo personalizada con un PIN o contraseña.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Bloqueo del dispositivo", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Bloqueo con PIN", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Tiempo tras el cual la aplicación se bloquea después de ser colocada en segundo plano", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Ocultar contenido", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Oculta el contenido de la aplicación en el selector de aplicaciones y desactiva las capturas de pantalla", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Ocultar el contenido de la aplicación en el selector de aplicaciones", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Demasiados intentos incorrectos", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Toca para desbloquear", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "¿Seguro que quieres cerrar la sesión?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Sí, cerrar la sesión", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Por favor, autentícate para ver tus secretos", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Siguiente", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Establece una nueva contraseña", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Ingresa el PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Establecer nuevo PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Confirmar", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Reescribe tu contraseña", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Reescribe tu PIN", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Verificar identidad", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "No reconocido. Inténtalo de nuevo.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Autenticación exitosa", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Cancelar", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Se necesita autenticación biométrica", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Se necesita autenticación biométrica", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Se necesitan credenciales de dispositivo", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Se necesitan credenciales de dispositivo", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Ir a Ajustes", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "La autenticación biométrica no está configurada en tu dispositivo. Ve a 'Ajustes > Seguridad' para configurar la autenticación biométrica.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "La autenticación biométrica está deshabilitada. Por favor bloquea y desbloquea la pantalla para habilitarla.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "Aceptar", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "Correo electrónico ya registrado.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "Correo electrónico no registrado.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Este correo electrónico ya está en uso", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Correo electrónico cambiado a {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Error de autenticación, por favor inténtalo de nuevo", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "¡Autenticación exitosa!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "La sesión ha expirado", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Clave de recuperación incorrecta", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "La clave de recuperación introducida es incorrecta", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Autenticación de doble factor restablecida con éxito", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Tu código de verificación ha expirado", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Código incorrecto", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Lo sentimos, el código que has introducido es incorrecto", + "@sorryTheCodeYouveEnteredIsIncorrected": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Ajustes de desarrollador", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Endpoint del servidor", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Endpoint no válido", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Lo sentimos, el endpoint introducido no es válido. Por favor, introduce un endpoint válido y vuelve a intentarlo.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Endpoint actualizado con éxito", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_et.arb b/mobile/packages/strings/lib/l10n/arb/strings_et.arb new file mode 100644 index 0000000000..3a6ab9a777 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_et.arb @@ -0,0 +1,306 @@ +{ + "error": "Viga", + "@error": { + "description": "Generic error title" + }, + "ok": "Sobib", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "KKK", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Võtke ühendust klienditoega", + "@contactSupport": { + "description": "Contact support button label" + }, + "cancel": "Katkesta", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Teata veast", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "save": "Salvesta", + "@save": { + "description": "Label for save button" + }, + "send": "Saada", + "@send": { + "description": "Label for send button" + }, + "enterNewEmailHint": "Sisesta oma uus e-posti aadress", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "E-post", + "@email": { + "description": "Email field label" + }, + "verify": "Kinnita", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Vigane e-posti aadress", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Palun sisesta korrektne e-posti aadress.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Palun oota...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Korda salasõna", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Vale salasõna", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Palun proovi uuesti", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Sisesta salasõna", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Sisesta oma salasõna", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "oops": "Vaat kus lops!", + "@oops": { + "description": "Oops error title" + }, + "weakStrength": "Nõrk", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Keskmine", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Tugev", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Kustuta kasutajakonto", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Meil on kahju, et soovid lahkuda. Kas sul tekkis mõni viga või probleem?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Jah, saadan tagasisidet", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Ei, kustuta kasutajakonto", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Kasutajakonto kustutamiseks palun tuvasta end", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "delete": "Kustuta", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Loo uus kasutajakonto", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Salasõna", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Korda salasõna", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Salasõna tugevus: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "termsOfServicesTitle": "Kasutustingimused", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Privaatsusreeglid", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Ma saan aru, et salasõna kaotamisel kaotan ka ligipääsu oma andmetele - minu andmed on ju läbivalt krüptitud.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Krüptimine", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Logi sisse", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Tere tulemast tagasi!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Sisselogdes nõustun kasutustingimustega ja privaatsusreeglitega", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "recreatePasswordTitle": "Loo salasõna uuesti", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "useRecoveryKey": "Kasuta taastevõtit", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Unustasin salasõna", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Muuda e-posti aadressi", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Kinnita e-posti aadress", + "@verifyEmail": { + "description": "Verify email title" + }, + "sendEmail": "Saada e-kiri", + "tryAgain": "Proovi uuesti", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Kontrolli olekut", + "@checkStatus": { + "description": "Check status button label" + }, + "recoverAccount": "Taasta oma kasutajakonto", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Sisesta salasõna", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Muuda salasõna", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Lähtesta salasõna", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Krüptovõtmed", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "howItWorks": "Kuidas see töötab", + "@howItWorks": { + "description": "How it works button label" + }, + "passwordChangedSuccessfully": "Salasõna muutmine õnnestus", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "generatingEncryptionKeysTitle": "Loon krüptovõtmeid...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "recoveryKeyCopiedToClipboard": "Taastevõti on kopeeritud lõikelauale", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Taastevõti", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Kui unustad oma salasõna, siis see krüptovõti on ainus võimalus sinu andmete taastamiseks.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "saveKey": "Salvesta võti", + "@saveKey": { + "description": "Save key button label" + }, + "noRecoveryKeyTitle": "Sul pole taastevõtit?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "enterCodeHint": "Sisesta oma autentimisrakendusest\n6-numbriline kood", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Kas kaotasid oma seadme?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Sisesta oma taastevõti", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Taasta", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Väljalogimine...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "areYouSureYouWantToLogout": "Kas oled kindel, et soovid välja logida?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Jah, logi välja", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "setNewPassword": "Sisesta uus salasõna", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Sisesta PIN-kood", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Määra uus PIN-kood", + "@setNewPin": { + "description": "Set new PIN title" + }, + "reEnterPassword": "Sisesta salasõna uuesti", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "emailAlreadyRegistered": "E-posti aadress on juba registreeritud.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "E-posti aadress pole registreeritud.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "sessionExpired": "Sessioon on aegunud", + "@sessionExpired": { + "description": "Error message when session has expired" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_fa.arb b/mobile/packages/strings/lib/l10n/arb/strings_fa.arb new file mode 100644 index 0000000000..f1f1cc05a9 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_fa.arb @@ -0,0 +1,620 @@ +{ + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "به نظر می‌رسد مشکلی پیش آمده است. لطفا بعد از مدتی دوباره تلاش کنید. اگر همچنان با خطا مواجه می‌شوید، لطفا با تیم پشتیبانی ما ارتباط برقرار کنید.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "خطا", + "@error": { + "description": "Generic error title" + }, + "ok": "تایید", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "سوالات متداول", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "ارتباط با پشتیبانی", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "لاگ‌های خود را ایمیل کنید", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "لطفا لاگ‌ها را به ایمیل زیر ارسال کنید \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "کپی آدرس ایمیل", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "صدور لاگ‌ها", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "لغو", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "گزارش یک اشکال", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "متصل شده به {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "ذخیره", + "@save": { + "description": "Label for save button" + }, + "send": "ارسال", + "@send": { + "description": "Label for send button" + }, + "email": "ایمیل", + "@email": { + "description": "Email field label" + }, + "verify": "تایید", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "آدرس ایمیل نامعتبر است", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "لطفا یک آدرس ایمیل معتبر وارد کنید.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "لطفا صبر کنید...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "تایید رمز عبور", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "رمز عبور نادرست", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "لطفا دوباره تلاش کنید", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "رمز عبور را وارد کنید", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "رمز عبور خود را وارد کنید", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "نشست های فعال", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "اوه", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "مشکلی پیش آمده، لطفا دوباره تلاش کنید", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "این کار شما را از این دستگاه خارج می‌کند!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "با این کار شما از دستگاه زیر خارج می‌شوید:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "خروچ دستگاه؟", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "خروج", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "این دستگاه", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "ایجاد حساب کاربری", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "ضعیف", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "متوسط", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "قوی", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "حذف حساب کاربری", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "از رفتن شما متاسفیم. آیا با مشکلی روبرو هستید؟", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "بله، ارسال بازخورد", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "خیر، حساب کاربری را حذف کن", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "لطفا جهت شروع فرآیند حذف حساب کاربری، اعتبارسنجی کنید", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "تایید حذف حساب کاربری", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "delete": "حذف", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "ایجاد حساب کاربری جدید", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "رمز عبور", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "تایید رمز عبور", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "قدرت رمز عبور: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "از کجا در مورد Ente شنیدی؟ (اختیاری)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "ما نصب برنامه را ردیابی نمی‌کنیم. اگر بگویید کجا ما را پیدا کردید، به ما کمک می‌کند!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "با شرایط استفاده از خدمات و سیاست حفظ حریم خصوصی موافقت می‌کنم", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "شرایط و ضوابط", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "سیاست حفظ حریم خصوصی", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "می‌دانم که اگر رمز عبور خود را گم کنم، از آنجایی که اطلاعات من رمزگذاری سرتاسری شده است، ممکن است اطلاعاتم را از دست بدهم.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "رمزگذاری", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "ورود", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "خوش آمدید!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "با کلیک روی ورود، با شرایط استفاده از خدمات و سیاست حفظ حریم خصوصی موافقت می‌کنم", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "نبود اتصال اینترنت", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "لطفا اتصال اینترنت خود را بررسی کنید و دوباره امتحان کنید.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "تایید ناموفق بود، لطفا مجددا تلاش کنید", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "بازتولید رمز عبور", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "دستگاه فعلی جهت تایید رمز عبور شما به اندازه کافی قدرتمند نیست، اما ما میتوانیم آن را به گونه ای بازتولید کنیم که با همه دستگاه‌ها کار کند.\n\nلطفا با استفاده از کلید بازیابی خود وارد شوید و رمز عبور خود را دوباره ایجاد کنید (در صورت تمایل می‌توانید دوباره از همان رمز عبور استفاده کنید).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "از کلید بازیابی استفاده کنید", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "فراموشی رمز عبور", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "تغییر ایمیل", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "تایید ایمیل", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "ما یک ایمیل به {email} ارسال کرده‌ایم", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "برای تنظیم مجدد رمز عبور، لطفا ابتدا ایمیل خود را تایید کنید.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "لطفا صندوق ورودی (و هرزنامه) خود را برای تایید کامل بررسی کنید", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "برای وارد کردن کد ضربه بزنید", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "ارسال ایمیل", + "resendEmail": "ارسال مجدد ایمیل", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "تأییدیه هنوز در انتظار است", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "نشست منقضی شده است", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "نشست شما منقضی شده. لطفا دوباره وارد شوید.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "تایید کردن پسکی", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "درانتظار تأییدیه...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "دوباره امتحان کنید", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "بررسی وضعیت", + "@checkStatus": { + "description": "Check status button label" + }, + "recoverAccount": "بازیابی حساب کاربری", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "تنظیم پسورد", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "تغییر رمز عبور", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "بازنشانی رمز عبور", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "کلیدهای رمزنگاری", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "رمز عبوری را وارد کنید که بتوانیم از آن برای رمزگذاری اطلاعات شما استفاده کنیم", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "رمز عبور جدیدی را وارد کنید که بتوانیم از آن برای رمزگذاری اطلاعات شما استفاده کنیم", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "howItWorks": "چگونه کار می‌کند", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "در حال تولید کلیدهای رمزگذاری...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "رمز عبور با موفقیت تغییر کرد", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "از دستگاه های دیگر خارج شوید", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherDevices": "از دستگاه های دیگر خارج شوید", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "خارج نشوید", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "در حال تولید کلید‌های رمزگذاری...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "ادامه", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "دستگاه ناامن", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "با عرض پوزش، ما نمی‌توانیم کلیدهای امن را در این دستگاه تولید کنیم.\n\nلطفا از دستگاه دیگری ثبت نام کنید.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "کلید بازیابی به حافظه موقت کپی شد", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "کلید بازیابی", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "اگر رمز عبور خود را فراموش کرده‌اید، این کد تنها راهی است که با آن می‌توانید اطلاعات خود را بازیابی کنید.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "ما این کلید را ذخیره نمی‌کنیم، لطفا این کلید ۲۴ کلمه‌ای را در مکانی امن ذخیره کنید.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "بعداً انجام شود", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "ذخیره کلید", + "@saveKey": { + "description": "Save key button label" + }, + "noRecoveryKeyTitle": "کلید بازیابی ندارید؟", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "احراز هویت دو مرحله‌ای", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "کد تایید ۶ رقمی را از برنامه\nاحراز هویت خود وارد کنید", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "دستگاه را گم کرده‌اید؟", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "کلید بازیابی خود را وارد کنید", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "بازیابی", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "در حال خروج از سیستم...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "فوری", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "قفل برنامه", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "قفل خودکار", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "هیج قبل سیستمی پیدا نشد", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLock": "قفل دستگاه", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "پین قفل", + "@pinLock": { + "description": "PIN lock option title" + }, + "hideContent": "پنهان کردن محتوا", + "@hideContent": { + "description": "Hide content setting title" + }, + "tapToUnlock": "برای باز کردن قفل ضربه بزنید", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "آیا مطمئنید که می‌خواهید خارج شوید؟", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "بله، خروج", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "لطفا جهت دیدن راز های خود احراز هویت کنید", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "بعدی", + "@next": { + "description": "Next button label" + }, + "enterPin": "پین را وارد کنید", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "پین جدید انتخاب کنید", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "تایید", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "رمز عبور را مجدداً وارد کنید", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "پین را مجدداً وارد کنید", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "تایید هویت", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "شناخته نشد. دوباره امتحان کنید.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "موفقیت", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "لغو", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "احراز هویت لازم است", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "بیومتریک لازم است", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "اعتبار دستگاه لازم است", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "اعتبار دستگاه لازم است", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "به تنظیمات بروید", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "iOSOkButton": "تأیید", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "thisEmailIsAlreadyInUse": "این ایمیل درحال استفاده است", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "ایمیل عوض شد به {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "احراز هویت ناموفق بود، لطفا دوباره تلاش کنید", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "احراز هویت موفق آمیز!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "نشست منقضی شده است", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "کلید بازیابی درست نیست", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "کلید بازیابی که وارد کردید درست نیست", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "احراز هویت دو مرحله با موفقیت بازنشانی شد", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "کد تایید شما باطل شد", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "کد اشتباه", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "معظرت میخوام، کدی که شما وارد کردید اشتباه است", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_fi.arb b/mobile/packages/strings/lib/l10n/arb/strings_fi.arb new file mode 100644 index 0000000000..6cebf2c8d6 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_fi.arb @@ -0,0 +1,271 @@ +{ + "error": "Virhe", + "@error": { + "description": "Generic error title" + }, + "ok": "Selvä", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Usein kysyttyä", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Ota yhteyttä käyttötukeen", + "@contactSupport": { + "description": "Contact support button label" + }, + "cancel": "Keskeytä", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Ilmoita virhetoiminnosta", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "save": "Tallenna", + "@save": { + "description": "Label for save button" + }, + "send": "Lähetä", + "@send": { + "description": "Label for send button" + }, + "email": "Sähköposti", + "@email": { + "description": "Email field label" + }, + "verify": "Vahvista", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Epäkelpo sähköpostiosoite", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Syötä kelpoisa sähköpostiosoite.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Odota hetki...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Vahvista salasana", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Salasana on väärin", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Yritä uudestaan", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Syötä salasana", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Syötä salasanasi", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "oops": "Hupsista", + "@oops": { + "description": "Oops error title" + }, + "createAccount": "Luo tili", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Heikko salasana", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Kohtalainen salasana", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Vahva salasana", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Poista tili", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Olemme pahoillamme että lähdet keskuudestamme. Kohtasitko käytössä jonkin ongelman?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Kyllä, lähetä palautetta", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "En, poista tili", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Ole hyvä ja tee todennus käynnistääksesi tilisi poistoprosessin", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "delete": "Poista", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Luo uusi tili", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Salasana", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Vahvista salasana", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "encryption": "Salaus", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Kirjaudu sisään", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Tervetuloa takaisin!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "useRecoveryKey": "Käytä palautusavainta", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Olen unohtanut salasanani", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "vaihda sähköpostiosoitteesi", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Vahvista sähköpostiosoite", + "@verifyEmail": { + "description": "Verify email title" + }, + "sendEmail": "Lähetä sähköpostia", + "passkeyAuthTitle": "Avainkoodin vahvistus", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "recoverAccount": "Palauta tilisi", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Luo salasana", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Vaihda salasana", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Nollaa salasana", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Salausavaimet", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "generatingEncryptionKeys": "Luodaan salausavaimia...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Salasana vaihdettu onnistuneesti", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "generatingEncryptionKeysTitle": "Luodaan salausavaimia...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Jatka", + "@continueLabel": { + "description": "Continue button label" + }, + "recoveryKeyCopiedToClipboard": "Palautusavain kopioitu leikepöydälle", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Palautusavain", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Jos unohdat salasanasi, ainoa tapa palauttaa tietosi on tällä avaimella.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Emme tallenna tätä avainta, ole hyvä ja tallenna tämä 24 sanan avain turvalliseen paikkaan.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Tee tämä myöhemmin", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Tallenna avain", + "@saveKey": { + "description": "Save key button label" + }, + "noRecoveryKeyTitle": "Ei palautusavainta?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Kaksivaiheinen vahvistus", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Syötä 6-merkkinen koodi varmennussovelluksestasi", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Kadonnut laite?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Syötä palautusavaimesi", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Palauta", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Kirjaudutaan ulos...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "yesLogout": "Kyllä, kirjaudu ulos", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "androidBiometricSuccess": "Kirjautuminen onnistui", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Peruuta", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "goToSettings": "Mene asetuksiin", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "sessionExpired": "Istunto on vanheutunut", + "@sessionExpired": { + "description": "Error message when session has expired" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_fr.arb b/mobile/packages/strings/lib/l10n/arb/strings_fr.arb new file mode 100644 index 0000000000..7db985902d --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_fr.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "Impossible de se connecter à Ente, veuillez vérifier vos paramètres réseau et contacter le support si l'erreur persiste.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Impossible de se connecter à Ente, veuillez réessayer après un certain temps. Si l'erreur persiste, veuillez contacter le support.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Il semble qu'une erreur s'est produite. Veuillez réessayer après un certain temps. Si l'erreur persiste, veuillez contacter notre équipe d'assistance.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Erreur", + "@error": { + "description": "Generic error title" + }, + "ok": "Ok", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "FAQ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Contacter le support", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Envoyez vos logs par e-mail", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Envoyez les logs à {toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Copier l’adresse e-mail", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Exporter les journaux", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Annuler", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Signaler un bug", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Connecté à {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Sauvegarder", + "@save": { + "description": "Label for save button" + }, + "send": "Envoyer", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Voulez-vous enregistrer ceci sur votre stockage (dossier Téléchargements par défaut) ou l'envoyer à d'autres applications ?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Voulez-vous enregistrer ceci sur votre stockage (dossier Téléchargements par défaut) ?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Saisissez votre nouvelle adresse email", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "E-mail", + "@email": { + "description": "Email field label" + }, + "verify": "Vérifier", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Adresse e-mail invalide", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Veuillez saisir une adresse e-mail valide.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Veuillez patienter...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Vérifier le mot de passe", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Mot de passe incorrect", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Veuillez réessayer", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Saisissez le mot de passe", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Entrez votre mot de passe", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Sessions actives", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Oups", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Quelque chose s'est mal passé, veuillez recommencer", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Cela vous déconnectera de cet appareil !", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Cela vous déconnectera de l'appareil suivant :", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Quitter la session ?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Quitter", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Cet appareil", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Créer un compte", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Faible", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Modéré", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Fort", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Supprimer le compte", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Nous sommes désolés de vous voir partir. Rencontrez-vous un problème ?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Oui, envoyer un commentaire", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Non, supprimer le compte", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Veuillez vous authentifier pour débuter la suppression du compte", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Confirmer la suppression du compte", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Ce compte est lié à d'autres applications ente, si vous en utilisez une.\n\nVos données téléchargées, dans toutes les applications ente, seront planifiées pour suppression, et votre compte sera définitivement supprimé.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Supprimer", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Créer un nouveau compte", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Mot de passe", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Confirmer le mot de passe", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Force du mot de passe : {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Comment avez-vous entendu parler de Ente? (facultatif)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Nous ne suivons pas les installations d'applications. Il serait utile que vous nous disiez comment vous nous avez trouvés !", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "J'accepte les conditions d'utilisation et la politique de confidentialité", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Conditions", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Politique de confidentialité", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Je comprends que si je perds mon mot de passe, je risque de perdre mes données puisque celles-ci sont chiffrées de bout en bout.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Chiffrement", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Connexion", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Bon retour parmi nous !", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "En cliquant sur \"Connexion\", j'accepte les conditions d'utilisation et la politique de confidentialité", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Aucune connexion internet", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Veuillez vérifier votre connexion internet puis réessayer.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "La vérification a échouée, veuillez réessayer", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Recréer le mot de passe", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "L'appareil actuel n'est pas assez puissant pour vérifier votre mot de passe, donc nous avons besoin de le régénérer une fois d'une manière qu'il fonctionne avec tous les périphériques.\n\nVeuillez vous connecter en utilisant votre clé de récupération et régénérer votre mot de passe (vous pouvez utiliser le même si vous le souhaitez).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Utiliser la clé de récupération", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Mot de passe oublié", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Modifier l'e-mail", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Vérifier l'e-mail", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Nous avons envoyé un mail à {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Pour réinitialiser votre mot de passe, veuillez d'abord vérifier votre e-mail.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Veuillez consulter votre boîte de courriels (et les spam) pour compléter la vérification", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Appuyez pour entrer un code", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Envoyer un e-mail", + "resendEmail": "Renvoyer le courriel", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "La vérification est toujours en attente", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Session expirée", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Votre session a expiré. Veuillez vous reconnecter.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Vérification du code d'accès", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "En attente de vérification...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Réessayer", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Vérifier le statut", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Se connecter avec un code TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Récupérer un compte", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Définir le mot de passe", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Modifier le mot de passe", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Réinitialiser le mot de passe", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Clés de chiffrement", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Entrez un mot de passe que nous pouvons utiliser pour chiffrer vos données", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Entrez un nouveau mot de passe que nous pouvons utiliser pour chiffrer vos données", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Nous ne stockons pas ce mot de passe. Si vous l'oubliez, nous ne pourrons pas déchiffrer vos données", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Comment ça fonctionne", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Génération des clés de chiffrement...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Le mot de passe a été modifié avec succès", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Déconnexion des autres appareils", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Si vous pensez que quelqu'un connaît peut-être votre mot de passe, vous pouvez forcer tous les autres appareils utilisant votre compte à se déconnecter.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Déconnecter les autres appareils", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Ne pas se déconnecter", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Génération des clés de chiffrement...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Continuer", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Appareil non sécurisé", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Désolé, nous n'avons pas pu générer de clés sécurisées sur cet appareil.\n\nVeuillez vous inscrire depuis un autre appareil.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Clé de récupération copiée dans le presse-papiers", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Clé de récupération", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Si vous oubliez votre mot de passe, la seule façon de récupérer vos données sera grâce à cette clé.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Nous ne stockons pas cette clé, veuillez enregistrer cette clé de 24 mots dans un endroit sûr.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Plus tard", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Enregistrer la clé", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Clé de récupération enregistrée dans le dossier Téléchargements !", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Pas de clé de récupération ?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Authentification à deux facteurs", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Entrez le code à 6 chiffres de votre application d'authentification", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Appareil perdu ?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Saisissez votre clé de récupération", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Restaurer", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Deconnexion...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Immédiatement", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Verrouillage d'application", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Verrouillage automatique", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Aucun verrou système trouvé", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Pour activer l'écran de verrouillage, veuillez configurer le code d'accès de l'appareil ou le verrouillage de l'écran dans les paramètres de votre système.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Choisissez entre l'écran de verrouillage par défaut de votre appareil et un écran de verrouillage par code PIN ou mot de passe personnalisé.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Verrouillage de l'appareil", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Verrouillage par code PIN", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Délai après lequel l'application se verrouille une fois qu'elle a été mise en arrière-plan", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Masquer le contenu", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Masque le contenu de l'application sur le menu et désactive les captures d'écran", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Masque le contenu de l'application sur le menu", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Trop de tentatives incorrectes", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Appuyer pour déverrouiller", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Êtes-vous sûr de vouloir vous déconnecter ?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Oui, se déconnecter", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Veuillez vous authentifier pour voir vos souvenirs", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Suivant", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Définir un nouveau mot de passe", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Saisir le code PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Définir un nouveau code PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Confirmer", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Ressaisir le mot de passe", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Ressaisir le code PIN", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Vérifier l’identité", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Non reconnu. Veuillez réessayer.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Parfait", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Annuler", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Authentification requise", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Empreinte digitale requise", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Identifiants de l'appareil requis", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Identifiants de l'appareil requis", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Allez dans les paramètres", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "L'authentification biométrique n'est pas configurée sur votre appareil. Allez dans 'Paramètres > Sécurité' pour l'ajouter.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "L'authentification biométrique est désactivée. Veuillez verrouiller et déverrouiller votre écran pour l'activer.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "Ok", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "E-mail déjà enregistré.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "E-mail non enregistré.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Cette adresse mail est déjà utilisé", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "L'e-mail a été changé en {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "L'authentification a échouée, veuillez réessayer", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Authentification réussie!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Session expirée", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Clé de récupération non valide", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "La clé de récupération que vous avez entrée est incorrecte", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "L'authentification à deux facteurs a été réinitialisée avec succès ", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Votre code de vérification a expiré", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Code non valide", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Le code que vous avez saisi est incorrect", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Paramètres du développeur", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Point de terminaison serveur", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Point de terminaison non valide", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Désolé, le point de terminaison que vous avez entré n'est pas valide. Veuillez en entrer un valide puis réessayez.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Point de terminaison mis à jour avec succès", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_gu.arb b/mobile/packages/strings/lib/l10n/arb/strings_gu.arb new file mode 100644 index 0000000000..b62a73a638 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_gu.arb @@ -0,0 +1,90 @@ +{ + "ok": "સારું", + "@ok": { + "description": "Generic OK button label" + }, + "contactSupport": "સહાયતા માટે સંપર્ક કરો", + "@contactSupport": { + "description": "Contact support button label" + }, + "cancel": "રદ કરો", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "બગની જાણ કરો", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "email": "ઇમેઇલ", + "@email": { + "description": "Email field label" + }, + "pleaseWait": "કૃપા કરીને રાહ જુવો...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "પાસવર્ડ ચકાસો", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "ખોટો પાસવર્ડ", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "enterPassword": "પાસવર્ડ દાખલ કરો", + "@enterPassword": { + "description": "Enter password field label" + }, + "delete": "કાઢી નાખો", + "@delete": { + "description": "Delete button label" + }, + "welcomeBack": "ફરી તમારુ સ્વાગત છે!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "useRecoveryKey": "પુનઃપ્રાપ્તિ કીનો ઉપયોગ કરો", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "changeEmail": "ઈ - મેઈલ બદલો", + "@changeEmail": { + "description": "Change email button label" + }, + "generatingEncryptionKeysTitle": "એન્ક્રિપ્શન ચાવીઓ જનરેટ કરી રહ્યાં છીએ...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "recoveryKey": "પુનઃપ્રાપ્તિ ચાવી", + "@recoveryKey": { + "description": "Recovery key label" + }, + "loggingOut": "લૉગ આઉટ થઈ રહ્યું છે...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "androidBiometricSuccess": "સફળતા", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "રદ કરો", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "પ્રમાણીકરણ જરૂરી છે", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "goToSettings": "સેટિંગ માં જાઓ", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "iOSOkButton": "બરાબર", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "sessionExpired": "સત્ર સમાપ્ત થયુ", + "@sessionExpired": { + "description": "Error message when session has expired" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_he.arb b/mobile/packages/strings/lib/l10n/arb/strings_he.arb new file mode 100644 index 0000000000..fc6e0fbcbd --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_he.arb @@ -0,0 +1,478 @@ +{ + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "נראה שמשהו לא פעל כשורה. אנא נסה שוב אחרי כמה זמן. אם הבעיה ממשיכה, אנא צור קשר עם צוות התמיכה שלנו.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "שגיאה", + "@error": { + "description": "Generic error title" + }, + "ok": "אוקיי", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "שאלות נפוצות", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "צור קשר עם התמיכה", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "שלח באימייל את הלוגים שלך", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "אנא שלחו את הלוגים האלו ל-{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "העתק כתובת דוא\"ל", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "ייצוא לוגים", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "בטל", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "דווח על באג", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "save": "שמור", + "@save": { + "description": "Label for save button" + }, + "send": "שלח", + "@send": { + "description": "Label for send button" + }, + "email": "דוא\"ל", + "@email": { + "description": "Email field label" + }, + "verify": "אמת", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "כתובת דוא״ל לא תקינה", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "אנא הכנס כתובת דוא\"ל תקינה.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "אנא המתן...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "אמת סיסמא", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "סיסמא לא נכונה", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "אנא נסה שנית", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "הזן את הסיסמה", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "הכנס סיסמא", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "חיבורים פעילים", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "אופס", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "משהו השתבש, אנא נסה שנית", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "זה ינתק אותך במכשיר זה!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "זה ינתק אותך מהמכשיר הבא:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "סיים חיבור?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "סיים", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "מכשיר זה", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "צור חשבון", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "חלש", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "מתון", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "חזק", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "מחק חשבון", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "אנו מצטערים שאתה עוזב. האם יש בעיות שאתה חווה?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "כן, שלח משוב", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "לא, מחק את החשבון", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "אנא אמת על מנת להתחיל את מחיקת החשבון שלך", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "אישור מחיקת חשבון", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "delete": "למחוק", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "צור חשבון חדש", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "סיסמא", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "אמת סיסמא", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "חוזק הסיסמא: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "signUpTerms": "אני מסכים לתנאי שירות ולמדיניות הפרטיות", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "תנאים", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "מדיניות פרטיות", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "אני מבין שאם אאבד את הסיסמא, אני עלול לאבד את המידע שלי מכיוון שהמידע שלי מוצפן מקצה אל קצה.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "הצפנה", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "התחבר", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "ברוך שובך!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "על ידי לחיצה על התחברות, אני מסכים לתנאי שירות ולמדיניות הפרטיות", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "אין חיבור לאינטרנט", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "אנא בדוק את חיבור האינטרנט שלך ונסה שוב.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "אימות נכשל, אנא נסה שנית", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "צור סיסמא מחדש", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "המכשיר הנוכחי אינו חזק מספיק כדי לאמת את הסיסמא שלך, אבל אנחנו יכולים ליצור מחדש בצורה שתעבוד עם כל המכשירים.\n\nאנא התחבר בעזרת המפתח שחזור שלך וצור מחדש את הסיסמא שלך (אתה יכול להשתמש באותה אחת אם אתה רוצה).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "השתמש במפתח שחזור", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "שכחתי סיסמה", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "שנה דוא\"ל", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "אימות דוא\"ל", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "שלחנו דוא\"ל ל{email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "כדי לאפס את הסיסמא שלך, אנא אמת את האימייל שלך קודם.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "אנא בדוק את תיבת הדואר שלך (והספאם) כדי להשלים את האימות", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "הקש כדי להזין את הקוד", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "שלח אימייל", + "resendEmail": "שלח דוא\"ל מחדש", + "@resendEmail": { + "description": "Resend email button label" + }, + "tryAgain": "נסה שוב", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "בדוק סטטוס", + "@checkStatus": { + "description": "Check status button label" + }, + "recoverAccount": "שחזר חשבון", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "הגדר סיסמא", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "שנה סיסמה", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "איפוס סיסמה", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "מפתחות ההצפנה", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "הזן סיסמא כדי שנוכל להצפין את המידע שלך", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "הכנס סיסמא חדשה כדי שנוכל להצפין את המידע שלך", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "אנחנו לא שומרים את הסיסמא הזו, לכן אם אתה שוכח אותה, אנחנו לא יכולים לפענח את המידע שלך", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "איך זה עובד", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "יוצר מפתחות הצפנה...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "הסיסמה הוחלפה בהצלחה", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "generatingEncryptionKeysTitle": "יוצר מפתחות הצפנה...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "המשך", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "מכשיר בלתי מאובטח", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "אנחנו מצטערים, לא הצלחנו ליצור מפתחות מאובטחים על מכשיר זה.\n\nאנא הירשם ממכשיר אחר.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "מפתח השחזור הועתק ללוח", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "מפתח שחזור", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "אם אתה שוכח את הסיסמא שלך, הדרך היחידה שתוכל לשחזר את המידע שלך היא עם המפתח הזה.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "אנחנו לא מאחסנים את המפתח הזה, אנא שמור את המפתח 24 מילים הזה במקום בטוח.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "עשה זאת מאוחר יותר", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "שמור מפתח", + "@saveKey": { + "description": "Save key button label" + }, + "noRecoveryKeyTitle": "אין מפתח שחזור?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "אימות דו-שלבי", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "הכנס את הקוד בעל 6 ספרות מתוך\nאפליקציית האימות שלך", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "איבדת את המכשיר?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "הזן את מפתח השחזור שלך", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "שחזר", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "מתנתק...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "areYouSureYouWantToLogout": "האם את/ה בטוח/ה שאת/ה רוצה לצאת?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "כן, התנתק", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "confirm": "אשר", + "@confirm": { + "description": "Confirm button label" + }, + "thisEmailIsAlreadyInUse": "כתובת דואר אלקטרוני זאת כבר נמצאת בשימוש", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "אימייל שונה ל-{newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "אימות נכשל, אנא נסה שוב", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "אימות הצליח!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "זמן החיבור הסתיים", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "מפתח שחזור שגוי", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "המפתח שחזור שהזנת שגוי", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "אימות דו-שלבי אופס בהצלחה", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "קוד האימות שלך פג תוקף", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "קוד שגוי", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "אנו מתנצלים, אבל הקוד שהזנת איננו נכון", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_hi.arb b/mobile/packages/strings/lib/l10n/arb/strings_hi.arb new file mode 100644 index 0000000000..8bac8f273f --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_hi.arb @@ -0,0 +1,102 @@ +{ + "ok": "ठीक है", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "अक्सर किये गए सवाल", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "सपोर्ट टीम से संपर्क करें", + "@contactSupport": { + "description": "Contact support button label" + }, + "cancel": "रद्द करें", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "बग रिपोर्ट करें", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "verify": "सत्यापित करें", + "@verify": { + "description": "Verify button label" + }, + "pleaseWait": "कृपया प्रतीक्षा करें...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "पासवर्ड सत्यापित करें", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "ग़लत पासवर्ड", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "कृपया पुन: प्रयास करें", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterYourPasswordHint": "अपना पासवर्ड दर्ज करें", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "oops": "ओह", + "@oops": { + "description": "Oops error title" + }, + "delete": "हटाएं", + "@delete": { + "description": "Delete button label" + }, + "welcomeBack": "आपका पुनः स्वागत है!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "useRecoveryKey": "रिकवरी कुंजी का उपयोग करें", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "पासवर्ड भूल गए", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "ईमेल बदलें", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "ईमेल सत्यापित करें", + "@verifyEmail": { + "description": "Verify email title" + }, + "generatingEncryptionKeysTitle": "एन्क्रिप्शन कुंजियाँ उत्पन्न हो रही हैं...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "recoveryKey": "पुनःप्राप्ति कुंजी", + "@recoveryKey": { + "description": "Recovery key label" + }, + "twoFactorAuthTitle": "दो-चरणीय प्रमाणीकरण |", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "loggingOut": "लॉग आउट हो रहा है...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "emailAlreadyRegistered": "ईमेल पहले से ही पंजीकृत है।", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "ईमेल पंजीकृत नहीं है।", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "sessionExpired": "सत्र की अवधि समाप्त", + "@sessionExpired": { + "description": "Error message when session has expired" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_hu.arb b/mobile/packages/strings/lib/l10n/arb/strings_hu.arb new file mode 100644 index 0000000000..0bdbdaaeed --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_hu.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "Nem lehet csatlakozni az Ente-hez. Kérjük, ellenőrizze a hálózati beállításokat, és ha a hiba továbbra is fennáll, forduljon az ügyfélszolgálathoz.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Nem lehet csatlakozni az Ente-hez, próbálja újra egy idő után. Ha a hiba továbbra is fennáll, forduljon az ügyfélszolgálathoz.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Úgy tűnik, valami hiba történt. Kérjük, próbálja újra egy idő után. Ha a hiba továbbra is fennáll, forduljon ügyfélszolgálatunkhoz.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Hiba", + "@error": { + "description": "Generic error title" + }, + "ok": "OK", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "GY. I. K.", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Lépj kapcsolatba az Ügyfélszolgálattal", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "E-mailben küldje el naplóit", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Kérjük, küldje el a naplókat erre az e-mail címre\n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "E-mail cím másolása", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Naplófájlok exportálása", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Mégse", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Hiba bejelentése", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Csatlakozva a következőhöz: {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Mentés", + "@save": { + "description": "Label for save button" + }, + "send": "Küldés", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "El szeretné menteni ezt a tárhelyére (alapértelmezés szerint a Letöltések mappába), vagy elküldi más alkalmazásoknak?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "El szeretné menteni ezt a tárhelyére (alapértelmezés szerint a Letöltések mappába)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Add meg az új e-mail címed", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "E-mail", + "@email": { + "description": "Email field label" + }, + "verify": "Hitelesítés", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Érvénytelen e-mail cím", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Kérjük, adjon meg egy érvényes e-mail címet.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Kérem várjon...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Jelszó megerősítése", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Érvénytelen jelszó", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Kérjük, próbálja meg újra", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Adja meg a jelszót", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Adja meg a jelszavát", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Aktív munkamenetek", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Hoppá", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Hiba történt. Kérjük, próbálkozz újra", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Ezzel kijelentkezik erről az eszközről!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Ezzel kijelentkezik a következő eszközről:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Megszakítja a munkamenetet?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Befejezés", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Ez az eszköz", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Felhasználó létrehozás", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Gyenge", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Mérsékelt", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Erős", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Fiók törlése", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Szomorúan tapasztaljuk. Problémába ütköztél?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Igen, visszajelzés küldése", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Fiók végleges törlése", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Kérjük, hitelesítse magát a fiók törlésének kezdeményezéséhez", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Fiók törlésének megerősítése", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Ez a fiók össze van kapcsolva más Ente-alkalmazásokkal, ha használ ilyet.\n\nA feltöltött adataid törlését ütemezzük az összes Ente alkalmazásban, és a fiókod véglegesen törlésre kerül.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Törlés", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Új fiók létrehozása", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Jelszó", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Jelszó megerősítése", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Jelszó erőssége: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Honnan hallottál Ente-ről? (opcionális)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Nem követjük nyomon az alkalmazástelepítéseket. Segítene, ha elmondaná, hol talált ránk!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Elfogadom az szolgáltatási feltételeket és az adatvédelmi irányelveket", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Használati feltételek", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Adatvédelmi irányelvek", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Tudomásul veszem, hogy ha elveszítem a jelszavamat, elveszíthetem az adataimat, mivel adataim végponttól végpontig titkosítva vannak.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Titkosítás", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Bejelentkezés", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Köszöntjük ismét!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "A bejelentkezés gombra kattintva elfogadom az szolgáltatási feltételeket és az adatvédelmi irányelveket", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Nincs internet kapcsolat", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Kérjük, ellenőrizze az internetkapcsolatát, és próbálja meg újra.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Az ellenőrzés sikertelen, próbálja újra", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Új jelszó létrehozása", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "A jelenlegi eszköz nem elég erős a jelszavának ellenőrzéséhez, de újra tudjuk úgy generálni, hogy az minden eszközzel működjön.\n\nKérjük, jelentkezzen be helyreállítási kulcsával, és állítsa be újra jelszavát (ha szeretné, újra használhatja ugyanazt).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Helyreállítási kulcs használata", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Elfelejtett jelszó", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "E-mail cím módosítása", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "E-mail cím megerősítése", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "E-mailt küldtünk a következő címre: {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Jelszava visszaállításához először igazolja e-mail-címét.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Kérjük, ellenőrizze beérkező leveleit (és spam mappát) az ellenőrzés befejezéséhez", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Koppintson a kód beírásához", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "E-mail küldése", + "resendEmail": "E-mail újraküldése", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Az ellenőrzés még függőben van", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Lejárt a munkamenet", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "A munkameneted lejárt. Kérem lépjen be újra.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Álkulcs megerősítése", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Várakozás az ellenőrzésre...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Próbáld újra", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Állapot ellenőrzése", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Bejelentkezés TOTP-vel", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Fiók visszaállítása", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Jelszó beállítás", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Jelszó módosítás", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Jelszó visszaállítás", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Titkosító kulcsok", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Adjon meg egy jelszót, amellyel titkosíthatjuk adatait", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Adjon meg egy új jelszót, amellyel titkosíthatjuk adatait", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Ezt a jelszót nem tároljuk, így ha elfelejti, nem tudjuk visszafejteni adatait", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Hogyan működik", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Titkosító kulcsok generálása...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "A jelszó sikeresen meg lett változtatva", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Jelentkezzen ki más eszközökről", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Ha úgy gondolja, hogy valaki ismeri jelszavát, kényszerítheti a fiókját használó összes többi eszközt a kijelentkezésre.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Jelentkezzen ki a többi eszközről", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Ne jelentkezzen ki", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Titkosítási kulcs generálása...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Folytatás", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Nem biztonságos eszköz", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Sajnáljuk, nem tudtunk biztonságos kulcsokat generálni ezen az eszközön.\n\nkérjük, regisztráljon egy másik eszközről.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "A helyreállítási kulcs a vágólapra másolva", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Visszaállítási kulcs", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Ha elfelejti jelszavát, csak ezzel a kulccsal tudja visszaállítani adatait.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Ezt a kulcsot nem tároljuk, kérjük, őrizze meg ezt a 24 szavas kulcsot egy biztonságos helyen.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Később", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Mentés", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "A helyreállítási kulcs a Letöltések mappába mentve!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Nincs helyreállítási kulcs?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Kétlépcsős hitelesítés (2FA)", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Írja be a 6 számjegyű kódot a hitelesítő alkalmazásból", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Elveszett a készüléked?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Visszaállító kód megadása", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Visszaállít", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Kijelentkezés...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Azonnal", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Alkalmazások zárolása", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Automatikus lezárás", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Nem található rendszerzár", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Az eszközzár engedélyezéséhez állítsa be az eszköz jelszavát vagy a zárképernyőt a rendszerbeállításokban.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Válasszon az eszköz alapértelmezett zárolási képernyője és a PIN-kóddal vagy jelszóval rendelkező egyéni zárolási képernyő között.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Eszköz lezárás", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "PIN feloldás", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Az az idő, amely elteltével az alkalmazás zárolásra kerül, miután a háttérbe került", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Tartalom elrejtése", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Elrejti az alkalmazás tartalmát az alkalmazásváltóban, és letiltja a képernyőképeket", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Elrejti az alkalmazás tartalmát az alkalmazásváltóban", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Túl sok helytelen próbálkozás", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Koppintson a feloldáshoz", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Biztos benne, hogy kijelentkezik?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Igen, kijelentkezés", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "A titkos kulcsok megtekintéséhez hitelesítse magát", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Következő", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Új jelszó beállítása", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "PIN kód megadása", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Új PIN kód beállítása", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Megerősítés", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Írja be újra a jelszót", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Írja be újra a PIN-kódot", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Személyazonosság ellenőrzése", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Nem felismerhető. Próbáld újra.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Sikeres", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Mégse", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Hitelesítés szükséges", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biometria szükséges", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Az eszköz hitelesítő adatai szükségesek", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Az eszköz hitelesítő adatai szükségesek", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Beállítások megnyitása", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "A biometrikus hitelesítés nincs beállítva az eszközön. A biometrikus hitelesítés hozzáadásához lépjen a 'Beállítások > Biztonság' menüpontra.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "A biometrikus hitelesítés ki van kapcsolva. Az engedélyezéséhez zárja le és oldja fel a képernyőt.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "Ez az e-mai cím már regisztrálva van.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "Ez az e-mail cím nincs regisztrálva.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Ez az e-mail már használatban van", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Az e-mail cím módosítva erre: {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "A hitelesítés sikertelen, próbálja újra", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Sikeres hitelesítés!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "A munkamenet lejárt", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Helytelen helyreállítási kulcs", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "A megadott helyreállítási kulcs helytelen", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "A kétfaktoros hitelesítés visszaállítása sikeres", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Ez az ellenőrző kód lejárt", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Helytelen kód", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Sajnáljuk, a megadott kód helytelen", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Fejlesztői beállítások", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Szerver végpont", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Érvénytelen végpont", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Sajnáljuk, a megadott végpont érvénytelen. Adjon meg egy érvényes végpontot, és próbálja újra.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "A végpont sikeresen frissítve", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_id.arb b/mobile/packages/strings/lib/l10n/arb/strings_id.arb new file mode 100644 index 0000000000..22c203c0f5 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_id.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "Tidak dapat terhubung ke Ente. Mohon periksa kembali koneksi internet Anda dan hubungi tim bantuan kami jika galat masih ada.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Sepertinya ada yang salah. Mohon coba lagi setelah beberapa waktu. Jika galat masih ada, Anda dapat menghubungi tim bantuan kami.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Sepertinya ada yang salah. Mohon coba lagi setelah beberapa waktu. Jika galat masih ada, Anda dapat menghubungi tim bantuan kami.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Kesalahan", + "@error": { + "description": "Generic error title" + }, + "ok": "Oke", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Pertanyaan yang sering ditanyakan", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Hubungi dukungan", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Kirimkan log Anda melalui surel", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Mohon kirim log ke {toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Salin alamat surel", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Ekspor log", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Batal", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Laporkan bug", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Terhubung ke {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Simpan", + "@save": { + "description": "Label for save button" + }, + "send": "Kirim", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Anda ingin menyimpan kode ke penyimpanan Anda (folder pilihan bawaan adalah folder Downloads) atau Anda ingin kirimkan ke aplikasi lain?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Anda ingin menyimpan kode ke penyimpanan Anda (folder pilihan bawaan adalah folder Downloads)", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Masukkan alamat email baru anda", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "Email", + "@email": { + "description": "Email field label" + }, + "verify": "Verifikasi", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Alamat email tidak valid", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Harap masukkan alamat email yang valid.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Mohon tunggu...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Verifikasi kata sandi", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Kata sandi salah", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Harap coba lagi", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Masukkan kata sandi", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Masukkan kata sandi Anda", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Sesi aktif", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Ups", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Ada yang salah. Mohon coba kembali", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Langkah ini akan mengeluarkan Anda dari gawai ini!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Langkah ini akan mengeluarkan Anda dari gawai berikut:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Akhiri sesi?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Akhiri", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Gawai ini", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Buat akun", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Lemah", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Sedang", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Kuat", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Hapus akun", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Kami akan merasa kehilangan Anda. Apakah Anda menghadapi masalah?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Ya, kirim umpan balik", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Tidak, hapus akun", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Harap autentikasi untuk memulai penghapusan akun", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Konfirmasikan penghapusan akun", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Akun ini terhubung dengan aplikasi Ente yang lain (jika Anda pakai).\n\nData yang Anda unggah di seluruh aplikasi Ente akan dijadwalkan untuk dihapus. Akun Anda juga akan dihapus secara permanen.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Hapus", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Buat akun baru", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Kata Sandi", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Konfirmasi kata sandi", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Tingkat kekuatan kata sandi: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Dari mana Anda menemukan Ente? (opsional)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Kami tidak melacak penginstalan aplikasi kami. Akan sangat membantu kami bila Anda memberitahu kami dari mana Anda mengetahui Ente!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Saya menyetujui syarat dan ketentuan serta kebijakan privasi Ente", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Ketentuan", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Kebijakan Privasi", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Saya mengerti bahwa jika saya lupa kata sandi saya, data saya dapat hilang karena data saya terenkripsi secara end-to-end.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Enkripsi", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Masuk akun", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Selamat datang kembali!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Dengan menekan masuk akun, saya menyetujui syarat dan ketentuan serta kebijakan privasi Ente", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Tiada koneksi internet", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Mohon periksa koneksi internet Anda dan coba kembali.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Gagal memverifikasi. Mohon coba lagi", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Membuat kembali kata sandi", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Gawai Anda saat ini tidak dapat memverifikasi kata sandi Anda. Namun, kami dapat membuat ulang dengan cara yang dapat digunakan pada semua gawai.\n\nMohon masuk log dengan kunci pemulihan dan buat ulang kata sandi Anda (kata sandi yang sama diperbolehkan).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Gunakan kunci pemulihan", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Lupa kata sandi", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Ubah alamat email", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Verifikasi email", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Kami telah mengirimkan sebuah posel ke {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Untuk mengatur ulang kata sandi, mohon verifikasi surel Anda terlebih dahulu.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Mohon cek", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Ketuk untuk memasukkan kode", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Kirim surel", + "resendEmail": "Kirim ulang surel", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Verifikasi tertunda", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Sesi sudah berakhir", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Sesi Anda sudah berakhir. Mohon masuk log kembali.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Verifikasi passkey", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Menantikan verifikasi...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Coba lagi", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Periksa status", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Masuk menggunakan TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Pulihkan akun", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Atur kata sandi", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Ubah kata sandi", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Atur ulang kata sandi", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Kunci enkripsi", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Masukkan kata sandi yang dapat kami gunakan untuk mengenkripsi data Anda", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Masukkan kata sandi baru yang dapat kami gunakan untuk mengenkripsi data Anda", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Kami tidak menyimpan kata sandi Anda. Jika Anda lupa, kami tidak dapat mendekripsi data Anda", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Cara kerjanya", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Sedang membuat kunci enkripsi...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Kata sandi sukses diubah", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Keluar dari gawai yang lain", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Jika Anda pikir seseorang mungkin mengetahui kata sandi Anda, Anda dapat mengeluarkan akun Anda pada semua gawai", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Keluar akun pada gawai yang lain", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Jangan keluar", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Sedang membuat kunci enkripsi...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Lanjutkan", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Perangkat tidak aman", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Maaf, kami tidak dapat membuat kunci yang aman pada perangkat ini.\n\nHarap mendaftar dengan perangkat lain.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Kunci pemulihan disalin ke papan klip", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Kunci pemulihan", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Jika Anda lupa kata sandi, satu-satunya cara memulihkan data Anda adalah dengan kunci ini.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Kami tidak menyimpan kunci ini, jadi harap simpan kunci yang berisi 24 kata ini dengan aman.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Lakukan lain kali", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Simpan kunci", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Kunci pemulihan sudah tersimpan di folder 'Downloads'!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Tidak punya kunci pemulihan?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Autentikasi dua langkah", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Masukkan kode 6 digit dari aplikasi autentikator Anda", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Perangkat hilang?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Masukkan kunci pemulihan Anda", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Pulihkan", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Mengeluarkan akun...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Segera", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Kunci aplikasi", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Kunci otomatis", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Tidak ditemukan kunci sistem", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Pasang kunci sandi atau kunci layar pada pengaturan sistem untuk menyalakan Pengunci Gawai.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Pilih layar kunci bawaan gawai Anda ATAU layar kunci kustom dengan PIN atau kata sandi.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Kunci perangkat", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "PIN", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Durasi waktu aplikasi akan terkunci setelah aplikasi ditutup", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Sembunyikan isi", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Menyembunyikan konten aplikasi di pemilih aplikasi dan menonaktifkan tangkapan layar", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Menyembunyikan konten aplikasi di pemilih aplikasi", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Terlalu banyak percobaan yang salah", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Ketuk untuk membuka", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Anda yakin ingin keluar dari akun ini?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Ya, keluar akun", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Harap lakukan autentikasi untuk melihat rahasia Anda", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Selanjutnya", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Pasang kata sandi baru", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Masukkan PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Pasang PIN yang baru", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Konfirmasikan", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Masukkan kembali kata sandi", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Masukkan kembali PIN", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Verifikasikan identitas Anda", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Tidak dikenal. Coba lagi.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Sukses", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Batalkan", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Autentikasi diperlukan", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biometrik diperlukan", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Kredensial perangkat diperlukan", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Kredensial perangkat diperlukan", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Pergi ke pengaturan", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Tidak ada autentikasi biometrik pada gawai Anda. Buka 'Pengaturan > Keamanan' untuk menambahkan autentikasi biometrik pada gawai Anda.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Autentikasi biometrik dimatikan. Kunci dan buka layar Anda untuk menyalakan autentikasi biometrik.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "Oke", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "Email sudah terdaftar.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "Email belum terdaftar.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Surel ini sudah dipakai!", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Surel sudah diganti menjadi {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Gagal mengautentikasi. Mohon coba lagi", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Sukses mengautentikasi!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Sesi berakhir", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Kunci pemulihan takbenar", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Kunci pemulihan yang Anda masukkan takbenar", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Autentikasi dwifaktor sukses diatur ulang", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Kode verifikasi Anda telah kedaluwarsa", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Kode takbenar", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Maaf, kode yang Anda masukkan takbenar", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Pengaturan Pengembang", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Peladen endpoint", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Endpoint takvalid", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Maaf, endpoint yang Anda masukkan takvalid. Mohon masukkan endpoint yang valid, lalu coba kembali.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Endpoint berhasil diubah", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_it.arb b/mobile/packages/strings/lib/l10n/arb/strings_it.arb new file mode 100644 index 0000000000..ec8fd65de2 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_it.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "Impossibile connettersi a Ente, controlla le impostazioni di rete e contatta l'assistenza se l'errore persiste.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Impossibile connettersi a Ente, riprova tra un po' di tempo. Se l'errore persiste, contatta l'assistenza.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Sembra che qualcosa sia andato storto. Riprova tra un po'. Se l'errore persiste, contatta il nostro team di supporto.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Errore", + "@error": { + "description": "Generic error title" + }, + "ok": "OK", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "FAQ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Contatta il supporto", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Invia una mail dei tuoi log", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Invia i log a \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Copia indirizzo email", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Esporta log", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Annulla", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Segnala un problema", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Connesso a {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Salva", + "@save": { + "description": "Label for save button" + }, + "send": "Invia", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Vuoi salvarlo nel tuo spazio di archiviazione (cartella Download per impostazione predefinita) o inviarlo ad altre applicazioni?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Vuoi salvarlo nel tuo spazio di archiviazione (cartella Download per impostazione predefinita)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Inserisci il tuo nuovo indirizzo email", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "Email", + "@email": { + "description": "Email field label" + }, + "verify": "Verifica", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Indirizzo email non valido", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Inserisci un indirizzo email valido.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Attendere prego...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Verifica la password", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Password sbagliata", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Per favore riprova", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Inserisci la password", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Inserisci la tua password", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Sessioni attive", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Oops", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Qualcosa è andato storto, per favore riprova", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Questo ti disconnetterà da questo dispositivo!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Questo ti disconnetterà dal seguente dispositivo:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Termina sessione?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Termina", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Questo dispositivo", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Crea account", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Debole", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Mediocre", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Forte", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Elimina account", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Ci dispiace vederti andare via. Stai avendo qualche problema?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Sì, invia un feedback", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "No, elimina l'account", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Si prega di autenticarsi per avviare l'eliminazione dell'account", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Conferma l'eliminazione dell'account", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Questo account è collegato ad altre app di Ente, se ne utilizzi.\n\nI tuoi dati caricati, su tutte le app di Ente, saranno pianificati per la cancellazione e il tuo account verrà eliminato definitivamente.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Cancella", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Crea un nuovo account", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Password", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Conferma la password", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Forza password: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Dove hai sentito parlare di Ente? (opzionale)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Non teniamo traccia delle installazioni dell'app. Sarebbe utile se ci dicessi dove ci hai trovato!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Accetto i termini di servizio e la politica sulla privacy", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Termini", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Politica sulla Privacy", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Comprendo che se perdo la password, potrei perdere l'accesso ai miei dati poiché i miei dati sono criptati end-to-end.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Crittografia", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Accedi", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Bentornato!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Cliccando sul pulsante Accedi, accetto i termini di servizio e la politica sulla privacy", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Nessuna connessione internet", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Si prega di verificare la propria connessione Internet e riprovare.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Verifica fallita, per favore riprova", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Ricrea password", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Il dispositivo attuale non è abbastanza potente per verificare la tua password, ma la possiamo rigenerare in un modo che funzioni su tutti i dispositivi.\n\nEffettua il login utilizzando la tua chiave di recupero e rigenera la tua password (puoi utilizzare nuovamente la stessa se vuoi).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Utilizza un codice di recupero", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Password dimenticata", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Modifica email", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Verifica email", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Abbiamo inviato una mail a {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Per reimpostare la tua password, prima verifica la tua email.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Per favore, controlla la tua casella di posta (e lo spam) per completare la verifica", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Tocca per inserire il codice", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Invia email", + "resendEmail": "Rinvia email", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "La verifica è ancora in corso", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Sessione scaduta", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "La sessione è scaduta. Si prega di accedere nuovamente.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Verifica della passkey", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "In attesa di verifica...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Riprova", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Verifica stato", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Login con TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Recupera account", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Imposta password", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Modifica password", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Reimposta password", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Chiavi di crittografia", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Inserisci una password per criptare i tuoi dati", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Inserisci una nuova password per criptare i tuoi dati", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Non memorizziamo questa password, quindi se te la dimentichi, non possiamo decriptare i tuoi dati", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Come funziona", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Generazione delle chiavi di crittografia...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Password modificata con successo", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Esci dagli altri dispositivi", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Se pensi che qualcuno possa conoscere la tua password, puoi forzare tutti gli altri dispositivi che usano il tuo account ad uscire.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Esci dagli altri dispositivi", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Non uscire", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Generazione delle chiavi di crittografia...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Continua", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Dispositivo non sicuro", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Siamo spiacenti, non possiamo generare le chiavi sicure su questo dispositivo.\n\nPer favore, accedi da un altro dispositivo.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Chiave di recupero copiata negli appunti", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Chiave di recupero", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Se dimentichi la password, l'unico modo per recuperare i tuoi dati è con questa chiave.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Non memorizziamo questa chiave, per favore salva questa chiave di 24 parole in un posto sicuro.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Fallo più tardi", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Salva chiave", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Chiave di recupero salvata nella cartella Download!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Nessuna chiave di recupero?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Autenticazione a due fattori", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Inserisci il codice di 6 cifre dalla tua app di autenticazione", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Dispositivo perso?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Inserisci la tua chiave di recupero", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Recupera", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Disconnessione...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Immediatamente", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Blocco app", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Blocco automatico", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Nessun blocco di sistema trovato", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Per attivare il blocco del dispositivo, impostare il codice di accesso o il blocco dello schermo nelle impostazioni del sistema.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Scegli tra la schermata di blocco predefinita del dispositivo e una schermata di blocco personalizzata con PIN o password.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Blocco del dispositivo", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Blocco con PIN", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Tempo dopo il quale l'applicazione si blocca dopo essere stata messa in background", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Nascondi il contenuto", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Nasconde il contenuto nel selettore delle app e disabilita gli screenshot", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Nasconde il contenuto nel selettore delle app", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Troppi tentativi errati", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Tocca per sbloccare", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Sei sicuro di volerti disconnettere?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Si, effettua la disconnessione", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Autenticati per visualizzare i tuoi segreti", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Successivo", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Imposta una nuova password", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Inserisci PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Imposta un nuovo PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Conferma", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Reinserisci la password", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Reinserisci il PIN", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Verifica l'identità", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Non riconosciuto. Riprova.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Successo", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Annulla", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Autenticazione necessaria", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Autenticazione biometrica richiesta", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Credenziali del dispositivo richieste", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Credenziali del dispositivo richieste", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Vai alle impostazioni", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "L'autenticazione biometrica non è impostata sul tuo dispositivo. Vai a 'Impostazioni > Sicurezza' per impostarla.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "L'autenticazione biometrica è disabilitata. Blocca e sblocca lo schermo per abilitarla.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "Email già registrata.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "Email non registrata.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Questa email é già in uso", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Email modificata in {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Autenticazione non riuscita, riprova", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Autenticazione riuscita!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Sessione scaduta", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Chiave di recupero errata", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "La chiave di recupero che hai inserito non è corretta", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Autenticazione a due fattori ripristinata con successo", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Il tuo codice di verifica è scaduto", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Codice errato", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Spiacenti, il codice che hai inserito non è corretto", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Impostazioni sviluppatore", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Endpoint del server", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Endpoint invalido", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Spiacenti, l'endpoint inserito non è valido. Inserisci un endpoint valido e riprova.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Endpoint aggiornato con successo", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_ja.arb b/mobile/packages/strings/lib/l10n/arb/strings_ja.arb new file mode 100644 index 0000000000..cb42550abb --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_ja.arb @@ -0,0 +1,720 @@ +{ + "networkHostLookUpErr": "Enteに接続できませんでした。ネットワーク設定を確認し、エラーが解決しない場合はサポートにお問い合わせください。", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Enteに接続できませんでした。しばらくしてから再試行してください。エラーが解決しない場合は、サポートにお問い合わせください。", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "問題が発生したようです。しばらくしてから再試行してください。エラーが解決しない場合は、サポートチームにお問い合わせください。", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "エラー", + "@error": { + "description": "Generic error title" + }, + "ok": "OK", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "FAQ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "サポートに問い合わせ", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "ログをメールで送信", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "ログを以下のアドレスに送信してください \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "メールアドレスをコピー", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "ログのエクスポート", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "キャンセル", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "バグを報告", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "{endpoint} に接続しました", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "保存", + "@save": { + "description": "Label for save button" + }, + "send": "送信", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "これをストレージ (デフォルトではダウンロードフォルダ) に保存しますか、もしくは他のアプリに送信しますか?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "これをストレージに保存しますか? (デフォルトではダウンロードフォルダに保存)", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "E メール", + "@email": { + "description": "Email field label" + }, + "verify": "認証", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "メールアドレスが無効です", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "有効なメールアドレスを入力して下さい", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "お待ちください...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "パスワードを確認", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "パスワードが正しくありません", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "再度お試しください", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "パスワードを入力", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "パスワードを入力してください", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "アクティブセッション", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "おっと", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "問題が発生しました、再試行してください", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "このデバイスからログアウトします!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "以下のデバイスからログアウトします:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "セッションを終了しますか?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "終了", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "このデバイス", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "アカウント作成", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "脆弱", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "まあまあ", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "強力", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "アカウント削除", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "ご不便をおかけして申し訳ありません。なにか問題が発生していますか?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "はい、フィードバックを送信します", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "いいえ、アカウントを削除します", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "アカウントの削除を開始するためには認証が必要です", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "アカウントの削除に同意", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "このアカウントは他のEnteアプリも使用している場合はそれらにも紐づけされています。\nすべてのEnteアプリでアップロードされたデータは削除され、アカウントは完全に削除されます。", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "削除", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "新規アカウント作成", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "パスワード", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "パスワードの確認", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "パスワードの強度: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Ente についてどのようにお聞きになりましたか?(任意)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "私たちはアプリのインストールを追跡していません。私たちをお知りになった場所を教えてください!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "利用規約プライバシー ポリシーに同意します", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "利用規約", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "プライバシーポリシー", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "私のデータはエンドツーエンドで暗号化されるため、パスワードを紛失した場合、データが失われる可能性があることを理解しています。", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "暗号化", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "ログイン", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "おかえりなさい!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "ログインをクリックする場合、利用規約およびプライバシー ポリシーに同意するものとします。", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "インターネット接続なし", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "インターネット接続を確認して、再試行してください。", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "確認に失敗しました、再試行してください", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "パスワードを再設定", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "現在のデバイスはパスワードを確認するのには不十分ですが、すべてのデバイスで動作するように再生成することはできます。\n\n回復キーを使用してログインし、パスワードを再生成してください(ご希望の場合は同じものを再度使用できます)。", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "回復キーを使用", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "パスワードを忘れた場合", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "メールアドレスを変更", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "メールアドレス認証", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "{email} にメールを送信しました", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "パスワードをリセットするには、メールの確認を先に行ってください。", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "受信トレイ(および迷惑メール)を確認して認証を完了してください", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "タップしてコードを入力", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "メール送信", + "resendEmail": "メールを再送信", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "認証はまだ保留中です", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "セッションの有効期限が切れました", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "セッションの有効期限が切れました。再度ログインしてください。", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "パスキー認証", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "認証を待っています...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "再試行", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "ステータスの確認", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "TOTPでログイン", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "アカウントを回復", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "パスワードの設定", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "パスワードの変更", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "パスワードのリセット", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "暗号鍵", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "データの暗号化に使用するパスワードを入力してください", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "データの暗号化に使用する新しいパスワードを入力してください", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "私たちはこのパスワードを保存していないので、あなたがそれを忘れた場合に私たちがあなたのデータを代わりに復号することはできません", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "動作の仕組み", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "暗号鍵を生成中...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "パスワードを変更しました", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "他のデバイスからサインアウトする", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "他の誰かがあなたのパスワードを知っている可能性があると判断した場合は、あなたのアカウントを使用している他のすべてのデバイスから強制的にサインアウトできます。", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "他のデバイスからサインアウトする", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "サインアウトしない", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "暗号化鍵を生成中...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "続行", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "安全ではないデバイス", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "申し訳ありませんが、このデバイスでは安全な鍵を生成できませんでした。\n\n別のデバイスから登録してください。", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "回復キーをクリップボードにコピーしました", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "回復キー", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "パスワードを忘れた場合、データを回復できる唯一の方法がこのキーです。", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "私たちはこのキーを保存しません。この 24 単語のキーを安全な場所に保存してください。", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "後で行う", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "キーを保存", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "リカバリキーをダウンロードフォルダに保存しました!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "回復キーがありませんか?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "2 要素認証", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "認証アプリに表示された 6 桁のコードを入力してください", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "デバイスを紛失しましたか?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "回復キーを入力", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "回復", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "ログアウト中...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "すぐに", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "アプリのロック", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "自動ロック", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "システムロックが見つかりませんでした", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "端末のロックを有効にするには、システム設定で端末のパスコードまたは画面ロックを設定してください。", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "端末のデフォルトのロック画面と、PINまたはパスワードを使用したカスタムロック画面を選択します。", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "生体認証", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "PIN", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "アプリをバックグラウンドでロックするまでの時間", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "内容を非表示", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "アプリの内容を非表示にし、スクリーンショットを無効にします", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "アプリを切り替えた際に、アプリの内容を非表示にします", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "間違った回数が多すぎます", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "タップして解除", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "本当にログアウトしてよろしいですか?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "はい、ログアウトします", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "秘密鍵を閲覧するためには認証が必要です", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "次へ", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "新しいパスワードを設定", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "PINを入力してください", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "新しいPINを設定", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "確認", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "パスワードを再入力してください", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "PINを再入力してください", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "本人を確認する", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "認識できません。再試行してください。", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "成功", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "キャンセル", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "認証が必要です", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "生体認証が必要です", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "デバイスの認証情報が必要です", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "デバイスの認証情報が必要です", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "設定を開く", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "生体認証がデバイスで設定されていません。生体認証を追加するには、\"設定 > セキュリティ\"を開いてください。", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "生体認証が無効化されています。画面をロック・ロック解除して生体認証を有効化してください。", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "メールアドレスはすでに登録されています。", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "メールアドレスはまだ登録されていません。", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "このアドレスは既に使用されています", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "メールアドレスが {newEmail} に変更されました", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "認証に失敗しました、再試行してください", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "認証に成功しました!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "セッションが失効しました", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "不正な回復キー", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "入力された回復キーは正しくありません", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "2 要素認証は正常にリセットされました", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "確認コードが失効しました", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "不正なコード", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "申し訳ありませんが、入力されたコードは正しくありません", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "開発者向け設定", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "サーバーエンドポイント", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "無効なエンドポイントです", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "入力されたエンドポイントは無効です。有効なエンドポイントを入力して再試行してください。", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "エンドポイントの更新に成功しました", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_ka.arb b/mobile/packages/strings/lib/l10n/arb/strings_ka.arb new file mode 100644 index 0000000000..9ef5cf62f0 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_ka.arb @@ -0,0 +1,110 @@ +{ + "contactSupport": "მხარდაჭერის გუნდთან დაკავშირება", + "@contactSupport": { + "description": "Contact support button label" + }, + "cancel": "გაუქმება", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "პრობლემის შესახებ შეტყობინება", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "email": "ელექტრონული ფოსტა", + "@email": { + "description": "Email field label" + }, + "pleaseWait": "გთხოვთ, დაელოდოთ...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "პაროლის დასტური", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "არასწორი პაროლი", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "გთხოვთ, სცადოთ ხელახლა", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "delete": "წაშლა", + "@delete": { + "description": "Delete button label" + }, + "hearUsWhereTitle": "როგორ შეიტყვეთ Ente-ს შესახებ? (არასავალდებულო)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "ჩვენ არ ვაკონტროლებთ აპლიკაციის ინსტალაციას. სასარგებლო იქნებოდა, თუ გვეტყოდით, სად გვიპოვეთ!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "welcomeBack": "კეთილი იყოს თქვენი დაბრუნება!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "noInternetConnection": "ინტერნეტთან კავშირი არ არის", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "გთხოვთ, შეამოწმოთ თქვენი ინტერნეტ კავშირი და სცადოთ ხელახლა.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "changeEmail": "ელექტრონული ფოსტის შეცვლა", + "@changeEmail": { + "description": "Change email button label" + }, + "signOutFromOtherDevices": "ყველა მოწყობილობიდან გამოსვლა", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "თუ ფიქრობთ, რომ ვინმემ შესაძლოა იცოდეს თქვენი პაროლი, შეგიძლიათ ყველა მოწყობილობაზე იძულებითი გამოსვლა, რომელიც იყენებს თქვენს ანგარიშს.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "სხვა მოწყობილობებიდან გამოსვლა", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "არ მოხდეს გამოსვლა", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "მიმდინარეობს დაშიფრვის გასაღებების გენერირება...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "recoveryKey": "აღდგენის კოდი", + "@recoveryKey": { + "description": "Recovery key label" + }, + "loggingOut": "მიმდინარეობს გამოსვლა...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "androidBiometricNotRecognized": "ამოცნობა ვერ მოხერხდა. გთხოვთ, სცადოთ ხელახლა.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "წარმატებით", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "გაუქმება", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "საჭიროა აუთენთიფიკაცია", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "sessionExpired": "სესიის დრო ამოიწურა", + "@sessionExpired": { + "description": "Error message when session has expired" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_km.arb b/mobile/packages/strings/lib/l10n/arb/strings_km.arb new file mode 100644 index 0000000000..8d3de3eee4 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_km.arb @@ -0,0 +1,188 @@ +{ + "error": "មានកំហុស", + "@error": { + "description": "Generic error title" + }, + "ok": "យល់ព្រម", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "សំនួរ-ចម្លើយ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "ទំនាក់ទំនងសេវា", + "@contactSupport": { + "description": "Contact support button label" + }, + "copyEmailAddress": "ចម្លង​អាសយដ្ឋាន​អ៊ីមែល", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "នាំចេញកំណត់ហេតុ", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "បោះបង់", + "@cancel": { + "description": "Cancel button label" + }, + "save": "រក្សាទុក", + "@save": { + "description": "Label for save button" + }, + "send": "ផ្ញើរចេញ", + "@send": { + "description": "Label for send button" + }, + "email": "អ៊ីម៉ែល", + "@email": { + "description": "Email field label" + }, + "verify": "ផ្ទៀងផ្ទាត់", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "អ៊ីម៉ែលនេះមិនត្រឹមត្រូវទេ", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "verifyPassword": "ផ្ទៀងផ្ទាត់ពាក្យសម្ងាត់", + "@verifyPassword": { + "description": "Verify password button label" + }, + "pleaseTryAgain": "សូមសាកល្បងម្តងទៀត", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "វាយបញ្ចូល​ពាក្យ​សម្ងាត់", + "@enterPassword": { + "description": "Enter password field label" + }, + "activeSessions": "សកម្មភាពគណនី", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "somethingWentWrongPleaseTryAgain": "មាន​អ្វីមួយ​មិន​ប្រក្រតី។ សូម​ព្យាយាម​ម្តង​ទៀត", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "terminateSession": "កាត់់ផ្តាច់សកម្មភាពគណនីនេះ?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "កាត់ផ្តាច់", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "ទូរស័ព្ទនេះ", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "បង្កើតគណនី", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "ខ្សោយ", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "មធ្យម", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "ខ្លាំង", + "@strongStrength": { + "description": "Strong password strength label" + }, + "createNewAccount": "បង្កើតគណនីថ្មី", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "ពាក្យសម្ងាត់", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "បញ្ជាក់ពាក្យសម្ងាត់", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "កម្លាំងពាក្យសម្ងាត់: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "privacyPolicyTitle": "គោលការណ៍​ភាព​ឯកជន", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "logInLabel": "ចូលគណនី", + "@logInLabel": { + "description": "Log in button label" + }, + "forgotPassword": "ភ្លេចកូដសម្ងាត់", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "resendEmail": "ផ្ញើអ៊ីមែលម្ដងទៀត", + "@resendEmail": { + "description": "Resend email button label" + }, + "checkStatus": "ពិនិត្យស្ថានភាព", + "@checkStatus": { + "description": "Check status button label" + }, + "resetPasswordTitle": "ផ្ដាស់ប្ដូរពាក្សសម្ងាត់", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "recover": "សង្គ្រោះមកវិញ", + "@recover": { + "description": "Recover button label" + }, + "immediately": "ភ្លាមៗ", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "hideContent": "លាក់ពត័មាន", + "@hideContent": { + "description": "Hide content setting title" + }, + "tapToUnlock": "ប៉ះដើម្បីដោះសោ", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "enterPin": "វាយបញ្ចូល​លេខ​កូដ PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "reEnterPassword": "បញ្ចូលពាក្យសម្ងាត់ម្តងទៀត", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "androidCancelButton": "បោះបង់", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "តម្រូវឱ្យមានការបញ្ជាក់ភាពត្រឹមត្រូវ", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "emailChangedTo": "អ៊ីម៉ែលត្រូវបានផ្លាស់ប្តូរទៅ{newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_ko.arb b/mobile/packages/strings/lib/l10n/arb/strings_ko.arb new file mode 100644 index 0000000000..f581bd6667 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_ko.arb @@ -0,0 +1,720 @@ +{ + "networkHostLookUpErr": "Ente에 접속할 수 없습니다, 네트워크 설정을 확인해주시고 에러가 반복되는 경우 저희 지원 팀에 문의해주세요.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Ente에 접속할 수 없습니다, 잠시 후에 다시 시도해주세요. 에러가 반복되는 경우, 저희 지원 팀에 문의해주세요.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "뭔가 잘못된 것 같습니다. 잠시 후에 다시 시도해주세요. 에러가 반복되는 경우, 저희 지원 팀에 문의해주세요.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "에러", + "@error": { + "description": "Generic error title" + }, + "ok": "확인", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "FAQ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "지원 문의", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "로그를 이메일로 보내기", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "이 로그를 {toEmail} 쪽으로 보내주세요", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "이메일 주소 복사", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "로그 내보내기", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "취소", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "버그 제보", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "{endpoint}에 접속됨", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "저장", + "@save": { + "description": "Label for save button" + }, + "send": "보내기", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "이것을 당신의 스토리지 (일반적으로 다운로드 폴더) 에 저장하시겠습니까, 아니면 다른 App으로 전송하시겠습니까?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "이것을 당신의 스토리지 (일반적으로 다운로드 폴더) 에 저장하시겠습니까?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "이메일", + "@email": { + "description": "Email field label" + }, + "verify": "인증", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "잘못 된 이메일 주소", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "유효한 이메일 주소를 입력해주세요", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "잠시만 기다려주세요...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "비밀번호 확인", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "올바르지 않은 비밀번호", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "다시 시도해주세요", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "암호 입력", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "암호 입력", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "활성화된 Session", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "이런!", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "뭔가 잘못됐습니다, 다시 시도해주세요", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "이 작업을 하시면 기기에서 로그아웃하게 됩니다!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "이 작업을 하시면 다음 기기에서 로그아웃하게 됩니다:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "세션을 종결하시겠습니까?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "종결", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "이 기기", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "계정 만들기", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "약함", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "보통", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "강함", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "계정 삭제하기", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "떠나신다니 아쉽습니다. 뭔가 문제가 있으셨나요?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "네, 피드백을 보냅니다", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "아니오, 계정을 지웁니다", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "계정 삭제 절차를 시작하려면 인증 절차를 거쳐주세요", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "계정 삭제 확인", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "다른 Ente의 서비스를 이용하고 계시다면, 해당 계정은 모두 연결이 되어있습니다.\n\n모든 Ente 서비스에 업로드 된 당신의 데이터는 삭제 수순에 들어가며, 계정은 불가역적으로 삭제됩니다.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "삭제", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "새 계정 만들기", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "암호", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "암호 확인", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "암호 보안 강도: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Ente에 대해 어떻게 알게 되셨나요? (선택사항)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "저희는 어플 설치 과정을 관찰하지 않습니다. 어디에서 저희를 발견하셨는지 알려주신다면 도움이 될 겁니다!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "나는 사용자 약관개인정보 취급방침에 동의합니다.", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "약관", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "개인정보 취급 방침", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "나는 암호를 분실한 경우, 데이터가 종단 간 암호화되어있기에 데이터를 손실할 수 있음을 이해합니다.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "암호화", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "로그인", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "돌아오신 것을 환영합니다!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "로그인을 누름으로써, 나는 사용자 약관개인정보 취급방침에 동의합니다.", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "인터넷 연결 없음", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "인터넷 연결을 확인하시고 다시 시도해주세요.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "검증 실패, 다시 시도해주세요", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "암호 다시 생성", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "현재 사용 중인 기기는 암호를 확인하기에 적합하지 않으나, 모든 기기에서 작동하는 방식으로 비밀번호를 다시 생성할 수 있습니다.\n\n복구 키를 사용하여 로그인하고 암호를 다시 생성해주세요. (원하시면 현재 사용 중인 암호와 같은 암호를 다시 사용하실 수 있습니다.)", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "복구 키 사용", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "암호 분실", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "이메일 변경", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "이메일 인증하기", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "{email} 쪽으로 메일을 보냈습니다", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "암호를 재설정하시려면, 먼저 이메일을 인증해주세요.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "검증을 위해 메일 보관함을 (또는 스팸 메일 보관함) 확인해주세요", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "눌러서 코드 입력하기", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "이메일 보내기", + "resendEmail": "이메일 다시 보내기", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "검증 절차가 마무리되지 않았습니다", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "세션 만료됨", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "세션이 만료되었습니다. 다시 로그인해주세요.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "패스키 검증", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "검증 대기 중...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "다시 시도해주세요", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "상태 확인", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "TOTP로 로그인 하기", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "계정 복구", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "암호 지정", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "암호 변경", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "암호 초기화", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "암호화 키", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "데이터 암호화를 위한 암호 입력", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "데이터 암호화를 위한 새로운 암호 입력", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "저희는 이 암호를 저장하지 않으니, 만약 잊어버리시게 되면, 데이터를 복호화 해드릴 수 없습니다", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "작동 원리", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "암호화 키 생성 중...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "암호가 성공적으로 변경되었습니다", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "다른 기기들에서 로그아웃하기", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "다른 사람이 내 암호를 알 수도 있을 거란 의심이 드신다면, 당신의 계정을 사용 중인 다른 모든 기기에서 로그아웃할 수 있습니다.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "다른 기기들을 로그아웃시키기", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "로그아웃 하지 않기", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "암호화 키를 생성하는 중...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "계속", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "보안이 허술한 기기", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "죄송합니다, 이 기기에서 보안 키를 생성할 수 없습니다.\n\n다른 기기에서 계정을 생성해주세요.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "클립보드에 복구 키 복사 됨", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "복구 키", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "암호를 잊어버린 경우, 데이터를 복구하려면 이 키를 이용하는 방법 뿐입니다.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "저희는 이 키를 보관하지 않으니, 여기에 있는 24 단어로 구성된 키를 안전하게 보관해주세요.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "나중에 하기", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "키 저장하기", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "다운로드 폴더에 복구 키가 저장되었습니다!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "복구 키가 없으세요?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "2단계 인증", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Authenticator에 적힌 6 자리 코드를 입력해주세요", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "기기를 잃어버리셨나요?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "복구 키를 입력하세요", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "복구", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "로그아웃하는 중...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "즉시", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "어플 잠금", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "자동 잠금", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "시스템 잠금 찾을 수 없음", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "기기 잠금을 활성화하시려면, 기기의 암호를 만들거나 시스템 설정에서 화면 잠금을 설정해주세요.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "기본 잠금 화면이나, PIN 번호나 암호를 사용한 사용자 설정 잠금 화면 중에 선택하세요.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "기기 잠금", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Pin 잠금", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Background로 App 넘어가고 잠기기까지 걸리는 시간", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "내용 숨기기", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "App 전환 화면에서 App의 내용을 숨기고 Screenshot 촬영을 막습니다", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "App 전환 화면에서 App의 내용을 숨깁니다", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "잘못된 시도 횟수가 너무 많습니다", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "잠금을 해제하려면 누르세요", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "로그아웃 하시겠습니까?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "네, 로그아웃하기", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "비밀 부분을 확인하려면 인증 절차를 거쳐주세요", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "다음", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "새 비밀번호 설정", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "PIN 번호 입력", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "새 PIN 번호 설정", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "확인", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "암호 다시 입력", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "핀 다시 입력", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "신원 확인", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "식별할 수 없습니다. 다시 시도해주세요.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "성공", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "취소", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "인증 필요", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "생체인증 필요", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "장치 자격 증명 필요", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "장치 자격 증명 필요", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "설정으로 가기", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "기기에 생체인증이 설정되어있지 않습니다. '설정 > 보안'으로 가셔서 생체인증을 설정해주세요.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "생체인증에 문제가 있습니다. 활성화하시려면 기기를 잠궜다가 다시 풀어주세요.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "확인", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "이미 등록된 이메일입니다.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "등록되지 않은 이메일입니다.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "이 이메일은 이미 사용 중입니다", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "{newEmail}로 메일이 변경되었습니다", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "인증절차 실패, 다시 시도해주세요", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "인증 성공!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "세션 만료", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "잘못 된 복구 키", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "입력하신 복구 키가 맞지 않습니다", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "2FA가 성공적으로 초기화되었습니다", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "검증 코드의 유효시간이 경과하였습니다", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "잘못된 코드", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "죄송합니다, 입력하신 코드가 맞지 않습니다", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "개발자 설정", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "서버 엔드포인트", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "유효하지 않은 엔드포인트", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "죄송합니다, 입력하신 엔드포인트가 유효하지 않습니다. 유효한 엔드포인트를 입력하시고 다시 시도해주세요.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "엔드포인트가 성공적으로 업데이트됨", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_lt.arb b/mobile/packages/strings/lib/l10n/arb/strings_lt.arb new file mode 100644 index 0000000000..474622e1da --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_lt.arb @@ -0,0 +1,720 @@ +{ + "networkHostLookUpErr": "Nepavyksta prisijungti prie „Ente“. Patikrinkite tinklo nustatymus ir susisiekite su palaikymo komanda, jei klaida tęsiasi.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Nepavyksta prisijungti prie „Ente“. Bandykite dar kartą po kurio laiko. Jei klaida tęsiasi, susisiekite su palaikymo komanda.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Atrodo, kad kažkas nutiko ne taip. Bandykite dar kartą po kurio laiko. Jei klaida tęsiasi, susisiekite su mūsų palaikymo komanda.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Klaida", + "@error": { + "description": "Generic error title" + }, + "ok": "Gerai", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "DUK", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Susisiekti su palaikymo komanda", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Atsiųskite žurnalus el. laišku", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Siųskite žurnalus adresu\n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Kopijuoti el. pašto adresą", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Eksportuoti žurnalus", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Atšaukti", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Pranešti apie riktą", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Prijungta prie {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Išsaugoti", + "@save": { + "description": "Label for save button" + }, + "send": "Siųsti", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Ar norite tai išsaugoti saugykloje (pagal numatytuosius nustatymus – atsisiuntimų aplanke), ar siųsti į kitas programas?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Ar norite tai išsaugoti savo saugykloje (pagal numatytuosius nustatymus – atsisiuntimų aplanke)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "El. paštas", + "@email": { + "description": "Email field label" + }, + "verify": "Patvirtinti", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Netinkamas el. pašto adresas", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Įveskite tinkamą el. pašto adresą.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Palaukite...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Patvirtinkite slaptažodį", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Neteisingas slaptažodis.", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Bandykite dar kartą.", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Įveskite slaptažodį", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Įveskite savo slaptažodį", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Aktyvūs seansai", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Ups", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Kažkas nutiko ne taip. Bandykite dar kartą.", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Tai jus atjungs nuo šio įrenginio.", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Tai jus atjungs nuo toliau nurodyto įrenginio:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Baigti seansą?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Baigti", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Šis įrenginys", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Kurti paskyrą", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Silpna", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Vidutinė", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Stipri", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Ištrinti paskyrą", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Apgailestausime, kad išeinate. Ar susiduriate su kažkokiomis problemomis?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Taip, siųsti atsiliepimą", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Ne, ištrinti paskyrą", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Nustatykite tapatybę, kad pradėtumėte paskyros ištrynimą", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Patvirtinkite paskyros ištrynimą", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Ši paskyra susieta su kitomis „Ente“ programomis, jei jas naudojate.\n\nJūsų įkelti duomenys per visas „Ente“ programas bus planuojama ištrinti, o jūsų paskyra bus ištrinta negrįžtamai.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Ištrinti", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Kurti naują paskyrą", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Slaptažodis", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Patvirtinkite slaptažodį", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Slaptažodžio stiprumas: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Kaip išgirdote apie „Ente“? (nebūtina)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Mes nesekame programų diegimų. Mums padėtų, jei pasakytumėte, kur mus radote.", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Sutinku su paslaugų sąlygomis ir privatumo politika", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Sąlygos", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Privatumo politika", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Suprantu, kad jei prarasiu slaptažodį, galiu prarasti savo duomenis, kadangi duomenys yra visapusiškai užšifruota.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Šifravimas", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Prisijungti", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Sveiki sugrįžę!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Spustelėjus Prisijungti sutinku su paslaugų sąlygomis ir privatumo politika", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Nėra interneto ryšio", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Patikrinkite savo interneto ryšį ir bandykite dar kartą.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Patvirtinimas nepavyko. Bandykite dar kartą.", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Iš naujo sukurti slaptažodį", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Dabartinis įrenginys nėra pakankamai galingas, kad patvirtintų jūsų slaptažodį, bet mes galime iš naujo sugeneruoti taip, kad jis veiktų su visais įrenginiais.\n\nPrisijunkite naudodami atkūrimo raktą ir sugeneruokite iš naujo slaptažodį (jei norite, galite vėl naudoti tą patį).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Naudoti atkūrimo raktą", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Pamiršau slaptažodį", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Keisti el. paštą", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Patvirtinti el. paštą", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Išsiuntėme laišką adresu {email}.", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Kad iš naujo nustatytumėte slaptažodį, pirmiausia patvirtinkite savo el. paštą.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Patikrinkite savo gautieją (ir šlamštą), kad užbaigtumėte patvirtinimą.", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Palieskite, kad įvestumėte kodą", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Siųsti el. laišką", + "resendEmail": "Iš naujo siųsti el. laišką", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Vis dar laukiama patvirtinimo", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Seansas baigėsi", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Jūsų seansas baigėsi. Prisijunkite iš naujo.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Slaptarakčio patvirtinimas", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Laukiama patvirtinimo...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Bandyti dar kartą", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Tikrinti būseną", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Prisijungti su TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Atkurti paskyrą", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Nustatyti slaptažodį", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Keisti slaptažodį", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Nustatyti slaptažodį iš naujo", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Šifravimo raktai", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Įveskite slaptažodį, kurį galime naudoti jūsų duomenims užšifruoti", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Įveskite naują slaptažodį, kurį galime naudoti jūsų duomenims užšifruoti", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Šio slaptažodžio nesaugome, todėl jei jį pamiršite, negalėsime iššifruoti jūsų duomenų", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Kaip tai veikia", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Generuojami šifravimo raktai...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Slaptažodis sėkmingai pakeistas", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Atsijungti iš kitų įrenginių", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Jei manote, kad kas nors gali žinoti jūsų slaptažodį, galite priverstinai atsijungti iš visų kitų įrenginių, naudojančių jūsų paskyrą.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Atsijungti kitus įrenginius", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Neatsijungti", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Generuojami šifravimo raktai...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Tęsti", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Nesaugus įrenginys", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Atsiprašome, šiame įrenginyje nepavyko sugeneruoti saugių raktų.\n\nRegistruokitės iš kito įrenginio.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Nukopijuotas atkūrimo raktas į iškarpinę", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Atkūrimo raktas", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Jei pamiršote slaptažodį, vienintelis būdas atkurti duomenis – naudoti šį raktą.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Šio rakto nesaugome, todėl išsaugokite šį 24 žodžių raktą saugioje vietoje.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Daryti tai vėliau", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Išsaugoti raktą", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Atkūrimo raktas išsaugotas atsisiuntimų aplanke.", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Neturite atkūrimo rakto?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Dvigubas tapatybės nustatymas", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Įveskite 6 skaitmenų kodą\niš autentifikatoriaus programos", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Prarastas įrenginys?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Įveskite atkūrimo raktą", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Atkurti", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Atsijungiama...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Iš karto", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Programos užraktas", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Automatinis užraktas", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Nerastas sistemos užraktas", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Kad įjungtumėte įrenginio užraktą, sistemos nustatymuose nustatykite įrenginio prieigos kodą arba ekrano užraktą.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Pasirinkite tarp numatytojo įrenginio užrakinimo ekrano ir pasirinktinio užrakinimo ekrano su PIN kodu arba slaptažodžiu.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Įrenginio užraktas", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "PIN užraktas", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Laikas, po kurio programa užrakinama perkėlus ją į foną.", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Slėpti turinį", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Paslepia programų turinį programų perjungiklyje ir išjungia ekrano kopijas.", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Paslepia programos turinį programos perjungiklyje.", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Per daug neteisingų bandymų.", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Palieskite, kad atrakintumėte", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Ar tikrai norite atsijungti?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Taip, atsijungti", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Nustatykite tapatybę, kad peržiūrėtumėte savo paslaptis", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Toliau", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Nustatykite naują slaptažodį", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Įveskite PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Nustatykite naują PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Patvirtinti", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Įveskite slaptažodį iš naujo", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Įveskite PIN iš naujo", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Patvirtinkite tapatybę", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Neatpažinta. Bandykite dar kartą.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Sėkmė", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Atšaukti", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Privalomas tapatybės nustatymas", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Privaloma biometrija", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Privalomi įrenginio kredencialai", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Privalomi įrenginio kredencialai", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Eiti į nustatymus", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Biometrinis tapatybės nustatymas jūsų įrenginyje nenustatytas. Eikite į Nustatymai > Saugumas ir pridėkite biometrinį tapatybės nustatymą.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biometrinis tapatybės nustatymas išjungtas. Kad jį įjungtumėte, užrakinkite ir atrakinkite ekraną.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "Gerai", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "El. paštas jau užregistruotas.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "El. paštas neregistruotas.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Šis el. paštas jau naudojamas.", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "El. paštas pakeistas į {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Tapatybės nustatymas nepavyko. Bandykite dar kartą.", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Tapatybės nustatymas sėkmingas.", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Seansas baigėsi", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Neteisingas atkūrimo raktas", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Įvestas atkūrimo raktas yra neteisingas.", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Dvigubas tapatybės nustatymas sėkmingai iš naujo nustatytas.", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Jūsų patvirtinimo kodas nebegaliojantis.", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Neteisingas kodas", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Atsiprašome, įvestas kodas yra neteisingas.", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Kūrėjo nustatymai", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Serverio galutinis taškas", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Netinkamas galutinis taškas", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Atsiprašome, įvestas galutinis taškas netinkamas. Įveskite tinkamą galutinį tašką ir bandykite dar kartą.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Galutinis taškas sėkmingai atnaujintas", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_lv.arb b/mobile/packages/strings/lib/l10n/arb/strings_lv.arb new file mode 100644 index 0000000000..4e94c83c00 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_lv.arb @@ -0,0 +1,125 @@ +{ + "error": "Kļūda", + "@error": { + "description": "Generic error title" + }, + "ok": "Labi", + "@ok": { + "description": "Generic OK button label" + }, + "cancel": "Atcelt", + "@cancel": { + "description": "Cancel button label" + }, + "save": "Saglabāt", + "@save": { + "description": "Label for save button" + }, + "email": "E-pasts", + "@email": { + "description": "Email field label" + }, + "invalidEmailTitle": "Nederīga e-pasta adrese", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Lūdzu ievadiet derīgu e-pasta adresi.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "enterPassword": "Ievadiet paroli", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Ievadiet savu paroli", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "thisDevice": "Šī ierīce", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Izveidot kontu", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Vāja", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Vidēji spēcīga", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Spēcīga", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Dzēst kontu", + "@deleteAccount": { + "description": "Delete account button label" + }, + "noDeleteAccountAction": "Nē, dzēst kontu", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "confirmAccountDeleteTitle": "Apstiprināt konta dzēšanu", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "delete": "Dzēst", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Izveidot jaunu kontu", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Parole", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Apstiprināt paroli", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "encryption": "Šifrēšana", + "@encryption": { + "description": "Encryption label" + }, + "noInternetConnection": "Nav interneta savienojums", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "howItWorks": "Kā tas darbojas", + "@howItWorks": { + "description": "How it works button label" + }, + "saveKey": "Saglabāt atslēgu", + "@saveKey": { + "description": "Save key button label" + }, + "hideContent": "Slēpt saturu", + "@hideContent": { + "description": "Hide content setting title" + }, + "androidCancelButton": "Atcelt", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "thisEmailIsAlreadyInUse": "Šis e-pasts jau tiek izmantots", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "E-pasts nomainīts uz {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_ml.arb b/mobile/packages/strings/lib/l10n/arb/strings_ml.arb new file mode 100644 index 0000000000..29f3e3fffe --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_ml.arb @@ -0,0 +1,62 @@ +{ + "ok": "ശരി", + "@ok": { + "description": "Generic OK button label" + }, + "cancel": "റദ്ദാക്കുക", + "@cancel": { + "description": "Cancel button label" + }, + "email": "ഇമെയിൽ", + "@email": { + "description": "Email field label" + }, + "verify": "പരിശോധിക്കുക", + "@verify": { + "description": "Verify button label" + }, + "verifyPassword": "പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "തെറ്റായ പാസ്‌വേഡ്", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "createNewAccount": "പുതിയ അക്കൗണ്ട് സൃഷ്ടിക്കുക", + "@createNewAccount": { + "description": "Create new account button label" + }, + "confirmPassword": "പാസ്വേഡ് സ്ഥിരീകരിക്കുക", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "welcomeBack": "വീണ്ടും സ്വാഗതം!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "changeEmail": "ഇമെയിൽ മാറ്റുക", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "ഇമെയിൽ സ്ഥിരീകരിക്കുക", + "@verifyEmail": { + "description": "Verify email title" + }, + "twoFactorAuthTitle": "ടു-ഫാക്ടർ ആധികാരികത", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "നിങ്ങളുടെ ഓതന്റിക്കേറ്റർ ആപ്പിൽ നിന്നുള്ള 6 അക്ക കോഡ് നൽകുക", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "emailAlreadyRegistered": "ഇമെയിൽ ഇതിനകം രജിസ്റ്റർ ചെയ്തിട്ടുണ്ട്.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "ഇമെയിൽ രജിസ്റ്റർ ചെയ്തിട്ടില്ല.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_nl.arb b/mobile/packages/strings/lib/l10n/arb/strings_nl.arb new file mode 100644 index 0000000000..a4b1d6ba36 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_nl.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "Kan geen verbinding maken met Ente, controleer uw netwerkinstellingen en neem contact op met ondersteuning als de fout zich blijft voordoen.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Kan geen verbinding maken met Ente, probeer het later opnieuw. Als de fout zich blijft voordoen, neem dan contact op met support.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Het lijkt erop dat er iets fout is gegaan. Probeer het later opnieuw. Als de fout zich blijft voordoen, neem dan contact op met ons supportteam.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Foutmelding", + "@error": { + "description": "Generic error title" + }, + "ok": "Oké", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Veelgestelde vragen", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Klantenservice", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "E-mail uw logs", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Verstuur de logs alsjeblieft naar {toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "E-mailadres kopiëren", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Logs exporteren", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Annuleer", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Een fout melden", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Verbonden met {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Opslaan", + "@save": { + "description": "Label for save button" + }, + "send": "Verzenden", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Wil je dit opslaan naar je opslagruimte (Downloads map) of naar andere apps versturen?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Wil je dit opslaan naar je opslagruimte (Downloads map)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Voer uw nieuwe e-mailadres in", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "E-mail", + "@email": { + "description": "Email field label" + }, + "verify": "Verifiëren", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Ongeldig e-mailadres", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Voer een geldig e-mailadres in.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Een ogenblik geduld...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Bevestig wachtwoord", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Onjuist wachtwoord", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Probeer het nog eens", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Voer wachtwoord in", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Voer je wachtwoord in", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Actieve sessies", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Oeps", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Er is iets fout gegaan, probeer het opnieuw", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Dit zal je uitloggen van dit apparaat!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Dit zal je uitloggen van het volgende apparaat:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Sessie beëindigen?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Beëindigen", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Dit apparaat", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Account aanmaken", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Zwak", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Matig", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Sterk", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Account verwijderen", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "We zullen het vervelend vinden om je te zien vertrekken. Zijn er problemen?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Ja, geef feedback", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Nee, verwijder account", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Gelieve te verifiëren om het account te verwijderen", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Account verwijderen bevestigen", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Dit account is gekoppeld aan andere Ente apps, als je er gebruik van maakt.\n\nJe geüploade gegevens worden in alle Ente apps gepland voor verwijdering, en je account wordt permanent verwijderd voor alle Ente diensten.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Verwijderen", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Nieuw account aanmaken", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Wachtwoord", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Wachtwoord bevestigen", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Wachtwoord sterkte: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Hoe hoorde je over Ente? (optioneel)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Wij gebruiken geen tracking. Het zou helpen als je ons vertelt waar je ons gevonden hebt!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Ik ga akkoord met de gebruiksvoorwaarden en privacybeleid", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Voorwaarden", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Privacybeleid", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Ik begrijp dat als ik mijn wachtwoord verlies, ik mijn gegevens kan verliezen omdat mijn gegevens end-to-end versleuteld zijn.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Encryptie", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Inloggen", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Welkom terug!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Door op inloggen te klikken, ga ik akkoord met de gebruiksvoorwaarden en privacybeleid", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Geen internetverbinding", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Controleer je internetverbinding en probeer het opnieuw.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Verificatie mislukt, probeer het opnieuw", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Wachtwoord opnieuw instellen", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Het huidige apparaat is niet krachtig genoeg om je wachtwoord te verifiëren, dus moeten we de code een keer opnieuw genereren op een manier die met alle apparaten werkt.\n\nLog in met behulp van uw herstelsleutel en genereer opnieuw uw wachtwoord (je kunt dezelfde indien gewenst opnieuw gebruiken).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Herstelsleutel gebruiken", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Wachtwoord vergeten", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "E-mailadres wijzigen", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Bevestig e-mail", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "We hebben een e-mail gestuurd naar {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Verifieer eerst je e-mailadres om je wachtwoord opnieuw in te stellen.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Controleer je inbox (en spam) om verificatie te voltooien", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Tik om code in te voeren", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "E-mail versturen", + "resendEmail": "E-mail opnieuw versturen", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Verificatie is nog in behandeling", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Sessie verlopen", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Jouw sessie is verlopen. Log opnieuw in.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Passkey verificatie", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Wachten op verificatie...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Probeer opnieuw", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Status controleren", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Inloggen met TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Account herstellen", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Wachtwoord instellen", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Wachtwoord wijzigen", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Wachtwoord resetten", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Encryptiesleutels", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Voer een wachtwoord in dat we kunnen gebruiken om je gegevens te versleutelen", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Voer een nieuw wachtwoord in dat we kunnen gebruiken om je gegevens te versleutelen", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Wij slaan dit wachtwoord niet op, dus als je het vergeet, kunnen we jouw gegevens niet ontsleutelen", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Hoe het werkt", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Encryptiesleutels worden gegenereerd...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Wachtwoord succesvol aangepast", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Afmelden bij andere apparaten", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Als je denkt dat iemand je wachtwoord zou kunnen kennen, kun je alle andere apparaten die je account gebruiken dwingen om uit te loggen.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Afmelden bij andere apparaten", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Niet uitloggen", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Encryptiesleutels genereren...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Doorgaan", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Onveilig apparaat", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Sorry, we konden geen beveiligde sleutels genereren op dit apparaat.\n\nMeld je aan vanaf een ander apparaat.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Herstelsleutel gekopieerd naar klembord", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Herstelsleutel", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Als je je wachtwoord vergeet, kun je alleen met deze code je gegevens herstellen.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "We slaan deze code niet op, bewaar deze code met 24 woorden op een veilige plaats.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Doe dit later", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Sleutel opslaan", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Herstelsleutel opgeslagen in de Downloads map!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Geen herstelsleutel?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Tweestapsverificatie", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Voer de 6-cijferige code van je verificatie-app in", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Apparaat verloren?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Voer je herstelsleutel in", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Herstellen", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Bezig met uitloggen...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Onmiddellijk", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "App-vergrendeling", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Automatische vergrendeling", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Geen systeemvergrendeling gevonden", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Om toestelvergrendeling in te schakelen, stelt u de toegangscode van het apparaat of schermvergrendeling in uw systeeminstellingen in.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Kies tussen de standaard schermvergrendeling van uw apparaat en een aangepaste schermvergrendeling met een pincode of wachtwoord.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Apparaat vergrendeling", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Pin vergrendeling", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Tijd waarna de app vergrendelt nadat ze op de achtergrond is gezet", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Inhoud verbergen", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Verbergt de app inhoud in de app switcher en schakelt schermafbeeldingen uit", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Verbergt de inhoud van de app in de app switcher", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Te veel onjuiste pogingen", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Tik om te ontgrendelen", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Weet je zeker dat je wilt uitloggen?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Ja, uitloggen", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Graag verifiëren om uw herstelsleutel te bekijken", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Volgende", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Nieuw wachtwoord instellen", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Pin invoeren", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Nieuwe pin instellen", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Bevestig", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Wachtwoord opnieuw invoeren", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "PIN opnieuw invoeren", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Identiteit verifiëren", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Niet herkend. Probeer het opnieuw.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Succes", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Annuleren", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Verificatie vereist", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biometrische verificatie vereist", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Apparaatgegevens vereist", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Apparaatgegevens vereist", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Ga naar instellingen", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Biometrische verificatie is niet ingesteld op uw apparaat. Ga naar 'Instellingen > Beveiliging' om biometrische verificatie toe te voegen.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biometrische verificatie is uitgeschakeld. Vergrendel en ontgrendel uw scherm om het in te schakelen.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "Oké", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "E-mail is al geregistreerd.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "E-mail niet geregistreerd.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Dit e-mailadres is al in gebruik", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "E-mailadres gewijzigd naar {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Verificatie mislukt, probeer het opnieuw", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Verificatie geslaagd!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Sessie verlopen", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Onjuiste herstelsleutel", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "De ingevoerde herstelsleutel is onjuist", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Tweestapsverificatie succesvol gereset", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Uw verificatiecode is verlopen", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Onjuiste code", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Sorry, de ingevoerde code is onjuist", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Ontwikkelaarsinstellingen", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Server eindpunt", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Ongeldig eindpunt", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Sorry, het eindpunt dat u hebt ingevoerd is ongeldig. Voer een geldig eindpunt in en probeer het opnieuw.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Eindpunt met succes bijgewerkt", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_pl.arb b/mobile/packages/strings/lib/l10n/arb/strings_pl.arb new file mode 100644 index 0000000000..1cd3b15fdf --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_pl.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "Nie można połączyć się z Ente, sprawdź ustawienia sieci i skontaktuj się z pomocą techniczną, jeśli błąd będzie się powtarzał.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Nie można połączyć się z Ente, spróbuj ponownie po pewnym czasie. Jeśli błąd będzie się powtarzał, skontaktuj się z pomocą techniczną.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Wygląda na to, że coś poszło nie tak. Spróbuj ponownie po pewnym czasie. Jeśli błąd będzie się powtarzał, skontaktuj się z naszym zespołem pomocy technicznej.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Błąd", + "@error": { + "description": "Generic error title" + }, + "ok": "Ok", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Najczęściej zadawane pytania (FAQ)", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Skontaktuj się z pomocą techniczną", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Wyślij mailem logi", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Prosimy wysłać logi do {toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Kopiuj adres e-mail", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Eksportuj logi", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Anuluj", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Zgłoś błąd", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Połączono z {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Zapisz", + "@save": { + "description": "Label for save button" + }, + "send": "Wyślij", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Czy chcesz zapisać to do swojej pamięci masowej (domyślnie folder Pobrane) czy wysłać to do innych aplikacji?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Czy chcesz zapisać to do swojej pamięci masowej (domyślnie folder Pobrane)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Wprowadź nowy adres e-mail", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "Email", + "@email": { + "description": "Email field label" + }, + "verify": "Zweryfikuj", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Nieprawidłowy adres e-mail", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Prosimy podać prawidłowy adres e-mail.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Prosimy czekać...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Zweryfikuj hasło", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Nieprawidłowe hasło", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Prosimy spróbować ponownie", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Wprowadź hasło", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Wprowadź swoje hasło", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Aktywne sesje", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Ups", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Coś poszło nie tak, spróbuj ponownie", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "To wyloguje Cię z tego urządzenia!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "To wyloguje Cię z tego urządzenia:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Zakończyć sesję?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Zakończ", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "To urządzenie", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Utwórz konto", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Słabe", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Umiarkowane", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Silne", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Usuń konto", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Będzie nam przykro, że odchodzisz. Masz jakiś problem?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Tak, wyślij opinię", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Nie, usuń moje konto", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Prosimy uwierzytelnić się, aby zainicjować usuwanie konta", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Potwierdź usunięcie konta", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "To konto jest połączone z innymi aplikacjami Ente, jeśli ich używasz.\n\nTwoje przesłane dane, we wszystkich aplikacjach Ente, zostaną zaplanowane do usunięcia, a Twoje konto zostanie trwale usunięte.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Usuń", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Utwórz nowe konto", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Hasło", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Potwierdź hasło", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Siła hasła: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Jak usłyszałeś/aś o Ente? (opcjonalnie)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Nie śledzimy instalacji aplikacji. Pomogłyby nam, gdybyś powiedział/a nam, gdzie nas znalazłeś/aś!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Akceptuję warunki korzystania z usługi i politykę prywatności", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Regulamin", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Polityka prywatności", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Rozumiem, że jeśli utracę hasło, mogę stracić moje dane, ponieważ moje dane są szyfrowane end-to-end.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Szyfrowanie", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Zaloguj się", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Witaj ponownie!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Klikając, zaloguj się, zgadzam się na regulamin i politykę prywatności", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Brak połączenia z Internetem", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Prosimy sprawdzić połączenie internetowe i spróbować ponownie.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Weryfikacja nie powiodła się, spróbuj ponownie", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Zresetuj hasło", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Obecne urządzenie nie jest wystarczająco wydajne, aby zweryfikować Twoje hasło, więc musimy je raz zregenerować w sposób, który działa ze wszystkimi urządzeniami. \n\nZaloguj się przy użyciu klucza odzyskiwania i zresetuj swoje hasło (możesz ponownie użyć tego samego, jeśli chcesz).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Użyj kodu odzyskiwania", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Nie pamiętam hasła", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Zmień adres e-mail", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Zweryfikuj adres e-mail", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Wysłaliśmy wiadomość do {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Aby zresetować hasło, najpierw zweryfikuj swój e-mail.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Sprawdź swoją skrzynkę odbiorczą (i spam), aby zakończyć weryfikację", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Dotknij, aby wprowadzić kod", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Wyślij e-mail", + "resendEmail": "Wyślij e-mail ponownie", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Weryfikacja jest nadal w toku", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Sesja wygasła", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Twoja sesja wygasła. Zaloguj się ponownie.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Weryfikacja kluczem dostępu", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Oczekiwanie na weryfikację...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Spróbuj ponownie", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Sprawdź stan", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Zaloguj się za pomocą TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Odzyskaj konto", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Ustaw hasło", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Zmień hasło", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Zresetuj hasło", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Klucz szyfrowania", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Wprowadź hasło, którego możemy użyć do zaszyfrowania Twoich danych", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Wprowadź nowe hasło, którego możemy użyć do zaszyfrowania Twoich danych", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Nie przechowujemy tego hasła, więc jeśli go zapomnisz, nie będziemy w stanie odszyfrować Twoich danych", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Jak to działa", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Generowanie kluczy szyfrujących...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Hasło zostało pomyślnie zmienione", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Wyloguj z pozostałych urządzeń", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Jeśli uważasz, że ktoś może znać Twoje hasło, możesz wymusić wylogowanie na wszystkich innych urządzeniach korzystających z Twojego konta.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Wyloguj z pozostałych urządzeń", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Nie wylogowuj mnie", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Generowanie kluczy szyfrujących...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Kontynuuj", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Niezabezpieczone urządzenie", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Przepraszamy, nie mogliśmy wygenerować kluczy bezpiecznych na tym urządzeniu.\n\nZarejestruj się z innego urządzenia.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Klucz odzyskiwania został skopiowany do schowka", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Klucz odzyskiwania", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Jeśli zapomnisz hasła, jedynym sposobem na odzyskanie danych jest ten klucz.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Nie przechowujemy tego klucza, prosimy zachować ten 24-słowny klucz w bezpiecznym miejscu.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Zrób to później", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Zapisz klucz", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Klucz odzyskiwania zapisany w folderze Pobrane!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Brak klucza odzyskiwania?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Uwierzytelnianie dwustopniowe", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Wprowadź sześciocyfrowy kod z \nTwojej aplikacji uwierzytelniającej", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Zagubiono urządzenie?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Wprowadź swój klucz odzyskiwania", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Odzyskaj", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Wylogowywanie...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Natychmiast", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Blokada aplikacji", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Automatyczna blokada", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Nie znaleziono blokady systemowej", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Aby włączyć blokadę aplikacji, należy skonfigurować hasło urządzenia lub blokadę ekranu w ustawieniach Twojego systemu.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Wybierz między domyślnym ekranem blokady urządzenia a niestandardowym ekranem blokady z kodem PIN lub hasłem.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Blokada urządzenia", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Blokada PIN", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Czas, po którym aplikacja blokuje się po umieszczeniu jej w tle", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Ukryj zawartość", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Ukrywa zawartość aplikacji w przełączniku aplikacji i wyłącza zrzuty ekranu", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Ukrywa zawartość aplikacji w przełączniku aplikacji", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Zbyt wiele błędnych prób", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Naciśnij, aby odblokować", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Czy na pewno chcesz się wylogować?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Tak, wyloguj", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Prosimy uwierzytelnić się, aby wyświetlić swoje sekrety", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Dalej", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Ustaw nowe hasło", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Wprowadź kod PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Ustaw nowy kod PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Potwierdź", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Wprowadź ponownie hasło", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Wprowadź ponownie kod PIN", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Potwierdź swoją tożsamość", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Nie rozpoznano. Spróbuj ponownie.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Sukces", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Anuluj", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Wymagana autoryzacja", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Wymagana biometria", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Wymagane dane logowania urządzenia", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Wymagane dane logowania urządzenia", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Przejdź do ustawień", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Uwierzytelnianie biometryczne nie jest skonfigurowane na tym urządzeniu. Przejdź do 'Ustawienia > Bezpieczeństwo', aby dodać uwierzytelnianie biometryczne.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Uwierzytelnianie biometryczne jest wyłączone. Prosimy zablokować i odblokować ekran, aby je włączyć.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "Adres e-mail jest już zarejestrowany.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "Adres e-mail nie jest zarejestrowany.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Ten adres e-mail już jest zajęty", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Adres e-mail został zmieniony na {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Uwierzytelnianie nie powiodło się, prosimy spróbować ponownie", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Uwierzytelnianie powiodło się!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Sesja wygasła", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Nieprawidłowy klucz odzyskiwania", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Wprowadzony klucz odzyskiwania jest nieprawidłowy", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Pomyślnie zresetowano uwierzytelnianie dwustopniowe", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Twój kod weryfikacyjny wygasł", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Nieprawidłowy kod", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Niestety, wprowadzony kod jest nieprawidłowy", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Ustawienia dla programistów", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Punkt końcowy serwera", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Punkt końcowy jest nieprawidłowy", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Niestety, wprowadzony punkt końcowy jest nieprawidłowy. Wprowadź prawidłowy punkt końcowy i spróbuj ponownie.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Punkt końcowy zaktualizowany pomyślnie", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_pt.arb b/mobile/packages/strings/lib/l10n/arb/strings_pt.arb new file mode 100644 index 0000000000..72e947dc72 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_pt.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "Não foi possível conectar-se ao Ente, verifique suas configurações de rede e entre em contato com o suporte se o erro persistir.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Não foi possível conectar ao Ente, tente novamente após algum tempo. Se o erro persistir, entre em contato com o suporte.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Parece que algo deu errado. Tente novamente mais tarde. Se o erro persistir, entre em contato com nossa equipe de ajuda.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Erro", + "@error": { + "description": "Generic error title" + }, + "ok": "Ok", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Perguntas frequentes", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Contatar suporte", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Enviar registros por e-mail", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Envie os logs para \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Copiar endereço de e-mail", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Exportar logs", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Cancelar", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Informar erro", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Conectado a {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Salvar", + "@save": { + "description": "Label for save button" + }, + "send": "Enviar", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Deseja mesmo salvar isso no armazenamento (pasta de Downloads por padrão) ou enviar a outros aplicativos?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Deseja mesmo salvar em seu armazenamento (pasta de Downloads por padrão)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Insira seu novo e-mail", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "E-mail", + "@email": { + "description": "Email field label" + }, + "verify": "Verificar", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Endereço de e-mail inválido", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Insira um endereço de e-mail válido.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Aguarde...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Verificar senha", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Senha incorreta", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Tente novamente", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Inserir senha", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Insira sua senha", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Sessões ativas", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Opa", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Algo deu errado. Tente outra vez", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Isso fará com que você saia deste dispositivo!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Isso fará você sair do dispositivo a seguir:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Sair?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Encerrar", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Esse dispositivo", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Criar conta", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Fraca", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Moderada", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Forte", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Excluir conta", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Estamos tristes por vê-lo sair. Você enfrentou algum problema?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Sim, enviar feedback", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Não, excluir conta", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Autentique-se para iniciar a exclusão de conta", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Confirmar exclusão de conta", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Esta conta está vinculada a outros apps Ente, se você usa algum.\n\nSeus dados enviados, entre todos os apps Ente, serão marcados para exclusão, e sua conta será apagada permanentemente.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Excluir", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Criar nova conta", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Senha", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Confirmar senha", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Força da senha: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Como você descobriu o Ente? (opcional)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Não rastreamos instalações. Ajudaria bastante se você contasse onde nos achou!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Eu concordo com os termos de serviço e a política de privacidade", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Termos", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Política de Privacidade", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Eu entendo que se eu perder minha senha, posso perder meus dados, já que meus dados são criptografados de ponta a ponta.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Criptografia", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Entrar", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Bem-vindo(a) de volta!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Ao clicar em iniciar sessão, eu concordo com os termos de serviço e a política de privacidade", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Não conectado à internet", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Verifique sua conexão com a internet e tente novamente.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Falhou na verificação. Tente novamente", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Redefinir senha", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Não é possível verificar a sua senha no dispositivo atual, mas podemos regenerá-la para que funcione em todos os dispositivos. \n\nEntre com a sua chave de recuperação e regenere sua senha (você pode usar a mesma se quiser).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Usar chave de recuperação", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Esqueci a senha", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Alterar e-mail", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Verificar e-mail", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Enviamos um e-mail à {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Para redefinir sua senha, verifique seu e-mail primeiramente.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Verifique sua caixa de entrada (e spam) para concluir a verificação", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Toque para inserir código", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Enviar e-mail", + "resendEmail": "Reenviar e-mail", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "A verificação ainda está pendente", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Sessão expirada", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Sua sessão expirou. Registre-se novamente.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Verificação de chave de acesso", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Aguardando verificação...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Tente novamente", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Verificar status", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Registrar com TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Recuperar conta", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Definir senha", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Alterar senha", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Redefinir senha", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Chaves de criptografia", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Insira uma senha que podemos usar para criptografar seus dados", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Insira uma nova senha para criptografar seus dados", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Não salvamos esta senha, então se você esquecê-la, não podemos descriptografar seus dados", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Como funciona", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Gerando chaves de criptografia...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "A senha foi alterada", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Sair da conta em outros dispositivos", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Se você acha que alguém possa saber da sua senha, você pode forçar desconectar sua conta de outros dispositivos.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Sair em outros dispositivos", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Não sair", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Gerando chaves de criptografia...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Continuar", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Dispositivo inseguro", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Desculpe, não foi possível gerar chaves de segurança nesse dispositivo.\n\ninicie sessão em um dispositivo diferente.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Chave de recuperação copiada para a área de transferência", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Chave de recuperação", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Caso esqueça sua senha, a única maneira de recuperar seus dados é com esta chave.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Não armazenamos esta chave de 24 palavras. Salve-a em um lugar seguro.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Fazer isso depois", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Salvar chave", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Chave de recuperação salva na pasta Downloads!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Sem chave de recuperação?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Autenticação de dois fatores", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Insira o código de 6 dígitos do aplicativo autenticador", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Perdeu o dispositivo?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Digite a chave de recuperação", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Recuperar", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Desconectando...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Imediatamente", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Bloqueio do aplicativo", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Bloqueio automático", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Nenhum bloqueio do sistema encontrado", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Para ativar o bloqueio do dispositivo, configure a senha do dispositivo ou o bloqueio de tela nas configurações do seu sistema.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Escolha entre a tela de bloqueio padrão do seu dispositivo e uma tela de bloqueio personalizada com PIN ou senha.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Bloqueio do dispositivo", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "PIN de bloqueio", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Tempo de bloqueio do aplicativo em segundo plano", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Ocultar conteúdo", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Oculta o conteúdo do aplicativo no seletor de aplicativos e desativa as capturas de tela", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Oculta o conteúdo do seletor de aplicativos", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Muitas tentativas incorretas", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Toque para desbloquear", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Deseja mesmo sair?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Sim, quero sair", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Autentique-se para ver suas chaves secretas", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Avançar", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Defina a nova senha", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Inserir PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Definir novo PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Confirmar", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Reinserir senha", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Reinserir PIN", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Verificar identidade", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Não reconhecido. Tente de novo.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Sucesso", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Cancelar", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Autenticação necessária", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biometria necessária", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Credenciais necessários do dispositivo", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Credenciais necessários do dispositivo", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Ir para Opções", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "A autenticação biométrica não está configurada no seu dispositivo. Vá em 'Configurações > Segurança' para adicionar a autenticação biométrica.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "A autenticação biométrica está desativada. Bloqueie e desbloqueie sua tela para ativá-la.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "E-mail já registrado.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "E-mail não registrado.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Este e-mail já está em uso", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "E-mail alterado para {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "A autenticação falhou. Tente novamente", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Autenticado!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Sessão expirada", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Chave de recuperação incorreta", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "A chave de recuperação inserida está incorreta", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Autenticação de dois fatores redefinida com sucesso", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Seu código de verificação expirou", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Código incorreto", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "O código inserido está incorreto", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Opções de Desenvolvedor", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Endpoint do servidor", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Endpoint inválido", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Desculpe, o ponto de acesso inserido é inválido. Insira um ponto de acesso válido e tente novamente.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "O endpoint foi atualizado", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_ro.arb b/mobile/packages/strings/lib/l10n/arb/strings_ro.arb new file mode 100644 index 0000000000..d65054a7f0 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_ro.arb @@ -0,0 +1,311 @@ +{ + "error": "Eroare", + "@error": { + "description": "Generic error title" + }, + "ok": "Ok", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Întrebări frecvente", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Contactează suportul", + "@contactSupport": { + "description": "Contact support button label" + }, + "pleaseSendTheLogsTo": "Te rugăm să trimiți jurnalele la {toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Copiază adresa de e-mail", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "cancel": "Anulare", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Raportează o eroare", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "save": "Salvare", + "@save": { + "description": "Label for save button" + }, + "send": "Trimitere", + "@send": { + "description": "Label for send button" + }, + "email": "E-mail", + "@email": { + "description": "Email field label" + }, + "invalidEmailTitle": "Adresa e-mail nu este validă", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Te rugăm să introduci o adresă de e-mail validă.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Te rog așteaptă...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Verifică parola", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Parolă incorectă", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Te rugăm să încerci din nou", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Introdu parola", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Introdu parola", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Sesiuni active", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Ups", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Ceva n-a mers bine, te rog încearcă din nou", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisDevice": "Acest dispozitiv", + "@thisDevice": { + "description": "Label for current device" + }, + "deleteAccount": "Ștergere cont", + "@deleteAccount": { + "description": "Delete account button label" + }, + "yesSendFeedbackAction": "Da, trimite feedback", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Nu, șterge contul", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Te rugăm să te autentifici pentru a iniția ștergerea contului", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "delete": "Ștergere", + "@delete": { + "description": "Delete button label" + }, + "password": "Parolă", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Confirmă parola", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "termsOfServicesTitle": "Termeni", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "welcomeBack": "Bine ai revenit!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "noInternetConnection": "Nu există conexiune la internet", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "recreatePasswordTitle": "Recreează parola", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "forgotPassword": "Am uitat parola", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Schimbă e-mailul", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Verifică e-mail", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Am trimis un e-mail la {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Pentru a reseta parola, te rugăm să confirmi mai întâi adresa de e-mail.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "tapToEnterCode": "Apasă pentru a introduce codul", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Trimite e-mail", + "resendEmail": "Retrimite e-mail", + "@resendEmail": { + "description": "Resend email button label" + }, + "loginSessionExpired": "Sesiune expirată", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "tryAgain": "Încearcă din nou", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Verifică status", + "@checkStatus": { + "description": "Check status button label" + }, + "recoverAccount": "Recuperare cont", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Setează parola", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Schimbă parola", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Resetează parola", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "enterPasswordToEncrypt": "Introdu o parolă pe care o putem folosi pentru a-ți cripta datele", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "passwordWarning": "Nu stocăm această parolă, deci dacă o uiți, nu îți putem decripta datele", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "passwordChangedSuccessfully": "Parola a fost modificată cu succes", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Deconectare de pe alte dispozitive", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherDevices": "Deconectează alte dispozitive", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Nu te deconecta", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "recoveryKeyCopiedToClipboard": "Cheie de recuperare salvată în clipboard", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Cheie de recuperare", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Dacă îți uiți parola, singura modalitate prin care poți recupera datele este cu această cheie.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Nu stocăm această cheie, vă rugăm salvați această cheie de 24 de cuvinte într-un loc sigur.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "saveKey": "Salvare cheie", + "@saveKey": { + "description": "Save key button label" + }, + "twoFactorAuthTitle": "Autentificare cu doi factori", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "recover": "Recuperează", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Deconectare...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "androidBiometricHint": "Verifică identitatea", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Neidentificat. Încearcă din nou.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Succes", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Anulare", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Autentificare necesară", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "goToSettings": "Mergi la setări", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "iOSOkButton": "Ok", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "thisEmailIsAlreadyInUse": "Această adresă de e-mail este deja folosită", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "E-mail modificat în {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "sessionExpired": "Sesiune expirată", + "@sessionExpired": { + "description": "Error message when session has expired" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_ru.arb b/mobile/packages/strings/lib/l10n/arb/strings_ru.arb new file mode 100644 index 0000000000..80b84d8de8 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_ru.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "Не удается подключиться к Ente, пожалуйста, проверьте настройки своей сети и обратитесь в службу поддержки, если ошибка повторится.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Не удается подключиться к Ente, пожалуйста, повторите попытку через некоторое время. Если ошибка не устраняется, обратитесь в службу поддержки.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Похоже, что-то пошло не так. Пожалуйста, повторите попытку через некоторое время. Если ошибка повторится, обратитесь в нашу службу поддержки.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Ошибка", + "@error": { + "description": "Generic error title" + }, + "ok": "Ок", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "ЧаВо", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Связаться с поддержкой", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Отправить свои журналы", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Пожалуйста, отправьте журналы на \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Копировать адрес электронной почты", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Экспорт журналов", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Отмена", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Сообщить об ошибке", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Подключено к {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Сохранить", + "@save": { + "description": "Label for save button" + }, + "send": "Отправить", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Вы хотите сохранить это в хранилище (папку загрузок по умолчанию) или отправить в другие приложения?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Вы хотите сохранить это в хранилище (по умолчанию папка загрузок)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Введите ваш новый адрес электронной почты", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "Электронная почта", + "@email": { + "description": "Email field label" + }, + "verify": "Подтвердить", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Неверный адрес электронной почты", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Пожалуйста, введите действительный адрес электронной почты.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Пожалуйста, подождите...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Подтверждение пароля", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Неправильный пароль", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Пожалуйста, попробуйте ещё раз", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Введите пароль", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Введите пароль", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Активные сеансы", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Ой", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Что-то пошло не так. Попробуйте еще раз", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Вы выйдете из этого устройства!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Вы выйдете из списка следующих устройств:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Завершить сеанс?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Завершить", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Это устройство", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Создать аккаунт", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Слабый", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Средний", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Сильный", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Удалить аккаунт", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Нам будет жаль, если вы уйдете. Вы столкнулись с какой-то проблемой?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Да, отправить отзыв", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Нет, удалить аккаунт", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Пожалуйста, авторизуйтесь, чтобы начать удаление аккаунта", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Подтвердить удаление аккаунта", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Эта учетная запись связана с другими приложениями Ente, если вы ими пользуетесь.\n\nЗагруженные вами данные во всех приложениях Ente будут запланированы к удалению, а ваша учетная запись будет удалена без возможности восстановления.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Удалить", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Создать новый аккаунт", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Пароль", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Подтвердить пароль", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Мощность пароля: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Как вы узнали о Ente? (необязательно)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Мы не отслеживаем установки приложений. Было бы полезно, если бы вы сказали, где нас нашли!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Я согласен с условиями предоставления услуг и политикой конфиденциальности", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Условия использования", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Политика конфиденциальности", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Я понимаю, что если я потеряю свой пароль, я могу потерять свои данные, так как мои данные в сквозном шифровании.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Шифрование", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Войти", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "С возвращением!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Нажимая на логин, я принимаю условия использования и политику конфиденциальности", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Нет подключения к Интернету", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Проверьте подключение к Интернету и повторите попытку.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Проверка не удалась, попробуйте еще раз", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Пересоздать пароль", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Текущее устройство недостаточно мощно для верификации пароля, но мы можем регенерировать так, как это работает со всеми устройствами.\n\nПожалуйста, войдите, используя ваш ключ восстановления и сгенерируйте ваш пароль (вы можете использовать тот же пароль, если пожелаете).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Использовать ключ восстановления", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Забыл пароль", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Изменить адрес электронной почты", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Подтвердить адрес электронной почты", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Мы отправили письмо на {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Подтвердите адрес электронной почты, чтобы сбросить пароль.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Пожалуйста, проверьте свой почтовый ящик (и спам) для завершения верификации", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Нажмите, чтобы ввести код", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Отправить электронное письмо", + "resendEmail": "Отправить письмо еще раз", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Верификация еще не завершена", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Сессия недействительна", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Сессия истекла. Войдите снова.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Проверка с помощью ключа доступа", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Ожидание подтверждения...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Попробовать снова", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Проверить статус", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Войти с помощью TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Восстановить аккаунт", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Поставить пароль", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Изменить пароль", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Сбросить пароль", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Ключи шифрования", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Введите пароль, который мы можем использовать для шифрования ваших данных", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Введите новый пароль, который мы можем использовать для шифрования ваших данных", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Мы не храним этот пароль, поэтому если вы забудете его, мы не сможем расшифровать ваши данные", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Как это работает", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Генерируем ключи шифрования...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Пароль успешно изменён", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Выйти из других устройств", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Если вы думаете, что кто-то может знать ваш пароль, вы можете принудительно выйти из всех устройств.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Выйти из других устройств", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Не выходить", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Генерируем ключи шифрования...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Далее", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Небезопасное устройство", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "К сожалению, мы не смогли сгенерировать безопасные ключи на этом устройстве.\n\nПожалуйста, зарегистрируйтесь с другого устройства.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Ключ восстановления скопирован в буфер обмена", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Ключ восстановления", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Если вы забыли свой пароль, то восстановить данные можно только с помощью этого ключа.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Мы не храним этот ключ, пожалуйста, сохраните этот ключ в безопасном месте.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Сделать позже", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Сохранить ключ", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Ключ восстановления сохранён в папке Загрузки!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Нет ключа восстановления?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Двухфакторная аутентификация", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Введите 6-значный код из\nвашего приложения-аутентификатора", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Потеряно устройство?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Введите ключ восстановления", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Восстановить", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Выходим...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Немедленно", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Блокировка приложения", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Автоблокировка", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Системная блокировка не найдена", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Чтобы включить блокировку устройства, пожалуйста, настройте пароль или блокировку экрана в настройках системы.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Выберите между экраном блокировки вашего устройства и пользовательским экраном блокировки с PIN-кодом или паролем.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Блокировка устройства", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Pin блокировка", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Время в фоне, после которого приложение блокируется", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Скрыть содержимое", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Скрывает содержимое приложения в переключателе приложений и отключает скриншоты", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Скрывает содержимое приложения в переключателе приложений", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Слишком много неудачных попыток", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Нажмите для разблокировки", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Вы уверены, что хотите выйти?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Да, выйти", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Пожалуйста, авторизуйтесь для просмотра ваших секретов", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Далее", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Задать новый пароль", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Введите PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Установите новый PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Подтвердить", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Подтвердите пароль", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Введите PIN-код ещё раз", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Подтвердите личность", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Не распознано. Попробуйте еще раз.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Успешно", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Отменить", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Требуется аутентификация", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Требуется биометрия", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Требуются учетные данные устройства", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Требуются учетные данные устройства", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Перейдите к настройкам", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Биометрическая аутентификация не настроена на вашем устройстве. Перейдите в \"Настройки > Безопасность\", чтобы добавить биометрическую аутентификацию.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Биометрическая аутентификация отключена. Пожалуйста, заблокируйте и разблокируйте экран, чтобы включить ее.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "ОК", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "Адрес электронной почты уже зарегистрирован.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "Адрес электронной почты не зарегистрирован.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Этот адрес электронной почты уже используется", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Адрес электронной почты изменен на {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Аутентификация не удалась, попробуйте еще раз", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Аутентификация прошла успешно!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Сеанс истек", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Неправильный ключ восстановления", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Введен неправильный ключ восстановления", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Двухфакторная аутентификация успешно сброшена", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Срок действия вашего проверочного кода истек", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Неверный код", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Извините, введенный вами код неверный", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Настройки разработчика", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Конечная точка сервера", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Неверная конечная точка", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Извините, введенная вами конечная точка неверна. Пожалуйста, введите корректную конечную точку и повторите попытку.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Конечная точка успешно обновлена", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_sk.arb b/mobile/packages/strings/lib/l10n/arb/strings_sk.arb new file mode 100644 index 0000000000..e219a19391 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_sk.arb @@ -0,0 +1,712 @@ +{ + "networkHostLookUpErr": "Nemožno sa pripojiť k Ente, skontrolujte svoje nastavenia siete a kontaktujte podporu, ak chyba pretrváva.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Nemožno sa pripojiť k Ente, skúste znova v krátkom čase. Ak chyba pretrváva, kontaktujte podporu.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Vyzerá to, že sa niečo pokazilo. Skúste znova v krátkom čase. Ak chyba pretrváva, kontaktujte náš tím podpory.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Chyba", + "@error": { + "description": "Generic error title" + }, + "ok": "Ok", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Často kladené otázky", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Kontaktovať podporu", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Odoslať vaše logy emailom", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Prosím, pošlite logy na adresu \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Skopírovať e-mailovú adresu", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Exportovať logy", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Zrušiť", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Nahlásiť chybu", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Pripojený k endpointu {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Uložiť", + "@save": { + "description": "Label for save button" + }, + "send": "Odoslať", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Chcete to uložiť do svojho zariadenia (predvolený priečinok Stiahnuté súbory) alebo to odoslať do iných aplikácií?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Chcete to uložiť do svojho zariadenia (predvolený priečinok Stiahnuté súbory)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "Email", + "@email": { + "description": "Email field label" + }, + "verify": "Overiť", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Neplatná emailová adresa", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Zadajte platnú e-mailovú adresu.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Prosím počkajte...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Potvrďte heslo", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Nesprávne heslo", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Prosím, skúste to znova", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Zadajte heslo", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Zadajte vaše heslo", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Aktívne relácie", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Ups", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Niečo sa pokazilo, skúste to prosím znova", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Toto vás odhlási z tohto zariadenia!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Toto vás odhlási z následujúceho zariadenia:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Ukončiť reláciu?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Ukončiť", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Toto zariadenie", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Vytvoriť účet", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Slabé", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Mierne", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Silné", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Odstrániť účet", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Bude nám ľúto ak odídeš. Máš nejaký problém?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Áno, odoslať spätnú väzbu", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Nie, odstrániť účet", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Je potrebné overenie pre spustenie odstránenia účtu", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Potvrď odstránenie účtu", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Tento účet je prepojený s inými aplikáciami Ente, ak nejaké používaš.\n\nTvoje nahrané údaje vo všetkých Ente aplikáciách budú naplánované na odstránenie a tvoj účet bude natrvalo odstránený.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Odstrániť", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Vytvoriť nový účet", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Heslo", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Potvrdiť heslo", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Sila hesla: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Ako ste sa dozvedeli o Ente? (voliteľné)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Nesledujeme inštalácie aplikácie. Veľmi by nám pomohlo, keby ste nám povedali, ako ste sa o nás dozvedeli!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Súhlasím s podmienkami používania a zásadami ochrany osobných údajov", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Podmienky používania", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Zásady ochrany osobných údajov", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Rozumiem, že ak stratím alebo zabudnem heslo, môžem stratiť svoje údaje, pretože moje údaje sú šifrované end-to-end.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Šifrovanie", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Prihlásenie", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Vitajte späť!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Kliknutím na prihlásenie, súhlasím s podmienkami používania a zásadami ochrany osobných údajov", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Žiadne internetové pripojenie", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Skontrolujte svoje internetové pripojenie a skúste to znova.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Overenie zlyhalo, skúste to znova", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Resetovať heslo", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Aktuálne zariadenie nie je dostatočne výkonné na overenie vášho hesla, avšak vieme ho regenerovať spôsobom, ktorý funguje vo všetkých zariadeniach.\n\nPrihláste sa pomocou kľúča na obnovenie a znovu vygenerujte svoje heslo (ak si prajete, môžete znova použiť rovnaké).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Použiť kľúč na obnovenie", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Zabudnuté heslo", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Zmeniť e-mail", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Overiť email", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Odoslali sme email na adresu {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Ak chcete obnoviť svoje heslo, najskôr overte svoj email.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Skontrolujte svoju doručenú poštu (a spam) pre dokončenie overenia", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Klepnutím zadajte kód", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Odoslať email", + "resendEmail": "Znovu odoslať email", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Overenie stále prebieha", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Relácia vypršala", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Vaša relácia vypršala. Prosím, prihláste sa znovu.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Overenie pomocou passkey", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Čakanie na overenie...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Skúsiť znova", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Overiť stav", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Prihlásenie pomocou TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Obnoviť účet", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Nastaviť heslo", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Zmeniť heslo", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Obnoviť heslo", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Šifrovacie kľúče", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Zadajte heslo, ktoré môžeme použiť na šifrovanie vašich údajov", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Zadajte nové heslo, ktoré môžeme použiť na šifrovanie vašich údajov", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Ente neukladá tohto heslo. V prípade, že ho zabudnete, nie sme schopní rozšifrovať vaše údaje", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Ako to funguje", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Generovanie šifrovacích kľúčov...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Heslo bolo úspešne zmenené", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Odhlásiť sa z iných zariadení", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Ak si myslíš, že by niekto mohol poznať tvoje heslo, môžeš vynútiť odhlásenie všetkých ostatných zariadení používajúcich tvoj účet.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Odhlásiť iné zariadenie", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Neodhlasovať", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Generovanie šifrovacích kľúčov...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Pokračovať", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Slabo zabezpečené zariadenie", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Ospravedlňujeme sa, v tomto zariadení sme nemohli generovať bezpečnostné kľúče.\n\nzaregistrujte sa z iného zariadenia.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Skopírovaný kód pre obnovenie do schránky", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Kľúč pre obnovenie", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Ak zabudnete heslo, jediným spôsobom, ako môžete obnoviť svoje údaje, je tento kľúč.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "My tento kľúč neuchovávame, uložte si tento kľúč obsahujúci 24 slov na bezpečnom mieste.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Urobiť to neskôr", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Uložiť kľúč", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Kľúč na obnovenie uložený v priečinku Stiahnutých súborov!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Nemáte kľúč pre obnovenie?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Dvojfaktorové overovanie", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Zadajte 6-miestny kód z\nvašej overovacej aplikácie", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Stratené zariadenie?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Vložte váš kód pre obnovenie", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Obnoviť", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Odhlasovanie...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Okamžite", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Zámok aplikácie", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Automatické uzamknutie", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Nenájdená žiadna zámka obrazovky", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Pre povolenie zámku zariadenia, nastavte prístupový kód zariadenia alebo zámok obrazovky v nastaveniach systému.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Vyberte si medzi predvolenou zámkou obrazovky vášho zariadenia a vlastnou zámkou obrazovky s PIN kódom alebo heslom.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Zámok zariadenia", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Zámok PIN", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Čas, po ktorom sa aplikácia uzamkne po nečinnosti", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Skryť obsah", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Skrýva obsah v prepínači aplikácii a zakazuje snímky obrazovky", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Skrýva obsah v prepínači aplikácii", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Príliš veľa chybných pokusov", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Ťuknutím odomknete", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Naozaj sa chcete odhlásiť?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Áno, odhlásiť sa", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Pre zobrazenie vašich tajných údajov sa musíte overiť", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Ďalej", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Nastaviť nové heslo", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Zadajte PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Nastaviť nový PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Potvrdiť", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Zadajte heslo znova", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Zadajte PIN znova", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Overiť identitu", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Nerozpoznané. Skúste znova.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Overenie úspešné", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Zrušiť", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Vyžaduje sa overenie", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Vyžaduje sa biometria", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Vyžadujú sa poverenia zariadenia", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Vyžadujú sa poverenia zariadenia", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Prejsť do nastavení", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Overenie pomocou biometrie nie je na vašom zariadení nastavené. Prejdite na 'Nastavenie > Zabezpečenie' a pridajte overenie pomocou biometrie.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Overenie pomocou biometrie je zakázané. Zamknite a odomknite svoju obrazovku, aby ste ho povolili.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "thisEmailIsAlreadyInUse": "Tento e-mail sa už používa", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Emailová adresa bola zmenená na {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Overenie zlyhalo. Skúste to znova", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Overenie sa podarilo!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Relácia vypršala", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Nesprávny kľúč na obnovenie", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Kľúč na obnovenie, ktorý ste zadali, je nesprávny", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Dvojfaktorové overovanie bolo úspešne obnovené", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Platnosť overovacieho kódu uplynula", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Neplatný kód", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Ľutujeme, zadaný kód je nesprávny", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Nastavenia pre vývojárov", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Endpoint servera", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Neplatný endpoint", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Ospravedlňujeme sa, endpoint, ktorý ste zadali, je neplatný. Zadajte platný endpoint a skúste to znova.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Endpoint úspešne aktualizovaný", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_sl.arb b/mobile/packages/strings/lib/l10n/arb/strings_sl.arb new file mode 100644 index 0000000000..e8d3845abc --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_sl.arb @@ -0,0 +1,720 @@ +{ + "networkHostLookUpErr": "Ne morete se povezati z Ente, preverite omrežne nastavitve in se obrnite na podporo, če se napaka nadaljuje.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Ne morete se povezati z Ente, poskusite znova čez nekaj časa. Če se napaka nadaljuje, se obrnite na podporo.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Zdi se, da je šlo nekaj narobe. Po določenem času poskusite znova. Če se napaka nadaljuje, se obrnite na našo ekipo za podporo.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Napaka", + "@error": { + "description": "Generic error title" + }, + "ok": "V redu", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Pogosta vprašanja", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Stik s podporo", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Pošlji loge po e-pošti", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Loge pošljite na naslov \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Kopiraj e-poštni naslov", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Izvozi loge", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Prekliči", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Prijavite napako", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Povezano na {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Shrani", + "@save": { + "description": "Label for save button" + }, + "send": "Pošlji", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Želite to shraniti v shrambo (privzeto: mapa Prenosi) ali poslati drugim aplikacijam?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Želite to shraniti v shrambo (privzeto: mapa Prenosi)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "E-pošta", + "@email": { + "description": "Email field label" + }, + "verify": "Preveri", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Neveljaven e-poštni naslov", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Prosimo vnesite veljaven e-poštni naslov.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Prosim počakajte...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Potrdite geslo", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Nepravilno geslo", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Prosimo, poskusite ponovno", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Vnesite geslo", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Vnesite svoje geslo", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Aktivne seje", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Ups", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Nekaj je šlo narobe, prosimo poizkusite znova.", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "To vas bo odjavilo iz te naprave!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "To vas bo odjavilo iz naslednje naprave:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Končaj sejo", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Končaj", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Ta naprava", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Ustvari račun", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Šibko", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Zmerno", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Močno", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Izbriši račun", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Žal nam je, da odhajate. Imate kakšne težave?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Ja, pošlji povratne informacije", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Ne, izbriši račun", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Za izbris računa, se overite", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Potrdi brisanje računa", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Ta račun je povezan z drugimi aplikacijami Ente, če jih uporabljate.\n\nVaši naloženi podatki v vseh aplikacijah Ente bodo načrtovane za izbris, vaš račun pa bo trajno izbrisan.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Ustvari nov račun", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Geslo", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Potrdi geslo", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Moč gesla: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Kako ste slišali o Ente? (izbirno)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Namestitvam aplikacij ne sledimo. Pomagalo bi, če bi nam povedali, kje ste nas našli!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Strinjam se s pogoji uporabe in politiko zasebnosti", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Pogoji uporabe", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Politika zasebnosti", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Razumem, da lahko z izgubo gesla, izgubim svoje podatke, saj so end-to-end šifrirani", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Šifriranje", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Prijava", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Dobrodošli nazaj!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "S klikom na prijava, se strinjam s pogoji uporabe in politiko zasebnosti", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Ni internetne povezave", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Preverite internetno povezavo in poskusite znova.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Potrjevanje ni bilo uspešno, prosimo poskusite znova.", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Ponovno ustvarite geslo", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Trenutna naprava, ni dovolj zmogljiva za preverjanje vašega gesla, a ga lahko generiramo na način, ki deluje z vsemi napravami.\n\nProsimo, prijavite se z vašim ključem za obnovo in ponovno ustvarite geslo (če želite lahko uporabite enako kot prej).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Uporabi ključ za obnovo", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Pozabljeno geslo", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Sprememba e-poštnega naslova", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Potrdite e-pošto", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Poslali smo e-pošto na {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Če želite ponastaviti geslo, najprej potrdite svoj e-poštni naslov.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Prosimo, preverite svoj e-poštni predal (in nezaželeno pošto), da končate verifikacijo", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Pritisni za vnos kode", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Pošlji e-pošto", + "resendEmail": "Ponovno pošlji e-pošto", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Preverjanje še ni zaključeno", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Seja je potekla", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Vaša seja je potekla. Prosimo ponovno se prijavite.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Potrditev ključa za dostop (passkey)", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Čakanje na potrditev...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Poskusite ponovno", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Preveri status", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Prijava z TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Obnovi račun", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Nastavite geslo", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Sprememba gesla", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Ponastavitev gesla", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Šifrirni ključi", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Vnesite geslo, ki ga lahko uporabimo za šifriranje vaših podatkov", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Vnesite novo geslo, ki ga lahko uporabimo za šifriranje vaših podatkov", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Tega gesla ne shranjujemo, zato v primeru, da ga pozabite, ne moremo dešifrirati vaših podatkov.", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Kako deluje? ", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Ustvarjanje ključe za šifriranje", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Geslo je bilo uspešno spremenjeno", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Odjavi se iz ostalih naprav", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Če menite, da bi lahko kdo poznal vaše geslo, lahko vse druge naprave, ki uporabljajo vaš račun, prisilite, da se odjavijo.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Odjavi ostale naprave", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Ne odjavi se", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Generiramo ključe za šifriranje", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Nadaljuj", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Nezanesljiva naprava", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Žal v tej napravi nismo mogli ustvariti varnih ključev.\n\nProsimo, prijavite se iz druge naprave.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Ključ za obnovo kopiran v odložišče", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Ključ za obnovitev", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Če pozabite svoje geslo, je edini način da obnovite svoje podatke s tem ključem", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Tega ključa ne hranimo, prosimo shranite teh 24 besed na varnem", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Stori to kasneje", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Shrani ključ", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Ključ za obnovitev je shranjen v mapi Prenosi!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Nimate ključa za obnovo?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Dvojno preverjanja pristnosti", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Vnesite 6 mestno kodo iz vaše aplikacije za preverjanje pristnosti", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Izgubljena naprava?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Vnesite vaš ključ za obnovitev", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Obnovi", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Odjavljanje...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Takoj", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Zaklep aplikacije", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Samodejno zaklepanje", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Nobeno zaklepanje sistema ni bilo najdeno", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Da omogočite zaklepanje naprave, prosimo nastavite kodo ali zaklepanje zaslona v sistemskih nastavitvah.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Izbirate lahko med privzetim zaklenjenim zaslonom naprave in zaklenjenim zaslonom po meri s kodo PIN ali geslom.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Zaklepanje naprave", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Zaklepanje s PIN", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Čas po katerem se aplikacije zaklene, ko jo enkrat zapustite.", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Skrij vsebino", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Skrije vsebino aplikacije v menjalniku opravil in onemogoči posnetke zaslona", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Skrije vsebino aplikacije v menjalniku opravil", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Preveč nepravilnih poskusov", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Kliknite za odklepanje", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Ali ste prepričani, da se želite odjaviti?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Ja, odjavi se", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Če si želite ogledati svoje skrivne ključe, se overite", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Naprej", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Nastavi novo geslo", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Vnesi PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Nastavi nov PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Potrdi", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Ponovno vnesite geslo", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Ponovno vnesite PIN", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Potrdite identiteto", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Ni prepoznano. Poskusite znova.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Uspešno", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Prekliči", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Potrebna je overitev", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Zahtevani biometrični podatki", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Zahtevani podatki za vpis v napravo", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Zahtevani podatki za vpis v napravo", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Pojdi v nastavitve", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Biometrično overjanje v vaši napravi ni nastavljeno. Pojdite v \"Nastavitve > Varnost\" in dodajte biometrično overjanje.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biometrično overjanje je onemogočeno. Če ga želite omogočiti, zaklenite in odklenite zaslon.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "V redu", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "E-poštni naslov je že registriran.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "E-poštni naslov ni registriran.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Ta e-poštni naslove je že v uporabi.", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "E-poštni naslove je bil spremenjen na {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Overitev ni uspela, prosimo poskusite znova", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Overitev uspešna!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Seja je potekla", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Nepravilen ključ za obnovitev", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Ključ za obnovitev, ki ste ga vnesli ni pravilen", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Uspešna ponastavitev dvostopenjske avtentikacije", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Vaša koda za potrditev je potekla.", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Nepravilna koda", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Oprostite, koda ki ste jo vnesli ni pravilna", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Nastavitve za razvijalce", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Endpoint strežnika", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Nepravilen endpoint", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Oprostite endpoint, ki ste ga vnesli ni bil pravilen. Prosimo, vnesite pravilen endpoint in poskusite znova.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Endpoint posodobljen uspešno", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_sr.arb b/mobile/packages/strings/lib/l10n/arb/strings_sr.arb new file mode 100644 index 0000000000..4d9592d94e --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_sr.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "Није могуће повезивање са Ente-ом, молимо вас да проверите мрежне поставке и контактирајте подршку ако грешка и даље постоји.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Није могуће повезивање са Ente-ом, покушајте поново мало касније. Ако грешка настави, обратите се подршци.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Изгледа да је нешто погрешно. Покушајте поново након неког времена. Ако грешка настави, обратите се нашем тиму за подршку.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Грешка", + "@error": { + "description": "Generic error title" + }, + "ok": "У реду", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Питања", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Контактирати подршку", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Имејлирајте извештаје", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Пошаљите извештаје на \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Копирати имејл адресу", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Извези изештаје", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Откажи", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Пријави грешку", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Везано за {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Сачувај", + "@save": { + "description": "Label for save button" + }, + "send": "Пошаљи", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Да ли желите да ово сачувате у складиште (фасцикли за преузимање подразумевано) или да га пошаљете другим апликацијама?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Да ли желите да ово сачувате у складиште (фасцикли за преузимање подразумевано)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Унесите Ваш нови имејл", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "Имејл", + "@email": { + "description": "Email field label" + }, + "verify": "Верификуј", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Погрешна имејл адреса", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Унесите важећи имејл.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Молимо сачекајте...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Верификујте лозинку", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Неисправна лозинка", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Пробајте поново", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Унеси лозинку", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Унесите лозинку", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Активне сесије", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Упс", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Нешто је пошло наопако. Покушајте поново", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Ово ће вас одјавити из овог уређаја!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Ово ће вас одјавити из овог уређаја:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Прекинути сесију?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Прекини", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Овај уређај", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Направи налог", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Слабо", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Умерено", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Јако", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Избриши налог", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Жао нам је што одлазите. Да ли се суочавате са неком грешком?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Да, послати повратне информације", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Не, избрисати налог", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Молимо вас да се аутентификујете за брисање рачуна", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Потврда брисања рачуна", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Овај налог је повезан са другим Ente апликацијама, ако користите било коју.\n\nВаши преношени подаци, на свим Ente апликацијама биће заказани за брисање, и ваш рачун ће се трајно избрисати.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Обриши", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Креирај нови налог", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Лозинка", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Потврдите лозинку", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Снага лозинке: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Како сте чули о Ente? (опционо)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Не пратимо инсталацију апликације. Помогло би да нам кажеш како си нас нашао!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Прихватам услове сервиса и политику приватности", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Услови", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Политика приватности", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Разумем да ако изгубим лозинку, могу изгубити своје податке пошто су шифрирани од краја до краја.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Шифровање", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Пријави се", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Добродошли назад!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Кликом на пријаву, прихватам услове сервиса и политику приватности", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Нема интернет везе", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Провери своју везу са интернетом и покушај поново.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Неуспешна верификација, покушајте поново", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Поново креирати лозинку", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Тренутни уређај није довољно моћан да потврди вашу лозинку, али можемо регенерирати на начин који ради са свим уређајима.\n\nПријавите се помоћу кључа за опоравак и обновите своју лозинку (можете поново користити исту ако желите).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Користите кључ за опоравак", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Заборавио сам лозинку", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Промени имејл", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Потврди имејл", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Послали смо имејл на {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Да бисте ресетовали лозинку, прво потврдите свој имејл.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Молимо вас да проверите примљену пошту (и нежељену пошту) да бисте довршили верификацију", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Пипните да бисте унели кôд", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Шаљи имејл", + "resendEmail": "Поново послати имејл", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Верификација је још у току", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Сесија је истекла", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Ваша сесија је истекла. Молимо пријавите се поново.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Верификација сигурносном кључем", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Чека се верификација...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Покушај поново", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Провери статус", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Пријава са TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Опоравак налога", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Постави лозинку", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Промени лозинку", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Ресетуј лозинку", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Кључеве шифровања", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Унесите лозинку за употребу за шифровање ваших података", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Унесите нову лозинку за употребу за шифровање ваших података", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Не чувамо ову лозинку, па ако је заборавите, не можемо дешифрирати ваше податке", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Како то функционише", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Генерисање кључева за шифровање...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Лозинка је успешно промењена", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Одјави се из других уређаја", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Ако мислиш да неко може знати твоју лозинку, можеш приморати одјављивање све остале уређаје које користе твој налог.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Одјави друге уређаје", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Не одјави", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Генерисање кључева за шифровање...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Настави", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Уређај није сигуран", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Извините, не можемо да генеришемо сигурне кључеве на овом уређају.\n\nМолимо пријавите се са другог уређаја.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Кључ за опоравак копирано у остави", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Резервни Кључ", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Ако заборавите лозинку, једини начин на који можете повратити податке је са овим кључем.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Не чувамо овај кључ, молимо да сачувате кључ од 24 речи на сигурном месту.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Уради то касније", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Сачувај кључ", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Кључ за опоравак сачуван у фасцикли за преузимање!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Немате кључ за опоравак?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Дво-факторска аутентификација", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Унесите 6-цифрени кôд из\nапликације за аутентификацију", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Узгубили сте уређај?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Унети кључ за опоравак", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Опорави", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Одјављивање...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Одмах", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Закључавање апликације", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Ауто-закључавање", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Није пронађено ниједно закључавање система", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Да бисте омогућили закључавање уређаја, молимо вас да подесите шифру уређаја или закључавање екрана у системским подешавањима.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Изаберите између заданог закључавање екрана вашег уређаја и прилагођени екран за закључавање са ПИН-ом или лозинком.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Закључавање уређаја", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "ПИН клокирање", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Време након којег се апликација блокира након што је постављенеа у позадину", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Сакриј садржај", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Сакрива садржај апликације у пребацивање апликација и онемогућује снимке екрана", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Сакрива садржај апликације у пребацивање апликација", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Превише погрешних покушаја", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Додирните да бисте откључали", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Да ли сте сигурни да се одјавите?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Да, одјави ме", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Аутентификујте се да бисте прегледали Ваше тајне", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Следеће", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Постави нову лозинку", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Унеси ПИН", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Постави нови ПИН", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Потврди", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Поново унеси лозинку", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Поново унеси ПИН", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Потврдите идентитет", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Нисмо препознали. Покушати поново.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Успех", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Откажи", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Потребна аутентификација", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Потребна је биометрија", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Потребни су акредитиви уређаја", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Потребни су акредитиви уређаја", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Иди на поставке", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Биометријска аутентификација није постављена на вашем уређају. Идите на \"Подешавања> Сигурност\" да бисте додали биометријску аутентификацију.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Биометријска аутентификација је онемогућена. Закључајте и откључите екран да бисте је омогућили.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "У реду", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "Имејл је већ регистрован.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "Имејл није регистрован.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Овај имејл је већ у употреби", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Имејл промењен на {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Аутентификација није успела, покушајте поново", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Успешна аутентификација!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Сесија је истекла", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Нетачан кључ за опоравак", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Унети кључ за опоравак је натачан", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Двофакторска аутентификација успешно рисетирана", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Ваш верификациони кôд је истекао", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Погрешан кôд", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Унет кôд није добар", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Подешавања за програмере", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Крајња тачка сервера", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Погрешна крајња тачка", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Извини, крајња тачка коју си унео је неважећа. Унеси важећу крајњу тачку и покушај поново.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Крајна тачка успешно ажурирана", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_sv.arb b/mobile/packages/strings/lib/l10n/arb/strings_sv.arb new file mode 100644 index 0000000000..924ef97553 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_sv.arb @@ -0,0 +1,716 @@ +{ + "networkHostLookUpErr": "Det gick inte att ansluta till Ente, kontrollera dina nätverksinställningar och kontakta supporten om felet kvarstår.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Det gick inte att ansluta till Ente, försök igen om en stund. Om felet kvarstår, vänligen kontakta support.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Det ser ut som om något gick fel. Försök igen efter en stund. Om felet kvarstår, vänligen kontakta vår support.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Fel", + "@error": { + "description": "Generic error title" + }, + "ok": "OK", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "FAQ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Kontakta support", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Maila dina loggar", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Vänligen skicka loggarna till \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Kopiera e-postadress", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Exportera loggar", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Avbryt", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Rapportera en bugg", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Ansluten till {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Spara", + "@save": { + "description": "Label for save button" + }, + "send": "Skicka", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Vill du spara detta till din lagringsmapp (Nedladdningsmappen som standard) eller skicka den till andra appar?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Vill du spara detta till din lagringsmapp (Nedladdningsmappen som standard)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Ange din nya e-postadress", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "E-post", + "@email": { + "description": "Email field label" + }, + "verify": "Verifiera", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Ogiltig e-postadress", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Ange en giltig e-postadress.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Vänligen vänta...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Bekräfta lösenord", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Felaktigt lösenord", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Försök igen", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Ange lösenord", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Ange ditt lösenord", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Aktiva sessioner", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Hoppsan", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Något gick fel, vänligen försök igen", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Detta kommer att logga ut dig från den här enheten!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Detta kommer att logga ut dig från följande enhet:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Avsluta session?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Avsluta", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Den här enheten", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Skapa konto", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Svag", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Måttligt", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Stark", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Radera konto", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Vi kommer att vara ledsna över att se dig gå. Har du något problem?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Ja, skicka feedback", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Nej, radera konto", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Vänligen autentisera för att initiera borttagning av konto", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Bekräfta radering av kontot", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Detta konto är kopplat till andra Ente apps, om du använder någon.\n\nDina uppladdade data, över alla Ente appar, kommer att schemaläggas för radering och ditt konto kommer att raderas permanent.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Radera", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Skapa nytt konto", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Lösenord", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Bekräfta lösenord", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Lösenordsstyrka: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Hur hörde du talas om Ente? (valfritt)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Vi spårar inte appinstallationer, Det skulle hjälpa oss om du berättade var du hittade oss!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Jag samtycker till användarvillkoren och integritetspolicyn", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Villkor", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Integritetspolicy", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Jag förstår att om jag förlorar mitt lösenord kan jag förlora mina data eftersom min data är end-to-end-krypterad.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Kryptering", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Logga in", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Välkommen tillbaka!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Jag samtycker till användarvillkoren och integritetspolicyn", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Ingen internetanslutning", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Kontrollera din internetanslutning och försök igen.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Verifiering misslyckades, vänligen försök igen", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Återskapa lösenord", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Denna enhet är inte tillräckligt kraftfull för att verifiera ditt lösenord, men vi kan återskapa det på ett sätt som fungerar med alla enheter.\n\nLogga in med din återställningsnyckel och återskapa ditt lösenord (du kan använda samma igen om du vill).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Använd återställningsnyckel", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Glömt lösenord", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Ändra e-postadress", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Verifiera e-postadress", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Vi har skickat ett mail till {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "För att återställa ditt lösenord måste du först bekräfta din e-postadress.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Vänligen kontrollera din inkorg (och skräppost) för att slutföra verifieringen", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Tryck för att ange kod", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Skicka e-post", + "resendEmail": "Skicka e-post igen", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Verifiering pågår fortfarande", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Sessionen har gått ut", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Din session har upphört. Logga in igen.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Verifiering med inloggningsnyckel", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Väntar på verifiering...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Försök igen", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Kontrollera status", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Logga in med TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Återställ konto", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Ställ in lösenord", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Ändra lösenord", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Återställ lösenord", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Krypteringsnycklar", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Ange ett lösenord som vi kan använda för att kryptera din data", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Ange ett nytt lösenord som vi kan använda för att kryptera din data", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Vi lagrar inte detta lösenord, så om du glömmer bort det, kan vi inte dekryptera dina data", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Så här fungerar det", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Skapar krypteringsnycklar...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Lösenordet har ändrats", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Logga ut från andra enheter", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Om du tror att någon kanske känner till ditt lösenord kan du tvinga alla andra enheter med ditt konto att logga ut.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Logga ut andra enheter", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Logga inte ut", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Skapar krypteringsnycklar...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Fortsätt", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Osäker enhet", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Tyvärr, kunde vi inte generera säkra nycklar på den här enheten.\n\nvänligen registrera dig från en annan enhet.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Återställningsnyckel kopierad till urklipp", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Återställningsnyckel", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Om du glömmer ditt lösenord är det enda sättet du kan återställa dina data med denna nyckel.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Vi lagrar inte och har därför inte åtkomst till denna nyckel, vänligen spara denna 24 ords nyckel på en säker plats.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Gör detta senare", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Spara nyckel", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Återställningsnyckel sparad i nedladdningsmappen!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Ingen återställningsnyckel?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Tvåfaktorsautentisering", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Ange den 6-siffriga koden från din autentiseringsapp", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Förlorad enhet?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Ange din återställningsnyckel", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Återställ", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Loggar ut...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Omedelbart", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Applås", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Automatisk låsning", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Inget systemlås hittades", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "För att aktivera enhetslås, vänligen ställ in enhetens lösenord eller skärmlås i dina systeminställningar.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "deviceLock": "Enhetslås", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Pinkodslås", + "@pinLock": { + "description": "PIN lock option title" + }, + "hideContent": "Dölj innehåll", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Döljer appinnehåll i app-växlaren och inaktiverar skärmdumpar", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Döljer appinnehåll i app-växlaren", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "För många felaktiga försök", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Tryck för att låsa upp", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Är du säker på att du vill logga ut?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Ja, logga ut", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Autentisera för att visa din återställningsnyckel", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Nästa", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Ställ in nytt lösenord", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Ange PIN-kod", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Ställ in ny PIN-kod", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Bekräfta", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Ange lösenord igen", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Ange PIN-kod igen", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Verifiera identitet", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Ej godkänd. Försök igen.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Slutförd", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Avbryt", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Obligatorisk autentisering", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biometriska uppgifter krävs", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Enhetsuppgifter krävs", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Enhetsuppgifter krävs", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Gå till inställningar", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Biometrisk autentisering är inte konfigurerad på din enhet. Gå till \"Inställningar > Säkerhet\" för att lägga till biometrisk autentisering.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biometrisk autentisering är inaktiverat. Lås och lås upp din skärm för att aktivera den.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "E-postadress redan registrerad.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "E-postadress ej registrerad.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Denna e-postadress används redan", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "E-post ändrad till {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Autentisering misslyckades, vänligen försök igen", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Autentisering lyckades!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Sessionen har gått ut", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Felaktig återställningsnyckel", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Återställningsnyckeln du angav är felaktig", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Tvåfaktorsautentisering återställd", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Din verifieringskod har upphört att gälla", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Felaktig kod", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Tyvärr, den kod som du har angett är felaktig", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Utvecklarinställningar", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Serverns slutpunkt", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Ogiltig slutpunkt", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Tyvärr, slutpunkten du angav är ogiltig. Ange en giltig slutpunkt och försök igen.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Slutpunkten har uppdaterats", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_ti.arb b/mobile/packages/strings/lib/l10n/arb/strings_ti.arb new file mode 100644 index 0000000000..a8a44c1d38 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_ti.arb @@ -0,0 +1,542 @@ +{ + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Error", + "@error": { + "description": "Generic error title" + }, + "ok": "Ok", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "FAQ", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "ደገፍ ኣድራሻ", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Email your logs", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Please send the logs to \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Copy email address", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Export logs", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Cancel", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "ጌጋ ጸብጻብ ልኣኸ", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "email": "Email", + "@email": { + "description": "Email field label" + }, + "verify": "Verify", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Invalid email address", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Please enter a valid email address.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "በጃኻ ተጸበ...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "ቃለ-ምስጢር ኣረጋግጽ", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "ግጉይ ቃለ-ምስጢር", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Please try again", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Enter password", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Enter your password", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Active sessions", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "ዉዉኡ", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Something went wrong, please try again", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "This will log you out of this device!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "This will log you out of the following device:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Terminate session?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Terminate", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "This device", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Create account", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Weak", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Moderate", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Strong", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Delete account", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "We'll be sorry to see you go. Are you facing some issue?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Yes, send feedback", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "No, delete account", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Please authenticate to initiate account deletion", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Confirm account deletion", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "delete": "Delete", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Create new account", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Password", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Confirm password", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Password strength: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "How did you hear about Ente? (optional)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "ተጠቀምትና ኣይንከታተልን ኢና። ኣበይ ከም ዝረኸብካና እንተ ትነግረና ሓጋዚ እዩ፦", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "I agree to the terms of service and privacy policy", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Terms", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Privacy Policy", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Encryption", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Log in", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "እንኳዕ ብደሓን ተመለስካ!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "By clicking log in, I agree to the terms of service and privacy policy", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "No internet connection", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Please check your internet connection and try again.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Verification failed, please try again", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Recreate password", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "ምሕዋይ መፍትሕ ተጠቐም", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Forgot password", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Change email", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Verify email", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "We have sent a mail to {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "To reset your password, please verify your email first.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Please check your inbox (and spam) to complete verification", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Tap to enter code", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Send email", + "resendEmail": "Resend email", + "@resendEmail": { + "description": "Resend email button label" + }, + "tryAgain": "Try again", + "@tryAgain": { + "description": "Try again button label" + }, + "recoverAccount": "Recover account", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Set password", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Change password", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Reset password", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Encryption keys", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Enter a password we can use to encrypt your data", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Enter a new password we can use to encrypt your data", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "We don't store this password, so if you forget, we cannot decrypt your data", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "How it works", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Generating encryption keys...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Password changed successfully", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Sign out from other devices", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "If you think someone might know your password, you can force all other devices using your account to sign out.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "ካብ ካልኦት መሳርሒታት ኣውጽኡኒ።", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "ኣውጽኡኒ።", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "ናይ ምስጢራዊ ቁልፊ ዪፍጠር...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Continue", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Insecure device", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Recovery key copied to clipboard", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "ምሕዋይ መፍትሕ", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "If you forget your password, the only way you can recover your data is with this key.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Do this later", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Save key", + "@saveKey": { + "description": "Save key button label" + }, + "noRecoveryKeyTitle": "No recovery key?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Two-factor authentication", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Enter the 6-digit code from\nyour authenticator app", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Lost device?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Enter your recovery key", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Recover", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "ወጸ...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "areYouSureYouWantToLogout": "Are you sure you want to logout?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Yes, logout", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Please authenticate to view your secrets", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "confirm": "Confirm", + "@confirm": { + "description": "Confirm button label" + }, + "androidBiometricHint": "Verify identity", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Not recognized. Try again.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Success", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Cancel", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Authentication required", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biometric required", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Device credentials required", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Device credentials required", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Go to settings", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Biometric authentication is not set up on your device. Go to 'Settings > Security' to add biometric authentication.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biometric authentication is disabled. Please lock and unlock your screen to enable it.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "thisEmailIsAlreadyInUse": "This email is already in use", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Email changed to {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Authentication failed, please try again", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Authentication successful!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "ክፍለ ግዜኡ ኣኺሉ።", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Incorrect recovery key", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "The recovery key you entered is incorrect", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Two-factor authentication successfully reset", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Your verification code has expired", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Incorrect code", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Sorry, the code you've entered is incorrect", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_tr.arb b/mobile/packages/strings/lib/l10n/arb/strings_tr.arb new file mode 100644 index 0000000000..39ee00c798 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_tr.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "Ente'ye bağlanılamıyor, lütfen ağ ayarlarınızı kontrol edin ve hata devam ederse desteğe başvurun.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Ente'ye bağlanılamıyor, lütfen daha sonra tekrar deneyin. Hata devam ederse, lütfen desteğe başvurun.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Bir şeyler ters gitmiş gibi görünüyor. Lütfen bir süre sonra tekrar deneyin. Hata devam ederse, lütfen destek ekibimizle iletişime geçin.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Hata", + "@error": { + "description": "Generic error title" + }, + "ok": "Tamam", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "SSS", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Destek ekibiyle iletişime geçin", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Kayıtlarınızı e-postayla gönderin", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Lütfen kayıtları şu adrese gönderin\n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "E-posta adresini kopyala", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Kayıtları dışa aktar", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "İptal Et", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Hata bildirin", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Bağlandı: {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Kaydet", + "@save": { + "description": "Label for save button" + }, + "send": "Gönder", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Bunu belleğinize mi kaydedeceksiniz (İndirilenler klasörü varsayılandır) yoksa diğer uygulamalara mı göndereceksiniz?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Bunu belleğinize kaydetmek ister misiniz? (İndirilenler klasörü varsayılandır)", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "Yeni e-posta adresinizi girin", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "E-Posta", + "@email": { + "description": "Email field label" + }, + "verify": "Doğrula", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Geçersiz e-posta adresi", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Lütfen geçerli bir e-posta adresi girin.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Lütfen bekleyin...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Şifreyi doğrulayın", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Yanlış şifre", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Lütfen tekrar deneyin", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Şifreyi girin", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Parolanızı girin", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Aktif oturumlar", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Hay aksi", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Bir şeyler ters gitti, lütfen tekrar deneyin", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Bu sizin bu cihazdaki oturumunuzu kapatacaktır!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Bu, aşağıdaki cihazdan çıkış yapmanızı sağlayacaktır:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Oturumu sonlandır?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Sonlandır", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Bu cihaz", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Hesap oluştur", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Zayıf", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Orta", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Güçlü", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Hesabı sil", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Sizin gittiğinizi görmekten üzüleceğiz. Bazı problemlerle mi karşılaşıyorsunuz?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Evet, geri bildirimi gönder", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Hayır, hesabı sil", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Hesap silme işlemini yapabilmek için lütfen kimliğinizi doğrulayın", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Hesap silme işlemini onayla", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Kullandığınız Ente uygulamaları varsa bu hesap diğer Ente uygulamalarıyla bağlantılıdır.\n\nTüm Ente uygulamalarına yüklediğiniz veriler ve hesabınız kalıcı olarak silinecektir.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Sil", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Yeni hesap oluşturun", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Şifre", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Şifreyi onayla", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Şifre gücü: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Ente'yi nereden duydunuz? (opsiyonel)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Biz uygulama kurulumlarını takip etmiyoruz. Bizi nereden duyduğunuzdan bahsetmeniz bize çok yardımcı olacak!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Kullanım şartlarını ve gizlilik politikasını kabul ediyorum", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Şartlar", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Gizlilik Politikası", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Eğer şifremi kaybedersem, verilerim uçtan uca şifrelendiğinden verilerimi kaybedebileceğimi anladım.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Şifreleme", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Giriş yapın", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Tekrar hoş geldiniz!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Giriş yaparak, kullanım şartları nı ve gizlilik politikası nı onaylıyorum", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "İnternet bağlantısı yok", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Lütfen internet bağlantınızı kontrol edin ve yeniden deneyin.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Doğrulama başarısız oldu, lütfen tekrar deneyin", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Şifreyi yeniden oluştur", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Mevcut cihaz şifrenizi doğrulayacak kadar güçlü değil, ancak tüm cihazlarla çalışacak şekilde yeniden oluşturabiliriz.\n\nLütfen kurtarma anahtarınızı kullanarak giriş yapın ve şifrenizi yeniden oluşturun (isterseniz aynı şifreyi tekrar kullanabilirsiniz).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Kurtarma anahtarını kullan", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Şifremi unuttum", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "E-posta adresini değiştir", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "E-posta adresini doğrulayın", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "{email} adresine bir posta gönderdik", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Şifrenizi sıfırlamak için lütfen önce e-postanızı doğrulayın.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Doğrulamayı tamamlamak için lütfen gelen kutunuzu (ve spam kutunuzu) kontrol edin", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Kodu girmek için dokunun", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "E-posta gönder", + "resendEmail": "E-postayı yeniden gönder", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Doğrulama hala bekliyor", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Oturum süresi doldu", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Oturum süreniz doldu. Tekrar giriş yapın.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Geçiş anahtarı doğrulaması", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Doğrulama bekleniyor...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Tekrar deneyin", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Durumu kontrol et", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "TOTP ile giriş yap", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Hesap kurtarma", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Şifre belirleyin", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Şifreyi değiştirin", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Şifreyi sıfırlayın", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Şifreleme anahtarları", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Verilerinizi şifrelemek için kullanabileceğimiz bir şifre girin", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Verilerinizi şifrelemek için kullanabileceğimiz yeni bir şifre girin", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Bu şifreyi saklamıyoruz, bu nedenle unutursanız, verilerinizin şifresini çözemeyiz", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Nasıl çalışır", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Şifreleme anahtarları oluşturuluyor...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Şifre başarıyla değiştirildi", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Diğer cihazlardan çıkış yap", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Eğer başka birisinin parolanızı bildiğini düşünüyorsanız, diğer tüm cihazları hesabınızdan çıkışa zorlayabilirsiniz.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Diğer cihazlardan çıkış yap", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Çıkış yapma", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Şifreleme anahtarları üretiliyor...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Devam et", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Güvenli olmayan cihaz", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Üzgünüz, bu cihazda güvenli anahtarlar oluşturamadık.\n\nlütfen farklı bir cihazdan kaydolun.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Kurtarma anahtarı panoya kopyalandı", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Kurtarma Anahtarı", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Eğer şifrenizi unutursanız, verilerinizi kurtarabileceğiniz tek yol bu anahtardır.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Biz bu anahtarı saklamıyoruz, lütfen. bu 24 kelimelik anahtarı güvenli bir yerde saklayın.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Bunu daha sonra yap", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Anahtarı kaydet", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Kurtarma anahtarı İndirilenler klasörüne kaydedildi!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Kurtarma anahtarınız yok mu?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "İki faktörlü kimlik doğrulama", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Kimlik doğrulayıcı uygulamanızdaki 6 haneli doğrulama kodunu girin", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Cihazınızı mı kaybettiniz?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Kurtarma anahtarınızı girin", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Kurtar", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Çıkış yapılıyor...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Hemen", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Uygulama kilidi", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Otomatik Kilit", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Sistem kilidi bulunamadı", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Cihaz kilidini etkinleştirmek için, lütfen cihaz şifresini veya ekran kilidini ayarlayın.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Cihazınızın varsayılan kilit ekranı ile PIN veya parola içeren özel bir kilit ekranı arasında seçim yapın.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Cihaz kilidi", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Pin kilidi", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Uygulamayı arka plana attıktan sonra kilitlendiği süre", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "İçeriği gizle", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Uygulama değiştiricide bulunan uygulama içeriğini gizler ve ekran görüntülerini devre dışı bırakır", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Uygulama değiştiricideki uygulama içeriğini gizler", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Çok fazla hatalı deneme", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Açmak için dokun", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Çıkış yapmak istediğinize emin misiniz?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Evet, çıkış yap", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Kodlarınızı görmek için lütfen kimlik doğrulaması yapın", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Sonraki", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Yeni şifre belirle", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "PIN Girin", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Yeni PIN belirleyin", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Doğrula", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Şifrenizi tekrar girin", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "PIN'inizi tekrar girin", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Kimliği doğrula", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Tanınmadı. Tekrar deneyin.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Başarılı", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "İptal et", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Kimlik doğrulaması gerekli", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biyometrik gerekli", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Cihaz kimlik bilgileri gerekli", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Cihaz kimlik bilgileri gerekmekte", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Ayarlara git", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Biyometrik kimlik doğrulama cihazınızda ayarlanmamış. Biyometrik kimlik doğrulama eklemek için 'Ayarlar > Güvenlik' bölümüne gidin.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Biyometrik kimlik doğrulama devre dışı. Etkinleştirmek için lütfen ekranınızı kilitleyin ve kilidini açın.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "Tamam", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "E-posta zaten kayıtlı.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "E-posta kayıtlı değil.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Bu e-posta zaten kullanılıyor", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "E-posta {newEmail} olarak değiştirildi", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Kimlik doğrulama başarısız oldu, lütfen tekrar deneyin", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Kimlik doğrulama başarılı!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Oturum süresi doldu", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Yanlış kurtarma kodu", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Girdiğiniz kurtarma kodu yanlış", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "İki faktörlü kimlik doğrulama başarıyla sıfırlandı", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Doğrulama kodunuzun süresi doldu", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Yanlış kod", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Üzgünüz, girdiğiniz kod yanlış", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Geliştirici ayarları", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Sunucu uç noktası", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Geçersiz uç nokta", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Üzgünüz, girdiğiniz uç nokta geçersiz. Lütfen geçerli bir uç nokta girin ve tekrar deneyin.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Uç nokta başarıyla güncellendi", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_uk.arb b/mobile/packages/strings/lib/l10n/arb/strings_uk.arb new file mode 100644 index 0000000000..432de3e5dd --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_uk.arb @@ -0,0 +1,712 @@ +{ + "networkHostLookUpErr": "Не вдалося приєднатися до Ente. Будь ласка, перевірте налаштування мережі. Зверніться до нашої команди підтримки, якщо помилка залишиться.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Не вдалося приєднатися до Ente. Будь ласка, спробуйте ще раз через деякий час. Якщо помилка не зникне, зв'яжіться з нашою командою підтримки.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Схоже, що щось пішло не так. Будь ласка, спробуйте ще раз через деякий час. Якщо помилка не зникне, зв'яжіться з нашою командою підтримки.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Помилка", + "@error": { + "description": "Generic error title" + }, + "ok": "Ок", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Часті питання", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Звернутися до служби підтримки", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Відправте ваші журнали електронною поштою", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Будь ласка, надішліть журнали до електронної пошти {toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Копіювати електронну адресу", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Експортувати журнал", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Скасувати", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Повідомити про помилку", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Приєднано до {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Зберегти", + "@save": { + "description": "Label for save button" + }, + "send": "Надіслати", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Чи хочете ви зберегти це до свого сховища (типово тека Downloads), чи надіслати його в інші застосунки?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Чи хочете Ви зберегти це до свого сховища (тека Downloads за замовчуванням)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "Адреса електронної пошти", + "@email": { + "description": "Email field label" + }, + "verify": "Перевірити", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Хибна адреса електронної пошти", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Введіть дійсну адресу електронної пошти.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Будь ласка, зачекайте...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Підтвердження пароля", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Невірний пароль", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Будь ласка, спробуйте ще раз", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Введіть пароль", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Введіть свій пароль", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Активні сеанси", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "От халепа", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Щось пішло не так, спробуйте, будь ласка, знову", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Це призведе до виходу на цьому пристрої!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Це призведе до виходу на наступному пристрої:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Припинити сеанс?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Припинити", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Цей пристрій", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Створити обліковий запис", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Слабкий", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Помірний", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Надійний", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Видалити обліковий запис", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Нам дуже шкода, що Ви залишаєте нас. Чи Ви зіткнулися з якоюсь проблемою?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Так, надіслати відгук", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Ні, видаліть мій обліковий запис", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Будь ласка, авторизуйтесь, щоб розпочати видалення облікового запису", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Підтвердіть видалення облікового запису", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Цей обліковий запис є зв'язаним з іншими програмами Ente, якщо ви використовуєте якісь з них.\n\nВаші завантажені дані у всіх програмах Ente будуть заплановані до видалення, а обліковий запис буде видалено назавжди.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Видалити", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Створити новий обліковий запис", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Пароль", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Підтвердити пароль", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Сила пароля: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Як ви дізналися про Ente? (опціонально)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Ми не відстежуємо встановлення застосунків. Але, якщо ви скажете нам, де ви нас знайшли, це допоможе!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Я приймаю умови використання і політику конфіденційності", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Умови", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Політика конфіденційності", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Я розумію, що якщо я втрачу свій пароль, я можу втратити свої дані, тому що вони є захищені наскрізним шифруванням.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Шифрування", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Увійти", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "З поверненням!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Натискаючи «Увійти», я приймаю умови використання і політику конфіденційності", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Немає підключення до Інтернету", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Будь ласка, перевірте підключення до Інтернету та спробуйте ще раз.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Перевірка не вдалася, спробуйте ще", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Повторно створити пароль", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Поточний пристрій не є достатньо потужним для підтвердження пароля, але ми можемо відновити його таким чином, щоб він працював на всіх пристроях.\n\nБудь ласка, увійдіть за допомогою вашого ключа відновлення і відновіть ваш пароль (ви можете знову використати той самий пароль, якщо бажаєте).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Застосувати ключ відновлення", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Нагадати пароль", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Змінити адресу електронної пошти", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Підтвердити електронну адресу", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Ми надіслали листа на адресу електронної пошти {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Щоб скинути пароль, будь ласка, спочатку підтвердіть адресу своєї електронної пошти.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Будь ласка, перевірте вашу скриньку електронної пошти (та спам), щоб завершити перевірку", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Натисніть, щоб ввести код", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Надіслати електронного листа", + "resendEmail": "Повторно надіслати лист на електронну пошту", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Підтвердження все ще в процесі", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Час сеансу минув", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Термін дії вашого сеансу завершився. Будь ласка, увійдіть знову.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Перевірка секретного ключа", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Очікується підтвердження...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Спробуйте ще раз", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Перевірити стан", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Увійти за допомогою TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Відновити обліковий запис", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Встановити пароль", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Змінити пароль", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Скинути пароль", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Ключі шифрування", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Введіть пароль, який ми зможемо використати для шифрування ваших даних", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Введіть новий пароль, який ми зможемо використати для шифрування ваших даних", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Ми не зберігаємо цей пароль, тому, якщо ви його забудете, ми не зможемо розшифрувати Ваші дані", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Як це працює", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Створення ключів шифрування...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Пароль успішно змінено", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Вийти на інших пристроях", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Якщо ви думаєте, що хтось може знати ваш пароль, ви можете примусити всі інші пристрої, які використовують ваш обліковий запис, вийти з нього.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Вийти на інших пристроях", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Не виходити", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Створення ключів шифрування...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Продовжити", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Незахищений пристрій", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "На жаль, нам не вдалося згенерувати захищені ключі на цьому пристрої.\n\nБудь ласка, увійдіть з іншого пристрою.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Ключ відновлення скопійований в буфер обміну", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Ключ відновлення", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Якщо ви забудете свій пароль, то єдиний спосіб відновити ваші дані – за допомогою цього ключа.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Ми не зберігаємо цей ключ, будь ласка, збережіть цей ключ з 24 слів в надійному місці.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Зробити це пізніше", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Зберегти ключ", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Ключ відновлення збережений у теці Downloads!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Немає ключа відновлення?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Двоетапна автентифікація", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Введіть нижче шестизначний код із застосунку для автентифікації", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Загубили пристрій?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Введіть ваш ключ відновлення", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Відновлення", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Вихід із системи...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Негайно", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Блокування", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Автоблокування", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Не знайдено системного блокування", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Для увімкнення блокування програми, будь ласка, налаштуйте пароль пристрою або блокування екрана в системних налаштуваннях.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Виберіть між типовим екраном блокування вашого пристрою та власним екраном блокування з PIN-кодом або паролем.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Блокування пристрою", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "PIN-код", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Час, через який застосунок буде заблоковано після розміщення у фоновому режимі", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Приховати вміст", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Приховує вміст програми у перемикачі застосунків і вимикає знімки екрану", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Приховує вміст у перемикачі застосунків", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Завелика кількість невірних спроб", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Доторкніться, щоб розблокувати", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Ви впевнені, що хочете вийти з системи?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Так, вийти з системи", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Будь ласка, пройдіть автентифікацію, щоб переглянути ваші секретні коди", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Наступний", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Встановити новий пароль", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Введіть PIN-код", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Встановити новий PIN-код", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Підтвердити", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Введіть пароль ще раз", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Введіть PIN-код ще раз", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Підтвердити ідентифікацію", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Не розпізнано. Спробуйте ще раз.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Успіх", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Скасувати", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Необхідна автентифікація", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Потрібна біометрична автентифікація", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Необхідні облікові дані пристрою", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Необхідні облікові дані пристрою", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Перейти до налаштувань", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Біометрична автентифікація не налаштована на вашому пристрої. Перейдіть в «Налаштування > Безпека», щоб додати біометричну автентифікацію.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Біометрична автентифікація вимкнена. Будь ласка, заблокуйте і розблокуйте свій екран, щоб увімкнути її.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "OK", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "thisEmailIsAlreadyInUse": "Ця адреса електронної пошти вже використовується", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Адресу електронної пошти змінено на {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Автентифікація не пройдена. Будь ласка, спробуйте ще раз", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Автентифікацію виконано!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Час сеансу минув", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Неправильний ключ відновлення", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Ви ввели неправильний ключ відновлення", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Двоетапна автентифікація успішно скинута", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Час дії коду підтвердження минув", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Невірний код", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Вибачте, але введений вами код є невірним", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Налаштування розробника", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Кінцева точка сервера", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Некоректна кінцева точка", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Вибачте, введена вами кінцева точка є недійсною. Введіть дійсну кінцеву точку та спробуйте ще раз.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Точка входу успішно оновлена", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_vi.arb b/mobile/packages/strings/lib/l10n/arb/strings_vi.arb new file mode 100644 index 0000000000..b571755af6 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_vi.arb @@ -0,0 +1,720 @@ +{ + "networkHostLookUpErr": "Không thể kết nối đến Ente, vui lòng kiểm tra lại kết nối mạng. Nếu vẫn còn lỗi, xin vui lòng liên hệ hỗ trợ.", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "Không thể kết nối đến Ente, vui lòng thử lại sau. Nếu vẫn còn lỗi, xin vui lòng liên hệ hỗ trợ.", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Có vẻ như đã xảy ra sự cố. Vui lòng thử lại sau một thời gian. Nếu lỗi vẫn tiếp diễn, vui lòng liên hệ với nhóm hỗ trợ của chúng tôi.", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "Lỗi", + "@error": { + "description": "Generic error title" + }, + "ok": "Đồng ý", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "Câu hỏi thường gặp", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "Liên hệ hỗ trợ", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "Gửi email nhật ký của bạn", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "Vui lòng gửi nhật ký đến \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "Sao chép địa chỉ email", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "Xuất nhật ký", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "Hủy", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "Báo cáo lỗi", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "Đã kết nối đến", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "Lưu", + "@save": { + "description": "Label for save button" + }, + "send": "Gửi", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "Bạn có muốn lưu vào bộ nhớ (Mặc định lưu vào thư mục Tải về) hoặc chuyển qua ứng dụng khác?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "Bạn có muốn lưu vào bộ nhớ không (Mặc định lưu vào thư mục Tải về)?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "Thư điện tử", + "@email": { + "description": "Email field label" + }, + "verify": "Xác minh", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "Địa chỉ email không hợp lệ", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "Xin vui lòng nhập một địa chỉ email hợp lệ.", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "Vui lòng chờ...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "Xác nhận mật khẩu", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "Mật khẩu không đúng", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "Vui lòng thử lại", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "Nhập mật khẩu", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "Nhập mật khẩu của bạn", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "Các phiên làm việc hiện tại", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "Rất tiếc", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "Phát hiện có lỗi, xin thử lại", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "Thao tác này sẽ đăng xuất bạn khỏi thiết bị này!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "Thao tác này sẽ đăng xuất bạn khỏi thiết bị sau:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "Kết thúc phiên?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "Kết thúc", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "Thiết bị này", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "Tạo tài khoản", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "Yếu", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "Trung bình", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "Mạnh", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "Xoá tài khoản", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "Chúng tôi sẽ rất tiếc khi thấy bạn đi. Bạn đang phải đối mặt với một số vấn đề?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "Có, gửi phản hồi", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "Không, xóa tài khoản", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "Vui lòng xác thực để bắt đầu xóa tài khoản", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "Xác nhận xóa tài khoản", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "Tài khoản này được liên kết với các ứng dụng Ente trên các nền tảng khác, nếu bạn có sử dụng.\n\nDữ liệu đã tải lên của bạn, trên mọi nền tảng, sẽ bị lên lịch xóa và tài khoản của bạn sẽ bị xóa vĩnh viễn.", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "Xóa", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "Tạo tài khoản mới", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "Mật khẩu", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "Xác nhận mật khẩu", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "Độ mạnh mật khẩu: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "Bạn biết đến Ente bằng cách nào? (không bắt buộc)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "Chúng tôi không theo dõi lượt cài đặt ứng dụng. Sẽ rất hữu ích nếu bạn cho chúng tôi biết nơi bạn tìm thấy chúng tôi!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "Tôi đồng ý với điều khoản dịch vụchính sách quyền riêng tư", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "Điều khoản", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "Chính sách bảo mật", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "Tôi hiểu rằng việc mất mật khẩu có thể đồng nghĩa với việc mất dữ liệu của tôi vì dữ liệu của tôi được mã hóa hai đầu.", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "Mã hóa", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "Đăng nhập", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "Chào mừng trở lại!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "Bằng cách nhấp vào đăng nhập, tôi đồng ý với điều khoản dịch vụchính sách quyền riêng tư", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "Không có kết nối Internet", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "Vui lòng kiểm tra kết nối internet của bạn và thử lại.", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "Mã xác nhận thất bại. Vui lòng thử lại", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "Tạo lại mật khẩu", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "Thiết bị hiện tại không đủ mạnh để xác minh mật khẩu của bạn nhưng chúng tôi có thể tạo lại mật khẩu theo cách hoạt động với tất cả các thiết bị.\n\nVui lòng đăng nhập bằng khóa khôi phục và tạo lại mật khẩu của bạn (bạn có thể sử dụng lại cùng một mật khẩu nếu muốn).", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "Dùng khóa khôi phục", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "Quên mật khẩu", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "Thay đổi email", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "Xác nhận địa chỉ Email", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "Chúng tôi đã gửi thư đến {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "Để đặt lại mật khẩu, vui lòng xác minh email của bạn trước.", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "Vui lòng kiểm tra hộp thư đến (và thư rác) của bạn để hoàn tất xác minh", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "Chạm để nhập mã", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "Gửi email", + "resendEmail": "Gửi lại email", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "Đang chờ xác thực", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "Phiên làm việc hết hạn", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "Phiên làm việc hết hạn. Vui lòng đăng nhập lại.", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "Xác minh mã khóa", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "Đang chờ xác thực", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "Thử lại", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "Kiểm tra trạng thái", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "Đăng nhập bằng TOTP", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "Khôi phục tài khoản", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "Đặt mật khẩu", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "Thay đổi mật khẩu", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "Đặt lại mật khẩu", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "Khóa mã hóa", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "Nhập mật khẩu mà chúng tôi có thể sử dụng để mã hóa dữ liệu của bạn", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "Nhập một mật khẩu mới mà chúng tôi có thể sử dụng để mã hóa dữ liệu của bạn", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "Chúng tôi không lưu trữ mật khẩu này, vì vậy nếu bạn quên, chúng tôi không thể giải mã dữ liệu của bạn", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "Cách thức hoạt động", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "Đang tạo khóa mã hóa...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "Thay đổi mật khẩu thành công", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "Đăng xuất khỏi các thiết bị khác", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "Nếu bạn cho rằng ai đó có thể biết mật khẩu của mình, bạn có thể buộc đăng xuất tất cả các thiết bị khác đang sử dụng tài khoản của mình.", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "Đăng xuất khỏi các thiết bị khác", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "Không được đăng xuất", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "Đang tạo khóa mã hóa...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "Tiếp tục", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "Thiết bị không an toàn", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Rất tiếc, chúng tôi không thể tạo khóa bảo mật trên thiết bị này.\n\nvui lòng đăng ký từ một thiết bị khác.", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "Đã sao chép khóa khôi phục vào bộ nhớ tạm", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "Khóa khôi phục", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "Nếu bạn quên mật khẩu, cách duy nhất bạn có thể khôi phục dữ liệu của mình là sử dụng khóa này.", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "Chúng tôi không lưu trữ khóa này, vui lòng lưu khóa 24 từ này ở nơi an toàn.", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "Để sau", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "Lưu khóa", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "Đã lưu khoá dự phòng vào thư mục Tải về!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "Không có khóa khôi phục?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "Xác thực hai yếu tố", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "Nhập mã gồm 6 chữ số từ ứng dụng xác thực của bạn", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "Mất thiết bị?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "Nhập khóa khôi phục của bạn", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "Khôi phục", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "Đang đăng xuất...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "Tức thì", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "Khóa ứng dụng", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "Tự động khóa", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "Không thấy khoá hệ thống", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "Để bật khoá thiết bị, vui lòng thiết lập mật khẩu thiết bị hoặc khóa màn hình trong cài đặt hệ thống của bạn.", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "Chọn giữa màn hình khoá mặc định của thiết bị và màn hình khoá tự chọn dùng mã PIN hoặc mật khẩu.", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "Khóa thiết bị", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Mã PIN", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "Thời gian ứng dụng tự khoá sau khi ở trạng thái nền", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "Ẩn nội dung", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "Ẩn nội dung khi chuyển ứng dụng và chặn chụp màn hình", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "Ẩn nội dung khi chuyển ứng dụng", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "Quá nhiều lần thử không chính xác", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "Nhấn để mở khóa", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "Bạn có chắc chắn muốn đăng xuất?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "Có, đăng xuất", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "Vui lòng xác thực để xem bí mật của bạn", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "Tiếp theo", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "Đặt lại mật khẩu", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "Nhập mã PIN", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "Đổi mã PIN", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "Xác nhận", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "Nhập lại mật khẩu", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "Nhập lại mã PIN", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "Xác định danh tính", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Không nhận dạng được. Vui lòng thử lại.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Thành công", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Hủy bỏ", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Yêu cầu xác thực", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Yêu cầu sinh trắc học", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Yêu cầu thông tin xác thực thiết bị", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Yêu cầu thông tin xác thực thiết bị", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Chuyển đến cài đặt", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "Xác thực sinh trắc học chưa được thiết lập trên thiết bị của bạn. Đi tới 'Cài đặt > Bảo mật' để thêm xác thực sinh trắc học.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "Xác thực sinh trắc học bị vô hiệu hóa. Vui lòng khóa và mở khóa màn hình của bạn để kích hoạt nó.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "Đồng ý", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "Email đã được đăng kí.", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "Email chưa được đăng kí.", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "Email này đã được sử dụng", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "Thay đổi email thành {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "Xác thực lỗi, vui lòng thử lại", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "Xác thực thành công!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "Phiên làm việc đã hết hạn", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "Khóa khôi phục không chính xác", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "Khóa khôi phục bạn đã nhập không chính xác", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "Xác thực hai bước được khôi phục thành công", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "Mã xác minh của bạn đã hết hạn", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "Mã không chính xác", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "Xin lỗi, mã bạn đã nhập không chính xác", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "Cài đặt cho nhà phát triển", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "Điểm cuối máy chủ", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "Điểm cuối không hợp lệ", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "Xin lỗi, điểm cuối bạn nhập không hợp lệ. Vui lòng nhập một điểm cuối hợp lệ và thử lại.", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "Cập nhật điểm cuối thành công", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_zh.arb b/mobile/packages/strings/lib/l10n/arb/strings_zh.arb new file mode 100644 index 0000000000..82f2b20bd1 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_zh.arb @@ -0,0 +1,720 @@ +{ + "networkHostLookUpErr": "无法连接到 Ente,请检查您的网络设置,如果错误仍然存​​在,请联系支持。", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "无法连接到 Ente,请稍后重试。如果错误仍然存​​在,请联系支持人员。", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "看起来出了点问题。 请稍后重试。 如果错误仍然存在,请联系我们的支持团队。", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "错误", + "@error": { + "description": "Generic error title" + }, + "ok": "确定", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "常见问题", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "联系支持", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "通过电子邮件发送您的日志", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "请将日志发送至 \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "复制电子邮件地址", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "导出日志", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "取消", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "报告错误", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "已连接至 {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "保存", + "@save": { + "description": "Label for save button" + }, + "send": "发送", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "您想将其保存到您的内置存储(默认情况下为“下载”文件夹)还是将其发送到其他应用程序?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "您想将其保存到您的内置存储中(默认情况下为“下载”文件夹)吗?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "email": "电子邮件地址", + "@email": { + "description": "Email field label" + }, + "verify": "验证", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "无效的电子邮件地址", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "请输入一个有效的电子邮件地址。", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "请稍候...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "验证密码", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "密码错误", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "请重试", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "输入密码", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "输入您的密码", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "已登录的设备", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "哎呀", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "出了点问题,请重试", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "这将使您登出该设备!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "这将使您登出以下设备:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "是否终止会话?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "终止", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "此设备", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "创建账户", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "弱", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "中", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "强", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "删除账户", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "我们很抱歉看到您离开。您面临一些问题?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "是,发送反馈", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "否,删除账户", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "请进行身份验证以启动账户删除", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "确认删除账户", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "如果您使用其他 Ente 应用程序,该账户将会与其他应用程序链接。\n\n在所有 Ente 应用程序中,您上传的数据将被安排用于删除,并且您的账户将被永久删除。", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "删除", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "创建新账号", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "密码", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "请确认密码", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "密码强度: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "您是怎么知道 Ente 的?(可选)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "我同意 服务条款隐私政策", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "服务条款", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "隐私政策", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "我明白,如果我丢失密码,我可能会丢失我的数据,因为我的数据是 端到端加密的。", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "加密", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "登录", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "欢迎回来!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "点击登录后,我同意 服务条款隐私政策", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "无互联网连接", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "请检查您的互联网连接,然后重试。", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "验证失败,请再试一次", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "重新创建密码", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "当前设备的功能不足以验证您的密码,但我们可以以适用于所有设备的方式重新生成。\n\n请使用您的恢复密钥登录并重新生成您的密码(如果您愿意,可以再次使用相同的密码)。", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "使用恢复密钥", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "忘记密码", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "修改邮箱", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "验证电子邮件", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "我们已经发送邮件到 {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "要重置您的密码,请先验证您的电子邮件。", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "请检查您的收件箱 (或者是在您的“垃圾邮件”列表内) 以完成验证", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "点击以输入代码", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "发送电子邮件", + "resendEmail": "重新发送电子邮件", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "仍需验证", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "会话已过期", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "您的会话已过期。请重新登录。", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "通行密钥验证", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "等待验证...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "请再试一次", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "检查状态", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "使用 TOTP 登录", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "恢复账户", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "设置密码", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "修改密码", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "重置密码", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "加密密钥", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "输入我们可以用来加密您的数据的密码", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "输入我们可以用来加密您的数据的新密码", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "我们不储存这个密码,所以如果忘记, 我们不能解密您的数据", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "工作原理", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "正在生成加密密钥...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "密码修改成功", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "从其他设备登出", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "如果您认为有人可能知道您的密码,您可以强制所有其他使用您账户的设备登出。", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "登出其他设备", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "不要登出", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "正在生成加密密钥...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "继续", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "设备不安全", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "抱歉,我们无法在此设备上生成安全密钥。\n\n请使用其他设备注册。", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "恢复密钥已复制到剪贴板", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "恢复密钥", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "如果您忘记了密码,恢复数据的唯一方法就是使用此密钥。", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "我们不会存储此密钥,请将此24个单词密钥保存在一个安全的地方。", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "稍后再做", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "保存密钥", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "恢复密钥已保存在下载文件夹中!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "没有恢复密钥吗?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "两步验证", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "从你的身份验证器应用中\n输入6位数字代码", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "丢失了设备吗?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "输入您的恢复密钥", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "恢复", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "正在登出...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "立即", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "应用锁", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "自动锁定", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "未找到系统锁", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "要启用设备锁,请在系统设置中设置设备密码或屏幕锁。", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "在设备的默认锁定屏幕和带有 PIN 或密码的自定义锁定屏幕之间进行选择。", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "设备锁", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Pin 锁定", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "应用程序进入后台后锁定的时间", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "隐藏内容", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "在应用切换器中隐藏应用内容并禁用屏幕截图", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "在应用切换器中隐藏应用内容", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "错误的尝试次数过多", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "点击解锁", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "您确定要登出吗?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "是的,登出", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "请进行身份验证以查看您的密钥", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "下一步", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "设置新密码", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "输入 PIN 码", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "设置新 PIN 码", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "确认", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "再次输入密码", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "再次输入 PIN 码", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "验证身份", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "未能识别,请重试。", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "成功", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "取消", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "需要进行身份验证", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "需要进行生物识别认证", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "需要设备凭据", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "需要设备凭据", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "前往设置", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "您的设备上未设置生物识别身份验证。转到“设置 > 安全”以添加生物识别身份验证。", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "生物识别身份验证已禁用。请锁定并解锁屏幕以启用该功能。", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "好", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "电子邮件地址已被注册。", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "电子邮件地址未注册。", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "该电子邮件已被使用", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "电子邮件已更改为 {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "认证失败,请重试", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "认证成功!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "会话已过期", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "恢复密钥不正确", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "您输入的恢复密钥不正确", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "两步验证已成功重置", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "您的验证码已过期", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "验证码错误", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "抱歉,您输入的验证码不正确", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "开发者设置", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "服务器端点", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "端点无效", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "抱歉,您输入的端点无效。请输入有效的端点,然后重试。", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "端点更新成功", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_zh_CN.arb b/mobile/packages/strings/lib/l10n/arb/strings_zh_CN.arb new file mode 100644 index 0000000000..a7fdb432a6 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_zh_CN.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "无法连接到 Ente,请检查您的网络设置,如果错误仍然存​​在,请联系支持。", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "无法连接到 Ente,请稍后重试。如果错误仍然存​​在,请联系支持人员。", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "看起来出了点问题。 请稍后重试。 如果错误仍然存在,请联系我们的支持团队。", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "错误", + "@error": { + "description": "Generic error title" + }, + "ok": "确定", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "常见问题", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "联系支持", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "通过电子邮件发送您的日志", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "请将日志发送至 \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "复制电子邮件地址", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "导出日志", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "取消", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "报告错误", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "已连接至 {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "保存", + "@save": { + "description": "Label for save button" + }, + "send": "发送", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "您想将其保存到您的内置存储(默认情况下为“下载”文件夹)还是将其发送到其他应用程序?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "您想将其保存到您的内置存储中(默认情况下为“下载”文件夹)吗?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "请输入您的新电子邮件地址", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "电子邮件地址", + "@email": { + "description": "Email field label" + }, + "verify": "验证", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "无效的电子邮件地址", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "请输入一个有效的电子邮件地址。", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "请稍候...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "验证密码", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "密码错误", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "请重试", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "输入密码", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "输入您的密码", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "已登录的设备", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "哎呀", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "出了点问题,请重试", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "这将使您登出该设备!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "这将使您登出以下设备:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "是否终止会话?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "终止", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "此设备", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "创建账户", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "弱", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "中", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "强", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "删除账户", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "我们很抱歉看到您离开。您面临一些问题?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "是,发送反馈", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "否,删除账户", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "请进行身份验证以启动账户删除", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "确认删除账户", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "如果您使用其他 Ente 应用程序,该账户将会与其他应用程序链接。\n\n在所有 Ente 应用程序中,您上传的数据将被安排用于删除,并且您的账户将被永久删除。", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "删除", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "创建新账号", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "密码", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "请确认密码", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "密码强度: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "您是怎么知道 Ente 的?(可选)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "我同意 服务条款隐私政策", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "服务条款", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "隐私政策", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "我明白,如果我丢失密码,我可能会丢失我的数据,因为我的数据是 端到端加密的。", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "加密", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "登录", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "欢迎回来!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "点击登录后,我同意 服务条款隐私政策", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "无互联网连接", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "请检查您的互联网连接,然后重试。", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "验证失败,请再试一次", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "重新创建密码", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "当前设备的功能不足以验证您的密码,但我们可以以适用于所有设备的方式重新生成。\n\n请使用您的恢复密钥登录并重新生成您的密码(如果您愿意,可以再次使用相同的密码)。", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "使用恢复密钥", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "忘记密码", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "修改邮箱", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "验证电子邮件", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "我们已经发送邮件到 {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "要重置您的密码,请先验证您的电子邮件。", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "请检查您的收件箱 (或者是在您的“垃圾邮件”列表内) 以完成验证", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "点击以输入代码", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "发送电子邮件", + "resendEmail": "重新发送电子邮件", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "仍需验证", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "会话已过期", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "您的会话已过期。请重新登录。", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "通行密钥验证", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "等待验证...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "请再试一次", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "检查状态", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "使用 TOTP 登录", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "恢复账户", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "设置密码", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "修改密码", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "重置密码", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "加密密钥", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "输入我们可以用来加密您的数据的密码", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "输入我们可以用来加密您的数据的新密码", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "我们不储存这个密码,所以如果忘记, 我们不能解密您的数据", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "工作原理", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "正在生成加密密钥...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "密码修改成功", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "从其他设备登出", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "如果您认为有人可能知道您的密码,您可以强制所有其他使用您账户的设备登出。", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "登出其他设备", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "不要登出", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "正在生成加密密钥...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "继续", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "设备不安全", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "抱歉,我们无法在此设备上生成安全密钥。\n\n请使用其他设备注册。", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "恢复密钥已复制到剪贴板", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "恢复密钥", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "如果您忘记了密码,恢复数据的唯一方法就是使用此密钥。", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "我们不会存储此密钥,请将此24个单词密钥保存在一个安全的地方。", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "稍后再做", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "保存密钥", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "恢复密钥已保存在下载文件夹中!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "没有恢复密钥吗?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "两步验证", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "从你的身份验证器应用中\n输入6位数字代码", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "丢失了设备吗?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "输入您的恢复密钥", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "恢复", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "正在登出...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "立即", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "应用锁", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "自动锁定", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "未找到系统锁", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "要启用设备锁,请在系统设置中设置设备密码或屏幕锁。", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "在设备的默认锁定屏幕和带有 PIN 或密码的自定义锁定屏幕之间进行选择。", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "设备锁", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Pin 锁定", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "应用程序进入后台后锁定的时间", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "隐藏内容", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "在应用切换器中隐藏应用内容并禁用屏幕截图", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "在应用切换器中隐藏应用内容", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "错误的尝试次数过多", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "点击解锁", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "您确定要登出吗?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "是的,登出", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "请进行身份验证以查看您的密钥", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "下一步", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "设置新密码", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "输入 PIN 码", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "设置新 PIN 码", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "确认", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "再次输入密码", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "再次输入 PIN 码", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "验证身份", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "未能识别,请重试。", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "成功", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "取消", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "需要进行身份验证", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "需要进行生物识别认证", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "需要设备凭据", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "需要设备凭据", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "前往设置", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "您的设备上未设置生物识别身份验证。转到“设置 > 安全”以添加生物识别身份验证。", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "生物识别身份验证已禁用。请锁定并解锁屏幕以启用该功能。", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "好", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "电子邮件地址已被注册。", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "电子邮件地址未注册。", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "该电子邮件已被使用", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "电子邮件已更改为 {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "认证失败,请重试", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "认证成功!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "会话已过期", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "恢复密钥不正确", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "您输入的恢复密钥不正确", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "两步验证已成功重置", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "您的验证码已过期", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "验证码错误", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "抱歉,您输入的验证码不正确", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "开发者设置", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "服务器端点", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "端点无效", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "抱歉,您输入的端点无效。请输入有效的端点,然后重试。", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "端点更新成功", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb b/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb new file mode 100644 index 0000000000..a5f30aab0e --- /dev/null +++ b/mobile/packages/strings/lib/l10n/arb/strings_zh_TW.arb @@ -0,0 +1,724 @@ +{ + "networkHostLookUpErr": "無法連接到 Ente,請檢查您的網路設定,如果錯誤仍然存​​在,請聯絡支援。", + "@networkHostLookUpErr": { + "description": "Error message shown when the app cannot connect to Ente due to network host lookup failure" + }, + "networkConnectionRefusedErr": "無法連接到 Ente,請稍後重試。如果錯誤仍然存​​在,請聯絡支援人員。", + "@networkConnectionRefusedErr": { + "description": "Error message shown when the app cannot connect to Ente due to connection refused" + }, + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "看起來出了點問題。 請稍後重試。 如果錯誤仍然存在,請聯絡我們的支援團隊。", + "@itLooksLikeSomethingWentWrongPleaseRetryAfterSome": { + "description": "Generic error message for temporary issues" + }, + "error": "錯誤", + "@error": { + "description": "Generic error title" + }, + "ok": "確定", + "@ok": { + "description": "Generic OK button label" + }, + "faq": "常見問題", + "@faq": { + "description": "FAQ link label" + }, + "contactSupport": "聯絡支援", + "@contactSupport": { + "description": "Contact support button label" + }, + "emailYourLogs": "通過電子郵件傳送您的日誌", + "@emailYourLogs": { + "description": "Title for emailing logs dialog" + }, + "pleaseSendTheLogsTo": "請將日誌傳送至 \n{toEmail}", + "@pleaseSendTheLogsTo": { + "description": "Message asking user to send logs to email address", + "placeholders": { + "toEmail": { + "type": "String", + "description": "Email address to send logs to" + } + } + }, + "copyEmailAddress": "複製電子郵件地址", + "@copyEmailAddress": { + "description": "Button to copy email address to clipboard" + }, + "exportLogs": "匯出日誌", + "@exportLogs": { + "description": "Button to export logs" + }, + "cancel": "取消", + "@cancel": { + "description": "Cancel button label" + }, + "reportABug": "報告錯誤", + "@reportABug": { + "description": "Label for reporting a bug" + }, + "customEndpoint": "已連接至 {endpoint}", + "@customEndpoint": { + "description": "Text showing user is connected to a custom endpoint", + "placeholders": { + "endpoint": { + "type": "String", + "description": "URL of the custom endpoint" + } + } + }, + "save": "儲存", + "@save": { + "description": "Label for save button" + }, + "send": "傳送", + "@send": { + "description": "Label for send button" + }, + "saveOrSendDescription": "您想將其儲存到您的內建儲存(預設情況下為“下載”資料夾)還是將其傳送到其他APP?", + "@saveOrSendDescription": { + "description": "Description text asking user if they want to save to storage or share with other apps" + }, + "saveOnlyDescription": "您想將其儲存到您的內建儲存中(預設情況下為“下載”資料夾)嗎?", + "@saveOnlyDescription": { + "description": "Description text asking user if they want to save to storage (for platforms that don't support sharing)" + }, + "enterNewEmailHint": "輸入你的新電子郵件地址", + "@enterNewEmailHint": { + "description": "Hint text for entering new email address" + }, + "email": "電子郵件地址", + "@email": { + "description": "Email field label" + }, + "verify": "驗證", + "@verify": { + "description": "Verify button label" + }, + "invalidEmailTitle": "無效的電子郵件地址", + "@invalidEmailTitle": { + "description": "Title for invalid email error dialog" + }, + "invalidEmailMessage": "請輸入一個有效的電子郵件地址。", + "@invalidEmailMessage": { + "description": "Message for invalid email error dialog" + }, + "pleaseWait": "請稍候...", + "@pleaseWait": { + "description": "Please wait message" + }, + "verifyPassword": "驗證密碼", + "@verifyPassword": { + "description": "Verify password button label" + }, + "incorrectPasswordTitle": "密碼錯誤", + "@incorrectPasswordTitle": { + "description": "Title for incorrect password error" + }, + "pleaseTryAgain": "請重試", + "@pleaseTryAgain": { + "description": "Message asking user to try again" + }, + "enterPassword": "輸入密碼", + "@enterPassword": { + "description": "Enter password field label" + }, + "enterYourPasswordHint": "輸入您的密碼", + "@enterYourPasswordHint": { + "description": "Hint for password field" + }, + "activeSessions": "已登錄的裝置", + "@activeSessions": { + "description": "Title for active sessions page" + }, + "oops": "哎呀", + "@oops": { + "description": "Oops error title" + }, + "somethingWentWrongPleaseTryAgain": "出了點問題,請重試", + "@somethingWentWrongPleaseTryAgain": { + "description": "Generic error message" + }, + "thisWillLogYouOutOfThisDevice": "這將使您登出該裝置!", + "@thisWillLogYouOutOfThisDevice": { + "description": "Warning message for logging out of current device" + }, + "thisWillLogYouOutOfTheFollowingDevice": "這將使您登出以下裝置:", + "@thisWillLogYouOutOfTheFollowingDevice": { + "description": "Warning message for logging out of another device" + }, + "terminateSession": "是否終止工作階段?", + "@terminateSession": { + "description": "Title for terminate session dialog" + }, + "terminate": "終止", + "@terminate": { + "description": "Terminate button label" + }, + "thisDevice": "此裝置", + "@thisDevice": { + "description": "Label for current device" + }, + "createAccount": "建立帳戶", + "@createAccount": { + "description": "Create account button label" + }, + "weakStrength": "弱", + "@weakStrength": { + "description": "Weak password strength label" + }, + "moderateStrength": "中", + "@moderateStrength": { + "description": "Moderate password strength label" + }, + "strongStrength": "強", + "@strongStrength": { + "description": "Strong password strength label" + }, + "deleteAccount": "刪除帳戶", + "@deleteAccount": { + "description": "Delete account button label" + }, + "deleteAccountQuery": "我們很抱歉看到您要刪除帳戶。您似乎面臨著一些問題?", + "@deleteAccountQuery": { + "description": "Message asking user why they want to delete account" + }, + "yesSendFeedbackAction": "是,傳送回饋", + "@yesSendFeedbackAction": { + "description": "Button to confirm sending feedback" + }, + "noDeleteAccountAction": "否,刪除帳戶", + "@noDeleteAccountAction": { + "description": "Button to proceed with account deletion" + }, + "initiateAccountDeleteTitle": "請進行身份驗證以啟動帳戶刪除", + "@initiateAccountDeleteTitle": { + "description": "Title for authentication dialog before account deletion" + }, + "confirmAccountDeleteTitle": "確認刪除帳戶", + "@confirmAccountDeleteTitle": { + "description": "Title for account deletion confirmation dialog" + }, + "confirmAccountDeleteMessage": "如果您使用其他 Ente APP,該帳戶將會與其他APP連結。\n\n在所有 Ente APP中,您上傳的資料將被安排用於刪除,並且您的帳戶將被永久刪除。", + "@confirmAccountDeleteMessage": { + "description": "Message warning about account deletion consequences" + }, + "delete": "刪除", + "@delete": { + "description": "Delete button label" + }, + "createNewAccount": "建立新帳號", + "@createNewAccount": { + "description": "Create new account button label" + }, + "password": "密碼", + "@password": { + "description": "Password field label" + }, + "confirmPassword": "請確認密碼", + "@confirmPassword": { + "description": "Confirm password field label" + }, + "passwordStrength": "密碼強度: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + } + }, + "hearUsWhereTitle": "您是怎麼知道 Ente 的?(可選)", + "@hearUsWhereTitle": { + "description": "Title asking how user heard about Ente" + }, + "hearUsExplanation": "我們不跟蹤APP安裝情況。如果您告訴我們您是在哪裡找到我們的,將會有所幫助!", + "@hearUsExplanation": { + "description": "Explanation for asking how user heard about Ente" + }, + "signUpTerms": "我同意 服務條款隱私政策", + "@signUpTerms": { + "description": "Terms agreement text for sign up" + }, + "termsOfServicesTitle": "服務條款", + "@termsOfServicesTitle": { + "description": "Terms of service title" + }, + "privacyPolicyTitle": "隱私政策", + "@privacyPolicyTitle": { + "description": "Privacy policy title" + }, + "ackPasswordLostWarning": "我明白,如果我遺失密碼,我可能會遺失我的資料,因為我的資料是 端到端加密的。", + "@ackPasswordLostWarning": { + "description": "Warning about password loss" + }, + "encryption": "加密", + "@encryption": { + "description": "Encryption label" + }, + "logInLabel": "登錄", + "@logInLabel": { + "description": "Log in button label" + }, + "welcomeBack": "歡迎回來!", + "@welcomeBack": { + "description": "Welcome back message" + }, + "loginTerms": "點選登錄後,我同意 服務條款隱私政策", + "@loginTerms": { + "description": "Terms agreement text for login" + }, + "noInternetConnection": "無網際網路連接", + "@noInternetConnection": { + "description": "No internet connection error message" + }, + "pleaseCheckYourInternetConnectionAndTryAgain": "請檢查您的網際網路連接,然後重試。", + "@pleaseCheckYourInternetConnectionAndTryAgain": { + "description": "Message asking user to check internet connection" + }, + "verificationFailedPleaseTryAgain": "驗證失敗,請再試一次", + "@verificationFailedPleaseTryAgain": { + "description": "Verification failed error message" + }, + "recreatePasswordTitle": "重新建立密碼", + "@recreatePasswordTitle": { + "description": "Title for recreate password dialog" + }, + "recreatePasswordBody": "目前裝置的功能不足以驗證您的密碼,但我們可以以適用於所有裝置的方式重新產生。\n\n請使用您的復原密鑰登錄並重新產生您的密碼(如果您願意,可以再次使用相同的密碼)。", + "@recreatePasswordBody": { + "description": "Body text for recreate password dialog" + }, + "useRecoveryKey": "使用復原密鑰", + "@useRecoveryKey": { + "description": "Use recovery key button label" + }, + "forgotPassword": "忘記密碼", + "@forgotPassword": { + "description": "Forgot password link label" + }, + "changeEmail": "修改信箱", + "@changeEmail": { + "description": "Change email button label" + }, + "verifyEmail": "驗證電子郵件", + "@verifyEmail": { + "description": "Verify email title" + }, + "weHaveSendEmailTo": "我們已經傳送郵件到 {email}", + "@weHaveSendEmailTo": { + "description": "Text to indicate that we have sent a mail to the user", + "placeholders": { + "email": { + "description": "The email address of the user", + "type": "String", + "example": "example@ente.io" + } + } + }, + "toResetVerifyEmail": "要重設您的密碼,請先驗證您的電子郵件。", + "@toResetVerifyEmail": { + "description": "Message asking user to verify email before password reset" + }, + "checkInboxAndSpamFolder": "請檢查您的收件箱 (或者是在您的“垃圾郵件”列表內) 以完成驗證", + "@checkInboxAndSpamFolder": { + "description": "Message asking user to check inbox and spam folder" + }, + "tapToEnterCode": "點選以輸入程式碼", + "@tapToEnterCode": { + "description": "Hint for entering verification code" + }, + "sendEmail": "傳送電子郵件", + "resendEmail": "重新傳送電子郵件", + "@resendEmail": { + "description": "Resend email button label" + }, + "passKeyPendingVerification": "仍需驗證", + "@passKeyPendingVerification": { + "description": "Message when passkey verification is pending" + }, + "loginSessionExpired": "工作階段已過期", + "@loginSessionExpired": { + "description": "Login session expired title" + }, + "loginSessionExpiredDetails": "您的工作階段已過期。請重新登錄。", + "@loginSessionExpiredDetails": { + "description": "Login session expired details" + }, + "passkeyAuthTitle": "通行金鑰驗證", + "@passkeyAuthTitle": { + "description": "Passkey authentication title" + }, + "waitingForVerification": "等待驗證...", + "@waitingForVerification": { + "description": "Waiting for verification message" + }, + "tryAgain": "請再試一次", + "@tryAgain": { + "description": "Try again button label" + }, + "checkStatus": "檢查狀態", + "@checkStatus": { + "description": "Check status button label" + }, + "loginWithTOTP": "使用 TOTP 登錄", + "@loginWithTOTP": { + "description": "Login with TOTP button label" + }, + "recoverAccount": "恢復帳戶", + "@recoverAccount": { + "description": "Recover account button label" + }, + "setPasswordTitle": "設定密碼", + "@setPasswordTitle": { + "description": "Set password title" + }, + "changePasswordTitle": "修改密碼", + "@changePasswordTitle": { + "description": "Change password title" + }, + "resetPasswordTitle": "重設密碼", + "@resetPasswordTitle": { + "description": "Reset password title" + }, + "encryptionKeys": "加密金鑰", + "@encryptionKeys": { + "description": "Encryption keys title" + }, + "enterPasswordToEncrypt": "輸入我們可以用來加密您的資料的密碼", + "@enterPasswordToEncrypt": { + "description": "Prompt to enter password for encryption" + }, + "enterNewPasswordToEncrypt": "輸入我們可以用來加密您的資料的新密碼", + "@enterNewPasswordToEncrypt": { + "description": "Prompt to enter new password for encryption" + }, + "passwordWarning": "我們不會儲存這個密碼,所以如果忘記, 我們無法解密您的資料", + "@passwordWarning": { + "description": "Warning about password storage" + }, + "howItWorks": "工作原理", + "@howItWorks": { + "description": "How it works button label" + }, + "generatingEncryptionKeys": "正在產生加密金鑰...", + "@generatingEncryptionKeys": { + "description": "Generating encryption keys message" + }, + "passwordChangedSuccessfully": "密碼修改成功", + "@passwordChangedSuccessfully": { + "description": "Password changed successfully message" + }, + "signOutFromOtherDevices": "從其他裝置登出", + "@signOutFromOtherDevices": { + "description": "Sign out from other devices title" + }, + "signOutOtherBody": "如果您認為有人可能知道您的密碼,您可以強制所有其他使用您帳戶的裝置登出。", + "@signOutOtherBody": { + "description": "Sign out other devices explanation" + }, + "signOutOtherDevices": "登出其他裝置", + "@signOutOtherDevices": { + "description": "Sign out other devices button label" + }, + "doNotSignOut": "不要登出", + "@doNotSignOut": { + "description": "Do not sign out button label" + }, + "generatingEncryptionKeysTitle": "正在產生加密金鑰...", + "@generatingEncryptionKeysTitle": { + "description": "Generating encryption keys title" + }, + "continueLabel": "繼續", + "@continueLabel": { + "description": "Continue button label" + }, + "insecureDevice": "裝置不安全", + "@insecureDevice": { + "description": "Insecure device warning title" + }, + "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "抱歉,我們無法在此裝置上產生安全金鑰。\n\n請使用其他裝置註冊。", + "@sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": { + "description": "Error message for insecure device" + }, + "recoveryKeyCopiedToClipboard": "復原密鑰已複製到剪貼簿", + "@recoveryKeyCopiedToClipboard": { + "description": "Recovery key copied message" + }, + "recoveryKey": "復原密鑰", + "@recoveryKey": { + "description": "Recovery key label" + }, + "recoveryKeyOnForgotPassword": "如果您忘記了密碼,恢復資料的唯一方法就是使用此金鑰。", + "@recoveryKeyOnForgotPassword": { + "description": "Recovery key importance explanation" + }, + "recoveryKeySaveDescription": "我們不會儲存此金鑰,請將此24個單詞金鑰儲存在一個安全的地方。", + "@recoveryKeySaveDescription": { + "description": "Recovery key save description" + }, + "doThisLater": "稍後再做", + "@doThisLater": { + "description": "Do this later button label" + }, + "saveKey": "儲存金鑰", + "@saveKey": { + "description": "Save key button label" + }, + "recoveryKeySaved": "復原密鑰已儲存在下載資料夾中!", + "@recoveryKeySaved": { + "description": "Recovery key saved confirmation message" + }, + "noRecoveryKeyTitle": "沒有復原密鑰嗎?", + "@noRecoveryKeyTitle": { + "description": "No recovery key title" + }, + "twoFactorAuthTitle": "二步驟驗證", + "@twoFactorAuthTitle": { + "description": "Two-factor authentication title" + }, + "enterCodeHint": "從你的身份驗證器APP中\n輸入6位數字程式碼", + "@enterCodeHint": { + "description": "Hint for entering 2FA code" + }, + "lostDeviceTitle": "遺失了裝置嗎?", + "@lostDeviceTitle": { + "description": "Lost device title" + }, + "enterRecoveryKeyHint": "輸入您的復原密鑰", + "@enterRecoveryKeyHint": { + "description": "Hint for entering recovery key" + }, + "recover": "恢復", + "@recover": { + "description": "Recover button label" + }, + "loggingOut": "正在登出...", + "@loggingOut": { + "description": "Message shown while logging out" + }, + "immediately": "立即", + "@immediately": { + "description": "Immediately option for auto lock timing" + }, + "appLock": "APP鎖", + "@appLock": { + "description": "App lock setting title" + }, + "autoLock": "自動鎖定", + "@autoLock": { + "description": "Auto lock setting title" + }, + "noSystemLockFound": "未找到系統鎖", + "@noSystemLockFound": { + "description": "Error when no system lock is found" + }, + "deviceLockEnablePreSteps": "要啟用裝置鎖,請在系統設定中設定裝置密碼或螢幕鎖。", + "@deviceLockEnablePreSteps": { + "description": "Instructions for enabling device lock" + }, + "appLockDescription": "在裝置的預設鎖定螢幕和帶有 PIN 或密碼的自訂鎖定螢幕之間進行選擇。", + "@appLockDescription": { + "description": "Description of app lock feature" + }, + "deviceLock": "裝置鎖", + "@deviceLock": { + "description": "Device lock option title" + }, + "pinLock": "Pin 鎖定", + "@pinLock": { + "description": "PIN lock option title" + }, + "autoLockFeatureDescription": "APP進入後台後鎖定的時間", + "@autoLockFeatureDescription": { + "description": "Description of auto lock feature" + }, + "hideContent": "隱藏內容", + "@hideContent": { + "description": "Hide content setting title" + }, + "hideContentDescriptionAndroid": "在APP切換器中隱藏APP內容並停用螢幕截圖", + "@hideContentDescriptionAndroid": { + "description": "Description of hide content feature on Android" + }, + "hideContentDescriptioniOS": "在APP切換器中隱藏APP內容", + "@hideContentDescriptioniOS": { + "description": "Description of hide content feature on iOS" + }, + "tooManyIncorrectAttempts": "錯誤的嘗試次數過多", + "@tooManyIncorrectAttempts": { + "description": "Message shown when too many incorrect attempts are made" + }, + "tapToUnlock": "點選解鎖", + "@tapToUnlock": { + "description": "Message prompting user to tap to unlock" + }, + "areYouSureYouWantToLogout": "您確定要登出嗎?", + "@areYouSureYouWantToLogout": { + "description": "Confirmation message before logout" + }, + "yesLogout": "是的,登出", + "@yesLogout": { + "description": "Confirmation button for logout" + }, + "authToViewSecrets": "請進行身份驗證以查看您的金鑰", + "@authToViewSecrets": { + "description": "Message prompting authentication to view secrets" + }, + "next": "下一步", + "@next": { + "description": "Next button label" + }, + "setNewPassword": "設定新密碼", + "@setNewPassword": { + "description": "Set new password title" + }, + "enterPin": "輸入 PIN 碼", + "@enterPin": { + "description": "Enter PIN prompt" + }, + "setNewPin": "設定新 PIN 碼", + "@setNewPin": { + "description": "Set new PIN title" + }, + "confirm": "確認", + "@confirm": { + "description": "Confirm button label" + }, + "reEnterPassword": "再次輸入密碼", + "@reEnterPassword": { + "description": "Re-enter password prompt" + }, + "reEnterPin": "再次輸入 PIN 碼", + "@reEnterPin": { + "description": "Re-enter PIN prompt" + }, + "androidBiometricHint": "驗證身份", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "未能辨識,請重試。", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "成功", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "取消", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "需要進行身份驗證", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "需要進行生物辨識認證", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "需要裝置憑據", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "需要裝置憑據", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "前往設定", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "您的裝置上未設定生物辨識身份驗證。轉到“設定 > 安全”以加入生物辨識身份驗證。", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "生物辨識身份驗證已停用。請鎖定並解鎖螢幕以啟用該功能。", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "好", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "emailAlreadyRegistered": "電子郵件地址已被註冊。", + "@emailAlreadyRegistered": { + "description": "Error message when email is already registered" + }, + "emailNotRegistered": "電子郵件地址未註冊。", + "@emailNotRegistered": { + "description": "Error message when email is not registered" + }, + "thisEmailIsAlreadyInUse": "該電子郵件已被使用", + "@thisEmailIsAlreadyInUse": { + "description": "Error message when email is already in use" + }, + "emailChangedTo": "電子郵件已更改為 {newEmail}", + "@emailChangedTo": { + "description": "Message when email has been changed", + "placeholders": { + "newEmail": { + "description": "The new email address", + "type": "String", + "example": "new@example.com" + } + } + }, + "authenticationFailedPleaseTryAgain": "認證失敗,請重試", + "@authenticationFailedPleaseTryAgain": { + "description": "Error message when authentication fails" + }, + "authenticationSuccessful": "認證成功!", + "@authenticationSuccessful": { + "description": "Success message when authentication is successful" + }, + "sessionExpired": "工作階段已過期", + "@sessionExpired": { + "description": "Error message when session has expired" + }, + "incorrectRecoveryKey": "復原密鑰不正確", + "@incorrectRecoveryKey": { + "description": "Error message when recovery key is incorrect" + }, + "theRecoveryKeyYouEnteredIsIncorrect": "您輸入的復原密鑰不正確", + "@theRecoveryKeyYouEnteredIsIncorrect": { + "description": "Detailed error message when recovery key is incorrect" + }, + "twofactorAuthenticationSuccessfullyReset": "二步驟驗證已成功重設", + "@twofactorAuthenticationSuccessfullyReset": { + "description": "Message when two-factor authentication is successfully reset" + }, + "yourVerificationCodeHasExpired": "您的驗證碼已過期", + "@yourVerificationCodeHasExpired": { + "description": "Error message when verification code has expired" + }, + "incorrectCode": "驗證碼錯誤", + "@incorrectCode": { + "description": "Error message when code is incorrect" + }, + "sorryTheCodeYouveEnteredIsIncorrect": "抱歉,您輸入的驗證碼不正確", + "@sorryTheCodeYouveEnteredIsIncorrect": { + "description": "Detailed error message when code is incorrect" + }, + "developerSettings": "開發者設定", + "@developerSettings": { + "description": "Label for developerSettings" + }, + "serverEndpoint": "伺服器端點", + "@serverEndpoint": { + "description": "Label for serverEndpoint" + }, + "invalidEndpoint": "端點無效", + "@invalidEndpoint": { + "description": "Label for invalidEndpoint" + }, + "invalidEndpointMessage": "抱歉,您輸入的端點無效。請輸入有效的端點,然後重試。", + "@invalidEndpointMessage": { + "description": "Label for invalidEndpointMessage" + }, + "endpointUpdatedMessage": "端點更新成功", + "@endpointUpdatedMessage": { + "description": "Label for endpointUpdatedMessage" + } +} \ No newline at end of file diff --git a/mobile/packages/strings/lib/l10n/strings_localizations.dart b/mobile/packages/strings/lib/l10n/strings_localizations.dart new file mode 100644 index 0000000000..afa91a9126 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations.dart @@ -0,0 +1,1468 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'strings_localizations_ar.dart'; +import 'strings_localizations_be.dart'; +import 'strings_localizations_bg.dart'; +import 'strings_localizations_ca.dart'; +import 'strings_localizations_cs.dart'; +import 'strings_localizations_da.dart'; +import 'strings_localizations_de.dart'; +import 'strings_localizations_el.dart'; +import 'strings_localizations_en.dart'; +import 'strings_localizations_es.dart'; +import 'strings_localizations_et.dart'; +import 'strings_localizations_fa.dart'; +import 'strings_localizations_fi.dart'; +import 'strings_localizations_fr.dart'; +import 'strings_localizations_gu.dart'; +import 'strings_localizations_he.dart'; +import 'strings_localizations_hi.dart'; +import 'strings_localizations_hu.dart'; +import 'strings_localizations_id.dart'; +import 'strings_localizations_it.dart'; +import 'strings_localizations_ja.dart'; +import 'strings_localizations_ka.dart'; +import 'strings_localizations_km.dart'; +import 'strings_localizations_ko.dart'; +import 'strings_localizations_lt.dart'; +import 'strings_localizations_lv.dart'; +import 'strings_localizations_ml.dart'; +import 'strings_localizations_nl.dart'; +import 'strings_localizations_pl.dart'; +import 'strings_localizations_pt.dart'; +import 'strings_localizations_ro.dart'; +import 'strings_localizations_ru.dart'; +import 'strings_localizations_sk.dart'; +import 'strings_localizations_sl.dart'; +import 'strings_localizations_sr.dart'; +import 'strings_localizations_sv.dart'; +import 'strings_localizations_ti.dart'; +import 'strings_localizations_tr.dart'; +import 'strings_localizations_uk.dart'; +import 'strings_localizations_vi.dart'; +import 'strings_localizations_zh.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of StringsLocalizations +/// returned by `StringsLocalizations.of(context)`. +/// +/// Applications need to include `StringsLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/strings_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: StringsLocalizations.localizationsDelegates, +/// supportedLocales: StringsLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the StringsLocalizations.supportedLocales +/// property. +abstract class StringsLocalizations { + StringsLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static StringsLocalizations of(BuildContext context) { + return Localizations.of( + context, + StringsLocalizations, + )!; + } + + static const LocalizationsDelegate delegate = + _StringsLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('ar'), + Locale('be'), + Locale('bg'), + Locale('ca'), + Locale('cs'), + Locale('da'), + Locale('de'), + Locale('el'), + Locale('en'), + Locale('es'), + Locale('et'), + Locale('fa'), + Locale('fi'), + Locale('fr'), + Locale('gu'), + Locale('he'), + Locale('hi'), + Locale('hu'), + Locale('id'), + Locale('it'), + Locale('ja'), + Locale('ka'), + Locale('km'), + Locale('ko'), + Locale('lt'), + Locale('lv'), + Locale('ml'), + Locale('nl'), + Locale('pl'), + Locale('pt'), + Locale('ro'), + Locale('ru'), + Locale('sk'), + Locale('sl'), + Locale('sr'), + Locale('sv'), + Locale('ti'), + Locale('tr'), + Locale('uk'), + Locale('vi'), + Locale('zh'), + Locale('zh', 'CN'), + Locale('zh', 'TW'), + ]; + + /// Error message shown when the app cannot connect to Ente due to network host lookup failure + /// + /// In en, this message translates to: + /// **'Unable to connect to Ente, please check your network settings and contact support if the error persists.'** + String get networkHostLookUpErr; + + /// Error message shown when the app cannot connect to Ente due to connection refused + /// + /// In en, this message translates to: + /// **'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'** + String get networkConnectionRefusedErr; + + /// Generic error message for temporary issues + /// + /// In en, this message translates to: + /// **'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'** + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome; + + /// Generic error title + /// + /// In en, this message translates to: + /// **'Error'** + String get error; + + /// Generic OK button label + /// + /// In en, this message translates to: + /// **'Ok'** + String get ok; + + /// FAQ link label + /// + /// In en, this message translates to: + /// **'FAQ'** + String get faq; + + /// Contact support button label + /// + /// In en, this message translates to: + /// **'Contact support'** + String get contactSupport; + + /// Title for emailing logs dialog + /// + /// In en, this message translates to: + /// **'Email your logs'** + String get emailYourLogs; + + /// Message asking user to send logs to email address + /// + /// In en, this message translates to: + /// **'Please send the logs to \n{toEmail}'** + String pleaseSendTheLogsTo(String toEmail); + + /// Button to copy email address to clipboard + /// + /// In en, this message translates to: + /// **'Copy email address'** + String get copyEmailAddress; + + /// Button to export logs + /// + /// In en, this message translates to: + /// **'Export logs'** + String get exportLogs; + + /// Cancel button label + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; + + /// Message showing email address for support + /// + /// In en, this message translates to: + /// **'Email us at {toEmail}'** + String pleaseEmailUsAt(String toEmail); + + /// Snackbar message when email address is copied + /// + /// In en, this message translates to: + /// **'Email address copied'** + String get emailAddressCopied; + + /// Default subject for support emails + /// + /// In en, this message translates to: + /// **'[Support]'** + String get supportEmailSubject; + + /// Label for debug information in emails + /// + /// In en, this message translates to: + /// **'Following information can help us in debugging if you are facing any issue'** + String get clientDebugInfoLabel; + + /// Label for registered email in debug info + /// + /// In en, this message translates to: + /// **'Registered email:'** + String get registeredEmailLabel; + + /// Label for client information in debug info + /// + /// In en, this message translates to: + /// **'Client:'** + String get clientLabel; + + /// Label for version information in debug info + /// + /// In en, this message translates to: + /// **'Version :'** + String get versionLabel; + + /// Not available text + /// + /// In en, this message translates to: + /// **'N/A'** + String get notAvailable; + + /// Label for reporting a bug + /// + /// In en, this message translates to: + /// **'Report a bug'** + String get reportABug; + + /// Body text for the logs dialog explaining what will be sent + /// + /// In en, this message translates to: + /// **'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'** + String get logsDialogBody; + + /// Button to view logs + /// + /// In en, this message translates to: + /// **'View logs'** + String get viewLogs; + + /// Text showing user is connected to a custom endpoint + /// + /// In en, this message translates to: + /// **'Connected to {endpoint}'** + String customEndpoint(String endpoint); + + /// Label for save button + /// + /// In en, this message translates to: + /// **'Save'** + String get save; + + /// Label for send button + /// + /// In en, this message translates to: + /// **'Send'** + String get send; + + /// Description text asking user if they want to save to storage or share with other apps + /// + /// In en, this message translates to: + /// **'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'** + String get saveOrSendDescription; + + /// Description text asking user if they want to save to storage (for platforms that don't support sharing) + /// + /// In en, this message translates to: + /// **'Do you want to save this to your storage (Downloads folder by default)?'** + String get saveOnlyDescription; + + /// Hint text for entering new email address + /// + /// In en, this message translates to: + /// **'Enter your new email address'** + String get enterNewEmailHint; + + /// Email field label + /// + /// In en, this message translates to: + /// **'Email'** + String get email; + + /// Verify button label + /// + /// In en, this message translates to: + /// **'Verify'** + String get verify; + + /// Title for invalid email error dialog + /// + /// In en, this message translates to: + /// **'Invalid email address'** + String get invalidEmailTitle; + + /// Message for invalid email error dialog + /// + /// In en, this message translates to: + /// **'Please enter a valid email address.'** + String get invalidEmailMessage; + + /// Please wait message + /// + /// In en, this message translates to: + /// **'Please wait...'** + String get pleaseWait; + + /// Verify password button label + /// + /// In en, this message translates to: + /// **'Verify password'** + String get verifyPassword; + + /// Title for incorrect password error + /// + /// In en, this message translates to: + /// **'Incorrect password'** + String get incorrectPasswordTitle; + + /// Message asking user to try again + /// + /// In en, this message translates to: + /// **'Please try again'** + String get pleaseTryAgain; + + /// Enter password field label + /// + /// In en, this message translates to: + /// **'Enter password'** + String get enterPassword; + + /// Hint for password field + /// + /// In en, this message translates to: + /// **'Enter your password'** + String get enterYourPasswordHint; + + /// Title for active sessions page + /// + /// In en, this message translates to: + /// **'Active sessions'** + String get activeSessions; + + /// Oops error title + /// + /// In en, this message translates to: + /// **'Oops'** + String get oops; + + /// Generic error message + /// + /// In en, this message translates to: + /// **'Something went wrong, please try again'** + String get somethingWentWrongPleaseTryAgain; + + /// Warning message for logging out of current device + /// + /// In en, this message translates to: + /// **'This will log you out of this device!'** + String get thisWillLogYouOutOfThisDevice; + + /// Warning message for logging out of another device + /// + /// In en, this message translates to: + /// **'This will log you out of the following device:'** + String get thisWillLogYouOutOfTheFollowingDevice; + + /// Title for terminate session dialog + /// + /// In en, this message translates to: + /// **'Terminate session?'** + String get terminateSession; + + /// Terminate button label + /// + /// In en, this message translates to: + /// **'Terminate'** + String get terminate; + + /// Label for current device + /// + /// In en, this message translates to: + /// **'This device'** + String get thisDevice; + + /// Create account button label + /// + /// In en, this message translates to: + /// **'Create account'** + String get createAccount; + + /// Weak password strength label + /// + /// In en, this message translates to: + /// **'Weak'** + String get weakStrength; + + /// Moderate password strength label + /// + /// In en, this message translates to: + /// **'Moderate'** + String get moderateStrength; + + /// Strong password strength label + /// + /// In en, this message translates to: + /// **'Strong'** + String get strongStrength; + + /// Delete account button label + /// + /// In en, this message translates to: + /// **'Delete account'** + String get deleteAccount; + + /// Message asking user why they want to delete account + /// + /// In en, this message translates to: + /// **'We\'ll be sorry to see you go. Are you facing some issue?'** + String get deleteAccountQuery; + + /// Button to confirm sending feedback + /// + /// In en, this message translates to: + /// **'Yes, send feedback'** + String get yesSendFeedbackAction; + + /// Button to proceed with account deletion + /// + /// In en, this message translates to: + /// **'No, delete account'** + String get noDeleteAccountAction; + + /// Title for authentication dialog before account deletion + /// + /// In en, this message translates to: + /// **'Please authenticate to initiate account deletion'** + String get initiateAccountDeleteTitle; + + /// Title for account deletion confirmation dialog + /// + /// In en, this message translates to: + /// **'Confirm account deletion'** + String get confirmAccountDeleteTitle; + + /// Message warning about account deletion consequences + /// + /// In en, this message translates to: + /// **'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.'** + String get confirmAccountDeleteMessage; + + /// Delete button label + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// Create new account button label + /// + /// In en, this message translates to: + /// **'Create new account'** + String get createNewAccount; + + /// Password field label + /// + /// In en, this message translates to: + /// **'Password'** + String get password; + + /// Confirm password field label + /// + /// In en, this message translates to: + /// **'Confirm password'** + String get confirmPassword; + + /// Text to indicate the password strength + /// + /// In en, this message translates to: + /// **'Password strength: {passwordStrengthValue}'** + String passwordStrength(String passwordStrengthValue); + + /// Title asking how user heard about Ente + /// + /// In en, this message translates to: + /// **'How did you hear about Ente? (optional)'** + String get hearUsWhereTitle; + + /// Explanation for asking how user heard about Ente + /// + /// In en, this message translates to: + /// **'We don\'t track app installs. It\'d help if you told us where you found us!'** + String get hearUsExplanation; + + /// Terms agreement text for sign up + /// + /// In en, this message translates to: + /// **'I agree to the terms of service and privacy policy'** + String get signUpTerms; + + /// Terms of service title + /// + /// In en, this message translates to: + /// **'Terms'** + String get termsOfServicesTitle; + + /// Privacy policy title + /// + /// In en, this message translates to: + /// **'Privacy Policy'** + String get privacyPolicyTitle; + + /// Warning about password loss + /// + /// In en, this message translates to: + /// **'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'** + String get ackPasswordLostWarning; + + /// Encryption label + /// + /// In en, this message translates to: + /// **'Encryption'** + String get encryption; + + /// Log in button label + /// + /// In en, this message translates to: + /// **'Log in'** + String get logInLabel; + + /// Welcome back message + /// + /// In en, this message translates to: + /// **'Welcome back!'** + String get welcomeBack; + + /// Terms agreement text for login + /// + /// In en, this message translates to: + /// **'By clicking log in, I agree to the terms of service and privacy policy'** + String get loginTerms; + + /// No internet connection error message + /// + /// In en, this message translates to: + /// **'No internet connection'** + String get noInternetConnection; + + /// Message asking user to check internet connection + /// + /// In en, this message translates to: + /// **'Please check your internet connection and try again.'** + String get pleaseCheckYourInternetConnectionAndTryAgain; + + /// Verification failed error message + /// + /// In en, this message translates to: + /// **'Verification failed, please try again'** + String get verificationFailedPleaseTryAgain; + + /// Title for recreate password dialog + /// + /// In en, this message translates to: + /// **'Recreate password'** + String get recreatePasswordTitle; + + /// Body text for recreate password dialog + /// + /// In en, this message translates to: + /// **'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'** + String get recreatePasswordBody; + + /// Use recovery key button label + /// + /// In en, this message translates to: + /// **'Use recovery key'** + String get useRecoveryKey; + + /// Forgot password link label + /// + /// In en, this message translates to: + /// **'Forgot password'** + String get forgotPassword; + + /// Change email button label + /// + /// In en, this message translates to: + /// **'Change email'** + String get changeEmail; + + /// Verify email title + /// + /// In en, this message translates to: + /// **'Verify email'** + String get verifyEmail; + + /// Text to indicate that we have sent a mail to the user + /// + /// In en, this message translates to: + /// **'We have sent a mail to {email}'** + String weHaveSendEmailTo(String email); + + /// Message asking user to verify email before password reset + /// + /// In en, this message translates to: + /// **'To reset your password, please verify your email first.'** + String get toResetVerifyEmail; + + /// Message asking user to check inbox and spam folder + /// + /// In en, this message translates to: + /// **'Please check your inbox (and spam) to complete verification'** + String get checkInboxAndSpamFolder; + + /// Hint for entering verification code + /// + /// In en, this message translates to: + /// **'Tap to enter code'** + String get tapToEnterCode; + + /// No description provided for @sendEmail. + /// + /// In en, this message translates to: + /// **'Send email'** + String get sendEmail; + + /// Resend email button label + /// + /// In en, this message translates to: + /// **'Resend email'** + String get resendEmail; + + /// Message when passkey verification is pending + /// + /// In en, this message translates to: + /// **'Verification is still pending'** + String get passKeyPendingVerification; + + /// Login session expired title + /// + /// In en, this message translates to: + /// **'Session expired'** + String get loginSessionExpired; + + /// Login session expired details + /// + /// In en, this message translates to: + /// **'Your session has expired. Please login again.'** + String get loginSessionExpiredDetails; + + /// Passkey authentication title + /// + /// In en, this message translates to: + /// **'Passkey verification'** + String get passkeyAuthTitle; + + /// Waiting for verification message + /// + /// In en, this message translates to: + /// **'Waiting for verification...'** + String get waitingForVerification; + + /// Try again button label + /// + /// In en, this message translates to: + /// **'Try again'** + String get tryAgain; + + /// Check status button label + /// + /// In en, this message translates to: + /// **'Check status'** + String get checkStatus; + + /// Login with TOTP button label + /// + /// In en, this message translates to: + /// **'Login with TOTP'** + String get loginWithTOTP; + + /// Recover account button label + /// + /// In en, this message translates to: + /// **'Recover account'** + String get recoverAccount; + + /// Set password title + /// + /// In en, this message translates to: + /// **'Set password'** + String get setPasswordTitle; + + /// Change password title + /// + /// In en, this message translates to: + /// **'Change password'** + String get changePasswordTitle; + + /// Reset password title + /// + /// In en, this message translates to: + /// **'Reset password'** + String get resetPasswordTitle; + + /// Encryption keys title + /// + /// In en, this message translates to: + /// **'Encryption keys'** + String get encryptionKeys; + + /// Prompt to enter password for encryption + /// + /// In en, this message translates to: + /// **'Enter a password we can use to encrypt your data'** + String get enterPasswordToEncrypt; + + /// Prompt to enter new password for encryption + /// + /// In en, this message translates to: + /// **'Enter a new password we can use to encrypt your data'** + String get enterNewPasswordToEncrypt; + + /// Warning about password storage + /// + /// In en, this message translates to: + /// **'We don\'t store this password, so if you forget, we cannot decrypt your data'** + String get passwordWarning; + + /// How it works button label + /// + /// In en, this message translates to: + /// **'How it works'** + String get howItWorks; + + /// Generating encryption keys message + /// + /// In en, this message translates to: + /// **'Generating encryption keys...'** + String get generatingEncryptionKeys; + + /// Password changed successfully message + /// + /// In en, this message translates to: + /// **'Password changed successfully'** + String get passwordChangedSuccessfully; + + /// Sign out from other devices title + /// + /// In en, this message translates to: + /// **'Sign out from other devices'** + String get signOutFromOtherDevices; + + /// Sign out other devices explanation + /// + /// In en, this message translates to: + /// **'If you think someone might know your password, you can force all other devices using your account to sign out.'** + String get signOutOtherBody; + + /// Sign out other devices button label + /// + /// In en, this message translates to: + /// **'Sign out other devices'** + String get signOutOtherDevices; + + /// Do not sign out button label + /// + /// In en, this message translates to: + /// **'Do not sign out'** + String get doNotSignOut; + + /// Generating encryption keys title + /// + /// In en, this message translates to: + /// **'Generating encryption keys...'** + String get generatingEncryptionKeysTitle; + + /// Continue button label + /// + /// In en, this message translates to: + /// **'Continue'** + String get continueLabel; + + /// Insecure device warning title + /// + /// In en, this message translates to: + /// **'Insecure device'** + String get insecureDevice; + + /// Error message for insecure device + /// + /// In en, this message translates to: + /// **'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'** + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease; + + /// Recovery key copied message + /// + /// In en, this message translates to: + /// **'Recovery key copied to clipboard'** + String get recoveryKeyCopiedToClipboard; + + /// Recovery key label + /// + /// In en, this message translates to: + /// **'Recovery key'** + String get recoveryKey; + + /// Recovery key importance explanation + /// + /// In en, this message translates to: + /// **'If you forget your password, the only way you can recover your data is with this key.'** + String get recoveryKeyOnForgotPassword; + + /// Recovery key save description + /// + /// In en, this message translates to: + /// **'We don\'t store this key, please save this 24 word key in a safe place.'** + String get recoveryKeySaveDescription; + + /// Do this later button label + /// + /// In en, this message translates to: + /// **'Do this later'** + String get doThisLater; + + /// Save key button label + /// + /// In en, this message translates to: + /// **'Save key'** + String get saveKey; + + /// Recovery key saved confirmation message + /// + /// In en, this message translates to: + /// **'Recovery key saved in Downloads folder!'** + String get recoveryKeySaved; + + /// No recovery key title + /// + /// In en, this message translates to: + /// **'No recovery key?'** + String get noRecoveryKeyTitle; + + /// Two-factor authentication title + /// + /// In en, this message translates to: + /// **'Two-factor authentication'** + String get twoFactorAuthTitle; + + /// Hint for entering 2FA code + /// + /// In en, this message translates to: + /// **'Enter the 6-digit code from\nyour authenticator app'** + String get enterCodeHint; + + /// Lost device title + /// + /// In en, this message translates to: + /// **'Lost device?'** + String get lostDeviceTitle; + + /// Hint for entering recovery key + /// + /// In en, this message translates to: + /// **'Enter your recovery key'** + String get enterRecoveryKeyHint; + + /// Recover button label + /// + /// In en, this message translates to: + /// **'Recover'** + String get recover; + + /// Message shown while logging out + /// + /// In en, this message translates to: + /// **'Logging out...'** + String get loggingOut; + + /// Immediately option for auto lock timing + /// + /// In en, this message translates to: + /// **'Immediately'** + String get immediately; + + /// App lock setting title + /// + /// In en, this message translates to: + /// **'App lock'** + String get appLock; + + /// Auto lock setting title + /// + /// In en, this message translates to: + /// **'Auto lock'** + String get autoLock; + + /// Error when no system lock is found + /// + /// In en, this message translates to: + /// **'No system lock found'** + String get noSystemLockFound; + + /// Instructions for enabling device lock + /// + /// In en, this message translates to: + /// **'To enable device lock, please setup device passcode or screen lock in your system settings.'** + String get deviceLockEnablePreSteps; + + /// Description of app lock feature + /// + /// In en, this message translates to: + /// **'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'** + String get appLockDescription; + + /// Device lock option title + /// + /// In en, this message translates to: + /// **'Device lock'** + String get deviceLock; + + /// PIN lock option title + /// + /// In en, this message translates to: + /// **'Pin lock'** + String get pinLock; + + /// Description of auto lock feature + /// + /// In en, this message translates to: + /// **'Time after which the app locks after being put in the background'** + String get autoLockFeatureDescription; + + /// Hide content setting title + /// + /// In en, this message translates to: + /// **'Hide content'** + String get hideContent; + + /// Description of hide content feature on Android + /// + /// In en, this message translates to: + /// **'Hides app content in the app switcher and disables screenshots'** + String get hideContentDescriptionAndroid; + + /// Description of hide content feature on iOS + /// + /// In en, this message translates to: + /// **'Hides app content in the app switcher'** + String get hideContentDescriptioniOS; + + /// Message shown when too many incorrect attempts are made + /// + /// In en, this message translates to: + /// **'Too many incorrect attempts'** + String get tooManyIncorrectAttempts; + + /// Message prompting user to tap to unlock + /// + /// In en, this message translates to: + /// **'Tap to unlock'** + String get tapToUnlock; + + /// Confirmation message before logout + /// + /// In en, this message translates to: + /// **'Are you sure you want to logout?'** + String get areYouSureYouWantToLogout; + + /// Confirmation button for logout + /// + /// In en, this message translates to: + /// **'Yes, logout'** + String get yesLogout; + + /// Message prompting authentication to view secrets + /// + /// In en, this message translates to: + /// **'Please authenticate to view your secrets'** + String get authToViewSecrets; + + /// Next button label + /// + /// In en, this message translates to: + /// **'Next'** + String get next; + + /// Set new password title + /// + /// In en, this message translates to: + /// **'Set new password'** + String get setNewPassword; + + /// Enter PIN prompt + /// + /// In en, this message translates to: + /// **'Enter PIN'** + String get enterPin; + + /// Set new PIN title + /// + /// In en, this message translates to: + /// **'Set new PIN'** + String get setNewPin; + + /// Confirm button label + /// + /// In en, this message translates to: + /// **'Confirm'** + String get confirm; + + /// Re-enter password prompt + /// + /// In en, this message translates to: + /// **'Re-enter password'** + String get reEnterPassword; + + /// Re-enter PIN prompt + /// + /// In en, this message translates to: + /// **'Re-enter PIN'** + String get reEnterPin; + + /// Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters. + /// + /// In en, this message translates to: + /// **'Verify identity'** + String get androidBiometricHint; + + /// Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters. + /// + /// In en, this message translates to: + /// **'Not recognized. Try again.'** + String get androidBiometricNotRecognized; + + /// Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters. + /// + /// In en, this message translates to: + /// **'Success'** + String get androidBiometricSuccess; + + /// Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters. + /// + /// In en, this message translates to: + /// **'Cancel'** + String get androidCancelButton; + + /// Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters. + /// + /// In en, this message translates to: + /// **'Authentication required'** + String get androidSignInTitle; + + /// Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters. + /// + /// In en, this message translates to: + /// **'Biometric required'** + String get androidBiometricRequiredTitle; + + /// Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters. + /// + /// In en, this message translates to: + /// **'Device credentials required'** + String get androidDeviceCredentialsRequiredTitle; + + /// Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side. + /// + /// In en, this message translates to: + /// **'Device credentials required'** + String get androidDeviceCredentialsSetupDescription; + + /// Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters. + /// + /// In en, this message translates to: + /// **'Go to settings'** + String get goToSettings; + + /// Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side. + /// + /// In en, this message translates to: + /// **'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'** + String get androidGoToSettingsDescription; + + /// Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side. + /// + /// In en, this message translates to: + /// **'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'** + String get iOSLockOut; + + /// Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters. + /// + /// In en, this message translates to: + /// **'OK'** + String get iOSOkButton; + + /// Error message when email is already registered + /// + /// In en, this message translates to: + /// **'Email already registered.'** + String get emailAlreadyRegistered; + + /// Error message when email is not registered + /// + /// In en, this message translates to: + /// **'Email not registered.'** + String get emailNotRegistered; + + /// Error message when email is already in use + /// + /// In en, this message translates to: + /// **'This email is already in use'** + String get thisEmailIsAlreadyInUse; + + /// Message when email has been changed + /// + /// In en, this message translates to: + /// **'Email changed to {newEmail}'** + String emailChangedTo(String newEmail); + + /// Error message when authentication fails + /// + /// In en, this message translates to: + /// **'Authentication failed, please try again'** + String get authenticationFailedPleaseTryAgain; + + /// Success message when authentication is successful + /// + /// In en, this message translates to: + /// **'Authentication successful!'** + String get authenticationSuccessful; + + /// Error message when session has expired + /// + /// In en, this message translates to: + /// **'Session expired'** + String get sessionExpired; + + /// Error message when recovery key is incorrect + /// + /// In en, this message translates to: + /// **'Incorrect recovery key'** + String get incorrectRecoveryKey; + + /// Detailed error message when recovery key is incorrect + /// + /// In en, this message translates to: + /// **'The recovery key you entered is incorrect'** + String get theRecoveryKeyYouEnteredIsIncorrect; + + /// Message when two-factor authentication is successfully reset + /// + /// In en, this message translates to: + /// **'Two-factor authentication successfully reset'** + String get twofactorAuthenticationSuccessfullyReset; + + /// Error message when no recovery key is found + /// + /// In en, this message translates to: + /// **'No recovery key'** + String get noRecoveryKey; + + /// Confirmation message when account has been deleted + /// + /// In en, this message translates to: + /// **'Your account has been deleted'** + String get yourAccountHasBeenDeleted; + + /// Label for verification ID + /// + /// In en, this message translates to: + /// **'Verification ID'** + String get verificationId; + + /// Error message when verification code has expired + /// + /// In en, this message translates to: + /// **'Your verification code has expired'** + String get yourVerificationCodeHasExpired; + + /// Error message when code is incorrect + /// + /// In en, this message translates to: + /// **'Incorrect code'** + String get incorrectCode; + + /// Detailed error message when code is incorrect + /// + /// In en, this message translates to: + /// **'Sorry, the code you\'ve entered is incorrect'** + String get sorryTheCodeYouveEnteredIsIncorrect; + + /// Label for developer settings + /// + /// In en, this message translates to: + /// **'Developer settings'** + String get developerSettings; + + /// Label for server endpoint setting + /// + /// In en, this message translates to: + /// **'Server endpoint'** + String get serverEndpoint; + + /// Error message when endpoint is invalid + /// + /// In en, this message translates to: + /// **'Invalid endpoint'** + String get invalidEndpoint; + + /// Detailed error message when endpoint is invalid + /// + /// In en, this message translates to: + /// **'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'** + String get invalidEndpointMessage; + + /// Success message when endpoint is updated + /// + /// In en, this message translates to: + /// **'Endpoint updated successfully'** + String get endpointUpdatedMessage; +} + +class _StringsLocalizationsDelegate + extends LocalizationsDelegate { + const _StringsLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture( + lookupStringsLocalizations(locale), + ); + } + + @override + bool isSupported(Locale locale) => [ + 'ar', + 'be', + 'bg', + 'ca', + 'cs', + 'da', + 'de', + 'el', + 'en', + 'es', + 'et', + 'fa', + 'fi', + 'fr', + 'gu', + 'he', + 'hi', + 'hu', + 'id', + 'it', + 'ja', + 'ka', + 'km', + 'ko', + 'lt', + 'lv', + 'ml', + 'nl', + 'pl', + 'pt', + 'ro', + 'ru', + 'sk', + 'sl', + 'sr', + 'sv', + 'ti', + 'tr', + 'uk', + 'vi', + 'zh', + ].contains(locale.languageCode); + + @override + bool shouldReload(_StringsLocalizationsDelegate old) => false; +} + +StringsLocalizations lookupStringsLocalizations(Locale locale) { + // Lookup logic when language+country codes are specified. + switch (locale.languageCode) { + case 'zh': + { + switch (locale.countryCode) { + case 'CN': + return StringsLocalizationsZhCn(); + case 'TW': + return StringsLocalizationsZhTw(); + } + break; + } + } + + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'ar': + return StringsLocalizationsAr(); + case 'be': + return StringsLocalizationsBe(); + case 'bg': + return StringsLocalizationsBg(); + case 'ca': + return StringsLocalizationsCa(); + case 'cs': + return StringsLocalizationsCs(); + case 'da': + return StringsLocalizationsDa(); + case 'de': + return StringsLocalizationsDe(); + case 'el': + return StringsLocalizationsEl(); + case 'en': + return StringsLocalizationsEn(); + case 'es': + return StringsLocalizationsEs(); + case 'et': + return StringsLocalizationsEt(); + case 'fa': + return StringsLocalizationsFa(); + case 'fi': + return StringsLocalizationsFi(); + case 'fr': + return StringsLocalizationsFr(); + case 'gu': + return StringsLocalizationsGu(); + case 'he': + return StringsLocalizationsHe(); + case 'hi': + return StringsLocalizationsHi(); + case 'hu': + return StringsLocalizationsHu(); + case 'id': + return StringsLocalizationsId(); + case 'it': + return StringsLocalizationsIt(); + case 'ja': + return StringsLocalizationsJa(); + case 'ka': + return StringsLocalizationsKa(); + case 'km': + return StringsLocalizationsKm(); + case 'ko': + return StringsLocalizationsKo(); + case 'lt': + return StringsLocalizationsLt(); + case 'lv': + return StringsLocalizationsLv(); + case 'ml': + return StringsLocalizationsMl(); + case 'nl': + return StringsLocalizationsNl(); + case 'pl': + return StringsLocalizationsPl(); + case 'pt': + return StringsLocalizationsPt(); + case 'ro': + return StringsLocalizationsRo(); + case 'ru': + return StringsLocalizationsRu(); + case 'sk': + return StringsLocalizationsSk(); + case 'sl': + return StringsLocalizationsSl(); + case 'sr': + return StringsLocalizationsSr(); + case 'sv': + return StringsLocalizationsSv(); + case 'ti': + return StringsLocalizationsTi(); + case 'tr': + return StringsLocalizationsTr(); + case 'uk': + return StringsLocalizationsUk(); + case 'vi': + return StringsLocalizationsVi(); + case 'zh': + return StringsLocalizationsZh(); + } + + throw FlutterError( + 'StringsLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart b/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart new file mode 100644 index 0000000000..cdffa58cc6 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_ar.dart @@ -0,0 +1,625 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Arabic (`ar`). +class StringsLocalizationsAr extends StringsLocalizations { + StringsLocalizationsAr([String locale = 'ar']) : super(locale); + + @override + String get networkHostLookUpErr => + 'تعذر الاتصال بـEnte، فضلا تحقق من إعدادات الشبكة الخاصة بك وتواصل مع الدعم إذا استمر الخطأ.'; + + @override + String get networkConnectionRefusedErr => + 'تعذر الإتصال بـEnte، فضلا أعد المحاولة لاحقا. إذا استمر الخطأ، فضلا تواصل مع الدعم.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'يبدو أنه حدث خطأ ما. الرجاء إعادة المحاولة لاحقا. إذا استمر الخطأ، يرجى الاتصال بفريق الدعم.'; + + @override + String get error => 'خطأ'; + + @override + String get ok => 'حسناً'; + + @override + String get faq => 'الأسئلة الأكثر شيوعاً'; + + @override + String get contactSupport => 'الاتصال بالدعم'; + + @override + String get emailYourLogs => 'إرسال السجلات عبر البريد الإلكتروني'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'الرجاء إرسال السجلات إلى $toEmail'; + } + + @override + String get copyEmailAddress => 'نسخ عنوان البريد الإلكتروني'; + + @override + String get exportLogs => 'تصدير السجلات'; + + @override + String get cancel => 'إلغاء'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'ألإبلاغ عن خلل تقني'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'متصل بـ$endpoint'; + } + + @override + String get save => 'حفظ'; + + @override + String get send => 'إرسال'; + + @override + String get saveOrSendDescription => + 'هل تريد حفظه إلى السعة التخزينية الخاصة بك (مجلد التنزيلات افتراضيا) أم إرساله إلى تطبيقات أخرى؟'; + + @override + String get saveOnlyDescription => + 'هل تريد حفظه إلى السعة التخزينية الخاصة بك (مجلد التنزيلات افتراضيا)؟'; + + @override + String get enterNewEmailHint => 'أدخل عنوان بريدك الإلكتروني الجديد'; + + @override + String get email => 'البريد الإلكتروني'; + + @override + String get verify => 'التحقق'; + + @override + String get invalidEmailTitle => 'عنوان البريد الإلكتروني غير صالح'; + + @override + String get invalidEmailMessage => 'الرجاء إدخال بريد إلكتروني صالح.'; + + @override + String get pleaseWait => 'انتظر قليلاً...'; + + @override + String get verifyPassword => 'التحقق من كلمة المرور'; + + @override + String get incorrectPasswordTitle => 'كلمة المرور غير صحيحة'; + + @override + String get pleaseTryAgain => 'يرجى المحاولة مرة أخرى'; + + @override + String get enterPassword => 'أدخل كلمة المرور'; + + @override + String get enterYourPasswordHint => 'أدخل كلمة المرور الخاصة بك'; + + @override + String get activeSessions => 'الجلسات النشطة'; + + @override + String get oops => 'عذرًا'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'حدث خطأ ما، يرجى المحاولة مرة أخرى'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'سيؤدي هذا إلى تسجيل خروجك من هذا الجهاز!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'سيؤدي هذا إلى تسجيل خروجك من هذا الجهاز:'; + + @override + String get terminateSession => 'إنهاء الجلسة؟'; + + @override + String get terminate => 'إنهاء'; + + @override + String get thisDevice => 'هذا الجهاز'; + + @override + String get createAccount => 'إنشاء حساب'; + + @override + String get weakStrength => 'ضعيف'; + + @override + String get moderateStrength => 'متوسط'; + + @override + String get strongStrength => 'قوي'; + + @override + String get deleteAccount => 'إزالة الحساب'; + + @override + String get deleteAccountQuery => + 'سوف نأسف لرؤيتك تذهب. هل تواجه بعض المشاكل؟'; + + @override + String get yesSendFeedbackAction => 'نعم، ارسل الملاحظات'; + + @override + String get noDeleteAccountAction => 'لا، حذف الحساب'; + + @override + String get initiateAccountDeleteTitle => 'الرجاء المصادقة لبدء حذف الحساب'; + + @override + String get confirmAccountDeleteTitle => 'تأكيد حذف الحساب'; + + @override + String get confirmAccountDeleteMessage => + 'هذا الحساب مرتبط بتطبيقات Ente أخرى، إذا كنت تستخدم أحدها.\n\nسنضع موعدا لحذف بياناتك المرفوعة عبر كل تطبيقات Ente، وسيتم حذف حسابك بصورة دائمة.'; + + @override + String get delete => 'حذف'; + + @override + String get createNewAccount => 'إنشاء حساب جديد'; + + @override + String get password => 'كلمة المرور'; + + @override + String get confirmPassword => 'تأكيد كلمة المرور'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'قوة كلمة المرور: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'كيف سمعت عن Ente؟ (اختياري)'; + + @override + String get hearUsExplanation => + 'نحن لا نتتبع تثبيت التطبيق. سيكون من المفيد إذا أخبرتنا أين وجدتنا!'; + + @override + String get signUpTerms => + 'أوافق على شروط الخدمة وسياسة الخصوصية'; + + @override + String get termsOfServicesTitle => 'الشروط'; + + @override + String get privacyPolicyTitle => 'سياسة الخصوصية'; + + @override + String get ackPasswordLostWarning => + 'أنا أفهم أنه إذا فقدت كلمة المرور الخاصة بي، قد أفقد بياناتي لأن بياناتي هي مشفرة من الند للند.'; + + @override + String get encryption => 'التشفير'; + + @override + String get logInLabel => 'تسجيل الدخول'; + + @override + String get welcomeBack => 'مرحبًا مجددًا!'; + + @override + String get loginTerms => + 'بالنقر على تسجيل الدخول، أوافق على شروط الخدمة و سياسة الخصوصية'; + + @override + String get noInternetConnection => 'لا يوجد اتصال بالإنترنت'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'يرجى التحقق من اتصالك بالإنترنت ثم المحاولة من جديد.'; + + @override + String get verificationFailedPleaseTryAgain => + 'فشل في المصادقة ، يرجى المحاولة مرة أخرى في وقت لاحق'; + + @override + String get recreatePasswordTitle => 'إعادة كتابة كلمة المرور'; + + @override + String get recreatePasswordBody => + 'الجهاز الحالي ليس قويًا بما يكفي للتحقق من كلمة المرور الخاصة بك، لذا نحتاج إلى إعادة إنشائها مرة واحدة بطريقة تعمل مع جميع الأجهزة.\n\nالرجاء تسجيل الدخول باستخدام مفتاح الاسترداد وإعادة إنشاء كلمة المرور الخاصة بك (يمكنك استخدام نفس كلمة المرور مرة أخرى إذا كنت ترغب في ذلك).'; + + @override + String get useRecoveryKey => 'استخدم مفتاح الاسترداد'; + + @override + String get forgotPassword => 'هل نسيت كلمة المرور'; + + @override + String get changeEmail => 'غير البريد الإلكتروني'; + + @override + String get verifyEmail => 'تأكيد البريد الإلكتروني'; + + @override + String weHaveSendEmailTo(String email) { + return 'لقد أرسلنا رسالة إلى $email'; + } + + @override + String get toResetVerifyEmail => + 'لإعادة تعيين كلمة المرور الخاصة بك، يرجى التحقق من بريدك الإلكتروني أولاً.'; + + @override + String get checkInboxAndSpamFolder => + 'الرجاء التحقق من صندوق الوارد (والرسائل غير المرغوب فيها) لإكمال التحقق'; + + @override + String get tapToEnterCode => 'انقر لإدخال الرمز'; + + @override + String get sendEmail => 'إرسال بريد إلكتروني'; + + @override + String get resendEmail => 'إعادة إرسال البريد الإلكتروني'; + + @override + String get passKeyPendingVerification => 'التحقق ما زال جارٍ'; + + @override + String get loginSessionExpired => 'انتهت صلاحية الجلسة'; + + @override + String get loginSessionExpiredDetails => + 'انتهت صلاحية جلستك. فضلا أعد تسجيل الدخول.'; + + @override + String get passkeyAuthTitle => 'التحقق من مفتاح المرور'; + + @override + String get waitingForVerification => 'بانتظار التحقق...'; + + @override + String get tryAgain => 'حاول مرة أخرى'; + + @override + String get checkStatus => 'تحقق من الحالة'; + + @override + String get loginWithTOTP => ''; + + @override + String get recoverAccount => 'إسترجاع الحساب'; + + @override + String get setPasswordTitle => 'تعيين كلمة المرور'; + + @override + String get changePasswordTitle => 'تغيير كلمة المرور'; + + @override + String get resetPasswordTitle => 'إعادة تعيين كلمة المرور'; + + @override + String get encryptionKeys => 'مفاتيح التشفير'; + + @override + String get enterPasswordToEncrypt => + 'أدخل كلمة المرور التي يمكننا استخدامها لتشفير بياناتك'; + + @override + String get enterNewPasswordToEncrypt => + 'أدخل كلمة مرور جديدة يمكننا استخدامها لتشفير بياناتك'; + + @override + String get passwordWarning => + 'نحن لا نقوم بتخزين كلمة المرور هذه، لذا إذا نسيتها، لا يمكننا فك تشفير بياناتك'; + + @override + String get howItWorks => 'كيف يعمل'; + + @override + String get generatingEncryptionKeys => 'توليد مفاتيح التشفير...'; + + @override + String get passwordChangedSuccessfully => 'تم تغيير كلمة المرور بنجاح'; + + @override + String get signOutFromOtherDevices => 'تسجيل الخروج من الأجهزة الأخرى'; + + @override + String get signOutOtherBody => + 'إذا كنت تعتقد أن شخصا ما يعرف كلمة المرور الخاصة بك، يمكنك إجبار جميع الأجهزة الأخرى الستخدمة حاليا لحسابك على تسجيل الخروج.'; + + @override + String get signOutOtherDevices => 'تسجيل الخروج من الأجهزة الأخرى'; + + @override + String get doNotSignOut => 'لا تقم بتسجيل الخروج'; + + @override + String get generatingEncryptionKeysTitle => 'توليد مفاتيح التشفير...'; + + @override + String get continueLabel => 'المتابعة'; + + @override + String get insecureDevice => 'جهاز غير آمن'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'عذرًا، لم نتمكن من إنشاء مفاتيح آمنة على هذا الجهاز.\n\nيرجى التسجيل من جهاز مختلف.'; + + @override + String get recoveryKeyCopiedToClipboard => 'تم نسخ عبارة الاسترداد للحافظة'; + + @override + String get recoveryKey => 'مفتاح الاسترداد'; + + @override + String get recoveryKeyOnForgotPassword => + 'إذا نسيت كلمة المرور الخاصة بك، فالطريقة الوحيدة التي يمكنك بها استرداد بياناتك هي بهذا المفتاح.'; + + @override + String get recoveryKeySaveDescription => + 'نحن لا نخزن هذا المفتاح، يرجى حفظ مفتاح الـ 24 كلمة هذا في مكان آمن.'; + + @override + String get doThisLater => 'قم بهذا لاحقاً'; + + @override + String get saveKey => 'حفظ المفتاح'; + + @override + String get recoveryKeySaved => 'حُفِظ مفتاح الاستعادة في مجلد التنزيلات!'; + + @override + String get noRecoveryKeyTitle => 'لا يوجد مفتاح استرجاع؟'; + + @override + String get twoFactorAuthTitle => 'المصادقة الثنائية'; + + @override + String get enterCodeHint => 'أدخل الرمز المكون من 6 أرقام من\nتطبيق المصادقة'; + + @override + String get lostDeviceTitle => 'جهاز مفقود ؟'; + + @override + String get enterRecoveryKeyHint => 'أدخل رمز الاسترداد'; + + @override + String get recover => 'استرداد'; + + @override + String get loggingOut => 'جاري تسجيل الخروج...'; + + @override + String get immediately => 'فورًا'; + + @override + String get appLock => 'قُفْل التطبيق'; + + @override + String get autoLock => 'قفل تلقائي'; + + @override + String get noSystemLockFound => 'لا يوجد قفل نظام'; + + @override + String get deviceLockEnablePreSteps => + 'لتفعيل قُفْل الجهاز، اضبط رمز مرور أو قُفْل الشاشة من الإعدادات'; + + @override + String get appLockDescription => 'اختر نوع قُفْل الشاشة: افتراضي أو مخصص.'; + + @override + String get deviceLock => 'قفل الجهاز'; + + @override + String get pinLock => 'قفل رقم التعريف الشخصي'; + + @override + String get autoLockFeatureDescription => + 'الوقت الذي بعده ينقفل التطبيق بعدما يوضع في الخلفية'; + + @override + String get hideContent => 'أخفِ المحتوى'; + + @override + String get hideContentDescriptionAndroid => + 'يخفي محتوى التطبيق في مبدل التطبيقات ويمنع لقطات الشاشة'; + + @override + String get hideContentDescriptioniOS => + 'يخفي محتوى التطبيق في مبدل التطبيقات'; + + @override + String get tooManyIncorrectAttempts => 'محاولات خاطئة أكثر من المسموح'; + + @override + String get tapToUnlock => 'المس لإلغاء القفل'; + + @override + String get areYouSureYouWantToLogout => + 'هل أنت متأكد من أنك تريد تسجيل الخروج؟'; + + @override + String get yesLogout => 'نعم، تسجيل الخروج'; + + @override + String get authToViewSecrets => + 'الرجاء المصادقة لعرض مفتاح الاسترداد الخاص بك'; + + @override + String get next => 'التالي'; + + @override + String get setNewPassword => 'عين كلمة مرور جديدة'; + + @override + String get enterPin => 'أدخل رقم التعريف الشخصي'; + + @override + String get setNewPin => 'عين رقم تعريف شخصي جديد'; + + @override + String get confirm => 'تأكيد'; + + @override + String get reEnterPassword => 'أعد إدخال كلمة المرور'; + + @override + String get reEnterPin => 'أعد إدخال رقم التعريف الشخصي'; + + @override + String get androidBiometricHint => 'التحقق من الهوية'; + + @override + String get androidBiometricNotRecognized => + 'لم يتم التعرف عليه. حاول مرة أخرى.'; + + @override + String get androidBiometricSuccess => 'تم بنجاح'; + + @override + String get androidCancelButton => 'إلغاء'; + + @override + String get androidSignInTitle => 'المصادقة مطلوبة'; + + @override + String get androidBiometricRequiredTitle => 'البيومترية مطلوبة'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'بيانات اعتماد الجهاز مطلوبة'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'بيانات اعتماد الجهاز مطلوبة'; + + @override + String get goToSettings => 'الانتقال إلى الإعدادات'; + + @override + String get androidGoToSettingsDescription => + 'لم يتم إعداد المصادقة الحيوية على جهازك. انتقل إلى \'الإعدادات > الأمن\' لإضافة المصادقة البيومترية.'; + + @override + String get iOSLockOut => + 'المصادقة البيومترية معطلة. الرجاء قفل الشاشة وفتح القفل لتفعيلها.'; + + @override + String get iOSOkButton => 'حسناً'; + + @override + String get emailAlreadyRegistered => 'البريد الإلكتروني مُسَجَّل من قبل.'; + + @override + String get emailNotRegistered => 'البريد الإلكتروني غير مُسَجَّل.'; + + @override + String get thisEmailIsAlreadyInUse => 'هذا البريد مستخدم مسبقاً'; + + @override + String emailChangedTo(String newEmail) { + return 'تم تغيير البريد الإلكتروني إلى $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'فشلت المصادقة. الرجاء المحاولة مرة أخرى'; + + @override + String get authenticationSuccessful => 'تمت المصادقة بنجاح!'; + + @override + String get sessionExpired => 'انتهت صَلاحِيَة الجِلسة'; + + @override + String get incorrectRecoveryKey => 'مفتاح الاسترداد غير صحيح'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'مفتاح الاسترداد الذي أدخلته غير صحيح'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'تم تحديث المصادقة الثنائية بنجاح'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => 'انتهت صلاحية رمز التحقق'; + + @override + String get incorrectCode => 'رمز غير صحيح'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'عذراً، الرمز الذي أدخلته غير صحيح'; + + @override + String get developerSettings => 'اعدادات المطور'; + + @override + String get serverEndpoint => 'نقطة طرف الخادم'; + + @override + String get invalidEndpoint => 'نقطة طرف غير صالحة'; + + @override + String get invalidEndpointMessage => + 'عذرا، نقطة الطرف التي أدخلتها غير صالحة. فضلا أدخل نقطة طرف صالحة وأعد المحاولة.'; + + @override + String get endpointUpdatedMessage => 'حُدِّثَت نقطة الطرف بنجاح'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_be.dart b/mobile/packages/strings/lib/l10n/strings_localizations_be.dart new file mode 100644 index 0000000000..3ff70f8c41 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_be.dart @@ -0,0 +1,626 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Belarusian (`be`). +class StringsLocalizationsBe extends StringsLocalizations { + StringsLocalizationsBe([String locale = 'be']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'Памылка'; + + @override + String get ok => 'OK'; + + @override + String get faq => 'Частыя пытанні'; + + @override + String get contactSupport => 'Звярнуцца ў службу падтрымкі'; + + @override + String get emailYourLogs => 'Адправіць журналы'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Please send the logs to \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copy email address'; + + @override + String get exportLogs => 'Экспартаваць журналы'; + + @override + String get cancel => 'Скасаваць'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Паведаміць аб памылцы'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'Захаваць'; + + @override + String get send => 'Адправіць'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Электронная пошта'; + + @override + String get verify => 'Праверыць'; + + @override + String get invalidEmailTitle => 'Invalid email address'; + + @override + String get invalidEmailMessage => 'Please enter a valid email address.'; + + @override + String get pleaseWait => 'Пачакайце...'; + + @override + String get verifyPassword => 'Праверыць пароль'; + + @override + String get incorrectPasswordTitle => 'Няправільны пароль'; + + @override + String get pleaseTryAgain => 'Калі ласка, паспрабуйце яшчэ раз'; + + @override + String get enterPassword => 'Увядзіце пароль'; + + @override + String get enterYourPasswordHint => 'Увядзіце ваш пароль'; + + @override + String get activeSessions => 'Актыўныя сеансы'; + + @override + String get oops => 'Вой'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Something went wrong, please try again'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'Terminate session?'; + + @override + String get terminate => 'Перарваць'; + + @override + String get thisDevice => 'Гэта прылада'; + + @override + String get createAccount => 'Стварыць уліковы запіс'; + + @override + String get weakStrength => 'Ненадзейны'; + + @override + String get moderateStrength => 'Умераная'; + + @override + String get strongStrength => 'Надзейны'; + + @override + String get deleteAccount => 'Выдаліць уліковы запіс'; + + @override + String get deleteAccountQuery => + 'We\'ll be sorry to see you go. Are you facing some issue?'; + + @override + String get yesSendFeedbackAction => 'Yes, send feedback'; + + @override + String get noDeleteAccountAction => 'Не, выдаліць уліковы запіс'; + + @override + String get initiateAccountDeleteTitle => + 'Please authenticate to initiate account deletion'; + + @override + String get confirmAccountDeleteTitle => 'Confirm account deletion'; + + @override + String get 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.'; + + @override + String get delete => 'Выдаліць'; + + @override + String get createNewAccount => 'Стварыць новы ўліковы запіс'; + + @override + String get password => 'Пароль'; + + @override + String get confirmPassword => 'Пацвердзіць пароль'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Password strength: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'We don\'t track app installs. It\'d help if you told us where you found us!'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Умовы'; + + @override + String get privacyPolicyTitle => 'Палітыка прыватнасці'; + + @override + String get ackPasswordLostWarning => + 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'; + + @override + String get encryption => 'Шыфраванне'; + + @override + String get logInLabel => 'Увайсці'; + + @override + String get welcomeBack => 'З вяртаннем!'; + + @override + String get loginTerms => + 'By clicking log in, I agree to the terms of service and privacy policy'; + + @override + String get noInternetConnection => 'No internet connection'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Please check your internet connection and try again.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Recreate password'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'Выкарыстоўваць ключ аднаўлення'; + + @override + String get forgotPassword => 'Забылі пароль'; + + @override + String get changeEmail => 'Змяніць адрас электроннай пошты'; + + @override + String get verifyEmail => 'Праверыць электронную пошту'; + + @override + String weHaveSendEmailTo(String email) { + return 'We have sent a mail to $email'; + } + + @override + String get toResetVerifyEmail => + 'To reset your password, please verify your email first.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Tap to enter code'; + + @override + String get sendEmail => 'Адправіць ліст'; + + @override + String get resendEmail => 'Адправіць ліст яшчэ раз'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Сеанс завяршыўся'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Паспрабуйце яшчэ раз'; + + @override + String get checkStatus => 'Праверыць статус'; + + @override + String get loginWithTOTP => 'Увайсці з TOTP'; + + @override + String get recoverAccount => 'Аднавіць уліковы запіс'; + + @override + String get setPasswordTitle => 'Задаць пароль'; + + @override + String get changePasswordTitle => 'Змяніць пароль'; + + @override + String get resetPasswordTitle => 'Скінуць пароль'; + + @override + String get encryptionKeys => 'Ключы шыфравання'; + + @override + String get enterPasswordToEncrypt => + 'Enter a password we can use to encrypt your data'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'Як гэта працуе'; + + @override + String get generatingEncryptionKeys => 'Generating encryption keys...'; + + @override + String get passwordChangedSuccessfully => 'Password changed successfully'; + + @override + String get signOutFromOtherDevices => 'Sign out from other devices'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'Sign out other devices'; + + @override + String get doNotSignOut => 'Не выходзіць'; + + @override + String get generatingEncryptionKeysTitle => 'Генерацыя ключоў шыфравання...'; + + @override + String get continueLabel => 'Працягнуць'; + + @override + String get insecureDevice => 'Небяспечная прылада'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => 'Recovery key copied to clipboard'; + + @override + String get recoveryKey => 'Ключ аднаўлення'; + + @override + String get recoveryKeyOnForgotPassword => + 'If you forget your password, the only way you can recover your data is with this key.'; + + @override + String get recoveryKeySaveDescription => + 'We don\'t store this key, please save this 24 word key in a safe place.'; + + @override + String get doThisLater => 'Зрабіць гэта пазней'; + + @override + String get saveKey => 'Захаваць ключ'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'No recovery key?'; + + @override + String get twoFactorAuthTitle => 'Two-factor authentication'; + + @override + String get enterCodeHint => + 'Enter the 6-digit code from\nyour authenticator app'; + + @override + String get lostDeviceTitle => 'Згубілі прыладу?'; + + @override + String get enterRecoveryKeyHint => 'Enter your recovery key'; + + @override + String get recover => 'Аднавіць'; + + @override + String get loggingOut => 'Выхад...'; + + @override + String get immediately => 'Адразу'; + + @override + String get appLock => 'Блакіроўка праграмы'; + + @override + String get autoLock => 'Аўтаблакіроўка'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Блакіроўка прылады'; + + @override + String get pinLock => 'Блакіроўка PIN\'ам'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Схаваць змест'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Націсніце для разблакіроўкі'; + + @override + String get areYouSureYouWantToLogout => 'Are you sure you want to logout?'; + + @override + String get yesLogout => 'Так, выйсці'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Далей'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'Увядзіце PIN-код'; + + @override + String get setNewPin => 'Задаць новы PIN'; + + @override + String get confirm => 'Пацвердзіць'; + + @override + String get reEnterPassword => 'Re-enter password'; + + @override + String get reEnterPin => 'Увядзіце PIN-код яшчэ раз'; + + @override + String get androidBiometricHint => 'Праверыць ідэнтыфікацыю'; + + @override + String get androidBiometricNotRecognized => 'Not recognized. Try again.'; + + @override + String get androidBiometricSuccess => 'Паспяхова'; + + @override + String get androidCancelButton => 'Скасаваць'; + + @override + String get androidSignInTitle => 'Authentication required'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Перайсці ў налады'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => 'This email is already in use'; + + @override + String emailChangedTo(String newEmail) { + return 'Email changed to $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'Сеанс завяршыўся'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Няправільны код'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart b/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart new file mode 100644 index 0000000000..a79fe0c723 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_bg.dart @@ -0,0 +1,633 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Bulgarian (`bg`). +class StringsLocalizationsBg extends StringsLocalizations { + StringsLocalizationsBg([String locale = 'bg']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Не може да се свърже с Ente, моля, проверете мрежовите си настройки и се свържете с поддръжката, ако проблемът продължава.'; + + @override + String get networkConnectionRefusedErr => + 'Не може да се свърже с Ente, моля, опитайте отново след известно време. Ако проблемът продължава, моля, свържете се с поддръжката.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Изглежда нещо се обърка. Моля, опитайте отново след известно време. Ако грешката продължава, моля, свържете се с нашия екип за поддръжка.'; + + @override + String get error => 'Грешка'; + + @override + String get ok => 'Ок'; + + @override + String get faq => 'ЧЗВ'; + + @override + String get contactSupport => 'Свържете се с поддръжката'; + + @override + String get emailYourLogs => 'Изпратете Вашата история на действията на имейл'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Моля, изпратете историята на действията на \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Копиране на имейл адрес'; + + @override + String get exportLogs => 'Експорт на файловете с историята'; + + @override + String get cancel => 'Отказ'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Докладване на проблем'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Свързан към $endpoint'; + } + + @override + String get save => 'Запазване'; + + @override + String get send => 'Изпращане'; + + @override + String get saveOrSendDescription => + 'Искате ли да запазите това в хранилището си (папка за Изтегляния по подразбиране) или да го изпратите на други приложения?'; + + @override + String get saveOnlyDescription => + 'Искате ли да запазите това в хранилището си (папка за Изтегляния по подразбиране)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Имейл'; + + @override + String get verify => 'Потвърждаване'; + + @override + String get invalidEmailTitle => 'Невалиден имейл адрес'; + + @override + String get invalidEmailMessage => 'Моля, въведете валиден имейл адрес.'; + + @override + String get pleaseWait => 'Моля изчакайте...'; + + @override + String get verifyPassword => 'Потвърдете паролата'; + + @override + String get incorrectPasswordTitle => 'Грешна парола'; + + @override + String get pleaseTryAgain => 'Опитайте отново'; + + @override + String get enterPassword => 'Въведете парола'; + + @override + String get enterYourPasswordHint => 'Въведете паролата си'; + + @override + String get activeSessions => 'Активни сесии'; + + @override + String get oops => 'Опа'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Нещо се обърка, моля опитайте отново'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Това ще Ви изкара от профила на това устройство!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Това ще Ви изкара от профила на следното устройство:'; + + @override + String get terminateSession => 'Прекратяване на сесията?'; + + @override + String get terminate => 'Прекратяване'; + + @override + String get thisDevice => 'Това устройство'; + + @override + String get createAccount => 'Създаване на акаунт'; + + @override + String get weakStrength => 'Слаба'; + + @override + String get moderateStrength => 'Умерена'; + + @override + String get strongStrength => 'Силна'; + + @override + String get deleteAccount => 'Изтриване на акаунта'; + + @override + String get deleteAccountQuery => + 'Ще съжаляваме да си тръгнете. Изправени ли сте пред някакъв проблем?'; + + @override + String get yesSendFeedbackAction => 'Да, изпращане на обратна връзка'; + + @override + String get noDeleteAccountAction => 'Не, изтриване на акаунта'; + + @override + String get initiateAccountDeleteTitle => + 'Моля, удостоверете се, за да инициирате изтриването на акаунта'; + + @override + String get confirmAccountDeleteTitle => 'Потвърдете изтриването на акаунта'; + + @override + String get confirmAccountDeleteMessage => + 'Този акаунт е свързан с други приложения на Ente, ако използвате такива.\n\nВашите качени данни във всички приложения на Ente ще бъдат планирани за изтриване и акаунтът Ви ще бъде изтрит за постоянно.'; + + @override + String get delete => 'Изтриване'; + + @override + String get createNewAccount => 'Създаване на нов акаунт'; + + @override + String get password => 'Парола'; + + @override + String get confirmPassword => 'Потвърждаване на паролата'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Сила на паролата: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Как научихте за Ente? (по избор)'; + + @override + String get hearUsExplanation => + 'Ние не проследяваме инсталиранията на приложения. Ще помогне, ако ни кажете къде ни намерихте!'; + + @override + String get signUpTerms => + 'Съгласявам се с условията за ползване и политиката за поверителност'; + + @override + String get termsOfServicesTitle => 'Условия'; + + @override + String get privacyPolicyTitle => 'Политика за поверителност'; + + @override + String get ackPasswordLostWarning => + 'Разбирам, че ако загубя паролата си, може да загубя данните си, тъй като данните ми са шифровани от край до край.'; + + @override + String get encryption => 'Шифроване'; + + @override + String get logInLabel => 'Вход'; + + @override + String get welcomeBack => 'Добре дошли отново!'; + + @override + String get loginTerms => + 'С натискането на вход, се съгласявам с условията за ползване и политиката за поверителност'; + + @override + String get noInternetConnection => 'Няма връзка с интернет'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Моля, проверете интернет връзката си и опитайте отново.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Неуспешно проверка, моля опитайте отново'; + + @override + String get recreatePasswordTitle => 'Създайте отново парола'; + + @override + String get recreatePasswordBody => + 'Текущото устройство не е достатъчно мощно, за да потвърди паролата Ви, но можем да я регенерираме по начин, който работи с всички устройства.\n\nМоля, влезте с Вашия ключ за възстановяване и генерирайте отново паролата си (можете да използвате същата отново, ако желаете).'; + + @override + String get useRecoveryKey => 'Използвайте ключ за възстановяване'; + + @override + String get forgotPassword => 'Забравена парола'; + + @override + String get changeEmail => 'Промяна на имейл'; + + @override + String get verifyEmail => 'Потвърдете имейла'; + + @override + String weHaveSendEmailTo(String email) { + return 'Изпратихме имейл до $email'; + } + + @override + String get toResetVerifyEmail => + 'За да нулирате паролата си, моля, първо потвърдете своя имейл.'; + + @override + String get checkInboxAndSpamFolder => + 'Моля, проверете входящата си поща (и спама), за да завършите проверката'; + + @override + String get tapToEnterCode => 'Докоснете, за да въведете код'; + + @override + String get sendEmail => 'Изпратете имейл'; + + @override + String get resendEmail => 'Повторно изпращане на имейл'; + + @override + String get passKeyPendingVerification => 'Потвърждението все още се изчаква'; + + @override + String get loginSessionExpired => 'Сесията изтече'; + + @override + String get loginSessionExpiredDetails => + 'Вашата сесия изтече. Моля влезте отново.'; + + @override + String get passkeyAuthTitle => 'Удостоверяване с ключ за парола'; + + @override + String get waitingForVerification => 'Изчаква се потвърждение...'; + + @override + String get tryAgain => 'Опитайте отново'; + + @override + String get checkStatus => 'Проверка на състоянието'; + + @override + String get loginWithTOTP => 'Влизане с еднократен код'; + + @override + String get recoverAccount => 'Възстановяване на акаунт'; + + @override + String get setPasswordTitle => 'Задаване на парола'; + + @override + String get changePasswordTitle => 'Промяна на паролата'; + + @override + String get resetPasswordTitle => 'Нулиране на паролата'; + + @override + String get encryptionKeys => 'Ключове за шифроване'; + + @override + String get enterPasswordToEncrypt => + 'Въведете парола, която да използваме за шифроване на Вашите данни'; + + @override + String get enterNewPasswordToEncrypt => + 'Въведете нова парола, която да използваме за шифроване на Вашите данни'; + + @override + String get passwordWarning => + 'Ние не съхраняваме тази парола, така че ако я забравите, не можем да дешифрираме Вашите данни'; + + @override + String get howItWorks => 'Как работи'; + + @override + String get generatingEncryptionKeys => + 'Генериране на ключове за шифроване...'; + + @override + String get passwordChangedSuccessfully => 'Паролата е променена успешно'; + + @override + String get signOutFromOtherDevices => 'Излизане от други устройства'; + + @override + String get signOutOtherBody => + 'Ако смятате, че някой може да знае паролата Ви, можете да принудите всички други устройства, използващи Вашия акаунт, да излязат.'; + + @override + String get signOutOtherDevices => 'Излизане от други устройства'; + + @override + String get doNotSignOut => 'Не излизайте'; + + @override + String get generatingEncryptionKeysTitle => + 'Генерират се ключове за шифроване...'; + + @override + String get continueLabel => 'Продължете'; + + @override + String get insecureDevice => 'Несигурно устройство'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'За съжаление не можахме да генерираме защитени ключове на това устройство.\n\nМоля, регистрирайте се от друго устройство.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Ключът за възстановяване е копиран в буферната памет'; + + @override + String get recoveryKey => 'Ключ за възстановяване'; + + @override + String get recoveryKeyOnForgotPassword => + 'Ако забравите паролата си, единственият начин да възстановите данните си е с този ключ.'; + + @override + String get recoveryKeySaveDescription => + 'Ние не съхраняваме този ключ, моля, запазете този ключ от 24 думи на сигурно място.'; + + @override + String get doThisLater => 'Направете това по-късно'; + + @override + String get saveKey => 'Запазване на ключа'; + + @override + String get recoveryKeySaved => + 'Ключът за възстановяване е запазен в папка за Изтегляния!'; + + @override + String get noRecoveryKeyTitle => 'Няма ключ за възстановяване?'; + + @override + String get twoFactorAuthTitle => 'Двуфакторно удостоверяване'; + + @override + String get enterCodeHint => + 'Въведете 6-цифрения код от\nВашето приложение за удостоверяване'; + + @override + String get lostDeviceTitle => 'Загубено устройство?'; + + @override + String get enterRecoveryKeyHint => 'Въведете Вашия ключ за възстановяване'; + + @override + String get recover => 'Възстановяване'; + + @override + String get loggingOut => 'Излизане от профила...'; + + @override + String get immediately => 'Незабавно'; + + @override + String get appLock => 'Заключване на приложението'; + + @override + String get autoLock => 'Автоматично заключване'; + + @override + String get noSystemLockFound => 'Не е намерено заключване на системата'; + + @override + String get deviceLockEnablePreSteps => + 'За да активирате заключването на устройството, моля, задайте парола за устройството или заключване на екрана в системните настройки.'; + + @override + String get appLockDescription => + 'Изберете между заключен екран по подразбиране на Вашето устройство и персонализиран заключен екран с ПИН код или парола.'; + + @override + String get deviceLock => 'Заключване на устройството'; + + @override + String get pinLock => 'Заключване с ПИН код'; + + @override + String get autoLockFeatureDescription => + 'Време, след което приложението се заключва, след като е поставено на заден план'; + + @override + String get hideContent => 'Скриване на съдържанието'; + + @override + String get hideContentDescriptionAndroid => + 'Скрива съдържанието на приложението в превключвателя на приложения и деактивира екранните снимки'; + + @override + String get hideContentDescriptioniOS => + 'Скрива съдържанието на приложението в превключвателя на приложения'; + + @override + String get tooManyIncorrectAttempts => 'Твърде много неуспешни опити'; + + @override + String get tapToUnlock => 'Докоснете, за да отключите'; + + @override + String get areYouSureYouWantToLogout => + 'Наистина ли искате да излезете от профила си?'; + + @override + String get yesLogout => 'Да, излез'; + + @override + String get authToViewSecrets => + 'Моля, удостоверете се, за да видите Вашите кодове'; + + @override + String get next => 'Следващ'; + + @override + String get setNewPassword => 'Задаване на нова парола'; + + @override + String get enterPin => 'Въведете ПИН код'; + + @override + String get setNewPin => 'Задаване на нов ПИН код'; + + @override + String get confirm => 'Потвърждаване'; + + @override + String get reEnterPassword => 'Въведете отново паролата'; + + @override + String get reEnterPin => 'Въведете отново ПИН кода'; + + @override + String get androidBiometricHint => 'Потвърждаване на самоличността'; + + @override + String get androidBiometricNotRecognized => + 'Не е разпознат. Опитайте отново.'; + + @override + String get androidBiometricSuccess => 'Успешно'; + + @override + String get androidCancelButton => 'Отказ'; + + @override + String get androidSignInTitle => 'Необходимо е удостоверяване'; + + @override + String get androidBiometricRequiredTitle => 'Изискват се биометрични данни'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Изискват се идентификационни данни за устройството'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Изискват се идентификационни данни за устройството'; + + @override + String get goToSettings => 'Отваряне на настройките'; + + @override + String get androidGoToSettingsDescription => + 'Биометричното удостоверяване не е настроено на Вашето устройство. Отидете на „Настройки > Сигурност“, за да добавите биометрично удостоверяване.'; + + @override + String get iOSLockOut => + 'Биометричното удостоверяване е деактивирано. Моля, заключете и отключете екрана си, за да го активирате.'; + + @override + String get iOSOkButton => 'ОК'; + + @override + String get emailAlreadyRegistered => 'Имейлът вече е регистриран.'; + + @override + String get emailNotRegistered => 'Имейлът не е регистриран.'; + + @override + String get thisEmailIsAlreadyInUse => 'Този имейл вече се използва'; + + @override + String emailChangedTo(String newEmail) { + return 'Имейлът е променен на $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Неуспешно удостоверяване, моля опитайте отново'; + + @override + String get authenticationSuccessful => 'Успешно удостоверяване!'; + + @override + String get sessionExpired => 'Сесията е изтекла'; + + @override + String get incorrectRecoveryKey => 'Неправилен ключ за възстановяване'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Въведеният от Вас ключ за възстановяване е неправилен'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Двуфакторното удостоверяване бе успешно нулирано'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Вашият код за потвърждение е изтекъл'; + + @override + String get incorrectCode => 'Неправилен код'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'За съжаление кодът, който сте въвели, е неправилен'; + + @override + String get developerSettings => 'Настройки за програмисти'; + + @override + String get serverEndpoint => 'Крайна точка на сървъра'; + + @override + String get invalidEndpoint => 'Невалидна крайна точка'; + + @override + String get invalidEndpointMessage => + 'За съжаление въведената от Вас крайна точка е невалидна. Моля, въведете валидна крайна точка и опитайте отново.'; + + @override + String get endpointUpdatedMessage => 'Крайната точка е актуализирана успешно'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_ca.dart b/mobile/packages/strings/lib/l10n/strings_localizations_ca.dart new file mode 100644 index 0000000000..e4078b94c0 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_ca.dart @@ -0,0 +1,635 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Catalan Valencian (`ca`). +class StringsLocalizationsCa extends StringsLocalizations { + StringsLocalizationsCa([String locale = 'ca']) : super(locale); + + @override + String get networkHostLookUpErr => + 'No s\'ha pogut connectar a Ente, si us plau, comprova la configuració de la xarxa i contacta amb suport si l\'error persisteix.'; + + @override + String get networkConnectionRefusedErr => + 'No s\'ha pogut connectar a Ente, si us plau, torna-ho a intentar després d\'un temps. Si l\'error persisteix, contacta amb suport.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Sembla que alguna cosa ha anat malament. Si us plau, torna-ho a intentar després d\'un temps. Si l\'error persisteix, contacta amb el nostre equip de suport.'; + + @override + String get error => 'Error'; + + @override + String get ok => 'D\'acord'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'Contacta amb suport'; + + @override + String get emailYourLogs => 'Envia els teus registres per correu'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Si us plau, envia els registres a \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copia l\'adreça de correu'; + + @override + String get exportLogs => 'Exporta els registres'; + + @override + String get cancel => 'Cancel·la'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Informa d\'un error'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connectat a $endpoint'; + } + + @override + String get save => 'Guarda'; + + @override + String get send => 'Envia'; + + @override + String get saveOrSendDescription => + 'Vols guardar-ho al teu emmagatzematge (per defecte, a la carpeta Descàrregues) o enviar-ho a altres aplicacions?'; + + @override + String get saveOnlyDescription => + 'Vols guardar-ho al teu emmagatzematge (per defecte, a la carpeta Descàrregues)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Correu electrònic'; + + @override + String get verify => 'Verifica'; + + @override + String get invalidEmailTitle => 'Adreça de correu electrònic no vàlida'; + + @override + String get invalidEmailMessage => + 'Si us plau, introdueix una adreça de correu electrònic vàlida.'; + + @override + String get pleaseWait => 'Si us plau, espera...'; + + @override + String get verifyPassword => 'Verifica la contrasenya'; + + @override + String get incorrectPasswordTitle => 'Contrasenya incorrecta'; + + @override + String get pleaseTryAgain => 'Si us plau, intenta-ho de nou'; + + @override + String get enterPassword => 'Introdueix la contrasenya'; + + @override + String get enterYourPasswordHint => 'Introdueix la teva contrasenya'; + + @override + String get activeSessions => 'Sessions actives'; + + @override + String get oops => 'Ups'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'S\'ha produït un error, si us plau, intenta-ho de nou'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Això tancarà la sessió en aquest dispositiu!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Això tancarà la sessió en el següent dispositiu:'; + + @override + String get terminateSession => 'Finalitzar sessió?'; + + @override + String get terminate => 'Finalitzar'; + + @override + String get thisDevice => 'Aquest dispositiu'; + + @override + String get createAccount => 'Crea un compte'; + + @override + String get weakStrength => 'Feble'; + + @override + String get moderateStrength => 'Moderada'; + + @override + String get strongStrength => 'Forta'; + + @override + String get deleteAccount => 'Elimina el compte'; + + @override + String get deleteAccountQuery => + 'Ens sabrà greu veure\'t marxar. Tens algun problema?'; + + @override + String get yesSendFeedbackAction => 'Sí, envia comentaris'; + + @override + String get noDeleteAccountAction => 'No, elimina el compte'; + + @override + String get initiateAccountDeleteTitle => + 'Si us plau, autentica\'t per iniciar l\'eliminació del compte'; + + @override + String get confirmAccountDeleteTitle => 'Confirma la supressió del compte'; + + @override + String get confirmAccountDeleteMessage => + 'Aquest compte està vinculat a altres apps d\'Ente, si en fas ús.\n\nLes dades pujades, a través de totes les apps d\'Ente, es programaran per a la supressió, i el teu compte s\'eliminarà permanentment.'; + + @override + String get delete => 'Elimina'; + + @override + String get createNewAccount => 'Crea un nou compte'; + + @override + String get password => 'Contrasenya'; + + @override + String get confirmPassword => 'Confirma la contrasenya'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Força de la contrasenya: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Com vas conèixer Ente? (opcional)'; + + @override + String get hearUsExplanation => + 'No fem seguiment de les instal·lacions de l\'app. Ens ajudaria saber on ens has trobat!'; + + @override + String get signUpTerms => + 'Estic d\'acord amb els termes del servei i la política de privacitat'; + + @override + String get termsOfServicesTitle => 'Termes'; + + @override + String get privacyPolicyTitle => 'Política de privacitat'; + + @override + String get ackPasswordLostWarning => + 'Entenc que si perdo la meva contrasenya, puc perdre les meves dades ja que les meves dades estan xifrades d\'extrem a extrem.'; + + @override + String get encryption => 'Xifratge'; + + @override + String get logInLabel => 'Inicia sessió'; + + @override + String get welcomeBack => 'Benvingut de nou!'; + + @override + String get loginTerms => + 'En fer clic a iniciar sessió, estic d\'acord amb els termes del servei i la política de privacitat'; + + @override + String get noInternetConnection => 'Sense connexió a Internet'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Comprova la connexió a Internet i torna-ho a intentar.'; + + @override + String get verificationFailedPleaseTryAgain => + 'La verificació ha fallat, intenta-ho de nou'; + + @override + String get recreatePasswordTitle => 'Recrea la contrasenya'; + + @override + String get recreatePasswordBody => + 'El dispositiu actual no és prou potent per verificar la teva contrasenya, però podem regenerar-la d\'una manera que funcioni amb tots els dispositius.\n\nSi us plau, inicia sessió utilitzant la teva clau de recuperació i regenera la teva contrasenya (pots tornar a utilitzar la mateixa si ho desitges).'; + + @override + String get useRecoveryKey => 'Usa la clau de recuperació'; + + @override + String get forgotPassword => 'Has oblidat la contrasenya'; + + @override + String get changeEmail => 'Canvia el correu electrònic'; + + @override + String get verifyEmail => 'Verifica el correu electrònic'; + + @override + String weHaveSendEmailTo(String email) { + return 'Hem enviat un correu a $email'; + } + + @override + String get toResetVerifyEmail => + 'Per restablir la teva contrasenya, si us plau verifica primer el teu correu electrònic.'; + + @override + String get checkInboxAndSpamFolder => + 'Comprova la teva safata d\'entrada (i el correu no desitjat) per completar la verificació'; + + @override + String get tapToEnterCode => 'Toca per introduir el codi'; + + @override + String get sendEmail => 'Envia correu electrònic'; + + @override + String get resendEmail => 'Reenviar correu electrònic'; + + @override + String get passKeyPendingVerification => 'La verificació encara està pendent'; + + @override + String get loginSessionExpired => 'Sessió caducada'; + + @override + String get loginSessionExpiredDetails => + 'La teva sessió ha caducat. Torna a iniciar sessió.'; + + @override + String get passkeyAuthTitle => 'Verificació per passkey'; + + @override + String get waitingForVerification => 'Esperant verificació...'; + + @override + String get tryAgain => 'Intenta-ho de nou'; + + @override + String get checkStatus => 'Comprova l\'estat'; + + @override + String get loginWithTOTP => 'Inici de sessió amb TOTP'; + + @override + String get recoverAccount => 'Recupera el compte'; + + @override + String get setPasswordTitle => 'Configura la contrasenya'; + + @override + String get changePasswordTitle => 'Canvia la contrasenya'; + + @override + String get resetPasswordTitle => 'Restableix la contrasenya'; + + @override + String get encryptionKeys => 'Claus de xifratge'; + + @override + String get enterPasswordToEncrypt => + 'Introdueix una contrasenya que puguem utilitzar per xifrar les teves dades'; + + @override + String get enterNewPasswordToEncrypt => + 'Introdueix una nova contrasenya que puguem utilitzar per xifrar les teves dades'; + + @override + String get passwordWarning => + 'No guardem aquesta contrasenya, per tant, si l\'oblides, no podrem desxifrar les teves dades'; + + @override + String get howItWorks => 'Com funciona'; + + @override + String get generatingEncryptionKeys => 'Generant claus de xifratge...'; + + @override + String get passwordChangedSuccessfully => + 'La contrasenya s\'ha canviat amb èxit'; + + @override + String get signOutFromOtherDevices => 'Tanca sessió en altres dispositius'; + + @override + String get signOutOtherBody => + 'Si creus que algú pot saber la teva contrasenya, pots forçar tots els altres dispositius que usen el teu compte a tancar sessió.'; + + @override + String get signOutOtherDevices => 'Tancar sessió en altres dispositius'; + + @override + String get doNotSignOut => 'No tancar sessió'; + + @override + String get generatingEncryptionKeysTitle => + 'Generant claus d\'encriptació...'; + + @override + String get continueLabel => 'Continua'; + + @override + String get insecureDevice => 'Dispositiu no segur'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Ho sentim, no hem pogut generar claus segures en aquest dispositiu.\n\nSi us plau, registra\'t des d\'un altre dispositiu.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'La clau de recuperació s\'ha copiat al porta-retalls'; + + @override + String get recoveryKey => 'Clau de recuperació'; + + @override + String get recoveryKeyOnForgotPassword => + 'Si oblides la teva contrasenya, l\'única manera de recuperar les teves dades és amb aquesta clau.'; + + @override + String get recoveryKeySaveDescription => + 'No guardem aquesta clau, si us plau, guarda aquesta clau de 24 paraules en un lloc segur.'; + + @override + String get doThisLater => 'Fes-ho més tard'; + + @override + String get saveKey => 'Guarda la clau'; + + @override + String get recoveryKeySaved => + 'Clau de recuperació guardada a la carpeta Descàrregues!'; + + @override + String get noRecoveryKeyTitle => 'No tens clau de recuperació?'; + + @override + String get twoFactorAuthTitle => 'Autenticació de dos factors'; + + @override + String get enterCodeHint => + 'Introdueix el codi de 6 dígits de\nl\'aplicació d\'autenticació'; + + @override + String get lostDeviceTitle => 'Dispositiu perdut?'; + + @override + String get enterRecoveryKeyHint => 'Introdueix la teva clau de recuperació'; + + @override + String get recover => 'Recupera'; + + @override + String get loggingOut => 'Tancant sessió...'; + + @override + String get immediately => 'Immediatament'; + + @override + String get appLock => 'Bloqueig de l\'aplicació'; + + @override + String get autoLock => 'Bloqueig automàtic'; + + @override + String get noSystemLockFound => 'No s\'ha trobat cap bloqueig del sistema'; + + @override + String get deviceLockEnablePreSteps => + 'Per habilitar el bloqueig de dispositiu, configura un codi o bloqueig de pantalla en la configuració del sistema.'; + + @override + String get appLockDescription => + 'Tria entre el bloqueig predeterminat del dispositiu o un bloqueig personalitzat amb PIN o contrasenya.'; + + @override + String get deviceLock => 'Bloqueig del dispositiu'; + + @override + String get pinLock => 'Bloqueig amb PIN'; + + @override + String get autoLockFeatureDescription => + 'Temps després del qual l\'app es bloqueja quan es posa en segon pla'; + + @override + String get hideContent => 'Amaga el contingut'; + + @override + String get hideContentDescriptionAndroid => + 'Amaga el contingut d\'aquesta app en el commutador d\'apps del sistema i desactiva les captures de pantalla'; + + @override + String get hideContentDescriptioniOS => + 'Amaga el contingut d\'aquesta app en el commutador d\'apps del sistema'; + + @override + String get tooManyIncorrectAttempts => 'Massa intents incorrectes'; + + @override + String get tapToUnlock => 'Toca per desbloquejar'; + + @override + String get areYouSureYouWantToLogout => 'Segur que vols tancar la sessió?'; + + @override + String get yesLogout => 'Sí, tanca la sessió'; + + @override + String get authToViewSecrets => + 'Si us plau, autentica\'t per veure els teus secrets'; + + @override + String get next => 'Següent'; + + @override + String get setNewPassword => 'Estableix una nova contrasenya'; + + @override + String get enterPin => 'Introdueix el PIN'; + + @override + String get setNewPin => 'Estableix un nou PIN'; + + @override + String get confirm => 'Confirma'; + + @override + String get reEnterPassword => 'Torna a introduir la contrasenya'; + + @override + String get reEnterPin => 'Torna a introduir el PIN'; + + @override + String get androidBiometricHint => 'Verifica la identitat'; + + @override + String get androidBiometricNotRecognized => + 'No reconegut. Torna-ho a provar.'; + + @override + String get androidBiometricSuccess => 'Correcte'; + + @override + String get androidCancelButton => 'Cancel·la'; + + @override + String get androidSignInTitle => 'Es requereix autenticació'; + + @override + String get androidBiometricRequiredTitle => 'Biometria necessària'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Credencials del dispositiu requerides'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Es requereixen credencials del dispositiu'; + + @override + String get goToSettings => 'Ves a configuració'; + + @override + String get androidGoToSettingsDescription => + 'L\'autenticació biomètrica no està configurada al teu dispositiu. Ves a \'Configuració > Seguretat\' per afegir autenticació biomètrica.'; + + @override + String get iOSLockOut => + 'L\'autenticació biomètrica està desactivada. Bloqueja i desbloqueja la pantalla per activar-la.'; + + @override + String get iOSOkButton => 'D\'acord'; + + @override + String get emailAlreadyRegistered => + 'El correu electrònic ja està registrat.'; + + @override + String get emailNotRegistered => 'El correu electrònic no està registrat.'; + + @override + String get thisEmailIsAlreadyInUse => + 'Aquest correu electrònic ja està en ús'; + + @override + String emailChangedTo(String newEmail) { + return 'Correu electrònic canviat a $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Autenticació fallida, intenta-ho de nou'; + + @override + String get authenticationSuccessful => 'Autenticació amb èxit!'; + + @override + String get sessionExpired => 'La sessió ha caducat'; + + @override + String get incorrectRecoveryKey => 'Clau de recuperació incorrecta'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'La clau de recuperació que has introduït és incorrecta'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Autenticació de dos factors restablerta amb èxit'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'El teu codi de verificació ha expirat'; + + @override + String get incorrectCode => 'Codi incorrecte'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Ho sentim, el codi que has introduït és incorrecte'; + + @override + String get developerSettings => 'Configuració de desenvolupador'; + + @override + String get serverEndpoint => 'Endpoint del servidor'; + + @override + String get invalidEndpoint => 'Endpoint no vàlid'; + + @override + String get invalidEndpointMessage => + 'Ho sentim, l\'endpoint que has introduït no és vàlid. Introdueix un endpoint vàlid i torna-ho a intentar.'; + + @override + String get endpointUpdatedMessage => 'Extrem actualitzat correctament'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart b/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart new file mode 100644 index 0000000000..f72957d5d5 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_cs.dart @@ -0,0 +1,627 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Czech (`cs`). +class StringsLocalizationsCs extends StringsLocalizations { + StringsLocalizationsCs([String locale = 'cs']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Nelze se připojit k Ente, zkontrolujte, prosím, nastavení své sítě a kontaktujte podporu, pokud chyba přetrvává'; + + @override + String get networkConnectionRefusedErr => + 'Nepodařilo se připojit k Ente, zkuste to po nějaké době znovu. Pokud chyba přetrvává, kontaktujte, prosím, podporu.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Vypadá to, že se něco pokazilo. Zkuste to prosím znovu po nějaké době. Pokud chyba přetrvává, kontaktujte prosím naši podporu.'; + + @override + String get error => 'Chyba'; + + @override + String get ok => 'Ok'; + + @override + String get faq => 'Často kladené dotazy (FAQ)'; + + @override + String get contactSupport => 'Kontaktovat podporu'; + + @override + String get emailYourLogs => 'Zašlete své logy e-mailem'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Pošlete prosím logy na \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Kopírovat e-mailovou adresu'; + + @override + String get exportLogs => 'Exportovat logy'; + + @override + String get cancel => 'Zrušit'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Nahlásit chybu'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Připojeno k $endpoint'; + } + + @override + String get save => 'Uložit'; + + @override + String get send => 'Odeslat'; + + @override + String get saveOrSendDescription => + 'Chcete toto uložit do paměti zařízení (ve výchozím nastavení do složky Stažené soubory), nebo odeslat do jiných aplikací?'; + + @override + String get saveOnlyDescription => + 'Chcete toto uložit do paměti zařízení (ve výchozím nastavení do složky Stažené soubory)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'E-mail'; + + @override + String get verify => 'Ověřit'; + + @override + String get invalidEmailTitle => 'Neplatná e-mailová adresa'; + + @override + String get invalidEmailMessage => + 'Prosím, zadejte platnou e-mailovou adresu.'; + + @override + String get pleaseWait => 'Čekejte prosím...'; + + @override + String get verifyPassword => 'Ověření hesla'; + + @override + String get incorrectPasswordTitle => 'Nesprávné heslo'; + + @override + String get pleaseTryAgain => 'Zkuste to prosím znovu'; + + @override + String get enterPassword => 'Zadejte heslo'; + + @override + String get enterYourPasswordHint => 'Zadejte své heslo'; + + @override + String get activeSessions => 'Aktivní relace'; + + @override + String get oops => 'Jejda'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Něco se pokazilo. Zkuste to, prosím, znovu'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Tato akce Vás odhlásí z tohoto zařízení!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Toto Vás odhlásí z následujícího zařízení:'; + + @override + String get terminateSession => 'Ukončit relaci?'; + + @override + String get terminate => 'Ukončit'; + + @override + String get thisDevice => 'Toto zařízení'; + + @override + String get createAccount => 'Vytvořit účet'; + + @override + String get weakStrength => 'Slabé'; + + @override + String get moderateStrength => 'Střední'; + + @override + String get strongStrength => 'Silné'; + + @override + String get deleteAccount => 'Odstranit účet'; + + @override + String get deleteAccountQuery => + 'Mrzí nás, že odcházíte. Máte nějaké problémy s aplikací?'; + + @override + String get yesSendFeedbackAction => 'Ano, poslat zpětnou vazbu'; + + @override + String get noDeleteAccountAction => 'Ne, odstranit účet'; + + @override + String get initiateAccountDeleteTitle => + 'Pro zahájení odstranění účtu se, prosím, ověřte'; + + @override + String get confirmAccountDeleteTitle => 'Potvrdit odstranění účtu'; + + @override + String get confirmAccountDeleteMessage => ' '; + + @override + String get delete => 'Smazat'; + + @override + String get createNewAccount => 'Vytvořit nový účet'; + + @override + String get password => 'Heslo'; + + @override + String get confirmPassword => 'Potvrzení hesla'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Síla hesla: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Jak jste se dozvěděli o Ente? (volitelné)'; + + @override + String get hearUsExplanation => + 'Ne sledujeme instalace aplikace. Pomůže nám, když nám sdělíte, kde jste nás našli!'; + + @override + String get signUpTerms => + 'Souhlasím s podmínkami služby a zásadami ochrany osobních údajů'; + + @override + String get termsOfServicesTitle => 'Podmínky'; + + @override + String get privacyPolicyTitle => 'Podmínky ochrany osobních údajů'; + + @override + String get ackPasswordLostWarning => + 'Rozumím, že při zapomenutí hesla mohu ztratit svá data, protože jsou zabezpečena koncovým šifrováním.'; + + @override + String get encryption => 'Šifrování'; + + @override + String get logInLabel => 'Přihlásit se'; + + @override + String get welcomeBack => 'Vítejte zpět!'; + + @override + String get loginTerms => + 'Kliknutím na přihlášení souhlasím s podmínkami služby a zásadami ochrany osobních údajů'; + + @override + String get noInternetConnection => 'Žádné připojení k internetu'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Zkontrolujte, prosím, své připojení k internetu a zkuste to znovu.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Ověření selhalo, přihlaste se, prosím, znovu'; + + @override + String get recreatePasswordTitle => 'Resetovat heslo'; + + @override + String get recreatePasswordBody => + 'Aktzální zařízení není dostatečně výkonné pro ověření Vašeho hesla, ale můžeme ho regenerovat způsobem, který funguje ve všech zařízením.\n\nPřihlašte se pomocí obnovovacího klíče a znovu si vygenerujte své heslo (můžete použít opět stejné, pokud chcete).'; + + @override + String get useRecoveryKey => 'Použít obnovovací klíč'; + + @override + String get forgotPassword => 'Zapomenuté heslo'; + + @override + String get changeEmail => 'Změnit e-mail'; + + @override + String get verifyEmail => 'Ověřit e-mail'; + + @override + String weHaveSendEmailTo(String email) { + return 'Odeslali jsme e-mail na $email'; + } + + @override + String get toResetVerifyEmail => + 'Pro obnovení hesla obnovte, prosím, nejprve svůj e-mail.'; + + @override + String get checkInboxAndSpamFolder => + 'Pro dokončení ověření prosím zkontrolujte, prosím, svou doručenou poštu (a spamy)'; + + @override + String get tapToEnterCode => 'Klepnutím zadejte kód'; + + @override + String get sendEmail => 'Odeslat e-mail'; + + @override + String get resendEmail => 'Odeslat e-mail znovu'; + + @override + String get passKeyPendingVerification => 'Ověřování stále probíhá'; + + @override + String get loginSessionExpired => 'Relace vypršela'; + + @override + String get loginSessionExpiredDetails => + 'Vaše relace vypršela. Přihlaste se, prosím, znovu.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Čekání na ověření...'; + + @override + String get tryAgain => 'Zkusit znovu'; + + @override + String get checkStatus => 'Zkontrolovat stav'; + + @override + String get loginWithTOTP => 'Přihlášení s TOTP'; + + @override + String get recoverAccount => 'Obnovit účet'; + + @override + String get setPasswordTitle => 'Nastavit heslo'; + + @override + String get changePasswordTitle => 'Změnit heslo'; + + @override + String get resetPasswordTitle => 'Obnovit heslo'; + + @override + String get encryptionKeys => 'Šifrovací klíče'; + + @override + String get enterPasswordToEncrypt => + 'Zadejte heslo, kterým můžeme zašifrovat Vaše data'; + + @override + String get enterNewPasswordToEncrypt => + 'Zadejte nové heslo, kterým můžeme šifrovat Vaše data'; + + @override + String get passwordWarning => + 'Vaše heslo neuchováváme. Pokud ho zapomenete, nemůžeme Vaše data dešifrovat'; + + @override + String get howItWorks => 'Jak to funguje'; + + @override + String get generatingEncryptionKeys => 'Generování šifrovacích klíčů...'; + + @override + String get passwordChangedSuccessfully => 'Heslo úspěšně změněno'; + + @override + String get signOutFromOtherDevices => 'Odhlásit z ostatních zařízení'; + + @override + String get signOutOtherBody => + 'Pokud si myslíte, že by někdo mohl znát Vaše heslo, můžete vynutit odhlášení ostatních zařízení používajících Váš účet.'; + + @override + String get signOutOtherDevices => 'Odhlásit z ostatních zařízení'; + + @override + String get doNotSignOut => 'Neodhlašovat'; + + @override + String get generatingEncryptionKeysTitle => 'Generování šifrovacích klíčů...'; + + @override + String get continueLabel => 'Pokračovat'; + + @override + String get insecureDevice => 'Nezabezpečené zařízení'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Omlouváme se, na tomto zařízení nemůžeme vygenerovat bezpečné klíče.\n\nprosím přihlaste se z jiného zařízení.'; + + @override + String get recoveryKeyCopiedToClipboard => 'Obnovovací klíč byl zkopírován'; + + @override + String get recoveryKey => 'Obnovovací klíč'; + + @override + String get recoveryKeyOnForgotPassword => + 'Tento klíč je jedinou cestou pro obnovení Vašich dat, pokud zapomenete heslo.'; + + @override + String get recoveryKeySaveDescription => + 'Tento 24místný klíč neuchováváme, uschovejte ho, prosím, na bezpečném místě.'; + + @override + String get doThisLater => 'Udělat později'; + + @override + String get saveKey => 'Uložit klíč'; + + @override + String get recoveryKeySaved => + 'Obnovovací klíč uložen do složky Stažené soubory!'; + + @override + String get noRecoveryKeyTitle => 'Nemáte obnovovací klíč?'; + + @override + String get twoFactorAuthTitle => 'Dvoufaktorové ověření'; + + @override + String get enterCodeHint => + 'Zadejte 6místný kód ze své autentizační aplikace'; + + @override + String get lostDeviceTitle => 'Ztratili jste zařízení?'; + + @override + String get enterRecoveryKeyHint => 'Zadejte svůj obnovovací klíč'; + + @override + String get recover => 'Obnovit'; + + @override + String get loggingOut => 'Odhlašování...'; + + @override + String get immediately => 'Ihned'; + + @override + String get appLock => 'Zámek aplikace'; + + @override + String get autoLock => 'Automatické zamykání'; + + @override + String get noSystemLockFound => 'Zámek systému nenalezen'; + + @override + String get deviceLockEnablePreSteps => + 'Pro aktivaci zámku zařízení si nastavte přístupový kód zařízení nebo zámek obrazovky v nastavení systému.'; + + @override + String get appLockDescription => + 'Vyberte si mezi zámkem obrazovky svého zařízení a vlastním zámkem obrazovky s PIN kódem nebo heslem.'; + + @override + String get deviceLock => 'Zámek zařízení'; + + @override + String get pinLock => 'Uzamčení na PIN'; + + @override + String get autoLockFeatureDescription => + 'Interval, po kterém se aplikace běžící na pozadí uzamkne'; + + @override + String get hideContent => 'Skrýt obsah'; + + @override + String get hideContentDescriptionAndroid => 'Skryje obsah aplikace ve '; + + @override + String get hideContentDescriptioniOS => + 'Skryje obsah aplikace při přepínání úloh'; + + @override + String get tooManyIncorrectAttempts => 'Příliš mnoho neúspěšných pokusů'; + + @override + String get tapToUnlock => 'Pro odemčení klepněte'; + + @override + String get areYouSureYouWantToLogout => 'Opravdu se chcete odhlásit?'; + + @override + String get yesLogout => 'Ano, odhlásit se'; + + @override + String get authToViewSecrets => + 'Pro zobrazení svých tajných údajů se musíte ověřit'; + + @override + String get next => 'Další'; + + @override + String get setNewPassword => 'Nastavit nové heslo'; + + @override + String get enterPin => 'Zadejte PIN'; + + @override + String get setNewPin => 'Nadra'; + + @override + String get confirm => 'Potvrdit'; + + @override + String get reEnterPassword => 'Zadejte heslo znovu'; + + @override + String get reEnterPin => 'Zadejte PIN znovu'; + + @override + String get androidBiometricHint => 'Ověřte svou identitu'; + + @override + String get androidBiometricNotRecognized => 'Nerozpoznáno. Zkuste znovu.'; + + @override + String get androidBiometricSuccess => 'Úspěch'; + + @override + String get androidCancelButton => 'Zrušit'; + + @override + String get androidSignInTitle => 'Je požadováno ověření'; + + @override + String get androidBiometricRequiredTitle => + 'Je požadováno biometrické ověření'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Jsou vyžadovány přihlašovací údaje zařízení'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Jsou vyžadovány přihlašovací údaje zařízení'; + + @override + String get goToSettings => 'Jít do nastavení'; + + @override + String get androidGoToSettingsDescription => + 'Na Vašem zařízení není nastaveno biometrické ověřování. Pro aktivaci běžte do \'Nastavení > Zabezpečení\'.'; + + @override + String get iOSLockOut => + 'Biometrické ověřování není povoleno. Pro povolení zamkněte a odemkněte obrazovku.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'E-mail je již registrován.'; + + @override + String get emailNotRegistered => 'E-mail není registrován.'; + + @override + String get thisEmailIsAlreadyInUse => 'Tento e-mail je již používán'; + + @override + String emailChangedTo(String newEmail) { + return 'E-mail změněn na $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Ověření selhalo, zkuste to, prosím, znovu'; + + @override + String get authenticationSuccessful => 'Ověření bylo úspěšné!'; + + @override + String get sessionExpired => 'Relace vypršela'; + + @override + String get incorrectRecoveryKey => 'Nesprávný obnovovací klíč'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Vámi zadaný obnovovací klíč je nesprávný'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Dvoufázové ověření bylo úspěšně obnoveno'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => 'Váš ověřovací kód vypršel'; + + @override + String get incorrectCode => 'Nesprávný kód'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Omlouváme se, zadaný kód je nesprávný'; + + @override + String get developerSettings => 'Nastavení pro vývojáře'; + + @override + String get serverEndpoint => 'Koncový bod serveru'; + + @override + String get invalidEndpoint => 'Neplatný koncový bod'; + + @override + String get invalidEndpointMessage => + 'Zadaný koncový bod je neplatný. Zadejte prosím platný koncový bod a zkuste to znovu.'; + + @override + String get endpointUpdatedMessage => 'Koncový bod byl úspěšně aktualizován'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_da.dart b/mobile/packages/strings/lib/l10n/strings_localizations_da.dart new file mode 100644 index 0000000000..82f2d03070 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_da.dart @@ -0,0 +1,629 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Danish (`da`). +class StringsLocalizationsDa extends StringsLocalizations { + StringsLocalizationsDa([String locale = 'da']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Ude af stand til at forbinde til Ente. Tjek venligst dine netværksindstillinger og kontakt support hvis fejlen varer ved.'; + + @override + String get networkConnectionRefusedErr => + 'Ude af stand til at forbinde til Ente. Forsøg igen efter et stykke tid. Hvis fejlen varer ved, kontakt da venligst support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Det ser ud til at noget gik galt. Forsøg venligst igen efter lidt tid. Hvis fejlen varer ved, kontakt da venligst support.'; + + @override + String get error => 'Fejl'; + + @override + String get ok => 'OK'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'Kontakt support'; + + @override + String get emailYourLogs => 'Email dine logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Send venligst logs til $toEmail'; + } + + @override + String get copyEmailAddress => 'Kopier email adresse'; + + @override + String get exportLogs => 'Eksporter logs'; + + @override + String get cancel => 'Afbryd'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Rapporter en fejl'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Forbindelse oprettet til $endpoint'; + } + + @override + String get save => 'Gem'; + + @override + String get send => 'Send'; + + @override + String get saveOrSendDescription => + 'Vil du gemme på din enhed (Downloads mappe som udgangspunkt) eller sende til andre apps?'; + + @override + String get saveOnlyDescription => + 'Vil du gemme på din enhed (Downloads mappe som udgangspunkt)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Email'; + + @override + String get verify => 'Bekræft'; + + @override + String get invalidEmailTitle => 'Ugyldig email adresse'; + + @override + String get invalidEmailMessage => 'Indtast en gyldig email adresse.'; + + @override + String get pleaseWait => 'Vent venligst...'; + + @override + String get verifyPassword => 'Bekræft adgangskode'; + + @override + String get incorrectPasswordTitle => 'Forkert adgangskode'; + + @override + String get pleaseTryAgain => 'Forsøg venligst igen'; + + @override + String get enterPassword => 'Indtast adgangskode'; + + @override + String get enterYourPasswordHint => 'Indtast adgangskode'; + + @override + String get activeSessions => 'Aktive sessioner'; + + @override + String get oops => 'Ups'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Noget gik galt, forsøg venligst igen'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Dette vil logge dig ud af denne enhed!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Dette vil logge dig ud af den følgende enhed:'; + + @override + String get terminateSession => 'Afslut session?'; + + @override + String get terminate => 'Afslut'; + + @override + String get thisDevice => 'Denne enhed'; + + @override + String get createAccount => 'Opret konto'; + + @override + String get weakStrength => 'Svagt'; + + @override + String get moderateStrength => 'Middel'; + + @override + String get strongStrength => 'Stærkt'; + + @override + String get deleteAccount => 'Slet konto'; + + @override + String get deleteAccountQuery => + 'Vi er kede af at se dig gå. Er du stødt på et problem?'; + + @override + String get yesSendFeedbackAction => 'Ja, send feedback'; + + @override + String get noDeleteAccountAction => 'Nej, slet konto'; + + @override + String get initiateAccountDeleteTitle => + 'Bekræft venligst for at påbegynde sletning af konto'; + + @override + String get confirmAccountDeleteTitle => 'Bekræft sletning af konto'; + + @override + String get confirmAccountDeleteMessage => + 'Denne konto er forbundet til andre Ente apps, hvis du benytter nogle.\n\nDine uploadede data for alle Ente apps vil blive slettet, og din konto vil blive slettet permanent.'; + + @override + String get delete => 'Slet'; + + @override + String get createNewAccount => 'Opret konto'; + + @override + String get password => 'Kodeord'; + + @override + String get confirmPassword => 'Bekræft kodeord'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Kodeordets styrke: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Hvordan hørte du om Ente? (valgfrit)'; + + @override + String get hearUsExplanation => + 'Vi tracker ikke app installeringer. Det ville hjælpe os at vide hvordan du fandt os!'; + + @override + String get signUpTerms => + 'Jeg er enig i betingelser for brug og privatlivspolitik'; + + @override + String get termsOfServicesTitle => 'Betingelser'; + + @override + String get privacyPolicyTitle => 'Privatlivspolitik'; + + @override + String get ackPasswordLostWarning => + 'Jeg forstår at hvis jeg mister min adgangskode kan jeg miste mine data, da mine data er end-to-end krypteret.'; + + @override + String get encryption => 'Kryptering'; + + @override + String get logInLabel => 'Log ind'; + + @override + String get welcomeBack => 'Velkommen tilbage!'; + + @override + String get loginTerms => + 'Ved at logge ind godkender jeg Ente\'s betingelser for brug og privatlivspolitik.'; + + @override + String get noInternetConnection => 'Ingen internetforbindelse'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Tjek venligst din internetforbindelse og forsøg igen.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Bekræftelse fejlede, forsøg venligst igen'; + + @override + String get recreatePasswordTitle => 'Gendan adgangskode'; + + @override + String get recreatePasswordBody => + 'Denne enhed er ikke kraftfuld nok til at bekræfte adgangskoden, men vi kan gendanne den på en måde der fungerer for alle enheder.\n\nLog venligst ind med din gendannelsesnøgle og gendan din adgangskode (du kan bruge den samme adgangskode igen hvis du ønsker).'; + + @override + String get useRecoveryKey => 'Brug gendannelsesnøgle'; + + @override + String get forgotPassword => 'Glemt adgangskode'; + + @override + String get changeEmail => 'Skift email adresse'; + + @override + String get verifyEmail => 'Bekræft email adresse'; + + @override + String weHaveSendEmailTo(String email) { + return 'Vi har sendt en email til $email'; + } + + @override + String get toResetVerifyEmail => + 'For at nulstille din adgangskode, bekræft venligst din email adresse.'; + + @override + String get checkInboxAndSpamFolder => + 'Tjek venligst din indboks (og spam) for at færdiggøre verificeringen'; + + @override + String get tapToEnterCode => 'Tryk for at indtaste kode'; + + @override + String get sendEmail => 'Send email'; + + @override + String get resendEmail => 'Send email igen'; + + @override + String get passKeyPendingVerification => 'Bekræftelse afventes stadig'; + + @override + String get loginSessionExpired => 'Session udløbet'; + + @override + String get loginSessionExpiredDetails => + 'Din session er udløbet. Log venligst på igen.'; + + @override + String get passkeyAuthTitle => 'Bekræftelse af adgangskode'; + + @override + String get waitingForVerification => 'Venter på bekræftelse...'; + + @override + String get tryAgain => 'Forsøg igen'; + + @override + String get checkStatus => 'Tjek status'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Gendan konto'; + + @override + String get setPasswordTitle => 'Angiv adgangskode'; + + @override + String get changePasswordTitle => 'Skift adgangskode'; + + @override + String get resetPasswordTitle => 'Nulstil adgangskode'; + + @override + String get encryptionKeys => 'Krypteringsnøgler'; + + @override + String get enterPasswordToEncrypt => + 'Indtast en adgangskode vi kan bruge til at kryptere dine data'; + + @override + String get enterNewPasswordToEncrypt => + 'Indtast en ny adgangskode vi kan bruge til at kryptere dine data'; + + @override + String get passwordWarning => + 'Vi gemmer ikke denne adgangskode, så hvis du glemmer den kan vi ikke dekryptere dine data'; + + @override + String get howItWorks => 'Sådan fungerer det'; + + @override + String get generatingEncryptionKeys => 'Genererer krypteringsnøgler...'; + + @override + String get passwordChangedSuccessfully => 'Adgangskoden er blevet ændret'; + + @override + String get signOutFromOtherDevices => 'Log ud af andre enheder'; + + @override + String get signOutOtherBody => + 'Hvis du mistænker at nogen kender din adgangskode kan du tvinge alle enheder der benytter din konto til at logge ud.'; + + @override + String get signOutOtherDevices => 'Log ud af andre enheder'; + + @override + String get doNotSignOut => 'Log ikke ud'; + + @override + String get generatingEncryptionKeysTitle => 'Genererer krypteringsnøgler...'; + + @override + String get continueLabel => 'Fortsæt'; + + @override + String get insecureDevice => 'Usikker enhed'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Beklager, vi kunne ikke generere sikre krypteringsnøgler på denne enhed.\n\nForsøg venligst at oprette en konto fra en anden enhed.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Gendannelsesnøgle kopieret til udklipsholderen'; + + @override + String get recoveryKey => 'Gendannelsesnøgle'; + + @override + String get recoveryKeyOnForgotPassword => + 'Hvis du glemmer dit kodeord er gendannelsesnøglen den eneste mulighed for at få adgang til dine data.'; + + @override + String get recoveryKeySaveDescription => + 'Vi gemmer ikke denne nøgle, gem venligst denne 24-ords nøgle et sikkert sted.'; + + @override + String get doThisLater => 'Gør det senere'; + + @override + String get saveKey => 'Gem nøgle'; + + @override + String get recoveryKeySaved => + 'Gendannelsesnøgle gemt i din Downloads mappe!'; + + @override + String get noRecoveryKeyTitle => 'Ingen gendannelsesnøgle?'; + + @override + String get twoFactorAuthTitle => 'Tofaktorgodkendelse'; + + @override + String get enterCodeHint => + 'Indtast den 6-cifrede kode fra din authenticator app'; + + @override + String get lostDeviceTitle => 'Mistet enhed?'; + + @override + String get enterRecoveryKeyHint => 'Indtast din gendannelsesnøgle'; + + @override + String get recover => 'Gendan'; + + @override + String get loggingOut => 'Logger ud...'; + + @override + String get immediately => 'Med det samme'; + + @override + String get appLock => 'Låsning af app'; + + @override + String get autoLock => 'Automatisk lås'; + + @override + String get noSystemLockFound => 'Ingen systemlås fundet'; + + @override + String get deviceLockEnablePreSteps => + 'For at aktivere enhedslås, indstil venligst kode eller skærmlås på din enhed i dine systemindstillinger.'; + + @override + String get appLockDescription => + 'Vælg mellem din enheds standard skærmlås eller skærmlås med pinkode eller adgangskode.'; + + @override + String get deviceLock => 'Enhedslås'; + + @override + String get pinLock => 'Låsning med pinkode'; + + @override + String get autoLockFeatureDescription => + 'Tid til låsning af app efter at være blevet placeret i baggrunden'; + + @override + String get hideContent => 'Skjul indhold'; + + @override + String get hideContentDescriptionAndroid => + 'Skjul app indhold i app-vælger og deaktiver screenshots'; + + @override + String get hideContentDescriptioniOS => 'Skjul app indhold i app-vælger'; + + @override + String get tooManyIncorrectAttempts => 'For mange forkerte forsøg'; + + @override + String get tapToUnlock => 'Tryk for at låse op'; + + @override + String get areYouSureYouWantToLogout => 'Er du sikker på at du vil logge ud?'; + + @override + String get yesLogout => 'Ja, log ud'; + + @override + String get authToViewSecrets => + 'Bekræft venligst din identitet for at se dine hemmeligheder'; + + @override + String get next => 'Næste'; + + @override + String get setNewPassword => 'Indstil ny adgangskode'; + + @override + String get enterPin => 'Indtast pinkode'; + + @override + String get setNewPin => 'Indstil ny pinkode'; + + @override + String get confirm => 'Bekræft'; + + @override + String get reEnterPassword => 'Indtast adgangskode igen'; + + @override + String get reEnterPin => 'Indtast pinkode igen'; + + @override + String get androidBiometricHint => 'Bekræft identitet'; + + @override + String get androidBiometricNotRecognized => 'Ikke genkendt. Forsøg igen.'; + + @override + String get androidBiometricSuccess => 'Succes'; + + @override + String get androidCancelButton => 'Afbryd'; + + @override + String get androidSignInTitle => 'Godkendelse påkrævet'; + + @override + String get androidBiometricRequiredTitle => 'Biometri påkrævet'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Enhedsoplysninger påkrævet'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Enhedsoplysninger påkrævet'; + + @override + String get goToSettings => 'Gå til indstillinger'; + + @override + String get androidGoToSettingsDescription => + 'Biometrisk godkendelse er ikke indstillet på din enhed. Gå til \"Indstillinger > Sikkerhed\" for at indstille biometrisk godkendelse.'; + + @override + String get iOSLockOut => + 'Biometrisk godkendelse er slået fra. Lås din skærm, og lås den derefter op for at aktivere det.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => + 'Denne email adresse er allerede i brug'; + + @override + String emailChangedTo(String newEmail) { + return 'Email adresse ændret til $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Bekræftelse af identitet fejlede, forsøg venligst igen'; + + @override + String get authenticationSuccessful => 'Bekræftelse af identitet lykkedes!'; + + @override + String get sessionExpired => 'Session udløbet'; + + @override + String get incorrectRecoveryKey => 'Forkert gendannelsesnøgle'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Den indtastede gendannelsesnøgle er ikke korrekt'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Tofaktorgodkendelse nulstillet'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Din bekræftelseskode er udløbet'; + + @override + String get incorrectCode => 'Forkert kode'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Beklager, den indtastede kode er forkert'; + + @override + String get developerSettings => 'Udvikler-indstillinger'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Ugyldigt endpoint'; + + @override + String get invalidEndpointMessage => + 'Beklager, det indtastede endpoint er ugyldigt. Indtast venligst et gyldigt endpoint og forsøg igen.'; + + @override + String get endpointUpdatedMessage => 'Endpoint opdateret'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_de.dart b/mobile/packages/strings/lib/l10n/strings_localizations_de.dart new file mode 100644 index 0000000000..3bf0aca783 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_de.dart @@ -0,0 +1,636 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for German (`de`). +class StringsLocalizationsDe extends StringsLocalizations { + StringsLocalizationsDe([String locale = 'de']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Ente ist im Moment nicht erreichbar. Bitte überprüfen Sie Ihre Netzwerkeinstellungen. Sollte das Problem bestehen bleiben, wenden Sie sich bitte an den Support.'; + + @override + String get networkConnectionRefusedErr => + 'Ente ist im Moment nicht erreichbar. Bitte versuchen Sie es später erneut. Sollte das Problem bestehen bleiben, wenden Sie sich bitte an den Support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Etwas ist schiefgelaufen. Bitte versuchen Sie es später noch einmal. Sollte der Fehler weiter bestehen, kontaktieren Sie unser Supportteam.'; + + @override + String get error => 'Fehler'; + + @override + String get ok => 'Ok'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'Support kontaktieren'; + + @override + String get emailYourLogs => 'E-Mail mit Logs senden'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Bitte Logs an $toEmail senden'; + } + + @override + String get copyEmailAddress => 'E-Mail-Adresse kopieren'; + + @override + String get exportLogs => 'Logs exportieren'; + + @override + String get cancel => 'Abbrechen'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Einen Fehler melden'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Mit $endpoint verbunden'; + } + + @override + String get save => 'Speichern'; + + @override + String get send => 'Senden'; + + @override + String get saveOrSendDescription => + 'Möchtest du dies in deinem Speicher (standardmäßig im Ordner Downloads) speichern oder an andere Apps senden?'; + + @override + String get saveOnlyDescription => + 'Möchtest du dies in deinem Speicher (standardmäßig im Ordner Downloads) speichern?'; + + @override + String get enterNewEmailHint => 'Gib deine neue E-Mail-Adresse ein'; + + @override + String get email => 'E-Mail'; + + @override + String get verify => 'Verifizieren'; + + @override + String get invalidEmailTitle => 'Ungültige E-Mail-Adresse'; + + @override + String get invalidEmailMessage => + 'Bitte geben Sie eine gültige E-Mail-Adresse ein.'; + + @override + String get pleaseWait => 'Bitte warten...'; + + @override + String get verifyPassword => 'Passwort überprüfen'; + + @override + String get incorrectPasswordTitle => 'Falsches Passwort'; + + @override + String get pleaseTryAgain => 'Bitte versuchen Sie es erneut'; + + @override + String get enterPassword => 'Passwort eingeben'; + + @override + String get enterYourPasswordHint => 'Geben Sie Ihr Passwort ein'; + + @override + String get activeSessions => 'Aktive Sitzungen'; + + @override + String get oops => 'Hoppla'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Ein Fehler ist aufgetreten, bitte erneut versuchen'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Dadurch werden Sie von diesem Gerät abgemeldet!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Dadurch werden Sie vom folgendem Gerät abgemeldet:'; + + @override + String get terminateSession => 'Sitzung beenden?'; + + @override + String get terminate => 'Beenden'; + + @override + String get thisDevice => 'Dieses Gerät'; + + @override + String get createAccount => 'Konto erstellen'; + + @override + String get weakStrength => 'Schwach'; + + @override + String get moderateStrength => 'Mittel'; + + @override + String get strongStrength => 'Stark'; + + @override + String get deleteAccount => 'Konto löschen'; + + @override + String get deleteAccountQuery => + 'Es tut uns leid, dass Sie gehen. Haben Sie ein Problem?'; + + @override + String get yesSendFeedbackAction => 'Ja, Feedback senden'; + + @override + String get noDeleteAccountAction => 'Nein, Konto löschen'; + + @override + String get initiateAccountDeleteTitle => + 'Bitte authentifizieren Sie sich, um die Kontolöschung einzuleiten'; + + @override + String get confirmAccountDeleteTitle => 'Kontolöschung bestätigen'; + + @override + String get confirmAccountDeleteMessage => + 'Dieses Konto ist mit anderen Ente-Apps verknüpft, falls Sie welche verwenden.\n\nIhre hochgeladenen Daten werden in allen Ente-Apps zur Löschung vorgemerkt und Ihr Konto wird endgültig gelöscht.'; + + @override + String get delete => 'Löschen'; + + @override + String get createNewAccount => 'Neues Konto erstellen'; + + @override + String get password => 'Passwort'; + + @override + String get confirmPassword => 'Bestätigen Sie das Passwort'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Passwortstärke: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Wie hast du von Ente erfahren? (optional)'; + + @override + String get hearUsExplanation => + 'Wir tracken keine App-Installationen. Es würde uns jedoch helfen, wenn du uns mitteilst, wie du von uns erfahren hast!'; + + @override + String get signUpTerms => + 'Ich stimme den Nutzerbedingungen und Datenschutzbestimmungen zu'; + + @override + String get termsOfServicesTitle => 'Bedingungen'; + + @override + String get privacyPolicyTitle => 'Datenschutzbestimmungen'; + + @override + String get ackPasswordLostWarning => + 'Ich verstehe, dass der Verlust meines Passworts zum Verlust meiner Daten führen kann, denn diese sind Ende-zu-Ende verschlüsselt.'; + + @override + String get encryption => 'Verschlüsselung'; + + @override + String get logInLabel => 'Einloggen'; + + @override + String get welcomeBack => 'Willkommen zurück!'; + + @override + String get loginTerms => + 'Durch das Klicken auf den Login-Button, stimme ich den Nutzungsbedingungen und den Datenschutzbestimmungen zu'; + + @override + String get noInternetConnection => 'Keine Internetverbindung'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie erneut.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verifizierung fehlgeschlagen, bitte versuchen Sie es erneut'; + + @override + String get recreatePasswordTitle => 'Neues Passwort erstellen'; + + @override + String get recreatePasswordBody => + 'Das benutzte Gerät ist nicht leistungsfähig genug das Passwort zu prüfen. Wir können es aber neu erstellen damit es auf jedem Gerät funktioniert. \n\nBitte loggen sie sich mit ihrem Wiederherstellungsschlüssel ein und erstellen sie ein neues Passwort (Sie können das selbe Passwort wieder verwenden, wenn sie möchten).'; + + @override + String get useRecoveryKey => 'Wiederherstellungsschlüssel verwenden'; + + @override + String get forgotPassword => 'Passwort vergessen'; + + @override + String get changeEmail => 'E-Mail ändern'; + + @override + String get verifyEmail => 'E-Mail-Adresse verifizieren'; + + @override + String weHaveSendEmailTo(String email) { + return 'Wir haben eine E-Mail an $email gesendet'; + } + + @override + String get toResetVerifyEmail => + 'Um Ihr Passwort zurückzusetzen, verifizieren Sie bitte zuerst Ihre E-Mail-Adresse.'; + + @override + String get checkInboxAndSpamFolder => + 'Bitte überprüfe deinen E-Mail-Posteingang (und Spam), um die Verifizierung abzuschließen'; + + @override + String get tapToEnterCode => 'Antippen, um den Code einzugeben'; + + @override + String get sendEmail => 'E-Mail senden'; + + @override + String get resendEmail => 'E-Mail erneut senden'; + + @override + String get passKeyPendingVerification => 'Verifizierung steht noch aus'; + + @override + String get loginSessionExpired => 'Sitzung abgelaufen'; + + @override + String get loginSessionExpiredDetails => + 'Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.'; + + @override + String get passkeyAuthTitle => 'Passkey Authentifizierung'; + + @override + String get waitingForVerification => 'Warte auf Bestätigung...'; + + @override + String get tryAgain => 'Noch einmal versuchen'; + + @override + String get checkStatus => 'Status überprüfen'; + + @override + String get loginWithTOTP => 'Mit TOTP anmelden'; + + @override + String get recoverAccount => 'Konto wiederherstellen'; + + @override + String get setPasswordTitle => 'Passwort setzen'; + + @override + String get changePasswordTitle => 'Passwort ändern'; + + @override + String get resetPasswordTitle => 'Passwort zurücksetzen'; + + @override + String get encryptionKeys => 'Verschlüsselungsschlüssel'; + + @override + String get enterPasswordToEncrypt => + 'Geben Sie ein Passwort ein, mit dem wir Ihre Daten verschlüsseln können'; + + @override + String get enterNewPasswordToEncrypt => + 'Geben Sie ein neues Passwort ein, mit dem wir Ihre Daten verschlüsseln können'; + + @override + String get passwordWarning => + 'Wir speichern dieses Passwort nicht. Wenn Sie es vergessen, können wir Ihre Daten nicht entschlüsseln'; + + @override + String get howItWorks => 'So funktioniert\'s'; + + @override + String get generatingEncryptionKeys => + 'Generierung von Verschlüsselungsschlüsseln...'; + + @override + String get passwordChangedSuccessfully => 'Passwort erfolgreich geändert'; + + @override + String get signOutFromOtherDevices => 'Von anderen Geräten abmelden'; + + @override + String get signOutOtherBody => + 'Falls Sie denken, dass jemand Ihr Passwort kennen könnte, können Sie alle anderen Geräte forcieren, sich von Ihrem Konto abzumelden.'; + + @override + String get signOutOtherDevices => 'Andere Geräte abmelden'; + + @override + String get doNotSignOut => 'Nicht abmelden'; + + @override + String get generatingEncryptionKeysTitle => + 'Generierung von Verschlüsselungsschlüsseln...'; + + @override + String get continueLabel => 'Weiter'; + + @override + String get insecureDevice => 'Unsicheres Gerät'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Es tut uns leid, wir konnten keine sicheren Schlüssel auf diesem Gerät generieren.\n\nBitte registrieren Sie sich auf einem anderen Gerät.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Wiederherstellungsschlüssel in die Zwischenablage kopiert'; + + @override + String get recoveryKey => 'Wiederherstellungsschlüssel'; + + @override + String get recoveryKeyOnForgotPassword => + 'Sollten sie ihr Passwort vergessen, dann ist dieser Schlüssel die einzige Möglichkeit ihre Daten wiederherzustellen.'; + + @override + String get recoveryKeySaveDescription => + 'Wir speichern diesen Schlüssel nicht. Sichern Sie diesen Schlüssel bestehend aus 24 Wörtern an einem sicheren Platz.'; + + @override + String get doThisLater => 'Auf später verschieben'; + + @override + String get saveKey => 'Schlüssel speichern'; + + @override + String get recoveryKeySaved => + 'Wiederherstellungsschlüssel im Downloads-Ordner gespeichert!'; + + @override + String get noRecoveryKeyTitle => 'Kein Wiederherstellungsschlüssel?'; + + @override + String get twoFactorAuthTitle => 'Zwei-Faktor-Authentifizierung'; + + @override + String get enterCodeHint => + 'Geben Sie den 6-stelligen Code \naus Ihrer Authentifikator-App ein.'; + + @override + String get lostDeviceTitle => 'Gerät verloren?'; + + @override + String get enterRecoveryKeyHint => + 'Geben Sie Ihren Wiederherstellungsschlüssel ein'; + + @override + String get recover => 'Wiederherstellen'; + + @override + String get loggingOut => 'Wird abgemeldet...'; + + @override + String get immediately => 'Sofort'; + + @override + String get appLock => 'App-Sperre'; + + @override + String get autoLock => 'Automatisches Sperren'; + + @override + String get noSystemLockFound => 'Keine Systemsperre gefunden'; + + @override + String get deviceLockEnablePreSteps => + 'Um die Gerätesperre zu aktivieren, richte bitte einen Gerätepasscode oder eine Bildschirmsperre in den Systemeinstellungen ein.'; + + @override + String get appLockDescription => + 'Wähle zwischen dem Standard-Sperrbildschirm deines Gerätes und einem eigenen Sperrbildschirm mit PIN oder Passwort.'; + + @override + String get deviceLock => 'Gerätesperre'; + + @override + String get pinLock => 'PIN-Sperre'; + + @override + String get autoLockFeatureDescription => + 'Zeit, nach der die App gesperrt wird, nachdem sie in den Hintergrund verschoben wurde'; + + @override + String get hideContent => 'Inhalte verstecken'; + + @override + String get hideContentDescriptionAndroid => + 'Versteckt Inhalte der App beim Wechseln zwischen Apps und deaktiviert Screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Versteckt Inhalte der App beim Wechseln zwischen Apps'; + + @override + String get tooManyIncorrectAttempts => 'Zu viele fehlerhafte Versuche'; + + @override + String get tapToUnlock => 'Zum Entsperren antippen'; + + @override + String get areYouSureYouWantToLogout => + 'Sind sie sicher, dass sie sich ausloggen möchten?'; + + @override + String get yesLogout => 'Ja ausloggen'; + + @override + String get authToViewSecrets => + 'Bitte authentifizieren, um Ihren Wiederherstellungscode anzuzeigen'; + + @override + String get next => 'Weiter'; + + @override + String get setNewPassword => 'Neues Passwort festlegen'; + + @override + String get enterPin => 'PIN eingeben'; + + @override + String get setNewPin => 'Neue PIN festlegen'; + + @override + String get confirm => 'Bestätigen'; + + @override + String get reEnterPassword => 'Passwort erneut eingeben'; + + @override + String get reEnterPin => 'PIN erneut eingeben'; + + @override + String get androidBiometricHint => 'Identität bestätigen'; + + @override + String get androidBiometricNotRecognized => + 'Nicht erkannt. Versuchen Sie es erneut.'; + + @override + String get androidBiometricSuccess => 'Erfolgreich'; + + @override + String get androidCancelButton => 'Abbrechen'; + + @override + String get androidSignInTitle => 'Authentifizierung erforderlich'; + + @override + String get androidBiometricRequiredTitle => 'Biometrie erforderlich'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Geräteanmeldeinformationen erforderlich'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Geräteanmeldeinformationen erforderlich'; + + @override + String get goToSettings => 'Zu den Einstellungen'; + + @override + String get androidGoToSettingsDescription => + 'Auf Ihrem Gerät ist keine biometrische Authentifizierung eingerichtet. Gehen Sie zu \'Einstellungen > Sicherheit\', um die biometrische Authentifizierung hinzuzufügen.'; + + @override + String get iOSLockOut => + 'Die biometrische Authentifizierung ist deaktiviert. Bitte sperren und entsperren Sie Ihren Bildschirm, um sie wieder zu aktivieren.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'E-Mail ist bereits registriert.'; + + @override + String get emailNotRegistered => 'E-Mail-Adresse nicht registriert.'; + + @override + String get thisEmailIsAlreadyInUse => + 'Diese E-Mail-Adresse wird bereits verwendet'; + + @override + String emailChangedTo(String newEmail) { + return 'E-Mail-Adresse geändert zu $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentifizierung fehlgeschlagen, bitte erneut versuchen'; + + @override + String get authenticationSuccessful => 'Authentifizierung erfolgreich!'; + + @override + String get sessionExpired => 'Sitzung abgelaufen'; + + @override + String get incorrectRecoveryKey => 'Falscher Wiederherstellungs-Schlüssel'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Der eingegebene Wiederherstellungs-Schlüssel ist ungültig'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Zwei-Faktor-Authentifizierung (2FA) erfolgreich zurückgesetzt'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Ihr Bestätigungscode ist abgelaufen'; + + @override + String get incorrectCode => 'Falscher Code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Leider ist der eingegebene Code falsch'; + + @override + String get developerSettings => 'Entwicklereinstellungen'; + + @override + String get serverEndpoint => 'Server Endpunkt'; + + @override + String get invalidEndpoint => 'Ungültiger Endpunkt'; + + @override + String get invalidEndpointMessage => + 'Der eingegebene Endpunkt ist ungültig. Bitte geben Sie einen gültigen Endpunkt ein und versuchen Sie es erneut.'; + + @override + String get endpointUpdatedMessage => 'Endpunkt erfolgreich aktualisiert'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_el.dart b/mobile/packages/strings/lib/l10n/strings_localizations_el.dart new file mode 100644 index 0000000000..f2af902f83 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_el.dart @@ -0,0 +1,637 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Modern Greek (`el`). +class StringsLocalizationsEl extends StringsLocalizations { + StringsLocalizationsEl([String locale = 'el']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Δεν είναι δυνατή η σύνδεση με το Ente, ελέγξτε τις ρυθμίσεις του δικτύου σας και επικοινωνήστε με την υποστήριξη αν το σφάλμα παραμένει.'; + + @override + String get networkConnectionRefusedErr => + 'Δεν είναι δυνατή η σύνδεση με το Ente, παρακαλώ προσπαθήστε ξανά μετά από λίγο. Εάν το σφάλμα παραμένει, παρακαλούμε επικοινωνήστε με την υποστήριξη.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Φαίνεται ότι κάτι πήγε στραβά. Παρακαλώ προσπαθήστε ξανά μετά από λίγο. Αν το σφάλμα παραμένει, παρακαλούμε επικοινωνήστε με την ομάδα υποστήριξης μας.'; + + @override + String get error => 'Σφάλμα'; + + @override + String get ok => 'Οκ'; + + @override + String get faq => 'Συχνές Ερωτήσεις'; + + @override + String get contactSupport => 'Επικοινωνήστε με την υποστήριξη'; + + @override + String get emailYourLogs => 'Στείλτε με email τα αρχεία καταγραφής σας'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Παρακαλώ στείλτε τα αρχεία καταγραφής σας στο \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Αντιγραφή διεύθυνσης email'; + + @override + String get exportLogs => 'Εξαγωγή αρχείων καταγραφής'; + + @override + String get cancel => 'Ακύρωση'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Αναφορά Σφάλματος'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Συνδεδεμένο στο $endpoint'; + } + + @override + String get save => 'Αποθήκευση'; + + @override + String get send => 'Αποστολή'; + + @override + String get saveOrSendDescription => + 'Θέλετε να το αποθηκεύσετε στον αποθηκευτικό σας χώρο (φάκελος Λήψεις από προεπιλογή) ή να το στείλετε σε άλλες εφαρμογές;'; + + @override + String get saveOnlyDescription => + 'Θέλετε να το αποθηκεύσετε στον αποθηκευτικό σας χώρο (φάκελος Λήψεις από προεπιλογή);'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Email'; + + @override + String get verify => 'Επαλήθευση'; + + @override + String get invalidEmailTitle => 'Μη έγκυρη διεύθυνση email'; + + @override + String get invalidEmailMessage => + 'Παρακαλούμε εισάγετε μια έγκυρη διεύθυνση email.'; + + @override + String get pleaseWait => 'Παρακαλώ περιμένετε…'; + + @override + String get verifyPassword => 'Επαλήθευση κωδικού πρόσβασης'; + + @override + String get incorrectPasswordTitle => 'Λάθος κωδικός πρόσβασης'; + + @override + String get pleaseTryAgain => 'Παρακαλώ δοκιμάστε ξανά'; + + @override + String get enterPassword => 'Εισάγετε κωδικό πρόσβασης'; + + @override + String get enterYourPasswordHint => 'Εισάγετε τον κωδικό πρόσβασης σας'; + + @override + String get activeSessions => 'Ενεργές συνεδρίες'; + + @override + String get oops => 'Ουπς'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Κάτι πήγε στραβά, παρακαλώ προσπαθήστε ξανά'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Αυτό θα σας αποσυνδέσει από αυτή τη συσκευή!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Αυτό θα σας αποσυνδέσει από την ακόλουθη συσκευή:'; + + @override + String get terminateSession => 'Τερματισμός συνεδρίας;'; + + @override + String get terminate => 'Τερματισμός'; + + @override + String get thisDevice => 'Αυτή η συσκευή'; + + @override + String get createAccount => 'Δημιουργία λογαριασμού'; + + @override + String get weakStrength => 'Αδύναμος'; + + @override + String get moderateStrength => 'Μέτριος'; + + @override + String get strongStrength => 'Δυνατός'; + + @override + String get deleteAccount => 'Διαγραφή λογαριασμού'; + + @override + String get deleteAccountQuery => + 'Λυπόμαστε που σας βλέπουμε να φεύγετε. Αντιμετωπίζετε κάποιο πρόβλημα;'; + + @override + String get yesSendFeedbackAction => 'Ναι, αποστολή σχολίων'; + + @override + String get noDeleteAccountAction => 'Όχι, διαγραφή λογαριασμού'; + + @override + String get initiateAccountDeleteTitle => + 'Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να ξεκινήσετε τη διαγραφή λογαριασμού'; + + @override + String get confirmAccountDeleteTitle => 'Επιβεβαίωση διαγραφής λογαριασμού'; + + @override + String get confirmAccountDeleteMessage => + 'Αυτός ο λογαριασμός είναι συνδεδεμένος με άλλες εφαρμογές Ente, εάν χρησιμοποιείτε κάποια.\n\nΤα δεδομένα σας, σε όλες τις εφαρμογές Ente, θα προγραμματιστούν για διαγραφή και ο λογαριασμός σας θα διαγραφεί οριστικά.'; + + @override + String get delete => 'Διαγραφή'; + + @override + String get createNewAccount => 'Δημιουργία νέου λογαριασμού'; + + @override + String get password => 'Κωδικόs πρόσβασης'; + + @override + String get confirmPassword => 'Επιβεβαίωση κωδικού πρόσβασης'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Ισχύς κωδικού πρόσβασης: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Πώς ακούσατε για το Ente; (προαιρετικό)'; + + @override + String get hearUsExplanation => + 'Δεν παρακολουθούμε εγκαταστάσεις εφαρμογών. Θα βοηθούσε αν μας είπατε πού μας βρήκατε!'; + + @override + String get signUpTerms => + 'Συμφωνώ με τους όρους χρήσης και την πολιτική απορρήτου'; + + @override + String get termsOfServicesTitle => 'Όροι'; + + @override + String get privacyPolicyTitle => 'Πολιτική Απορρήτου'; + + @override + String get ackPasswordLostWarning => + 'Καταλαβαίνω ότι αν χάσω τον κωδικό μου μπορεί να χάσω τα δεδομένα μου αφού τα δεδομένα μου είναι από άκρο-σε-άκρο κρυπτογραφημένα.'; + + @override + String get encryption => 'Kρυπτογράφηση'; + + @override + String get logInLabel => 'Σύνδεση'; + + @override + String get welcomeBack => 'Καλωσορίσατε και πάλι!'; + + @override + String get loginTerms => + 'Κάνοντας κλικ στη σύνδεση, συμφωνώ με τους όρους χρήσης και την πολιτική απορρήτου'; + + @override + String get noInternetConnection => 'Χωρίς σύνδεση στο διαδίκτυο'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Παρακαλούμε ελέγξτε τη σύνδεσή σας στο διαδίκτυο και προσπαθήστε ξανά.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Η επαλήθευση απέτυχε, παρακαλώ προσπαθήστε ξανά'; + + @override + String get recreatePasswordTitle => 'Επαναδημιουργία κωδικού πρόσβασης'; + + @override + String get recreatePasswordBody => + 'Η τρέχουσα συσκευή δεν είναι αρκετά ισχυρή για να επαληθεύσει τον κωδικό πρόσβασής σας, αλλά μπορούμε να τον αναδημιουργήσουμε με έναν τρόπο που λειτουργεί με όλες τις συσκευές.\n\nΠαρακαλούμε συνδεθείτε χρησιμοποιώντας το κλειδί ανάκτησης και αναδημιουργήστε τον κωδικό πρόσβασής σας (μπορείτε να χρησιμοποιήσετε ξανά τον ίδιο αν το επιθυμείτε).'; + + @override + String get useRecoveryKey => 'Χρήση κλειδιού ανάκτησης'; + + @override + String get forgotPassword => 'Ξέχασα τον κωδικό πρόσβασης σας'; + + @override + String get changeEmail => 'Αλλαγή email'; + + @override + String get verifyEmail => 'Επαλήθευση email'; + + @override + String weHaveSendEmailTo(String email) { + return 'Έχουμε στείλει ένα μήνυμα στο $email'; + } + + @override + String get toResetVerifyEmail => + 'Για να επαναφέρετε τον κωδικό πρόσβασής σας, επαληθεύστε πρώτα το email σας.'; + + @override + String get checkInboxAndSpamFolder => + 'Παρακαλώ ελέγξτε τα εισερχόμενά σας (και τα ανεπιθύμητα) για να ολοκληρώσετε την επαλήθευση'; + + @override + String get tapToEnterCode => 'Πατήστε για να εισάγετε τον κωδικό'; + + @override + String get sendEmail => 'Αποστολή email'; + + @override + String get resendEmail => 'Επανάληψη αποστολής email'; + + @override + String get passKeyPendingVerification => + 'Η επαλήθευση εξακολουθεί να εκκρεμεί'; + + @override + String get loginSessionExpired => 'Η συνεδρία έληξε'; + + @override + String get loginSessionExpiredDetails => + 'Η συνεδρία σας έληξε. Παρακαλώ συνδεθείτε ξανά.'; + + @override + String get passkeyAuthTitle => 'Επιβεβαίωση κλειδιού πρόσβασης'; + + @override + String get waitingForVerification => 'Αναμονή για επαλήθευση...'; + + @override + String get tryAgain => 'Προσπαθήστε ξανά'; + + @override + String get checkStatus => 'Έλεγχος κατάστασης'; + + @override + String get loginWithTOTP => 'Είσοδος με TOTP'; + + @override + String get recoverAccount => 'Ανάκτηση λογαριασμού'; + + @override + String get setPasswordTitle => 'Ορισμός κωδικού πρόσβασης'; + + @override + String get changePasswordTitle => 'Αλλαγή κωδικού πρόσβασής'; + + @override + String get resetPasswordTitle => 'Επαναφορά κωδικού πρόσβασης'; + + @override + String get encryptionKeys => 'Κλειδιά κρυπτογράφησης'; + + @override + String get enterPasswordToEncrypt => + 'Εισάγετε έναν κωδικό πρόσβασης που μπορούμε να χρησιμοποιήσουμε για την κρυπτογράφηση των δεδομένων σας'; + + @override + String get enterNewPasswordToEncrypt => + 'Εισάγετε ένα νέο κωδικό πρόσβασης που μπορούμε να χρησιμοποιήσουμε για να κρυπτογραφήσουμε τα δεδομένα σας'; + + @override + String get passwordWarning => + 'Δεν αποθηκεύουμε αυτόν τον κωδικό πρόσβασης, οπότε αν τον ξεχάσετε δεν μπορούμε να αποκρυπτογραφήσουμε τα δεδομένα σας'; + + @override + String get howItWorks => 'Πώς λειτουργεί'; + + @override + String get generatingEncryptionKeys => + 'Δημιουργία κλειδιών κρυπτογράφησης...'; + + @override + String get passwordChangedSuccessfully => + 'Ο κωδικός πρόσβασης άλλαξε επιτυχώς'; + + @override + String get signOutFromOtherDevices => 'Αποσύνδεση από άλλες συσκευές'; + + @override + String get signOutOtherBody => + 'Αν νομίζετε ότι κάποιος μπορεί να γνωρίζει τον κωδικό πρόσβασής σας, μπορείτε να αναγκάσετε όλες τις άλλες συσκευές που χρησιμοποιούν το λογαριασμό σας να αποσυνδεθούν.'; + + @override + String get signOutOtherDevices => 'Αποσύνδεση άλλων συσκευών'; + + @override + String get doNotSignOut => 'Μην αποσυνδεθείτε'; + + @override + String get generatingEncryptionKeysTitle => + 'Δημιουργία κλειδιών κρυπτογράφησης…'; + + @override + String get continueLabel => 'Συνέχεια'; + + @override + String get insecureDevice => 'Μη ασφαλής συσκευή'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Συγγνώμη, δεν μπορέσαμε να δημιουργήσουμε ασφαλή κλειδιά σε αυτήν τη συσκευή.\n\nπαρακαλώ εγγραφείτε από μια διαφορετική συσκευή.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Το κλειδί ανάκτησης αντιγράφηκε στο πρόχειρο'; + + @override + String get recoveryKey => 'Κλειδί ανάκτησης'; + + @override + String get recoveryKeyOnForgotPassword => + 'Εάν ξεχάσετε τον κωδικό πρόσβασής σας, ο μόνος τρόπος για να ανακτήσετε τα δεδομένα σας είναι με αυτό το κλειδί.'; + + @override + String get recoveryKeySaveDescription => + 'Δεν αποθηκεύουμε αυτό το κλειδί, παρακαλώ αποθηκεύστε αυτό το κλειδί 24 λέξεων σε μια ασφαλή τοποθεσία.'; + + @override + String get doThisLater => 'Κάντε το αργότερα'; + + @override + String get saveKey => 'Αποθήκευση κλειδιού'; + + @override + String get recoveryKeySaved => + 'Το κλειδί ανάκτησης αποθηκεύτηκε στο φάκελο Λήψεις!'; + + @override + String get noRecoveryKeyTitle => 'Χωρίς κλειδί ανάκτησης;'; + + @override + String get twoFactorAuthTitle => 'Αυθεντικοποίηση δύο παραγόντων'; + + @override + String get enterCodeHint => + 'Εισάγετε τον 6ψήφιο κωδικό από \nτην εφαρμογή αυθεντικοποίησης'; + + @override + String get lostDeviceTitle => 'Χαμένη συσκευή;'; + + @override + String get enterRecoveryKeyHint => 'Εισάγετε το κλειδί ανάκτησης σας'; + + @override + String get recover => 'Ανάκτηση'; + + @override + String get loggingOut => 'Αποσύνδεση…'; + + @override + String get immediately => 'Άμεσα'; + + @override + String get appLock => 'Κλείδωμα εφαρμογής'; + + @override + String get autoLock => 'Αυτόματο κλείδωμα'; + + @override + String get noSystemLockFound => 'Δεν βρέθηκε κλείδωμα συστήματος'; + + @override + String get deviceLockEnablePreSteps => + 'Για να ενεργοποιήσετε το κλείδωμα της συσκευής, παρακαλώ ρυθμίστε τον κωδικό πρόσβασης της συσκευής ή το κλείδωμα οθόνης στις ρυθμίσεις του συστήματός σας.'; + + @override + String get appLockDescription => + 'Επιλέξτε ανάμεσα στην προεπιλεγμένη οθόνη κλειδώματος της συσκευής σας και σε μια προσαρμοσμένη οθόνη κλειδώματος με ένα PIN ή έναν κωδικό πρόσβασης.'; + + @override + String get deviceLock => 'Κλείδωμα συσκευής'; + + @override + String get pinLock => 'Κλείδωμα καρφιτσωμάτων'; + + @override + String get autoLockFeatureDescription => + 'Χρόνος μετά τον οποίο η εφαρμογή κλειδώνει μετά την τοποθέτηση στο παρασκήνιο'; + + @override + String get hideContent => 'Απόκρυψη περιεχομένου'; + + @override + String get hideContentDescriptionAndroid => + 'Απόκρυψη περιεχομένου εφαρμογής στην εναλλαγή εφαρμογών και απενεργοποίηση στιγμιότυπων οθόνης'; + + @override + String get hideContentDescriptioniOS => + 'Απόκρυψη περιεχομένου εφαρμογών στην εναλλαγή εφαρμογών'; + + @override + String get tooManyIncorrectAttempts => 'Πάρα πολλές εσφαλμένες προσπάθειες'; + + @override + String get tapToUnlock => 'Πατήστε για ξεκλείδωμα'; + + @override + String get areYouSureYouWantToLogout => + 'Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;'; + + @override + String get yesLogout => 'Ναι, αποσύνδεση'; + + @override + String get authToViewSecrets => + 'Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να δείτε τα μυστικά σας'; + + @override + String get next => 'Επόμενο'; + + @override + String get setNewPassword => 'Ορίστε νέο κωδικό πρόσβασης'; + + @override + String get enterPin => 'Εισαγωγή PIN'; + + @override + String get setNewPin => 'Ορίστε νέο PIN'; + + @override + String get confirm => 'Επιβεβαίωση'; + + @override + String get reEnterPassword => 'Πληκτρολογήστε ξανά τον κωδικό πρόσβασης'; + + @override + String get reEnterPin => 'Πληκτρολογήστε ξανά το PIN'; + + @override + String get androidBiometricHint => 'Επαλήθευση ταυτότητας'; + + @override + String get androidBiometricNotRecognized => + 'Δεν αναγνωρίζεται. Δοκιμάστε ξανά.'; + + @override + String get androidBiometricSuccess => 'Επιτυχία'; + + @override + String get androidCancelButton => 'Ακύρωση'; + + @override + String get androidSignInTitle => 'Απαιτείται έλεγχος ταυτότητας'; + + @override + String get androidBiometricRequiredTitle => 'Απαιτούνται βιομετρικά'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Απαιτούνται στοιχεία συσκευής'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Απαιτούνται στοιχεία συσκευής'; + + @override + String get goToSettings => 'Μετάβαση στις ρυθμίσεις'; + + @override + String get androidGoToSettingsDescription => + 'Η βιομετρική πιστοποίηση δεν έχει ρυθμιστεί στη συσκευή σας. Μεταβείτε στις \'Ρυθμίσεις > Ασφάλεια\' για να προσθέσετε βιομετρική ταυτοποίηση.'; + + @override + String get iOSLockOut => + 'Η βιομετρική ταυτοποίηση είναι απενεργοποιημένη. Παρακαλώ κλειδώστε και ξεκλειδώστε την οθόνη σας για να την ενεργοποιήσετε.'; + + @override + String get iOSOkButton => 'ΟΚ'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => 'Αυτό το email είναι ήδη σε χρήση'; + + @override + String emailChangedTo(String newEmail) { + return 'Το email άλλαξε σε $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Αποτυχία ελέγχου ταυτότητας, παρακαλώ προσπαθήστε ξανά'; + + @override + String get authenticationSuccessful => 'Επιτυχής έλεγχος ταυτότητας!'; + + @override + String get sessionExpired => 'Η συνεδρία έληξε'; + + @override + String get incorrectRecoveryKey => 'Εσφαλμένο κλειδί ανάκτησης'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Το κλειδί ανάκτησης που εισάγατε είναι εσφαλμένο'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Η αυθεντικοποίηση δύο παραγόντων επαναφέρθηκε επιτυχώς'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Ο κωδικός επαλήθευσης σας έχει λήξει'; + + @override + String get incorrectCode => 'Εσφαλμένος κωδικός'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Λυπούμαστε, ο κωδικός που εισαγάγατε είναι εσφαλμένος'; + + @override + String get developerSettings => 'Ρυθμίσεις προγραμματιστή'; + + @override + String get serverEndpoint => 'Τερματικό σημείο διακομιστή'; + + @override + String get invalidEndpoint => 'Μη έγκυρο τερματικό σημείο'; + + @override + String get invalidEndpointMessage => + 'Λυπούμαστε, το τερματικό σημείο που εισάγατε δεν είναι έγκυρο. Παρακαλώ εισάγετε ένα έγκυρο τερματικό σημείο και προσπαθήστε ξανά.'; + + @override + String get endpointUpdatedMessage => + 'Το τερματκό σημείο ενημερώθηκε επιτυχώς'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_en.dart b/mobile/packages/strings/lib/l10n/strings_localizations_en.dart new file mode 100644 index 0000000000..37baaf6702 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_en.dart @@ -0,0 +1,626 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class StringsLocalizationsEn extends StringsLocalizations { + StringsLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'Error'; + + @override + String get ok => 'Ok'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'Contact support'; + + @override + String get emailYourLogs => 'Email your logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Please send the logs to \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copy email address'; + + @override + String get exportLogs => 'Export logs'; + + @override + String get cancel => 'Cancel'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Report a bug'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'Save'; + + @override + String get send => 'Send'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Email'; + + @override + String get verify => 'Verify'; + + @override + String get invalidEmailTitle => 'Invalid email address'; + + @override + String get invalidEmailMessage => 'Please enter a valid email address.'; + + @override + String get pleaseWait => 'Please wait...'; + + @override + String get verifyPassword => 'Verify password'; + + @override + String get incorrectPasswordTitle => 'Incorrect password'; + + @override + String get pleaseTryAgain => 'Please try again'; + + @override + String get enterPassword => 'Enter password'; + + @override + String get enterYourPasswordHint => 'Enter your password'; + + @override + String get activeSessions => 'Active sessions'; + + @override + String get oops => 'Oops'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Something went wrong, please try again'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'Terminate session?'; + + @override + String get terminate => 'Terminate'; + + @override + String get thisDevice => 'This device'; + + @override + String get createAccount => 'Create account'; + + @override + String get weakStrength => 'Weak'; + + @override + String get moderateStrength => 'Moderate'; + + @override + String get strongStrength => 'Strong'; + + @override + String get deleteAccount => 'Delete account'; + + @override + String get deleteAccountQuery => + 'We\'ll be sorry to see you go. Are you facing some issue?'; + + @override + String get yesSendFeedbackAction => 'Yes, send feedback'; + + @override + String get noDeleteAccountAction => 'No, delete account'; + + @override + String get initiateAccountDeleteTitle => + 'Please authenticate to initiate account deletion'; + + @override + String get confirmAccountDeleteTitle => 'Confirm account deletion'; + + @override + String get 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.'; + + @override + String get delete => 'Delete'; + + @override + String get createNewAccount => 'Create new account'; + + @override + String get password => 'Password'; + + @override + String get confirmPassword => 'Confirm password'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Password strength: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'We don\'t track app installs. It\'d help if you told us where you found us!'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Terms'; + + @override + String get privacyPolicyTitle => 'Privacy Policy'; + + @override + String get ackPasswordLostWarning => + 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'; + + @override + String get encryption => 'Encryption'; + + @override + String get logInLabel => 'Log in'; + + @override + String get welcomeBack => 'Welcome back!'; + + @override + String get loginTerms => + 'By clicking log in, I agree to the terms of service and privacy policy'; + + @override + String get noInternetConnection => 'No internet connection'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Please check your internet connection and try again.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Recreate password'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'Use recovery key'; + + @override + String get forgotPassword => 'Forgot password'; + + @override + String get changeEmail => 'Change email'; + + @override + String get verifyEmail => 'Verify email'; + + @override + String weHaveSendEmailTo(String email) { + return 'We have sent a mail to $email'; + } + + @override + String get toResetVerifyEmail => + 'To reset your password, please verify your email first.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Tap to enter code'; + + @override + String get sendEmail => 'Send email'; + + @override + String get resendEmail => 'Resend email'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Session expired'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Try again'; + + @override + String get checkStatus => 'Check status'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Recover account'; + + @override + String get setPasswordTitle => 'Set password'; + + @override + String get changePasswordTitle => 'Change password'; + + @override + String get resetPasswordTitle => 'Reset password'; + + @override + String get encryptionKeys => 'Encryption keys'; + + @override + String get enterPasswordToEncrypt => + 'Enter a password we can use to encrypt your data'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'How it works'; + + @override + String get generatingEncryptionKeys => 'Generating encryption keys...'; + + @override + String get passwordChangedSuccessfully => 'Password changed successfully'; + + @override + String get signOutFromOtherDevices => 'Sign out from other devices'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'Sign out other devices'; + + @override + String get doNotSignOut => 'Do not sign out'; + + @override + String get generatingEncryptionKeysTitle => 'Generating encryption keys...'; + + @override + String get continueLabel => 'Continue'; + + @override + String get insecureDevice => 'Insecure device'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => 'Recovery key copied to clipboard'; + + @override + String get recoveryKey => 'Recovery key'; + + @override + String get recoveryKeyOnForgotPassword => + 'If you forget your password, the only way you can recover your data is with this key.'; + + @override + String get recoveryKeySaveDescription => + 'We don\'t store this key, please save this 24 word key in a safe place.'; + + @override + String get doThisLater => 'Do this later'; + + @override + String get saveKey => 'Save key'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'No recovery key?'; + + @override + String get twoFactorAuthTitle => 'Two-factor authentication'; + + @override + String get enterCodeHint => + 'Enter the 6-digit code from\nyour authenticator app'; + + @override + String get lostDeviceTitle => 'Lost device?'; + + @override + String get enterRecoveryKeyHint => 'Enter your recovery key'; + + @override + String get recover => 'Recover'; + + @override + String get loggingOut => 'Logging out...'; + + @override + String get immediately => 'Immediately'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Hide content'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Tap to unlock'; + + @override + String get areYouSureYouWantToLogout => 'Are you sure you want to logout?'; + + @override + String get yesLogout => 'Yes, logout'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'Enter PIN'; + + @override + String get setNewPin => 'Set new PIN'; + + @override + String get confirm => 'Confirm'; + + @override + String get reEnterPassword => 'Re-enter password'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verify identity'; + + @override + String get androidBiometricNotRecognized => 'Not recognized. Try again.'; + + @override + String get androidBiometricSuccess => 'Success'; + + @override + String get androidCancelButton => 'Cancel'; + + @override + String get androidSignInTitle => 'Authentication required'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Go to settings'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => 'This email is already in use'; + + @override + String emailChangedTo(String newEmail) { + return 'Email changed to $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'Session expired'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Incorrect code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_es.dart b/mobile/packages/strings/lib/l10n/strings_localizations_es.dart new file mode 100644 index 0000000000..d93fcd07e8 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_es.dart @@ -0,0 +1,635 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Spanish Castilian (`es`). +class StringsLocalizationsEs extends StringsLocalizations { + StringsLocalizationsEs([String locale = 'es']) : super(locale); + + @override + String get networkHostLookUpErr => + 'No se puede conectar a Ente. Por favor, comprueba tu configuración de red y ponte en contacto con el soporte técnico si el error persiste.'; + + @override + String get networkConnectionRefusedErr => + 'No se puede conectar a Ente. Por favor, vuelve a intentarlo pasado un tiempo. Si el error persiste, ponte en contacto con el soporte técnico.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Parece que algo salió mal. Por favor, vuelve a intentarlo pasado un tiempo. Si el error persiste, ponte en contacto con nuestro equipo de soporte.'; + + @override + String get error => 'Error'; + + @override + String get ok => 'Ok'; + + @override + String get faq => 'Preguntas Frecuentes'; + + @override + String get contactSupport => 'Ponerse en contacto con el equipo de soporte'; + + @override + String get emailYourLogs => 'Envíe sus registros por correo electrónico'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Por favor, envíe los registros a $toEmail'; + } + + @override + String get copyEmailAddress => 'Copiar dirección de correo electrónico'; + + @override + String get exportLogs => 'Exportar registros'; + + @override + String get cancel => 'Cancelar'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Reportar un error'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Conectado a $endpoint'; + } + + @override + String get save => 'Guardar'; + + @override + String get send => 'Enviar'; + + @override + String get saveOrSendDescription => + '¿Desea guardar el archivo en el almacenamiento (carpeta Descargas por defecto) o enviarlo a otras aplicaciones?'; + + @override + String get saveOnlyDescription => + '¿Desea guardar el archivo en el almacenamiento (carpeta Descargas por defecto)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Correo electrónico'; + + @override + String get verify => 'Verificar'; + + @override + String get invalidEmailTitle => 'Dirección de correo electrónico no válida'; + + @override + String get invalidEmailMessage => + 'Por favor, introduce una dirección de correo electrónico válida.'; + + @override + String get pleaseWait => 'Por favor, espere...'; + + @override + String get verifyPassword => 'Verificar contraseña'; + + @override + String get incorrectPasswordTitle => 'Contraseña incorrecta'; + + @override + String get pleaseTryAgain => 'Por favor, inténtalo de nuevo'; + + @override + String get enterPassword => 'Introduzca la contraseña'; + + @override + String get enterYourPasswordHint => 'Introduce tu contraseña'; + + @override + String get activeSessions => 'Sesiones activas'; + + @override + String get oops => 'Ups'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Algo ha ido mal, por favor, inténtelo de nuevo'; + + @override + String get thisWillLogYouOutOfThisDevice => + '¡Esto cerrará la sesión de este dispositivo!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Esto cerrará la sesión del siguiente dispositivo:'; + + @override + String get terminateSession => '¿Terminar sesión?'; + + @override + String get terminate => 'Terminar'; + + @override + String get thisDevice => 'Este dispositivo'; + + @override + String get createAccount => 'Crear cuenta'; + + @override + String get weakStrength => 'Poco segura'; + + @override + String get moderateStrength => 'Moderada'; + + @override + String get strongStrength => 'Segura'; + + @override + String get deleteAccount => 'Eliminar cuenta'; + + @override + String get deleteAccountQuery => + 'Lamentamos que te vayas. ¿Estás teniendo algún problema?'; + + @override + String get yesSendFeedbackAction => 'Sí, enviar comentarios'; + + @override + String get noDeleteAccountAction => 'No, eliminar cuenta'; + + @override + String get initiateAccountDeleteTitle => + 'Por favor, autentícate para iniciar la eliminación de la cuenta'; + + @override + String get confirmAccountDeleteTitle => 'Confirmar eliminación de la cuenta'; + + @override + String get confirmAccountDeleteMessage => + 'Esta cuenta está vinculada a otras aplicaciones de Ente, si utilizas alguna. \n\nSe programará la eliminación de los datos cargados en todas las aplicaciones de Ente, y tu cuenta se eliminará permanentemente.'; + + @override + String get delete => 'Borrar'; + + @override + String get createNewAccount => 'Crear cuenta nueva'; + + @override + String get password => 'Contraseña'; + + @override + String get confirmPassword => 'Confirmar contraseña'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Fortaleza de la contraseña: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => '¿Cómo conoció Ente? (opcional)'; + + @override + String get hearUsExplanation => + 'No rastreamos la instalación de las aplicaciones. ¡Nos ayudaría si nos dijera dónde nos encontró!'; + + @override + String get signUpTerms => + 'Estoy de acuerdo con los términos del servicio y la política de privacidad'; + + @override + String get termsOfServicesTitle => 'Términos'; + + @override + String get privacyPolicyTitle => 'Política de Privacidad'; + + @override + String get ackPasswordLostWarning => + 'Entiendo que si pierdo mi contraseña podría perder mis datos, ya que mis datos están cifrados de extremo a extremo.'; + + @override + String get encryption => 'Cifrado'; + + @override + String get logInLabel => 'Iniciar sesión'; + + @override + String get welcomeBack => '¡Te damos la bienvenida otra vez!'; + + @override + String get loginTerms => + 'Al hacer clic en iniciar sesión, acepto los términos de servicio y la política de privacidad'; + + @override + String get noInternetConnection => 'No hay conexión a Internet'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Compruebe su conexión a Internet e inténtelo de nuevo.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verificación fallida, por favor inténtalo de nuevo'; + + @override + String get recreatePasswordTitle => 'Recrear contraseña'; + + @override + String get recreatePasswordBody => + 'El dispositivo actual no es lo suficientemente potente para verificar su contraseña, pero podemos regenerarla de manera que funcione con todos los dispositivos.\n\nPor favor inicie sesión usando su clave de recuperación y regenere su contraseña (puede volver a utilizar la misma si lo desea).'; + + @override + String get useRecoveryKey => 'Usar clave de recuperación'; + + @override + String get forgotPassword => 'Olvidé mi contraseña'; + + @override + String get changeEmail => 'Cambiar correo electrónico'; + + @override + String get verifyEmail => 'Verificar correo electrónico'; + + @override + String weHaveSendEmailTo(String email) { + return 'Hemos enviado un correo electrónico a $email'; + } + + @override + String get toResetVerifyEmail => + 'Para restablecer tu contraseña, por favor verifica tu correo electrónico primero.'; + + @override + String get checkInboxAndSpamFolder => + 'Por favor revisa tu bandeja de entrada (y spam) para completar la verificación'; + + @override + String get tapToEnterCode => 'Toca para introducir el código'; + + @override + String get sendEmail => 'Enviar correo electrónico'; + + @override + String get resendEmail => 'Reenviar correo electrónico'; + + @override + String get passKeyPendingVerification => + 'La verificación todavía está pendiente'; + + @override + String get loginSessionExpired => 'La sesión ha expirado'; + + @override + String get loginSessionExpiredDetails => + 'Tu sesión ha expirado. Por favor, vuelve a iniciar sesión.'; + + @override + String get passkeyAuthTitle => 'Verificación de clave de acceso'; + + @override + String get waitingForVerification => 'Esperando verificación...'; + + @override + String get tryAgain => 'Inténtelo de nuevo'; + + @override + String get checkStatus => 'Comprobar estado'; + + @override + String get loginWithTOTP => 'Inicio de sesión con TOTP'; + + @override + String get recoverAccount => 'Recuperar cuenta'; + + @override + String get setPasswordTitle => 'Establecer contraseña'; + + @override + String get changePasswordTitle => 'Cambiar contraseña'; + + @override + String get resetPasswordTitle => 'Restablecer contraseña'; + + @override + String get encryptionKeys => 'Claves de cifrado'; + + @override + String get enterPasswordToEncrypt => + 'Introduzca una contraseña que podamos usar para cifrar sus datos'; + + @override + String get enterNewPasswordToEncrypt => + 'Introduzca una contraseña nueva que podamos usar para cifrar sus datos'; + + @override + String get passwordWarning => + 'No almacenamos esta contraseña, así que si la olvidas, no podremos descifrar tus datos'; + + @override + String get howItWorks => 'Cómo funciona'; + + @override + String get generatingEncryptionKeys => 'Generando claves de cifrado...'; + + @override + String get passwordChangedSuccessfully => 'Contraseña cambiada correctamente'; + + @override + String get signOutFromOtherDevices => 'Cerrar sesión en otros dispositivos'; + + @override + String get signOutOtherBody => + 'Si crees que alguien puede conocer tu contraseña, puedes forzar a todos los demás dispositivos que usen tu cuenta a cerrar la sesión.'; + + @override + String get signOutOtherDevices => 'Cerrar la sesión en otros dispositivos'; + + @override + String get doNotSignOut => 'No cerrar la sesión'; + + @override + String get generatingEncryptionKeysTitle => 'Generando claves de cifrado...'; + + @override + String get continueLabel => 'Continuar'; + + @override + String get insecureDevice => 'Dispositivo inseguro'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Lo sentimos, no hemos podido generar claves seguras en este dispositivo.\n\nRegístrate desde un dispositivo diferente.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Clave de recuperación copiada al portapapeles'; + + @override + String get recoveryKey => 'Clave de recuperación'; + + @override + String get recoveryKeyOnForgotPassword => + 'Si olvidas tu contraseña, la única forma de recuperar tus datos es con esta clave.'; + + @override + String get recoveryKeySaveDescription => + 'Nosotros no almacenamos esta clave, por favor guarda esta clave de 24 palabras en un lugar seguro.'; + + @override + String get doThisLater => 'Hacer esto más tarde'; + + @override + String get saveKey => 'Guardar clave'; + + @override + String get recoveryKeySaved => + '¡Clave de recuperación guardada en la carpeta Descargas!'; + + @override + String get noRecoveryKeyTitle => '¿No tienes la clave de recuperación?'; + + @override + String get twoFactorAuthTitle => 'Autenticación de dos factores'; + + @override + String get enterCodeHint => + 'Ingrese el código de seis dígitos de su aplicación de autenticación'; + + @override + String get lostDeviceTitle => '¿Dispositivo perdido?'; + + @override + String get enterRecoveryKeyHint => 'Introduce tu clave de recuperación'; + + @override + String get recover => 'Recuperar'; + + @override + String get loggingOut => 'Cerrando sesión...'; + + @override + String get immediately => 'Inmediatamente'; + + @override + String get appLock => 'Bloqueo de aplicación'; + + @override + String get autoLock => 'Bloqueo automático'; + + @override + String get noSystemLockFound => 'Bloqueo del sistema no encontrado'; + + @override + String get deviceLockEnablePreSteps => + 'Para activar el bloqueo de la aplicación, por favor configura el código de acceso del dispositivo o el bloqueo de pantalla en los ajustes de tu sistema.'; + + @override + String get appLockDescription => + 'Elija entre la pantalla de bloqueo por defecto de su dispositivo y una pantalla de bloqueo personalizada con un PIN o contraseña.'; + + @override + String get deviceLock => 'Bloqueo del dispositivo'; + + @override + String get pinLock => 'Bloqueo con PIN'; + + @override + String get autoLockFeatureDescription => + 'Tiempo tras el cual la aplicación se bloquea después de ser colocada en segundo plano'; + + @override + String get hideContent => 'Ocultar contenido'; + + @override + String get hideContentDescriptionAndroid => + 'Oculta el contenido de la aplicación en el selector de aplicaciones y desactiva las capturas de pantalla'; + + @override + String get hideContentDescriptioniOS => + 'Ocultar el contenido de la aplicación en el selector de aplicaciones'; + + @override + String get tooManyIncorrectAttempts => 'Demasiados intentos incorrectos'; + + @override + String get tapToUnlock => 'Toca para desbloquear'; + + @override + String get areYouSureYouWantToLogout => + '¿Seguro que quieres cerrar la sesión?'; + + @override + String get yesLogout => 'Sí, cerrar la sesión'; + + @override + String get authToViewSecrets => + 'Por favor, autentícate para ver tus secretos'; + + @override + String get next => 'Siguiente'; + + @override + String get setNewPassword => 'Establece una nueva contraseña'; + + @override + String get enterPin => 'Ingresa el PIN'; + + @override + String get setNewPin => 'Establecer nuevo PIN'; + + @override + String get confirm => 'Confirmar'; + + @override + String get reEnterPassword => 'Reescribe tu contraseña'; + + @override + String get reEnterPin => 'Reescribe tu PIN'; + + @override + String get androidBiometricHint => 'Verificar identidad'; + + @override + String get androidBiometricNotRecognized => + 'No reconocido. Inténtalo de nuevo.'; + + @override + String get androidBiometricSuccess => 'Autenticación exitosa'; + + @override + String get androidCancelButton => 'Cancelar'; + + @override + String get androidSignInTitle => 'Se necesita autenticación biométrica'; + + @override + String get androidBiometricRequiredTitle => + 'Se necesita autenticación biométrica'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Se necesitan credenciales de dispositivo'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Se necesitan credenciales de dispositivo'; + + @override + String get goToSettings => 'Ir a Ajustes'; + + @override + String get androidGoToSettingsDescription => + 'La autenticación biométrica no está configurada en tu dispositivo. Ve a \'Ajustes > Seguridad\' para configurar la autenticación biométrica.'; + + @override + String get iOSLockOut => + 'La autenticación biométrica está deshabilitada. Por favor bloquea y desbloquea la pantalla para habilitarla.'; + + @override + String get iOSOkButton => 'Aceptar'; + + @override + String get emailAlreadyRegistered => 'Correo electrónico ya registrado.'; + + @override + String get emailNotRegistered => 'Correo electrónico no registrado.'; + + @override + String get thisEmailIsAlreadyInUse => + 'Este correo electrónico ya está en uso'; + + @override + String emailChangedTo(String newEmail) { + return 'Correo electrónico cambiado a $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Error de autenticación, por favor inténtalo de nuevo'; + + @override + String get authenticationSuccessful => '¡Autenticación exitosa!'; + + @override + String get sessionExpired => 'La sesión ha expirado'; + + @override + String get incorrectRecoveryKey => 'Clave de recuperación incorrecta'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'La clave de recuperación introducida es incorrecta'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Autenticación de doble factor restablecida con éxito'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Tu código de verificación ha expirado'; + + @override + String get incorrectCode => 'Código incorrecto'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Lo sentimos, el código que has introducido es incorrecto'; + + @override + String get developerSettings => 'Ajustes de desarrollador'; + + @override + String get serverEndpoint => 'Endpoint del servidor'; + + @override + String get invalidEndpoint => 'Endpoint no válido'; + + @override + String get invalidEndpointMessage => + 'Lo sentimos, el endpoint introducido no es válido. Por favor, introduce un endpoint válido y vuelve a intentarlo.'; + + @override + String get endpointUpdatedMessage => 'Endpoint actualizado con éxito'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_et.dart b/mobile/packages/strings/lib/l10n/strings_localizations_et.dart new file mode 100644 index 0000000000..49506d60ef --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_et.dart @@ -0,0 +1,629 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Estonian (`et`). +class StringsLocalizationsEt extends StringsLocalizations { + StringsLocalizationsEt([String locale = 'et']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'Viga'; + + @override + String get ok => 'Sobib'; + + @override + String get faq => 'KKK'; + + @override + String get contactSupport => 'Võtke ühendust klienditoega'; + + @override + String get emailYourLogs => 'Email your logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Please send the logs to \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copy email address'; + + @override + String get exportLogs => 'Export logs'; + + @override + String get cancel => 'Katkesta'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Teata veast'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'Salvesta'; + + @override + String get send => 'Saada'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Sisesta oma uus e-posti aadress'; + + @override + String get email => 'E-post'; + + @override + String get verify => 'Kinnita'; + + @override + String get invalidEmailTitle => 'Vigane e-posti aadress'; + + @override + String get invalidEmailMessage => 'Palun sisesta korrektne e-posti aadress.'; + + @override + String get pleaseWait => 'Palun oota...'; + + @override + String get verifyPassword => 'Korda salasõna'; + + @override + String get incorrectPasswordTitle => 'Vale salasõna'; + + @override + String get pleaseTryAgain => 'Palun proovi uuesti'; + + @override + String get enterPassword => 'Sisesta salasõna'; + + @override + String get enterYourPasswordHint => 'Sisesta oma salasõna'; + + @override + String get activeSessions => 'Active sessions'; + + @override + String get oops => 'Vaat kus lops!'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Something went wrong, please try again'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'Terminate session?'; + + @override + String get terminate => 'Terminate'; + + @override + String get thisDevice => 'This device'; + + @override + String get createAccount => 'Create account'; + + @override + String get weakStrength => 'Nõrk'; + + @override + String get moderateStrength => 'Keskmine'; + + @override + String get strongStrength => 'Tugev'; + + @override + String get deleteAccount => 'Kustuta kasutajakonto'; + + @override + String get deleteAccountQuery => + 'Meil on kahju, et soovid lahkuda. Kas sul tekkis mõni viga või probleem?'; + + @override + String get yesSendFeedbackAction => 'Jah, saadan tagasisidet'; + + @override + String get noDeleteAccountAction => 'Ei, kustuta kasutajakonto'; + + @override + String get initiateAccountDeleteTitle => + 'Kasutajakonto kustutamiseks palun tuvasta end'; + + @override + String get confirmAccountDeleteTitle => 'Confirm account deletion'; + + @override + String get 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.'; + + @override + String get delete => 'Kustuta'; + + @override + String get createNewAccount => 'Loo uus kasutajakonto'; + + @override + String get password => 'Salasõna'; + + @override + String get confirmPassword => 'Korda salasõna'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Salasõna tugevus: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'We don\'t track app installs. It\'d help if you told us where you found us!'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Kasutustingimused'; + + @override + String get privacyPolicyTitle => 'Privaatsusreeglid'; + + @override + String get ackPasswordLostWarning => + 'Ma saan aru, et salasõna kaotamisel kaotan ka ligipääsu oma andmetele - minu andmed on ju läbivalt krüptitud.'; + + @override + String get encryption => 'Krüptimine'; + + @override + String get logInLabel => 'Logi sisse'; + + @override + String get welcomeBack => 'Tere tulemast tagasi!'; + + @override + String get loginTerms => + 'Sisselogdes nõustun kasutustingimustega ja privaatsusreeglitega'; + + @override + String get noInternetConnection => 'No internet connection'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Please check your internet connection and try again.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Loo salasõna uuesti'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'Kasuta taastevõtit'; + + @override + String get forgotPassword => 'Unustasin salasõna'; + + @override + String get changeEmail => 'Muuda e-posti aadressi'; + + @override + String get verifyEmail => 'Kinnita e-posti aadress'; + + @override + String weHaveSendEmailTo(String email) { + return 'We have sent a mail to $email'; + } + + @override + String get toResetVerifyEmail => + 'To reset your password, please verify your email first.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Tap to enter code'; + + @override + String get sendEmail => 'Saada e-kiri'; + + @override + String get resendEmail => 'Resend email'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Session expired'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Proovi uuesti'; + + @override + String get checkStatus => 'Kontrolli olekut'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Taasta oma kasutajakonto'; + + @override + String get setPasswordTitle => 'Sisesta salasõna'; + + @override + String get changePasswordTitle => 'Muuda salasõna'; + + @override + String get resetPasswordTitle => 'Lähtesta salasõna'; + + @override + String get encryptionKeys => 'Krüptovõtmed'; + + @override + String get enterPasswordToEncrypt => + 'Enter a password we can use to encrypt your data'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'Kuidas see töötab'; + + @override + String get generatingEncryptionKeys => 'Generating encryption keys...'; + + @override + String get passwordChangedSuccessfully => 'Salasõna muutmine õnnestus'; + + @override + String get signOutFromOtherDevices => 'Sign out from other devices'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'Sign out other devices'; + + @override + String get doNotSignOut => 'Do not sign out'; + + @override + String get generatingEncryptionKeysTitle => 'Loon krüptovõtmeid...'; + + @override + String get continueLabel => 'Continue'; + + @override + String get insecureDevice => 'Insecure device'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Taastevõti on kopeeritud lõikelauale'; + + @override + String get recoveryKey => 'Taastevõti'; + + @override + String get recoveryKeyOnForgotPassword => + 'Kui unustad oma salasõna, siis see krüptovõti on ainus võimalus sinu andmete taastamiseks.'; + + @override + String get recoveryKeySaveDescription => + 'We don\'t store this key, please save this 24 word key in a safe place.'; + + @override + String get doThisLater => 'Do this later'; + + @override + String get saveKey => 'Salvesta võti'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'Sul pole taastevõtit?'; + + @override + String get twoFactorAuthTitle => 'Two-factor authentication'; + + @override + String get enterCodeHint => + 'Sisesta oma autentimisrakendusest\n6-numbriline kood'; + + @override + String get lostDeviceTitle => 'Kas kaotasid oma seadme?'; + + @override + String get enterRecoveryKeyHint => 'Sisesta oma taastevõti'; + + @override + String get recover => 'Taasta'; + + @override + String get loggingOut => 'Väljalogimine...'; + + @override + String get immediately => 'Immediately'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Hide content'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Tap to unlock'; + + @override + String get areYouSureYouWantToLogout => + 'Kas oled kindel, et soovid välja logida?'; + + @override + String get yesLogout => 'Jah, logi välja'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Sisesta uus salasõna'; + + @override + String get enterPin => 'Sisesta PIN-kood'; + + @override + String get setNewPin => 'Määra uus PIN-kood'; + + @override + String get confirm => 'Confirm'; + + @override + String get reEnterPassword => 'Sisesta salasõna uuesti'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verify identity'; + + @override + String get androidBiometricNotRecognized => 'Not recognized. Try again.'; + + @override + String get androidBiometricSuccess => 'Success'; + + @override + String get androidCancelButton => 'Cancel'; + + @override + String get androidSignInTitle => 'Authentication required'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Go to settings'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => + 'E-posti aadress on juba registreeritud.'; + + @override + String get emailNotRegistered => 'E-posti aadress pole registreeritud.'; + + @override + String get thisEmailIsAlreadyInUse => 'This email is already in use'; + + @override + String emailChangedTo(String newEmail) { + return 'Email changed to $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'Sessioon on aegunud'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Incorrect code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_fa.dart b/mobile/packages/strings/lib/l10n/strings_localizations_fa.dart new file mode 100644 index 0000000000..8545b269d0 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_fa.dart @@ -0,0 +1,626 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Persian (`fa`). +class StringsLocalizationsFa extends StringsLocalizations { + StringsLocalizationsFa([String locale = 'fa']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'به نظر می‌رسد مشکلی پیش آمده است. لطفا بعد از مدتی دوباره تلاش کنید. اگر همچنان با خطا مواجه می‌شوید، لطفا با تیم پشتیبانی ما ارتباط برقرار کنید.'; + + @override + String get error => 'خطا'; + + @override + String get ok => 'تایید'; + + @override + String get faq => 'سوالات متداول'; + + @override + String get contactSupport => 'ارتباط با پشتیبانی'; + + @override + String get emailYourLogs => 'لاگ‌های خود را ایمیل کنید'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'لطفا لاگ‌ها را به ایمیل زیر ارسال کنید \n$toEmail'; + } + + @override + String get copyEmailAddress => 'کپی آدرس ایمیل'; + + @override + String get exportLogs => 'صدور لاگ‌ها'; + + @override + String get cancel => 'لغو'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'گزارش یک اشکال'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'متصل شده به $endpoint'; + } + + @override + String get save => 'ذخیره'; + + @override + String get send => 'ارسال'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'ایمیل'; + + @override + String get verify => 'تایید'; + + @override + String get invalidEmailTitle => 'آدرس ایمیل نامعتبر است'; + + @override + String get invalidEmailMessage => 'لطفا یک آدرس ایمیل معتبر وارد کنید.'; + + @override + String get pleaseWait => 'لطفا صبر کنید...'; + + @override + String get verifyPassword => 'تایید رمز عبور'; + + @override + String get incorrectPasswordTitle => 'رمز عبور نادرست'; + + @override + String get pleaseTryAgain => 'لطفا دوباره تلاش کنید'; + + @override + String get enterPassword => 'رمز عبور را وارد کنید'; + + @override + String get enterYourPasswordHint => 'رمز عبور خود را وارد کنید'; + + @override + String get activeSessions => 'نشست های فعال'; + + @override + String get oops => 'اوه'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'مشکلی پیش آمده، لطفا دوباره تلاش کنید'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'این کار شما را از این دستگاه خارج می‌کند!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'با این کار شما از دستگاه زیر خارج می‌شوید:'; + + @override + String get terminateSession => 'خروچ دستگاه؟'; + + @override + String get terminate => 'خروج'; + + @override + String get thisDevice => 'این دستگاه'; + + @override + String get createAccount => 'ایجاد حساب کاربری'; + + @override + String get weakStrength => 'ضعیف'; + + @override + String get moderateStrength => 'متوسط'; + + @override + String get strongStrength => 'قوی'; + + @override + String get deleteAccount => 'حذف حساب کاربری'; + + @override + String get deleteAccountQuery => + 'از رفتن شما متاسفیم. آیا با مشکلی روبرو هستید؟'; + + @override + String get yesSendFeedbackAction => 'بله، ارسال بازخورد'; + + @override + String get noDeleteAccountAction => 'خیر، حساب کاربری را حذف کن'; + + @override + String get initiateAccountDeleteTitle => + 'لطفا جهت شروع فرآیند حذف حساب کاربری، اعتبارسنجی کنید'; + + @override + String get confirmAccountDeleteTitle => 'تایید حذف حساب کاربری'; + + @override + String get 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.'; + + @override + String get delete => 'حذف'; + + @override + String get createNewAccount => 'ایجاد حساب کاربری جدید'; + + @override + String get password => 'رمز عبور'; + + @override + String get confirmPassword => 'تایید رمز عبور'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'قدرت رمز عبور: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'از کجا در مورد Ente شنیدی؟ (اختیاری)'; + + @override + String get hearUsExplanation => + 'ما نصب برنامه را ردیابی نمی‌کنیم. اگر بگویید کجا ما را پیدا کردید، به ما کمک می‌کند!'; + + @override + String get signUpTerms => + 'با شرایط استفاده از خدمات و سیاست حفظ حریم خصوصی موافقت می‌کنم'; + + @override + String get termsOfServicesTitle => 'شرایط و ضوابط'; + + @override + String get privacyPolicyTitle => 'سیاست حفظ حریم خصوصی'; + + @override + String get ackPasswordLostWarning => + 'می‌دانم که اگر رمز عبور خود را گم کنم، از آنجایی که اطلاعات من رمزگذاری سرتاسری شده است، ممکن است اطلاعاتم را از دست بدهم.'; + + @override + String get encryption => 'رمزگذاری'; + + @override + String get logInLabel => 'ورود'; + + @override + String get welcomeBack => 'خوش آمدید!'; + + @override + String get loginTerms => + 'با کلیک روی ورود، با شرایط استفاده از خدمات و سیاست حفظ حریم خصوصی موافقت می‌کنم'; + + @override + String get noInternetConnection => 'نبود اتصال اینترنت'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'لطفا اتصال اینترنت خود را بررسی کنید و دوباره امتحان کنید.'; + + @override + String get verificationFailedPleaseTryAgain => + 'تایید ناموفق بود، لطفا مجددا تلاش کنید'; + + @override + String get recreatePasswordTitle => 'بازتولید رمز عبور'; + + @override + String get recreatePasswordBody => + 'دستگاه فعلی جهت تایید رمز عبور شما به اندازه کافی قدرتمند نیست، اما ما میتوانیم آن را به گونه ای بازتولید کنیم که با همه دستگاه‌ها کار کند.\n\nلطفا با استفاده از کلید بازیابی خود وارد شوید و رمز عبور خود را دوباره ایجاد کنید (در صورت تمایل می‌توانید دوباره از همان رمز عبور استفاده کنید).'; + + @override + String get useRecoveryKey => 'از کلید بازیابی استفاده کنید'; + + @override + String get forgotPassword => 'فراموشی رمز عبور'; + + @override + String get changeEmail => 'تغییر ایمیل'; + + @override + String get verifyEmail => 'تایید ایمیل'; + + @override + String weHaveSendEmailTo(String email) { + return 'ما یک ایمیل به $email ارسال کرده‌ایم'; + } + + @override + String get toResetVerifyEmail => + 'برای تنظیم مجدد رمز عبور، لطفا ابتدا ایمیل خود را تایید کنید.'; + + @override + String get checkInboxAndSpamFolder => + 'لطفا صندوق ورودی (و هرزنامه) خود را برای تایید کامل بررسی کنید'; + + @override + String get tapToEnterCode => 'برای وارد کردن کد ضربه بزنید'; + + @override + String get sendEmail => 'ارسال ایمیل'; + + @override + String get resendEmail => 'ارسال مجدد ایمیل'; + + @override + String get passKeyPendingVerification => 'تأییدیه هنوز در انتظار است'; + + @override + String get loginSessionExpired => 'نشست منقضی شده است'; + + @override + String get loginSessionExpiredDetails => + 'نشست شما منقضی شده. لطفا دوباره وارد شوید.'; + + @override + String get passkeyAuthTitle => 'تایید کردن پسکی'; + + @override + String get waitingForVerification => 'درانتظار تأییدیه...'; + + @override + String get tryAgain => 'دوباره امتحان کنید'; + + @override + String get checkStatus => 'بررسی وضعیت'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'بازیابی حساب کاربری'; + + @override + String get setPasswordTitle => 'تنظیم پسورد'; + + @override + String get changePasswordTitle => 'تغییر رمز عبور'; + + @override + String get resetPasswordTitle => 'بازنشانی رمز عبور'; + + @override + String get encryptionKeys => 'کلیدهای رمزنگاری'; + + @override + String get enterPasswordToEncrypt => + 'رمز عبوری را وارد کنید که بتوانیم از آن برای رمزگذاری اطلاعات شما استفاده کنیم'; + + @override + String get enterNewPasswordToEncrypt => + 'رمز عبور جدیدی را وارد کنید که بتوانیم از آن برای رمزگذاری اطلاعات شما استفاده کنیم'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'چگونه کار می‌کند'; + + @override + String get generatingEncryptionKeys => 'در حال تولید کلیدهای رمزگذاری...'; + + @override + String get passwordChangedSuccessfully => 'رمز عبور با موفقیت تغییر کرد'; + + @override + String get signOutFromOtherDevices => 'از دستگاه های دیگر خارج شوید'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'از دستگاه های دیگر خارج شوید'; + + @override + String get doNotSignOut => 'خارج نشوید'; + + @override + String get generatingEncryptionKeysTitle => + 'در حال تولید کلید‌های رمزگذاری...'; + + @override + String get continueLabel => 'ادامه'; + + @override + String get insecureDevice => 'دستگاه ناامن'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'با عرض پوزش، ما نمی‌توانیم کلیدهای امن را در این دستگاه تولید کنیم.\n\nلطفا از دستگاه دیگری ثبت نام کنید.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'کلید بازیابی به حافظه موقت کپی شد'; + + @override + String get recoveryKey => 'کلید بازیابی'; + + @override + String get recoveryKeyOnForgotPassword => + 'اگر رمز عبور خود را فراموش کرده‌اید، این کد تنها راهی است که با آن می‌توانید اطلاعات خود را بازیابی کنید.'; + + @override + String get recoveryKeySaveDescription => + 'ما این کلید را ذخیره نمی‌کنیم، لطفا این کلید ۲۴ کلمه‌ای را در مکانی امن ذخیره کنید.'; + + @override + String get doThisLater => 'بعداً انجام شود'; + + @override + String get saveKey => 'ذخیره کلید'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'کلید بازیابی ندارید؟'; + + @override + String get twoFactorAuthTitle => 'احراز هویت دو مرحله‌ای'; + + @override + String get enterCodeHint => + 'کد تایید ۶ رقمی را از برنامه\nاحراز هویت خود وارد کنید'; + + @override + String get lostDeviceTitle => 'دستگاه را گم کرده‌اید؟'; + + @override + String get enterRecoveryKeyHint => 'کلید بازیابی خود را وارد کنید'; + + @override + String get recover => 'بازیابی'; + + @override + String get loggingOut => 'در حال خروج از سیستم...'; + + @override + String get immediately => 'فوری'; + + @override + String get appLock => 'قفل برنامه'; + + @override + String get autoLock => 'قفل خودکار'; + + @override + String get noSystemLockFound => 'هیج قبل سیستمی پیدا نشد'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'قفل دستگاه'; + + @override + String get pinLock => 'پین قفل'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'پنهان کردن محتوا'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'برای باز کردن قفل ضربه بزنید'; + + @override + String get areYouSureYouWantToLogout => 'آیا مطمئنید که می‌خواهید خارج شوید؟'; + + @override + String get yesLogout => 'بله، خروج'; + + @override + String get authToViewSecrets => 'لطفا جهت دیدن راز های خود احراز هویت کنید'; + + @override + String get next => 'بعدی'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'پین را وارد کنید'; + + @override + String get setNewPin => 'پین جدید انتخاب کنید'; + + @override + String get confirm => 'تایید'; + + @override + String get reEnterPassword => 'رمز عبور را مجدداً وارد کنید'; + + @override + String get reEnterPin => 'پین را مجدداً وارد کنید'; + + @override + String get androidBiometricHint => 'تایید هویت'; + + @override + String get androidBiometricNotRecognized => 'شناخته نشد. دوباره امتحان کنید.'; + + @override + String get androidBiometricSuccess => 'موفقیت'; + + @override + String get androidCancelButton => 'لغو'; + + @override + String get androidSignInTitle => 'احراز هویت لازم است'; + + @override + String get androidBiometricRequiredTitle => 'بیومتریک لازم است'; + + @override + String get androidDeviceCredentialsRequiredTitle => 'اعتبار دستگاه لازم است'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'اعتبار دستگاه لازم است'; + + @override + String get goToSettings => 'به تنظیمات بروید'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'تأیید'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => 'این ایمیل درحال استفاده است'; + + @override + String emailChangedTo(String newEmail) { + return 'ایمیل عوض شد به $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'احراز هویت ناموفق بود، لطفا دوباره تلاش کنید'; + + @override + String get authenticationSuccessful => 'احراز هویت موفق آمیز!'; + + @override + String get sessionExpired => 'نشست منقضی شده است'; + + @override + String get incorrectRecoveryKey => 'کلید بازیابی درست نیست'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'کلید بازیابی که وارد کردید درست نیست'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'احراز هویت دو مرحله با موفقیت بازنشانی شد'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => 'کد تایید شما باطل شد'; + + @override + String get incorrectCode => 'کد اشتباه'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'معظرت میخوام، کدی که شما وارد کردید اشتباه است'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_fi.dart b/mobile/packages/strings/lib/l10n/strings_localizations_fi.dart new file mode 100644 index 0000000000..3081b85c8f --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_fi.dart @@ -0,0 +1,627 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Finnish (`fi`). +class StringsLocalizationsFi extends StringsLocalizations { + StringsLocalizationsFi([String locale = 'fi']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'Virhe'; + + @override + String get ok => 'Selvä'; + + @override + String get faq => 'Usein kysyttyä'; + + @override + String get contactSupport => 'Ota yhteyttä käyttötukeen'; + + @override + String get emailYourLogs => 'Email your logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Please send the logs to \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copy email address'; + + @override + String get exportLogs => 'Export logs'; + + @override + String get cancel => 'Keskeytä'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Ilmoita virhetoiminnosta'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'Tallenna'; + + @override + String get send => 'Lähetä'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Sähköposti'; + + @override + String get verify => 'Vahvista'; + + @override + String get invalidEmailTitle => 'Epäkelpo sähköpostiosoite'; + + @override + String get invalidEmailMessage => 'Syötä kelpoisa sähköpostiosoite.'; + + @override + String get pleaseWait => 'Odota hetki...'; + + @override + String get verifyPassword => 'Vahvista salasana'; + + @override + String get incorrectPasswordTitle => 'Salasana on väärin'; + + @override + String get pleaseTryAgain => 'Yritä uudestaan'; + + @override + String get enterPassword => 'Syötä salasana'; + + @override + String get enterYourPasswordHint => 'Syötä salasanasi'; + + @override + String get activeSessions => 'Active sessions'; + + @override + String get oops => 'Hupsista'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Something went wrong, please try again'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'Terminate session?'; + + @override + String get terminate => 'Terminate'; + + @override + String get thisDevice => 'This device'; + + @override + String get createAccount => 'Luo tili'; + + @override + String get weakStrength => 'Heikko salasana'; + + @override + String get moderateStrength => 'Kohtalainen salasana'; + + @override + String get strongStrength => 'Vahva salasana'; + + @override + String get deleteAccount => 'Poista tili'; + + @override + String get deleteAccountQuery => + 'Olemme pahoillamme että lähdet keskuudestamme. Kohtasitko käytössä jonkin ongelman?'; + + @override + String get yesSendFeedbackAction => 'Kyllä, lähetä palautetta'; + + @override + String get noDeleteAccountAction => 'En, poista tili'; + + @override + String get initiateAccountDeleteTitle => + 'Ole hyvä ja tee todennus käynnistääksesi tilisi poistoprosessin'; + + @override + String get confirmAccountDeleteTitle => 'Confirm account deletion'; + + @override + String get 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.'; + + @override + String get delete => 'Poista'; + + @override + String get createNewAccount => 'Luo uusi tili'; + + @override + String get password => 'Salasana'; + + @override + String get confirmPassword => 'Vahvista salasana'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Password strength: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'We don\'t track app installs. It\'d help if you told us where you found us!'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Terms'; + + @override + String get privacyPolicyTitle => 'Privacy Policy'; + + @override + String get ackPasswordLostWarning => + 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'; + + @override + String get encryption => 'Salaus'; + + @override + String get logInLabel => 'Kirjaudu sisään'; + + @override + String get welcomeBack => 'Tervetuloa takaisin!'; + + @override + String get loginTerms => + 'By clicking log in, I agree to the terms of service and privacy policy'; + + @override + String get noInternetConnection => 'No internet connection'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Please check your internet connection and try again.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Recreate password'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'Käytä palautusavainta'; + + @override + String get forgotPassword => 'Olen unohtanut salasanani'; + + @override + String get changeEmail => 'vaihda sähköpostiosoitteesi'; + + @override + String get verifyEmail => 'Vahvista sähköpostiosoite'; + + @override + String weHaveSendEmailTo(String email) { + return 'We have sent a mail to $email'; + } + + @override + String get toResetVerifyEmail => + 'To reset your password, please verify your email first.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Tap to enter code'; + + @override + String get sendEmail => 'Lähetä sähköpostia'; + + @override + String get resendEmail => 'Resend email'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Session expired'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Avainkoodin vahvistus'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Try again'; + + @override + String get checkStatus => 'Check status'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Palauta tilisi'; + + @override + String get setPasswordTitle => 'Luo salasana'; + + @override + String get changePasswordTitle => 'Vaihda salasana'; + + @override + String get resetPasswordTitle => 'Nollaa salasana'; + + @override + String get encryptionKeys => 'Salausavaimet'; + + @override + String get enterPasswordToEncrypt => + 'Enter a password we can use to encrypt your data'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'How it works'; + + @override + String get generatingEncryptionKeys => 'Luodaan salausavaimia...'; + + @override + String get passwordChangedSuccessfully => 'Salasana vaihdettu onnistuneesti'; + + @override + String get signOutFromOtherDevices => 'Sign out from other devices'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'Sign out other devices'; + + @override + String get doNotSignOut => 'Do not sign out'; + + @override + String get generatingEncryptionKeysTitle => 'Luodaan salausavaimia...'; + + @override + String get continueLabel => 'Jatka'; + + @override + String get insecureDevice => 'Insecure device'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Palautusavain kopioitu leikepöydälle'; + + @override + String get recoveryKey => 'Palautusavain'; + + @override + String get recoveryKeyOnForgotPassword => + 'Jos unohdat salasanasi, ainoa tapa palauttaa tietosi on tällä avaimella.'; + + @override + String get recoveryKeySaveDescription => + 'Emme tallenna tätä avainta, ole hyvä ja tallenna tämä 24 sanan avain turvalliseen paikkaan.'; + + @override + String get doThisLater => 'Tee tämä myöhemmin'; + + @override + String get saveKey => 'Tallenna avain'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'Ei palautusavainta?'; + + @override + String get twoFactorAuthTitle => 'Kaksivaiheinen vahvistus'; + + @override + String get enterCodeHint => + 'Syötä 6-merkkinen koodi varmennussovelluksestasi'; + + @override + String get lostDeviceTitle => 'Kadonnut laite?'; + + @override + String get enterRecoveryKeyHint => 'Syötä palautusavaimesi'; + + @override + String get recover => 'Palauta'; + + @override + String get loggingOut => 'Kirjaudutaan ulos...'; + + @override + String get immediately => 'Immediately'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Hide content'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Tap to unlock'; + + @override + String get areYouSureYouWantToLogout => 'Are you sure you want to logout?'; + + @override + String get yesLogout => 'Kyllä, kirjaudu ulos'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'Enter PIN'; + + @override + String get setNewPin => 'Set new PIN'; + + @override + String get confirm => 'Confirm'; + + @override + String get reEnterPassword => 'Re-enter password'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verify identity'; + + @override + String get androidBiometricNotRecognized => 'Not recognized. Try again.'; + + @override + String get androidBiometricSuccess => 'Kirjautuminen onnistui'; + + @override + String get androidCancelButton => 'Peruuta'; + + @override + String get androidSignInTitle => 'Authentication required'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Mene asetuksiin'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => 'This email is already in use'; + + @override + String emailChangedTo(String newEmail) { + return 'Email changed to $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'Istunto on vanheutunut'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Incorrect code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart b/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart new file mode 100644 index 0000000000..3375cff00a --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_fr.dart @@ -0,0 +1,638 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for French (`fr`). +class StringsLocalizationsFr extends StringsLocalizations { + StringsLocalizationsFr([String locale = 'fr']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Impossible de se connecter à Ente, veuillez vérifier vos paramètres réseau et contacter le support si l\'erreur persiste.'; + + @override + String get networkConnectionRefusedErr => + 'Impossible de se connecter à Ente, veuillez réessayer après un certain temps. Si l\'erreur persiste, veuillez contacter le support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Il semble qu\'une erreur s\'est produite. Veuillez réessayer après un certain temps. Si l\'erreur persiste, veuillez contacter notre équipe d\'assistance.'; + + @override + String get error => 'Erreur'; + + @override + String get ok => 'Ok'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'Contacter le support'; + + @override + String get emailYourLogs => 'Envoyez vos logs par e-mail'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Envoyez les logs à $toEmail'; + } + + @override + String get copyEmailAddress => 'Copier l’adresse e-mail'; + + @override + String get exportLogs => 'Exporter les journaux'; + + @override + String get cancel => 'Annuler'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Signaler un bug'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connecté à $endpoint'; + } + + @override + String get save => 'Sauvegarder'; + + @override + String get send => 'Envoyer'; + + @override + String get saveOrSendDescription => + 'Voulez-vous enregistrer ceci sur votre stockage (dossier Téléchargements par défaut) ou l\'envoyer à d\'autres applications ?'; + + @override + String get saveOnlyDescription => + 'Voulez-vous enregistrer ceci sur votre stockage (dossier Téléchargements par défaut) ?'; + + @override + String get enterNewEmailHint => 'Saisissez votre nouvelle adresse email'; + + @override + String get email => 'E-mail'; + + @override + String get verify => 'Vérifier'; + + @override + String get invalidEmailTitle => 'Adresse e-mail invalide'; + + @override + String get invalidEmailMessage => + 'Veuillez saisir une adresse e-mail valide.'; + + @override + String get pleaseWait => 'Veuillez patienter...'; + + @override + String get verifyPassword => 'Vérifier le mot de passe'; + + @override + String get incorrectPasswordTitle => 'Mot de passe incorrect'; + + @override + String get pleaseTryAgain => 'Veuillez réessayer'; + + @override + String get enterPassword => 'Saisissez le mot de passe'; + + @override + String get enterYourPasswordHint => 'Entrez votre mot de passe'; + + @override + String get activeSessions => 'Sessions actives'; + + @override + String get oops => 'Oups'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Quelque chose s\'est mal passé, veuillez recommencer'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Cela vous déconnectera de cet appareil !'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Cela vous déconnectera de l\'appareil suivant :'; + + @override + String get terminateSession => 'Quitter la session ?'; + + @override + String get terminate => 'Quitter'; + + @override + String get thisDevice => 'Cet appareil'; + + @override + String get createAccount => 'Créer un compte'; + + @override + String get weakStrength => 'Faible'; + + @override + String get moderateStrength => 'Modéré'; + + @override + String get strongStrength => 'Fort'; + + @override + String get deleteAccount => 'Supprimer le compte'; + + @override + String get deleteAccountQuery => + 'Nous sommes désolés de vous voir partir. Rencontrez-vous un problème ?'; + + @override + String get yesSendFeedbackAction => 'Oui, envoyer un commentaire'; + + @override + String get noDeleteAccountAction => 'Non, supprimer le compte'; + + @override + String get initiateAccountDeleteTitle => + 'Veuillez vous authentifier pour débuter la suppression du compte'; + + @override + String get confirmAccountDeleteTitle => 'Confirmer la suppression du compte'; + + @override + String get confirmAccountDeleteMessage => + 'Ce compte est lié à d\'autres applications ente, si vous en utilisez une.\n\nVos données téléchargées, dans toutes les applications ente, seront planifiées pour suppression, et votre compte sera définitivement supprimé.'; + + @override + String get delete => 'Supprimer'; + + @override + String get createNewAccount => 'Créer un nouveau compte'; + + @override + String get password => 'Mot de passe'; + + @override + String get confirmPassword => 'Confirmer le mot de passe'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Force du mot de passe : $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => + 'Comment avez-vous entendu parler de Ente? (facultatif)'; + + @override + String get hearUsExplanation => + 'Nous ne suivons pas les installations d\'applications. Il serait utile que vous nous disiez comment vous nous avez trouvés !'; + + @override + String get signUpTerms => + 'J\'accepte les conditions d\'utilisation et la politique de confidentialité'; + + @override + String get termsOfServicesTitle => 'Conditions'; + + @override + String get privacyPolicyTitle => 'Politique de confidentialité'; + + @override + String get ackPasswordLostWarning => + 'Je comprends que si je perds mon mot de passe, je risque de perdre mes données puisque celles-ci sont chiffrées de bout en bout.'; + + @override + String get encryption => 'Chiffrement'; + + @override + String get logInLabel => 'Connexion'; + + @override + String get welcomeBack => 'Bon retour parmi nous !'; + + @override + String get loginTerms => + 'En cliquant sur \"Connexion\", j\'accepte les conditions d\'utilisation et la politique de confidentialité'; + + @override + String get noInternetConnection => 'Aucune connexion internet'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Veuillez vérifier votre connexion internet puis réessayer.'; + + @override + String get verificationFailedPleaseTryAgain => + 'La vérification a échouée, veuillez réessayer'; + + @override + String get recreatePasswordTitle => 'Recréer le mot de passe'; + + @override + String get recreatePasswordBody => + 'L\'appareil actuel n\'est pas assez puissant pour vérifier votre mot de passe, donc nous avons besoin de le régénérer une fois d\'une manière qu\'il fonctionne avec tous les périphériques.\n\nVeuillez vous connecter en utilisant votre clé de récupération et régénérer votre mot de passe (vous pouvez utiliser le même si vous le souhaitez).'; + + @override + String get useRecoveryKey => 'Utiliser la clé de récupération'; + + @override + String get forgotPassword => 'Mot de passe oublié'; + + @override + String get changeEmail => 'Modifier l\'e-mail'; + + @override + String get verifyEmail => 'Vérifier l\'e-mail'; + + @override + String weHaveSendEmailTo(String email) { + return 'Nous avons envoyé un mail à $email'; + } + + @override + String get toResetVerifyEmail => + 'Pour réinitialiser votre mot de passe, veuillez d\'abord vérifier votre e-mail.'; + + @override + String get checkInboxAndSpamFolder => + 'Veuillez consulter votre boîte de courriels (et les spam) pour compléter la vérification'; + + @override + String get tapToEnterCode => 'Appuyez pour entrer un code'; + + @override + String get sendEmail => 'Envoyer un e-mail'; + + @override + String get resendEmail => 'Renvoyer le courriel'; + + @override + String get passKeyPendingVerification => + 'La vérification est toujours en attente'; + + @override + String get loginSessionExpired => 'Session expirée'; + + @override + String get loginSessionExpiredDetails => + 'Votre session a expiré. Veuillez vous reconnecter.'; + + @override + String get passkeyAuthTitle => 'Vérification du code d\'accès'; + + @override + String get waitingForVerification => 'En attente de vérification...'; + + @override + String get tryAgain => 'Réessayer'; + + @override + String get checkStatus => 'Vérifier le statut'; + + @override + String get loginWithTOTP => 'Se connecter avec un code TOTP'; + + @override + String get recoverAccount => 'Récupérer un compte'; + + @override + String get setPasswordTitle => 'Définir le mot de passe'; + + @override + String get changePasswordTitle => 'Modifier le mot de passe'; + + @override + String get resetPasswordTitle => 'Réinitialiser le mot de passe'; + + @override + String get encryptionKeys => 'Clés de chiffrement'; + + @override + String get enterPasswordToEncrypt => + 'Entrez un mot de passe que nous pouvons utiliser pour chiffrer vos données'; + + @override + String get enterNewPasswordToEncrypt => + 'Entrez un nouveau mot de passe que nous pouvons utiliser pour chiffrer vos données'; + + @override + String get passwordWarning => + 'Nous ne stockons pas ce mot de passe. Si vous l\'oubliez, nous ne pourrons pas déchiffrer vos données'; + + @override + String get howItWorks => 'Comment ça fonctionne'; + + @override + String get generatingEncryptionKeys => + 'Génération des clés de chiffrement...'; + + @override + String get passwordChangedSuccessfully => + 'Le mot de passe a été modifié avec succès'; + + @override + String get signOutFromOtherDevices => 'Déconnexion des autres appareils'; + + @override + String get signOutOtherBody => + 'Si vous pensez que quelqu\'un connaît peut-être votre mot de passe, vous pouvez forcer tous les autres appareils utilisant votre compte à se déconnecter.'; + + @override + String get signOutOtherDevices => 'Déconnecter les autres appareils'; + + @override + String get doNotSignOut => 'Ne pas se déconnecter'; + + @override + String get generatingEncryptionKeysTitle => + 'Génération des clés de chiffrement...'; + + @override + String get continueLabel => 'Continuer'; + + @override + String get insecureDevice => 'Appareil non sécurisé'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Désolé, nous n\'avons pas pu générer de clés sécurisées sur cet appareil.\n\nVeuillez vous inscrire depuis un autre appareil.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Clé de récupération copiée dans le presse-papiers'; + + @override + String get recoveryKey => 'Clé de récupération'; + + @override + String get recoveryKeyOnForgotPassword => + 'Si vous oubliez votre mot de passe, la seule façon de récupérer vos données sera grâce à cette clé.'; + + @override + String get recoveryKeySaveDescription => + 'Nous ne stockons pas cette clé, veuillez enregistrer cette clé de 24 mots dans un endroit sûr.'; + + @override + String get doThisLater => 'Plus tard'; + + @override + String get saveKey => 'Enregistrer la clé'; + + @override + String get recoveryKeySaved => + 'Clé de récupération enregistrée dans le dossier Téléchargements !'; + + @override + String get noRecoveryKeyTitle => 'Pas de clé de récupération ?'; + + @override + String get twoFactorAuthTitle => 'Authentification à deux facteurs'; + + @override + String get enterCodeHint => + 'Entrez le code à 6 chiffres de votre application d\'authentification'; + + @override + String get lostDeviceTitle => 'Appareil perdu ?'; + + @override + String get enterRecoveryKeyHint => 'Saisissez votre clé de récupération'; + + @override + String get recover => 'Restaurer'; + + @override + String get loggingOut => 'Deconnexion...'; + + @override + String get immediately => 'Immédiatement'; + + @override + String get appLock => 'Verrouillage d\'application'; + + @override + String get autoLock => 'Verrouillage automatique'; + + @override + String get noSystemLockFound => 'Aucun verrou système trouvé'; + + @override + String get deviceLockEnablePreSteps => + 'Pour activer l\'écran de verrouillage, veuillez configurer le code d\'accès de l\'appareil ou le verrouillage de l\'écran dans les paramètres de votre système.'; + + @override + String get appLockDescription => + 'Choisissez entre l\'écran de verrouillage par défaut de votre appareil et un écran de verrouillage par code PIN ou mot de passe personnalisé.'; + + @override + String get deviceLock => 'Verrouillage de l\'appareil'; + + @override + String get pinLock => 'Verrouillage par code PIN'; + + @override + String get autoLockFeatureDescription => + 'Délai après lequel l\'application se verrouille une fois qu\'elle a été mise en arrière-plan'; + + @override + String get hideContent => 'Masquer le contenu'; + + @override + String get hideContentDescriptionAndroid => + 'Masque le contenu de l\'application sur le menu et désactive les captures d\'écran'; + + @override + String get hideContentDescriptioniOS => + 'Masque le contenu de l\'application sur le menu'; + + @override + String get tooManyIncorrectAttempts => 'Trop de tentatives incorrectes'; + + @override + String get tapToUnlock => 'Appuyer pour déverrouiller'; + + @override + String get areYouSureYouWantToLogout => + 'Êtes-vous sûr de vouloir vous déconnecter ?'; + + @override + String get yesLogout => 'Oui, se déconnecter'; + + @override + String get authToViewSecrets => + 'Veuillez vous authentifier pour voir vos souvenirs'; + + @override + String get next => 'Suivant'; + + @override + String get setNewPassword => 'Définir un nouveau mot de passe'; + + @override + String get enterPin => 'Saisir le code PIN'; + + @override + String get setNewPin => 'Définir un nouveau code PIN'; + + @override + String get confirm => 'Confirmer'; + + @override + String get reEnterPassword => 'Ressaisir le mot de passe'; + + @override + String get reEnterPin => 'Ressaisir le code PIN'; + + @override + String get androidBiometricHint => 'Vérifier l’identité'; + + @override + String get androidBiometricNotRecognized => + 'Non reconnu. Veuillez réessayer.'; + + @override + String get androidBiometricSuccess => 'Parfait'; + + @override + String get androidCancelButton => 'Annuler'; + + @override + String get androidSignInTitle => 'Authentification requise'; + + @override + String get androidBiometricRequiredTitle => 'Empreinte digitale requise'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Identifiants de l\'appareil requis'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Identifiants de l\'appareil requis'; + + @override + String get goToSettings => 'Allez dans les paramètres'; + + @override + String get androidGoToSettingsDescription => + 'L\'authentification biométrique n\'est pas configurée sur votre appareil. Allez dans \'Paramètres > Sécurité\' pour l\'ajouter.'; + + @override + String get iOSLockOut => + 'L\'authentification biométrique est désactivée. Veuillez verrouiller et déverrouiller votre écran pour l\'activer.'; + + @override + String get iOSOkButton => 'Ok'; + + @override + String get emailAlreadyRegistered => 'E-mail déjà enregistré.'; + + @override + String get emailNotRegistered => 'E-mail non enregistré.'; + + @override + String get thisEmailIsAlreadyInUse => 'Cette adresse mail est déjà utilisé'; + + @override + String emailChangedTo(String newEmail) { + return 'L\'e-mail a été changé en $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'L\'authentification a échouée, veuillez réessayer'; + + @override + String get authenticationSuccessful => 'Authentification réussie!'; + + @override + String get sessionExpired => 'Session expirée'; + + @override + String get incorrectRecoveryKey => 'Clé de récupération non valide'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'La clé de récupération que vous avez entrée est incorrecte'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'L\'authentification à deux facteurs a été réinitialisée avec succès '; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Votre code de vérification a expiré'; + + @override + String get incorrectCode => 'Code non valide'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Le code que vous avez saisi est incorrect'; + + @override + String get developerSettings => 'Paramètres du développeur'; + + @override + String get serverEndpoint => 'Point de terminaison serveur'; + + @override + String get invalidEndpoint => 'Point de terminaison non valide'; + + @override + String get invalidEndpointMessage => + 'Désolé, le point de terminaison que vous avez entré n\'est pas valide. Veuillez en entrer un valide puis réessayez.'; + + @override + String get endpointUpdatedMessage => + 'Point de terminaison mis à jour avec succès'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_gu.dart b/mobile/packages/strings/lib/l10n/strings_localizations_gu.dart new file mode 100644 index 0000000000..caebb756ec --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_gu.dart @@ -0,0 +1,627 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Gujarati (`gu`). +class StringsLocalizationsGu extends StringsLocalizations { + StringsLocalizationsGu([String locale = 'gu']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'Error'; + + @override + String get ok => 'સારું'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'સહાયતા માટે સંપર્ક કરો'; + + @override + String get emailYourLogs => 'Email your logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Please send the logs to \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copy email address'; + + @override + String get exportLogs => 'Export logs'; + + @override + String get cancel => 'રદ કરો'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'બગની જાણ કરો'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'Save'; + + @override + String get send => 'Send'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'ઇમેઇલ'; + + @override + String get verify => 'Verify'; + + @override + String get invalidEmailTitle => 'Invalid email address'; + + @override + String get invalidEmailMessage => 'Please enter a valid email address.'; + + @override + String get pleaseWait => 'કૃપા કરીને રાહ જુવો...'; + + @override + String get verifyPassword => 'પાસવર્ડ ચકાસો'; + + @override + String get incorrectPasswordTitle => 'ખોટો પાસવર્ડ'; + + @override + String get pleaseTryAgain => 'Please try again'; + + @override + String get enterPassword => 'પાસવર્ડ દાખલ કરો'; + + @override + String get enterYourPasswordHint => 'Enter your password'; + + @override + String get activeSessions => 'Active sessions'; + + @override + String get oops => 'Oops'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Something went wrong, please try again'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'Terminate session?'; + + @override + String get terminate => 'Terminate'; + + @override + String get thisDevice => 'This device'; + + @override + String get createAccount => 'Create account'; + + @override + String get weakStrength => 'Weak'; + + @override + String get moderateStrength => 'Moderate'; + + @override + String get strongStrength => 'Strong'; + + @override + String get deleteAccount => 'Delete account'; + + @override + String get deleteAccountQuery => + 'We\'ll be sorry to see you go. Are you facing some issue?'; + + @override + String get yesSendFeedbackAction => 'Yes, send feedback'; + + @override + String get noDeleteAccountAction => 'No, delete account'; + + @override + String get initiateAccountDeleteTitle => + 'Please authenticate to initiate account deletion'; + + @override + String get confirmAccountDeleteTitle => 'Confirm account deletion'; + + @override + String get 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.'; + + @override + String get delete => 'કાઢી નાખો'; + + @override + String get createNewAccount => 'Create new account'; + + @override + String get password => 'Password'; + + @override + String get confirmPassword => 'Confirm password'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Password strength: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'We don\'t track app installs. It\'d help if you told us where you found us!'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Terms'; + + @override + String get privacyPolicyTitle => 'Privacy Policy'; + + @override + String get ackPasswordLostWarning => + 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'; + + @override + String get encryption => 'Encryption'; + + @override + String get logInLabel => 'Log in'; + + @override + String get welcomeBack => 'ફરી તમારુ સ્વાગત છે!'; + + @override + String get loginTerms => + 'By clicking log in, I agree to the terms of service and privacy policy'; + + @override + String get noInternetConnection => 'No internet connection'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Please check your internet connection and try again.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Recreate password'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'પુનઃપ્રાપ્તિ કીનો ઉપયોગ કરો'; + + @override + String get forgotPassword => 'Forgot password'; + + @override + String get changeEmail => 'ઈ - મેઈલ બદલો'; + + @override + String get verifyEmail => 'Verify email'; + + @override + String weHaveSendEmailTo(String email) { + return 'We have sent a mail to $email'; + } + + @override + String get toResetVerifyEmail => + 'To reset your password, please verify your email first.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Tap to enter code'; + + @override + String get sendEmail => 'Send email'; + + @override + String get resendEmail => 'Resend email'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Session expired'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Try again'; + + @override + String get checkStatus => 'Check status'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Recover account'; + + @override + String get setPasswordTitle => 'Set password'; + + @override + String get changePasswordTitle => 'Change password'; + + @override + String get resetPasswordTitle => 'Reset password'; + + @override + String get encryptionKeys => 'Encryption keys'; + + @override + String get enterPasswordToEncrypt => + 'Enter a password we can use to encrypt your data'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'How it works'; + + @override + String get generatingEncryptionKeys => 'Generating encryption keys...'; + + @override + String get passwordChangedSuccessfully => 'Password changed successfully'; + + @override + String get signOutFromOtherDevices => 'Sign out from other devices'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'Sign out other devices'; + + @override + String get doNotSignOut => 'Do not sign out'; + + @override + String get generatingEncryptionKeysTitle => + 'એન્ક્રિપ્શન ચાવીઓ જનરેટ કરી રહ્યાં છીએ...'; + + @override + String get continueLabel => 'Continue'; + + @override + String get insecureDevice => 'Insecure device'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => 'Recovery key copied to clipboard'; + + @override + String get recoveryKey => 'પુનઃપ્રાપ્તિ ચાવી'; + + @override + String get recoveryKeyOnForgotPassword => + 'If you forget your password, the only way you can recover your data is with this key.'; + + @override + String get recoveryKeySaveDescription => + 'We don\'t store this key, please save this 24 word key in a safe place.'; + + @override + String get doThisLater => 'Do this later'; + + @override + String get saveKey => 'Save key'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'No recovery key?'; + + @override + String get twoFactorAuthTitle => 'Two-factor authentication'; + + @override + String get enterCodeHint => + 'Enter the 6-digit code from\nyour authenticator app'; + + @override + String get lostDeviceTitle => 'Lost device?'; + + @override + String get enterRecoveryKeyHint => 'Enter your recovery key'; + + @override + String get recover => 'Recover'; + + @override + String get loggingOut => 'લૉગ આઉટ થઈ રહ્યું છે...'; + + @override + String get immediately => 'Immediately'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Hide content'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Tap to unlock'; + + @override + String get areYouSureYouWantToLogout => 'Are you sure you want to logout?'; + + @override + String get yesLogout => 'Yes, logout'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'Enter PIN'; + + @override + String get setNewPin => 'Set new PIN'; + + @override + String get confirm => 'Confirm'; + + @override + String get reEnterPassword => 'Re-enter password'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verify identity'; + + @override + String get androidBiometricNotRecognized => 'Not recognized. Try again.'; + + @override + String get androidBiometricSuccess => 'સફળતા'; + + @override + String get androidCancelButton => 'રદ કરો'; + + @override + String get androidSignInTitle => 'પ્રમાણીકરણ જરૂરી છે'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'સેટિંગ માં જાઓ'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'બરાબર'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => 'This email is already in use'; + + @override + String emailChangedTo(String newEmail) { + return 'Email changed to $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'સત્ર સમાપ્ત થયુ'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Incorrect code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_he.dart b/mobile/packages/strings/lib/l10n/strings_localizations_he.dart new file mode 100644 index 0000000000..c1bc4531fb --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_he.dart @@ -0,0 +1,621 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Hebrew (`he`). +class StringsLocalizationsHe extends StringsLocalizations { + StringsLocalizationsHe([String locale = 'he']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'נראה שמשהו לא פעל כשורה. אנא נסה שוב אחרי כמה זמן. אם הבעיה ממשיכה, אנא צור קשר עם צוות התמיכה שלנו.'; + + @override + String get error => 'שגיאה'; + + @override + String get ok => 'אוקיי'; + + @override + String get faq => 'שאלות נפוצות'; + + @override + String get contactSupport => 'צור קשר עם התמיכה'; + + @override + String get emailYourLogs => 'שלח באימייל את הלוגים שלך'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'אנא שלחו את הלוגים האלו ל-$toEmail'; + } + + @override + String get copyEmailAddress => 'העתק כתובת דוא\"ל'; + + @override + String get exportLogs => 'ייצוא לוגים'; + + @override + String get cancel => 'בטל'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'דווח על באג'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'שמור'; + + @override + String get send => 'שלח'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'דוא\"ל'; + + @override + String get verify => 'אמת'; + + @override + String get invalidEmailTitle => 'כתובת דוא״ל לא תקינה'; + + @override + String get invalidEmailMessage => 'אנא הכנס כתובת דוא\"ל תקינה.'; + + @override + String get pleaseWait => 'אנא המתן...'; + + @override + String get verifyPassword => 'אמת סיסמא'; + + @override + String get incorrectPasswordTitle => 'סיסמא לא נכונה'; + + @override + String get pleaseTryAgain => 'אנא נסה שנית'; + + @override + String get enterPassword => 'הזן את הסיסמה'; + + @override + String get enterYourPasswordHint => 'הכנס סיסמא'; + + @override + String get activeSessions => 'חיבורים פעילים'; + + @override + String get oops => 'אופס'; + + @override + String get somethingWentWrongPleaseTryAgain => 'משהו השתבש, אנא נסה שנית'; + + @override + String get thisWillLogYouOutOfThisDevice => 'זה ינתק אותך במכשיר זה!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'זה ינתק אותך מהמכשיר הבא:'; + + @override + String get terminateSession => 'סיים חיבור?'; + + @override + String get terminate => 'סיים'; + + @override + String get thisDevice => 'מכשיר זה'; + + @override + String get createAccount => 'צור חשבון'; + + @override + String get weakStrength => 'חלש'; + + @override + String get moderateStrength => 'מתון'; + + @override + String get strongStrength => 'חזק'; + + @override + String get deleteAccount => 'מחק חשבון'; + + @override + String get deleteAccountQuery => + 'אנו מצטערים שאתה עוזב. האם יש בעיות שאתה חווה?'; + + @override + String get yesSendFeedbackAction => 'כן, שלח משוב'; + + @override + String get noDeleteAccountAction => 'לא, מחק את החשבון'; + + @override + String get initiateAccountDeleteTitle => + 'אנא אמת על מנת להתחיל את מחיקת החשבון שלך'; + + @override + String get confirmAccountDeleteTitle => 'אישור מחיקת חשבון'; + + @override + String get 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.'; + + @override + String get delete => 'למחוק'; + + @override + String get createNewAccount => 'צור חשבון חדש'; + + @override + String get password => 'סיסמא'; + + @override + String get confirmPassword => 'אמת סיסמא'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'חוזק הסיסמא: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'We don\'t track app installs. It\'d help if you told us where you found us!'; + + @override + String get signUpTerms => + 'אני מסכים לתנאי שירות ולמדיניות הפרטיות'; + + @override + String get termsOfServicesTitle => 'תנאים'; + + @override + String get privacyPolicyTitle => 'מדיניות פרטיות'; + + @override + String get ackPasswordLostWarning => + 'אני מבין שאם אאבד את הסיסמא, אני עלול לאבד את המידע שלי מכיוון שהמידע שלי מוצפן מקצה אל קצה.'; + + @override + String get encryption => 'הצפנה'; + + @override + String get logInLabel => 'התחבר'; + + @override + String get welcomeBack => 'ברוך שובך!'; + + @override + String get loginTerms => + 'על ידי לחיצה על התחברות, אני מסכים לתנאי שירות ולמדיניות הפרטיות'; + + @override + String get noInternetConnection => 'אין חיבור לאינטרנט'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'אנא בדוק את חיבור האינטרנט שלך ונסה שוב.'; + + @override + String get verificationFailedPleaseTryAgain => 'אימות נכשל, אנא נסה שנית'; + + @override + String get recreatePasswordTitle => 'צור סיסמא מחדש'; + + @override + String get recreatePasswordBody => + 'המכשיר הנוכחי אינו חזק מספיק כדי לאמת את הסיסמא שלך, אבל אנחנו יכולים ליצור מחדש בצורה שתעבוד עם כל המכשירים.\n\nאנא התחבר בעזרת המפתח שחזור שלך וצור מחדש את הסיסמא שלך (אתה יכול להשתמש באותה אחת אם אתה רוצה).'; + + @override + String get useRecoveryKey => 'השתמש במפתח שחזור'; + + @override + String get forgotPassword => 'שכחתי סיסמה'; + + @override + String get changeEmail => 'שנה דוא\"ל'; + + @override + String get verifyEmail => 'אימות דוא\"ל'; + + @override + String weHaveSendEmailTo(String email) { + return 'שלחנו דוא\"ל ל$email'; + } + + @override + String get toResetVerifyEmail => + 'כדי לאפס את הסיסמא שלך, אנא אמת את האימייל שלך קודם.'; + + @override + String get checkInboxAndSpamFolder => + 'אנא בדוק את תיבת הדואר שלך (והספאם) כדי להשלים את האימות'; + + @override + String get tapToEnterCode => 'הקש כדי להזין את הקוד'; + + @override + String get sendEmail => 'שלח אימייל'; + + @override + String get resendEmail => 'שלח דוא\"ל מחדש'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Session expired'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'נסה שוב'; + + @override + String get checkStatus => 'בדוק סטטוס'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'שחזר חשבון'; + + @override + String get setPasswordTitle => 'הגדר סיסמא'; + + @override + String get changePasswordTitle => 'שנה סיסמה'; + + @override + String get resetPasswordTitle => 'איפוס סיסמה'; + + @override + String get encryptionKeys => 'מפתחות ההצפנה'; + + @override + String get enterPasswordToEncrypt => + 'הזן סיסמא כדי שנוכל להצפין את המידע שלך'; + + @override + String get enterNewPasswordToEncrypt => + 'הכנס סיסמא חדשה כדי שנוכל להצפין את המידע שלך'; + + @override + String get passwordWarning => + 'אנחנו לא שומרים את הסיסמא הזו, לכן אם אתה שוכח אותה, אנחנו לא יכולים לפענח את המידע שלך'; + + @override + String get howItWorks => 'איך זה עובד'; + + @override + String get generatingEncryptionKeys => 'יוצר מפתחות הצפנה...'; + + @override + String get passwordChangedSuccessfully => 'הסיסמה הוחלפה בהצלחה'; + + @override + String get signOutFromOtherDevices => 'Sign out from other devices'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'Sign out other devices'; + + @override + String get doNotSignOut => 'Do not sign out'; + + @override + String get generatingEncryptionKeysTitle => 'יוצר מפתחות הצפנה...'; + + @override + String get continueLabel => 'המשך'; + + @override + String get insecureDevice => 'מכשיר בלתי מאובטח'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'אנחנו מצטערים, לא הצלחנו ליצור מפתחות מאובטחים על מכשיר זה.\n\nאנא הירשם ממכשיר אחר.'; + + @override + String get recoveryKeyCopiedToClipboard => 'מפתח השחזור הועתק ללוח'; + + @override + String get recoveryKey => 'מפתח שחזור'; + + @override + String get recoveryKeyOnForgotPassword => + 'אם אתה שוכח את הסיסמא שלך, הדרך היחידה שתוכל לשחזר את המידע שלך היא עם המפתח הזה.'; + + @override + String get recoveryKeySaveDescription => + 'אנחנו לא מאחסנים את המפתח הזה, אנא שמור את המפתח 24 מילים הזה במקום בטוח.'; + + @override + String get doThisLater => 'עשה זאת מאוחר יותר'; + + @override + String get saveKey => 'שמור מפתח'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'אין מפתח שחזור?'; + + @override + String get twoFactorAuthTitle => 'אימות דו-שלבי'; + + @override + String get enterCodeHint => + 'הכנס את הקוד בעל 6 ספרות מתוך\nאפליקציית האימות שלך'; + + @override + String get lostDeviceTitle => 'איבדת את המכשיר?'; + + @override + String get enterRecoveryKeyHint => 'הזן את מפתח השחזור שלך'; + + @override + String get recover => 'שחזר'; + + @override + String get loggingOut => 'מתנתק...'; + + @override + String get immediately => 'Immediately'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Hide content'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Tap to unlock'; + + @override + String get areYouSureYouWantToLogout => 'האם את/ה בטוח/ה שאת/ה רוצה לצאת?'; + + @override + String get yesLogout => 'כן, התנתק'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'Enter PIN'; + + @override + String get setNewPin => 'Set new PIN'; + + @override + String get confirm => 'אשר'; + + @override + String get reEnterPassword => 'Re-enter password'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verify identity'; + + @override + String get androidBiometricNotRecognized => 'Not recognized. Try again.'; + + @override + String get androidBiometricSuccess => 'Success'; + + @override + String get androidCancelButton => 'Cancel'; + + @override + String get androidSignInTitle => 'Authentication required'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Go to settings'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => + 'כתובת דואר אלקטרוני זאת כבר נמצאת בשימוש'; + + @override + String emailChangedTo(String newEmail) { + return 'אימייל שונה ל-$newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => 'אימות נכשל, אנא נסה שוב'; + + @override + String get authenticationSuccessful => 'אימות הצליח!'; + + @override + String get sessionExpired => 'זמן החיבור הסתיים'; + + @override + String get incorrectRecoveryKey => 'מפתח שחזור שגוי'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => 'המפתח שחזור שהזנת שגוי'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'אימות דו-שלבי אופס בהצלחה'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => 'קוד האימות שלך פג תוקף'; + + @override + String get incorrectCode => 'קוד שגוי'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'אנו מתנצלים, אבל הקוד שהזנת איננו נכון'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_hi.dart b/mobile/packages/strings/lib/l10n/strings_localizations_hi.dart new file mode 100644 index 0000000000..f45d282cb2 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_hi.dart @@ -0,0 +1,627 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Hindi (`hi`). +class StringsLocalizationsHi extends StringsLocalizations { + StringsLocalizationsHi([String locale = 'hi']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'Error'; + + @override + String get ok => 'ठीक है'; + + @override + String get faq => 'अक्सर किये गए सवाल'; + + @override + String get contactSupport => 'सपोर्ट टीम से संपर्क करें'; + + @override + String get emailYourLogs => 'Email your logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Please send the logs to \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copy email address'; + + @override + String get exportLogs => 'Export logs'; + + @override + String get cancel => 'रद्द करें'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'बग रिपोर्ट करें'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'Save'; + + @override + String get send => 'Send'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Email'; + + @override + String get verify => 'सत्यापित करें'; + + @override + String get invalidEmailTitle => 'Invalid email address'; + + @override + String get invalidEmailMessage => 'Please enter a valid email address.'; + + @override + String get pleaseWait => 'कृपया प्रतीक्षा करें...'; + + @override + String get verifyPassword => 'पासवर्ड सत्यापित करें'; + + @override + String get incorrectPasswordTitle => 'ग़लत पासवर्ड'; + + @override + String get pleaseTryAgain => 'कृपया पुन: प्रयास करें'; + + @override + String get enterPassword => 'Enter password'; + + @override + String get enterYourPasswordHint => 'अपना पासवर्ड दर्ज करें'; + + @override + String get activeSessions => 'Active sessions'; + + @override + String get oops => 'ओह'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Something went wrong, please try again'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'Terminate session?'; + + @override + String get terminate => 'Terminate'; + + @override + String get thisDevice => 'This device'; + + @override + String get createAccount => 'Create account'; + + @override + String get weakStrength => 'Weak'; + + @override + String get moderateStrength => 'Moderate'; + + @override + String get strongStrength => 'Strong'; + + @override + String get deleteAccount => 'Delete account'; + + @override + String get deleteAccountQuery => + 'We\'ll be sorry to see you go. Are you facing some issue?'; + + @override + String get yesSendFeedbackAction => 'Yes, send feedback'; + + @override + String get noDeleteAccountAction => 'No, delete account'; + + @override + String get initiateAccountDeleteTitle => + 'Please authenticate to initiate account deletion'; + + @override + String get confirmAccountDeleteTitle => 'Confirm account deletion'; + + @override + String get 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.'; + + @override + String get delete => 'हटाएं'; + + @override + String get createNewAccount => 'Create new account'; + + @override + String get password => 'Password'; + + @override + String get confirmPassword => 'Confirm password'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Password strength: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'We don\'t track app installs. It\'d help if you told us where you found us!'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Terms'; + + @override + String get privacyPolicyTitle => 'Privacy Policy'; + + @override + String get ackPasswordLostWarning => + 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'; + + @override + String get encryption => 'Encryption'; + + @override + String get logInLabel => 'Log in'; + + @override + String get welcomeBack => 'आपका पुनः स्वागत है!'; + + @override + String get loginTerms => + 'By clicking log in, I agree to the terms of service and privacy policy'; + + @override + String get noInternetConnection => 'No internet connection'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Please check your internet connection and try again.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Recreate password'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'रिकवरी कुंजी का उपयोग करें'; + + @override + String get forgotPassword => 'पासवर्ड भूल गए'; + + @override + String get changeEmail => 'ईमेल बदलें'; + + @override + String get verifyEmail => 'ईमेल सत्यापित करें'; + + @override + String weHaveSendEmailTo(String email) { + return 'We have sent a mail to $email'; + } + + @override + String get toResetVerifyEmail => + 'To reset your password, please verify your email first.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Tap to enter code'; + + @override + String get sendEmail => 'Send email'; + + @override + String get resendEmail => 'Resend email'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Session expired'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Try again'; + + @override + String get checkStatus => 'Check status'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Recover account'; + + @override + String get setPasswordTitle => 'Set password'; + + @override + String get changePasswordTitle => 'Change password'; + + @override + String get resetPasswordTitle => 'Reset password'; + + @override + String get encryptionKeys => 'Encryption keys'; + + @override + String get enterPasswordToEncrypt => + 'Enter a password we can use to encrypt your data'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'How it works'; + + @override + String get generatingEncryptionKeys => 'Generating encryption keys...'; + + @override + String get passwordChangedSuccessfully => 'Password changed successfully'; + + @override + String get signOutFromOtherDevices => 'Sign out from other devices'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'Sign out other devices'; + + @override + String get doNotSignOut => 'Do not sign out'; + + @override + String get generatingEncryptionKeysTitle => + 'एन्क्रिप्शन कुंजियाँ उत्पन्न हो रही हैं...'; + + @override + String get continueLabel => 'Continue'; + + @override + String get insecureDevice => 'Insecure device'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => 'Recovery key copied to clipboard'; + + @override + String get recoveryKey => 'पुनःप्राप्ति कुंजी'; + + @override + String get recoveryKeyOnForgotPassword => + 'If you forget your password, the only way you can recover your data is with this key.'; + + @override + String get recoveryKeySaveDescription => + 'We don\'t store this key, please save this 24 word key in a safe place.'; + + @override + String get doThisLater => 'Do this later'; + + @override + String get saveKey => 'Save key'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'No recovery key?'; + + @override + String get twoFactorAuthTitle => 'दो-चरणीय प्रमाणीकरण |'; + + @override + String get enterCodeHint => + 'Enter the 6-digit code from\nyour authenticator app'; + + @override + String get lostDeviceTitle => 'Lost device?'; + + @override + String get enterRecoveryKeyHint => 'Enter your recovery key'; + + @override + String get recover => 'Recover'; + + @override + String get loggingOut => 'लॉग आउट हो रहा है...'; + + @override + String get immediately => 'Immediately'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Hide content'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Tap to unlock'; + + @override + String get areYouSureYouWantToLogout => 'Are you sure you want to logout?'; + + @override + String get yesLogout => 'Yes, logout'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'Enter PIN'; + + @override + String get setNewPin => 'Set new PIN'; + + @override + String get confirm => 'Confirm'; + + @override + String get reEnterPassword => 'Re-enter password'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verify identity'; + + @override + String get androidBiometricNotRecognized => 'Not recognized. Try again.'; + + @override + String get androidBiometricSuccess => 'Success'; + + @override + String get androidCancelButton => 'Cancel'; + + @override + String get androidSignInTitle => 'Authentication required'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Go to settings'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'ईमेल पहले से ही पंजीकृत है।'; + + @override + String get emailNotRegistered => 'ईमेल पंजीकृत नहीं है।'; + + @override + String get thisEmailIsAlreadyInUse => 'This email is already in use'; + + @override + String emailChangedTo(String newEmail) { + return 'Email changed to $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'सत्र की अवधि समाप्त'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Incorrect code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_hu.dart b/mobile/packages/strings/lib/l10n/strings_localizations_hu.dart new file mode 100644 index 0000000000..3ccc5091ae --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_hu.dart @@ -0,0 +1,630 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Hungarian (`hu`). +class StringsLocalizationsHu extends StringsLocalizations { + StringsLocalizationsHu([String locale = 'hu']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Nem lehet csatlakozni az Ente-hez. Kérjük, ellenőrizze a hálózati beállításokat, és ha a hiba továbbra is fennáll, forduljon az ügyfélszolgálathoz.'; + + @override + String get networkConnectionRefusedErr => + 'Nem lehet csatlakozni az Ente-hez, próbálja újra egy idő után. Ha a hiba továbbra is fennáll, forduljon az ügyfélszolgálathoz.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Úgy tűnik, valami hiba történt. Kérjük, próbálja újra egy idő után. Ha a hiba továbbra is fennáll, forduljon ügyfélszolgálatunkhoz.'; + + @override + String get error => 'Hiba'; + + @override + String get ok => 'OK'; + + @override + String get faq => 'GY. I. K.'; + + @override + String get contactSupport => 'Lépj kapcsolatba az Ügyfélszolgálattal'; + + @override + String get emailYourLogs => 'E-mailben küldje el naplóit'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Kérjük, küldje el a naplókat erre az e-mail címre\n$toEmail'; + } + + @override + String get copyEmailAddress => 'E-mail cím másolása'; + + @override + String get exportLogs => 'Naplófájlok exportálása'; + + @override + String get cancel => 'Mégse'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Hiba bejelentése'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Csatlakozva a következőhöz: $endpoint'; + } + + @override + String get save => 'Mentés'; + + @override + String get send => 'Küldés'; + + @override + String get saveOrSendDescription => + 'El szeretné menteni ezt a tárhelyére (alapértelmezés szerint a Letöltések mappába), vagy elküldi más alkalmazásoknak?'; + + @override + String get saveOnlyDescription => + 'El szeretné menteni ezt a tárhelyére (alapértelmezés szerint a Letöltések mappába)?'; + + @override + String get enterNewEmailHint => 'Add meg az új e-mail címed'; + + @override + String get email => 'E-mail'; + + @override + String get verify => 'Hitelesítés'; + + @override + String get invalidEmailTitle => 'Érvénytelen e-mail cím'; + + @override + String get invalidEmailMessage => + 'Kérjük, adjon meg egy érvényes e-mail címet.'; + + @override + String get pleaseWait => 'Kérem várjon...'; + + @override + String get verifyPassword => 'Jelszó megerősítése'; + + @override + String get incorrectPasswordTitle => 'Érvénytelen jelszó'; + + @override + String get pleaseTryAgain => 'Kérjük, próbálja meg újra'; + + @override + String get enterPassword => 'Adja meg a jelszót'; + + @override + String get enterYourPasswordHint => 'Adja meg a jelszavát'; + + @override + String get activeSessions => 'Aktív munkamenetek'; + + @override + String get oops => 'Hoppá'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Hiba történt. Kérjük, próbálkozz újra'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Ezzel kijelentkezik erről az eszközről!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Ezzel kijelentkezik a következő eszközről:'; + + @override + String get terminateSession => 'Megszakítja a munkamenetet?'; + + @override + String get terminate => 'Befejezés'; + + @override + String get thisDevice => 'Ez az eszköz'; + + @override + String get createAccount => 'Felhasználó létrehozás'; + + @override + String get weakStrength => 'Gyenge'; + + @override + String get moderateStrength => 'Mérsékelt'; + + @override + String get strongStrength => 'Erős'; + + @override + String get deleteAccount => 'Fiók törlése'; + + @override + String get deleteAccountQuery => + 'Szomorúan tapasztaljuk. Problémába ütköztél?'; + + @override + String get yesSendFeedbackAction => 'Igen, visszajelzés küldése'; + + @override + String get noDeleteAccountAction => 'Fiók végleges törlése'; + + @override + String get initiateAccountDeleteTitle => + 'Kérjük, hitelesítse magát a fiók törlésének kezdeményezéséhez'; + + @override + String get confirmAccountDeleteTitle => 'Fiók törlésének megerősítése'; + + @override + String get confirmAccountDeleteMessage => + 'Ez a fiók össze van kapcsolva más Ente-alkalmazásokkal, ha használ ilyet.\n\nA feltöltött adataid törlését ütemezzük az összes Ente alkalmazásban, és a fiókod véglegesen törlésre kerül.'; + + @override + String get delete => 'Törlés'; + + @override + String get createNewAccount => 'Új fiók létrehozása'; + + @override + String get password => 'Jelszó'; + + @override + String get confirmPassword => 'Jelszó megerősítése'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Jelszó erőssége: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Honnan hallottál Ente-ről? (opcionális)'; + + @override + String get hearUsExplanation => + 'Nem követjük nyomon az alkalmazástelepítéseket. Segítene, ha elmondaná, hol talált ránk!'; + + @override + String get signUpTerms => + 'Elfogadom az szolgáltatási feltételeket és az adatvédelmi irányelveket'; + + @override + String get termsOfServicesTitle => 'Használati feltételek'; + + @override + String get privacyPolicyTitle => 'Adatvédelmi irányelvek'; + + @override + String get ackPasswordLostWarning => + 'Tudomásul veszem, hogy ha elveszítem a jelszavamat, elveszíthetem az adataimat, mivel adataim végponttól végpontig titkosítva vannak.'; + + @override + String get encryption => 'Titkosítás'; + + @override + String get logInLabel => 'Bejelentkezés'; + + @override + String get welcomeBack => 'Köszöntjük ismét!'; + + @override + String get loginTerms => + 'A bejelentkezés gombra kattintva elfogadom az szolgáltatási feltételeket és az adatvédelmi irányelveket'; + + @override + String get noInternetConnection => 'Nincs internet kapcsolat'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Kérjük, ellenőrizze az internetkapcsolatát, és próbálja meg újra.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Az ellenőrzés sikertelen, próbálja újra'; + + @override + String get recreatePasswordTitle => 'Új jelszó létrehozása'; + + @override + String get recreatePasswordBody => + 'A jelenlegi eszköz nem elég erős a jelszavának ellenőrzéséhez, de újra tudjuk úgy generálni, hogy az minden eszközzel működjön.\n\nKérjük, jelentkezzen be helyreállítási kulcsával, és állítsa be újra jelszavát (ha szeretné, újra használhatja ugyanazt).'; + + @override + String get useRecoveryKey => 'Helyreállítási kulcs használata'; + + @override + String get forgotPassword => 'Elfelejtett jelszó'; + + @override + String get changeEmail => 'E-mail cím módosítása'; + + @override + String get verifyEmail => 'E-mail cím megerősítése'; + + @override + String weHaveSendEmailTo(String email) { + return 'E-mailt küldtünk a következő címre: $email'; + } + + @override + String get toResetVerifyEmail => + 'Jelszava visszaállításához először igazolja e-mail-címét.'; + + @override + String get checkInboxAndSpamFolder => + 'Kérjük, ellenőrizze beérkező leveleit (és spam mappát) az ellenőrzés befejezéséhez'; + + @override + String get tapToEnterCode => 'Koppintson a kód beírásához'; + + @override + String get sendEmail => 'E-mail küldése'; + + @override + String get resendEmail => 'E-mail újraküldése'; + + @override + String get passKeyPendingVerification => 'Az ellenőrzés még függőben van'; + + @override + String get loginSessionExpired => 'Lejárt a munkamenet'; + + @override + String get loginSessionExpiredDetails => + 'A munkameneted lejárt. Kérem lépjen be újra.'; + + @override + String get passkeyAuthTitle => 'Álkulcs megerősítése'; + + @override + String get waitingForVerification => 'Várakozás az ellenőrzésre...'; + + @override + String get tryAgain => 'Próbáld újra'; + + @override + String get checkStatus => 'Állapot ellenőrzése'; + + @override + String get loginWithTOTP => 'Bejelentkezés TOTP-vel'; + + @override + String get recoverAccount => 'Fiók visszaállítása'; + + @override + String get setPasswordTitle => 'Jelszó beállítás'; + + @override + String get changePasswordTitle => 'Jelszó módosítás'; + + @override + String get resetPasswordTitle => 'Jelszó visszaállítás'; + + @override + String get encryptionKeys => 'Titkosító kulcsok'; + + @override + String get enterPasswordToEncrypt => + 'Adjon meg egy jelszót, amellyel titkosíthatjuk adatait'; + + @override + String get enterNewPasswordToEncrypt => + 'Adjon meg egy új jelszót, amellyel titkosíthatjuk adatait'; + + @override + String get passwordWarning => + 'Ezt a jelszót nem tároljuk, így ha elfelejti, nem tudjuk visszafejteni adatait'; + + @override + String get howItWorks => 'Hogyan működik'; + + @override + String get generatingEncryptionKeys => 'Titkosító kulcsok generálása...'; + + @override + String get passwordChangedSuccessfully => + 'A jelszó sikeresen meg lett változtatva'; + + @override + String get signOutFromOtherDevices => 'Jelentkezzen ki más eszközökről'; + + @override + String get signOutOtherBody => + 'Ha úgy gondolja, hogy valaki ismeri jelszavát, kényszerítheti a fiókját használó összes többi eszközt a kijelentkezésre.'; + + @override + String get signOutOtherDevices => 'Jelentkezzen ki a többi eszközről'; + + @override + String get doNotSignOut => 'Ne jelentkezzen ki'; + + @override + String get generatingEncryptionKeysTitle => 'Titkosítási kulcs generálása...'; + + @override + String get continueLabel => 'Folytatás'; + + @override + String get insecureDevice => 'Nem biztonságos eszköz'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sajnáljuk, nem tudtunk biztonságos kulcsokat generálni ezen az eszközön.\n\nkérjük, regisztráljon egy másik eszközről.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'A helyreállítási kulcs a vágólapra másolva'; + + @override + String get recoveryKey => 'Visszaállítási kulcs'; + + @override + String get recoveryKeyOnForgotPassword => + 'Ha elfelejti jelszavát, csak ezzel a kulccsal tudja visszaállítani adatait.'; + + @override + String get recoveryKeySaveDescription => + 'Ezt a kulcsot nem tároljuk, kérjük, őrizze meg ezt a 24 szavas kulcsot egy biztonságos helyen.'; + + @override + String get doThisLater => 'Később'; + + @override + String get saveKey => 'Mentés'; + + @override + String get recoveryKeySaved => + 'A helyreállítási kulcs a Letöltések mappába mentve!'; + + @override + String get noRecoveryKeyTitle => 'Nincs helyreállítási kulcs?'; + + @override + String get twoFactorAuthTitle => 'Kétlépcsős hitelesítés (2FA)'; + + @override + String get enterCodeHint => + 'Írja be a 6 számjegyű kódot a hitelesítő alkalmazásból'; + + @override + String get lostDeviceTitle => 'Elveszett a készüléked?'; + + @override + String get enterRecoveryKeyHint => 'Visszaállító kód megadása'; + + @override + String get recover => 'Visszaállít'; + + @override + String get loggingOut => 'Kijelentkezés...'; + + @override + String get immediately => 'Azonnal'; + + @override + String get appLock => 'Alkalmazások zárolása'; + + @override + String get autoLock => 'Automatikus lezárás'; + + @override + String get noSystemLockFound => 'Nem található rendszerzár'; + + @override + String get deviceLockEnablePreSteps => + 'Az eszközzár engedélyezéséhez állítsa be az eszköz jelszavát vagy a zárképernyőt a rendszerbeállításokban.'; + + @override + String get appLockDescription => + 'Válasszon az eszköz alapértelmezett zárolási képernyője és a PIN-kóddal vagy jelszóval rendelkező egyéni zárolási képernyő között.'; + + @override + String get deviceLock => 'Eszköz lezárás'; + + @override + String get pinLock => 'PIN feloldás'; + + @override + String get autoLockFeatureDescription => + 'Az az idő, amely elteltével az alkalmazás zárolásra kerül, miután a háttérbe került'; + + @override + String get hideContent => 'Tartalom elrejtése'; + + @override + String get hideContentDescriptionAndroid => + 'Elrejti az alkalmazás tartalmát az alkalmazásváltóban, és letiltja a képernyőképeket'; + + @override + String get hideContentDescriptioniOS => + 'Elrejti az alkalmazás tartalmát az alkalmazásváltóban'; + + @override + String get tooManyIncorrectAttempts => 'Túl sok helytelen próbálkozás'; + + @override + String get tapToUnlock => 'Koppintson a feloldáshoz'; + + @override + String get areYouSureYouWantToLogout => 'Biztos benne, hogy kijelentkezik?'; + + @override + String get yesLogout => 'Igen, kijelentkezés'; + + @override + String get authToViewSecrets => + 'A titkos kulcsok megtekintéséhez hitelesítse magát'; + + @override + String get next => 'Következő'; + + @override + String get setNewPassword => 'Új jelszó beállítása'; + + @override + String get enterPin => 'PIN kód megadása'; + + @override + String get setNewPin => 'Új PIN kód beállítása'; + + @override + String get confirm => 'Megerősítés'; + + @override + String get reEnterPassword => 'Írja be újra a jelszót'; + + @override + String get reEnterPin => 'Írja be újra a PIN-kódot'; + + @override + String get androidBiometricHint => 'Személyazonosság ellenőrzése'; + + @override + String get androidBiometricNotRecognized => 'Nem felismerhető. Próbáld újra.'; + + @override + String get androidBiometricSuccess => 'Sikeres'; + + @override + String get androidCancelButton => 'Mégse'; + + @override + String get androidSignInTitle => 'Hitelesítés szükséges'; + + @override + String get androidBiometricRequiredTitle => 'Biometria szükséges'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Az eszköz hitelesítő adatai szükségesek'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Az eszköz hitelesítő adatai szükségesek'; + + @override + String get goToSettings => 'Beállítások megnyitása'; + + @override + String get androidGoToSettingsDescription => + 'A biometrikus hitelesítés nincs beállítva az eszközön. A biometrikus hitelesítés hozzáadásához lépjen a \'Beállítások > Biztonság\' menüpontra.'; + + @override + String get iOSLockOut => + 'A biometrikus hitelesítés ki van kapcsolva. Az engedélyezéséhez zárja le és oldja fel a képernyőt.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Ez az e-mai cím már regisztrálva van.'; + + @override + String get emailNotRegistered => 'Ez az e-mail cím nincs regisztrálva.'; + + @override + String get thisEmailIsAlreadyInUse => 'Ez az e-mail már használatban van'; + + @override + String emailChangedTo(String newEmail) { + return 'Az e-mail cím módosítva erre: $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'A hitelesítés sikertelen, próbálja újra'; + + @override + String get authenticationSuccessful => 'Sikeres hitelesítés!'; + + @override + String get sessionExpired => 'A munkamenet lejárt'; + + @override + String get incorrectRecoveryKey => 'Helytelen helyreállítási kulcs'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'A megadott helyreállítási kulcs helytelen'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'A kétfaktoros hitelesítés visszaállítása sikeres'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => 'Ez az ellenőrző kód lejárt'; + + @override + String get incorrectCode => 'Helytelen kód'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sajnáljuk, a megadott kód helytelen'; + + @override + String get developerSettings => 'Fejlesztői beállítások'; + + @override + String get serverEndpoint => 'Szerver végpont'; + + @override + String get invalidEndpoint => 'Érvénytelen végpont'; + + @override + String get invalidEndpointMessage => + 'Sajnáljuk, a megadott végpont érvénytelen. Adjon meg egy érvényes végpontot, és próbálja újra.'; + + @override + String get endpointUpdatedMessage => 'A végpont sikeresen frissítve'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_id.dart b/mobile/packages/strings/lib/l10n/strings_localizations_id.dart new file mode 100644 index 0000000000..e3839340a6 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_id.dart @@ -0,0 +1,630 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Indonesian (`id`). +class StringsLocalizationsId extends StringsLocalizations { + StringsLocalizationsId([String locale = 'id']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Tidak dapat terhubung ke Ente. Mohon periksa kembali koneksi internet Anda dan hubungi tim bantuan kami jika galat masih ada.'; + + @override + String get networkConnectionRefusedErr => + 'Sepertinya ada yang salah. Mohon coba lagi setelah beberapa waktu. Jika galat masih ada, Anda dapat menghubungi tim bantuan kami.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Sepertinya ada yang salah. Mohon coba lagi setelah beberapa waktu. Jika galat masih ada, Anda dapat menghubungi tim bantuan kami.'; + + @override + String get error => 'Kesalahan'; + + @override + String get ok => 'Oke'; + + @override + String get faq => 'Pertanyaan yang sering ditanyakan'; + + @override + String get contactSupport => 'Hubungi dukungan'; + + @override + String get emailYourLogs => 'Kirimkan log Anda melalui surel'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Mohon kirim log ke $toEmail'; + } + + @override + String get copyEmailAddress => 'Salin alamat surel'; + + @override + String get exportLogs => 'Ekspor log'; + + @override + String get cancel => 'Batal'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Laporkan bug'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Terhubung ke $endpoint'; + } + + @override + String get save => 'Simpan'; + + @override + String get send => 'Kirim'; + + @override + String get saveOrSendDescription => + 'Anda ingin menyimpan kode ke penyimpanan Anda (folder pilihan bawaan adalah folder Downloads) atau Anda ingin kirimkan ke aplikasi lain?'; + + @override + String get saveOnlyDescription => + 'Anda ingin menyimpan kode ke penyimpanan Anda (folder pilihan bawaan adalah folder Downloads)'; + + @override + String get enterNewEmailHint => 'Masukkan alamat email baru anda'; + + @override + String get email => 'Email'; + + @override + String get verify => 'Verifikasi'; + + @override + String get invalidEmailTitle => 'Alamat email tidak valid'; + + @override + String get invalidEmailMessage => 'Harap masukkan alamat email yang valid.'; + + @override + String get pleaseWait => 'Mohon tunggu...'; + + @override + String get verifyPassword => 'Verifikasi kata sandi'; + + @override + String get incorrectPasswordTitle => 'Kata sandi salah'; + + @override + String get pleaseTryAgain => 'Harap coba lagi'; + + @override + String get enterPassword => 'Masukkan kata sandi'; + + @override + String get enterYourPasswordHint => 'Masukkan kata sandi Anda'; + + @override + String get activeSessions => 'Sesi aktif'; + + @override + String get oops => 'Ups'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Ada yang salah. Mohon coba kembali'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Langkah ini akan mengeluarkan Anda dari gawai ini!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Langkah ini akan mengeluarkan Anda dari gawai berikut:'; + + @override + String get terminateSession => 'Akhiri sesi?'; + + @override + String get terminate => 'Akhiri'; + + @override + String get thisDevice => 'Gawai ini'; + + @override + String get createAccount => 'Buat akun'; + + @override + String get weakStrength => 'Lemah'; + + @override + String get moderateStrength => 'Sedang'; + + @override + String get strongStrength => 'Kuat'; + + @override + String get deleteAccount => 'Hapus akun'; + + @override + String get deleteAccountQuery => + 'Kami akan merasa kehilangan Anda. Apakah Anda menghadapi masalah?'; + + @override + String get yesSendFeedbackAction => 'Ya, kirim umpan balik'; + + @override + String get noDeleteAccountAction => 'Tidak, hapus akun'; + + @override + String get initiateAccountDeleteTitle => + 'Harap autentikasi untuk memulai penghapusan akun'; + + @override + String get confirmAccountDeleteTitle => 'Konfirmasikan penghapusan akun'; + + @override + String get confirmAccountDeleteMessage => + 'Akun ini terhubung dengan aplikasi Ente yang lain (jika Anda pakai).\n\nData yang Anda unggah di seluruh aplikasi Ente akan dijadwalkan untuk dihapus. Akun Anda juga akan dihapus secara permanen.'; + + @override + String get delete => 'Hapus'; + + @override + String get createNewAccount => 'Buat akun baru'; + + @override + String get password => 'Kata Sandi'; + + @override + String get confirmPassword => 'Konfirmasi kata sandi'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Tingkat kekuatan kata sandi: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Dari mana Anda menemukan Ente? (opsional)'; + + @override + String get hearUsExplanation => + 'Kami tidak melacak penginstalan aplikasi kami. Akan sangat membantu kami bila Anda memberitahu kami dari mana Anda mengetahui Ente!'; + + @override + String get signUpTerms => + 'Saya menyetujui syarat dan ketentuan serta kebijakan privasi Ente'; + + @override + String get termsOfServicesTitle => 'Ketentuan'; + + @override + String get privacyPolicyTitle => 'Kebijakan Privasi'; + + @override + String get ackPasswordLostWarning => + 'Saya mengerti bahwa jika saya lupa kata sandi saya, data saya dapat hilang karena data saya terenkripsi secara end-to-end.'; + + @override + String get encryption => 'Enkripsi'; + + @override + String get logInLabel => 'Masuk akun'; + + @override + String get welcomeBack => 'Selamat datang kembali!'; + + @override + String get loginTerms => + 'Dengan menekan masuk akun, saya menyetujui syarat dan ketentuan serta kebijakan privasi Ente'; + + @override + String get noInternetConnection => 'Tiada koneksi internet'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Mohon periksa koneksi internet Anda dan coba kembali.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Gagal memverifikasi. Mohon coba lagi'; + + @override + String get recreatePasswordTitle => 'Membuat kembali kata sandi'; + + @override + String get recreatePasswordBody => + 'Gawai Anda saat ini tidak dapat memverifikasi kata sandi Anda. Namun, kami dapat membuat ulang dengan cara yang dapat digunakan pada semua gawai.\n\nMohon masuk log dengan kunci pemulihan dan buat ulang kata sandi Anda (kata sandi yang sama diperbolehkan).'; + + @override + String get useRecoveryKey => 'Gunakan kunci pemulihan'; + + @override + String get forgotPassword => 'Lupa kata sandi'; + + @override + String get changeEmail => 'Ubah alamat email'; + + @override + String get verifyEmail => 'Verifikasi email'; + + @override + String weHaveSendEmailTo(String email) { + return 'Kami telah mengirimkan sebuah posel ke $email'; + } + + @override + String get toResetVerifyEmail => + 'Untuk mengatur ulang kata sandi, mohon verifikasi surel Anda terlebih dahulu.'; + + @override + String get checkInboxAndSpamFolder => 'Mohon cek'; + + @override + String get tapToEnterCode => 'Ketuk untuk memasukkan kode'; + + @override + String get sendEmail => 'Kirim surel'; + + @override + String get resendEmail => 'Kirim ulang surel'; + + @override + String get passKeyPendingVerification => 'Verifikasi tertunda'; + + @override + String get loginSessionExpired => 'Sesi sudah berakhir'; + + @override + String get loginSessionExpiredDetails => + 'Sesi Anda sudah berakhir. Mohon masuk log kembali.'; + + @override + String get passkeyAuthTitle => 'Verifikasi passkey'; + + @override + String get waitingForVerification => 'Menantikan verifikasi...'; + + @override + String get tryAgain => 'Coba lagi'; + + @override + String get checkStatus => 'Periksa status'; + + @override + String get loginWithTOTP => 'Masuk menggunakan TOTP'; + + @override + String get recoverAccount => 'Pulihkan akun'; + + @override + String get setPasswordTitle => 'Atur kata sandi'; + + @override + String get changePasswordTitle => 'Ubah kata sandi'; + + @override + String get resetPasswordTitle => 'Atur ulang kata sandi'; + + @override + String get encryptionKeys => 'Kunci enkripsi'; + + @override + String get enterPasswordToEncrypt => + 'Masukkan kata sandi yang dapat kami gunakan untuk mengenkripsi data Anda'; + + @override + String get enterNewPasswordToEncrypt => + 'Masukkan kata sandi baru yang dapat kami gunakan untuk mengenkripsi data Anda'; + + @override + String get passwordWarning => + 'Kami tidak menyimpan kata sandi Anda. Jika Anda lupa, kami tidak dapat mendekripsi data Anda'; + + @override + String get howItWorks => 'Cara kerjanya'; + + @override + String get generatingEncryptionKeys => 'Sedang membuat kunci enkripsi...'; + + @override + String get passwordChangedSuccessfully => 'Kata sandi sukses diubah'; + + @override + String get signOutFromOtherDevices => 'Keluar dari gawai yang lain'; + + @override + String get signOutOtherBody => + 'Jika Anda pikir seseorang mungkin mengetahui kata sandi Anda, Anda dapat mengeluarkan akun Anda pada semua gawai'; + + @override + String get signOutOtherDevices => 'Keluar akun pada gawai yang lain'; + + @override + String get doNotSignOut => 'Jangan keluar'; + + @override + String get generatingEncryptionKeysTitle => + 'Sedang membuat kunci enkripsi...'; + + @override + String get continueLabel => 'Lanjutkan'; + + @override + String get insecureDevice => 'Perangkat tidak aman'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Maaf, kami tidak dapat membuat kunci yang aman pada perangkat ini.\n\nHarap mendaftar dengan perangkat lain.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Kunci pemulihan disalin ke papan klip'; + + @override + String get recoveryKey => 'Kunci pemulihan'; + + @override + String get recoveryKeyOnForgotPassword => + 'Jika Anda lupa kata sandi, satu-satunya cara memulihkan data Anda adalah dengan kunci ini.'; + + @override + String get recoveryKeySaveDescription => + 'Kami tidak menyimpan kunci ini, jadi harap simpan kunci yang berisi 24 kata ini dengan aman.'; + + @override + String get doThisLater => 'Lakukan lain kali'; + + @override + String get saveKey => 'Simpan kunci'; + + @override + String get recoveryKeySaved => + 'Kunci pemulihan sudah tersimpan di folder \'Downloads\'!'; + + @override + String get noRecoveryKeyTitle => 'Tidak punya kunci pemulihan?'; + + @override + String get twoFactorAuthTitle => 'Autentikasi dua langkah'; + + @override + String get enterCodeHint => + 'Masukkan kode 6 digit dari aplikasi autentikator Anda'; + + @override + String get lostDeviceTitle => 'Perangkat hilang?'; + + @override + String get enterRecoveryKeyHint => 'Masukkan kunci pemulihan Anda'; + + @override + String get recover => 'Pulihkan'; + + @override + String get loggingOut => 'Mengeluarkan akun...'; + + @override + String get immediately => 'Segera'; + + @override + String get appLock => 'Kunci aplikasi'; + + @override + String get autoLock => 'Kunci otomatis'; + + @override + String get noSystemLockFound => 'Tidak ditemukan kunci sistem'; + + @override + String get deviceLockEnablePreSteps => + 'Pasang kunci sandi atau kunci layar pada pengaturan sistem untuk menyalakan Pengunci Gawai.'; + + @override + String get appLockDescription => + 'Pilih layar kunci bawaan gawai Anda ATAU layar kunci kustom dengan PIN atau kata sandi.'; + + @override + String get deviceLock => 'Kunci perangkat'; + + @override + String get pinLock => 'PIN'; + + @override + String get autoLockFeatureDescription => + 'Durasi waktu aplikasi akan terkunci setelah aplikasi ditutup'; + + @override + String get hideContent => 'Sembunyikan isi'; + + @override + String get hideContentDescriptionAndroid => + 'Menyembunyikan konten aplikasi di pemilih aplikasi dan menonaktifkan tangkapan layar'; + + @override + String get hideContentDescriptioniOS => + 'Menyembunyikan konten aplikasi di pemilih aplikasi'; + + @override + String get tooManyIncorrectAttempts => 'Terlalu banyak percobaan yang salah'; + + @override + String get tapToUnlock => 'Ketuk untuk membuka'; + + @override + String get areYouSureYouWantToLogout => + 'Anda yakin ingin keluar dari akun ini?'; + + @override + String get yesLogout => 'Ya, keluar akun'; + + @override + String get authToViewSecrets => + 'Harap lakukan autentikasi untuk melihat rahasia Anda'; + + @override + String get next => 'Selanjutnya'; + + @override + String get setNewPassword => 'Pasang kata sandi baru'; + + @override + String get enterPin => 'Masukkan PIN'; + + @override + String get setNewPin => 'Pasang PIN yang baru'; + + @override + String get confirm => 'Konfirmasikan'; + + @override + String get reEnterPassword => 'Masukkan kembali kata sandi'; + + @override + String get reEnterPin => 'Masukkan kembali PIN'; + + @override + String get androidBiometricHint => 'Verifikasikan identitas Anda'; + + @override + String get androidBiometricNotRecognized => 'Tidak dikenal. Coba lagi.'; + + @override + String get androidBiometricSuccess => 'Sukses'; + + @override + String get androidCancelButton => 'Batalkan'; + + @override + String get androidSignInTitle => 'Autentikasi diperlukan'; + + @override + String get androidBiometricRequiredTitle => 'Biometrik diperlukan'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Kredensial perangkat diperlukan'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Kredensial perangkat diperlukan'; + + @override + String get goToSettings => 'Pergi ke pengaturan'; + + @override + String get androidGoToSettingsDescription => + 'Tidak ada autentikasi biometrik pada gawai Anda. Buka \'Pengaturan > Keamanan\' untuk menambahkan autentikasi biometrik pada gawai Anda.'; + + @override + String get iOSLockOut => + 'Autentikasi biometrik dimatikan. Kunci dan buka layar Anda untuk menyalakan autentikasi biometrik.'; + + @override + String get iOSOkButton => 'Oke'; + + @override + String get emailAlreadyRegistered => 'Email sudah terdaftar.'; + + @override + String get emailNotRegistered => 'Email belum terdaftar.'; + + @override + String get thisEmailIsAlreadyInUse => 'Surel ini sudah dipakai!'; + + @override + String emailChangedTo(String newEmail) { + return 'Surel sudah diganti menjadi $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Gagal mengautentikasi. Mohon coba lagi'; + + @override + String get authenticationSuccessful => 'Sukses mengautentikasi!'; + + @override + String get sessionExpired => 'Sesi berakhir'; + + @override + String get incorrectRecoveryKey => 'Kunci pemulihan takbenar'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Kunci pemulihan yang Anda masukkan takbenar'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Autentikasi dwifaktor sukses diatur ulang'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Kode verifikasi Anda telah kedaluwarsa'; + + @override + String get incorrectCode => 'Kode takbenar'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Maaf, kode yang Anda masukkan takbenar'; + + @override + String get developerSettings => 'Pengaturan Pengembang'; + + @override + String get serverEndpoint => 'Peladen endpoint'; + + @override + String get invalidEndpoint => 'Endpoint takvalid'; + + @override + String get invalidEndpointMessage => + 'Maaf, endpoint yang Anda masukkan takvalid. Mohon masukkan endpoint yang valid, lalu coba kembali.'; + + @override + String get endpointUpdatedMessage => 'Endpoint berhasil diubah'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_it.dart b/mobile/packages/strings/lib/l10n/strings_localizations_it.dart new file mode 100644 index 0000000000..9a13c0cd7c --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_it.dart @@ -0,0 +1,634 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Italian (`it`). +class StringsLocalizationsIt extends StringsLocalizations { + StringsLocalizationsIt([String locale = 'it']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Impossibile connettersi a Ente, controlla le impostazioni di rete e contatta l\'assistenza se l\'errore persiste.'; + + @override + String get networkConnectionRefusedErr => + 'Impossibile connettersi a Ente, riprova tra un po\' di tempo. Se l\'errore persiste, contatta l\'assistenza.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Sembra che qualcosa sia andato storto. Riprova tra un po\'. Se l\'errore persiste, contatta il nostro team di supporto.'; + + @override + String get error => 'Errore'; + + @override + String get ok => 'OK'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'Contatta il supporto'; + + @override + String get emailYourLogs => 'Invia una mail dei tuoi log'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Invia i log a \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copia indirizzo email'; + + @override + String get exportLogs => 'Esporta log'; + + @override + String get cancel => 'Annulla'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Segnala un problema'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connesso a $endpoint'; + } + + @override + String get save => 'Salva'; + + @override + String get send => 'Invia'; + + @override + String get saveOrSendDescription => + 'Vuoi salvarlo nel tuo spazio di archiviazione (cartella Download per impostazione predefinita) o inviarlo ad altre applicazioni?'; + + @override + String get saveOnlyDescription => + 'Vuoi salvarlo nel tuo spazio di archiviazione (cartella Download per impostazione predefinita)?'; + + @override + String get enterNewEmailHint => 'Inserisci il tuo nuovo indirizzo email'; + + @override + String get email => 'Email'; + + @override + String get verify => 'Verifica'; + + @override + String get invalidEmailTitle => 'Indirizzo email non valido'; + + @override + String get invalidEmailMessage => 'Inserisci un indirizzo email valido.'; + + @override + String get pleaseWait => 'Attendere prego...'; + + @override + String get verifyPassword => 'Verifica la password'; + + @override + String get incorrectPasswordTitle => 'Password sbagliata'; + + @override + String get pleaseTryAgain => 'Per favore riprova'; + + @override + String get enterPassword => 'Inserisci la password'; + + @override + String get enterYourPasswordHint => 'Inserisci la tua password'; + + @override + String get activeSessions => 'Sessioni attive'; + + @override + String get oops => 'Oops'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Qualcosa è andato storto, per favore riprova'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Questo ti disconnetterà da questo dispositivo!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Questo ti disconnetterà dal seguente dispositivo:'; + + @override + String get terminateSession => 'Termina sessione?'; + + @override + String get terminate => 'Termina'; + + @override + String get thisDevice => 'Questo dispositivo'; + + @override + String get createAccount => 'Crea account'; + + @override + String get weakStrength => 'Debole'; + + @override + String get moderateStrength => 'Mediocre'; + + @override + String get strongStrength => 'Forte'; + + @override + String get deleteAccount => 'Elimina account'; + + @override + String get deleteAccountQuery => + 'Ci dispiace vederti andare via. Stai avendo qualche problema?'; + + @override + String get yesSendFeedbackAction => 'Sì, invia un feedback'; + + @override + String get noDeleteAccountAction => 'No, elimina l\'account'; + + @override + String get initiateAccountDeleteTitle => + 'Si prega di autenticarsi per avviare l\'eliminazione dell\'account'; + + @override + String get confirmAccountDeleteTitle => + 'Conferma l\'eliminazione dell\'account'; + + @override + String get confirmAccountDeleteMessage => + 'Questo account è collegato ad altre app di Ente, se ne utilizzi.\n\nI tuoi dati caricati, su tutte le app di Ente, saranno pianificati per la cancellazione e il tuo account verrà eliminato definitivamente.'; + + @override + String get delete => 'Cancella'; + + @override + String get createNewAccount => 'Crea un nuovo account'; + + @override + String get password => 'Password'; + + @override + String get confirmPassword => 'Conferma la password'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Forza password: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => + 'Dove hai sentito parlare di Ente? (opzionale)'; + + @override + String get hearUsExplanation => + 'Non teniamo traccia delle installazioni dell\'app. Sarebbe utile se ci dicessi dove ci hai trovato!'; + + @override + String get signUpTerms => + 'Accetto i termini di servizio e la politica sulla privacy'; + + @override + String get termsOfServicesTitle => 'Termini'; + + @override + String get privacyPolicyTitle => 'Politica sulla Privacy'; + + @override + String get ackPasswordLostWarning => + 'Comprendo che se perdo la password, potrei perdere l\'accesso ai miei dati poiché i miei dati sono criptati end-to-end.'; + + @override + String get encryption => 'Crittografia'; + + @override + String get logInLabel => 'Accedi'; + + @override + String get welcomeBack => 'Bentornato!'; + + @override + String get loginTerms => + 'Cliccando sul pulsante Accedi, accetto i termini di servizio e la politica sulla privacy'; + + @override + String get noInternetConnection => 'Nessuna connessione internet'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Si prega di verificare la propria connessione Internet e riprovare.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verifica fallita, per favore riprova'; + + @override + String get recreatePasswordTitle => 'Ricrea password'; + + @override + String get recreatePasswordBody => + 'Il dispositivo attuale non è abbastanza potente per verificare la tua password, ma la possiamo rigenerare in un modo che funzioni su tutti i dispositivi.\n\nEffettua il login utilizzando la tua chiave di recupero e rigenera la tua password (puoi utilizzare nuovamente la stessa se vuoi).'; + + @override + String get useRecoveryKey => 'Utilizza un codice di recupero'; + + @override + String get forgotPassword => 'Password dimenticata'; + + @override + String get changeEmail => 'Modifica email'; + + @override + String get verifyEmail => 'Verifica email'; + + @override + String weHaveSendEmailTo(String email) { + return 'Abbiamo inviato una mail a $email'; + } + + @override + String get toResetVerifyEmail => + 'Per reimpostare la tua password, prima verifica la tua email.'; + + @override + String get checkInboxAndSpamFolder => + 'Per favore, controlla la tua casella di posta (e lo spam) per completare la verifica'; + + @override + String get tapToEnterCode => 'Tocca per inserire il codice'; + + @override + String get sendEmail => 'Invia email'; + + @override + String get resendEmail => 'Rinvia email'; + + @override + String get passKeyPendingVerification => 'La verifica è ancora in corso'; + + @override + String get loginSessionExpired => 'Sessione scaduta'; + + @override + String get loginSessionExpiredDetails => + 'La sessione è scaduta. Si prega di accedere nuovamente.'; + + @override + String get passkeyAuthTitle => 'Verifica della passkey'; + + @override + String get waitingForVerification => 'In attesa di verifica...'; + + @override + String get tryAgain => 'Riprova'; + + @override + String get checkStatus => 'Verifica stato'; + + @override + String get loginWithTOTP => 'Login con TOTP'; + + @override + String get recoverAccount => 'Recupera account'; + + @override + String get setPasswordTitle => 'Imposta password'; + + @override + String get changePasswordTitle => 'Modifica password'; + + @override + String get resetPasswordTitle => 'Reimposta password'; + + @override + String get encryptionKeys => 'Chiavi di crittografia'; + + @override + String get enterPasswordToEncrypt => + 'Inserisci una password per criptare i tuoi dati'; + + @override + String get enterNewPasswordToEncrypt => + 'Inserisci una nuova password per criptare i tuoi dati'; + + @override + String get passwordWarning => + 'Non memorizziamo questa password, quindi se te la dimentichi, non possiamo decriptare i tuoi dati'; + + @override + String get howItWorks => 'Come funziona'; + + @override + String get generatingEncryptionKeys => + 'Generazione delle chiavi di crittografia...'; + + @override + String get passwordChangedSuccessfully => 'Password modificata con successo'; + + @override + String get signOutFromOtherDevices => 'Esci dagli altri dispositivi'; + + @override + String get signOutOtherBody => + 'Se pensi che qualcuno possa conoscere la tua password, puoi forzare tutti gli altri dispositivi che usano il tuo account ad uscire.'; + + @override + String get signOutOtherDevices => 'Esci dagli altri dispositivi'; + + @override + String get doNotSignOut => 'Non uscire'; + + @override + String get generatingEncryptionKeysTitle => + 'Generazione delle chiavi di crittografia...'; + + @override + String get continueLabel => 'Continua'; + + @override + String get insecureDevice => 'Dispositivo non sicuro'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Siamo spiacenti, non possiamo generare le chiavi sicure su questo dispositivo.\n\nPer favore, accedi da un altro dispositivo.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Chiave di recupero copiata negli appunti'; + + @override + String get recoveryKey => 'Chiave di recupero'; + + @override + String get recoveryKeyOnForgotPassword => + 'Se dimentichi la password, l\'unico modo per recuperare i tuoi dati è con questa chiave.'; + + @override + String get recoveryKeySaveDescription => + 'Non memorizziamo questa chiave, per favore salva questa chiave di 24 parole in un posto sicuro.'; + + @override + String get doThisLater => 'Fallo più tardi'; + + @override + String get saveKey => 'Salva chiave'; + + @override + String get recoveryKeySaved => + 'Chiave di recupero salvata nella cartella Download!'; + + @override + String get noRecoveryKeyTitle => 'Nessuna chiave di recupero?'; + + @override + String get twoFactorAuthTitle => 'Autenticazione a due fattori'; + + @override + String get enterCodeHint => + 'Inserisci il codice di 6 cifre dalla tua app di autenticazione'; + + @override + String get lostDeviceTitle => 'Dispositivo perso?'; + + @override + String get enterRecoveryKeyHint => 'Inserisci la tua chiave di recupero'; + + @override + String get recover => 'Recupera'; + + @override + String get loggingOut => 'Disconnessione...'; + + @override + String get immediately => 'Immediatamente'; + + @override + String get appLock => 'Blocco app'; + + @override + String get autoLock => 'Blocco automatico'; + + @override + String get noSystemLockFound => 'Nessun blocco di sistema trovato'; + + @override + String get deviceLockEnablePreSteps => + 'Per attivare il blocco del dispositivo, impostare il codice di accesso o il blocco dello schermo nelle impostazioni del sistema.'; + + @override + String get appLockDescription => + 'Scegli tra la schermata di blocco predefinita del dispositivo e una schermata di blocco personalizzata con PIN o password.'; + + @override + String get deviceLock => 'Blocco del dispositivo'; + + @override + String get pinLock => 'Blocco con PIN'; + + @override + String get autoLockFeatureDescription => + 'Tempo dopo il quale l\'applicazione si blocca dopo essere stata messa in background'; + + @override + String get hideContent => 'Nascondi il contenuto'; + + @override + String get hideContentDescriptionAndroid => + 'Nasconde il contenuto nel selettore delle app e disabilita gli screenshot'; + + @override + String get hideContentDescriptioniOS => + 'Nasconde il contenuto nel selettore delle app'; + + @override + String get tooManyIncorrectAttempts => 'Troppi tentativi errati'; + + @override + String get tapToUnlock => 'Tocca per sbloccare'; + + @override + String get areYouSureYouWantToLogout => + 'Sei sicuro di volerti disconnettere?'; + + @override + String get yesLogout => 'Si, effettua la disconnessione'; + + @override + String get authToViewSecrets => 'Autenticati per visualizzare i tuoi segreti'; + + @override + String get next => 'Successivo'; + + @override + String get setNewPassword => 'Imposta una nuova password'; + + @override + String get enterPin => 'Inserisci PIN'; + + @override + String get setNewPin => 'Imposta un nuovo PIN'; + + @override + String get confirm => 'Conferma'; + + @override + String get reEnterPassword => 'Reinserisci la password'; + + @override + String get reEnterPin => 'Reinserisci il PIN'; + + @override + String get androidBiometricHint => 'Verifica l\'identità'; + + @override + String get androidBiometricNotRecognized => 'Non riconosciuto. Riprova.'; + + @override + String get androidBiometricSuccess => 'Successo'; + + @override + String get androidCancelButton => 'Annulla'; + + @override + String get androidSignInTitle => 'Autenticazione necessaria'; + + @override + String get androidBiometricRequiredTitle => + 'Autenticazione biometrica richiesta'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Credenziali del dispositivo richieste'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Credenziali del dispositivo richieste'; + + @override + String get goToSettings => 'Vai alle impostazioni'; + + @override + String get androidGoToSettingsDescription => + 'L\'autenticazione biometrica non è impostata sul tuo dispositivo. Vai a \'Impostazioni > Sicurezza\' per impostarla.'; + + @override + String get iOSLockOut => + 'L\'autenticazione biometrica è disabilitata. Blocca e sblocca lo schermo per abilitarla.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email già registrata.'; + + @override + String get emailNotRegistered => 'Email non registrata.'; + + @override + String get thisEmailIsAlreadyInUse => 'Questa email é già in uso'; + + @override + String emailChangedTo(String newEmail) { + return 'Email modificata in $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Autenticazione non riuscita, riprova'; + + @override + String get authenticationSuccessful => 'Autenticazione riuscita!'; + + @override + String get sessionExpired => 'Sessione scaduta'; + + @override + String get incorrectRecoveryKey => 'Chiave di recupero errata'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'La chiave di recupero che hai inserito non è corretta'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Autenticazione a due fattori ripristinata con successo'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Il tuo codice di verifica è scaduto'; + + @override + String get incorrectCode => 'Codice errato'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Spiacenti, il codice che hai inserito non è corretto'; + + @override + String get developerSettings => 'Impostazioni sviluppatore'; + + @override + String get serverEndpoint => 'Endpoint del server'; + + @override + String get invalidEndpoint => 'Endpoint invalido'; + + @override + String get invalidEndpointMessage => + 'Spiacenti, l\'endpoint inserito non è valido. Inserisci un endpoint valido e riprova.'; + + @override + String get endpointUpdatedMessage => 'Endpoint aggiornato con successo'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart b/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart new file mode 100644 index 0000000000..54766bcdb1 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_ja.dart @@ -0,0 +1,603 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Japanese (`ja`). +class StringsLocalizationsJa extends StringsLocalizations { + StringsLocalizationsJa([String locale = 'ja']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Enteに接続できませんでした。ネットワーク設定を確認し、エラーが解決しない場合はサポートにお問い合わせください。'; + + @override + String get networkConnectionRefusedErr => + 'Enteに接続できませんでした。しばらくしてから再試行してください。エラーが解決しない場合は、サポートにお問い合わせください。'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + '問題が発生したようです。しばらくしてから再試行してください。エラーが解決しない場合は、サポートチームにお問い合わせください。'; + + @override + String get error => 'エラー'; + + @override + String get ok => 'OK'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'サポートに問い合わせ'; + + @override + String get emailYourLogs => 'ログをメールで送信'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'ログを以下のアドレスに送信してください \n$toEmail'; + } + + @override + String get copyEmailAddress => 'メールアドレスをコピー'; + + @override + String get exportLogs => 'ログのエクスポート'; + + @override + String get cancel => 'キャンセル'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'バグを報告'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return '$endpoint に接続しました'; + } + + @override + String get save => '保存'; + + @override + String get send => '送信'; + + @override + String get saveOrSendDescription => + 'これをストレージ (デフォルトではダウンロードフォルダ) に保存しますか、もしくは他のアプリに送信しますか?'; + + @override + String get saveOnlyDescription => 'これをストレージに保存しますか? (デフォルトではダウンロードフォルダに保存)'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'E メール'; + + @override + String get verify => '認証'; + + @override + String get invalidEmailTitle => 'メールアドレスが無効です'; + + @override + String get invalidEmailMessage => '有効なメールアドレスを入力して下さい'; + + @override + String get pleaseWait => 'お待ちください...'; + + @override + String get verifyPassword => 'パスワードを確認'; + + @override + String get incorrectPasswordTitle => 'パスワードが正しくありません'; + + @override + String get pleaseTryAgain => '再度お試しください'; + + @override + String get enterPassword => 'パスワードを入力'; + + @override + String get enterYourPasswordHint => 'パスワードを入力してください'; + + @override + String get activeSessions => 'アクティブセッション'; + + @override + String get oops => 'おっと'; + + @override + String get somethingWentWrongPleaseTryAgain => '問題が発生しました、再試行してください'; + + @override + String get thisWillLogYouOutOfThisDevice => 'このデバイスからログアウトします!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => '以下のデバイスからログアウトします:'; + + @override + String get terminateSession => 'セッションを終了しますか?'; + + @override + String get terminate => '終了'; + + @override + String get thisDevice => 'このデバイス'; + + @override + String get createAccount => 'アカウント作成'; + + @override + String get weakStrength => '脆弱'; + + @override + String get moderateStrength => 'まあまあ'; + + @override + String get strongStrength => '強力'; + + @override + String get deleteAccount => 'アカウント削除'; + + @override + String get deleteAccountQuery => 'ご不便をおかけして申し訳ありません。なにか問題が発生していますか?'; + + @override + String get yesSendFeedbackAction => 'はい、フィードバックを送信します'; + + @override + String get noDeleteAccountAction => 'いいえ、アカウントを削除します'; + + @override + String get initiateAccountDeleteTitle => 'アカウントの削除を開始するためには認証が必要です'; + + @override + String get confirmAccountDeleteTitle => 'アカウントの削除に同意'; + + @override + String get confirmAccountDeleteMessage => + 'このアカウントは他のEnteアプリも使用している場合はそれらにも紐づけされています。\nすべてのEnteアプリでアップロードされたデータは削除され、アカウントは完全に削除されます。'; + + @override + String get delete => '削除'; + + @override + String get createNewAccount => '新規アカウント作成'; + + @override + String get password => 'パスワード'; + + @override + String get confirmPassword => 'パスワードの確認'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'パスワードの強度: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Ente についてどのようにお聞きになりましたか?(任意)'; + + @override + String get hearUsExplanation => + '私たちはアプリのインストールを追跡していません。私たちをお知りになった場所を教えてください!'; + + @override + String get signUpTerms => + '利用規約プライバシー ポリシーに同意します'; + + @override + String get termsOfServicesTitle => '利用規約'; + + @override + String get privacyPolicyTitle => 'プライバシーポリシー'; + + @override + String get ackPasswordLostWarning => + '私のデータはエンドツーエンドで暗号化されるため、パスワードを紛失した場合、データが失われる可能性があることを理解しています。'; + + @override + String get encryption => '暗号化'; + + @override + String get logInLabel => 'ログイン'; + + @override + String get welcomeBack => 'おかえりなさい!'; + + @override + String get loginTerms => + 'ログインをクリックする場合、利用規約およびプライバシー ポリシーに同意するものとします。'; + + @override + String get noInternetConnection => 'インターネット接続なし'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'インターネット接続を確認して、再試行してください。'; + + @override + String get verificationFailedPleaseTryAgain => '確認に失敗しました、再試行してください'; + + @override + String get recreatePasswordTitle => 'パスワードを再設定'; + + @override + String get recreatePasswordBody => + '現在のデバイスはパスワードを確認するのには不十分ですが、すべてのデバイスで動作するように再生成することはできます。\n\n回復キーを使用してログインし、パスワードを再生成してください(ご希望の場合は同じものを再度使用できます)。'; + + @override + String get useRecoveryKey => '回復キーを使用'; + + @override + String get forgotPassword => 'パスワードを忘れた場合'; + + @override + String get changeEmail => 'メールアドレスを変更'; + + @override + String get verifyEmail => 'メールアドレス認証'; + + @override + String weHaveSendEmailTo(String email) { + return '$email にメールを送信しました'; + } + + @override + String get toResetVerifyEmail => 'パスワードをリセットするには、メールの確認を先に行ってください。'; + + @override + String get checkInboxAndSpamFolder => '受信トレイ(および迷惑メール)を確認して認証を完了してください'; + + @override + String get tapToEnterCode => 'タップしてコードを入力'; + + @override + String get sendEmail => 'メール送信'; + + @override + String get resendEmail => 'メールを再送信'; + + @override + String get passKeyPendingVerification => '認証はまだ保留中です'; + + @override + String get loginSessionExpired => 'セッションの有効期限が切れました'; + + @override + String get loginSessionExpiredDetails => 'セッションの有効期限が切れました。再度ログインしてください。'; + + @override + String get passkeyAuthTitle => 'パスキー認証'; + + @override + String get waitingForVerification => '認証を待っています...'; + + @override + String get tryAgain => '再試行'; + + @override + String get checkStatus => 'ステータスの確認'; + + @override + String get loginWithTOTP => 'TOTPでログイン'; + + @override + String get recoverAccount => 'アカウントを回復'; + + @override + String get setPasswordTitle => 'パスワードの設定'; + + @override + String get changePasswordTitle => 'パスワードの変更'; + + @override + String get resetPasswordTitle => 'パスワードのリセット'; + + @override + String get encryptionKeys => '暗号鍵'; + + @override + String get enterPasswordToEncrypt => 'データの暗号化に使用するパスワードを入力してください'; + + @override + String get enterNewPasswordToEncrypt => 'データの暗号化に使用する新しいパスワードを入力してください'; + + @override + String get passwordWarning => + '私たちはこのパスワードを保存していないので、あなたがそれを忘れた場合に私たちがあなたのデータを代わりに復号することはできません'; + + @override + String get howItWorks => '動作の仕組み'; + + @override + String get generatingEncryptionKeys => '暗号鍵を生成中...'; + + @override + String get passwordChangedSuccessfully => 'パスワードを変更しました'; + + @override + String get signOutFromOtherDevices => '他のデバイスからサインアウトする'; + + @override + String get signOutOtherBody => + '他の誰かがあなたのパスワードを知っている可能性があると判断した場合は、あなたのアカウントを使用している他のすべてのデバイスから強制的にサインアウトできます。'; + + @override + String get signOutOtherDevices => '他のデバイスからサインアウトする'; + + @override + String get doNotSignOut => 'サインアウトしない'; + + @override + String get generatingEncryptionKeysTitle => '暗号化鍵を生成中...'; + + @override + String get continueLabel => '続行'; + + @override + String get insecureDevice => '安全ではないデバイス'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + '申し訳ありませんが、このデバイスでは安全な鍵を生成できませんでした。\n\n別のデバイスから登録してください。'; + + @override + String get recoveryKeyCopiedToClipboard => '回復キーをクリップボードにコピーしました'; + + @override + String get recoveryKey => '回復キー'; + + @override + String get recoveryKeyOnForgotPassword => + 'パスワードを忘れた場合、データを回復できる唯一の方法がこのキーです。'; + + @override + String get recoveryKeySaveDescription => + '私たちはこのキーを保存しません。この 24 単語のキーを安全な場所に保存してください。'; + + @override + String get doThisLater => '後で行う'; + + @override + String get saveKey => 'キーを保存'; + + @override + String get recoveryKeySaved => 'リカバリキーをダウンロードフォルダに保存しました!'; + + @override + String get noRecoveryKeyTitle => '回復キーがありませんか?'; + + @override + String get twoFactorAuthTitle => '2 要素認証'; + + @override + String get enterCodeHint => '認証アプリに表示された 6 桁のコードを入力してください'; + + @override + String get lostDeviceTitle => 'デバイスを紛失しましたか?'; + + @override + String get enterRecoveryKeyHint => '回復キーを入力'; + + @override + String get recover => '回復'; + + @override + String get loggingOut => 'ログアウト中...'; + + @override + String get immediately => 'すぐに'; + + @override + String get appLock => 'アプリのロック'; + + @override + String get autoLock => '自動ロック'; + + @override + String get noSystemLockFound => 'システムロックが見つかりませんでした'; + + @override + String get deviceLockEnablePreSteps => + '端末のロックを有効にするには、システム設定で端末のパスコードまたは画面ロックを設定してください。'; + + @override + String get appLockDescription => + '端末のデフォルトのロック画面と、PINまたはパスワードを使用したカスタムロック画面を選択します。'; + + @override + String get deviceLock => '生体認証'; + + @override + String get pinLock => 'PIN'; + + @override + String get autoLockFeatureDescription => 'アプリをバックグラウンドでロックするまでの時間'; + + @override + String get hideContent => '内容を非表示'; + + @override + String get hideContentDescriptionAndroid => 'アプリの内容を非表示にし、スクリーンショットを無効にします'; + + @override + String get hideContentDescriptioniOS => 'アプリを切り替えた際に、アプリの内容を非表示にします'; + + @override + String get tooManyIncorrectAttempts => '間違った回数が多すぎます'; + + @override + String get tapToUnlock => 'タップして解除'; + + @override + String get areYouSureYouWantToLogout => '本当にログアウトしてよろしいですか?'; + + @override + String get yesLogout => 'はい、ログアウトします'; + + @override + String get authToViewSecrets => '秘密鍵を閲覧するためには認証が必要です'; + + @override + String get next => '次へ'; + + @override + String get setNewPassword => '新しいパスワードを設定'; + + @override + String get enterPin => 'PINを入力してください'; + + @override + String get setNewPin => '新しいPINを設定'; + + @override + String get confirm => '確認'; + + @override + String get reEnterPassword => 'パスワードを再入力してください'; + + @override + String get reEnterPin => 'PINを再入力してください'; + + @override + String get androidBiometricHint => '本人を確認する'; + + @override + String get androidBiometricNotRecognized => '認識できません。再試行してください。'; + + @override + String get androidBiometricSuccess => '成功'; + + @override + String get androidCancelButton => 'キャンセル'; + + @override + String get androidSignInTitle => '認証が必要です'; + + @override + String get androidBiometricRequiredTitle => '生体認証が必要です'; + + @override + String get androidDeviceCredentialsRequiredTitle => 'デバイスの認証情報が必要です'; + + @override + String get androidDeviceCredentialsSetupDescription => 'デバイスの認証情報が必要です'; + + @override + String get goToSettings => '設定を開く'; + + @override + String get androidGoToSettingsDescription => + '生体認証がデバイスで設定されていません。生体認証を追加するには、\"設定 > セキュリティ\"を開いてください。'; + + @override + String get iOSLockOut => '生体認証が無効化されています。画面をロック・ロック解除して生体認証を有効化してください。'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'メールアドレスはすでに登録されています。'; + + @override + String get emailNotRegistered => 'メールアドレスはまだ登録されていません。'; + + @override + String get thisEmailIsAlreadyInUse => 'このアドレスは既に使用されています'; + + @override + String emailChangedTo(String newEmail) { + return 'メールアドレスが $newEmail に変更されました'; + } + + @override + String get authenticationFailedPleaseTryAgain => '認証に失敗しました、再試行してください'; + + @override + String get authenticationSuccessful => '認証に成功しました!'; + + @override + String get sessionExpired => 'セッションが失効しました'; + + @override + String get incorrectRecoveryKey => '不正な回復キー'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => '入力された回復キーは正しくありません'; + + @override + String get twofactorAuthenticationSuccessfullyReset => '2 要素認証は正常にリセットされました'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => '確認コードが失効しました'; + + @override + String get incorrectCode => '不正なコード'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + '申し訳ありませんが、入力されたコードは正しくありません'; + + @override + String get developerSettings => '開発者向け設定'; + + @override + String get serverEndpoint => 'サーバーエンドポイント'; + + @override + String get invalidEndpoint => '無効なエンドポイントです'; + + @override + String get invalidEndpointMessage => + '入力されたエンドポイントは無効です。有効なエンドポイントを入力して再試行してください。'; + + @override + String get endpointUpdatedMessage => 'エンドポイントの更新に成功しました'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_ka.dart b/mobile/packages/strings/lib/l10n/strings_localizations_ka.dart new file mode 100644 index 0000000000..3470bb7b33 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_ka.dart @@ -0,0 +1,629 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Georgian (`ka`). +class StringsLocalizationsKa extends StringsLocalizations { + StringsLocalizationsKa([String locale = 'ka']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'Error'; + + @override + String get ok => 'Ok'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'მხარდაჭერის გუნდთან დაკავშირება'; + + @override + String get emailYourLogs => 'Email your logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Please send the logs to \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copy email address'; + + @override + String get exportLogs => 'Export logs'; + + @override + String get cancel => 'გაუქმება'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'პრობლემის შესახებ შეტყობინება'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'Save'; + + @override + String get send => 'Send'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'ელექტრონული ფოსტა'; + + @override + String get verify => 'Verify'; + + @override + String get invalidEmailTitle => 'Invalid email address'; + + @override + String get invalidEmailMessage => 'Please enter a valid email address.'; + + @override + String get pleaseWait => 'გთხოვთ, დაელოდოთ...'; + + @override + String get verifyPassword => 'პაროლის დასტური'; + + @override + String get incorrectPasswordTitle => 'არასწორი პაროლი'; + + @override + String get pleaseTryAgain => 'გთხოვთ, სცადოთ ხელახლა'; + + @override + String get enterPassword => 'Enter password'; + + @override + String get enterYourPasswordHint => 'Enter your password'; + + @override + String get activeSessions => 'Active sessions'; + + @override + String get oops => 'Oops'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Something went wrong, please try again'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'Terminate session?'; + + @override + String get terminate => 'Terminate'; + + @override + String get thisDevice => 'This device'; + + @override + String get createAccount => 'Create account'; + + @override + String get weakStrength => 'Weak'; + + @override + String get moderateStrength => 'Moderate'; + + @override + String get strongStrength => 'Strong'; + + @override + String get deleteAccount => 'Delete account'; + + @override + String get deleteAccountQuery => + 'We\'ll be sorry to see you go. Are you facing some issue?'; + + @override + String get yesSendFeedbackAction => 'Yes, send feedback'; + + @override + String get noDeleteAccountAction => 'No, delete account'; + + @override + String get initiateAccountDeleteTitle => + 'Please authenticate to initiate account deletion'; + + @override + String get confirmAccountDeleteTitle => 'Confirm account deletion'; + + @override + String get 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.'; + + @override + String get delete => 'წაშლა'; + + @override + String get createNewAccount => 'Create new account'; + + @override + String get password => 'Password'; + + @override + String get confirmPassword => 'Confirm password'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Password strength: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => + 'როგორ შეიტყვეთ Ente-ს შესახებ? (არასავალდებულო)'; + + @override + String get hearUsExplanation => + 'ჩვენ არ ვაკონტროლებთ აპლიკაციის ინსტალაციას. სასარგებლო იქნებოდა, თუ გვეტყოდით, სად გვიპოვეთ!'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Terms'; + + @override + String get privacyPolicyTitle => 'Privacy Policy'; + + @override + String get ackPasswordLostWarning => + 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'; + + @override + String get encryption => 'Encryption'; + + @override + String get logInLabel => 'Log in'; + + @override + String get welcomeBack => 'კეთილი იყოს თქვენი დაბრუნება!'; + + @override + String get loginTerms => + 'By clicking log in, I agree to the terms of service and privacy policy'; + + @override + String get noInternetConnection => 'ინტერნეტთან კავშირი არ არის'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'გთხოვთ, შეამოწმოთ თქვენი ინტერნეტ კავშირი და სცადოთ ხელახლა.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Recreate password'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'Use recovery key'; + + @override + String get forgotPassword => 'Forgot password'; + + @override + String get changeEmail => 'ელექტრონული ფოსტის შეცვლა'; + + @override + String get verifyEmail => 'Verify email'; + + @override + String weHaveSendEmailTo(String email) { + return 'We have sent a mail to $email'; + } + + @override + String get toResetVerifyEmail => + 'To reset your password, please verify your email first.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Tap to enter code'; + + @override + String get sendEmail => 'Send email'; + + @override + String get resendEmail => 'Resend email'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Session expired'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Try again'; + + @override + String get checkStatus => 'Check status'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Recover account'; + + @override + String get setPasswordTitle => 'Set password'; + + @override + String get changePasswordTitle => 'Change password'; + + @override + String get resetPasswordTitle => 'Reset password'; + + @override + String get encryptionKeys => 'Encryption keys'; + + @override + String get enterPasswordToEncrypt => + 'Enter a password we can use to encrypt your data'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'How it works'; + + @override + String get generatingEncryptionKeys => 'Generating encryption keys...'; + + @override + String get passwordChangedSuccessfully => 'Password changed successfully'; + + @override + String get signOutFromOtherDevices => 'ყველა მოწყობილობიდან გამოსვლა'; + + @override + String get signOutOtherBody => + 'თუ ფიქრობთ, რომ ვინმემ შესაძლოა იცოდეს თქვენი პაროლი, შეგიძლიათ ყველა მოწყობილობაზე იძულებითი გამოსვლა, რომელიც იყენებს თქვენს ანგარიშს.'; + + @override + String get signOutOtherDevices => 'სხვა მოწყობილობებიდან გამოსვლა'; + + @override + String get doNotSignOut => 'არ მოხდეს გამოსვლა'; + + @override + String get generatingEncryptionKeysTitle => + 'მიმდინარეობს დაშიფრვის გასაღებების გენერირება...'; + + @override + String get continueLabel => 'Continue'; + + @override + String get insecureDevice => 'Insecure device'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => 'Recovery key copied to clipboard'; + + @override + String get recoveryKey => 'აღდგენის კოდი'; + + @override + String get recoveryKeyOnForgotPassword => + 'If you forget your password, the only way you can recover your data is with this key.'; + + @override + String get recoveryKeySaveDescription => + 'We don\'t store this key, please save this 24 word key in a safe place.'; + + @override + String get doThisLater => 'Do this later'; + + @override + String get saveKey => 'Save key'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'No recovery key?'; + + @override + String get twoFactorAuthTitle => 'Two-factor authentication'; + + @override + String get enterCodeHint => + 'Enter the 6-digit code from\nyour authenticator app'; + + @override + String get lostDeviceTitle => 'Lost device?'; + + @override + String get enterRecoveryKeyHint => 'Enter your recovery key'; + + @override + String get recover => 'Recover'; + + @override + String get loggingOut => 'მიმდინარეობს გამოსვლა...'; + + @override + String get immediately => 'Immediately'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Hide content'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Tap to unlock'; + + @override + String get areYouSureYouWantToLogout => 'Are you sure you want to logout?'; + + @override + String get yesLogout => 'Yes, logout'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'Enter PIN'; + + @override + String get setNewPin => 'Set new PIN'; + + @override + String get confirm => 'Confirm'; + + @override + String get reEnterPassword => 'Re-enter password'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verify identity'; + + @override + String get androidBiometricNotRecognized => + 'ამოცნობა ვერ მოხერხდა. გთხოვთ, სცადოთ ხელახლა.'; + + @override + String get androidBiometricSuccess => 'წარმატებით'; + + @override + String get androidCancelButton => 'გაუქმება'; + + @override + String get androidSignInTitle => 'საჭიროა აუთენთიფიკაცია'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Go to settings'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => 'This email is already in use'; + + @override + String emailChangedTo(String newEmail) { + return 'Email changed to $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'სესიის დრო ამოიწურა'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Incorrect code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_km.dart b/mobile/packages/strings/lib/l10n/strings_localizations_km.dart new file mode 100644 index 0000000000..e918845c22 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_km.dart @@ -0,0 +1,626 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Khmer Central Khmer (`km`). +class StringsLocalizationsKm extends StringsLocalizations { + StringsLocalizationsKm([String locale = 'km']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'មានកំហុស'; + + @override + String get ok => 'យល់ព្រម'; + + @override + String get faq => 'សំនួរ-ចម្លើយ'; + + @override + String get contactSupport => 'ទំនាក់ទំនងសេវា'; + + @override + String get emailYourLogs => 'Email your logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Please send the logs to \n$toEmail'; + } + + @override + String get copyEmailAddress => 'ចម្លង​អាសយដ្ឋាន​អ៊ីមែល'; + + @override + String get exportLogs => 'នាំចេញកំណត់ហេតុ'; + + @override + String get cancel => 'បោះបង់'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Report a bug'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'រក្សាទុក'; + + @override + String get send => 'ផ្ញើរចេញ'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'អ៊ីម៉ែល'; + + @override + String get verify => 'ផ្ទៀងផ្ទាត់'; + + @override + String get invalidEmailTitle => 'អ៊ីម៉ែលនេះមិនត្រឹមត្រូវទេ'; + + @override + String get invalidEmailMessage => 'Please enter a valid email address.'; + + @override + String get pleaseWait => 'Please wait...'; + + @override + String get verifyPassword => 'ផ្ទៀងផ្ទាត់ពាក្យសម្ងាត់'; + + @override + String get incorrectPasswordTitle => 'Incorrect password'; + + @override + String get pleaseTryAgain => 'សូមសាកល្បងម្តងទៀត'; + + @override + String get enterPassword => 'វាយបញ្ចូល​ពាក្យ​សម្ងាត់'; + + @override + String get enterYourPasswordHint => 'Enter your password'; + + @override + String get activeSessions => 'សកម្មភាពគណនី'; + + @override + String get oops => 'Oops'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'មាន​អ្វីមួយ​មិន​ប្រក្រតី។ សូម​ព្យាយាម​ម្តង​ទៀត'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'កាត់់ផ្តាច់សកម្មភាពគណនីនេះ?'; + + @override + String get terminate => 'កាត់ផ្តាច់'; + + @override + String get thisDevice => 'ទូរស័ព្ទនេះ'; + + @override + String get createAccount => 'បង្កើតគណនី'; + + @override + String get weakStrength => 'ខ្សោយ'; + + @override + String get moderateStrength => 'មធ្យម'; + + @override + String get strongStrength => 'ខ្លាំង'; + + @override + String get deleteAccount => 'Delete account'; + + @override + String get deleteAccountQuery => + 'We\'ll be sorry to see you go. Are you facing some issue?'; + + @override + String get yesSendFeedbackAction => 'Yes, send feedback'; + + @override + String get noDeleteAccountAction => 'No, delete account'; + + @override + String get initiateAccountDeleteTitle => + 'Please authenticate to initiate account deletion'; + + @override + String get confirmAccountDeleteTitle => 'Confirm account deletion'; + + @override + String get 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.'; + + @override + String get delete => 'Delete'; + + @override + String get createNewAccount => 'បង្កើតគណនីថ្មី'; + + @override + String get password => 'ពាក្យសម្ងាត់'; + + @override + String get confirmPassword => 'បញ្ជាក់ពាក្យសម្ងាត់'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'កម្លាំងពាក្យសម្ងាត់: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'We don\'t track app installs. It\'d help if you told us where you found us!'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Terms'; + + @override + String get privacyPolicyTitle => 'គោលការណ៍​ភាព​ឯកជន'; + + @override + String get ackPasswordLostWarning => + 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'; + + @override + String get encryption => 'Encryption'; + + @override + String get logInLabel => 'ចូលគណនី'; + + @override + String get welcomeBack => 'Welcome back!'; + + @override + String get loginTerms => + 'By clicking log in, I agree to the terms of service and privacy policy'; + + @override + String get noInternetConnection => 'No internet connection'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Please check your internet connection and try again.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Recreate password'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'Use recovery key'; + + @override + String get forgotPassword => 'ភ្លេចកូដសម្ងាត់'; + + @override + String get changeEmail => 'Change email'; + + @override + String get verifyEmail => 'Verify email'; + + @override + String weHaveSendEmailTo(String email) { + return 'We have sent a mail to $email'; + } + + @override + String get toResetVerifyEmail => + 'To reset your password, please verify your email first.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Tap to enter code'; + + @override + String get sendEmail => 'Send email'; + + @override + String get resendEmail => 'ផ្ញើអ៊ីមែលម្ដងទៀត'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Session expired'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Try again'; + + @override + String get checkStatus => 'ពិនិត្យស្ថានភាព'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Recover account'; + + @override + String get setPasswordTitle => 'Set password'; + + @override + String get changePasswordTitle => 'Change password'; + + @override + String get resetPasswordTitle => 'ផ្ដាស់ប្ដូរពាក្សសម្ងាត់'; + + @override + String get encryptionKeys => 'Encryption keys'; + + @override + String get enterPasswordToEncrypt => + 'Enter a password we can use to encrypt your data'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'How it works'; + + @override + String get generatingEncryptionKeys => 'Generating encryption keys...'; + + @override + String get passwordChangedSuccessfully => 'Password changed successfully'; + + @override + String get signOutFromOtherDevices => 'Sign out from other devices'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'Sign out other devices'; + + @override + String get doNotSignOut => 'Do not sign out'; + + @override + String get generatingEncryptionKeysTitle => 'Generating encryption keys...'; + + @override + String get continueLabel => 'Continue'; + + @override + String get insecureDevice => 'Insecure device'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => 'Recovery key copied to clipboard'; + + @override + String get recoveryKey => 'Recovery key'; + + @override + String get recoveryKeyOnForgotPassword => + 'If you forget your password, the only way you can recover your data is with this key.'; + + @override + String get recoveryKeySaveDescription => + 'We don\'t store this key, please save this 24 word key in a safe place.'; + + @override + String get doThisLater => 'Do this later'; + + @override + String get saveKey => 'Save key'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'No recovery key?'; + + @override + String get twoFactorAuthTitle => 'Two-factor authentication'; + + @override + String get enterCodeHint => + 'Enter the 6-digit code from\nyour authenticator app'; + + @override + String get lostDeviceTitle => 'Lost device?'; + + @override + String get enterRecoveryKeyHint => 'Enter your recovery key'; + + @override + String get recover => 'សង្គ្រោះមកវិញ'; + + @override + String get loggingOut => 'Logging out...'; + + @override + String get immediately => 'ភ្លាមៗ'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'លាក់ពត័មាន'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'ប៉ះដើម្បីដោះសោ'; + + @override + String get areYouSureYouWantToLogout => 'Are you sure you want to logout?'; + + @override + String get yesLogout => 'Yes, logout'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'វាយបញ្ចូល​លេខ​កូដ PIN'; + + @override + String get setNewPin => 'Set new PIN'; + + @override + String get confirm => 'Confirm'; + + @override + String get reEnterPassword => 'បញ្ចូលពាក្យសម្ងាត់ម្តងទៀត'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verify identity'; + + @override + String get androidBiometricNotRecognized => 'Not recognized. Try again.'; + + @override + String get androidBiometricSuccess => 'Success'; + + @override + String get androidCancelButton => 'បោះបង់'; + + @override + String get androidSignInTitle => 'តម្រូវឱ្យមានការបញ្ជាក់ភាពត្រឹមត្រូវ'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Go to settings'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => 'This email is already in use'; + + @override + String emailChangedTo(String newEmail) { + return 'អ៊ីម៉ែលត្រូវបានផ្លាស់ប្តូរទៅ$newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'Session expired'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Incorrect code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart b/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart new file mode 100644 index 0000000000..bdb0abf69e --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_ko.dart @@ -0,0 +1,604 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Korean (`ko`). +class StringsLocalizationsKo extends StringsLocalizations { + StringsLocalizationsKo([String locale = 'ko']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Ente에 접속할 수 없습니다, 네트워크 설정을 확인해주시고 에러가 반복되는 경우 저희 지원 팀에 문의해주세요.'; + + @override + String get networkConnectionRefusedErr => + 'Ente에 접속할 수 없습니다, 잠시 후에 다시 시도해주세요. 에러가 반복되는 경우, 저희 지원 팀에 문의해주세요.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + '뭔가 잘못된 것 같습니다. 잠시 후에 다시 시도해주세요. 에러가 반복되는 경우, 저희 지원 팀에 문의해주세요.'; + + @override + String get error => '에러'; + + @override + String get ok => '확인'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => '지원 문의'; + + @override + String get emailYourLogs => '로그를 이메일로 보내기'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return '이 로그를 $toEmail 쪽으로 보내주세요'; + } + + @override + String get copyEmailAddress => '이메일 주소 복사'; + + @override + String get exportLogs => '로그 내보내기'; + + @override + String get cancel => '취소'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => '버그 제보'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return '$endpoint에 접속됨'; + } + + @override + String get save => '저장'; + + @override + String get send => '보내기'; + + @override + String get saveOrSendDescription => + '이것을 당신의 스토리지 (일반적으로 다운로드 폴더) 에 저장하시겠습니까, 아니면 다른 App으로 전송하시겠습니까?'; + + @override + String get saveOnlyDescription => '이것을 당신의 스토리지 (일반적으로 다운로드 폴더) 에 저장하시겠습니까?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => '이메일'; + + @override + String get verify => '인증'; + + @override + String get invalidEmailTitle => '잘못 된 이메일 주소'; + + @override + String get invalidEmailMessage => '유효한 이메일 주소를 입력해주세요'; + + @override + String get pleaseWait => '잠시만 기다려주세요...'; + + @override + String get verifyPassword => '비밀번호 확인'; + + @override + String get incorrectPasswordTitle => '올바르지 않은 비밀번호'; + + @override + String get pleaseTryAgain => '다시 시도해주세요'; + + @override + String get enterPassword => '암호 입력'; + + @override + String get enterYourPasswordHint => '암호 입력'; + + @override + String get activeSessions => '활성화된 Session'; + + @override + String get oops => '이런!'; + + @override + String get somethingWentWrongPleaseTryAgain => '뭔가 잘못됐습니다, 다시 시도해주세요'; + + @override + String get thisWillLogYouOutOfThisDevice => '이 작업을 하시면 기기에서 로그아웃하게 됩니다!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + '이 작업을 하시면 다음 기기에서 로그아웃하게 됩니다:'; + + @override + String get terminateSession => '세션을 종결하시겠습니까?'; + + @override + String get terminate => '종결'; + + @override + String get thisDevice => '이 기기'; + + @override + String get createAccount => '계정 만들기'; + + @override + String get weakStrength => '약함'; + + @override + String get moderateStrength => '보통'; + + @override + String get strongStrength => '강함'; + + @override + String get deleteAccount => '계정 삭제하기'; + + @override + String get deleteAccountQuery => '떠나신다니 아쉽습니다. 뭔가 문제가 있으셨나요?'; + + @override + String get yesSendFeedbackAction => '네, 피드백을 보냅니다'; + + @override + String get noDeleteAccountAction => '아니오, 계정을 지웁니다'; + + @override + String get initiateAccountDeleteTitle => '계정 삭제 절차를 시작하려면 인증 절차를 거쳐주세요'; + + @override + String get confirmAccountDeleteTitle => '계정 삭제 확인'; + + @override + String get confirmAccountDeleteMessage => + '다른 Ente의 서비스를 이용하고 계시다면, 해당 계정은 모두 연결이 되어있습니다.\n\n모든 Ente 서비스에 업로드 된 당신의 데이터는 삭제 수순에 들어가며, 계정은 불가역적으로 삭제됩니다.'; + + @override + String get delete => '삭제'; + + @override + String get createNewAccount => '새 계정 만들기'; + + @override + String get password => '암호'; + + @override + String get confirmPassword => '암호 확인'; + + @override + String passwordStrength(String passwordStrengthValue) { + return '암호 보안 강도: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Ente에 대해 어떻게 알게 되셨나요? (선택사항)'; + + @override + String get hearUsExplanation => + '저희는 어플 설치 과정을 관찰하지 않습니다. 어디에서 저희를 발견하셨는지 알려주신다면 도움이 될 겁니다!'; + + @override + String get signUpTerms => + '나는 사용자 약관개인정보 취급방침에 동의합니다.'; + + @override + String get termsOfServicesTitle => '약관'; + + @override + String get privacyPolicyTitle => '개인정보 취급 방침'; + + @override + String get ackPasswordLostWarning => + '나는 암호를 분실한 경우, 데이터가 종단 간 암호화되어있기에 데이터를 손실할 수 있음을 이해합니다.'; + + @override + String get encryption => '암호화'; + + @override + String get logInLabel => '로그인'; + + @override + String get welcomeBack => '돌아오신 것을 환영합니다!'; + + @override + String get loginTerms => + '로그인을 누름으로써, 나는 사용자 약관개인정보 취급방침에 동의합니다.'; + + @override + String get noInternetConnection => '인터넷 연결 없음'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + '인터넷 연결을 확인하시고 다시 시도해주세요.'; + + @override + String get verificationFailedPleaseTryAgain => '검증 실패, 다시 시도해주세요'; + + @override + String get recreatePasswordTitle => '암호 다시 생성'; + + @override + String get recreatePasswordBody => + '현재 사용 중인 기기는 암호를 확인하기에 적합하지 않으나, 모든 기기에서 작동하는 방식으로 비밀번호를 다시 생성할 수 있습니다.\n\n복구 키를 사용하여 로그인하고 암호를 다시 생성해주세요. (원하시면 현재 사용 중인 암호와 같은 암호를 다시 사용하실 수 있습니다.)'; + + @override + String get useRecoveryKey => '복구 키 사용'; + + @override + String get forgotPassword => '암호 분실'; + + @override + String get changeEmail => '이메일 변경'; + + @override + String get verifyEmail => '이메일 인증하기'; + + @override + String weHaveSendEmailTo(String email) { + return '$email 쪽으로 메일을 보냈습니다'; + } + + @override + String get toResetVerifyEmail => '암호를 재설정하시려면, 먼저 이메일을 인증해주세요.'; + + @override + String get checkInboxAndSpamFolder => '검증을 위해 메일 보관함을 (또는 스팸 메일 보관함) 확인해주세요'; + + @override + String get tapToEnterCode => '눌러서 코드 입력하기'; + + @override + String get sendEmail => '이메일 보내기'; + + @override + String get resendEmail => '이메일 다시 보내기'; + + @override + String get passKeyPendingVerification => '검증 절차가 마무리되지 않았습니다'; + + @override + String get loginSessionExpired => '세션 만료됨'; + + @override + String get loginSessionExpiredDetails => '세션이 만료되었습니다. 다시 로그인해주세요.'; + + @override + String get passkeyAuthTitle => '패스키 검증'; + + @override + String get waitingForVerification => '검증 대기 중...'; + + @override + String get tryAgain => '다시 시도해주세요'; + + @override + String get checkStatus => '상태 확인'; + + @override + String get loginWithTOTP => 'TOTP로 로그인 하기'; + + @override + String get recoverAccount => '계정 복구'; + + @override + String get setPasswordTitle => '암호 지정'; + + @override + String get changePasswordTitle => '암호 변경'; + + @override + String get resetPasswordTitle => '암호 초기화'; + + @override + String get encryptionKeys => '암호화 키'; + + @override + String get enterPasswordToEncrypt => '데이터 암호화를 위한 암호 입력'; + + @override + String get enterNewPasswordToEncrypt => '데이터 암호화를 위한 새로운 암호 입력'; + + @override + String get passwordWarning => + '저희는 이 암호를 저장하지 않으니, 만약 잊어버리시게 되면, 데이터를 복호화 해드릴 수 없습니다'; + + @override + String get howItWorks => '작동 원리'; + + @override + String get generatingEncryptionKeys => '암호화 키 생성 중...'; + + @override + String get passwordChangedSuccessfully => '암호가 성공적으로 변경되었습니다'; + + @override + String get signOutFromOtherDevices => '다른 기기들에서 로그아웃하기'; + + @override + String get signOutOtherBody => + '다른 사람이 내 암호를 알 수도 있을 거란 의심이 드신다면, 당신의 계정을 사용 중인 다른 모든 기기에서 로그아웃할 수 있습니다.'; + + @override + String get signOutOtherDevices => '다른 기기들을 로그아웃시키기'; + + @override + String get doNotSignOut => '로그아웃 하지 않기'; + + @override + String get generatingEncryptionKeysTitle => '암호화 키를 생성하는 중...'; + + @override + String get continueLabel => '계속'; + + @override + String get insecureDevice => '보안이 허술한 기기'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + '죄송합니다, 이 기기에서 보안 키를 생성할 수 없습니다.\n\n다른 기기에서 계정을 생성해주세요.'; + + @override + String get recoveryKeyCopiedToClipboard => '클립보드에 복구 키 복사 됨'; + + @override + String get recoveryKey => '복구 키'; + + @override + String get recoveryKeyOnForgotPassword => + '암호를 잊어버린 경우, 데이터를 복구하려면 이 키를 이용하는 방법 뿐입니다.'; + + @override + String get recoveryKeySaveDescription => + '저희는 이 키를 보관하지 않으니, 여기에 있는 24 단어로 구성된 키를 안전하게 보관해주세요.'; + + @override + String get doThisLater => '나중에 하기'; + + @override + String get saveKey => '키 저장하기'; + + @override + String get recoveryKeySaved => '다운로드 폴더에 복구 키가 저장되었습니다!'; + + @override + String get noRecoveryKeyTitle => '복구 키가 없으세요?'; + + @override + String get twoFactorAuthTitle => '2단계 인증'; + + @override + String get enterCodeHint => 'Authenticator에 적힌 6 자리 코드를 입력해주세요'; + + @override + String get lostDeviceTitle => '기기를 잃어버리셨나요?'; + + @override + String get enterRecoveryKeyHint => '복구 키를 입력하세요'; + + @override + String get recover => '복구'; + + @override + String get loggingOut => '로그아웃하는 중...'; + + @override + String get immediately => '즉시'; + + @override + String get appLock => '어플 잠금'; + + @override + String get autoLock => '자동 잠금'; + + @override + String get noSystemLockFound => '시스템 잠금 찾을 수 없음'; + + @override + String get deviceLockEnablePreSteps => + '기기 잠금을 활성화하시려면, 기기의 암호를 만들거나 시스템 설정에서 화면 잠금을 설정해주세요.'; + + @override + String get appLockDescription => + '기본 잠금 화면이나, PIN 번호나 암호를 사용한 사용자 설정 잠금 화면 중에 선택하세요.'; + + @override + String get deviceLock => '기기 잠금'; + + @override + String get pinLock => 'Pin 잠금'; + + @override + String get autoLockFeatureDescription => 'Background로 App 넘어가고 잠기기까지 걸리는 시간'; + + @override + String get hideContent => '내용 숨기기'; + + @override + String get hideContentDescriptionAndroid => + 'App 전환 화면에서 App의 내용을 숨기고 Screenshot 촬영을 막습니다'; + + @override + String get hideContentDescriptioniOS => 'App 전환 화면에서 App의 내용을 숨깁니다'; + + @override + String get tooManyIncorrectAttempts => '잘못된 시도 횟수가 너무 많습니다'; + + @override + String get tapToUnlock => '잠금을 해제하려면 누르세요'; + + @override + String get areYouSureYouWantToLogout => '로그아웃 하시겠습니까?'; + + @override + String get yesLogout => '네, 로그아웃하기'; + + @override + String get authToViewSecrets => '비밀 부분을 확인하려면 인증 절차를 거쳐주세요'; + + @override + String get next => '다음'; + + @override + String get setNewPassword => '새 비밀번호 설정'; + + @override + String get enterPin => 'PIN 번호 입력'; + + @override + String get setNewPin => '새 PIN 번호 설정'; + + @override + String get confirm => '확인'; + + @override + String get reEnterPassword => '암호 다시 입력'; + + @override + String get reEnterPin => '핀 다시 입력'; + + @override + String get androidBiometricHint => '신원 확인'; + + @override + String get androidBiometricNotRecognized => '식별할 수 없습니다. 다시 시도해주세요.'; + + @override + String get androidBiometricSuccess => '성공'; + + @override + String get androidCancelButton => '취소'; + + @override + String get androidSignInTitle => '인증 필요'; + + @override + String get androidBiometricRequiredTitle => '생체인증 필요'; + + @override + String get androidDeviceCredentialsRequiredTitle => '장치 자격 증명 필요'; + + @override + String get androidDeviceCredentialsSetupDescription => '장치 자격 증명 필요'; + + @override + String get goToSettings => '설정으로 가기'; + + @override + String get androidGoToSettingsDescription => + '기기에 생체인증이 설정되어있지 않습니다. \'설정 > 보안\'으로 가셔서 생체인증을 설정해주세요.'; + + @override + String get iOSLockOut => '생체인증에 문제가 있습니다. 활성화하시려면 기기를 잠궜다가 다시 풀어주세요.'; + + @override + String get iOSOkButton => '확인'; + + @override + String get emailAlreadyRegistered => '이미 등록된 이메일입니다.'; + + @override + String get emailNotRegistered => '등록되지 않은 이메일입니다.'; + + @override + String get thisEmailIsAlreadyInUse => '이 이메일은 이미 사용 중입니다'; + + @override + String emailChangedTo(String newEmail) { + return '$newEmail로 메일이 변경되었습니다'; + } + + @override + String get authenticationFailedPleaseTryAgain => '인증절차 실패, 다시 시도해주세요'; + + @override + String get authenticationSuccessful => '인증 성공!'; + + @override + String get sessionExpired => '세션 만료'; + + @override + String get incorrectRecoveryKey => '잘못 된 복구 키'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => '입력하신 복구 키가 맞지 않습니다'; + + @override + String get twofactorAuthenticationSuccessfullyReset => '2FA가 성공적으로 초기화되었습니다'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => '검증 코드의 유효시간이 경과하였습니다'; + + @override + String get incorrectCode => '잘못된 코드'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => '죄송합니다, 입력하신 코드가 맞지 않습니다'; + + @override + String get developerSettings => '개발자 설정'; + + @override + String get serverEndpoint => '서버 엔드포인트'; + + @override + String get invalidEndpoint => '유효하지 않은 엔드포인트'; + + @override + String get invalidEndpointMessage => + '죄송합니다, 입력하신 엔드포인트가 유효하지 않습니다. 유효한 엔드포인트를 입력하시고 다시 시도해주세요.'; + + @override + String get endpointUpdatedMessage => '엔드포인트가 성공적으로 업데이트됨'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart b/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart new file mode 100644 index 0000000000..67514db54f --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_lt.dart @@ -0,0 +1,630 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Lithuanian (`lt`). +class StringsLocalizationsLt extends StringsLocalizations { + StringsLocalizationsLt([String locale = 'lt']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Nepavyksta prisijungti prie „Ente“. Patikrinkite tinklo nustatymus ir susisiekite su palaikymo komanda, jei klaida tęsiasi.'; + + @override + String get networkConnectionRefusedErr => + 'Nepavyksta prisijungti prie „Ente“. Bandykite dar kartą po kurio laiko. Jei klaida tęsiasi, susisiekite su palaikymo komanda.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Atrodo, kad kažkas nutiko ne taip. Bandykite dar kartą po kurio laiko. Jei klaida tęsiasi, susisiekite su mūsų palaikymo komanda.'; + + @override + String get error => 'Klaida'; + + @override + String get ok => 'Gerai'; + + @override + String get faq => 'DUK'; + + @override + String get contactSupport => 'Susisiekti su palaikymo komanda'; + + @override + String get emailYourLogs => 'Atsiųskite žurnalus el. laišku'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Siųskite žurnalus adresu\n$toEmail'; + } + + @override + String get copyEmailAddress => 'Kopijuoti el. pašto adresą'; + + @override + String get exportLogs => 'Eksportuoti žurnalus'; + + @override + String get cancel => 'Atšaukti'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Pranešti apie riktą'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Prijungta prie $endpoint'; + } + + @override + String get save => 'Išsaugoti'; + + @override + String get send => 'Siųsti'; + + @override + String get saveOrSendDescription => + 'Ar norite tai išsaugoti saugykloje (pagal numatytuosius nustatymus – atsisiuntimų aplanke), ar siųsti į kitas programas?'; + + @override + String get saveOnlyDescription => + 'Ar norite tai išsaugoti savo saugykloje (pagal numatytuosius nustatymus – atsisiuntimų aplanke)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'El. paštas'; + + @override + String get verify => 'Patvirtinti'; + + @override + String get invalidEmailTitle => 'Netinkamas el. pašto adresas'; + + @override + String get invalidEmailMessage => 'Įveskite tinkamą el. pašto adresą.'; + + @override + String get pleaseWait => 'Palaukite...'; + + @override + String get verifyPassword => 'Patvirtinkite slaptažodį'; + + @override + String get incorrectPasswordTitle => 'Neteisingas slaptažodis.'; + + @override + String get pleaseTryAgain => 'Bandykite dar kartą.'; + + @override + String get enterPassword => 'Įveskite slaptažodį'; + + @override + String get enterYourPasswordHint => 'Įveskite savo slaptažodį'; + + @override + String get activeSessions => 'Aktyvūs seansai'; + + @override + String get oops => 'Ups'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Kažkas nutiko ne taip. Bandykite dar kartą.'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Tai jus atjungs nuo šio įrenginio.'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Tai jus atjungs nuo toliau nurodyto įrenginio:'; + + @override + String get terminateSession => 'Baigti seansą?'; + + @override + String get terminate => 'Baigti'; + + @override + String get thisDevice => 'Šis įrenginys'; + + @override + String get createAccount => 'Kurti paskyrą'; + + @override + String get weakStrength => 'Silpna'; + + @override + String get moderateStrength => 'Vidutinė'; + + @override + String get strongStrength => 'Stipri'; + + @override + String get deleteAccount => 'Ištrinti paskyrą'; + + @override + String get deleteAccountQuery => + 'Apgailestausime, kad išeinate. Ar susiduriate su kažkokiomis problemomis?'; + + @override + String get yesSendFeedbackAction => 'Taip, siųsti atsiliepimą'; + + @override + String get noDeleteAccountAction => 'Ne, ištrinti paskyrą'; + + @override + String get initiateAccountDeleteTitle => + 'Nustatykite tapatybę, kad pradėtumėte paskyros ištrynimą'; + + @override + String get confirmAccountDeleteTitle => 'Patvirtinkite paskyros ištrynimą'; + + @override + String get confirmAccountDeleteMessage => + 'Ši paskyra susieta su kitomis „Ente“ programomis, jei jas naudojate.\n\nJūsų įkelti duomenys per visas „Ente“ programas bus planuojama ištrinti, o jūsų paskyra bus ištrinta negrįžtamai.'; + + @override + String get delete => 'Ištrinti'; + + @override + String get createNewAccount => 'Kurti naują paskyrą'; + + @override + String get password => 'Slaptažodis'; + + @override + String get confirmPassword => 'Patvirtinkite slaptažodį'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Slaptažodžio stiprumas: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Kaip išgirdote apie „Ente“? (nebūtina)'; + + @override + String get hearUsExplanation => + 'Mes nesekame programų diegimų. Mums padėtų, jei pasakytumėte, kur mus radote.'; + + @override + String get signUpTerms => + 'Sutinku su paslaugų sąlygomis ir privatumo politika'; + + @override + String get termsOfServicesTitle => 'Sąlygos'; + + @override + String get privacyPolicyTitle => 'Privatumo politika'; + + @override + String get ackPasswordLostWarning => + 'Suprantu, kad jei prarasiu slaptažodį, galiu prarasti savo duomenis, kadangi duomenys yra visapusiškai užšifruota.'; + + @override + String get encryption => 'Šifravimas'; + + @override + String get logInLabel => 'Prisijungti'; + + @override + String get welcomeBack => 'Sveiki sugrįžę!'; + + @override + String get loginTerms => + 'Spustelėjus Prisijungti sutinku su paslaugų sąlygomis ir privatumo politika'; + + @override + String get noInternetConnection => 'Nėra interneto ryšio'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Patikrinkite savo interneto ryšį ir bandykite dar kartą.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Patvirtinimas nepavyko. Bandykite dar kartą.'; + + @override + String get recreatePasswordTitle => 'Iš naujo sukurti slaptažodį'; + + @override + String get recreatePasswordBody => + 'Dabartinis įrenginys nėra pakankamai galingas, kad patvirtintų jūsų slaptažodį, bet mes galime iš naujo sugeneruoti taip, kad jis veiktų su visais įrenginiais.\n\nPrisijunkite naudodami atkūrimo raktą ir sugeneruokite iš naujo slaptažodį (jei norite, galite vėl naudoti tą patį).'; + + @override + String get useRecoveryKey => 'Naudoti atkūrimo raktą'; + + @override + String get forgotPassword => 'Pamiršau slaptažodį'; + + @override + String get changeEmail => 'Keisti el. paštą'; + + @override + String get verifyEmail => 'Patvirtinti el. paštą'; + + @override + String weHaveSendEmailTo(String email) { + return 'Išsiuntėme laišką adresu $email.'; + } + + @override + String get toResetVerifyEmail => + 'Kad iš naujo nustatytumėte slaptažodį, pirmiausia patvirtinkite savo el. paštą.'; + + @override + String get checkInboxAndSpamFolder => + 'Patikrinkite savo gautieją (ir šlamštą), kad užbaigtumėte patvirtinimą.'; + + @override + String get tapToEnterCode => 'Palieskite, kad įvestumėte kodą'; + + @override + String get sendEmail => 'Siųsti el. laišką'; + + @override + String get resendEmail => 'Iš naujo siųsti el. laišką'; + + @override + String get passKeyPendingVerification => 'Vis dar laukiama patvirtinimo'; + + @override + String get loginSessionExpired => 'Seansas baigėsi'; + + @override + String get loginSessionExpiredDetails => + 'Jūsų seansas baigėsi. Prisijunkite iš naujo.'; + + @override + String get passkeyAuthTitle => 'Slaptarakčio patvirtinimas'; + + @override + String get waitingForVerification => 'Laukiama patvirtinimo...'; + + @override + String get tryAgain => 'Bandyti dar kartą'; + + @override + String get checkStatus => 'Tikrinti būseną'; + + @override + String get loginWithTOTP => 'Prisijungti su TOTP'; + + @override + String get recoverAccount => 'Atkurti paskyrą'; + + @override + String get setPasswordTitle => 'Nustatyti slaptažodį'; + + @override + String get changePasswordTitle => 'Keisti slaptažodį'; + + @override + String get resetPasswordTitle => 'Nustatyti slaptažodį iš naujo'; + + @override + String get encryptionKeys => 'Šifravimo raktai'; + + @override + String get enterPasswordToEncrypt => + 'Įveskite slaptažodį, kurį galime naudoti jūsų duomenims užšifruoti'; + + @override + String get enterNewPasswordToEncrypt => + 'Įveskite naują slaptažodį, kurį galime naudoti jūsų duomenims užšifruoti'; + + @override + String get passwordWarning => + 'Šio slaptažodžio nesaugome, todėl jei jį pamiršite, negalėsime iššifruoti jūsų duomenų'; + + @override + String get howItWorks => 'Kaip tai veikia'; + + @override + String get generatingEncryptionKeys => 'Generuojami šifravimo raktai...'; + + @override + String get passwordChangedSuccessfully => 'Slaptažodis sėkmingai pakeistas'; + + @override + String get signOutFromOtherDevices => 'Atsijungti iš kitų įrenginių'; + + @override + String get signOutOtherBody => + 'Jei manote, kad kas nors gali žinoti jūsų slaptažodį, galite priverstinai atsijungti iš visų kitų įrenginių, naudojančių jūsų paskyrą.'; + + @override + String get signOutOtherDevices => 'Atsijungti kitus įrenginius'; + + @override + String get doNotSignOut => 'Neatsijungti'; + + @override + String get generatingEncryptionKeysTitle => 'Generuojami šifravimo raktai...'; + + @override + String get continueLabel => 'Tęsti'; + + @override + String get insecureDevice => 'Nesaugus įrenginys'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Atsiprašome, šiame įrenginyje nepavyko sugeneruoti saugių raktų.\n\nRegistruokitės iš kito įrenginio.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Nukopijuotas atkūrimo raktas į iškarpinę'; + + @override + String get recoveryKey => 'Atkūrimo raktas'; + + @override + String get recoveryKeyOnForgotPassword => + 'Jei pamiršote slaptažodį, vienintelis būdas atkurti duomenis – naudoti šį raktą.'; + + @override + String get recoveryKeySaveDescription => + 'Šio rakto nesaugome, todėl išsaugokite šį 24 žodžių raktą saugioje vietoje.'; + + @override + String get doThisLater => 'Daryti tai vėliau'; + + @override + String get saveKey => 'Išsaugoti raktą'; + + @override + String get recoveryKeySaved => + 'Atkūrimo raktas išsaugotas atsisiuntimų aplanke.'; + + @override + String get noRecoveryKeyTitle => 'Neturite atkūrimo rakto?'; + + @override + String get twoFactorAuthTitle => 'Dvigubas tapatybės nustatymas'; + + @override + String get enterCodeHint => + 'Įveskite 6 skaitmenų kodą\niš autentifikatoriaus programos'; + + @override + String get lostDeviceTitle => 'Prarastas įrenginys?'; + + @override + String get enterRecoveryKeyHint => 'Įveskite atkūrimo raktą'; + + @override + String get recover => 'Atkurti'; + + @override + String get loggingOut => 'Atsijungiama...'; + + @override + String get immediately => 'Iš karto'; + + @override + String get appLock => 'Programos užraktas'; + + @override + String get autoLock => 'Automatinis užraktas'; + + @override + String get noSystemLockFound => 'Nerastas sistemos užraktas'; + + @override + String get deviceLockEnablePreSteps => + 'Kad įjungtumėte įrenginio užraktą, sistemos nustatymuose nustatykite įrenginio prieigos kodą arba ekrano užraktą.'; + + @override + String get appLockDescription => + 'Pasirinkite tarp numatytojo įrenginio užrakinimo ekrano ir pasirinktinio užrakinimo ekrano su PIN kodu arba slaptažodžiu.'; + + @override + String get deviceLock => 'Įrenginio užraktas'; + + @override + String get pinLock => 'PIN užraktas'; + + @override + String get autoLockFeatureDescription => + 'Laikas, po kurio programa užrakinama perkėlus ją į foną.'; + + @override + String get hideContent => 'Slėpti turinį'; + + @override + String get hideContentDescriptionAndroid => + 'Paslepia programų turinį programų perjungiklyje ir išjungia ekrano kopijas.'; + + @override + String get hideContentDescriptioniOS => + 'Paslepia programos turinį programos perjungiklyje.'; + + @override + String get tooManyIncorrectAttempts => 'Per daug neteisingų bandymų.'; + + @override + String get tapToUnlock => 'Palieskite, kad atrakintumėte'; + + @override + String get areYouSureYouWantToLogout => 'Ar tikrai norite atsijungti?'; + + @override + String get yesLogout => 'Taip, atsijungti'; + + @override + String get authToViewSecrets => + 'Nustatykite tapatybę, kad peržiūrėtumėte savo paslaptis'; + + @override + String get next => 'Toliau'; + + @override + String get setNewPassword => 'Nustatykite naują slaptažodį'; + + @override + String get enterPin => 'Įveskite PIN'; + + @override + String get setNewPin => 'Nustatykite naują PIN'; + + @override + String get confirm => 'Patvirtinti'; + + @override + String get reEnterPassword => 'Įveskite slaptažodį iš naujo'; + + @override + String get reEnterPin => 'Įveskite PIN iš naujo'; + + @override + String get androidBiometricHint => 'Patvirtinkite tapatybę'; + + @override + String get androidBiometricNotRecognized => + 'Neatpažinta. Bandykite dar kartą.'; + + @override + String get androidBiometricSuccess => 'Sėkmė'; + + @override + String get androidCancelButton => 'Atšaukti'; + + @override + String get androidSignInTitle => 'Privalomas tapatybės nustatymas'; + + @override + String get androidBiometricRequiredTitle => 'Privaloma biometrija'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Privalomi įrenginio kredencialai'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Privalomi įrenginio kredencialai'; + + @override + String get goToSettings => 'Eiti į nustatymus'; + + @override + String get androidGoToSettingsDescription => + 'Biometrinis tapatybės nustatymas jūsų įrenginyje nenustatytas. Eikite į Nustatymai > Saugumas ir pridėkite biometrinį tapatybės nustatymą.'; + + @override + String get iOSLockOut => + 'Biometrinis tapatybės nustatymas išjungtas. Kad jį įjungtumėte, užrakinkite ir atrakinkite ekraną.'; + + @override + String get iOSOkButton => 'Gerai'; + + @override + String get emailAlreadyRegistered => 'El. paštas jau užregistruotas.'; + + @override + String get emailNotRegistered => 'El. paštas neregistruotas.'; + + @override + String get thisEmailIsAlreadyInUse => 'Šis el. paštas jau naudojamas.'; + + @override + String emailChangedTo(String newEmail) { + return 'El. paštas pakeistas į $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Tapatybės nustatymas nepavyko. Bandykite dar kartą.'; + + @override + String get authenticationSuccessful => 'Tapatybės nustatymas sėkmingas.'; + + @override + String get sessionExpired => 'Seansas baigėsi'; + + @override + String get incorrectRecoveryKey => 'Neteisingas atkūrimo raktas'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Įvestas atkūrimo raktas yra neteisingas.'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Dvigubas tapatybės nustatymas sėkmingai iš naujo nustatytas.'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Jūsų patvirtinimo kodas nebegaliojantis.'; + + @override + String get incorrectCode => 'Neteisingas kodas'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Atsiprašome, įvestas kodas yra neteisingas.'; + + @override + String get developerSettings => 'Kūrėjo nustatymai'; + + @override + String get serverEndpoint => 'Serverio galutinis taškas'; + + @override + String get invalidEndpoint => 'Netinkamas galutinis taškas'; + + @override + String get invalidEndpointMessage => + 'Atsiprašome, įvestas galutinis taškas netinkamas. Įveskite tinkamą galutinį tašką ir bandykite dar kartą.'; + + @override + String get endpointUpdatedMessage => 'Galutinis taškas sėkmingai atnaujintas'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_lv.dart b/mobile/packages/strings/lib/l10n/strings_localizations_lv.dart new file mode 100644 index 0000000000..d6762e17e9 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_lv.dart @@ -0,0 +1,626 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Latvian (`lv`). +class StringsLocalizationsLv extends StringsLocalizations { + StringsLocalizationsLv([String locale = 'lv']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'Kļūda'; + + @override + String get ok => 'Labi'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'Contact support'; + + @override + String get emailYourLogs => 'Email your logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Please send the logs to \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copy email address'; + + @override + String get exportLogs => 'Export logs'; + + @override + String get cancel => 'Atcelt'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Report a bug'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'Saglabāt'; + + @override + String get send => 'Send'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'E-pasts'; + + @override + String get verify => 'Verify'; + + @override + String get invalidEmailTitle => 'Nederīga e-pasta adrese'; + + @override + String get invalidEmailMessage => 'Lūdzu ievadiet derīgu e-pasta adresi.'; + + @override + String get pleaseWait => 'Please wait...'; + + @override + String get verifyPassword => 'Verify password'; + + @override + String get incorrectPasswordTitle => 'Incorrect password'; + + @override + String get pleaseTryAgain => 'Please try again'; + + @override + String get enterPassword => 'Ievadiet paroli'; + + @override + String get enterYourPasswordHint => 'Ievadiet savu paroli'; + + @override + String get activeSessions => 'Active sessions'; + + @override + String get oops => 'Oops'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Something went wrong, please try again'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'Terminate session?'; + + @override + String get terminate => 'Terminate'; + + @override + String get thisDevice => 'Šī ierīce'; + + @override + String get createAccount => 'Izveidot kontu'; + + @override + String get weakStrength => 'Vāja'; + + @override + String get moderateStrength => 'Vidēji spēcīga'; + + @override + String get strongStrength => 'Spēcīga'; + + @override + String get deleteAccount => 'Dzēst kontu'; + + @override + String get deleteAccountQuery => + 'We\'ll be sorry to see you go. Are you facing some issue?'; + + @override + String get yesSendFeedbackAction => 'Yes, send feedback'; + + @override + String get noDeleteAccountAction => 'Nē, dzēst kontu'; + + @override + String get initiateAccountDeleteTitle => + 'Please authenticate to initiate account deletion'; + + @override + String get confirmAccountDeleteTitle => 'Apstiprināt konta dzēšanu'; + + @override + String get 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.'; + + @override + String get delete => 'Dzēst'; + + @override + String get createNewAccount => 'Izveidot jaunu kontu'; + + @override + String get password => 'Parole'; + + @override + String get confirmPassword => 'Apstiprināt paroli'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Password strength: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'We don\'t track app installs. It\'d help if you told us where you found us!'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Terms'; + + @override + String get privacyPolicyTitle => 'Privacy Policy'; + + @override + String get ackPasswordLostWarning => + 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'; + + @override + String get encryption => 'Šifrēšana'; + + @override + String get logInLabel => 'Log in'; + + @override + String get welcomeBack => 'Welcome back!'; + + @override + String get loginTerms => + 'By clicking log in, I agree to the terms of service and privacy policy'; + + @override + String get noInternetConnection => 'Nav interneta savienojums'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Please check your internet connection and try again.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Recreate password'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'Use recovery key'; + + @override + String get forgotPassword => 'Forgot password'; + + @override + String get changeEmail => 'Change email'; + + @override + String get verifyEmail => 'Verify email'; + + @override + String weHaveSendEmailTo(String email) { + return 'We have sent a mail to $email'; + } + + @override + String get toResetVerifyEmail => + 'To reset your password, please verify your email first.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Tap to enter code'; + + @override + String get sendEmail => 'Send email'; + + @override + String get resendEmail => 'Resend email'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Session expired'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Try again'; + + @override + String get checkStatus => 'Check status'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Recover account'; + + @override + String get setPasswordTitle => 'Set password'; + + @override + String get changePasswordTitle => 'Change password'; + + @override + String get resetPasswordTitle => 'Reset password'; + + @override + String get encryptionKeys => 'Encryption keys'; + + @override + String get enterPasswordToEncrypt => + 'Enter a password we can use to encrypt your data'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'Kā tas darbojas'; + + @override + String get generatingEncryptionKeys => 'Generating encryption keys...'; + + @override + String get passwordChangedSuccessfully => 'Password changed successfully'; + + @override + String get signOutFromOtherDevices => 'Sign out from other devices'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'Sign out other devices'; + + @override + String get doNotSignOut => 'Do not sign out'; + + @override + String get generatingEncryptionKeysTitle => 'Generating encryption keys...'; + + @override + String get continueLabel => 'Continue'; + + @override + String get insecureDevice => 'Insecure device'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => 'Recovery key copied to clipboard'; + + @override + String get recoveryKey => 'Recovery key'; + + @override + String get recoveryKeyOnForgotPassword => + 'If you forget your password, the only way you can recover your data is with this key.'; + + @override + String get recoveryKeySaveDescription => + 'We don\'t store this key, please save this 24 word key in a safe place.'; + + @override + String get doThisLater => 'Do this later'; + + @override + String get saveKey => 'Saglabāt atslēgu'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'No recovery key?'; + + @override + String get twoFactorAuthTitle => 'Two-factor authentication'; + + @override + String get enterCodeHint => + 'Enter the 6-digit code from\nyour authenticator app'; + + @override + String get lostDeviceTitle => 'Lost device?'; + + @override + String get enterRecoveryKeyHint => 'Enter your recovery key'; + + @override + String get recover => 'Recover'; + + @override + String get loggingOut => 'Logging out...'; + + @override + String get immediately => 'Immediately'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Slēpt saturu'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Tap to unlock'; + + @override + String get areYouSureYouWantToLogout => 'Are you sure you want to logout?'; + + @override + String get yesLogout => 'Yes, logout'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'Enter PIN'; + + @override + String get setNewPin => 'Set new PIN'; + + @override + String get confirm => 'Confirm'; + + @override + String get reEnterPassword => 'Re-enter password'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verify identity'; + + @override + String get androidBiometricNotRecognized => 'Not recognized. Try again.'; + + @override + String get androidBiometricSuccess => 'Success'; + + @override + String get androidCancelButton => 'Atcelt'; + + @override + String get androidSignInTitle => 'Authentication required'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Go to settings'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => 'Šis e-pasts jau tiek izmantots'; + + @override + String emailChangedTo(String newEmail) { + return 'E-pasts nomainīts uz $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'Session expired'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Incorrect code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_ml.dart b/mobile/packages/strings/lib/l10n/strings_localizations_ml.dart new file mode 100644 index 0000000000..cf523a3fb0 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_ml.dart @@ -0,0 +1,627 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Malayalam (`ml`). +class StringsLocalizationsMl extends StringsLocalizations { + StringsLocalizationsMl([String locale = 'ml']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'Error'; + + @override + String get ok => 'ശരി'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'Contact support'; + + @override + String get emailYourLogs => 'Email your logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Please send the logs to \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copy email address'; + + @override + String get exportLogs => 'Export logs'; + + @override + String get cancel => 'റദ്ദാക്കുക'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Report a bug'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'Save'; + + @override + String get send => 'Send'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'ഇമെയിൽ'; + + @override + String get verify => 'പരിശോധിക്കുക'; + + @override + String get invalidEmailTitle => 'Invalid email address'; + + @override + String get invalidEmailMessage => 'Please enter a valid email address.'; + + @override + String get pleaseWait => 'Please wait...'; + + @override + String get verifyPassword => 'പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക'; + + @override + String get incorrectPasswordTitle => 'തെറ്റായ പാസ്‌വേഡ്'; + + @override + String get pleaseTryAgain => 'Please try again'; + + @override + String get enterPassword => 'Enter password'; + + @override + String get enterYourPasswordHint => 'Enter your password'; + + @override + String get activeSessions => 'Active sessions'; + + @override + String get oops => 'Oops'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Something went wrong, please try again'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'Terminate session?'; + + @override + String get terminate => 'Terminate'; + + @override + String get thisDevice => 'This device'; + + @override + String get createAccount => 'Create account'; + + @override + String get weakStrength => 'Weak'; + + @override + String get moderateStrength => 'Moderate'; + + @override + String get strongStrength => 'Strong'; + + @override + String get deleteAccount => 'Delete account'; + + @override + String get deleteAccountQuery => + 'We\'ll be sorry to see you go. Are you facing some issue?'; + + @override + String get yesSendFeedbackAction => 'Yes, send feedback'; + + @override + String get noDeleteAccountAction => 'No, delete account'; + + @override + String get initiateAccountDeleteTitle => + 'Please authenticate to initiate account deletion'; + + @override + String get confirmAccountDeleteTitle => 'Confirm account deletion'; + + @override + String get 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.'; + + @override + String get delete => 'Delete'; + + @override + String get createNewAccount => 'പുതിയ അക്കൗണ്ട് സൃഷ്ടിക്കുക'; + + @override + String get password => 'Password'; + + @override + String get confirmPassword => 'പാസ്വേഡ് സ്ഥിരീകരിക്കുക'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Password strength: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'We don\'t track app installs. It\'d help if you told us where you found us!'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Terms'; + + @override + String get privacyPolicyTitle => 'Privacy Policy'; + + @override + String get ackPasswordLostWarning => + 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'; + + @override + String get encryption => 'Encryption'; + + @override + String get logInLabel => 'Log in'; + + @override + String get welcomeBack => 'വീണ്ടും സ്വാഗതം!'; + + @override + String get loginTerms => + 'By clicking log in, I agree to the terms of service and privacy policy'; + + @override + String get noInternetConnection => 'No internet connection'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Please check your internet connection and try again.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Recreate password'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'Use recovery key'; + + @override + String get forgotPassword => 'Forgot password'; + + @override + String get changeEmail => 'ഇമെയിൽ മാറ്റുക'; + + @override + String get verifyEmail => 'ഇമെയിൽ സ്ഥിരീകരിക്കുക'; + + @override + String weHaveSendEmailTo(String email) { + return 'We have sent a mail to $email'; + } + + @override + String get toResetVerifyEmail => + 'To reset your password, please verify your email first.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Tap to enter code'; + + @override + String get sendEmail => 'Send email'; + + @override + String get resendEmail => 'Resend email'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Session expired'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Try again'; + + @override + String get checkStatus => 'Check status'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Recover account'; + + @override + String get setPasswordTitle => 'Set password'; + + @override + String get changePasswordTitle => 'Change password'; + + @override + String get resetPasswordTitle => 'Reset password'; + + @override + String get encryptionKeys => 'Encryption keys'; + + @override + String get enterPasswordToEncrypt => + 'Enter a password we can use to encrypt your data'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'How it works'; + + @override + String get generatingEncryptionKeys => 'Generating encryption keys...'; + + @override + String get passwordChangedSuccessfully => 'Password changed successfully'; + + @override + String get signOutFromOtherDevices => 'Sign out from other devices'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'Sign out other devices'; + + @override + String get doNotSignOut => 'Do not sign out'; + + @override + String get generatingEncryptionKeysTitle => 'Generating encryption keys...'; + + @override + String get continueLabel => 'Continue'; + + @override + String get insecureDevice => 'Insecure device'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => 'Recovery key copied to clipboard'; + + @override + String get recoveryKey => 'Recovery key'; + + @override + String get recoveryKeyOnForgotPassword => + 'If you forget your password, the only way you can recover your data is with this key.'; + + @override + String get recoveryKeySaveDescription => + 'We don\'t store this key, please save this 24 word key in a safe place.'; + + @override + String get doThisLater => 'Do this later'; + + @override + String get saveKey => 'Save key'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'No recovery key?'; + + @override + String get twoFactorAuthTitle => 'ടു-ഫാക്ടർ ആധികാരികത'; + + @override + String get enterCodeHint => + 'നിങ്ങളുടെ ഓതന്റിക്കേറ്റർ ആപ്പിൽ നിന്നുള്ള 6 അക്ക കോഡ് നൽകുക'; + + @override + String get lostDeviceTitle => 'Lost device?'; + + @override + String get enterRecoveryKeyHint => 'Enter your recovery key'; + + @override + String get recover => 'Recover'; + + @override + String get loggingOut => 'Logging out...'; + + @override + String get immediately => 'Immediately'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Hide content'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Tap to unlock'; + + @override + String get areYouSureYouWantToLogout => 'Are you sure you want to logout?'; + + @override + String get yesLogout => 'Yes, logout'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'Enter PIN'; + + @override + String get setNewPin => 'Set new PIN'; + + @override + String get confirm => 'Confirm'; + + @override + String get reEnterPassword => 'Re-enter password'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verify identity'; + + @override + String get androidBiometricNotRecognized => 'Not recognized. Try again.'; + + @override + String get androidBiometricSuccess => 'Success'; + + @override + String get androidCancelButton => 'Cancel'; + + @override + String get androidSignInTitle => 'Authentication required'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Go to settings'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => + 'ഇമെയിൽ ഇതിനകം രജിസ്റ്റർ ചെയ്തിട്ടുണ്ട്.'; + + @override + String get emailNotRegistered => 'ഇമെയിൽ രജിസ്റ്റർ ചെയ്തിട്ടില്ല.'; + + @override + String get thisEmailIsAlreadyInUse => 'This email is already in use'; + + @override + String emailChangedTo(String newEmail) { + return 'Email changed to $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'Session expired'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Incorrect code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart b/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart new file mode 100644 index 0000000000..7fb99652a5 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_nl.dart @@ -0,0 +1,632 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Dutch Flemish (`nl`). +class StringsLocalizationsNl extends StringsLocalizations { + StringsLocalizationsNl([String locale = 'nl']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Kan geen verbinding maken met Ente, controleer uw netwerkinstellingen en neem contact op met ondersteuning als de fout zich blijft voordoen.'; + + @override + String get networkConnectionRefusedErr => + 'Kan geen verbinding maken met Ente, probeer het later opnieuw. Als de fout zich blijft voordoen, neem dan contact op met support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Het lijkt erop dat er iets fout is gegaan. Probeer het later opnieuw. Als de fout zich blijft voordoen, neem dan contact op met ons supportteam.'; + + @override + String get error => 'Foutmelding'; + + @override + String get ok => 'Oké'; + + @override + String get faq => 'Veelgestelde vragen'; + + @override + String get contactSupport => 'Klantenservice'; + + @override + String get emailYourLogs => 'E-mail uw logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Verstuur de logs alsjeblieft naar $toEmail'; + } + + @override + String get copyEmailAddress => 'E-mailadres kopiëren'; + + @override + String get exportLogs => 'Logs exporteren'; + + @override + String get cancel => 'Annuleer'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Een fout melden'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Verbonden met $endpoint'; + } + + @override + String get save => 'Opslaan'; + + @override + String get send => 'Verzenden'; + + @override + String get saveOrSendDescription => + 'Wil je dit opslaan naar je opslagruimte (Downloads map) of naar andere apps versturen?'; + + @override + String get saveOnlyDescription => + 'Wil je dit opslaan naar je opslagruimte (Downloads map)?'; + + @override + String get enterNewEmailHint => 'Voer uw nieuwe e-mailadres in'; + + @override + String get email => 'E-mail'; + + @override + String get verify => 'Verifiëren'; + + @override + String get invalidEmailTitle => 'Ongeldig e-mailadres'; + + @override + String get invalidEmailMessage => 'Voer een geldig e-mailadres in.'; + + @override + String get pleaseWait => 'Een ogenblik geduld...'; + + @override + String get verifyPassword => 'Bevestig wachtwoord'; + + @override + String get incorrectPasswordTitle => 'Onjuist wachtwoord'; + + @override + String get pleaseTryAgain => 'Probeer het nog eens'; + + @override + String get enterPassword => 'Voer wachtwoord in'; + + @override + String get enterYourPasswordHint => 'Voer je wachtwoord in'; + + @override + String get activeSessions => 'Actieve sessies'; + + @override + String get oops => 'Oeps'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Er is iets fout gegaan, probeer het opnieuw'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Dit zal je uitloggen van dit apparaat!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Dit zal je uitloggen van het volgende apparaat:'; + + @override + String get terminateSession => 'Sessie beëindigen?'; + + @override + String get terminate => 'Beëindigen'; + + @override + String get thisDevice => 'Dit apparaat'; + + @override + String get createAccount => 'Account aanmaken'; + + @override + String get weakStrength => 'Zwak'; + + @override + String get moderateStrength => 'Matig'; + + @override + String get strongStrength => 'Sterk'; + + @override + String get deleteAccount => 'Account verwijderen'; + + @override + String get deleteAccountQuery => + 'We zullen het vervelend vinden om je te zien vertrekken. Zijn er problemen?'; + + @override + String get yesSendFeedbackAction => 'Ja, geef feedback'; + + @override + String get noDeleteAccountAction => 'Nee, verwijder account'; + + @override + String get initiateAccountDeleteTitle => + 'Gelieve te verifiëren om het account te verwijderen'; + + @override + String get confirmAccountDeleteTitle => 'Account verwijderen bevestigen'; + + @override + String get confirmAccountDeleteMessage => + 'Dit account is gekoppeld aan andere Ente apps, als je er gebruik van maakt.\n\nJe geüploade gegevens worden in alle Ente apps gepland voor verwijdering, en je account wordt permanent verwijderd voor alle Ente diensten.'; + + @override + String get delete => 'Verwijderen'; + + @override + String get createNewAccount => 'Nieuw account aanmaken'; + + @override + String get password => 'Wachtwoord'; + + @override + String get confirmPassword => 'Wachtwoord bevestigen'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Wachtwoord sterkte: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Hoe hoorde je over Ente? (optioneel)'; + + @override + String get hearUsExplanation => + 'Wij gebruiken geen tracking. Het zou helpen als je ons vertelt waar je ons gevonden hebt!'; + + @override + String get signUpTerms => + 'Ik ga akkoord met de gebruiksvoorwaarden en privacybeleid'; + + @override + String get termsOfServicesTitle => 'Voorwaarden'; + + @override + String get privacyPolicyTitle => 'Privacybeleid'; + + @override + String get ackPasswordLostWarning => + 'Ik begrijp dat als ik mijn wachtwoord verlies, ik mijn gegevens kan verliezen omdat mijn gegevens end-to-end versleuteld zijn.'; + + @override + String get encryption => 'Encryptie'; + + @override + String get logInLabel => 'Inloggen'; + + @override + String get welcomeBack => 'Welkom terug!'; + + @override + String get loginTerms => + 'Door op inloggen te klikken, ga ik akkoord met de gebruiksvoorwaarden en privacybeleid'; + + @override + String get noInternetConnection => 'Geen internetverbinding'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Controleer je internetverbinding en probeer het opnieuw.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verificatie mislukt, probeer het opnieuw'; + + @override + String get recreatePasswordTitle => 'Wachtwoord opnieuw instellen'; + + @override + String get recreatePasswordBody => + 'Het huidige apparaat is niet krachtig genoeg om je wachtwoord te verifiëren, dus moeten we de code een keer opnieuw genereren op een manier die met alle apparaten werkt.\n\nLog in met behulp van uw herstelsleutel en genereer opnieuw uw wachtwoord (je kunt dezelfde indien gewenst opnieuw gebruiken).'; + + @override + String get useRecoveryKey => 'Herstelsleutel gebruiken'; + + @override + String get forgotPassword => 'Wachtwoord vergeten'; + + @override + String get changeEmail => 'E-mailadres wijzigen'; + + @override + String get verifyEmail => 'Bevestig e-mail'; + + @override + String weHaveSendEmailTo(String email) { + return 'We hebben een e-mail gestuurd naar $email'; + } + + @override + String get toResetVerifyEmail => + 'Verifieer eerst je e-mailadres om je wachtwoord opnieuw in te stellen.'; + + @override + String get checkInboxAndSpamFolder => + 'Controleer je inbox (en spam) om verificatie te voltooien'; + + @override + String get tapToEnterCode => 'Tik om code in te voeren'; + + @override + String get sendEmail => 'E-mail versturen'; + + @override + String get resendEmail => 'E-mail opnieuw versturen'; + + @override + String get passKeyPendingVerification => 'Verificatie is nog in behandeling'; + + @override + String get loginSessionExpired => 'Sessie verlopen'; + + @override + String get loginSessionExpiredDetails => + 'Jouw sessie is verlopen. Log opnieuw in.'; + + @override + String get passkeyAuthTitle => 'Passkey verificatie'; + + @override + String get waitingForVerification => 'Wachten op verificatie...'; + + @override + String get tryAgain => 'Probeer opnieuw'; + + @override + String get checkStatus => 'Status controleren'; + + @override + String get loginWithTOTP => 'Inloggen met TOTP'; + + @override + String get recoverAccount => 'Account herstellen'; + + @override + String get setPasswordTitle => 'Wachtwoord instellen'; + + @override + String get changePasswordTitle => 'Wachtwoord wijzigen'; + + @override + String get resetPasswordTitle => 'Wachtwoord resetten'; + + @override + String get encryptionKeys => 'Encryptiesleutels'; + + @override + String get enterPasswordToEncrypt => + 'Voer een wachtwoord in dat we kunnen gebruiken om je gegevens te versleutelen'; + + @override + String get enterNewPasswordToEncrypt => + 'Voer een nieuw wachtwoord in dat we kunnen gebruiken om je gegevens te versleutelen'; + + @override + String get passwordWarning => + 'Wij slaan dit wachtwoord niet op, dus als je het vergeet, kunnen we jouw gegevens niet ontsleutelen'; + + @override + String get howItWorks => 'Hoe het werkt'; + + @override + String get generatingEncryptionKeys => + 'Encryptiesleutels worden gegenereerd...'; + + @override + String get passwordChangedSuccessfully => 'Wachtwoord succesvol aangepast'; + + @override + String get signOutFromOtherDevices => 'Afmelden bij andere apparaten'; + + @override + String get signOutOtherBody => + 'Als je denkt dat iemand je wachtwoord zou kunnen kennen, kun je alle andere apparaten die je account gebruiken dwingen om uit te loggen.'; + + @override + String get signOutOtherDevices => 'Afmelden bij andere apparaten'; + + @override + String get doNotSignOut => 'Niet uitloggen'; + + @override + String get generatingEncryptionKeysTitle => 'Encryptiesleutels genereren...'; + + @override + String get continueLabel => 'Doorgaan'; + + @override + String get insecureDevice => 'Onveilig apparaat'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we konden geen beveiligde sleutels genereren op dit apparaat.\n\nMeld je aan vanaf een ander apparaat.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Herstelsleutel gekopieerd naar klembord'; + + @override + String get recoveryKey => 'Herstelsleutel'; + + @override + String get recoveryKeyOnForgotPassword => + 'Als je je wachtwoord vergeet, kun je alleen met deze code je gegevens herstellen.'; + + @override + String get recoveryKeySaveDescription => + 'We slaan deze code niet op, bewaar deze code met 24 woorden op een veilige plaats.'; + + @override + String get doThisLater => 'Doe dit later'; + + @override + String get saveKey => 'Sleutel opslaan'; + + @override + String get recoveryKeySaved => + 'Herstelsleutel opgeslagen in de Downloads map!'; + + @override + String get noRecoveryKeyTitle => 'Geen herstelsleutel?'; + + @override + String get twoFactorAuthTitle => 'Tweestapsverificatie'; + + @override + String get enterCodeHint => + 'Voer de 6-cijferige code van je verificatie-app in'; + + @override + String get lostDeviceTitle => 'Apparaat verloren?'; + + @override + String get enterRecoveryKeyHint => 'Voer je herstelsleutel in'; + + @override + String get recover => 'Herstellen'; + + @override + String get loggingOut => 'Bezig met uitloggen...'; + + @override + String get immediately => 'Onmiddellijk'; + + @override + String get appLock => 'App-vergrendeling'; + + @override + String get autoLock => 'Automatische vergrendeling'; + + @override + String get noSystemLockFound => 'Geen systeemvergrendeling gevonden'; + + @override + String get deviceLockEnablePreSteps => + 'Om toestelvergrendeling in te schakelen, stelt u de toegangscode van het apparaat of schermvergrendeling in uw systeeminstellingen in.'; + + @override + String get appLockDescription => + 'Kies tussen de standaard schermvergrendeling van uw apparaat en een aangepaste schermvergrendeling met een pincode of wachtwoord.'; + + @override + String get deviceLock => 'Apparaat vergrendeling'; + + @override + String get pinLock => 'Pin vergrendeling'; + + @override + String get autoLockFeatureDescription => + 'Tijd waarna de app vergrendelt nadat ze op de achtergrond is gezet'; + + @override + String get hideContent => 'Inhoud verbergen'; + + @override + String get hideContentDescriptionAndroid => + 'Verbergt de app inhoud in de app switcher en schakelt schermafbeeldingen uit'; + + @override + String get hideContentDescriptioniOS => + 'Verbergt de inhoud van de app in de app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Te veel onjuiste pogingen'; + + @override + String get tapToUnlock => 'Tik om te ontgrendelen'; + + @override + String get areYouSureYouWantToLogout => + 'Weet je zeker dat je wilt uitloggen?'; + + @override + String get yesLogout => 'Ja, uitloggen'; + + @override + String get authToViewSecrets => + 'Graag verifiëren om uw herstelsleutel te bekijken'; + + @override + String get next => 'Volgende'; + + @override + String get setNewPassword => 'Nieuw wachtwoord instellen'; + + @override + String get enterPin => 'Pin invoeren'; + + @override + String get setNewPin => 'Nieuwe pin instellen'; + + @override + String get confirm => 'Bevestig'; + + @override + String get reEnterPassword => 'Wachtwoord opnieuw invoeren'; + + @override + String get reEnterPin => 'PIN opnieuw invoeren'; + + @override + String get androidBiometricHint => 'Identiteit verifiëren'; + + @override + String get androidBiometricNotRecognized => + 'Niet herkend. Probeer het opnieuw.'; + + @override + String get androidBiometricSuccess => 'Succes'; + + @override + String get androidCancelButton => 'Annuleren'; + + @override + String get androidSignInTitle => 'Verificatie vereist'; + + @override + String get androidBiometricRequiredTitle => + 'Biometrische verificatie vereist'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Apparaatgegevens vereist'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Apparaatgegevens vereist'; + + @override + String get goToSettings => 'Ga naar instellingen'; + + @override + String get androidGoToSettingsDescription => + 'Biometrische verificatie is niet ingesteld op uw apparaat. Ga naar \'Instellingen > Beveiliging\' om biometrische verificatie toe te voegen.'; + + @override + String get iOSLockOut => + 'Biometrische verificatie is uitgeschakeld. Vergrendel en ontgrendel uw scherm om het in te schakelen.'; + + @override + String get iOSOkButton => 'Oké'; + + @override + String get emailAlreadyRegistered => 'E-mail is al geregistreerd.'; + + @override + String get emailNotRegistered => 'E-mail niet geregistreerd.'; + + @override + String get thisEmailIsAlreadyInUse => 'Dit e-mailadres is al in gebruik'; + + @override + String emailChangedTo(String newEmail) { + return 'E-mailadres gewijzigd naar $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Verificatie mislukt, probeer het opnieuw'; + + @override + String get authenticationSuccessful => 'Verificatie geslaagd!'; + + @override + String get sessionExpired => 'Sessie verlopen'; + + @override + String get incorrectRecoveryKey => 'Onjuiste herstelsleutel'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'De ingevoerde herstelsleutel is onjuist'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Tweestapsverificatie succesvol gereset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => 'Uw verificatiecode is verlopen'; + + @override + String get incorrectCode => 'Onjuiste code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, de ingevoerde code is onjuist'; + + @override + String get developerSettings => 'Ontwikkelaarsinstellingen'; + + @override + String get serverEndpoint => 'Server eindpunt'; + + @override + String get invalidEndpoint => 'Ongeldig eindpunt'; + + @override + String get invalidEndpointMessage => + 'Sorry, het eindpunt dat u hebt ingevoerd is ongeldig. Voer een geldig eindpunt in en probeer het opnieuw.'; + + @override + String get endpointUpdatedMessage => 'Eindpunt met succes bijgewerkt'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart b/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart new file mode 100644 index 0000000000..5d636efe39 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_pl.dart @@ -0,0 +1,630 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Polish (`pl`). +class StringsLocalizationsPl extends StringsLocalizations { + StringsLocalizationsPl([String locale = 'pl']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Nie można połączyć się z Ente, sprawdź ustawienia sieci i skontaktuj się z pomocą techniczną, jeśli błąd będzie się powtarzał.'; + + @override + String get networkConnectionRefusedErr => + 'Nie można połączyć się z Ente, spróbuj ponownie po pewnym czasie. Jeśli błąd będzie się powtarzał, skontaktuj się z pomocą techniczną.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Wygląda na to, że coś poszło nie tak. Spróbuj ponownie po pewnym czasie. Jeśli błąd będzie się powtarzał, skontaktuj się z naszym zespołem pomocy technicznej.'; + + @override + String get error => 'Błąd'; + + @override + String get ok => 'Ok'; + + @override + String get faq => 'Najczęściej zadawane pytania (FAQ)'; + + @override + String get contactSupport => 'Skontaktuj się z pomocą techniczną'; + + @override + String get emailYourLogs => 'Wyślij mailem logi'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Prosimy wysłać logi do $toEmail'; + } + + @override + String get copyEmailAddress => 'Kopiuj adres e-mail'; + + @override + String get exportLogs => 'Eksportuj logi'; + + @override + String get cancel => 'Anuluj'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Zgłoś błąd'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Połączono z $endpoint'; + } + + @override + String get save => 'Zapisz'; + + @override + String get send => 'Wyślij'; + + @override + String get saveOrSendDescription => + 'Czy chcesz zapisać to do swojej pamięci masowej (domyślnie folder Pobrane) czy wysłać to do innych aplikacji?'; + + @override + String get saveOnlyDescription => + 'Czy chcesz zapisać to do swojej pamięci masowej (domyślnie folder Pobrane)?'; + + @override + String get enterNewEmailHint => 'Wprowadź nowy adres e-mail'; + + @override + String get email => 'Email'; + + @override + String get verify => 'Zweryfikuj'; + + @override + String get invalidEmailTitle => 'Nieprawidłowy adres e-mail'; + + @override + String get invalidEmailMessage => 'Prosimy podać prawidłowy adres e-mail.'; + + @override + String get pleaseWait => 'Prosimy czekać...'; + + @override + String get verifyPassword => 'Zweryfikuj hasło'; + + @override + String get incorrectPasswordTitle => 'Nieprawidłowe hasło'; + + @override + String get pleaseTryAgain => 'Prosimy spróbować ponownie'; + + @override + String get enterPassword => 'Wprowadź hasło'; + + @override + String get enterYourPasswordHint => 'Wprowadź swoje hasło'; + + @override + String get activeSessions => 'Aktywne sesje'; + + @override + String get oops => 'Ups'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Coś poszło nie tak, spróbuj ponownie'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'To wyloguje Cię z tego urządzenia!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'To wyloguje Cię z tego urządzenia:'; + + @override + String get terminateSession => 'Zakończyć sesję?'; + + @override + String get terminate => 'Zakończ'; + + @override + String get thisDevice => 'To urządzenie'; + + @override + String get createAccount => 'Utwórz konto'; + + @override + String get weakStrength => 'Słabe'; + + @override + String get moderateStrength => 'Umiarkowane'; + + @override + String get strongStrength => 'Silne'; + + @override + String get deleteAccount => 'Usuń konto'; + + @override + String get deleteAccountQuery => + 'Będzie nam przykro, że odchodzisz. Masz jakiś problem?'; + + @override + String get yesSendFeedbackAction => 'Tak, wyślij opinię'; + + @override + String get noDeleteAccountAction => 'Nie, usuń moje konto'; + + @override + String get initiateAccountDeleteTitle => + 'Prosimy uwierzytelnić się, aby zainicjować usuwanie konta'; + + @override + String get confirmAccountDeleteTitle => 'Potwierdź usunięcie konta'; + + @override + String get confirmAccountDeleteMessage => + 'To konto jest połączone z innymi aplikacjami Ente, jeśli ich używasz.\n\nTwoje przesłane dane, we wszystkich aplikacjach Ente, zostaną zaplanowane do usunięcia, a Twoje konto zostanie trwale usunięte.'; + + @override + String get delete => 'Usuń'; + + @override + String get createNewAccount => 'Utwórz nowe konto'; + + @override + String get password => 'Hasło'; + + @override + String get confirmPassword => 'Potwierdź hasło'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Siła hasła: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Jak usłyszałeś/aś o Ente? (opcjonalnie)'; + + @override + String get hearUsExplanation => + 'Nie śledzimy instalacji aplikacji. Pomogłyby nam, gdybyś powiedział/a nam, gdzie nas znalazłeś/aś!'; + + @override + String get signUpTerms => + 'Akceptuję warunki korzystania z usługi i politykę prywatności'; + + @override + String get termsOfServicesTitle => 'Regulamin'; + + @override + String get privacyPolicyTitle => 'Polityka prywatności'; + + @override + String get ackPasswordLostWarning => + 'Rozumiem, że jeśli utracę hasło, mogę stracić moje dane, ponieważ moje dane są szyfrowane end-to-end.'; + + @override + String get encryption => 'Szyfrowanie'; + + @override + String get logInLabel => 'Zaloguj się'; + + @override + String get welcomeBack => 'Witaj ponownie!'; + + @override + String get loginTerms => + 'Klikając, zaloguj się, zgadzam się na regulamin i politykę prywatności'; + + @override + String get noInternetConnection => 'Brak połączenia z Internetem'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Prosimy sprawdzić połączenie internetowe i spróbować ponownie.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Weryfikacja nie powiodła się, spróbuj ponownie'; + + @override + String get recreatePasswordTitle => 'Zresetuj hasło'; + + @override + String get recreatePasswordBody => + 'Obecne urządzenie nie jest wystarczająco wydajne, aby zweryfikować Twoje hasło, więc musimy je raz zregenerować w sposób, który działa ze wszystkimi urządzeniami. \n\nZaloguj się przy użyciu klucza odzyskiwania i zresetuj swoje hasło (możesz ponownie użyć tego samego, jeśli chcesz).'; + + @override + String get useRecoveryKey => 'Użyj kodu odzyskiwania'; + + @override + String get forgotPassword => 'Nie pamiętam hasła'; + + @override + String get changeEmail => 'Zmień adres e-mail'; + + @override + String get verifyEmail => 'Zweryfikuj adres e-mail'; + + @override + String weHaveSendEmailTo(String email) { + return 'Wysłaliśmy wiadomość do $email'; + } + + @override + String get toResetVerifyEmail => + 'Aby zresetować hasło, najpierw zweryfikuj swój e-mail.'; + + @override + String get checkInboxAndSpamFolder => + 'Sprawdź swoją skrzynkę odbiorczą (i spam), aby zakończyć weryfikację'; + + @override + String get tapToEnterCode => 'Dotknij, aby wprowadzić kod'; + + @override + String get sendEmail => 'Wyślij e-mail'; + + @override + String get resendEmail => 'Wyślij e-mail ponownie'; + + @override + String get passKeyPendingVerification => 'Weryfikacja jest nadal w toku'; + + @override + String get loginSessionExpired => 'Sesja wygasła'; + + @override + String get loginSessionExpiredDetails => + 'Twoja sesja wygasła. Zaloguj się ponownie.'; + + @override + String get passkeyAuthTitle => 'Weryfikacja kluczem dostępu'; + + @override + String get waitingForVerification => 'Oczekiwanie na weryfikację...'; + + @override + String get tryAgain => 'Spróbuj ponownie'; + + @override + String get checkStatus => 'Sprawdź stan'; + + @override + String get loginWithTOTP => 'Zaloguj się za pomocą TOTP'; + + @override + String get recoverAccount => 'Odzyskaj konto'; + + @override + String get setPasswordTitle => 'Ustaw hasło'; + + @override + String get changePasswordTitle => 'Zmień hasło'; + + @override + String get resetPasswordTitle => 'Zresetuj hasło'; + + @override + String get encryptionKeys => 'Klucz szyfrowania'; + + @override + String get enterPasswordToEncrypt => + 'Wprowadź hasło, którego możemy użyć do zaszyfrowania Twoich danych'; + + @override + String get enterNewPasswordToEncrypt => + 'Wprowadź nowe hasło, którego możemy użyć do zaszyfrowania Twoich danych'; + + @override + String get passwordWarning => + 'Nie przechowujemy tego hasła, więc jeśli go zapomnisz, nie będziemy w stanie odszyfrować Twoich danych'; + + @override + String get howItWorks => 'Jak to działa'; + + @override + String get generatingEncryptionKeys => 'Generowanie kluczy szyfrujących...'; + + @override + String get passwordChangedSuccessfully => 'Hasło zostało pomyślnie zmienione'; + + @override + String get signOutFromOtherDevices => 'Wyloguj z pozostałych urządzeń'; + + @override + String get signOutOtherBody => + 'Jeśli uważasz, że ktoś może znać Twoje hasło, możesz wymusić wylogowanie na wszystkich innych urządzeniach korzystających z Twojego konta.'; + + @override + String get signOutOtherDevices => 'Wyloguj z pozostałych urządzeń'; + + @override + String get doNotSignOut => 'Nie wylogowuj mnie'; + + @override + String get generatingEncryptionKeysTitle => + 'Generowanie kluczy szyfrujących...'; + + @override + String get continueLabel => 'Kontynuuj'; + + @override + String get insecureDevice => 'Niezabezpieczone urządzenie'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Przepraszamy, nie mogliśmy wygenerować kluczy bezpiecznych na tym urządzeniu.\n\nZarejestruj się z innego urządzenia.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Klucz odzyskiwania został skopiowany do schowka'; + + @override + String get recoveryKey => 'Klucz odzyskiwania'; + + @override + String get recoveryKeyOnForgotPassword => + 'Jeśli zapomnisz hasła, jedynym sposobem na odzyskanie danych jest ten klucz.'; + + @override + String get recoveryKeySaveDescription => + 'Nie przechowujemy tego klucza, prosimy zachować ten 24-słowny klucz w bezpiecznym miejscu.'; + + @override + String get doThisLater => 'Zrób to później'; + + @override + String get saveKey => 'Zapisz klucz'; + + @override + String get recoveryKeySaved => + 'Klucz odzyskiwania zapisany w folderze Pobrane!'; + + @override + String get noRecoveryKeyTitle => 'Brak klucza odzyskiwania?'; + + @override + String get twoFactorAuthTitle => 'Uwierzytelnianie dwustopniowe'; + + @override + String get enterCodeHint => + 'Wprowadź sześciocyfrowy kod z \nTwojej aplikacji uwierzytelniającej'; + + @override + String get lostDeviceTitle => 'Zagubiono urządzenie?'; + + @override + String get enterRecoveryKeyHint => 'Wprowadź swój klucz odzyskiwania'; + + @override + String get recover => 'Odzyskaj'; + + @override + String get loggingOut => 'Wylogowywanie...'; + + @override + String get immediately => 'Natychmiast'; + + @override + String get appLock => 'Blokada aplikacji'; + + @override + String get autoLock => 'Automatyczna blokada'; + + @override + String get noSystemLockFound => 'Nie znaleziono blokady systemowej'; + + @override + String get deviceLockEnablePreSteps => + 'Aby włączyć blokadę aplikacji, należy skonfigurować hasło urządzenia lub blokadę ekranu w ustawieniach Twojego systemu.'; + + @override + String get appLockDescription => + 'Wybierz między domyślnym ekranem blokady urządzenia a niestandardowym ekranem blokady z kodem PIN lub hasłem.'; + + @override + String get deviceLock => 'Blokada urządzenia'; + + @override + String get pinLock => 'Blokada PIN'; + + @override + String get autoLockFeatureDescription => + 'Czas, po którym aplikacja blokuje się po umieszczeniu jej w tle'; + + @override + String get hideContent => 'Ukryj zawartość'; + + @override + String get hideContentDescriptionAndroid => + 'Ukrywa zawartość aplikacji w przełączniku aplikacji i wyłącza zrzuty ekranu'; + + @override + String get hideContentDescriptioniOS => + 'Ukrywa zawartość aplikacji w przełączniku aplikacji'; + + @override + String get tooManyIncorrectAttempts => 'Zbyt wiele błędnych prób'; + + @override + String get tapToUnlock => 'Naciśnij, aby odblokować'; + + @override + String get areYouSureYouWantToLogout => 'Czy na pewno chcesz się wylogować?'; + + @override + String get yesLogout => 'Tak, wyloguj'; + + @override + String get authToViewSecrets => + 'Prosimy uwierzytelnić się, aby wyświetlić swoje sekrety'; + + @override + String get next => 'Dalej'; + + @override + String get setNewPassword => 'Ustaw nowe hasło'; + + @override + String get enterPin => 'Wprowadź kod PIN'; + + @override + String get setNewPin => 'Ustaw nowy kod PIN'; + + @override + String get confirm => 'Potwierdź'; + + @override + String get reEnterPassword => 'Wprowadź ponownie hasło'; + + @override + String get reEnterPin => 'Wprowadź ponownie kod PIN'; + + @override + String get androidBiometricHint => 'Potwierdź swoją tożsamość'; + + @override + String get androidBiometricNotRecognized => + 'Nie rozpoznano. Spróbuj ponownie.'; + + @override + String get androidBiometricSuccess => 'Sukces'; + + @override + String get androidCancelButton => 'Anuluj'; + + @override + String get androidSignInTitle => 'Wymagana autoryzacja'; + + @override + String get androidBiometricRequiredTitle => 'Wymagana biometria'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Wymagane dane logowania urządzenia'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Wymagane dane logowania urządzenia'; + + @override + String get goToSettings => 'Przejdź do ustawień'; + + @override + String get androidGoToSettingsDescription => + 'Uwierzytelnianie biometryczne nie jest skonfigurowane na tym urządzeniu. Przejdź do \'Ustawienia > Bezpieczeństwo\', aby dodać uwierzytelnianie biometryczne.'; + + @override + String get iOSLockOut => + 'Uwierzytelnianie biometryczne jest wyłączone. Prosimy zablokować i odblokować ekran, aby je włączyć.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Adres e-mail jest już zarejestrowany.'; + + @override + String get emailNotRegistered => 'Adres e-mail nie jest zarejestrowany.'; + + @override + String get thisEmailIsAlreadyInUse => 'Ten adres e-mail już jest zajęty'; + + @override + String emailChangedTo(String newEmail) { + return 'Adres e-mail został zmieniony na $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Uwierzytelnianie nie powiodło się, prosimy spróbować ponownie'; + + @override + String get authenticationSuccessful => 'Uwierzytelnianie powiodło się!'; + + @override + String get sessionExpired => 'Sesja wygasła'; + + @override + String get incorrectRecoveryKey => 'Nieprawidłowy klucz odzyskiwania'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Wprowadzony klucz odzyskiwania jest nieprawidłowy'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Pomyślnie zresetowano uwierzytelnianie dwustopniowe'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => 'Twój kod weryfikacyjny wygasł'; + + @override + String get incorrectCode => 'Nieprawidłowy kod'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Niestety, wprowadzony kod jest nieprawidłowy'; + + @override + String get developerSettings => 'Ustawienia dla programistów'; + + @override + String get serverEndpoint => 'Punkt końcowy serwera'; + + @override + String get invalidEndpoint => 'Punkt końcowy jest nieprawidłowy'; + + @override + String get invalidEndpointMessage => + 'Niestety, wprowadzony punkt końcowy jest nieprawidłowy. Wprowadź prawidłowy punkt końcowy i spróbuj ponownie.'; + + @override + String get endpointUpdatedMessage => 'Punkt końcowy zaktualizowany pomyślnie'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart b/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart new file mode 100644 index 0000000000..b339c4f481 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_pt.dart @@ -0,0 +1,629 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Portuguese (`pt`). +class StringsLocalizationsPt extends StringsLocalizations { + StringsLocalizationsPt([String locale = 'pt']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Não foi possível conectar-se ao Ente, verifique suas configurações de rede e entre em contato com o suporte se o erro persistir.'; + + @override + String get networkConnectionRefusedErr => + 'Não foi possível conectar ao Ente, tente novamente após algum tempo. Se o erro persistir, entre em contato com o suporte.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Parece que algo deu errado. Tente novamente mais tarde. Se o erro persistir, entre em contato com nossa equipe de ajuda.'; + + @override + String get error => 'Erro'; + + @override + String get ok => 'Ok'; + + @override + String get faq => 'Perguntas frequentes'; + + @override + String get contactSupport => 'Contatar suporte'; + + @override + String get emailYourLogs => 'Enviar registros por e-mail'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Envie os logs para \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copiar endereço de e-mail'; + + @override + String get exportLogs => 'Exportar logs'; + + @override + String get cancel => 'Cancelar'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Informar erro'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Conectado a $endpoint'; + } + + @override + String get save => 'Salvar'; + + @override + String get send => 'Enviar'; + + @override + String get saveOrSendDescription => + 'Deseja mesmo salvar isso no armazenamento (pasta de Downloads por padrão) ou enviar a outros aplicativos?'; + + @override + String get saveOnlyDescription => + 'Deseja mesmo salvar em seu armazenamento (pasta de Downloads por padrão)?'; + + @override + String get enterNewEmailHint => 'Insira seu novo e-mail'; + + @override + String get email => 'E-mail'; + + @override + String get verify => 'Verificar'; + + @override + String get invalidEmailTitle => 'Endereço de e-mail inválido'; + + @override + String get invalidEmailMessage => 'Insira um endereço de e-mail válido.'; + + @override + String get pleaseWait => 'Aguarde...'; + + @override + String get verifyPassword => 'Verificar senha'; + + @override + String get incorrectPasswordTitle => 'Senha incorreta'; + + @override + String get pleaseTryAgain => 'Tente novamente'; + + @override + String get enterPassword => 'Inserir senha'; + + @override + String get enterYourPasswordHint => 'Insira sua senha'; + + @override + String get activeSessions => 'Sessões ativas'; + + @override + String get oops => 'Opa'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Algo deu errado. Tente outra vez'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Isso fará com que você saia deste dispositivo!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Isso fará você sair do dispositivo a seguir:'; + + @override + String get terminateSession => 'Sair?'; + + @override + String get terminate => 'Encerrar'; + + @override + String get thisDevice => 'Esse dispositivo'; + + @override + String get createAccount => 'Criar conta'; + + @override + String get weakStrength => 'Fraca'; + + @override + String get moderateStrength => 'Moderada'; + + @override + String get strongStrength => 'Forte'; + + @override + String get deleteAccount => 'Excluir conta'; + + @override + String get deleteAccountQuery => + 'Estamos tristes por vê-lo sair. Você enfrentou algum problema?'; + + @override + String get yesSendFeedbackAction => 'Sim, enviar feedback'; + + @override + String get noDeleteAccountAction => 'Não, excluir conta'; + + @override + String get initiateAccountDeleteTitle => + 'Autentique-se para iniciar a exclusão de conta'; + + @override + String get confirmAccountDeleteTitle => 'Confirmar exclusão de conta'; + + @override + String get confirmAccountDeleteMessage => + 'Esta conta está vinculada a outros apps Ente, se você usa algum.\n\nSeus dados enviados, entre todos os apps Ente, serão marcados para exclusão, e sua conta será apagada permanentemente.'; + + @override + String get delete => 'Excluir'; + + @override + String get createNewAccount => 'Criar nova conta'; + + @override + String get password => 'Senha'; + + @override + String get confirmPassword => 'Confirmar senha'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Força da senha: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Como você descobriu o Ente? (opcional)'; + + @override + String get hearUsExplanation => + 'Não rastreamos instalações. Ajudaria bastante se você contasse onde nos achou!'; + + @override + String get signUpTerms => + 'Eu concordo com os termos de serviço e a política de privacidade'; + + @override + String get termsOfServicesTitle => 'Termos'; + + @override + String get privacyPolicyTitle => 'Política de Privacidade'; + + @override + String get ackPasswordLostWarning => + 'Eu entendo que se eu perder minha senha, posso perder meus dados, já que meus dados são criptografados de ponta a ponta.'; + + @override + String get encryption => 'Criptografia'; + + @override + String get logInLabel => 'Entrar'; + + @override + String get welcomeBack => 'Bem-vindo(a) de volta!'; + + @override + String get loginTerms => + 'Ao clicar em iniciar sessão, eu concordo com os termos de serviço e a política de privacidade'; + + @override + String get noInternetConnection => 'Não conectado à internet'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Verifique sua conexão com a internet e tente novamente.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Falhou na verificação. Tente novamente'; + + @override + String get recreatePasswordTitle => 'Redefinir senha'; + + @override + String get recreatePasswordBody => + 'Não é possível verificar a sua senha no dispositivo atual, mas podemos regenerá-la para que funcione em todos os dispositivos. \n\nEntre com a sua chave de recuperação e regenere sua senha (você pode usar a mesma se quiser).'; + + @override + String get useRecoveryKey => 'Usar chave de recuperação'; + + @override + String get forgotPassword => 'Esqueci a senha'; + + @override + String get changeEmail => 'Alterar e-mail'; + + @override + String get verifyEmail => 'Verificar e-mail'; + + @override + String weHaveSendEmailTo(String email) { + return 'Enviamos um e-mail à $email'; + } + + @override + String get toResetVerifyEmail => + 'Para redefinir sua senha, verifique seu e-mail primeiramente.'; + + @override + String get checkInboxAndSpamFolder => + 'Verifique sua caixa de entrada (e spam) para concluir a verificação'; + + @override + String get tapToEnterCode => 'Toque para inserir código'; + + @override + String get sendEmail => 'Enviar e-mail'; + + @override + String get resendEmail => 'Reenviar e-mail'; + + @override + String get passKeyPendingVerification => 'A verificação ainda está pendente'; + + @override + String get loginSessionExpired => 'Sessão expirada'; + + @override + String get loginSessionExpiredDetails => + 'Sua sessão expirou. Registre-se novamente.'; + + @override + String get passkeyAuthTitle => 'Verificação de chave de acesso'; + + @override + String get waitingForVerification => 'Aguardando verificação...'; + + @override + String get tryAgain => 'Tente novamente'; + + @override + String get checkStatus => 'Verificar status'; + + @override + String get loginWithTOTP => 'Registrar com TOTP'; + + @override + String get recoverAccount => 'Recuperar conta'; + + @override + String get setPasswordTitle => 'Definir senha'; + + @override + String get changePasswordTitle => 'Alterar senha'; + + @override + String get resetPasswordTitle => 'Redefinir senha'; + + @override + String get encryptionKeys => 'Chaves de criptografia'; + + @override + String get enterPasswordToEncrypt => + 'Insira uma senha que podemos usar para criptografar seus dados'; + + @override + String get enterNewPasswordToEncrypt => + 'Insira uma nova senha para criptografar seus dados'; + + @override + String get passwordWarning => + 'Não salvamos esta senha, então se você esquecê-la, não podemos descriptografar seus dados'; + + @override + String get howItWorks => 'Como funciona'; + + @override + String get generatingEncryptionKeys => 'Gerando chaves de criptografia...'; + + @override + String get passwordChangedSuccessfully => 'A senha foi alterada'; + + @override + String get signOutFromOtherDevices => 'Sair da conta em outros dispositivos'; + + @override + String get signOutOtherBody => + 'Se você acha que alguém possa saber da sua senha, você pode forçar desconectar sua conta de outros dispositivos.'; + + @override + String get signOutOtherDevices => 'Sair em outros dispositivos'; + + @override + String get doNotSignOut => 'Não sair'; + + @override + String get generatingEncryptionKeysTitle => + 'Gerando chaves de criptografia...'; + + @override + String get continueLabel => 'Continuar'; + + @override + String get insecureDevice => 'Dispositivo inseguro'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Desculpe, não foi possível gerar chaves de segurança nesse dispositivo.\n\ninicie sessão em um dispositivo diferente.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Chave de recuperação copiada para a área de transferência'; + + @override + String get recoveryKey => 'Chave de recuperação'; + + @override + String get recoveryKeyOnForgotPassword => + 'Caso esqueça sua senha, a única maneira de recuperar seus dados é com esta chave.'; + + @override + String get recoveryKeySaveDescription => + 'Não armazenamos esta chave de 24 palavras. Salve-a em um lugar seguro.'; + + @override + String get doThisLater => 'Fazer isso depois'; + + @override + String get saveKey => 'Salvar chave'; + + @override + String get recoveryKeySaved => + 'Chave de recuperação salva na pasta Downloads!'; + + @override + String get noRecoveryKeyTitle => 'Sem chave de recuperação?'; + + @override + String get twoFactorAuthTitle => 'Autenticação de dois fatores'; + + @override + String get enterCodeHint => + 'Insira o código de 6 dígitos do aplicativo autenticador'; + + @override + String get lostDeviceTitle => 'Perdeu o dispositivo?'; + + @override + String get enterRecoveryKeyHint => 'Digite a chave de recuperação'; + + @override + String get recover => 'Recuperar'; + + @override + String get loggingOut => 'Desconectando...'; + + @override + String get immediately => 'Imediatamente'; + + @override + String get appLock => 'Bloqueio do aplicativo'; + + @override + String get autoLock => 'Bloqueio automático'; + + @override + String get noSystemLockFound => 'Nenhum bloqueio do sistema encontrado'; + + @override + String get deviceLockEnablePreSteps => + 'Para ativar o bloqueio do dispositivo, configure a senha do dispositivo ou o bloqueio de tela nas configurações do seu sistema.'; + + @override + String get appLockDescription => + 'Escolha entre a tela de bloqueio padrão do seu dispositivo e uma tela de bloqueio personalizada com PIN ou senha.'; + + @override + String get deviceLock => 'Bloqueio do dispositivo'; + + @override + String get pinLock => 'PIN de bloqueio'; + + @override + String get autoLockFeatureDescription => + 'Tempo de bloqueio do aplicativo em segundo plano'; + + @override + String get hideContent => 'Ocultar conteúdo'; + + @override + String get hideContentDescriptionAndroid => + 'Oculta o conteúdo do aplicativo no seletor de aplicativos e desativa as capturas de tela'; + + @override + String get hideContentDescriptioniOS => + 'Oculta o conteúdo do seletor de aplicativos'; + + @override + String get tooManyIncorrectAttempts => 'Muitas tentativas incorretas'; + + @override + String get tapToUnlock => 'Toque para desbloquear'; + + @override + String get areYouSureYouWantToLogout => 'Deseja mesmo sair?'; + + @override + String get yesLogout => 'Sim, quero sair'; + + @override + String get authToViewSecrets => 'Autentique-se para ver suas chaves secretas'; + + @override + String get next => 'Avançar'; + + @override + String get setNewPassword => 'Defina a nova senha'; + + @override + String get enterPin => 'Inserir PIN'; + + @override + String get setNewPin => 'Definir novo PIN'; + + @override + String get confirm => 'Confirmar'; + + @override + String get reEnterPassword => 'Reinserir senha'; + + @override + String get reEnterPin => 'Reinserir PIN'; + + @override + String get androidBiometricHint => 'Verificar identidade'; + + @override + String get androidBiometricNotRecognized => 'Não reconhecido. Tente de novo.'; + + @override + String get androidBiometricSuccess => 'Sucesso'; + + @override + String get androidCancelButton => 'Cancelar'; + + @override + String get androidSignInTitle => 'Autenticação necessária'; + + @override + String get androidBiometricRequiredTitle => 'Biometria necessária'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Credenciais necessários do dispositivo'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Credenciais necessários do dispositivo'; + + @override + String get goToSettings => 'Ir para Opções'; + + @override + String get androidGoToSettingsDescription => + 'A autenticação biométrica não está configurada no seu dispositivo. Vá em \'Configurações > Segurança\' para adicionar a autenticação biométrica.'; + + @override + String get iOSLockOut => + 'A autenticação biométrica está desativada. Bloqueie e desbloqueie sua tela para ativá-la.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'E-mail já registrado.'; + + @override + String get emailNotRegistered => 'E-mail não registrado.'; + + @override + String get thisEmailIsAlreadyInUse => 'Este e-mail já está em uso'; + + @override + String emailChangedTo(String newEmail) { + return 'E-mail alterado para $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'A autenticação falhou. Tente novamente'; + + @override + String get authenticationSuccessful => 'Autenticado!'; + + @override + String get sessionExpired => 'Sessão expirada'; + + @override + String get incorrectRecoveryKey => 'Chave de recuperação incorreta'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'A chave de recuperação inserida está incorreta'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Autenticação de dois fatores redefinida com sucesso'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Seu código de verificação expirou'; + + @override + String get incorrectCode => 'Código incorreto'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'O código inserido está incorreto'; + + @override + String get developerSettings => 'Opções de Desenvolvedor'; + + @override + String get serverEndpoint => 'Endpoint do servidor'; + + @override + String get invalidEndpoint => 'Endpoint inválido'; + + @override + String get invalidEndpointMessage => + 'Desculpe, o ponto de acesso inserido é inválido. Insira um ponto de acesso válido e tente novamente.'; + + @override + String get endpointUpdatedMessage => 'O endpoint foi atualizado'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_ro.dart b/mobile/packages/strings/lib/l10n/strings_localizations_ro.dart new file mode 100644 index 0000000000..432e033823 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_ro.dart @@ -0,0 +1,631 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Romanian Moldavian Moldovan (`ro`). +class StringsLocalizationsRo extends StringsLocalizations { + StringsLocalizationsRo([String locale = 'ro']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'Eroare'; + + @override + String get ok => 'Ok'; + + @override + String get faq => 'Întrebări frecvente'; + + @override + String get contactSupport => 'Contactează suportul'; + + @override + String get emailYourLogs => 'Email your logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Te rugăm să trimiți jurnalele la $toEmail'; + } + + @override + String get copyEmailAddress => 'Copiază adresa de e-mail'; + + @override + String get exportLogs => 'Export logs'; + + @override + String get cancel => 'Anulare'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Raportează o eroare'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'Salvare'; + + @override + String get send => 'Trimitere'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'E-mail'; + + @override + String get verify => 'Verify'; + + @override + String get invalidEmailTitle => 'Adresa e-mail nu este validă'; + + @override + String get invalidEmailMessage => + 'Te rugăm să introduci o adresă de e-mail validă.'; + + @override + String get pleaseWait => 'Te rog așteaptă...'; + + @override + String get verifyPassword => 'Verifică parola'; + + @override + String get incorrectPasswordTitle => 'Parolă incorectă'; + + @override + String get pleaseTryAgain => 'Te rugăm să încerci din nou'; + + @override + String get enterPassword => 'Introdu parola'; + + @override + String get enterYourPasswordHint => 'Introdu parola'; + + @override + String get activeSessions => 'Sesiuni active'; + + @override + String get oops => 'Ups'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Ceva n-a mers bine, te rog încearcă din nou'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'Terminate session?'; + + @override + String get terminate => 'Terminate'; + + @override + String get thisDevice => 'Acest dispozitiv'; + + @override + String get createAccount => 'Create account'; + + @override + String get weakStrength => 'Weak'; + + @override + String get moderateStrength => 'Moderate'; + + @override + String get strongStrength => 'Strong'; + + @override + String get deleteAccount => 'Ștergere cont'; + + @override + String get deleteAccountQuery => + 'We\'ll be sorry to see you go. Are you facing some issue?'; + + @override + String get yesSendFeedbackAction => 'Da, trimite feedback'; + + @override + String get noDeleteAccountAction => 'Nu, șterge contul'; + + @override + String get initiateAccountDeleteTitle => + 'Te rugăm să te autentifici pentru a iniția ștergerea contului'; + + @override + String get confirmAccountDeleteTitle => 'Confirm account deletion'; + + @override + String get 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.'; + + @override + String get delete => 'Ștergere'; + + @override + String get createNewAccount => 'Create new account'; + + @override + String get password => 'Parolă'; + + @override + String get confirmPassword => 'Confirmă parola'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Password strength: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'We don\'t track app installs. It\'d help if you told us where you found us!'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Termeni'; + + @override + String get privacyPolicyTitle => 'Privacy Policy'; + + @override + String get ackPasswordLostWarning => + 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'; + + @override + String get encryption => 'Encryption'; + + @override + String get logInLabel => 'Log in'; + + @override + String get welcomeBack => 'Bine ai revenit!'; + + @override + String get loginTerms => + 'By clicking log in, I agree to the terms of service and privacy policy'; + + @override + String get noInternetConnection => 'Nu există conexiune la internet'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Please check your internet connection and try again.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Recreează parola'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'Use recovery key'; + + @override + String get forgotPassword => 'Am uitat parola'; + + @override + String get changeEmail => 'Schimbă e-mailul'; + + @override + String get verifyEmail => 'Verifică e-mail'; + + @override + String weHaveSendEmailTo(String email) { + return 'Am trimis un e-mail la $email'; + } + + @override + String get toResetVerifyEmail => + 'Pentru a reseta parola, te rugăm să confirmi mai întâi adresa de e-mail.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Apasă pentru a introduce codul'; + + @override + String get sendEmail => 'Trimite e-mail'; + + @override + String get resendEmail => 'Retrimite e-mail'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Sesiune expirată'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Încearcă din nou'; + + @override + String get checkStatus => 'Verifică status'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Recuperare cont'; + + @override + String get setPasswordTitle => 'Setează parola'; + + @override + String get changePasswordTitle => 'Schimbă parola'; + + @override + String get resetPasswordTitle => 'Resetează parola'; + + @override + String get encryptionKeys => 'Encryption keys'; + + @override + String get enterPasswordToEncrypt => + 'Introdu o parolă pe care o putem folosi pentru a-ți cripta datele'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'Nu stocăm această parolă, deci dacă o uiți, nu îți putem decripta datele'; + + @override + String get howItWorks => 'How it works'; + + @override + String get generatingEncryptionKeys => 'Generating encryption keys...'; + + @override + String get passwordChangedSuccessfully => + 'Parola a fost modificată cu succes'; + + @override + String get signOutFromOtherDevices => 'Deconectare de pe alte dispozitive'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'Deconectează alte dispozitive'; + + @override + String get doNotSignOut => 'Nu te deconecta'; + + @override + String get generatingEncryptionKeysTitle => 'Generating encryption keys...'; + + @override + String get continueLabel => 'Continue'; + + @override + String get insecureDevice => 'Insecure device'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Cheie de recuperare salvată în clipboard'; + + @override + String get recoveryKey => 'Cheie de recuperare'; + + @override + String get recoveryKeyOnForgotPassword => + 'Dacă îți uiți parola, singura modalitate prin care poți recupera datele este cu această cheie.'; + + @override + String get recoveryKeySaveDescription => + 'Nu stocăm această cheie, vă rugăm salvați această cheie de 24 de cuvinte într-un loc sigur.'; + + @override + String get doThisLater => 'Do this later'; + + @override + String get saveKey => 'Salvare cheie'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'No recovery key?'; + + @override + String get twoFactorAuthTitle => 'Autentificare cu doi factori'; + + @override + String get enterCodeHint => + 'Enter the 6-digit code from\nyour authenticator app'; + + @override + String get lostDeviceTitle => 'Lost device?'; + + @override + String get enterRecoveryKeyHint => 'Enter your recovery key'; + + @override + String get recover => 'Recuperează'; + + @override + String get loggingOut => 'Deconectare...'; + + @override + String get immediately => 'Immediately'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Hide content'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Tap to unlock'; + + @override + String get areYouSureYouWantToLogout => 'Are you sure you want to logout?'; + + @override + String get yesLogout => 'Yes, logout'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'Enter PIN'; + + @override + String get setNewPin => 'Set new PIN'; + + @override + String get confirm => 'Confirm'; + + @override + String get reEnterPassword => 'Re-enter password'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verifică identitatea'; + + @override + String get androidBiometricNotRecognized => + 'Neidentificat. Încearcă din nou.'; + + @override + String get androidBiometricSuccess => 'Succes'; + + @override + String get androidCancelButton => 'Anulare'; + + @override + String get androidSignInTitle => 'Autentificare necesară'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Mergi la setări'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'Ok'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => + 'Această adresă de e-mail este deja folosită'; + + @override + String emailChangedTo(String newEmail) { + return 'E-mail modificat în $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'Sesiune expirată'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Incorrect code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart b/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart new file mode 100644 index 0000000000..52dfd4e049 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_ru.dart @@ -0,0 +1,632 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Russian (`ru`). +class StringsLocalizationsRu extends StringsLocalizations { + StringsLocalizationsRu([String locale = 'ru']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Не удается подключиться к Ente, пожалуйста, проверьте настройки своей сети и обратитесь в службу поддержки, если ошибка повторится.'; + + @override + String get networkConnectionRefusedErr => + 'Не удается подключиться к Ente, пожалуйста, повторите попытку через некоторое время. Если ошибка не устраняется, обратитесь в службу поддержки.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Похоже, что-то пошло не так. Пожалуйста, повторите попытку через некоторое время. Если ошибка повторится, обратитесь в нашу службу поддержки.'; + + @override + String get error => 'Ошибка'; + + @override + String get ok => 'Ок'; + + @override + String get faq => 'ЧаВо'; + + @override + String get contactSupport => 'Связаться с поддержкой'; + + @override + String get emailYourLogs => 'Отправить свои журналы'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Пожалуйста, отправьте журналы на \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Копировать адрес электронной почты'; + + @override + String get exportLogs => 'Экспорт журналов'; + + @override + String get cancel => 'Отмена'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Сообщить об ошибке'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Подключено к $endpoint'; + } + + @override + String get save => 'Сохранить'; + + @override + String get send => 'Отправить'; + + @override + String get saveOrSendDescription => + 'Вы хотите сохранить это в хранилище (папку загрузок по умолчанию) или отправить в другие приложения?'; + + @override + String get saveOnlyDescription => + 'Вы хотите сохранить это в хранилище (по умолчанию папка загрузок)?'; + + @override + String get enterNewEmailHint => 'Введите ваш новый адрес электронной почты'; + + @override + String get email => 'Электронная почта'; + + @override + String get verify => 'Подтвердить'; + + @override + String get invalidEmailTitle => 'Неверный адрес электронной почты'; + + @override + String get invalidEmailMessage => + 'Пожалуйста, введите действительный адрес электронной почты.'; + + @override + String get pleaseWait => 'Пожалуйста, подождите...'; + + @override + String get verifyPassword => 'Подтверждение пароля'; + + @override + String get incorrectPasswordTitle => 'Неправильный пароль'; + + @override + String get pleaseTryAgain => 'Пожалуйста, попробуйте ещё раз'; + + @override + String get enterPassword => 'Введите пароль'; + + @override + String get enterYourPasswordHint => 'Введите пароль'; + + @override + String get activeSessions => 'Активные сеансы'; + + @override + String get oops => 'Ой'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Что-то пошло не так. Попробуйте еще раз'; + + @override + String get thisWillLogYouOutOfThisDevice => 'Вы выйдете из этого устройства!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Вы выйдете из списка следующих устройств:'; + + @override + String get terminateSession => 'Завершить сеанс?'; + + @override + String get terminate => 'Завершить'; + + @override + String get thisDevice => 'Это устройство'; + + @override + String get createAccount => 'Создать аккаунт'; + + @override + String get weakStrength => 'Слабый'; + + @override + String get moderateStrength => 'Средний'; + + @override + String get strongStrength => 'Сильный'; + + @override + String get deleteAccount => 'Удалить аккаунт'; + + @override + String get deleteAccountQuery => + 'Нам будет жаль, если вы уйдете. Вы столкнулись с какой-то проблемой?'; + + @override + String get yesSendFeedbackAction => 'Да, отправить отзыв'; + + @override + String get noDeleteAccountAction => 'Нет, удалить аккаунт'; + + @override + String get initiateAccountDeleteTitle => + 'Пожалуйста, авторизуйтесь, чтобы начать удаление аккаунта'; + + @override + String get confirmAccountDeleteTitle => 'Подтвердить удаление аккаунта'; + + @override + String get confirmAccountDeleteMessage => + 'Эта учетная запись связана с другими приложениями Ente, если вы ими пользуетесь.\n\nЗагруженные вами данные во всех приложениях Ente будут запланированы к удалению, а ваша учетная запись будет удалена без возможности восстановления.'; + + @override + String get delete => 'Удалить'; + + @override + String get createNewAccount => 'Создать новый аккаунт'; + + @override + String get password => 'Пароль'; + + @override + String get confirmPassword => 'Подтвердить пароль'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Мощность пароля: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Как вы узнали о Ente? (необязательно)'; + + @override + String get hearUsExplanation => + 'Мы не отслеживаем установки приложений. Было бы полезно, если бы вы сказали, где нас нашли!'; + + @override + String get signUpTerms => + 'Я согласен с условиями предоставления услуг и политикой конфиденциальности'; + + @override + String get termsOfServicesTitle => 'Условия использования'; + + @override + String get privacyPolicyTitle => 'Политика конфиденциальности'; + + @override + String get ackPasswordLostWarning => + 'Я понимаю, что если я потеряю свой пароль, я могу потерять свои данные, так как мои данные в сквозном шифровании.'; + + @override + String get encryption => 'Шифрование'; + + @override + String get logInLabel => 'Войти'; + + @override + String get welcomeBack => 'С возвращением!'; + + @override + String get loginTerms => + 'Нажимая на логин, я принимаю условия использования и политику конфиденциальности'; + + @override + String get noInternetConnection => 'Нет подключения к Интернету'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Проверьте подключение к Интернету и повторите попытку.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Проверка не удалась, попробуйте еще раз'; + + @override + String get recreatePasswordTitle => 'Пересоздать пароль'; + + @override + String get recreatePasswordBody => + 'Текущее устройство недостаточно мощно для верификации пароля, но мы можем регенерировать так, как это работает со всеми устройствами.\n\nПожалуйста, войдите, используя ваш ключ восстановления и сгенерируйте ваш пароль (вы можете использовать тот же пароль, если пожелаете).'; + + @override + String get useRecoveryKey => 'Использовать ключ восстановления'; + + @override + String get forgotPassword => 'Забыл пароль'; + + @override + String get changeEmail => 'Изменить адрес электронной почты'; + + @override + String get verifyEmail => 'Подтвердить адрес электронной почты'; + + @override + String weHaveSendEmailTo(String email) { + return 'Мы отправили письмо на $email'; + } + + @override + String get toResetVerifyEmail => + 'Подтвердите адрес электронной почты, чтобы сбросить пароль.'; + + @override + String get checkInboxAndSpamFolder => + 'Пожалуйста, проверьте свой почтовый ящик (и спам) для завершения верификации'; + + @override + String get tapToEnterCode => 'Нажмите, чтобы ввести код'; + + @override + String get sendEmail => 'Отправить электронное письмо'; + + @override + String get resendEmail => 'Отправить письмо еще раз'; + + @override + String get passKeyPendingVerification => 'Верификация еще не завершена'; + + @override + String get loginSessionExpired => 'Сессия недействительна'; + + @override + String get loginSessionExpiredDetails => 'Сессия истекла. Войдите снова.'; + + @override + String get passkeyAuthTitle => 'Проверка с помощью ключа доступа'; + + @override + String get waitingForVerification => 'Ожидание подтверждения...'; + + @override + String get tryAgain => 'Попробовать снова'; + + @override + String get checkStatus => 'Проверить статус'; + + @override + String get loginWithTOTP => 'Войти с помощью TOTP'; + + @override + String get recoverAccount => 'Восстановить аккаунт'; + + @override + String get setPasswordTitle => 'Поставить пароль'; + + @override + String get changePasswordTitle => 'Изменить пароль'; + + @override + String get resetPasswordTitle => 'Сбросить пароль'; + + @override + String get encryptionKeys => 'Ключи шифрования'; + + @override + String get enterPasswordToEncrypt => + 'Введите пароль, который мы можем использовать для шифрования ваших данных'; + + @override + String get enterNewPasswordToEncrypt => + 'Введите новый пароль, который мы можем использовать для шифрования ваших данных'; + + @override + String get passwordWarning => + 'Мы не храним этот пароль, поэтому если вы забудете его, мы не сможем расшифровать ваши данные'; + + @override + String get howItWorks => 'Как это работает'; + + @override + String get generatingEncryptionKeys => 'Генерируем ключи шифрования...'; + + @override + String get passwordChangedSuccessfully => 'Пароль успешно изменён'; + + @override + String get signOutFromOtherDevices => 'Выйти из других устройств'; + + @override + String get signOutOtherBody => + 'Если вы думаете, что кто-то может знать ваш пароль, вы можете принудительно выйти из всех устройств.'; + + @override + String get signOutOtherDevices => 'Выйти из других устройств'; + + @override + String get doNotSignOut => 'Не выходить'; + + @override + String get generatingEncryptionKeysTitle => 'Генерируем ключи шифрования...'; + + @override + String get continueLabel => 'Далее'; + + @override + String get insecureDevice => 'Небезопасное устройство'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'К сожалению, мы не смогли сгенерировать безопасные ключи на этом устройстве.\n\nПожалуйста, зарегистрируйтесь с другого устройства.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Ключ восстановления скопирован в буфер обмена'; + + @override + String get recoveryKey => 'Ключ восстановления'; + + @override + String get recoveryKeyOnForgotPassword => + 'Если вы забыли свой пароль, то восстановить данные можно только с помощью этого ключа.'; + + @override + String get recoveryKeySaveDescription => + 'Мы не храним этот ключ, пожалуйста, сохраните этот ключ в безопасном месте.'; + + @override + String get doThisLater => 'Сделать позже'; + + @override + String get saveKey => 'Сохранить ключ'; + + @override + String get recoveryKeySaved => + 'Ключ восстановления сохранён в папке Загрузки!'; + + @override + String get noRecoveryKeyTitle => 'Нет ключа восстановления?'; + + @override + String get twoFactorAuthTitle => 'Двухфакторная аутентификация'; + + @override + String get enterCodeHint => + 'Введите 6-значный код из\nвашего приложения-аутентификатора'; + + @override + String get lostDeviceTitle => 'Потеряно устройство?'; + + @override + String get enterRecoveryKeyHint => 'Введите ключ восстановления'; + + @override + String get recover => 'Восстановить'; + + @override + String get loggingOut => 'Выходим...'; + + @override + String get immediately => 'Немедленно'; + + @override + String get appLock => 'Блокировка приложения'; + + @override + String get autoLock => 'Автоблокировка'; + + @override + String get noSystemLockFound => 'Системная блокировка не найдена'; + + @override + String get deviceLockEnablePreSteps => + 'Чтобы включить блокировку устройства, пожалуйста, настройте пароль или блокировку экрана в настройках системы.'; + + @override + String get appLockDescription => + 'Выберите между экраном блокировки вашего устройства и пользовательским экраном блокировки с PIN-кодом или паролем.'; + + @override + String get deviceLock => 'Блокировка устройства'; + + @override + String get pinLock => 'Pin блокировка'; + + @override + String get autoLockFeatureDescription => + 'Время в фоне, после которого приложение блокируется'; + + @override + String get hideContent => 'Скрыть содержимое'; + + @override + String get hideContentDescriptionAndroid => + 'Скрывает содержимое приложения в переключателе приложений и отключает скриншоты'; + + @override + String get hideContentDescriptioniOS => + 'Скрывает содержимое приложения в переключателе приложений'; + + @override + String get tooManyIncorrectAttempts => 'Слишком много неудачных попыток'; + + @override + String get tapToUnlock => 'Нажмите для разблокировки'; + + @override + String get areYouSureYouWantToLogout => 'Вы уверены, что хотите выйти?'; + + @override + String get yesLogout => 'Да, выйти'; + + @override + String get authToViewSecrets => + 'Пожалуйста, авторизуйтесь для просмотра ваших секретов'; + + @override + String get next => 'Далее'; + + @override + String get setNewPassword => 'Задать новый пароль'; + + @override + String get enterPin => 'Введите PIN'; + + @override + String get setNewPin => 'Установите новый PIN'; + + @override + String get confirm => 'Подтвердить'; + + @override + String get reEnterPassword => 'Подтвердите пароль'; + + @override + String get reEnterPin => 'Введите PIN-код ещё раз'; + + @override + String get androidBiometricHint => 'Подтвердите личность'; + + @override + String get androidBiometricNotRecognized => + 'Не распознано. Попробуйте еще раз.'; + + @override + String get androidBiometricSuccess => 'Успешно'; + + @override + String get androidCancelButton => 'Отменить'; + + @override + String get androidSignInTitle => 'Требуется аутентификация'; + + @override + String get androidBiometricRequiredTitle => 'Требуется биометрия'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Требуются учетные данные устройства'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Требуются учетные данные устройства'; + + @override + String get goToSettings => 'Перейдите к настройкам'; + + @override + String get androidGoToSettingsDescription => + 'Биометрическая аутентификация не настроена на вашем устройстве. Перейдите в \"Настройки > Безопасность\", чтобы добавить биометрическую аутентификацию.'; + + @override + String get iOSLockOut => + 'Биометрическая аутентификация отключена. Пожалуйста, заблокируйте и разблокируйте экран, чтобы включить ее.'; + + @override + String get iOSOkButton => 'ОК'; + + @override + String get emailAlreadyRegistered => + 'Адрес электронной почты уже зарегистрирован.'; + + @override + String get emailNotRegistered => + 'Адрес электронной почты не зарегистрирован.'; + + @override + String get thisEmailIsAlreadyInUse => + 'Этот адрес электронной почты уже используется'; + + @override + String emailChangedTo(String newEmail) { + return 'Адрес электронной почты изменен на $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Аутентификация не удалась, попробуйте еще раз'; + + @override + String get authenticationSuccessful => 'Аутентификация прошла успешно!'; + + @override + String get sessionExpired => 'Сеанс истек'; + + @override + String get incorrectRecoveryKey => 'Неправильный ключ восстановления'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Введен неправильный ключ восстановления'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Двухфакторная аутентификация успешно сброшена'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Срок действия вашего проверочного кода истек'; + + @override + String get incorrectCode => 'Неверный код'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Извините, введенный вами код неверный'; + + @override + String get developerSettings => 'Настройки разработчика'; + + @override + String get serverEndpoint => 'Конечная точка сервера'; + + @override + String get invalidEndpoint => 'Неверная конечная точка'; + + @override + String get invalidEndpointMessage => + 'Извините, введенная вами конечная точка неверна. Пожалуйста, введите корректную конечную точку и повторите попытку.'; + + @override + String get endpointUpdatedMessage => 'Конечная точка успешно обновлена'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart b/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart new file mode 100644 index 0000000000..6ab32314df --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_sk.dart @@ -0,0 +1,629 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Slovak (`sk`). +class StringsLocalizationsSk extends StringsLocalizations { + StringsLocalizationsSk([String locale = 'sk']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Nemožno sa pripojiť k Ente, skontrolujte svoje nastavenia siete a kontaktujte podporu, ak chyba pretrváva.'; + + @override + String get networkConnectionRefusedErr => + 'Nemožno sa pripojiť k Ente, skúste znova v krátkom čase. Ak chyba pretrváva, kontaktujte podporu.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Vyzerá to, že sa niečo pokazilo. Skúste znova v krátkom čase. Ak chyba pretrváva, kontaktujte náš tím podpory.'; + + @override + String get error => 'Chyba'; + + @override + String get ok => 'Ok'; + + @override + String get faq => 'Často kladené otázky'; + + @override + String get contactSupport => 'Kontaktovať podporu'; + + @override + String get emailYourLogs => 'Odoslať vaše logy emailom'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Prosím, pošlite logy na adresu \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Skopírovať e-mailovú adresu'; + + @override + String get exportLogs => 'Exportovať logy'; + + @override + String get cancel => 'Zrušiť'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Nahlásiť chybu'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Pripojený k endpointu $endpoint'; + } + + @override + String get save => 'Uložiť'; + + @override + String get send => 'Odoslať'; + + @override + String get saveOrSendDescription => + 'Chcete to uložiť do svojho zariadenia (predvolený priečinok Stiahnuté súbory) alebo to odoslať do iných aplikácií?'; + + @override + String get saveOnlyDescription => + 'Chcete to uložiť do svojho zariadenia (predvolený priečinok Stiahnuté súbory)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Email'; + + @override + String get verify => 'Overiť'; + + @override + String get invalidEmailTitle => 'Neplatná emailová adresa'; + + @override + String get invalidEmailMessage => 'Zadajte platnú e-mailovú adresu.'; + + @override + String get pleaseWait => 'Prosím počkajte...'; + + @override + String get verifyPassword => 'Potvrďte heslo'; + + @override + String get incorrectPasswordTitle => 'Nesprávne heslo'; + + @override + String get pleaseTryAgain => 'Prosím, skúste to znova'; + + @override + String get enterPassword => 'Zadajte heslo'; + + @override + String get enterYourPasswordHint => 'Zadajte vaše heslo'; + + @override + String get activeSessions => 'Aktívne relácie'; + + @override + String get oops => 'Ups'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Niečo sa pokazilo, skúste to prosím znova'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Toto vás odhlási z tohto zariadenia!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Toto vás odhlási z následujúceho zariadenia:'; + + @override + String get terminateSession => 'Ukončiť reláciu?'; + + @override + String get terminate => 'Ukončiť'; + + @override + String get thisDevice => 'Toto zariadenie'; + + @override + String get createAccount => 'Vytvoriť účet'; + + @override + String get weakStrength => 'Slabé'; + + @override + String get moderateStrength => 'Mierne'; + + @override + String get strongStrength => 'Silné'; + + @override + String get deleteAccount => 'Odstrániť účet'; + + @override + String get deleteAccountQuery => + 'Bude nám ľúto ak odídeš. Máš nejaký problém?'; + + @override + String get yesSendFeedbackAction => 'Áno, odoslať spätnú väzbu'; + + @override + String get noDeleteAccountAction => 'Nie, odstrániť účet'; + + @override + String get initiateAccountDeleteTitle => + 'Je potrebné overenie pre spustenie odstránenia účtu'; + + @override + String get confirmAccountDeleteTitle => 'Potvrď odstránenie účtu'; + + @override + String get confirmAccountDeleteMessage => + 'Tento účet je prepojený s inými aplikáciami Ente, ak nejaké používaš.\n\nTvoje nahrané údaje vo všetkých Ente aplikáciách budú naplánované na odstránenie a tvoj účet bude natrvalo odstránený.'; + + @override + String get delete => 'Odstrániť'; + + @override + String get createNewAccount => 'Vytvoriť nový účet'; + + @override + String get password => 'Heslo'; + + @override + String get confirmPassword => 'Potvrdiť heslo'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Sila hesla: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Ako ste sa dozvedeli o Ente? (voliteľné)'; + + @override + String get hearUsExplanation => + 'Nesledujeme inštalácie aplikácie. Veľmi by nám pomohlo, keby ste nám povedali, ako ste sa o nás dozvedeli!'; + + @override + String get signUpTerms => + 'Súhlasím s podmienkami používania a zásadami ochrany osobných údajov'; + + @override + String get termsOfServicesTitle => 'Podmienky používania'; + + @override + String get privacyPolicyTitle => 'Zásady ochrany osobných údajov'; + + @override + String get ackPasswordLostWarning => + 'Rozumiem, že ak stratím alebo zabudnem heslo, môžem stratiť svoje údaje, pretože moje údaje sú šifrované end-to-end.'; + + @override + String get encryption => 'Šifrovanie'; + + @override + String get logInLabel => 'Prihlásenie'; + + @override + String get welcomeBack => 'Vitajte späť!'; + + @override + String get loginTerms => + 'Kliknutím na prihlásenie, súhlasím s podmienkami používania a zásadami ochrany osobných údajov'; + + @override + String get noInternetConnection => 'Žiadne internetové pripojenie'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Skontrolujte svoje internetové pripojenie a skúste to znova.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Overenie zlyhalo, skúste to znova'; + + @override + String get recreatePasswordTitle => 'Resetovať heslo'; + + @override + String get recreatePasswordBody => + 'Aktuálne zariadenie nie je dostatočne výkonné na overenie vášho hesla, avšak vieme ho regenerovať spôsobom, ktorý funguje vo všetkých zariadeniach.\n\nPrihláste sa pomocou kľúča na obnovenie a znovu vygenerujte svoje heslo (ak si prajete, môžete znova použiť rovnaké).'; + + @override + String get useRecoveryKey => 'Použiť kľúč na obnovenie'; + + @override + String get forgotPassword => 'Zabudnuté heslo'; + + @override + String get changeEmail => 'Zmeniť e-mail'; + + @override + String get verifyEmail => 'Overiť email'; + + @override + String weHaveSendEmailTo(String email) { + return 'Odoslali sme email na adresu $email'; + } + + @override + String get toResetVerifyEmail => + 'Ak chcete obnoviť svoje heslo, najskôr overte svoj email.'; + + @override + String get checkInboxAndSpamFolder => + 'Skontrolujte svoju doručenú poštu (a spam) pre dokončenie overenia'; + + @override + String get tapToEnterCode => 'Klepnutím zadajte kód'; + + @override + String get sendEmail => 'Odoslať email'; + + @override + String get resendEmail => 'Znovu odoslať email'; + + @override + String get passKeyPendingVerification => 'Overenie stále prebieha'; + + @override + String get loginSessionExpired => 'Relácia vypršala'; + + @override + String get loginSessionExpiredDetails => + 'Vaša relácia vypršala. Prosím, prihláste sa znovu.'; + + @override + String get passkeyAuthTitle => 'Overenie pomocou passkey'; + + @override + String get waitingForVerification => 'Čakanie na overenie...'; + + @override + String get tryAgain => 'Skúsiť znova'; + + @override + String get checkStatus => 'Overiť stav'; + + @override + String get loginWithTOTP => 'Prihlásenie pomocou TOTP'; + + @override + String get recoverAccount => 'Obnoviť účet'; + + @override + String get setPasswordTitle => 'Nastaviť heslo'; + + @override + String get changePasswordTitle => 'Zmeniť heslo'; + + @override + String get resetPasswordTitle => 'Obnoviť heslo'; + + @override + String get encryptionKeys => 'Šifrovacie kľúče'; + + @override + String get enterPasswordToEncrypt => + 'Zadajte heslo, ktoré môžeme použiť na šifrovanie vašich údajov'; + + @override + String get enterNewPasswordToEncrypt => + 'Zadajte nové heslo, ktoré môžeme použiť na šifrovanie vašich údajov'; + + @override + String get passwordWarning => + 'Ente neukladá tohto heslo. V prípade, že ho zabudnete, nie sme schopní rozšifrovať vaše údaje'; + + @override + String get howItWorks => 'Ako to funguje'; + + @override + String get generatingEncryptionKeys => 'Generovanie šifrovacích kľúčov...'; + + @override + String get passwordChangedSuccessfully => 'Heslo bolo úspešne zmenené'; + + @override + String get signOutFromOtherDevices => 'Odhlásiť sa z iných zariadení'; + + @override + String get signOutOtherBody => + 'Ak si myslíš, že by niekto mohol poznať tvoje heslo, môžeš vynútiť odhlásenie všetkých ostatných zariadení používajúcich tvoj účet.'; + + @override + String get signOutOtherDevices => 'Odhlásiť iné zariadenie'; + + @override + String get doNotSignOut => 'Neodhlasovať'; + + @override + String get generatingEncryptionKeysTitle => + 'Generovanie šifrovacích kľúčov...'; + + @override + String get continueLabel => 'Pokračovať'; + + @override + String get insecureDevice => 'Slabo zabezpečené zariadenie'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Ospravedlňujeme sa, v tomto zariadení sme nemohli generovať bezpečnostné kľúče.\n\nzaregistrujte sa z iného zariadenia.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Skopírovaný kód pre obnovenie do schránky'; + + @override + String get recoveryKey => 'Kľúč pre obnovenie'; + + @override + String get recoveryKeyOnForgotPassword => + 'Ak zabudnete heslo, jediným spôsobom, ako môžete obnoviť svoje údaje, je tento kľúč.'; + + @override + String get recoveryKeySaveDescription => + 'My tento kľúč neuchovávame, uložte si tento kľúč obsahujúci 24 slov na bezpečnom mieste.'; + + @override + String get doThisLater => 'Urobiť to neskôr'; + + @override + String get saveKey => 'Uložiť kľúč'; + + @override + String get recoveryKeySaved => + 'Kľúč na obnovenie uložený v priečinku Stiahnutých súborov!'; + + @override + String get noRecoveryKeyTitle => 'Nemáte kľúč pre obnovenie?'; + + @override + String get twoFactorAuthTitle => 'Dvojfaktorové overovanie'; + + @override + String get enterCodeHint => + 'Zadajte 6-miestny kód z\nvašej overovacej aplikácie'; + + @override + String get lostDeviceTitle => 'Stratené zariadenie?'; + + @override + String get enterRecoveryKeyHint => 'Vložte váš kód pre obnovenie'; + + @override + String get recover => 'Obnoviť'; + + @override + String get loggingOut => 'Odhlasovanie...'; + + @override + String get immediately => 'Okamžite'; + + @override + String get appLock => 'Zámok aplikácie'; + + @override + String get autoLock => 'Automatické uzamknutie'; + + @override + String get noSystemLockFound => 'Nenájdená žiadna zámka obrazovky'; + + @override + String get deviceLockEnablePreSteps => + 'Pre povolenie zámku zariadenia, nastavte prístupový kód zariadenia alebo zámok obrazovky v nastaveniach systému.'; + + @override + String get appLockDescription => + 'Vyberte si medzi predvolenou zámkou obrazovky vášho zariadenia a vlastnou zámkou obrazovky s PIN kódom alebo heslom.'; + + @override + String get deviceLock => 'Zámok zariadenia'; + + @override + String get pinLock => 'Zámok PIN'; + + @override + String get autoLockFeatureDescription => + 'Čas, po ktorom sa aplikácia uzamkne po nečinnosti'; + + @override + String get hideContent => 'Skryť obsah'; + + @override + String get hideContentDescriptionAndroid => + 'Skrýva obsah v prepínači aplikácii a zakazuje snímky obrazovky'; + + @override + String get hideContentDescriptioniOS => 'Skrýva obsah v prepínači aplikácii'; + + @override + String get tooManyIncorrectAttempts => 'Príliš veľa chybných pokusov'; + + @override + String get tapToUnlock => 'Ťuknutím odomknete'; + + @override + String get areYouSureYouWantToLogout => 'Naozaj sa chcete odhlásiť?'; + + @override + String get yesLogout => 'Áno, odhlásiť sa'; + + @override + String get authToViewSecrets => + 'Pre zobrazenie vašich tajných údajov sa musíte overiť'; + + @override + String get next => 'Ďalej'; + + @override + String get setNewPassword => 'Nastaviť nové heslo'; + + @override + String get enterPin => 'Zadajte PIN'; + + @override + String get setNewPin => 'Nastaviť nový PIN'; + + @override + String get confirm => 'Potvrdiť'; + + @override + String get reEnterPassword => 'Zadajte heslo znova'; + + @override + String get reEnterPin => 'Zadajte PIN znova'; + + @override + String get androidBiometricHint => 'Overiť identitu'; + + @override + String get androidBiometricNotRecognized => 'Nerozpoznané. Skúste znova.'; + + @override + String get androidBiometricSuccess => 'Overenie úspešné'; + + @override + String get androidCancelButton => 'Zrušiť'; + + @override + String get androidSignInTitle => 'Vyžaduje sa overenie'; + + @override + String get androidBiometricRequiredTitle => 'Vyžaduje sa biometria'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Vyžadujú sa poverenia zariadenia'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Vyžadujú sa poverenia zariadenia'; + + @override + String get goToSettings => 'Prejsť do nastavení'; + + @override + String get androidGoToSettingsDescription => + 'Overenie pomocou biometrie nie je na vašom zariadení nastavené. Prejdite na \'Nastavenie > Zabezpečenie\' a pridajte overenie pomocou biometrie.'; + + @override + String get iOSLockOut => + 'Overenie pomocou biometrie je zakázané. Zamknite a odomknite svoju obrazovku, aby ste ho povolili.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => 'Tento e-mail sa už používa'; + + @override + String emailChangedTo(String newEmail) { + return 'Emailová adresa bola zmenená na $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Overenie zlyhalo. Skúste to znova'; + + @override + String get authenticationSuccessful => 'Overenie sa podarilo!'; + + @override + String get sessionExpired => 'Relácia vypršala'; + + @override + String get incorrectRecoveryKey => 'Nesprávny kľúč na obnovenie'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Kľúč na obnovenie, ktorý ste zadali, je nesprávny'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Dvojfaktorové overovanie bolo úspešne obnovené'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Platnosť overovacieho kódu uplynula'; + + @override + String get incorrectCode => 'Neplatný kód'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Ľutujeme, zadaný kód je nesprávny'; + + @override + String get developerSettings => 'Nastavenia pre vývojárov'; + + @override + String get serverEndpoint => 'Endpoint servera'; + + @override + String get invalidEndpoint => 'Neplatný endpoint'; + + @override + String get invalidEndpointMessage => + 'Ospravedlňujeme sa, endpoint, ktorý ste zadali, je neplatný. Zadajte platný endpoint a skúste to znova.'; + + @override + String get endpointUpdatedMessage => 'Endpoint úspešne aktualizovaný'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_sl.dart b/mobile/packages/strings/lib/l10n/strings_localizations_sl.dart new file mode 100644 index 0000000000..8571b1983f --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_sl.dart @@ -0,0 +1,629 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Slovenian (`sl`). +class StringsLocalizationsSl extends StringsLocalizations { + StringsLocalizationsSl([String locale = 'sl']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Ne morete se povezati z Ente, preverite omrežne nastavitve in se obrnite na podporo, če se napaka nadaljuje.'; + + @override + String get networkConnectionRefusedErr => + 'Ne morete se povezati z Ente, poskusite znova čez nekaj časa. Če se napaka nadaljuje, se obrnite na podporo.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Zdi se, da je šlo nekaj narobe. Po določenem času poskusite znova. Če se napaka nadaljuje, se obrnite na našo ekipo za podporo.'; + + @override + String get error => 'Napaka'; + + @override + String get ok => 'V redu'; + + @override + String get faq => 'Pogosta vprašanja'; + + @override + String get contactSupport => 'Stik s podporo'; + + @override + String get emailYourLogs => 'Pošlji loge po e-pošti'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Loge pošljite na naslov \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Kopiraj e-poštni naslov'; + + @override + String get exportLogs => 'Izvozi loge'; + + @override + String get cancel => 'Prekliči'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Prijavite napako'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Povezano na $endpoint'; + } + + @override + String get save => 'Shrani'; + + @override + String get send => 'Pošlji'; + + @override + String get saveOrSendDescription => + 'Želite to shraniti v shrambo (privzeto: mapa Prenosi) ali poslati drugim aplikacijam?'; + + @override + String get saveOnlyDescription => + 'Želite to shraniti v shrambo (privzeto: mapa Prenosi)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'E-pošta'; + + @override + String get verify => 'Preveri'; + + @override + String get invalidEmailTitle => 'Neveljaven e-poštni naslov'; + + @override + String get invalidEmailMessage => 'Prosimo vnesite veljaven e-poštni naslov.'; + + @override + String get pleaseWait => 'Prosim počakajte...'; + + @override + String get verifyPassword => 'Potrdite geslo'; + + @override + String get incorrectPasswordTitle => 'Nepravilno geslo'; + + @override + String get pleaseTryAgain => 'Prosimo, poskusite ponovno'; + + @override + String get enterPassword => 'Vnesite geslo'; + + @override + String get enterYourPasswordHint => 'Vnesite svoje geslo'; + + @override + String get activeSessions => 'Aktivne seje'; + + @override + String get oops => 'Ups'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Nekaj je šlo narobe, prosimo poizkusite znova.'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'To vas bo odjavilo iz te naprave!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'To vas bo odjavilo iz naslednje naprave:'; + + @override + String get terminateSession => 'Končaj sejo'; + + @override + String get terminate => 'Končaj'; + + @override + String get thisDevice => 'Ta naprava'; + + @override + String get createAccount => 'Ustvari račun'; + + @override + String get weakStrength => 'Šibko'; + + @override + String get moderateStrength => 'Zmerno'; + + @override + String get strongStrength => 'Močno'; + + @override + String get deleteAccount => 'Izbriši račun'; + + @override + String get deleteAccountQuery => + 'Žal nam je, da odhajate. Imate kakšne težave?'; + + @override + String get yesSendFeedbackAction => 'Ja, pošlji povratne informacije'; + + @override + String get noDeleteAccountAction => 'Ne, izbriši račun'; + + @override + String get initiateAccountDeleteTitle => 'Za izbris računa, se overite'; + + @override + String get confirmAccountDeleteTitle => 'Potrdi brisanje računa'; + + @override + String get confirmAccountDeleteMessage => + 'Ta račun je povezan z drugimi aplikacijami Ente, če jih uporabljate.\n\nVaši naloženi podatki v vseh aplikacijah Ente bodo načrtovane za izbris, vaš račun pa bo trajno izbrisan.'; + + @override + String get delete => ''; + + @override + String get createNewAccount => 'Ustvari nov račun'; + + @override + String get password => 'Geslo'; + + @override + String get confirmPassword => 'Potrdi geslo'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Moč gesla: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Kako ste slišali o Ente? (izbirno)'; + + @override + String get hearUsExplanation => + 'Namestitvam aplikacij ne sledimo. Pomagalo bi, če bi nam povedali, kje ste nas našli!'; + + @override + String get signUpTerms => + 'Strinjam se s pogoji uporabe in politiko zasebnosti'; + + @override + String get termsOfServicesTitle => 'Pogoji uporabe'; + + @override + String get privacyPolicyTitle => 'Politika zasebnosti'; + + @override + String get ackPasswordLostWarning => + 'Razumem, da lahko z izgubo gesla, izgubim svoje podatke, saj so end-to-end šifrirani'; + + @override + String get encryption => 'Šifriranje'; + + @override + String get logInLabel => 'Prijava'; + + @override + String get welcomeBack => 'Dobrodošli nazaj!'; + + @override + String get loginTerms => + 'S klikom na prijava, se strinjam s pogoji uporabe in politiko zasebnosti'; + + @override + String get noInternetConnection => 'Ni internetne povezave'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Preverite internetno povezavo in poskusite znova.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Potrjevanje ni bilo uspešno, prosimo poskusite znova.'; + + @override + String get recreatePasswordTitle => 'Ponovno ustvarite geslo'; + + @override + String get recreatePasswordBody => + 'Trenutna naprava, ni dovolj zmogljiva za preverjanje vašega gesla, a ga lahko generiramo na način, ki deluje z vsemi napravami.\n\nProsimo, prijavite se z vašim ključem za obnovo in ponovno ustvarite geslo (če želite lahko uporabite enako kot prej).'; + + @override + String get useRecoveryKey => 'Uporabi ključ za obnovo'; + + @override + String get forgotPassword => 'Pozabljeno geslo'; + + @override + String get changeEmail => 'Sprememba e-poštnega naslova'; + + @override + String get verifyEmail => 'Potrdite e-pošto'; + + @override + String weHaveSendEmailTo(String email) { + return 'Poslali smo e-pošto na $email'; + } + + @override + String get toResetVerifyEmail => + 'Če želite ponastaviti geslo, najprej potrdite svoj e-poštni naslov.'; + + @override + String get checkInboxAndSpamFolder => + 'Prosimo, preverite svoj e-poštni predal (in nezaželeno pošto), da končate verifikacijo'; + + @override + String get tapToEnterCode => 'Pritisni za vnos kode'; + + @override + String get sendEmail => 'Pošlji e-pošto'; + + @override + String get resendEmail => 'Ponovno pošlji e-pošto'; + + @override + String get passKeyPendingVerification => 'Preverjanje še ni zaključeno'; + + @override + String get loginSessionExpired => 'Seja je potekla'; + + @override + String get loginSessionExpiredDetails => + 'Vaša seja je potekla. Prosimo ponovno se prijavite.'; + + @override + String get passkeyAuthTitle => 'Potrditev ključa za dostop (passkey)'; + + @override + String get waitingForVerification => 'Čakanje na potrditev...'; + + @override + String get tryAgain => 'Poskusite ponovno'; + + @override + String get checkStatus => 'Preveri status'; + + @override + String get loginWithTOTP => 'Prijava z TOTP'; + + @override + String get recoverAccount => 'Obnovi račun'; + + @override + String get setPasswordTitle => 'Nastavite geslo'; + + @override + String get changePasswordTitle => 'Sprememba gesla'; + + @override + String get resetPasswordTitle => 'Ponastavitev gesla'; + + @override + String get encryptionKeys => 'Šifrirni ključi'; + + @override + String get enterPasswordToEncrypt => + 'Vnesite geslo, ki ga lahko uporabimo za šifriranje vaših podatkov'; + + @override + String get enterNewPasswordToEncrypt => + 'Vnesite novo geslo, ki ga lahko uporabimo za šifriranje vaših podatkov'; + + @override + String get passwordWarning => + 'Tega gesla ne shranjujemo, zato v primeru, da ga pozabite, ne moremo dešifrirati vaših podatkov.'; + + @override + String get howItWorks => 'Kako deluje? '; + + @override + String get generatingEncryptionKeys => 'Ustvarjanje ključe za šifriranje'; + + @override + String get passwordChangedSuccessfully => 'Geslo je bilo uspešno spremenjeno'; + + @override + String get signOutFromOtherDevices => 'Odjavi se iz ostalih naprav'; + + @override + String get signOutOtherBody => + 'Če menite, da bi lahko kdo poznal vaše geslo, lahko vse druge naprave, ki uporabljajo vaš račun, prisilite, da se odjavijo.'; + + @override + String get signOutOtherDevices => 'Odjavi ostale naprave'; + + @override + String get doNotSignOut => 'Ne odjavi se'; + + @override + String get generatingEncryptionKeysTitle => 'Generiramo ključe za šifriranje'; + + @override + String get continueLabel => 'Nadaljuj'; + + @override + String get insecureDevice => 'Nezanesljiva naprava'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Žal v tej napravi nismo mogli ustvariti varnih ključev.\n\nProsimo, prijavite se iz druge naprave.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Ključ za obnovo kopiran v odložišče'; + + @override + String get recoveryKey => 'Ključ za obnovitev'; + + @override + String get recoveryKeyOnForgotPassword => + 'Če pozabite svoje geslo, je edini način da obnovite svoje podatke s tem ključem'; + + @override + String get recoveryKeySaveDescription => + 'Tega ključa ne hranimo, prosimo shranite teh 24 besed na varnem'; + + @override + String get doThisLater => 'Stori to kasneje'; + + @override + String get saveKey => 'Shrani ključ'; + + @override + String get recoveryKeySaved => + 'Ključ za obnovitev je shranjen v mapi Prenosi!'; + + @override + String get noRecoveryKeyTitle => 'Nimate ključa za obnovo?'; + + @override + String get twoFactorAuthTitle => 'Dvojno preverjanja pristnosti'; + + @override + String get enterCodeHint => + 'Vnesite 6 mestno kodo iz vaše aplikacije za preverjanje pristnosti'; + + @override + String get lostDeviceTitle => 'Izgubljena naprava?'; + + @override + String get enterRecoveryKeyHint => 'Vnesite vaš ključ za obnovitev'; + + @override + String get recover => 'Obnovi'; + + @override + String get loggingOut => 'Odjavljanje...'; + + @override + String get immediately => 'Takoj'; + + @override + String get appLock => 'Zaklep aplikacije'; + + @override + String get autoLock => 'Samodejno zaklepanje'; + + @override + String get noSystemLockFound => 'Nobeno zaklepanje sistema ni bilo najdeno'; + + @override + String get deviceLockEnablePreSteps => + 'Da omogočite zaklepanje naprave, prosimo nastavite kodo ali zaklepanje zaslona v sistemskih nastavitvah.'; + + @override + String get appLockDescription => + 'Izbirate lahko med privzetim zaklenjenim zaslonom naprave in zaklenjenim zaslonom po meri s kodo PIN ali geslom.'; + + @override + String get deviceLock => 'Zaklepanje naprave'; + + @override + String get pinLock => 'Zaklepanje s PIN'; + + @override + String get autoLockFeatureDescription => + 'Čas po katerem se aplikacije zaklene, ko jo enkrat zapustite.'; + + @override + String get hideContent => 'Skrij vsebino'; + + @override + String get hideContentDescriptionAndroid => + 'Skrije vsebino aplikacije v menjalniku opravil in onemogoči posnetke zaslona'; + + @override + String get hideContentDescriptioniOS => + 'Skrije vsebino aplikacije v menjalniku opravil'; + + @override + String get tooManyIncorrectAttempts => 'Preveč nepravilnih poskusov'; + + @override + String get tapToUnlock => 'Kliknite za odklepanje'; + + @override + String get areYouSureYouWantToLogout => + 'Ali ste prepričani, da se želite odjaviti?'; + + @override + String get yesLogout => 'Ja, odjavi se'; + + @override + String get authToViewSecrets => + 'Če si želite ogledati svoje skrivne ključe, se overite'; + + @override + String get next => 'Naprej'; + + @override + String get setNewPassword => 'Nastavi novo geslo'; + + @override + String get enterPin => 'Vnesi PIN'; + + @override + String get setNewPin => 'Nastavi nov PIN'; + + @override + String get confirm => 'Potrdi'; + + @override + String get reEnterPassword => 'Ponovno vnesite geslo'; + + @override + String get reEnterPin => 'Ponovno vnesite PIN'; + + @override + String get androidBiometricHint => 'Potrdite identiteto'; + + @override + String get androidBiometricNotRecognized => 'Ni prepoznano. Poskusite znova.'; + + @override + String get androidBiometricSuccess => 'Uspešno'; + + @override + String get androidCancelButton => 'Prekliči'; + + @override + String get androidSignInTitle => 'Potrebna je overitev'; + + @override + String get androidBiometricRequiredTitle => 'Zahtevani biometrični podatki'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Zahtevani podatki za vpis v napravo'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Zahtevani podatki za vpis v napravo'; + + @override + String get goToSettings => 'Pojdi v nastavitve'; + + @override + String get androidGoToSettingsDescription => + 'Biometrično overjanje v vaši napravi ni nastavljeno. Pojdite v \"Nastavitve > Varnost\" in dodajte biometrično overjanje.'; + + @override + String get iOSLockOut => + 'Biometrično overjanje je onemogočeno. Če ga želite omogočiti, zaklenite in odklenite zaslon.'; + + @override + String get iOSOkButton => 'V redu'; + + @override + String get emailAlreadyRegistered => 'E-poštni naslov je že registriran.'; + + @override + String get emailNotRegistered => 'E-poštni naslov ni registriran.'; + + @override + String get thisEmailIsAlreadyInUse => 'Ta e-poštni naslove je že v uporabi.'; + + @override + String emailChangedTo(String newEmail) { + return 'E-poštni naslove je bil spremenjen na $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Overitev ni uspela, prosimo poskusite znova'; + + @override + String get authenticationSuccessful => 'Overitev uspešna!'; + + @override + String get sessionExpired => 'Seja je potekla'; + + @override + String get incorrectRecoveryKey => 'Nepravilen ključ za obnovitev'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Ključ za obnovitev, ki ste ga vnesli ni pravilen'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Uspešna ponastavitev dvostopenjske avtentikacije'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Vaša koda za potrditev je potekla.'; + + @override + String get incorrectCode => 'Nepravilna koda'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Oprostite, koda ki ste jo vnesli ni pravilna'; + + @override + String get developerSettings => 'Nastavitve za razvijalce'; + + @override + String get serverEndpoint => 'Endpoint strežnika'; + + @override + String get invalidEndpoint => 'Nepravilen endpoint'; + + @override + String get invalidEndpointMessage => + 'Oprostite endpoint, ki ste ga vnesli ni bil pravilen. Prosimo, vnesite pravilen endpoint in poskusite znova.'; + + @override + String get endpointUpdatedMessage => 'Endpoint posodobljen uspešno'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart b/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart new file mode 100644 index 0000000000..3a2c2fc4f7 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_sr.dart @@ -0,0 +1,630 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Serbian (`sr`). +class StringsLocalizationsSr extends StringsLocalizations { + StringsLocalizationsSr([String locale = 'sr']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Није могуће повезивање са Ente-ом, молимо вас да проверите мрежне поставке и контактирајте подршку ако грешка и даље постоји.'; + + @override + String get networkConnectionRefusedErr => + 'Није могуће повезивање са Ente-ом, покушајте поново мало касније. Ако грешка настави, обратите се подршци.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Изгледа да је нешто погрешно. Покушајте поново након неког времена. Ако грешка настави, обратите се нашем тиму за подршку.'; + + @override + String get error => 'Грешка'; + + @override + String get ok => 'У реду'; + + @override + String get faq => 'Питања'; + + @override + String get contactSupport => 'Контактирати подршку'; + + @override + String get emailYourLogs => 'Имејлирајте извештаје'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Пошаљите извештаје на \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Копирати имејл адресу'; + + @override + String get exportLogs => 'Извези изештаје'; + + @override + String get cancel => 'Откажи'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Пријави грешку'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Везано за $endpoint'; + } + + @override + String get save => 'Сачувај'; + + @override + String get send => 'Пошаљи'; + + @override + String get saveOrSendDescription => + 'Да ли желите да ово сачувате у складиште (фасцикли за преузимање подразумевано) или да га пошаљете другим апликацијама?'; + + @override + String get saveOnlyDescription => + 'Да ли желите да ово сачувате у складиште (фасцикли за преузимање подразумевано)?'; + + @override + String get enterNewEmailHint => 'Унесите Ваш нови имејл'; + + @override + String get email => 'Имејл'; + + @override + String get verify => 'Верификуј'; + + @override + String get invalidEmailTitle => 'Погрешна имејл адреса'; + + @override + String get invalidEmailMessage => 'Унесите важећи имејл.'; + + @override + String get pleaseWait => 'Молимо сачекајте...'; + + @override + String get verifyPassword => 'Верификујте лозинку'; + + @override + String get incorrectPasswordTitle => 'Неисправна лозинка'; + + @override + String get pleaseTryAgain => 'Пробајте поново'; + + @override + String get enterPassword => 'Унеси лозинку'; + + @override + String get enterYourPasswordHint => 'Унесите лозинку'; + + @override + String get activeSessions => 'Активне сесије'; + + @override + String get oops => 'Упс'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Нешто је пошло наопако. Покушајте поново'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Ово ће вас одјавити из овог уређаја!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Ово ће вас одјавити из овог уређаја:'; + + @override + String get terminateSession => 'Прекинути сесију?'; + + @override + String get terminate => 'Прекини'; + + @override + String get thisDevice => 'Овај уређај'; + + @override + String get createAccount => 'Направи налог'; + + @override + String get weakStrength => 'Слабо'; + + @override + String get moderateStrength => 'Умерено'; + + @override + String get strongStrength => 'Јако'; + + @override + String get deleteAccount => 'Избриши налог'; + + @override + String get deleteAccountQuery => + 'Жао нам је што одлазите. Да ли се суочавате са неком грешком?'; + + @override + String get yesSendFeedbackAction => 'Да, послати повратне информације'; + + @override + String get noDeleteAccountAction => 'Не, избрисати налог'; + + @override + String get initiateAccountDeleteTitle => + 'Молимо вас да се аутентификујете за брисање рачуна'; + + @override + String get confirmAccountDeleteTitle => 'Потврда брисања рачуна'; + + @override + String get confirmAccountDeleteMessage => + 'Овај налог је повезан са другим Ente апликацијама, ако користите било коју.\n\nВаши преношени подаци, на свим Ente апликацијама биће заказани за брисање, и ваш рачун ће се трајно избрисати.'; + + @override + String get delete => 'Обриши'; + + @override + String get createNewAccount => 'Креирај нови налог'; + + @override + String get password => 'Лозинка'; + + @override + String get confirmPassword => 'Потврдите лозинку'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Снага лозинке: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Како сте чули о Ente? (опционо)'; + + @override + String get hearUsExplanation => + 'Не пратимо инсталацију апликације. Помогло би да нам кажеш како си нас нашао!'; + + @override + String get signUpTerms => + 'Прихватам услове сервиса и политику приватности'; + + @override + String get termsOfServicesTitle => 'Услови'; + + @override + String get privacyPolicyTitle => 'Политика приватности'; + + @override + String get ackPasswordLostWarning => + 'Разумем да ако изгубим лозинку, могу изгубити своје податке пошто су шифрирани од краја до краја.'; + + @override + String get encryption => 'Шифровање'; + + @override + String get logInLabel => 'Пријави се'; + + @override + String get welcomeBack => 'Добродошли назад!'; + + @override + String get loginTerms => + 'Кликом на пријаву, прихватам услове сервиса и политику приватности'; + + @override + String get noInternetConnection => 'Нема интернет везе'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Провери своју везу са интернетом и покушај поново.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Неуспешна верификација, покушајте поново'; + + @override + String get recreatePasswordTitle => 'Поново креирати лозинку'; + + @override + String get recreatePasswordBody => + 'Тренутни уређај није довољно моћан да потврди вашу лозинку, али можемо регенерирати на начин који ради са свим уређајима.\n\nПријавите се помоћу кључа за опоравак и обновите своју лозинку (можете поново користити исту ако желите).'; + + @override + String get useRecoveryKey => 'Користите кључ за опоравак'; + + @override + String get forgotPassword => 'Заборавио сам лозинку'; + + @override + String get changeEmail => 'Промени имејл'; + + @override + String get verifyEmail => 'Потврди имејл'; + + @override + String weHaveSendEmailTo(String email) { + return 'Послали смо имејл на $email'; + } + + @override + String get toResetVerifyEmail => + 'Да бисте ресетовали лозинку, прво потврдите свој имејл.'; + + @override + String get checkInboxAndSpamFolder => + 'Молимо вас да проверите примљену пошту (и нежељену пошту) да бисте довршили верификацију'; + + @override + String get tapToEnterCode => 'Пипните да бисте унели кôд'; + + @override + String get sendEmail => 'Шаљи имејл'; + + @override + String get resendEmail => 'Поново послати имејл'; + + @override + String get passKeyPendingVerification => 'Верификација је још у току'; + + @override + String get loginSessionExpired => 'Сесија је истекла'; + + @override + String get loginSessionExpiredDetails => + 'Ваша сесија је истекла. Молимо пријавите се поново.'; + + @override + String get passkeyAuthTitle => 'Верификација сигурносном кључем'; + + @override + String get waitingForVerification => 'Чека се верификација...'; + + @override + String get tryAgain => 'Покушај поново'; + + @override + String get checkStatus => 'Провери статус'; + + @override + String get loginWithTOTP => 'Пријава са TOTP'; + + @override + String get recoverAccount => 'Опоравак налога'; + + @override + String get setPasswordTitle => 'Постави лозинку'; + + @override + String get changePasswordTitle => 'Промени лозинку'; + + @override + String get resetPasswordTitle => 'Ресетуј лозинку'; + + @override + String get encryptionKeys => 'Кључеве шифровања'; + + @override + String get enterPasswordToEncrypt => + 'Унесите лозинку за употребу за шифровање ваших података'; + + @override + String get enterNewPasswordToEncrypt => + 'Унесите нову лозинку за употребу за шифровање ваших података'; + + @override + String get passwordWarning => + 'Не чувамо ову лозинку, па ако је заборавите, не можемо дешифрирати ваше податке'; + + @override + String get howItWorks => 'Како то функционише'; + + @override + String get generatingEncryptionKeys => 'Генерисање кључева за шифровање...'; + + @override + String get passwordChangedSuccessfully => 'Лозинка је успешно промењена'; + + @override + String get signOutFromOtherDevices => 'Одјави се из других уређаја'; + + @override + String get signOutOtherBody => + 'Ако мислиш да неко може знати твоју лозинку, можеш приморати одјављивање све остале уређаје које користе твој налог.'; + + @override + String get signOutOtherDevices => 'Одјави друге уређаје'; + + @override + String get doNotSignOut => 'Не одјави'; + + @override + String get generatingEncryptionKeysTitle => + 'Генерисање кључева за шифровање...'; + + @override + String get continueLabel => 'Настави'; + + @override + String get insecureDevice => 'Уређај није сигуран'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Извините, не можемо да генеришемо сигурне кључеве на овом уређају.\n\nМолимо пријавите се са другог уређаја.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Кључ за опоравак копирано у остави'; + + @override + String get recoveryKey => 'Резервни Кључ'; + + @override + String get recoveryKeyOnForgotPassword => + 'Ако заборавите лозинку, једини начин на који можете повратити податке је са овим кључем.'; + + @override + String get recoveryKeySaveDescription => + 'Не чувамо овај кључ, молимо да сачувате кључ од 24 речи на сигурном месту.'; + + @override + String get doThisLater => 'Уради то касније'; + + @override + String get saveKey => 'Сачувај кључ'; + + @override + String get recoveryKeySaved => + 'Кључ за опоравак сачуван у фасцикли за преузимање!'; + + @override + String get noRecoveryKeyTitle => 'Немате кључ за опоравак?'; + + @override + String get twoFactorAuthTitle => 'Дво-факторска аутентификација'; + + @override + String get enterCodeHint => + 'Унесите 6-цифрени кôд из\nапликације за аутентификацију'; + + @override + String get lostDeviceTitle => 'Узгубили сте уређај?'; + + @override + String get enterRecoveryKeyHint => 'Унети кључ за опоравак'; + + @override + String get recover => 'Опорави'; + + @override + String get loggingOut => 'Одјављивање...'; + + @override + String get immediately => 'Одмах'; + + @override + String get appLock => 'Закључавање апликације'; + + @override + String get autoLock => 'Ауто-закључавање'; + + @override + String get noSystemLockFound => 'Није пронађено ниједно закључавање система'; + + @override + String get deviceLockEnablePreSteps => + 'Да бисте омогућили закључавање уређаја, молимо вас да подесите шифру уређаја или закључавање екрана у системским подешавањима.'; + + @override + String get appLockDescription => + 'Изаберите између заданог закључавање екрана вашег уређаја и прилагођени екран за закључавање са ПИН-ом или лозинком.'; + + @override + String get deviceLock => 'Закључавање уређаја'; + + @override + String get pinLock => 'ПИН клокирање'; + + @override + String get autoLockFeatureDescription => + 'Време након којег се апликација блокира након што је постављенеа у позадину'; + + @override + String get hideContent => 'Сакриј садржај'; + + @override + String get hideContentDescriptionAndroid => + 'Сакрива садржај апликације у пребацивање апликација и онемогућује снимке екрана'; + + @override + String get hideContentDescriptioniOS => + 'Сакрива садржај апликације у пребацивање апликација'; + + @override + String get tooManyIncorrectAttempts => 'Превише погрешних покушаја'; + + @override + String get tapToUnlock => 'Додирните да бисте откључали'; + + @override + String get areYouSureYouWantToLogout => 'Да ли сте сигурни да се одјавите?'; + + @override + String get yesLogout => 'Да, одјави ме'; + + @override + String get authToViewSecrets => + 'Аутентификујте се да бисте прегледали Ваше тајне'; + + @override + String get next => 'Следеће'; + + @override + String get setNewPassword => 'Постави нову лозинку'; + + @override + String get enterPin => 'Унеси ПИН'; + + @override + String get setNewPin => 'Постави нови ПИН'; + + @override + String get confirm => 'Потврди'; + + @override + String get reEnterPassword => 'Поново унеси лозинку'; + + @override + String get reEnterPin => 'Поново унеси ПИН'; + + @override + String get androidBiometricHint => 'Потврдите идентитет'; + + @override + String get androidBiometricNotRecognized => + 'Нисмо препознали. Покушати поново.'; + + @override + String get androidBiometricSuccess => 'Успех'; + + @override + String get androidCancelButton => 'Откажи'; + + @override + String get androidSignInTitle => 'Потребна аутентификација'; + + @override + String get androidBiometricRequiredTitle => 'Потребна је биометрија'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Потребни су акредитиви уређаја'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Потребни су акредитиви уређаја'; + + @override + String get goToSettings => 'Иди на поставке'; + + @override + String get androidGoToSettingsDescription => + 'Биометријска аутентификација није постављена на вашем уређају. Идите на \"Подешавања> Сигурност\" да бисте додали биометријску аутентификацију.'; + + @override + String get iOSLockOut => + 'Биометријска аутентификација је онемогућена. Закључајте и откључите екран да бисте је омогућили.'; + + @override + String get iOSOkButton => 'У реду'; + + @override + String get emailAlreadyRegistered => 'Имејл је већ регистрован.'; + + @override + String get emailNotRegistered => 'Имејл није регистрован.'; + + @override + String get thisEmailIsAlreadyInUse => 'Овај имејл је већ у употреби'; + + @override + String emailChangedTo(String newEmail) { + return 'Имејл промењен на $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Аутентификација није успела, покушајте поново'; + + @override + String get authenticationSuccessful => 'Успешна аутентификација!'; + + @override + String get sessionExpired => 'Сесија је истекла'; + + @override + String get incorrectRecoveryKey => 'Нетачан кључ за опоравак'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Унети кључ за опоравак је натачан'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Двофакторска аутентификација успешно рисетирана'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Ваш верификациони кôд је истекао'; + + @override + String get incorrectCode => 'Погрешан кôд'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => 'Унет кôд није добар'; + + @override + String get developerSettings => 'Подешавања за програмере'; + + @override + String get serverEndpoint => 'Крајња тачка сервера'; + + @override + String get invalidEndpoint => 'Погрешна крајња тачка'; + + @override + String get invalidEndpointMessage => + 'Извини, крајња тачка коју си унео је неважећа. Унеси важећу крајњу тачку и покушај поново.'; + + @override + String get endpointUpdatedMessage => 'Крајна тачка успешно ажурирана'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart b/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart new file mode 100644 index 0000000000..96d85243d0 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_sv.dart @@ -0,0 +1,628 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Swedish (`sv`). +class StringsLocalizationsSv extends StringsLocalizations { + StringsLocalizationsSv([String locale = 'sv']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Det gick inte att ansluta till Ente, kontrollera dina nätverksinställningar och kontakta supporten om felet kvarstår.'; + + @override + String get networkConnectionRefusedErr => + 'Det gick inte att ansluta till Ente, försök igen om en stund. Om felet kvarstår, vänligen kontakta support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Det ser ut som om något gick fel. Försök igen efter en stund. Om felet kvarstår, vänligen kontakta vår support.'; + + @override + String get error => 'Fel'; + + @override + String get ok => 'OK'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'Kontakta support'; + + @override + String get emailYourLogs => 'Maila dina loggar'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Vänligen skicka loggarna till \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Kopiera e-postadress'; + + @override + String get exportLogs => 'Exportera loggar'; + + @override + String get cancel => 'Avbryt'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Rapportera en bugg'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Ansluten till $endpoint'; + } + + @override + String get save => 'Spara'; + + @override + String get send => 'Skicka'; + + @override + String get saveOrSendDescription => + 'Vill du spara detta till din lagringsmapp (Nedladdningsmappen som standard) eller skicka den till andra appar?'; + + @override + String get saveOnlyDescription => + 'Vill du spara detta till din lagringsmapp (Nedladdningsmappen som standard)?'; + + @override + String get enterNewEmailHint => 'Ange din nya e-postadress'; + + @override + String get email => 'E-post'; + + @override + String get verify => 'Verifiera'; + + @override + String get invalidEmailTitle => 'Ogiltig e-postadress'; + + @override + String get invalidEmailMessage => 'Ange en giltig e-postadress.'; + + @override + String get pleaseWait => 'Vänligen vänta...'; + + @override + String get verifyPassword => 'Bekräfta lösenord'; + + @override + String get incorrectPasswordTitle => 'Felaktigt lösenord'; + + @override + String get pleaseTryAgain => 'Försök igen'; + + @override + String get enterPassword => 'Ange lösenord'; + + @override + String get enterYourPasswordHint => 'Ange ditt lösenord'; + + @override + String get activeSessions => 'Aktiva sessioner'; + + @override + String get oops => 'Hoppsan'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Något gick fel, vänligen försök igen'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Detta kommer att logga ut dig från den här enheten!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Detta kommer att logga ut dig från följande enhet:'; + + @override + String get terminateSession => 'Avsluta session?'; + + @override + String get terminate => 'Avsluta'; + + @override + String get thisDevice => 'Den här enheten'; + + @override + String get createAccount => 'Skapa konto'; + + @override + String get weakStrength => 'Svag'; + + @override + String get moderateStrength => 'Måttligt'; + + @override + String get strongStrength => 'Stark'; + + @override + String get deleteAccount => 'Radera konto'; + + @override + String get deleteAccountQuery => + 'Vi kommer att vara ledsna över att se dig gå. Har du något problem?'; + + @override + String get yesSendFeedbackAction => 'Ja, skicka feedback'; + + @override + String get noDeleteAccountAction => 'Nej, radera konto'; + + @override + String get initiateAccountDeleteTitle => + 'Vänligen autentisera för att initiera borttagning av konto'; + + @override + String get confirmAccountDeleteTitle => 'Bekräfta radering av kontot'; + + @override + String get confirmAccountDeleteMessage => + 'Detta konto är kopplat till andra Ente apps, om du använder någon.\n\nDina uppladdade data, över alla Ente appar, kommer att schemaläggas för radering och ditt konto kommer att raderas permanent.'; + + @override + String get delete => 'Radera'; + + @override + String get createNewAccount => 'Skapa nytt konto'; + + @override + String get password => 'Lösenord'; + + @override + String get confirmPassword => 'Bekräfta lösenord'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Lösenordsstyrka: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Hur hörde du talas om Ente? (valfritt)'; + + @override + String get hearUsExplanation => + 'Vi spårar inte appinstallationer, Det skulle hjälpa oss om du berättade var du hittade oss!'; + + @override + String get signUpTerms => + 'Jag samtycker till användarvillkoren och integritetspolicyn'; + + @override + String get termsOfServicesTitle => 'Villkor'; + + @override + String get privacyPolicyTitle => 'Integritetspolicy'; + + @override + String get ackPasswordLostWarning => + 'Jag förstår att om jag förlorar mitt lösenord kan jag förlora mina data eftersom min data är end-to-end-krypterad.'; + + @override + String get encryption => 'Kryptering'; + + @override + String get logInLabel => 'Logga in'; + + @override + String get welcomeBack => 'Välkommen tillbaka!'; + + @override + String get loginTerms => + 'Jag samtycker till användarvillkoren och integritetspolicyn'; + + @override + String get noInternetConnection => 'Ingen internetanslutning'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Kontrollera din internetanslutning och försök igen.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verifiering misslyckades, vänligen försök igen'; + + @override + String get recreatePasswordTitle => 'Återskapa lösenord'; + + @override + String get recreatePasswordBody => + 'Denna enhet är inte tillräckligt kraftfull för att verifiera ditt lösenord, men vi kan återskapa det på ett sätt som fungerar med alla enheter.\n\nLogga in med din återställningsnyckel och återskapa ditt lösenord (du kan använda samma igen om du vill).'; + + @override + String get useRecoveryKey => 'Använd återställningsnyckel'; + + @override + String get forgotPassword => 'Glömt lösenord'; + + @override + String get changeEmail => 'Ändra e-postadress'; + + @override + String get verifyEmail => 'Verifiera e-postadress'; + + @override + String weHaveSendEmailTo(String email) { + return 'Vi har skickat ett mail till $email'; + } + + @override + String get toResetVerifyEmail => + 'För att återställa ditt lösenord måste du först bekräfta din e-postadress.'; + + @override + String get checkInboxAndSpamFolder => + 'Vänligen kontrollera din inkorg (och skräppost) för att slutföra verifieringen'; + + @override + String get tapToEnterCode => 'Tryck för att ange kod'; + + @override + String get sendEmail => 'Skicka e-post'; + + @override + String get resendEmail => 'Skicka e-post igen'; + + @override + String get passKeyPendingVerification => 'Verifiering pågår fortfarande'; + + @override + String get loginSessionExpired => 'Sessionen har gått ut'; + + @override + String get loginSessionExpiredDetails => + 'Din session har upphört. Logga in igen.'; + + @override + String get passkeyAuthTitle => 'Verifiering med inloggningsnyckel'; + + @override + String get waitingForVerification => 'Väntar på verifiering...'; + + @override + String get tryAgain => 'Försök igen'; + + @override + String get checkStatus => 'Kontrollera status'; + + @override + String get loginWithTOTP => 'Logga in med TOTP'; + + @override + String get recoverAccount => 'Återställ konto'; + + @override + String get setPasswordTitle => 'Ställ in lösenord'; + + @override + String get changePasswordTitle => 'Ändra lösenord'; + + @override + String get resetPasswordTitle => 'Återställ lösenord'; + + @override + String get encryptionKeys => 'Krypteringsnycklar'; + + @override + String get enterPasswordToEncrypt => + 'Ange ett lösenord som vi kan använda för att kryptera din data'; + + @override + String get enterNewPasswordToEncrypt => + 'Ange ett nytt lösenord som vi kan använda för att kryptera din data'; + + @override + String get passwordWarning => + 'Vi lagrar inte detta lösenord, så om du glömmer bort det, kan vi inte dekryptera dina data'; + + @override + String get howItWorks => 'Så här fungerar det'; + + @override + String get generatingEncryptionKeys => 'Skapar krypteringsnycklar...'; + + @override + String get passwordChangedSuccessfully => 'Lösenordet har ändrats'; + + @override + String get signOutFromOtherDevices => 'Logga ut från andra enheter'; + + @override + String get signOutOtherBody => + 'Om du tror att någon kanske känner till ditt lösenord kan du tvinga alla andra enheter med ditt konto att logga ut.'; + + @override + String get signOutOtherDevices => 'Logga ut andra enheter'; + + @override + String get doNotSignOut => 'Logga inte ut'; + + @override + String get generatingEncryptionKeysTitle => 'Skapar krypteringsnycklar...'; + + @override + String get continueLabel => 'Fortsätt'; + + @override + String get insecureDevice => 'Osäker enhet'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Tyvärr, kunde vi inte generera säkra nycklar på den här enheten.\n\nvänligen registrera dig från en annan enhet.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Återställningsnyckel kopierad till urklipp'; + + @override + String get recoveryKey => 'Återställningsnyckel'; + + @override + String get recoveryKeyOnForgotPassword => + 'Om du glömmer ditt lösenord är det enda sättet du kan återställa dina data med denna nyckel.'; + + @override + String get recoveryKeySaveDescription => + 'Vi lagrar inte och har därför inte åtkomst till denna nyckel, vänligen spara denna 24 ords nyckel på en säker plats.'; + + @override + String get doThisLater => 'Gör detta senare'; + + @override + String get saveKey => 'Spara nyckel'; + + @override + String get recoveryKeySaved => + 'Återställningsnyckel sparad i nedladdningsmappen!'; + + @override + String get noRecoveryKeyTitle => 'Ingen återställningsnyckel?'; + + @override + String get twoFactorAuthTitle => 'Tvåfaktorsautentisering'; + + @override + String get enterCodeHint => + 'Ange den 6-siffriga koden från din autentiseringsapp'; + + @override + String get lostDeviceTitle => 'Förlorad enhet?'; + + @override + String get enterRecoveryKeyHint => 'Ange din återställningsnyckel'; + + @override + String get recover => 'Återställ'; + + @override + String get loggingOut => 'Loggar ut...'; + + @override + String get immediately => 'Omedelbart'; + + @override + String get appLock => 'Applås'; + + @override + String get autoLock => 'Automatisk låsning'; + + @override + String get noSystemLockFound => 'Inget systemlås hittades'; + + @override + String get deviceLockEnablePreSteps => + 'För att aktivera enhetslås, vänligen ställ in enhetens lösenord eller skärmlås i dina systeminställningar.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Enhetslås'; + + @override + String get pinLock => 'Pinkodslås'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Dölj innehåll'; + + @override + String get hideContentDescriptionAndroid => + 'Döljer appinnehåll i app-växlaren och inaktiverar skärmdumpar'; + + @override + String get hideContentDescriptioniOS => 'Döljer appinnehåll i app-växlaren'; + + @override + String get tooManyIncorrectAttempts => 'För många felaktiga försök'; + + @override + String get tapToUnlock => 'Tryck för att låsa upp'; + + @override + String get areYouSureYouWantToLogout => + 'Är du säker på att du vill logga ut?'; + + @override + String get yesLogout => 'Ja, logga ut'; + + @override + String get authToViewSecrets => + 'Autentisera för att visa din återställningsnyckel'; + + @override + String get next => 'Nästa'; + + @override + String get setNewPassword => 'Ställ in nytt lösenord'; + + @override + String get enterPin => 'Ange PIN-kod'; + + @override + String get setNewPin => 'Ställ in ny PIN-kod'; + + @override + String get confirm => 'Bekräfta'; + + @override + String get reEnterPassword => 'Ange lösenord igen'; + + @override + String get reEnterPin => 'Ange PIN-kod igen'; + + @override + String get androidBiometricHint => 'Verifiera identitet'; + + @override + String get androidBiometricNotRecognized => 'Ej godkänd. Försök igen.'; + + @override + String get androidBiometricSuccess => 'Slutförd'; + + @override + String get androidCancelButton => 'Avbryt'; + + @override + String get androidSignInTitle => 'Obligatorisk autentisering'; + + @override + String get androidBiometricRequiredTitle => 'Biometriska uppgifter krävs'; + + @override + String get androidDeviceCredentialsRequiredTitle => 'Enhetsuppgifter krävs'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Enhetsuppgifter krävs'; + + @override + String get goToSettings => 'Gå till inställningar'; + + @override + String get androidGoToSettingsDescription => + 'Biometrisk autentisering är inte konfigurerad på din enhet. Gå till \"Inställningar > Säkerhet\" för att lägga till biometrisk autentisering.'; + + @override + String get iOSLockOut => + 'Biometrisk autentisering är inaktiverat. Lås och lås upp din skärm för att aktivera den.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'E-postadress redan registrerad.'; + + @override + String get emailNotRegistered => 'E-postadress ej registrerad.'; + + @override + String get thisEmailIsAlreadyInUse => 'Denna e-postadress används redan'; + + @override + String emailChangedTo(String newEmail) { + return 'E-post ändrad till $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Autentisering misslyckades, vänligen försök igen'; + + @override + String get authenticationSuccessful => 'Autentisering lyckades!'; + + @override + String get sessionExpired => 'Sessionen har gått ut'; + + @override + String get incorrectRecoveryKey => 'Felaktig återställningsnyckel'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Återställningsnyckeln du angav är felaktig'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Tvåfaktorsautentisering återställd'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Din verifieringskod har upphört att gälla'; + + @override + String get incorrectCode => 'Felaktig kod'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Tyvärr, den kod som du har angett är felaktig'; + + @override + String get developerSettings => 'Utvecklarinställningar'; + + @override + String get serverEndpoint => 'Serverns slutpunkt'; + + @override + String get invalidEndpoint => 'Ogiltig slutpunkt'; + + @override + String get invalidEndpointMessage => + 'Tyvärr, slutpunkten du angav är ogiltig. Ange en giltig slutpunkt och försök igen.'; + + @override + String get endpointUpdatedMessage => 'Slutpunkten har uppdaterats'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_ti.dart b/mobile/packages/strings/lib/l10n/strings_localizations_ti.dart new file mode 100644 index 0000000000..5f29ac5338 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_ti.dart @@ -0,0 +1,626 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Tigrinya (`ti`). +class StringsLocalizationsTi extends StringsLocalizations { + StringsLocalizationsTi([String locale = 'ti']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Unable to connect to Ente, please check your network settings and contact support if the error persists.'; + + @override + String get networkConnectionRefusedErr => + 'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.'; + + @override + String get error => 'Error'; + + @override + String get ok => 'Ok'; + + @override + String get faq => 'FAQ'; + + @override + String get contactSupport => 'ደገፍ ኣድራሻ'; + + @override + String get emailYourLogs => 'Email your logs'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Please send the logs to \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Copy email address'; + + @override + String get exportLogs => 'Export logs'; + + @override + String get cancel => 'Cancel'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'ጌጋ ጸብጻብ ልኣኸ'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Connected to $endpoint'; + } + + @override + String get save => 'Save'; + + @override + String get send => 'Send'; + + @override + String get saveOrSendDescription => + 'Do you want to save this to your storage (Downloads folder by default) or send it to other apps?'; + + @override + String get saveOnlyDescription => + 'Do you want to save this to your storage (Downloads folder by default)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Email'; + + @override + String get verify => 'Verify'; + + @override + String get invalidEmailTitle => 'Invalid email address'; + + @override + String get invalidEmailMessage => 'Please enter a valid email address.'; + + @override + String get pleaseWait => 'በጃኻ ተጸበ...'; + + @override + String get verifyPassword => 'ቃለ-ምስጢር ኣረጋግጽ'; + + @override + String get incorrectPasswordTitle => 'ግጉይ ቃለ-ምስጢር'; + + @override + String get pleaseTryAgain => 'Please try again'; + + @override + String get enterPassword => 'Enter password'; + + @override + String get enterYourPasswordHint => 'Enter your password'; + + @override + String get activeSessions => 'Active sessions'; + + @override + String get oops => 'ዉዉኡ'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Something went wrong, please try again'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'This will log you out of this device!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'This will log you out of the following device:'; + + @override + String get terminateSession => 'Terminate session?'; + + @override + String get terminate => 'Terminate'; + + @override + String get thisDevice => 'This device'; + + @override + String get createAccount => 'Create account'; + + @override + String get weakStrength => 'Weak'; + + @override + String get moderateStrength => 'Moderate'; + + @override + String get strongStrength => 'Strong'; + + @override + String get deleteAccount => 'Delete account'; + + @override + String get deleteAccountQuery => + 'We\'ll be sorry to see you go. Are you facing some issue?'; + + @override + String get yesSendFeedbackAction => 'Yes, send feedback'; + + @override + String get noDeleteAccountAction => 'No, delete account'; + + @override + String get initiateAccountDeleteTitle => + 'Please authenticate to initiate account deletion'; + + @override + String get confirmAccountDeleteTitle => 'Confirm account deletion'; + + @override + String get 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.'; + + @override + String get delete => 'Delete'; + + @override + String get createNewAccount => 'Create new account'; + + @override + String get password => 'Password'; + + @override + String get confirmPassword => 'Confirm password'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Password strength: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'How did you hear about Ente? (optional)'; + + @override + String get hearUsExplanation => + 'ተጠቀምትና ኣይንከታተልን ኢና። ኣበይ ከም ዝረኸብካና እንተ ትነግረና ሓጋዚ እዩ፦'; + + @override + String get signUpTerms => + 'I agree to the terms of service and privacy policy'; + + @override + String get termsOfServicesTitle => 'Terms'; + + @override + String get privacyPolicyTitle => 'Privacy Policy'; + + @override + String get ackPasswordLostWarning => + 'I understand that if I lose my password, I may lose my data since my data is end-to-end encrypted.'; + + @override + String get encryption => 'Encryption'; + + @override + String get logInLabel => 'Log in'; + + @override + String get welcomeBack => 'እንኳዕ ብደሓን ተመለስካ!'; + + @override + String get loginTerms => + 'By clicking log in, I agree to the terms of service and privacy policy'; + + @override + String get noInternetConnection => 'No internet connection'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Please check your internet connection and try again.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Verification failed, please try again'; + + @override + String get recreatePasswordTitle => 'Recreate password'; + + @override + String get recreatePasswordBody => + 'The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).'; + + @override + String get useRecoveryKey => 'ምሕዋይ መፍትሕ ተጠቐም'; + + @override + String get forgotPassword => 'Forgot password'; + + @override + String get changeEmail => 'Change email'; + + @override + String get verifyEmail => 'Verify email'; + + @override + String weHaveSendEmailTo(String email) { + return 'We have sent a mail to $email'; + } + + @override + String get toResetVerifyEmail => + 'To reset your password, please verify your email first.'; + + @override + String get checkInboxAndSpamFolder => + 'Please check your inbox (and spam) to complete verification'; + + @override + String get tapToEnterCode => 'Tap to enter code'; + + @override + String get sendEmail => 'Send email'; + + @override + String get resendEmail => 'Resend email'; + + @override + String get passKeyPendingVerification => 'Verification is still pending'; + + @override + String get loginSessionExpired => 'Session expired'; + + @override + String get loginSessionExpiredDetails => + 'Your session has expired. Please login again.'; + + @override + String get passkeyAuthTitle => 'Passkey verification'; + + @override + String get waitingForVerification => 'Waiting for verification...'; + + @override + String get tryAgain => 'Try again'; + + @override + String get checkStatus => 'Check status'; + + @override + String get loginWithTOTP => 'Login with TOTP'; + + @override + String get recoverAccount => 'Recover account'; + + @override + String get setPasswordTitle => 'Set password'; + + @override + String get changePasswordTitle => 'Change password'; + + @override + String get resetPasswordTitle => 'Reset password'; + + @override + String get encryptionKeys => 'Encryption keys'; + + @override + String get enterPasswordToEncrypt => + 'Enter a password we can use to encrypt your data'; + + @override + String get enterNewPasswordToEncrypt => + 'Enter a new password we can use to encrypt your data'; + + @override + String get passwordWarning => + 'We don\'t store this password, so if you forget, we cannot decrypt your data'; + + @override + String get howItWorks => 'How it works'; + + @override + String get generatingEncryptionKeys => 'Generating encryption keys...'; + + @override + String get passwordChangedSuccessfully => 'Password changed successfully'; + + @override + String get signOutFromOtherDevices => 'Sign out from other devices'; + + @override + String get signOutOtherBody => + 'If you think someone might know your password, you can force all other devices using your account to sign out.'; + + @override + String get signOutOtherDevices => 'ካብ ካልኦት መሳርሒታት ኣውጽኡኒ።'; + + @override + String get doNotSignOut => 'ኣውጽኡኒ።'; + + @override + String get generatingEncryptionKeysTitle => 'ናይ ምስጢራዊ ቁልፊ ዪፍጠር...'; + + @override + String get continueLabel => 'Continue'; + + @override + String get insecureDevice => 'Insecure device'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.'; + + @override + String get recoveryKeyCopiedToClipboard => 'Recovery key copied to clipboard'; + + @override + String get recoveryKey => 'ምሕዋይ መፍትሕ'; + + @override + String get recoveryKeyOnForgotPassword => + 'If you forget your password, the only way you can recover your data is with this key.'; + + @override + String get recoveryKeySaveDescription => + 'We don\'t store this key, please save this 24 word key in a safe place.'; + + @override + String get doThisLater => 'Do this later'; + + @override + String get saveKey => 'Save key'; + + @override + String get recoveryKeySaved => 'Recovery key saved in Downloads folder!'; + + @override + String get noRecoveryKeyTitle => 'No recovery key?'; + + @override + String get twoFactorAuthTitle => 'Two-factor authentication'; + + @override + String get enterCodeHint => + 'Enter the 6-digit code from\nyour authenticator app'; + + @override + String get lostDeviceTitle => 'Lost device?'; + + @override + String get enterRecoveryKeyHint => 'Enter your recovery key'; + + @override + String get recover => 'Recover'; + + @override + String get loggingOut => 'ወጸ...'; + + @override + String get immediately => 'Immediately'; + + @override + String get appLock => 'App lock'; + + @override + String get autoLock => 'Auto lock'; + + @override + String get noSystemLockFound => 'No system lock found'; + + @override + String get deviceLockEnablePreSteps => + 'To enable device lock, please setup device passcode or screen lock in your system settings.'; + + @override + String get appLockDescription => + 'Choose between your device\'s default lock screen and a custom lock screen with a PIN or password.'; + + @override + String get deviceLock => 'Device lock'; + + @override + String get pinLock => 'Pin lock'; + + @override + String get autoLockFeatureDescription => + 'Time after which the app locks after being put in the background'; + + @override + String get hideContent => 'Hide content'; + + @override + String get hideContentDescriptionAndroid => + 'Hides app content in the app switcher and disables screenshots'; + + @override + String get hideContentDescriptioniOS => + 'Hides app content in the app switcher'; + + @override + String get tooManyIncorrectAttempts => 'Too many incorrect attempts'; + + @override + String get tapToUnlock => 'Tap to unlock'; + + @override + String get areYouSureYouWantToLogout => 'Are you sure you want to logout?'; + + @override + String get yesLogout => 'Yes, logout'; + + @override + String get authToViewSecrets => 'Please authenticate to view your secrets'; + + @override + String get next => 'Next'; + + @override + String get setNewPassword => 'Set new password'; + + @override + String get enterPin => 'Enter PIN'; + + @override + String get setNewPin => 'Set new PIN'; + + @override + String get confirm => 'Confirm'; + + @override + String get reEnterPassword => 'Re-enter password'; + + @override + String get reEnterPin => 'Re-enter PIN'; + + @override + String get androidBiometricHint => 'Verify identity'; + + @override + String get androidBiometricNotRecognized => 'Not recognized. Try again.'; + + @override + String get androidBiometricSuccess => 'Success'; + + @override + String get androidCancelButton => 'Cancel'; + + @override + String get androidSignInTitle => 'Authentication required'; + + @override + String get androidBiometricRequiredTitle => 'Biometric required'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Device credentials required'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Device credentials required'; + + @override + String get goToSettings => 'Go to settings'; + + @override + String get androidGoToSettingsDescription => + 'Biometric authentication is not set up on your device. Go to \'Settings > Security\' to add biometric authentication.'; + + @override + String get iOSLockOut => + 'Biometric authentication is disabled. Please lock and unlock your screen to enable it.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => 'This email is already in use'; + + @override + String emailChangedTo(String newEmail) { + return 'Email changed to $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Authentication failed, please try again'; + + @override + String get authenticationSuccessful => 'Authentication successful!'; + + @override + String get sessionExpired => 'ክፍለ ግዜኡ ኣኺሉ።'; + + @override + String get incorrectRecoveryKey => 'Incorrect recovery key'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'The recovery key you entered is incorrect'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Two-factor authentication successfully reset'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Your verification code has expired'; + + @override + String get incorrectCode => 'Incorrect code'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Sorry, the code you\'ve entered is incorrect'; + + @override + String get developerSettings => 'Developer settings'; + + @override + String get serverEndpoint => 'Server endpoint'; + + @override + String get invalidEndpoint => 'Invalid endpoint'; + + @override + String get invalidEndpointMessage => + 'Sorry, the endpoint you entered is invalid. Please enter a valid endpoint and try again.'; + + @override + String get endpointUpdatedMessage => 'Endpoint updated successfully'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart b/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart new file mode 100644 index 0000000000..c5be60b8db --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_tr.dart @@ -0,0 +1,632 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Turkish (`tr`). +class StringsLocalizationsTr extends StringsLocalizations { + StringsLocalizationsTr([String locale = 'tr']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Ente\'ye bağlanılamıyor, lütfen ağ ayarlarınızı kontrol edin ve hata devam ederse desteğe başvurun.'; + + @override + String get networkConnectionRefusedErr => + 'Ente\'ye bağlanılamıyor, lütfen daha sonra tekrar deneyin. Hata devam ederse, lütfen desteğe başvurun.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Bir şeyler ters gitmiş gibi görünüyor. Lütfen bir süre sonra tekrar deneyin. Hata devam ederse, lütfen destek ekibimizle iletişime geçin.'; + + @override + String get error => 'Hata'; + + @override + String get ok => 'Tamam'; + + @override + String get faq => 'SSS'; + + @override + String get contactSupport => 'Destek ekibiyle iletişime geçin'; + + @override + String get emailYourLogs => 'Kayıtlarınızı e-postayla gönderin'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Lütfen kayıtları şu adrese gönderin\n$toEmail'; + } + + @override + String get copyEmailAddress => 'E-posta adresini kopyala'; + + @override + String get exportLogs => 'Kayıtları dışa aktar'; + + @override + String get cancel => 'İptal Et'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Hata bildirin'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Bağlandı: $endpoint'; + } + + @override + String get save => 'Kaydet'; + + @override + String get send => 'Gönder'; + + @override + String get saveOrSendDescription => + 'Bunu belleğinize mi kaydedeceksiniz (İndirilenler klasörü varsayılandır) yoksa diğer uygulamalara mı göndereceksiniz?'; + + @override + String get saveOnlyDescription => + 'Bunu belleğinize kaydetmek ister misiniz? (İndirilenler klasörü varsayılandır)'; + + @override + String get enterNewEmailHint => 'Yeni e-posta adresinizi girin'; + + @override + String get email => 'E-Posta'; + + @override + String get verify => 'Doğrula'; + + @override + String get invalidEmailTitle => 'Geçersiz e-posta adresi'; + + @override + String get invalidEmailMessage => 'Lütfen geçerli bir e-posta adresi girin.'; + + @override + String get pleaseWait => 'Lütfen bekleyin...'; + + @override + String get verifyPassword => 'Şifreyi doğrulayın'; + + @override + String get incorrectPasswordTitle => 'Yanlış şifre'; + + @override + String get pleaseTryAgain => 'Lütfen tekrar deneyin'; + + @override + String get enterPassword => 'Şifreyi girin'; + + @override + String get enterYourPasswordHint => 'Parolanızı girin'; + + @override + String get activeSessions => 'Aktif oturumlar'; + + @override + String get oops => 'Hay aksi'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Bir şeyler ters gitti, lütfen tekrar deneyin'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Bu sizin bu cihazdaki oturumunuzu kapatacaktır!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Bu, aşağıdaki cihazdan çıkış yapmanızı sağlayacaktır:'; + + @override + String get terminateSession => 'Oturumu sonlandır?'; + + @override + String get terminate => 'Sonlandır'; + + @override + String get thisDevice => 'Bu cihaz'; + + @override + String get createAccount => 'Hesap oluştur'; + + @override + String get weakStrength => 'Zayıf'; + + @override + String get moderateStrength => 'Orta'; + + @override + String get strongStrength => 'Güçlü'; + + @override + String get deleteAccount => 'Hesabı sil'; + + @override + String get deleteAccountQuery => + 'Sizin gittiğinizi görmekten üzüleceğiz. Bazı problemlerle mi karşılaşıyorsunuz?'; + + @override + String get yesSendFeedbackAction => 'Evet, geri bildirimi gönder'; + + @override + String get noDeleteAccountAction => 'Hayır, hesabı sil'; + + @override + String get initiateAccountDeleteTitle => + 'Hesap silme işlemini yapabilmek için lütfen kimliğinizi doğrulayın'; + + @override + String get confirmAccountDeleteTitle => 'Hesap silme işlemini onayla'; + + @override + String get confirmAccountDeleteMessage => + 'Kullandığınız Ente uygulamaları varsa bu hesap diğer Ente uygulamalarıyla bağlantılıdır.\n\nTüm Ente uygulamalarına yüklediğiniz veriler ve hesabınız kalıcı olarak silinecektir.'; + + @override + String get delete => 'Sil'; + + @override + String get createNewAccount => 'Yeni hesap oluşturun'; + + @override + String get password => 'Şifre'; + + @override + String get confirmPassword => 'Şifreyi onayla'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Şifre gücü: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Ente\'yi nereden duydunuz? (opsiyonel)'; + + @override + String get hearUsExplanation => + 'Biz uygulama kurulumlarını takip etmiyoruz. Bizi nereden duyduğunuzdan bahsetmeniz bize çok yardımcı olacak!'; + + @override + String get signUpTerms => + 'Kullanım şartlarını ve gizlilik politikasını kabul ediyorum'; + + @override + String get termsOfServicesTitle => 'Şartlar'; + + @override + String get privacyPolicyTitle => 'Gizlilik Politikası'; + + @override + String get ackPasswordLostWarning => + 'Eğer şifremi kaybedersem, verilerim uçtan uca şifrelendiğinden verilerimi kaybedebileceğimi anladım.'; + + @override + String get encryption => 'Şifreleme'; + + @override + String get logInLabel => 'Giriş yapın'; + + @override + String get welcomeBack => 'Tekrar hoş geldiniz!'; + + @override + String get loginTerms => + 'Giriş yaparak, kullanım şartları nı ve gizlilik politikası nı onaylıyorum'; + + @override + String get noInternetConnection => 'İnternet bağlantısı yok'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Lütfen internet bağlantınızı kontrol edin ve yeniden deneyin.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Doğrulama başarısız oldu, lütfen tekrar deneyin'; + + @override + String get recreatePasswordTitle => 'Şifreyi yeniden oluştur'; + + @override + String get recreatePasswordBody => + 'Mevcut cihaz şifrenizi doğrulayacak kadar güçlü değil, ancak tüm cihazlarla çalışacak şekilde yeniden oluşturabiliriz.\n\nLütfen kurtarma anahtarınızı kullanarak giriş yapın ve şifrenizi yeniden oluşturun (isterseniz aynı şifreyi tekrar kullanabilirsiniz).'; + + @override + String get useRecoveryKey => 'Kurtarma anahtarını kullan'; + + @override + String get forgotPassword => 'Şifremi unuttum'; + + @override + String get changeEmail => 'E-posta adresini değiştir'; + + @override + String get verifyEmail => 'E-posta adresini doğrulayın'; + + @override + String weHaveSendEmailTo(String email) { + return '$email adresine bir posta gönderdik'; + } + + @override + String get toResetVerifyEmail => + 'Şifrenizi sıfırlamak için lütfen önce e-postanızı doğrulayın.'; + + @override + String get checkInboxAndSpamFolder => + 'Doğrulamayı tamamlamak için lütfen gelen kutunuzu (ve spam kutunuzu) kontrol edin'; + + @override + String get tapToEnterCode => 'Kodu girmek için dokunun'; + + @override + String get sendEmail => 'E-posta gönder'; + + @override + String get resendEmail => 'E-postayı yeniden gönder'; + + @override + String get passKeyPendingVerification => 'Doğrulama hala bekliyor'; + + @override + String get loginSessionExpired => 'Oturum süresi doldu'; + + @override + String get loginSessionExpiredDetails => + 'Oturum süreniz doldu. Tekrar giriş yapın.'; + + @override + String get passkeyAuthTitle => 'Geçiş anahtarı doğrulaması'; + + @override + String get waitingForVerification => 'Doğrulama bekleniyor...'; + + @override + String get tryAgain => 'Tekrar deneyin'; + + @override + String get checkStatus => 'Durumu kontrol et'; + + @override + String get loginWithTOTP => 'TOTP ile giriş yap'; + + @override + String get recoverAccount => 'Hesap kurtarma'; + + @override + String get setPasswordTitle => 'Şifre belirleyin'; + + @override + String get changePasswordTitle => 'Şifreyi değiştirin'; + + @override + String get resetPasswordTitle => 'Şifreyi sıfırlayın'; + + @override + String get encryptionKeys => 'Şifreleme anahtarları'; + + @override + String get enterPasswordToEncrypt => + 'Verilerinizi şifrelemek için kullanabileceğimiz bir şifre girin'; + + @override + String get enterNewPasswordToEncrypt => + 'Verilerinizi şifrelemek için kullanabileceğimiz yeni bir şifre girin'; + + @override + String get passwordWarning => + 'Bu şifreyi saklamıyoruz, bu nedenle unutursanız, verilerinizin şifresini çözemeyiz'; + + @override + String get howItWorks => 'Nasıl çalışır'; + + @override + String get generatingEncryptionKeys => + 'Şifreleme anahtarları oluşturuluyor...'; + + @override + String get passwordChangedSuccessfully => 'Şifre başarıyla değiştirildi'; + + @override + String get signOutFromOtherDevices => 'Diğer cihazlardan çıkış yap'; + + @override + String get signOutOtherBody => + 'Eğer başka birisinin parolanızı bildiğini düşünüyorsanız, diğer tüm cihazları hesabınızdan çıkışa zorlayabilirsiniz.'; + + @override + String get signOutOtherDevices => 'Diğer cihazlardan çıkış yap'; + + @override + String get doNotSignOut => 'Çıkış yapma'; + + @override + String get generatingEncryptionKeysTitle => + 'Şifreleme anahtarları üretiliyor...'; + + @override + String get continueLabel => 'Devam et'; + + @override + String get insecureDevice => 'Güvenli olmayan cihaz'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Üzgünüz, bu cihazda güvenli anahtarlar oluşturamadık.\n\nlütfen farklı bir cihazdan kaydolun.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Kurtarma anahtarı panoya kopyalandı'; + + @override + String get recoveryKey => 'Kurtarma Anahtarı'; + + @override + String get recoveryKeyOnForgotPassword => + 'Eğer şifrenizi unutursanız, verilerinizi kurtarabileceğiniz tek yol bu anahtardır.'; + + @override + String get recoveryKeySaveDescription => + 'Biz bu anahtarı saklamıyoruz, lütfen. bu 24 kelimelik anahtarı güvenli bir yerde saklayın.'; + + @override + String get doThisLater => 'Bunu daha sonra yap'; + + @override + String get saveKey => 'Anahtarı kaydet'; + + @override + String get recoveryKeySaved => + 'Kurtarma anahtarı İndirilenler klasörüne kaydedildi!'; + + @override + String get noRecoveryKeyTitle => 'Kurtarma anahtarınız yok mu?'; + + @override + String get twoFactorAuthTitle => 'İki faktörlü kimlik doğrulama'; + + @override + String get enterCodeHint => + 'Kimlik doğrulayıcı uygulamanızdaki 6 haneli doğrulama kodunu girin'; + + @override + String get lostDeviceTitle => 'Cihazınızı mı kaybettiniz?'; + + @override + String get enterRecoveryKeyHint => 'Kurtarma anahtarınızı girin'; + + @override + String get recover => 'Kurtar'; + + @override + String get loggingOut => 'Çıkış yapılıyor...'; + + @override + String get immediately => 'Hemen'; + + @override + String get appLock => 'Uygulama kilidi'; + + @override + String get autoLock => 'Otomatik Kilit'; + + @override + String get noSystemLockFound => 'Sistem kilidi bulunamadı'; + + @override + String get deviceLockEnablePreSteps => + 'Cihaz kilidini etkinleştirmek için, lütfen cihaz şifresini veya ekran kilidini ayarlayın.'; + + @override + String get appLockDescription => + 'Cihazınızın varsayılan kilit ekranı ile PIN veya parola içeren özel bir kilit ekranı arasında seçim yapın.'; + + @override + String get deviceLock => 'Cihaz kilidi'; + + @override + String get pinLock => 'Pin kilidi'; + + @override + String get autoLockFeatureDescription => + 'Uygulamayı arka plana attıktan sonra kilitlendiği süre'; + + @override + String get hideContent => 'İçeriği gizle'; + + @override + String get hideContentDescriptionAndroid => + 'Uygulama değiştiricide bulunan uygulama içeriğini gizler ve ekran görüntülerini devre dışı bırakır'; + + @override + String get hideContentDescriptioniOS => + 'Uygulama değiştiricideki uygulama içeriğini gizler'; + + @override + String get tooManyIncorrectAttempts => 'Çok fazla hatalı deneme'; + + @override + String get tapToUnlock => 'Açmak için dokun'; + + @override + String get areYouSureYouWantToLogout => + 'Çıkış yapmak istediğinize emin misiniz?'; + + @override + String get yesLogout => 'Evet, çıkış yap'; + + @override + String get authToViewSecrets => + 'Kodlarınızı görmek için lütfen kimlik doğrulaması yapın'; + + @override + String get next => 'Sonraki'; + + @override + String get setNewPassword => 'Yeni şifre belirle'; + + @override + String get enterPin => 'PIN Girin'; + + @override + String get setNewPin => 'Yeni PIN belirleyin'; + + @override + String get confirm => 'Doğrula'; + + @override + String get reEnterPassword => 'Şifrenizi tekrar girin'; + + @override + String get reEnterPin => 'PIN\'inizi tekrar girin'; + + @override + String get androidBiometricHint => 'Kimliği doğrula'; + + @override + String get androidBiometricNotRecognized => 'Tanınmadı. Tekrar deneyin.'; + + @override + String get androidBiometricSuccess => 'Başarılı'; + + @override + String get androidCancelButton => 'İptal et'; + + @override + String get androidSignInTitle => 'Kimlik doğrulaması gerekli'; + + @override + String get androidBiometricRequiredTitle => 'Biyometrik gerekli'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Cihaz kimlik bilgileri gerekli'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Cihaz kimlik bilgileri gerekmekte'; + + @override + String get goToSettings => 'Ayarlara git'; + + @override + String get androidGoToSettingsDescription => + 'Biyometrik kimlik doğrulama cihazınızda ayarlanmamış. Biyometrik kimlik doğrulama eklemek için \'Ayarlar > Güvenlik\' bölümüne gidin.'; + + @override + String get iOSLockOut => + 'Biyometrik kimlik doğrulama devre dışı. Etkinleştirmek için lütfen ekranınızı kilitleyin ve kilidini açın.'; + + @override + String get iOSOkButton => 'Tamam'; + + @override + String get emailAlreadyRegistered => 'E-posta zaten kayıtlı.'; + + @override + String get emailNotRegistered => 'E-posta kayıtlı değil.'; + + @override + String get thisEmailIsAlreadyInUse => 'Bu e-posta zaten kullanılıyor'; + + @override + String emailChangedTo(String newEmail) { + return 'E-posta $newEmail olarak değiştirildi'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Kimlik doğrulama başarısız oldu, lütfen tekrar deneyin'; + + @override + String get authenticationSuccessful => 'Kimlik doğrulama başarılı!'; + + @override + String get sessionExpired => 'Oturum süresi doldu'; + + @override + String get incorrectRecoveryKey => 'Yanlış kurtarma kodu'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Girdiğiniz kurtarma kodu yanlış'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'İki faktörlü kimlik doğrulama başarıyla sıfırlandı'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Doğrulama kodunuzun süresi doldu'; + + @override + String get incorrectCode => 'Yanlış kod'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Üzgünüz, girdiğiniz kod yanlış'; + + @override + String get developerSettings => 'Geliştirici ayarları'; + + @override + String get serverEndpoint => 'Sunucu uç noktası'; + + @override + String get invalidEndpoint => 'Geçersiz uç nokta'; + + @override + String get invalidEndpointMessage => + 'Üzgünüz, girdiğiniz uç nokta geçersiz. Lütfen geçerli bir uç nokta girin ve tekrar deneyin.'; + + @override + String get endpointUpdatedMessage => 'Uç nokta başarıyla güncellendi'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_uk.dart b/mobile/packages/strings/lib/l10n/strings_localizations_uk.dart new file mode 100644 index 0000000000..31acfef908 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_uk.dart @@ -0,0 +1,634 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Ukrainian (`uk`). +class StringsLocalizationsUk extends StringsLocalizations { + StringsLocalizationsUk([String locale = 'uk']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Не вдалося приєднатися до Ente. Будь ласка, перевірте налаштування мережі. Зверніться до нашої команди підтримки, якщо помилка залишиться.'; + + @override + String get networkConnectionRefusedErr => + 'Не вдалося приєднатися до Ente. Будь ласка, спробуйте ще раз через деякий час. Якщо помилка не зникне, зв\'яжіться з нашою командою підтримки.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Схоже, що щось пішло не так. Будь ласка, спробуйте ще раз через деякий час. Якщо помилка не зникне, зв\'яжіться з нашою командою підтримки.'; + + @override + String get error => 'Помилка'; + + @override + String get ok => 'Ок'; + + @override + String get faq => 'Часті питання'; + + @override + String get contactSupport => 'Звернутися до служби підтримки'; + + @override + String get emailYourLogs => 'Відправте ваші журнали електронною поштою'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Будь ласка, надішліть журнали до електронної пошти $toEmail'; + } + + @override + String get copyEmailAddress => 'Копіювати електронну адресу'; + + @override + String get exportLogs => 'Експортувати журнал'; + + @override + String get cancel => 'Скасувати'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Повідомити про помилку'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Приєднано до $endpoint'; + } + + @override + String get save => 'Зберегти'; + + @override + String get send => 'Надіслати'; + + @override + String get saveOrSendDescription => + 'Чи хочете ви зберегти це до свого сховища (типово тека Downloads), чи надіслати його в інші застосунки?'; + + @override + String get saveOnlyDescription => + 'Чи хочете Ви зберегти це до свого сховища (тека Downloads за замовчуванням)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Адреса електронної пошти'; + + @override + String get verify => 'Перевірити'; + + @override + String get invalidEmailTitle => 'Хибна адреса електронної пошти'; + + @override + String get invalidEmailMessage => 'Введіть дійсну адресу електронної пошти.'; + + @override + String get pleaseWait => 'Будь ласка, зачекайте...'; + + @override + String get verifyPassword => 'Підтвердження пароля'; + + @override + String get incorrectPasswordTitle => 'Невірний пароль'; + + @override + String get pleaseTryAgain => 'Будь ласка, спробуйте ще раз'; + + @override + String get enterPassword => 'Введіть пароль'; + + @override + String get enterYourPasswordHint => 'Введіть свій пароль'; + + @override + String get activeSessions => 'Активні сеанси'; + + @override + String get oops => 'От халепа'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Щось пішло не так, спробуйте, будь ласка, знову'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Це призведе до виходу на цьому пристрої!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Це призведе до виходу на наступному пристрої:'; + + @override + String get terminateSession => 'Припинити сеанс?'; + + @override + String get terminate => 'Припинити'; + + @override + String get thisDevice => 'Цей пристрій'; + + @override + String get createAccount => 'Створити обліковий запис'; + + @override + String get weakStrength => 'Слабкий'; + + @override + String get moderateStrength => 'Помірний'; + + @override + String get strongStrength => 'Надійний'; + + @override + String get deleteAccount => 'Видалити обліковий запис'; + + @override + String get deleteAccountQuery => + 'Нам дуже шкода, що Ви залишаєте нас. Чи Ви зіткнулися з якоюсь проблемою?'; + + @override + String get yesSendFeedbackAction => 'Так, надіслати відгук'; + + @override + String get noDeleteAccountAction => 'Ні, видаліть мій обліковий запис'; + + @override + String get initiateAccountDeleteTitle => + 'Будь ласка, авторизуйтесь, щоб розпочати видалення облікового запису'; + + @override + String get confirmAccountDeleteTitle => + 'Підтвердіть видалення облікового запису'; + + @override + String get confirmAccountDeleteMessage => + 'Цей обліковий запис є зв\'язаним з іншими програмами Ente, якщо ви використовуєте якісь з них.\n\nВаші завантажені дані у всіх програмах Ente будуть заплановані до видалення, а обліковий запис буде видалено назавжди.'; + + @override + String get delete => 'Видалити'; + + @override + String get createNewAccount => 'Створити новий обліковий запис'; + + @override + String get password => 'Пароль'; + + @override + String get confirmPassword => 'Підтвердити пароль'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Сила пароля: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => 'Як ви дізналися про Ente? (опціонально)'; + + @override + String get hearUsExplanation => + 'Ми не відстежуємо встановлення застосунків. Але, якщо ви скажете нам, де ви нас знайшли, це допоможе!'; + + @override + String get signUpTerms => + 'Я приймаю умови використання і політику конфіденційності'; + + @override + String get termsOfServicesTitle => 'Умови'; + + @override + String get privacyPolicyTitle => 'Політика конфіденційності'; + + @override + String get ackPasswordLostWarning => + 'Я розумію, що якщо я втрачу свій пароль, я можу втратити свої дані, тому що вони є захищені наскрізним шифруванням.'; + + @override + String get encryption => 'Шифрування'; + + @override + String get logInLabel => 'Увійти'; + + @override + String get welcomeBack => 'З поверненням!'; + + @override + String get loginTerms => + 'Натискаючи «Увійти», я приймаю умови використання і політику конфіденційності'; + + @override + String get noInternetConnection => 'Немає підключення до Інтернету'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Будь ласка, перевірте підключення до Інтернету та спробуйте ще раз.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Перевірка не вдалася, спробуйте ще'; + + @override + String get recreatePasswordTitle => 'Повторно створити пароль'; + + @override + String get recreatePasswordBody => + 'Поточний пристрій не є достатньо потужним для підтвердження пароля, але ми можемо відновити його таким чином, щоб він працював на всіх пристроях.\n\nБудь ласка, увійдіть за допомогою вашого ключа відновлення і відновіть ваш пароль (ви можете знову використати той самий пароль, якщо бажаєте).'; + + @override + String get useRecoveryKey => 'Застосувати ключ відновлення'; + + @override + String get forgotPassword => 'Нагадати пароль'; + + @override + String get changeEmail => 'Змінити адресу електронної пошти'; + + @override + String get verifyEmail => 'Підтвердити електронну адресу'; + + @override + String weHaveSendEmailTo(String email) { + return 'Ми надіслали листа на адресу електронної пошти $email'; + } + + @override + String get toResetVerifyEmail => + 'Щоб скинути пароль, будь ласка, спочатку підтвердіть адресу своєї електронної пошти.'; + + @override + String get checkInboxAndSpamFolder => + 'Будь ласка, перевірте вашу скриньку електронної пошти (та спам), щоб завершити перевірку'; + + @override + String get tapToEnterCode => 'Натисніть, щоб ввести код'; + + @override + String get sendEmail => 'Надіслати електронного листа'; + + @override + String get resendEmail => 'Повторно надіслати лист на електронну пошту'; + + @override + String get passKeyPendingVerification => 'Підтвердження все ще в процесі'; + + @override + String get loginSessionExpired => 'Час сеансу минув'; + + @override + String get loginSessionExpiredDetails => + 'Термін дії вашого сеансу завершився. Будь ласка, увійдіть знову.'; + + @override + String get passkeyAuthTitle => 'Перевірка секретного ключа'; + + @override + String get waitingForVerification => 'Очікується підтвердження...'; + + @override + String get tryAgain => 'Спробуйте ще раз'; + + @override + String get checkStatus => 'Перевірити стан'; + + @override + String get loginWithTOTP => 'Увійти за допомогою TOTP'; + + @override + String get recoverAccount => 'Відновити обліковий запис'; + + @override + String get setPasswordTitle => 'Встановити пароль'; + + @override + String get changePasswordTitle => 'Змінити пароль'; + + @override + String get resetPasswordTitle => 'Скинути пароль'; + + @override + String get encryptionKeys => 'Ключі шифрування'; + + @override + String get enterPasswordToEncrypt => + 'Введіть пароль, який ми зможемо використати для шифрування ваших даних'; + + @override + String get enterNewPasswordToEncrypt => + 'Введіть новий пароль, який ми зможемо використати для шифрування ваших даних'; + + @override + String get passwordWarning => + 'Ми не зберігаємо цей пароль, тому, якщо ви його забудете, ми не зможемо розшифрувати Ваші дані'; + + @override + String get howItWorks => 'Як це працює'; + + @override + String get generatingEncryptionKeys => 'Створення ключів шифрування...'; + + @override + String get passwordChangedSuccessfully => 'Пароль успішно змінено'; + + @override + String get signOutFromOtherDevices => 'Вийти на інших пристроях'; + + @override + String get signOutOtherBody => + 'Якщо ви думаєте, що хтось може знати ваш пароль, ви можете примусити всі інші пристрої, які використовують ваш обліковий запис, вийти з нього.'; + + @override + String get signOutOtherDevices => 'Вийти на інших пристроях'; + + @override + String get doNotSignOut => 'Не виходити'; + + @override + String get generatingEncryptionKeysTitle => 'Створення ключів шифрування...'; + + @override + String get continueLabel => 'Продовжити'; + + @override + String get insecureDevice => 'Незахищений пристрій'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'На жаль, нам не вдалося згенерувати захищені ключі на цьому пристрої.\n\nБудь ласка, увійдіть з іншого пристрою.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Ключ відновлення скопійований в буфер обміну'; + + @override + String get recoveryKey => 'Ключ відновлення'; + + @override + String get recoveryKeyOnForgotPassword => + 'Якщо ви забудете свій пароль, то єдиний спосіб відновити ваші дані – за допомогою цього ключа.'; + + @override + String get recoveryKeySaveDescription => + 'Ми не зберігаємо цей ключ, будь ласка, збережіть цей ключ з 24 слів в надійному місці.'; + + @override + String get doThisLater => 'Зробити це пізніше'; + + @override + String get saveKey => 'Зберегти ключ'; + + @override + String get recoveryKeySaved => + 'Ключ відновлення збережений у теці Downloads!'; + + @override + String get noRecoveryKeyTitle => 'Немає ключа відновлення?'; + + @override + String get twoFactorAuthTitle => 'Двоетапна автентифікація'; + + @override + String get enterCodeHint => + 'Введіть нижче шестизначний код із застосунку для автентифікації'; + + @override + String get lostDeviceTitle => 'Загубили пристрій?'; + + @override + String get enterRecoveryKeyHint => 'Введіть ваш ключ відновлення'; + + @override + String get recover => 'Відновлення'; + + @override + String get loggingOut => 'Вихід із системи...'; + + @override + String get immediately => 'Негайно'; + + @override + String get appLock => 'Блокування'; + + @override + String get autoLock => 'Автоблокування'; + + @override + String get noSystemLockFound => 'Не знайдено системного блокування'; + + @override + String get deviceLockEnablePreSteps => + 'Для увімкнення блокування програми, будь ласка, налаштуйте пароль пристрою або блокування екрана в системних налаштуваннях.'; + + @override + String get appLockDescription => + 'Виберіть між типовим екраном блокування вашого пристрою та власним екраном блокування з PIN-кодом або паролем.'; + + @override + String get deviceLock => 'Блокування пристрою'; + + @override + String get pinLock => 'PIN-код'; + + @override + String get autoLockFeatureDescription => + 'Час, через який застосунок буде заблоковано після розміщення у фоновому режимі'; + + @override + String get hideContent => 'Приховати вміст'; + + @override + String get hideContentDescriptionAndroid => + 'Приховує вміст програми у перемикачі застосунків і вимикає знімки екрану'; + + @override + String get hideContentDescriptioniOS => + 'Приховує вміст у перемикачі застосунків'; + + @override + String get tooManyIncorrectAttempts => 'Завелика кількість невірних спроб'; + + @override + String get tapToUnlock => 'Доторкніться, щоб розблокувати'; + + @override + String get areYouSureYouWantToLogout => + 'Ви впевнені, що хочете вийти з системи?'; + + @override + String get yesLogout => 'Так, вийти з системи'; + + @override + String get authToViewSecrets => + 'Будь ласка, пройдіть автентифікацію, щоб переглянути ваші секретні коди'; + + @override + String get next => 'Наступний'; + + @override + String get setNewPassword => 'Встановити новий пароль'; + + @override + String get enterPin => 'Введіть PIN-код'; + + @override + String get setNewPin => 'Встановити новий PIN-код'; + + @override + String get confirm => 'Підтвердити'; + + @override + String get reEnterPassword => 'Введіть пароль ще раз'; + + @override + String get reEnterPin => 'Введіть PIN-код ще раз'; + + @override + String get androidBiometricHint => 'Підтвердити ідентифікацію'; + + @override + String get androidBiometricNotRecognized => + 'Не розпізнано. Спробуйте ще раз.'; + + @override + String get androidBiometricSuccess => 'Успіх'; + + @override + String get androidCancelButton => 'Скасувати'; + + @override + String get androidSignInTitle => 'Необхідна автентифікація'; + + @override + String get androidBiometricRequiredTitle => + 'Потрібна біометрична автентифікація'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Необхідні облікові дані пристрою'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Необхідні облікові дані пристрою'; + + @override + String get goToSettings => 'Перейти до налаштувань'; + + @override + String get androidGoToSettingsDescription => + 'Біометрична автентифікація не налаштована на вашому пристрої. Перейдіть в «Налаштування > Безпека», щоб додати біометричну автентифікацію.'; + + @override + String get iOSLockOut => + 'Біометрична автентифікація вимкнена. Будь ласка, заблокуйте і розблокуйте свій екран, щоб увімкнути її.'; + + @override + String get iOSOkButton => 'OK'; + + @override + String get emailAlreadyRegistered => 'Email already registered.'; + + @override + String get emailNotRegistered => 'Email not registered.'; + + @override + String get thisEmailIsAlreadyInUse => + 'Ця адреса електронної пошти вже використовується'; + + @override + String emailChangedTo(String newEmail) { + return 'Адресу електронної пошти змінено на $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Автентифікація не пройдена. Будь ласка, спробуйте ще раз'; + + @override + String get authenticationSuccessful => 'Автентифікацію виконано!'; + + @override + String get sessionExpired => 'Час сеансу минув'; + + @override + String get incorrectRecoveryKey => 'Неправильний ключ відновлення'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Ви ввели неправильний ключ відновлення'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Двоетапна автентифікація успішно скинута'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => + 'Час дії коду підтвердження минув'; + + @override + String get incorrectCode => 'Невірний код'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Вибачте, але введений вами код є невірним'; + + @override + String get developerSettings => 'Налаштування розробника'; + + @override + String get serverEndpoint => 'Кінцева точка сервера'; + + @override + String get invalidEndpoint => 'Некоректна кінцева точка'; + + @override + String get invalidEndpointMessage => + 'Вибачте, введена вами кінцева точка є недійсною. Введіть дійсну кінцеву точку та спробуйте ще раз.'; + + @override + String get endpointUpdatedMessage => 'Точка входу успішно оновлена'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart b/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart new file mode 100644 index 0000000000..a55e1a242c --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_vi.dart @@ -0,0 +1,628 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Vietnamese (`vi`). +class StringsLocalizationsVi extends StringsLocalizations { + StringsLocalizationsVi([String locale = 'vi']) : super(locale); + + @override + String get networkHostLookUpErr => + 'Không thể kết nối đến Ente, vui lòng kiểm tra lại kết nối mạng. Nếu vẫn còn lỗi, xin vui lòng liên hệ hỗ trợ.'; + + @override + String get networkConnectionRefusedErr => + 'Không thể kết nối đến Ente, vui lòng thử lại sau. Nếu vẫn còn lỗi, xin vui lòng liên hệ hỗ trợ.'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + 'Có vẻ như đã xảy ra sự cố. Vui lòng thử lại sau một thời gian. Nếu lỗi vẫn tiếp diễn, vui lòng liên hệ với nhóm hỗ trợ của chúng tôi.'; + + @override + String get error => 'Lỗi'; + + @override + String get ok => 'Đồng ý'; + + @override + String get faq => 'Câu hỏi thường gặp'; + + @override + String get contactSupport => 'Liên hệ hỗ trợ'; + + @override + String get emailYourLogs => 'Gửi email nhật ký của bạn'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return 'Vui lòng gửi nhật ký đến \n$toEmail'; + } + + @override + String get copyEmailAddress => 'Sao chép địa chỉ email'; + + @override + String get exportLogs => 'Xuất nhật ký'; + + @override + String get cancel => 'Hủy'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => 'Báo cáo lỗi'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return 'Đã kết nối đến'; + } + + @override + String get save => 'Lưu'; + + @override + String get send => 'Gửi'; + + @override + String get saveOrSendDescription => + 'Bạn có muốn lưu vào bộ nhớ (Mặc định lưu vào thư mục Tải về) hoặc chuyển qua ứng dụng khác?'; + + @override + String get saveOnlyDescription => + 'Bạn có muốn lưu vào bộ nhớ không (Mặc định lưu vào thư mục Tải về)?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => 'Thư điện tử'; + + @override + String get verify => 'Xác minh'; + + @override + String get invalidEmailTitle => 'Địa chỉ email không hợp lệ'; + + @override + String get invalidEmailMessage => + 'Xin vui lòng nhập một địa chỉ email hợp lệ.'; + + @override + String get pleaseWait => 'Vui lòng chờ...'; + + @override + String get verifyPassword => 'Xác nhận mật khẩu'; + + @override + String get incorrectPasswordTitle => 'Mật khẩu không đúng'; + + @override + String get pleaseTryAgain => 'Vui lòng thử lại'; + + @override + String get enterPassword => 'Nhập mật khẩu'; + + @override + String get enterYourPasswordHint => 'Nhập mật khẩu của bạn'; + + @override + String get activeSessions => 'Các phiên làm việc hiện tại'; + + @override + String get oops => 'Rất tiếc'; + + @override + String get somethingWentWrongPleaseTryAgain => + 'Phát hiện có lỗi, xin thử lại'; + + @override + String get thisWillLogYouOutOfThisDevice => + 'Thao tác này sẽ đăng xuất bạn khỏi thiết bị này!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => + 'Thao tác này sẽ đăng xuất bạn khỏi thiết bị sau:'; + + @override + String get terminateSession => 'Kết thúc phiên?'; + + @override + String get terminate => 'Kết thúc'; + + @override + String get thisDevice => 'Thiết bị này'; + + @override + String get createAccount => 'Tạo tài khoản'; + + @override + String get weakStrength => 'Yếu'; + + @override + String get moderateStrength => 'Trung bình'; + + @override + String get strongStrength => 'Mạnh'; + + @override + String get deleteAccount => 'Xoá tài khoản'; + + @override + String get deleteAccountQuery => + 'Chúng tôi sẽ rất tiếc khi thấy bạn đi. Bạn đang phải đối mặt với một số vấn đề?'; + + @override + String get yesSendFeedbackAction => 'Có, gửi phản hồi'; + + @override + String get noDeleteAccountAction => 'Không, xóa tài khoản'; + + @override + String get initiateAccountDeleteTitle => + 'Vui lòng xác thực để bắt đầu xóa tài khoản'; + + @override + String get confirmAccountDeleteTitle => 'Xác nhận xóa tài khoản'; + + @override + String get confirmAccountDeleteMessage => + 'Tài khoản này được liên kết với các ứng dụng Ente trên các nền tảng khác, nếu bạn có sử dụng.\n\nDữ liệu đã tải lên của bạn, trên mọi nền tảng, sẽ bị lên lịch xóa và tài khoản của bạn sẽ bị xóa vĩnh viễn.'; + + @override + String get delete => 'Xóa'; + + @override + String get createNewAccount => 'Tạo tài khoản mới'; + + @override + String get password => 'Mật khẩu'; + + @override + String get confirmPassword => 'Xác nhận mật khẩu'; + + @override + String passwordStrength(String passwordStrengthValue) { + return 'Độ mạnh mật khẩu: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => + 'Bạn biết đến Ente bằng cách nào? (không bắt buộc)'; + + @override + String get hearUsExplanation => + 'Chúng tôi không theo dõi lượt cài đặt ứng dụng. Sẽ rất hữu ích nếu bạn cho chúng tôi biết nơi bạn tìm thấy chúng tôi!'; + + @override + String get signUpTerms => + 'Tôi đồng ý với điều khoản dịch vụchính sách quyền riêng tư'; + + @override + String get termsOfServicesTitle => 'Điều khoản'; + + @override + String get privacyPolicyTitle => 'Chính sách bảo mật'; + + @override + String get ackPasswordLostWarning => + 'Tôi hiểu rằng việc mất mật khẩu có thể đồng nghĩa với việc mất dữ liệu của tôi vì dữ liệu của tôi được mã hóa hai đầu.'; + + @override + String get encryption => 'Mã hóa'; + + @override + String get logInLabel => 'Đăng nhập'; + + @override + String get welcomeBack => 'Chào mừng trở lại!'; + + @override + String get loginTerms => + 'Bằng cách nhấp vào đăng nhập, tôi đồng ý với điều khoản dịch vụchính sách quyền riêng tư'; + + @override + String get noInternetConnection => 'Không có kết nối Internet'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + 'Vui lòng kiểm tra kết nối internet của bạn và thử lại.'; + + @override + String get verificationFailedPleaseTryAgain => + 'Mã xác nhận thất bại. Vui lòng thử lại'; + + @override + String get recreatePasswordTitle => 'Tạo lại mật khẩu'; + + @override + String get recreatePasswordBody => + 'Thiết bị hiện tại không đủ mạnh để xác minh mật khẩu của bạn nhưng chúng tôi có thể tạo lại mật khẩu theo cách hoạt động với tất cả các thiết bị.\n\nVui lòng đăng nhập bằng khóa khôi phục và tạo lại mật khẩu của bạn (bạn có thể sử dụng lại cùng một mật khẩu nếu muốn).'; + + @override + String get useRecoveryKey => 'Dùng khóa khôi phục'; + + @override + String get forgotPassword => 'Quên mật khẩu'; + + @override + String get changeEmail => 'Thay đổi email'; + + @override + String get verifyEmail => 'Xác nhận địa chỉ Email'; + + @override + String weHaveSendEmailTo(String email) { + return 'Chúng tôi đã gửi thư đến $email'; + } + + @override + String get toResetVerifyEmail => + 'Để đặt lại mật khẩu, vui lòng xác minh email của bạn trước.'; + + @override + String get checkInboxAndSpamFolder => + 'Vui lòng kiểm tra hộp thư đến (và thư rác) của bạn để hoàn tất xác minh'; + + @override + String get tapToEnterCode => 'Chạm để nhập mã'; + + @override + String get sendEmail => 'Gửi email'; + + @override + String get resendEmail => 'Gửi lại email'; + + @override + String get passKeyPendingVerification => 'Đang chờ xác thực'; + + @override + String get loginSessionExpired => 'Phiên làm việc hết hạn'; + + @override + String get loginSessionExpiredDetails => + 'Phiên làm việc hết hạn. Vui lòng đăng nhập lại.'; + + @override + String get passkeyAuthTitle => 'Xác minh mã khóa'; + + @override + String get waitingForVerification => 'Đang chờ xác thực'; + + @override + String get tryAgain => 'Thử lại'; + + @override + String get checkStatus => 'Kiểm tra trạng thái'; + + @override + String get loginWithTOTP => 'Đăng nhập bằng TOTP'; + + @override + String get recoverAccount => 'Khôi phục tài khoản'; + + @override + String get setPasswordTitle => 'Đặt mật khẩu'; + + @override + String get changePasswordTitle => 'Thay đổi mật khẩu'; + + @override + String get resetPasswordTitle => 'Đặt lại mật khẩu'; + + @override + String get encryptionKeys => 'Khóa mã hóa'; + + @override + String get enterPasswordToEncrypt => + 'Nhập mật khẩu mà chúng tôi có thể sử dụng để mã hóa dữ liệu của bạn'; + + @override + String get enterNewPasswordToEncrypt => + 'Nhập một mật khẩu mới mà chúng tôi có thể sử dụng để mã hóa dữ liệu của bạn'; + + @override + String get passwordWarning => + 'Chúng tôi không lưu trữ mật khẩu này, vì vậy nếu bạn quên, chúng tôi không thể giải mã dữ liệu của bạn'; + + @override + String get howItWorks => 'Cách thức hoạt động'; + + @override + String get generatingEncryptionKeys => 'Đang tạo khóa mã hóa...'; + + @override + String get passwordChangedSuccessfully => 'Thay đổi mật khẩu thành công'; + + @override + String get signOutFromOtherDevices => 'Đăng xuất khỏi các thiết bị khác'; + + @override + String get signOutOtherBody => + 'Nếu bạn cho rằng ai đó có thể biết mật khẩu của mình, bạn có thể buộc đăng xuất tất cả các thiết bị khác đang sử dụng tài khoản của mình.'; + + @override + String get signOutOtherDevices => 'Đăng xuất khỏi các thiết bị khác'; + + @override + String get doNotSignOut => 'Không được đăng xuất'; + + @override + String get generatingEncryptionKeysTitle => 'Đang tạo khóa mã hóa...'; + + @override + String get continueLabel => 'Tiếp tục'; + + @override + String get insecureDevice => 'Thiết bị không an toàn'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + 'Rất tiếc, chúng tôi không thể tạo khóa bảo mật trên thiết bị này.\n\nvui lòng đăng ký từ một thiết bị khác.'; + + @override + String get recoveryKeyCopiedToClipboard => + 'Đã sao chép khóa khôi phục vào bộ nhớ tạm'; + + @override + String get recoveryKey => 'Khóa khôi phục'; + + @override + String get recoveryKeyOnForgotPassword => + 'Nếu bạn quên mật khẩu, cách duy nhất bạn có thể khôi phục dữ liệu của mình là sử dụng khóa này.'; + + @override + String get recoveryKeySaveDescription => + 'Chúng tôi không lưu trữ khóa này, vui lòng lưu khóa 24 từ này ở nơi an toàn.'; + + @override + String get doThisLater => 'Để sau'; + + @override + String get saveKey => 'Lưu khóa'; + + @override + String get recoveryKeySaved => 'Đã lưu khoá dự phòng vào thư mục Tải về!'; + + @override + String get noRecoveryKeyTitle => 'Không có khóa khôi phục?'; + + @override + String get twoFactorAuthTitle => 'Xác thực hai yếu tố'; + + @override + String get enterCodeHint => + 'Nhập mã gồm 6 chữ số từ ứng dụng xác thực của bạn'; + + @override + String get lostDeviceTitle => 'Mất thiết bị?'; + + @override + String get enterRecoveryKeyHint => 'Nhập khóa khôi phục của bạn'; + + @override + String get recover => 'Khôi phục'; + + @override + String get loggingOut => 'Đang đăng xuất...'; + + @override + String get immediately => 'Tức thì'; + + @override + String get appLock => 'Khóa ứng dụng'; + + @override + String get autoLock => 'Tự động khóa'; + + @override + String get noSystemLockFound => 'Không thấy khoá hệ thống'; + + @override + String get deviceLockEnablePreSteps => + 'Để bật khoá thiết bị, vui lòng thiết lập mật khẩu thiết bị hoặc khóa màn hình trong cài đặt hệ thống của bạn.'; + + @override + String get appLockDescription => + 'Chọn giữa màn hình khoá mặc định của thiết bị và màn hình khoá tự chọn dùng mã PIN hoặc mật khẩu.'; + + @override + String get deviceLock => 'Khóa thiết bị'; + + @override + String get pinLock => 'Mã PIN'; + + @override + String get autoLockFeatureDescription => + 'Thời gian ứng dụng tự khoá sau khi ở trạng thái nền'; + + @override + String get hideContent => 'Ẩn nội dung'; + + @override + String get hideContentDescriptionAndroid => + 'Ẩn nội dung khi chuyển ứng dụng và chặn chụp màn hình'; + + @override + String get hideContentDescriptioniOS => 'Ẩn nội dung khi chuyển ứng dụng'; + + @override + String get tooManyIncorrectAttempts => 'Quá nhiều lần thử không chính xác'; + + @override + String get tapToUnlock => 'Nhấn để mở khóa'; + + @override + String get areYouSureYouWantToLogout => 'Bạn có chắc chắn muốn đăng xuất?'; + + @override + String get yesLogout => 'Có, đăng xuất'; + + @override + String get authToViewSecrets => 'Vui lòng xác thực để xem bí mật của bạn'; + + @override + String get next => 'Tiếp theo'; + + @override + String get setNewPassword => 'Đặt lại mật khẩu'; + + @override + String get enterPin => 'Nhập mã PIN'; + + @override + String get setNewPin => 'Đổi mã PIN'; + + @override + String get confirm => 'Xác nhận'; + + @override + String get reEnterPassword => 'Nhập lại mật khẩu'; + + @override + String get reEnterPin => 'Nhập lại mã PIN'; + + @override + String get androidBiometricHint => 'Xác định danh tính'; + + @override + String get androidBiometricNotRecognized => + 'Không nhận dạng được. Vui lòng thử lại.'; + + @override + String get androidBiometricSuccess => 'Thành công'; + + @override + String get androidCancelButton => 'Hủy bỏ'; + + @override + String get androidSignInTitle => 'Yêu cầu xác thực'; + + @override + String get androidBiometricRequiredTitle => 'Yêu cầu sinh trắc học'; + + @override + String get androidDeviceCredentialsRequiredTitle => + 'Yêu cầu thông tin xác thực thiết bị'; + + @override + String get androidDeviceCredentialsSetupDescription => + 'Yêu cầu thông tin xác thực thiết bị'; + + @override + String get goToSettings => 'Chuyển đến cài đặt'; + + @override + String get androidGoToSettingsDescription => + 'Xác thực sinh trắc học chưa được thiết lập trên thiết bị của bạn. Đi tới \'Cài đặt > Bảo mật\' để thêm xác thực sinh trắc học.'; + + @override + String get iOSLockOut => + 'Xác thực sinh trắc học bị vô hiệu hóa. Vui lòng khóa và mở khóa màn hình của bạn để kích hoạt nó.'; + + @override + String get iOSOkButton => 'Đồng ý'; + + @override + String get emailAlreadyRegistered => 'Email đã được đăng kí.'; + + @override + String get emailNotRegistered => 'Email chưa được đăng kí.'; + + @override + String get thisEmailIsAlreadyInUse => 'Email này đã được sử dụng'; + + @override + String emailChangedTo(String newEmail) { + return 'Thay đổi email thành $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => + 'Xác thực lỗi, vui lòng thử lại'; + + @override + String get authenticationSuccessful => 'Xác thực thành công!'; + + @override + String get sessionExpired => 'Phiên làm việc đã hết hạn'; + + @override + String get incorrectRecoveryKey => 'Khóa khôi phục không chính xác'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => + 'Khóa khôi phục bạn đã nhập không chính xác'; + + @override + String get twofactorAuthenticationSuccessfullyReset => + 'Xác thực hai bước được khôi phục thành công'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => 'Mã xác minh của bạn đã hết hạn'; + + @override + String get incorrectCode => 'Mã không chính xác'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => + 'Xin lỗi, mã bạn đã nhập không chính xác'; + + @override + String get developerSettings => 'Cài đặt cho nhà phát triển'; + + @override + String get serverEndpoint => 'Điểm cuối máy chủ'; + + @override + String get invalidEndpoint => 'Điểm cuối không hợp lệ'; + + @override + String get invalidEndpointMessage => + 'Xin lỗi, điểm cuối bạn nhập không hợp lệ. Vui lòng nhập một điểm cuối hợp lệ và thử lại.'; + + @override + String get endpointUpdatedMessage => 'Cập nhật điểm cuối thành công'; +} diff --git a/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart b/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart new file mode 100644 index 0000000000..d0447c1877 --- /dev/null +++ b/mobile/packages/strings/lib/l10n/strings_localizations_zh.dart @@ -0,0 +1,1684 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'strings_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Chinese (`zh`). +class StringsLocalizationsZh extends StringsLocalizations { + StringsLocalizationsZh([String locale = 'zh']) : super(locale); + + @override + String get networkHostLookUpErr => '无法连接到 Ente,请检查您的网络设置,如果错误仍然存​​在,请联系支持。'; + + @override + String get networkConnectionRefusedErr => + '无法连接到 Ente,请稍后重试。如果错误仍然存​​在,请联系支持人员。'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + '看起来出了点问题。 请稍后重试。 如果错误仍然存在,请联系我们的支持团队。'; + + @override + String get error => '错误'; + + @override + String get ok => '确定'; + + @override + String get faq => '常见问题'; + + @override + String get contactSupport => '联系支持'; + + @override + String get emailYourLogs => '通过电子邮件发送您的日志'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return '请将日志发送至 \n$toEmail'; + } + + @override + String get copyEmailAddress => '复制电子邮件地址'; + + @override + String get exportLogs => '导出日志'; + + @override + String get cancel => '取消'; + + @override + String pleaseEmailUsAt(String toEmail) { + return 'Email us at $toEmail'; + } + + @override + String get emailAddressCopied => 'Email address copied'; + + @override + String get supportEmailSubject => '[Support]'; + + @override + String get clientDebugInfoLabel => + 'Following information can help us in debugging if you are facing any issue'; + + @override + String get registeredEmailLabel => 'Registered email:'; + + @override + String get clientLabel => 'Client:'; + + @override + String get versionLabel => 'Version :'; + + @override + String get notAvailable => 'N/A'; + + @override + String get reportABug => '报告错误'; + + @override + String get logsDialogBody => + 'This will send across logs to help us debug your issue. Please note that file names will be included to help track issues with specific files.'; + + @override + String get viewLogs => 'View logs'; + + @override + String customEndpoint(String endpoint) { + return '已连接至 $endpoint'; + } + + @override + String get save => '保存'; + + @override + String get send => '发送'; + + @override + String get saveOrSendDescription => + '您想将其保存到您的内置存储(默认情况下为“下载”文件夹)还是将其发送到其他应用程序?'; + + @override + String get saveOnlyDescription => '您想将其保存到您的内置存储中(默认情况下为“下载”文件夹)吗?'; + + @override + String get enterNewEmailHint => 'Enter your new email address'; + + @override + String get email => '电子邮件地址'; + + @override + String get verify => '验证'; + + @override + String get invalidEmailTitle => '无效的电子邮件地址'; + + @override + String get invalidEmailMessage => '请输入一个有效的电子邮件地址。'; + + @override + String get pleaseWait => '请稍候...'; + + @override + String get verifyPassword => '验证密码'; + + @override + String get incorrectPasswordTitle => '密码错误'; + + @override + String get pleaseTryAgain => '请重试'; + + @override + String get enterPassword => '输入密码'; + + @override + String get enterYourPasswordHint => '输入您的密码'; + + @override + String get activeSessions => '已登录的设备'; + + @override + String get oops => '哎呀'; + + @override + String get somethingWentWrongPleaseTryAgain => '出了点问题,请重试'; + + @override + String get thisWillLogYouOutOfThisDevice => '这将使您登出该设备!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => '这将使您登出以下设备:'; + + @override + String get terminateSession => '是否终止会话?'; + + @override + String get terminate => '终止'; + + @override + String get thisDevice => '此设备'; + + @override + String get createAccount => '创建账户'; + + @override + String get weakStrength => '弱'; + + @override + String get moderateStrength => '中'; + + @override + String get strongStrength => '强'; + + @override + String get deleteAccount => '删除账户'; + + @override + String get deleteAccountQuery => '我们很抱歉看到您离开。您面临一些问题?'; + + @override + String get yesSendFeedbackAction => '是,发送反馈'; + + @override + String get noDeleteAccountAction => '否,删除账户'; + + @override + String get initiateAccountDeleteTitle => '请进行身份验证以启动账户删除'; + + @override + String get confirmAccountDeleteTitle => '确认删除账户'; + + @override + String get confirmAccountDeleteMessage => + '如果您使用其他 Ente 应用程序,该账户将会与其他应用程序链接。\n\n在所有 Ente 应用程序中,您上传的数据将被安排用于删除,并且您的账户将被永久删除。'; + + @override + String get delete => '删除'; + + @override + String get createNewAccount => '创建新账号'; + + @override + String get password => '密码'; + + @override + String get confirmPassword => '请确认密码'; + + @override + String passwordStrength(String passwordStrengthValue) { + return '密码强度: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => '您是怎么知道 Ente 的?(可选)'; + + @override + String get hearUsExplanation => '我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!'; + + @override + String get signUpTerms => + '我同意 服务条款隐私政策'; + + @override + String get termsOfServicesTitle => '服务条款'; + + @override + String get privacyPolicyTitle => '隐私政策'; + + @override + String get ackPasswordLostWarning => + '我明白,如果我丢失密码,我可能会丢失我的数据,因为我的数据是 端到端加密的。'; + + @override + String get encryption => '加密'; + + @override + String get logInLabel => '登录'; + + @override + String get welcomeBack => '欢迎回来!'; + + @override + String get loginTerms => + '点击登录后,我同意 服务条款隐私政策'; + + @override + String get noInternetConnection => '无互联网连接'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => '请检查您的互联网连接,然后重试。'; + + @override + String get verificationFailedPleaseTryAgain => '验证失败,请再试一次'; + + @override + String get recreatePasswordTitle => '重新创建密码'; + + @override + String get recreatePasswordBody => + '当前设备的功能不足以验证您的密码,但我们可以以适用于所有设备的方式重新生成。\n\n请使用您的恢复密钥登录并重新生成您的密码(如果您愿意,可以再次使用相同的密码)。'; + + @override + String get useRecoveryKey => '使用恢复密钥'; + + @override + String get forgotPassword => '忘记密码'; + + @override + String get changeEmail => '修改邮箱'; + + @override + String get verifyEmail => '验证电子邮件'; + + @override + String weHaveSendEmailTo(String email) { + return '我们已经发送邮件到 $email'; + } + + @override + String get toResetVerifyEmail => '要重置您的密码,请先验证您的电子邮件。'; + + @override + String get checkInboxAndSpamFolder => '请检查您的收件箱 (或者是在您的“垃圾邮件”列表内) 以完成验证'; + + @override + String get tapToEnterCode => '点击以输入代码'; + + @override + String get sendEmail => '发送电子邮件'; + + @override + String get resendEmail => '重新发送电子邮件'; + + @override + String get passKeyPendingVerification => '仍需验证'; + + @override + String get loginSessionExpired => '会话已过期'; + + @override + String get loginSessionExpiredDetails => '您的会话已过期。请重新登录。'; + + @override + String get passkeyAuthTitle => '通行密钥验证'; + + @override + String get waitingForVerification => '等待验证...'; + + @override + String get tryAgain => '请再试一次'; + + @override + String get checkStatus => '检查状态'; + + @override + String get loginWithTOTP => '使用 TOTP 登录'; + + @override + String get recoverAccount => '恢复账户'; + + @override + String get setPasswordTitle => '设置密码'; + + @override + String get changePasswordTitle => '修改密码'; + + @override + String get resetPasswordTitle => '重置密码'; + + @override + String get encryptionKeys => '加密密钥'; + + @override + String get enterPasswordToEncrypt => '输入我们可以用来加密您的数据的密码'; + + @override + String get enterNewPasswordToEncrypt => '输入我们可以用来加密您的数据的新密码'; + + @override + String get passwordWarning => + '我们不储存这个密码,所以如果忘记, 我们不能解密您的数据'; + + @override + String get howItWorks => '工作原理'; + + @override + String get generatingEncryptionKeys => '正在生成加密密钥...'; + + @override + String get passwordChangedSuccessfully => '密码修改成功'; + + @override + String get signOutFromOtherDevices => '从其他设备登出'; + + @override + String get signOutOtherBody => '如果您认为有人可能知道您的密码,您可以强制所有其他使用您账户的设备登出。'; + + @override + String get signOutOtherDevices => '登出其他设备'; + + @override + String get doNotSignOut => '不要登出'; + + @override + String get generatingEncryptionKeysTitle => '正在生成加密密钥...'; + + @override + String get continueLabel => '继续'; + + @override + String get insecureDevice => '设备不安全'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + '抱歉,我们无法在此设备上生成安全密钥。\n\n请使用其他设备注册。'; + + @override + String get recoveryKeyCopiedToClipboard => '恢复密钥已复制到剪贴板'; + + @override + String get recoveryKey => '恢复密钥'; + + @override + String get recoveryKeyOnForgotPassword => '如果您忘记了密码,恢复数据的唯一方法就是使用此密钥。'; + + @override + String get recoveryKeySaveDescription => '我们不会存储此密钥,请将此24个单词密钥保存在一个安全的地方。'; + + @override + String get doThisLater => '稍后再做'; + + @override + String get saveKey => '保存密钥'; + + @override + String get recoveryKeySaved => '恢复密钥已保存在下载文件夹中!'; + + @override + String get noRecoveryKeyTitle => '没有恢复密钥吗?'; + + @override + String get twoFactorAuthTitle => '两步验证'; + + @override + String get enterCodeHint => '从你的身份验证器应用中\n输入6位数字代码'; + + @override + String get lostDeviceTitle => '丢失了设备吗?'; + + @override + String get enterRecoveryKeyHint => '输入您的恢复密钥'; + + @override + String get recover => '恢复'; + + @override + String get loggingOut => '正在登出...'; + + @override + String get immediately => '立即'; + + @override + String get appLock => '应用锁'; + + @override + String get autoLock => '自动锁定'; + + @override + String get noSystemLockFound => '未找到系统锁'; + + @override + String get deviceLockEnablePreSteps => '要启用设备锁,请在系统设置中设置设备密码或屏幕锁。'; + + @override + String get appLockDescription => '在设备的默认锁定屏幕和带有 PIN 或密码的自定义锁定屏幕之间进行选择。'; + + @override + String get deviceLock => '设备锁'; + + @override + String get pinLock => 'Pin 锁定'; + + @override + String get autoLockFeatureDescription => '应用程序进入后台后锁定的时间'; + + @override + String get hideContent => '隐藏内容'; + + @override + String get hideContentDescriptionAndroid => '在应用切换器中隐藏应用内容并禁用屏幕截图'; + + @override + String get hideContentDescriptioniOS => '在应用切换器中隐藏应用内容'; + + @override + String get tooManyIncorrectAttempts => '错误的尝试次数过多'; + + @override + String get tapToUnlock => '点击解锁'; + + @override + String get areYouSureYouWantToLogout => '您确定要登出吗?'; + + @override + String get yesLogout => '是的,登出'; + + @override + String get authToViewSecrets => '请进行身份验证以查看您的密钥'; + + @override + String get next => '下一步'; + + @override + String get setNewPassword => '设置新密码'; + + @override + String get enterPin => '输入 PIN 码'; + + @override + String get setNewPin => '设置新 PIN 码'; + + @override + String get confirm => '确认'; + + @override + String get reEnterPassword => '再次输入密码'; + + @override + String get reEnterPin => '再次输入 PIN 码'; + + @override + String get androidBiometricHint => '验证身份'; + + @override + String get androidBiometricNotRecognized => '未能识别,请重试。'; + + @override + String get androidBiometricSuccess => '成功'; + + @override + String get androidCancelButton => '取消'; + + @override + String get androidSignInTitle => '需要进行身份验证'; + + @override + String get androidBiometricRequiredTitle => '需要进行生物识别认证'; + + @override + String get androidDeviceCredentialsRequiredTitle => '需要设备凭据'; + + @override + String get androidDeviceCredentialsSetupDescription => '需要设备凭据'; + + @override + String get goToSettings => '前往设置'; + + @override + String get androidGoToSettingsDescription => + '您的设备上未设置生物识别身份验证。转到“设置 > 安全”以添加生物识别身份验证。'; + + @override + String get iOSLockOut => '生物识别身份验证已禁用。请锁定并解锁屏幕以启用该功能。'; + + @override + String get iOSOkButton => '好'; + + @override + String get emailAlreadyRegistered => '电子邮件地址已被注册。'; + + @override + String get emailNotRegistered => '电子邮件地址未注册。'; + + @override + String get thisEmailIsAlreadyInUse => '该电子邮件已被使用'; + + @override + String emailChangedTo(String newEmail) { + return '电子邮件已更改为 $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => '认证失败,请重试'; + + @override + String get authenticationSuccessful => '认证成功!'; + + @override + String get sessionExpired => '会话已过期'; + + @override + String get incorrectRecoveryKey => '恢复密钥不正确'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => '您输入的恢复密钥不正确'; + + @override + String get twofactorAuthenticationSuccessfullyReset => '两步验证已成功重置'; + + @override + String get noRecoveryKey => 'No recovery key'; + + @override + String get yourAccountHasBeenDeleted => 'Your account has been deleted'; + + @override + String get verificationId => 'Verification ID'; + + @override + String get yourVerificationCodeHasExpired => '您的验证码已过期'; + + @override + String get incorrectCode => '验证码错误'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => '抱歉,您输入的验证码不正确'; + + @override + String get developerSettings => '开发者设置'; + + @override + String get serverEndpoint => '服务器端点'; + + @override + String get invalidEndpoint => '端点无效'; + + @override + String get invalidEndpointMessage => '抱歉,您输入的端点无效。请输入有效的端点,然后重试。'; + + @override + String get endpointUpdatedMessage => '端点更新成功'; +} + +/// The translations for Chinese, as used in China (`zh_CN`). +class StringsLocalizationsZhCn extends StringsLocalizationsZh { + StringsLocalizationsZhCn() : super('zh_CN'); + + @override + String get networkHostLookUpErr => '无法连接到 Ente,请检查您的网络设置,如果错误仍然存​​在,请联系支持。'; + + @override + String get networkConnectionRefusedErr => + '无法连接到 Ente,请稍后重试。如果错误仍然存​​在,请联系支持人员。'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + '看起来出了点问题。 请稍后重试。 如果错误仍然存在,请联系我们的支持团队。'; + + @override + String get error => '错误'; + + @override + String get ok => '确定'; + + @override + String get faq => '常见问题'; + + @override + String get contactSupport => '联系支持'; + + @override + String get emailYourLogs => '通过电子邮件发送您的日志'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return '请将日志发送至 \n$toEmail'; + } + + @override + String get copyEmailAddress => '复制电子邮件地址'; + + @override + String get exportLogs => '导出日志'; + + @override + String get cancel => '取消'; + + @override + String get reportABug => '报告错误'; + + @override + String customEndpoint(String endpoint) { + return '已连接至 $endpoint'; + } + + @override + String get save => '保存'; + + @override + String get send => '发送'; + + @override + String get saveOrSendDescription => + '您想将其保存到您的内置存储(默认情况下为“下载”文件夹)还是将其发送到其他应用程序?'; + + @override + String get saveOnlyDescription => '您想将其保存到您的内置存储中(默认情况下为“下载”文件夹)吗?'; + + @override + String get enterNewEmailHint => '请输入您的新电子邮件地址'; + + @override + String get email => '电子邮件地址'; + + @override + String get verify => '验证'; + + @override + String get invalidEmailTitle => '无效的电子邮件地址'; + + @override + String get invalidEmailMessage => '请输入一个有效的电子邮件地址。'; + + @override + String get pleaseWait => '请稍候...'; + + @override + String get verifyPassword => '验证密码'; + + @override + String get incorrectPasswordTitle => '密码错误'; + + @override + String get pleaseTryAgain => '请重试'; + + @override + String get enterPassword => '输入密码'; + + @override + String get enterYourPasswordHint => '输入您的密码'; + + @override + String get activeSessions => '已登录的设备'; + + @override + String get oops => '哎呀'; + + @override + String get somethingWentWrongPleaseTryAgain => '出了点问题,请重试'; + + @override + String get thisWillLogYouOutOfThisDevice => '这将使您登出该设备!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => '这将使您登出以下设备:'; + + @override + String get terminateSession => '是否终止会话?'; + + @override + String get terminate => '终止'; + + @override + String get thisDevice => '此设备'; + + @override + String get createAccount => '创建账户'; + + @override + String get weakStrength => '弱'; + + @override + String get moderateStrength => '中'; + + @override + String get strongStrength => '强'; + + @override + String get deleteAccount => '删除账户'; + + @override + String get deleteAccountQuery => '我们很抱歉看到您离开。您面临一些问题?'; + + @override + String get yesSendFeedbackAction => '是,发送反馈'; + + @override + String get noDeleteAccountAction => '否,删除账户'; + + @override + String get initiateAccountDeleteTitle => '请进行身份验证以启动账户删除'; + + @override + String get confirmAccountDeleteTitle => '确认删除账户'; + + @override + String get confirmAccountDeleteMessage => + '如果您使用其他 Ente 应用程序,该账户将会与其他应用程序链接。\n\n在所有 Ente 应用程序中,您上传的数据将被安排用于删除,并且您的账户将被永久删除。'; + + @override + String get delete => '删除'; + + @override + String get createNewAccount => '创建新账号'; + + @override + String get password => '密码'; + + @override + String get confirmPassword => '请确认密码'; + + @override + String passwordStrength(String passwordStrengthValue) { + return '密码强度: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => '您是怎么知道 Ente 的?(可选)'; + + @override + String get hearUsExplanation => '我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!'; + + @override + String get signUpTerms => + '我同意 服务条款隐私政策'; + + @override + String get termsOfServicesTitle => '服务条款'; + + @override + String get privacyPolicyTitle => '隐私政策'; + + @override + String get ackPasswordLostWarning => + '我明白,如果我丢失密码,我可能会丢失我的数据,因为我的数据是 端到端加密的。'; + + @override + String get encryption => '加密'; + + @override + String get logInLabel => '登录'; + + @override + String get welcomeBack => '欢迎回来!'; + + @override + String get loginTerms => + '点击登录后,我同意 服务条款隐私政策'; + + @override + String get noInternetConnection => '无互联网连接'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => '请检查您的互联网连接,然后重试。'; + + @override + String get verificationFailedPleaseTryAgain => '验证失败,请再试一次'; + + @override + String get recreatePasswordTitle => '重新创建密码'; + + @override + String get recreatePasswordBody => + '当前设备的功能不足以验证您的密码,但我们可以以适用于所有设备的方式重新生成。\n\n请使用您的恢复密钥登录并重新生成您的密码(如果您愿意,可以再次使用相同的密码)。'; + + @override + String get useRecoveryKey => '使用恢复密钥'; + + @override + String get forgotPassword => '忘记密码'; + + @override + String get changeEmail => '修改邮箱'; + + @override + String get verifyEmail => '验证电子邮件'; + + @override + String weHaveSendEmailTo(String email) { + return '我们已经发送邮件到 $email'; + } + + @override + String get toResetVerifyEmail => '要重置您的密码,请先验证您的电子邮件。'; + + @override + String get checkInboxAndSpamFolder => '请检查您的收件箱 (或者是在您的“垃圾邮件”列表内) 以完成验证'; + + @override + String get tapToEnterCode => '点击以输入代码'; + + @override + String get sendEmail => '发送电子邮件'; + + @override + String get resendEmail => '重新发送电子邮件'; + + @override + String get passKeyPendingVerification => '仍需验证'; + + @override + String get loginSessionExpired => '会话已过期'; + + @override + String get loginSessionExpiredDetails => '您的会话已过期。请重新登录。'; + + @override + String get passkeyAuthTitle => '通行密钥验证'; + + @override + String get waitingForVerification => '等待验证...'; + + @override + String get tryAgain => '请再试一次'; + + @override + String get checkStatus => '检查状态'; + + @override + String get loginWithTOTP => '使用 TOTP 登录'; + + @override + String get recoverAccount => '恢复账户'; + + @override + String get setPasswordTitle => '设置密码'; + + @override + String get changePasswordTitle => '修改密码'; + + @override + String get resetPasswordTitle => '重置密码'; + + @override + String get encryptionKeys => '加密密钥'; + + @override + String get enterPasswordToEncrypt => '输入我们可以用来加密您的数据的密码'; + + @override + String get enterNewPasswordToEncrypt => '输入我们可以用来加密您的数据的新密码'; + + @override + String get passwordWarning => + '我们不储存这个密码,所以如果忘记, 我们不能解密您的数据'; + + @override + String get howItWorks => '工作原理'; + + @override + String get generatingEncryptionKeys => '正在生成加密密钥...'; + + @override + String get passwordChangedSuccessfully => '密码修改成功'; + + @override + String get signOutFromOtherDevices => '从其他设备登出'; + + @override + String get signOutOtherBody => '如果您认为有人可能知道您的密码,您可以强制所有其他使用您账户的设备登出。'; + + @override + String get signOutOtherDevices => '登出其他设备'; + + @override + String get doNotSignOut => '不要登出'; + + @override + String get generatingEncryptionKeysTitle => '正在生成加密密钥...'; + + @override + String get continueLabel => '继续'; + + @override + String get insecureDevice => '设备不安全'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + '抱歉,我们无法在此设备上生成安全密钥。\n\n请使用其他设备注册。'; + + @override + String get recoveryKeyCopiedToClipboard => '恢复密钥已复制到剪贴板'; + + @override + String get recoveryKey => '恢复密钥'; + + @override + String get recoveryKeyOnForgotPassword => '如果您忘记了密码,恢复数据的唯一方法就是使用此密钥。'; + + @override + String get recoveryKeySaveDescription => '我们不会存储此密钥,请将此24个单词密钥保存在一个安全的地方。'; + + @override + String get doThisLater => '稍后再做'; + + @override + String get saveKey => '保存密钥'; + + @override + String get recoveryKeySaved => '恢复密钥已保存在下载文件夹中!'; + + @override + String get noRecoveryKeyTitle => '没有恢复密钥吗?'; + + @override + String get twoFactorAuthTitle => '两步验证'; + + @override + String get enterCodeHint => '从你的身份验证器应用中\n输入6位数字代码'; + + @override + String get lostDeviceTitle => '丢失了设备吗?'; + + @override + String get enterRecoveryKeyHint => '输入您的恢复密钥'; + + @override + String get recover => '恢复'; + + @override + String get loggingOut => '正在登出...'; + + @override + String get immediately => '立即'; + + @override + String get appLock => '应用锁'; + + @override + String get autoLock => '自动锁定'; + + @override + String get noSystemLockFound => '未找到系统锁'; + + @override + String get deviceLockEnablePreSteps => '要启用设备锁,请在系统设置中设置设备密码或屏幕锁。'; + + @override + String get appLockDescription => '在设备的默认锁定屏幕和带有 PIN 或密码的自定义锁定屏幕之间进行选择。'; + + @override + String get deviceLock => '设备锁'; + + @override + String get pinLock => 'Pin 锁定'; + + @override + String get autoLockFeatureDescription => '应用程序进入后台后锁定的时间'; + + @override + String get hideContent => '隐藏内容'; + + @override + String get hideContentDescriptionAndroid => '在应用切换器中隐藏应用内容并禁用屏幕截图'; + + @override + String get hideContentDescriptioniOS => '在应用切换器中隐藏应用内容'; + + @override + String get tooManyIncorrectAttempts => '错误的尝试次数过多'; + + @override + String get tapToUnlock => '点击解锁'; + + @override + String get areYouSureYouWantToLogout => '您确定要登出吗?'; + + @override + String get yesLogout => '是的,登出'; + + @override + String get authToViewSecrets => '请进行身份验证以查看您的密钥'; + + @override + String get next => '下一步'; + + @override + String get setNewPassword => '设置新密码'; + + @override + String get enterPin => '输入 PIN 码'; + + @override + String get setNewPin => '设置新 PIN 码'; + + @override + String get confirm => '确认'; + + @override + String get reEnterPassword => '再次输入密码'; + + @override + String get reEnterPin => '再次输入 PIN 码'; + + @override + String get androidBiometricHint => '验证身份'; + + @override + String get androidBiometricNotRecognized => '未能识别,请重试。'; + + @override + String get androidBiometricSuccess => '成功'; + + @override + String get androidCancelButton => '取消'; + + @override + String get androidSignInTitle => '需要进行身份验证'; + + @override + String get androidBiometricRequiredTitle => '需要进行生物识别认证'; + + @override + String get androidDeviceCredentialsRequiredTitle => '需要设备凭据'; + + @override + String get androidDeviceCredentialsSetupDescription => '需要设备凭据'; + + @override + String get goToSettings => '前往设置'; + + @override + String get androidGoToSettingsDescription => + '您的设备上未设置生物识别身份验证。转到“设置 > 安全”以添加生物识别身份验证。'; + + @override + String get iOSLockOut => '生物识别身份验证已禁用。请锁定并解锁屏幕以启用该功能。'; + + @override + String get iOSOkButton => '好'; + + @override + String get emailAlreadyRegistered => '电子邮件地址已被注册。'; + + @override + String get emailNotRegistered => '电子邮件地址未注册。'; + + @override + String get thisEmailIsAlreadyInUse => '该电子邮件已被使用'; + + @override + String emailChangedTo(String newEmail) { + return '电子邮件已更改为 $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => '认证失败,请重试'; + + @override + String get authenticationSuccessful => '认证成功!'; + + @override + String get sessionExpired => '会话已过期'; + + @override + String get incorrectRecoveryKey => '恢复密钥不正确'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => '您输入的恢复密钥不正确'; + + @override + String get twofactorAuthenticationSuccessfullyReset => '两步验证已成功重置'; + + @override + String get yourVerificationCodeHasExpired => '您的验证码已过期'; + + @override + String get incorrectCode => '验证码错误'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => '抱歉,您输入的验证码不正确'; + + @override + String get developerSettings => '开发者设置'; + + @override + String get serverEndpoint => '服务器端点'; + + @override + String get invalidEndpoint => '端点无效'; + + @override + String get invalidEndpointMessage => '抱歉,您输入的端点无效。请输入有效的端点,然后重试。'; + + @override + String get endpointUpdatedMessage => '端点更新成功'; +} + +/// The translations for Chinese, as used in Taiwan (`zh_TW`). +class StringsLocalizationsZhTw extends StringsLocalizationsZh { + StringsLocalizationsZhTw() : super('zh_TW'); + + @override + String get networkHostLookUpErr => '無法連接到 Ente,請檢查您的網路設定,如果錯誤仍然存​​在,請聯絡支援。'; + + @override + String get networkConnectionRefusedErr => + '無法連接到 Ente,請稍後重試。如果錯誤仍然存​​在,請聯絡支援人員。'; + + @override + String get itLooksLikeSomethingWentWrongPleaseRetryAfterSome => + '看起來出了點問題。 請稍後重試。 如果錯誤仍然存在,請聯絡我們的支援團隊。'; + + @override + String get error => '錯誤'; + + @override + String get ok => '確定'; + + @override + String get faq => '常見問題'; + + @override + String get contactSupport => '聯絡支援'; + + @override + String get emailYourLogs => '通過電子郵件傳送您的日誌'; + + @override + String pleaseSendTheLogsTo(String toEmail) { + return '請將日誌傳送至 \n$toEmail'; + } + + @override + String get copyEmailAddress => '複製電子郵件地址'; + + @override + String get exportLogs => '匯出日誌'; + + @override + String get cancel => '取消'; + + @override + String get reportABug => '報告錯誤'; + + @override + String customEndpoint(String endpoint) { + return '已連接至 $endpoint'; + } + + @override + String get save => '儲存'; + + @override + String get send => '傳送'; + + @override + String get saveOrSendDescription => + '您想將其儲存到您的內建儲存(預設情況下為“下載”資料夾)還是將其傳送到其他APP?'; + + @override + String get saveOnlyDescription => '您想將其儲存到您的內建儲存中(預設情況下為“下載”資料夾)嗎?'; + + @override + String get enterNewEmailHint => '輸入你的新電子郵件地址'; + + @override + String get email => '電子郵件地址'; + + @override + String get verify => '驗證'; + + @override + String get invalidEmailTitle => '無效的電子郵件地址'; + + @override + String get invalidEmailMessage => '請輸入一個有效的電子郵件地址。'; + + @override + String get pleaseWait => '請稍候...'; + + @override + String get verifyPassword => '驗證密碼'; + + @override + String get incorrectPasswordTitle => '密碼錯誤'; + + @override + String get pleaseTryAgain => '請重試'; + + @override + String get enterPassword => '輸入密碼'; + + @override + String get enterYourPasswordHint => '輸入您的密碼'; + + @override + String get activeSessions => '已登錄的裝置'; + + @override + String get oops => '哎呀'; + + @override + String get somethingWentWrongPleaseTryAgain => '出了點問題,請重試'; + + @override + String get thisWillLogYouOutOfThisDevice => '這將使您登出該裝置!'; + + @override + String get thisWillLogYouOutOfTheFollowingDevice => '這將使您登出以下裝置:'; + + @override + String get terminateSession => '是否終止工作階段?'; + + @override + String get terminate => '終止'; + + @override + String get thisDevice => '此裝置'; + + @override + String get createAccount => '建立帳戶'; + + @override + String get weakStrength => '弱'; + + @override + String get moderateStrength => '中'; + + @override + String get strongStrength => '強'; + + @override + String get deleteAccount => '刪除帳戶'; + + @override + String get deleteAccountQuery => '我們很抱歉看到您要刪除帳戶。您似乎面臨著一些問題?'; + + @override + String get yesSendFeedbackAction => '是,傳送回饋'; + + @override + String get noDeleteAccountAction => '否,刪除帳戶'; + + @override + String get initiateAccountDeleteTitle => '請進行身份驗證以啟動帳戶刪除'; + + @override + String get confirmAccountDeleteTitle => '確認刪除帳戶'; + + @override + String get confirmAccountDeleteMessage => + '如果您使用其他 Ente APP,該帳戶將會與其他APP連結。\n\n在所有 Ente APP中,您上傳的資料將被安排用於刪除,並且您的帳戶將被永久刪除。'; + + @override + String get delete => '刪除'; + + @override + String get createNewAccount => '建立新帳號'; + + @override + String get password => '密碼'; + + @override + String get confirmPassword => '請確認密碼'; + + @override + String passwordStrength(String passwordStrengthValue) { + return '密碼強度: $passwordStrengthValue'; + } + + @override + String get hearUsWhereTitle => '您是怎麼知道 Ente 的?(可選)'; + + @override + String get hearUsExplanation => '我們不跟蹤APP安裝情況。如果您告訴我們您是在哪裡找到我們的,將會有所幫助!'; + + @override + String get signUpTerms => + '我同意 服務條款隱私政策'; + + @override + String get termsOfServicesTitle => '服務條款'; + + @override + String get privacyPolicyTitle => '隱私政策'; + + @override + String get ackPasswordLostWarning => + '我明白,如果我遺失密碼,我可能會遺失我的資料,因為我的資料是 端到端加密的。'; + + @override + String get encryption => '加密'; + + @override + String get logInLabel => '登錄'; + + @override + String get welcomeBack => '歡迎回來!'; + + @override + String get loginTerms => + '點選登錄後,我同意 服務條款隱私政策'; + + @override + String get noInternetConnection => '無網際網路連接'; + + @override + String get pleaseCheckYourInternetConnectionAndTryAgain => + '請檢查您的網際網路連接,然後重試。'; + + @override + String get verificationFailedPleaseTryAgain => '驗證失敗,請再試一次'; + + @override + String get recreatePasswordTitle => '重新建立密碼'; + + @override + String get recreatePasswordBody => + '目前裝置的功能不足以驗證您的密碼,但我們可以以適用於所有裝置的方式重新產生。\n\n請使用您的復原密鑰登錄並重新產生您的密碼(如果您願意,可以再次使用相同的密碼)。'; + + @override + String get useRecoveryKey => '使用復原密鑰'; + + @override + String get forgotPassword => '忘記密碼'; + + @override + String get changeEmail => '修改信箱'; + + @override + String get verifyEmail => '驗證電子郵件'; + + @override + String weHaveSendEmailTo(String email) { + return '我們已經傳送郵件到 $email'; + } + + @override + String get toResetVerifyEmail => '要重設您的密碼,請先驗證您的電子郵件。'; + + @override + String get checkInboxAndSpamFolder => '請檢查您的收件箱 (或者是在您的“垃圾郵件”列表內) 以完成驗證'; + + @override + String get tapToEnterCode => '點選以輸入程式碼'; + + @override + String get sendEmail => '傳送電子郵件'; + + @override + String get resendEmail => '重新傳送電子郵件'; + + @override + String get passKeyPendingVerification => '仍需驗證'; + + @override + String get loginSessionExpired => '工作階段已過期'; + + @override + String get loginSessionExpiredDetails => '您的工作階段已過期。請重新登錄。'; + + @override + String get passkeyAuthTitle => '通行金鑰驗證'; + + @override + String get waitingForVerification => '等待驗證...'; + + @override + String get tryAgain => '請再試一次'; + + @override + String get checkStatus => '檢查狀態'; + + @override + String get loginWithTOTP => '使用 TOTP 登錄'; + + @override + String get recoverAccount => '恢復帳戶'; + + @override + String get setPasswordTitle => '設定密碼'; + + @override + String get changePasswordTitle => '修改密碼'; + + @override + String get resetPasswordTitle => '重設密碼'; + + @override + String get encryptionKeys => '加密金鑰'; + + @override + String get enterPasswordToEncrypt => '輸入我們可以用來加密您的資料的密碼'; + + @override + String get enterNewPasswordToEncrypt => '輸入我們可以用來加密您的資料的新密碼'; + + @override + String get passwordWarning => + '我們不會儲存這個密碼,所以如果忘記, 我們無法解密您的資料'; + + @override + String get howItWorks => '工作原理'; + + @override + String get generatingEncryptionKeys => '正在產生加密金鑰...'; + + @override + String get passwordChangedSuccessfully => '密碼修改成功'; + + @override + String get signOutFromOtherDevices => '從其他裝置登出'; + + @override + String get signOutOtherBody => '如果您認為有人可能知道您的密碼,您可以強制所有其他使用您帳戶的裝置登出。'; + + @override + String get signOutOtherDevices => '登出其他裝置'; + + @override + String get doNotSignOut => '不要登出'; + + @override + String get generatingEncryptionKeysTitle => '正在產生加密金鑰...'; + + @override + String get continueLabel => '繼續'; + + @override + String get insecureDevice => '裝置不安全'; + + @override + String get sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease => + '抱歉,我們無法在此裝置上產生安全金鑰。\n\n請使用其他裝置註冊。'; + + @override + String get recoveryKeyCopiedToClipboard => '復原密鑰已複製到剪貼簿'; + + @override + String get recoveryKey => '復原密鑰'; + + @override + String get recoveryKeyOnForgotPassword => '如果您忘記了密碼,恢復資料的唯一方法就是使用此金鑰。'; + + @override + String get recoveryKeySaveDescription => '我們不會儲存此金鑰,請將此24個單詞金鑰儲存在一個安全的地方。'; + + @override + String get doThisLater => '稍後再做'; + + @override + String get saveKey => '儲存金鑰'; + + @override + String get recoveryKeySaved => '復原密鑰已儲存在下載資料夾中!'; + + @override + String get noRecoveryKeyTitle => '沒有復原密鑰嗎?'; + + @override + String get twoFactorAuthTitle => '二步驟驗證'; + + @override + String get enterCodeHint => '從你的身份驗證器APP中\n輸入6位數字程式碼'; + + @override + String get lostDeviceTitle => '遺失了裝置嗎?'; + + @override + String get enterRecoveryKeyHint => '輸入您的復原密鑰'; + + @override + String get recover => '恢復'; + + @override + String get loggingOut => '正在登出...'; + + @override + String get immediately => '立即'; + + @override + String get appLock => 'APP鎖'; + + @override + String get autoLock => '自動鎖定'; + + @override + String get noSystemLockFound => '未找到系統鎖'; + + @override + String get deviceLockEnablePreSteps => '要啟用裝置鎖,請在系統設定中設定裝置密碼或螢幕鎖。'; + + @override + String get appLockDescription => '在裝置的預設鎖定螢幕和帶有 PIN 或密碼的自訂鎖定螢幕之間進行選擇。'; + + @override + String get deviceLock => '裝置鎖'; + + @override + String get pinLock => 'Pin 鎖定'; + + @override + String get autoLockFeatureDescription => 'APP進入後台後鎖定的時間'; + + @override + String get hideContent => '隱藏內容'; + + @override + String get hideContentDescriptionAndroid => '在APP切換器中隱藏APP內容並停用螢幕截圖'; + + @override + String get hideContentDescriptioniOS => '在APP切換器中隱藏APP內容'; + + @override + String get tooManyIncorrectAttempts => '錯誤的嘗試次數過多'; + + @override + String get tapToUnlock => '點選解鎖'; + + @override + String get areYouSureYouWantToLogout => '您確定要登出嗎?'; + + @override + String get yesLogout => '是的,登出'; + + @override + String get authToViewSecrets => '請進行身份驗證以查看您的金鑰'; + + @override + String get next => '下一步'; + + @override + String get setNewPassword => '設定新密碼'; + + @override + String get enterPin => '輸入 PIN 碼'; + + @override + String get setNewPin => '設定新 PIN 碼'; + + @override + String get confirm => '確認'; + + @override + String get reEnterPassword => '再次輸入密碼'; + + @override + String get reEnterPin => '再次輸入 PIN 碼'; + + @override + String get androidBiometricHint => '驗證身份'; + + @override + String get androidBiometricNotRecognized => '未能辨識,請重試。'; + + @override + String get androidBiometricSuccess => '成功'; + + @override + String get androidCancelButton => '取消'; + + @override + String get androidSignInTitle => '需要進行身份驗證'; + + @override + String get androidBiometricRequiredTitle => '需要進行生物辨識認證'; + + @override + String get androidDeviceCredentialsRequiredTitle => '需要裝置憑據'; + + @override + String get androidDeviceCredentialsSetupDescription => '需要裝置憑據'; + + @override + String get goToSettings => '前往設定'; + + @override + String get androidGoToSettingsDescription => + '您的裝置上未設定生物辨識身份驗證。轉到“設定 > 安全”以加入生物辨識身份驗證。'; + + @override + String get iOSLockOut => '生物辨識身份驗證已停用。請鎖定並解鎖螢幕以啟用該功能。'; + + @override + String get iOSOkButton => '好'; + + @override + String get emailAlreadyRegistered => '電子郵件地址已被註冊。'; + + @override + String get emailNotRegistered => '電子郵件地址未註冊。'; + + @override + String get thisEmailIsAlreadyInUse => '該電子郵件已被使用'; + + @override + String emailChangedTo(String newEmail) { + return '電子郵件已更改為 $newEmail'; + } + + @override + String get authenticationFailedPleaseTryAgain => '認證失敗,請重試'; + + @override + String get authenticationSuccessful => '認證成功!'; + + @override + String get sessionExpired => '工作階段已過期'; + + @override + String get incorrectRecoveryKey => '復原密鑰不正確'; + + @override + String get theRecoveryKeyYouEnteredIsIncorrect => '您輸入的復原密鑰不正確'; + + @override + String get twofactorAuthenticationSuccessfullyReset => '二步驟驗證已成功重設'; + + @override + String get yourVerificationCodeHasExpired => '您的驗證碼已過期'; + + @override + String get incorrectCode => '驗證碼錯誤'; + + @override + String get sorryTheCodeYouveEnteredIsIncorrect => '抱歉,您輸入的驗證碼不正確'; + + @override + String get developerSettings => '開發者設定'; + + @override + String get serverEndpoint => '伺服器端點'; + + @override + String get invalidEndpoint => '端點無效'; + + @override + String get invalidEndpointMessage => '抱歉,您輸入的端點無效。請輸入有效的端點,然後重試。'; + + @override + String get endpointUpdatedMessage => '端點更新成功'; +} diff --git a/mobile/packages/strings/pubspec.lock b/mobile/packages/strings/pubspec.lock new file mode 100644 index 0000000000..4506ee5e4d --- /dev/null +++ b/mobile/packages/strings/pubspec.lock @@ -0,0 +1,218 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/mobile/packages/strings/pubspec.yaml b/mobile/packages/strings/pubspec.yaml new file mode 100644 index 0000000000..975215c2f8 --- /dev/null +++ b/mobile/packages/strings/pubspec.yaml @@ -0,0 +1,22 @@ +name: ente_strings +description: A Flutter package containing shared localization strings for ente apps +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + intl: any + +dev_dependencies: + flutter_lints: ^5.0.0 + flutter_test: + sdk: flutter + +flutter: + generate: true diff --git a/mobile/packages/ui/analysis_options.yaml b/mobile/packages/ui/analysis_options.yaml new file mode 100644 index 0000000000..1bd78bc1b0 --- /dev/null +++ b/mobile/packages/ui/analysis_options.yaml @@ -0,0 +1,72 @@ +# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html +# or https://pub.dev/packages/lint (Effective dart) +# use "flutter analyze ." or "dart analyze ." for running lint checks + +include: package:flutter_lints/flutter.yaml +linter: + rules: + # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml + # Ref https://dart-lang.github.io/linter/lints/ + - avoid_print + - avoid_unnecessary_containers + - avoid_web_libraries_in_flutter + - no_logic_in_create_state + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_final_locals + - require_trailing_commas + - sized_box_for_whitespace + - use_full_hex_values_for_flutter_colors + - use_key_in_widget_constructors + - cancel_subscriptions + + + - avoid_empty_else + - exhaustive_cases + + # just style suggestions + - sort_pub_dependencies + - use_rethrow_when_possible + - prefer_double_quotes + - directives_ordering + - always_use_package_imports + - sort_child_properties_last + - unawaited_futures + +analyzer: + errors: + avoid_empty_else: error + exhaustive_cases: error + curly_braces_in_flow_control_structures: error + directives_ordering: error + require_trailing_commas: error + always_use_package_imports: warning + prefer_final_fields: error + unused_import: error + camel_case_types: error + prefer_is_empty: warning + use_rethrow_when_possible: info + unused_field: warning + use_key_in_widget_constructors: warning + sort_child_properties_last: warning + sort_pub_dependencies: warning + library_private_types_in_public_api: warning + constant_identifier_names: ignore + prefer_const_constructors: warning + prefer_const_declarations: warning + prefer_const_constructors_in_immutables: warning + prefer_final_locals: warning + unnecessary_const: error + cancel_subscriptions: error + unrelated_type_equality_checks: error + unnecessary_cast: info + + + unawaited_futures: warning # convert to warning after fixing existing issues + invalid_dependency: info + use_build_context_synchronously: ignore # experimental lint, requires many changes + prefer_interpolation_to_compose_strings: ignore # later too many warnings + prefer_double_quotes: ignore # too many warnings + avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides diff --git a/mobile/packages/ui/devtools_options.yaml b/mobile/packages/ui/devtools_options.yaml new file mode 100644 index 0000000000..fa0b357c4f --- /dev/null +++ b/mobile/packages/ui/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/mobile/packages/ui/lib/components/action_sheet_widget.dart b/mobile/packages/ui/lib/components/action_sheet_widget.dart new file mode 100644 index 0000000000..fbb34337a5 --- /dev/null +++ b/mobile/packages/ui/lib/components/action_sheet_widget.dart @@ -0,0 +1,221 @@ +import 'dart:ui'; + +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_result.dart'; +import 'package:ente_ui/components/components_constants.dart'; +import 'package:ente_ui/components/separators.dart'; +import 'package:ente_ui/theme/colors.dart'; +import 'package:ente_ui/theme/effects.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; + +enum ActionSheetType { + defaultActionSheet, + iconOnly, +} + +///Returns null if dismissed +Future showActionSheet({ + required BuildContext context, + required List buttons, + ActionSheetType actionSheetType = ActionSheetType.defaultActionSheet, + bool enableDrag = true, + bool isDismissible = true, + bool isCheckIconGreen = false, + String? title, + Widget? bodyWidget, + String? body, + String? bodyHighlight, +}) { + return showMaterialModalBottomSheet( + backgroundColor: Colors.transparent, + barrierColor: backdropFaintDark, + useRootNavigator: true, + context: context, + isDismissible: isDismissible, + enableDrag: enableDrag, + builder: (_) { + return ActionSheetWidget( + title: title, + bodyWidget: bodyWidget, + body: body, + bodyHighlight: bodyHighlight, + actionButtons: buttons, + actionSheetType: actionSheetType, + isCheckIconGreen: isCheckIconGreen, + ); + }, + ); +} + +class ActionSheetWidget extends StatelessWidget { + final String? title; + final Widget? bodyWidget; + final String? body; + final String? bodyHighlight; + final List actionButtons; + final ActionSheetType actionSheetType; + final bool isCheckIconGreen; + + const ActionSheetWidget({ + required this.actionButtons, + required this.actionSheetType, + required this.isCheckIconGreen, + this.title, + this.bodyWidget, + this.body, + this.bodyHighlight, + super.key, + }); + + @override + Widget build(BuildContext context) { + final isTitleAndBodyNull = + title == null && bodyWidget == null && body == null; + final blur = MediaQuery.of(context).platformBrightness == Brightness.light + ? blurMuted + : blurBase; + final extraWidth = MediaQuery.of(context).size.width - restrictedMaxWidth; + final double? horizontalPadding = extraWidth > 0 ? extraWidth / 2 : null; + return Padding( + padding: EdgeInsets.fromLTRB( + horizontalPadding ?? 12, + 12, + horizontalPadding ?? 12, + 32, + ), + child: Container( + decoration: BoxDecoration(boxShadow: shadowMenuLight), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), + child: Container( + color: backdropMutedDark, + child: Padding( + padding: EdgeInsets.fromLTRB( + 24, + 24, + 24, + isTitleAndBodyNull ? 24 : 28, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + isTitleAndBodyNull + ? const SizedBox.shrink() + : Padding( + padding: const EdgeInsets.only(bottom: 36), + child: ContentContainerWidget( + title: title, + bodyWidget: bodyWidget, + body: body, + bodyHighlight: bodyHighlight, + actionSheetType: actionSheetType, + isCheckIconGreen: isCheckIconGreen, + ), + ), + ActionButtons( + actionButtons, + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} + +class ContentContainerWidget extends StatelessWidget { + final String? title; + final Widget? bodyWidget; + final String? body; + final String? bodyHighlight; + final ActionSheetType actionSheetType; + final bool isCheckIconGreen; + + const ContentContainerWidget({ + required this.actionSheetType, + required this.isCheckIconGreen, + this.title, + this.bodyWidget, + this.body, + this.bodyHighlight, + super.key, + }); + + @override + Widget build(BuildContext context) { + final textTheme = getEnteTextTheme(context); + final bool bodyMissing = body == null && bodyWidget == null; + debugPrint("body missing $bodyMissing"); + return Column( + mainAxisSize: MainAxisSize.min, + //todo: set cross axis to center when icon should be shown in place of body + crossAxisAlignment: actionSheetType == ActionSheetType.defaultActionSheet + ? CrossAxisAlignment.stretch + : CrossAxisAlignment.center, + children: [ + title == null + ? const SizedBox.shrink() + : Text( + title!, + style: textTheme.largeBold + .copyWith(color: textBaseDark), //constant color + ), + title == null || bodyMissing + ? const SizedBox.shrink() + : const SizedBox(height: 19), + actionSheetType == ActionSheetType.defaultActionSheet + ? bodyMissing + ? const SizedBox.shrink() + : (bodyWidget != null + ? bodyWidget! + : Text( + body!, + style: textTheme.body + .copyWith(color: textMutedDark), //constant color + )) + : Icon( + Icons.check_outlined, + size: 48, + color: isCheckIconGreen + ? getEnteColorScheme(context).primary700 + : strokeBaseDark, + ), + actionSheetType == ActionSheetType.defaultActionSheet && + bodyHighlight != null + ? Padding( + padding: const EdgeInsets.only(top: 19.0), + child: Text( + bodyHighlight!, + style: textTheme.body + .copyWith(color: textBaseDark), //constant color + ), + ) + : const SizedBox.shrink(), + ], + ); + } +} + +class ActionButtons extends StatelessWidget { + final List actionButtons; + const ActionButtons(this.actionButtons, {super.key}); + + @override + Widget build(BuildContext context) { + final actionButtonsWithSeparators = actionButtons; + return Column( + children: + //Separator height is 8pts in figma. -2pts here as the action + //buttons are 2pts extra in height in code compared to figma because + //of the border(1pt top + 1pt bottom) of action buttons. + addSeparators(actionButtonsWithSeparators, const SizedBox(height: 6)), + ); + } +} diff --git a/mobile/packages/ui/lib/components/buttons/button_widget.dart b/mobile/packages/ui/lib/components/buttons/button_widget.dart new file mode 100644 index 0000000000..ea4255981c --- /dev/null +++ b/mobile/packages/ui/lib/components/buttons/button_widget.dart @@ -0,0 +1,529 @@ +import 'package:ente_base/typedefs.dart'; +import 'package:ente_ui/components/buttons/models/button_result.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/components/buttons/models/custom_button_style.dart'; +import 'package:ente_ui/components/loading_widget.dart'; +import 'package:ente_ui/models/execution_states.dart'; +import 'package:ente_ui/theme/colors.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_ui/theme/text_style.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_utils/debouncer.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; + +enum ButtonSize { small, large } + +enum ButtonAction { first, second, third, fourth, cancel, error } + +class ButtonWidget extends StatelessWidget { + final IconData? icon; + final String? labelText; + final ButtonType buttonType; + final FutureVoidCallback? onTap; + final bool isDisabled; + final ButtonSize buttonSize; + + ///Setting this flag to true will show a success confirmation as a 'check' + ///icon once the onTap(). This is expected to be used only if time taken to + ///execute onTap() takes less than debouce time. + final bool shouldShowSuccessConfirmation; + + ///Setting this flag to false will restrict the loading and success states of + ///the button from surfacing on the UI. The ExecutionState of the button will + ///change irrespective of the value of this flag. Only that it won't be + ///surfaced on the UI + final bool shouldSurfaceExecutionStates; + + /// iconColor should only be specified when we do not want to honor the default + /// iconColor based on buttonType. Most of the items, default iconColor is what + /// we need unless we want to pop out the icon in a non-primary button type + final Color? iconColor; + + ///Button action will only work if isInAlert is true + final ButtonAction? buttonAction; + + ///setting this flag to true will make the button appear like how it would + ///on dark theme irrespective of the app's theme. + final bool shouldStickToDarkTheme; + + ///isInAlert is to dismiss the alert if the action on the button is completed. + ///This should be set to true if the alert which uses this button needs to + ///return the Button's action. + final bool isInAlert; + + /// progressStatus can be used to display information about the action + /// progress when ExecutionState is in Progress. + final ValueNotifier? progressStatus; + + const ButtonWidget({ + super.key, + required this.buttonType, + this.buttonSize = ButtonSize.large, + this.icon, + this.labelText, + this.onTap, + this.shouldStickToDarkTheme = false, + this.isDisabled = false, + this.buttonAction, + this.isInAlert = false, + this.iconColor, + this.shouldSurfaceExecutionStates = true, + this.progressStatus, + this.shouldShowSuccessConfirmation = false, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = + shouldStickToDarkTheme ? darkScheme : getEnteColorScheme(context); + final inverseColorScheme = shouldStickToDarkTheme + ? lightScheme + : getEnteColorScheme(context, inverse: true); + final textTheme = + shouldStickToDarkTheme ? darkTextTheme : getEnteTextTheme(context); + final inverseTextTheme = shouldStickToDarkTheme + ? lightTextTheme + : getEnteTextTheme(context, inverse: true); + final buttonStyle = CustomButtonStyle( + //Dummy default values since we need to keep these properties non-nullable + defaultButtonColor: Colors.transparent, + defaultBorderColor: Colors.transparent, + defaultIconColor: Colors.transparent, + defaultLabelStyle: textTheme.body, + ); + buttonStyle.defaultButtonColor = buttonType.defaultButtonColor(colorScheme); + buttonStyle.pressedButtonColor = buttonType.pressedButtonColor(colorScheme); + buttonStyle.disabledButtonColor = + buttonType.disabledButtonColor(colorScheme, buttonSize); + buttonStyle.defaultBorderColor = + buttonType.defaultBorderColor(colorScheme, buttonSize); + buttonStyle.pressedBorderColor = buttonType.pressedBorderColor( + colorScheme: colorScheme, + buttonSize: buttonSize, + ); + buttonStyle.disabledBorderColor = + buttonType.disabledBorderColor(colorScheme, buttonSize); + buttonStyle.defaultIconColor = iconColor ?? + buttonType.defaultIconColor( + colorScheme: colorScheme, + inverseColorScheme: inverseColorScheme, + ); + buttonStyle.pressedIconColor = + buttonType.pressedIconColor(colorScheme, buttonSize); + buttonStyle.disabledIconColor = + buttonType.disabledIconColor(colorScheme, buttonSize); + buttonStyle.defaultLabelStyle = buttonType.defaultLabelStyle( + textTheme: textTheme, + inverseTextTheme: inverseTextTheme, + ); + buttonStyle.pressedLabelStyle = + buttonType.pressedLabelStyle(textTheme, colorScheme, buttonSize); + buttonStyle.disabledLabelStyle = + buttonType.disabledLabelStyle(textTheme, colorScheme); + buttonStyle.checkIconColor = buttonType.checkIconColor(colorScheme); + + return ButtonChildWidget( + buttonStyle: buttonStyle, + buttonType: buttonType, + isDisabled: isDisabled, + buttonSize: buttonSize, + isInAlert: isInAlert, + onTap: onTap, + labelText: labelText, + icon: icon, + buttonAction: buttonAction, + shouldSurfaceExecutionStates: shouldSurfaceExecutionStates, + progressStatus: progressStatus, + shouldShowSuccessConfirmation: shouldShowSuccessConfirmation, + ); + } +} + +class ButtonChildWidget extends StatefulWidget { + final CustomButtonStyle buttonStyle; + final FutureVoidCallback? onTap; + final ButtonType buttonType; + final String? labelText; + final IconData? icon; + final bool isDisabled; + final ButtonSize buttonSize; + final ButtonAction? buttonAction; + final bool isInAlert; + final bool shouldSurfaceExecutionStates; + final ValueNotifier? progressStatus; + final bool shouldShowSuccessConfirmation; + + const ButtonChildWidget({ + super.key, + required this.buttonStyle, + required this.buttonType, + required this.isDisabled, + required this.buttonSize, + required this.isInAlert, + required this.shouldSurfaceExecutionStates, + required this.shouldShowSuccessConfirmation, + this.progressStatus, + this.onTap, + this.labelText, + this.icon, + this.buttonAction, + }); + + @override + State createState() => _ButtonChildWidgetState(); +} + +class _ButtonChildWidgetState extends State { + late Color buttonColor; + late Color borderColor; + late Color iconColor; + late TextStyle labelStyle; + late Color checkIconColor; + late Color loadingIconColor; + ValueNotifier? progressStatus; + + ///This is used to store the width of the button in idle state (small button) + ///to be used as width for the button when the loading/succes states comes. + double? widthOfButton; + final _debouncer = Debouncer(const Duration(milliseconds: 300)); + ExecutionState executionState = ExecutionState.idle; + Exception? _exception; + + @override + void initState() { + _setButtonTheme(); + super.initState(); + } + + @override + void didUpdateWidget(covariant ButtonChildWidget oldWidget) { + _setButtonTheme(); + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + if (executionState == ExecutionState.successful) { + Future.delayed(Duration(seconds: widget.isInAlert ? 1 : 2), () { + setState(() { + executionState = ExecutionState.idle; + }); + }); + } + return GestureDetector( + onTap: _shouldRegisterGestures ? _onTap : null, + onTapDown: _shouldRegisterGestures ? _onTapDown : null, + onTapUp: _shouldRegisterGestures ? _onTapUp : null, + onTapCancel: _shouldRegisterGestures ? _onTapCancel : null, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(4)), + border: widget.buttonType == ButtonType.tertiaryCritical + ? Border.all(color: borderColor) + : null, + ), + child: AnimatedContainer( + duration: const Duration(milliseconds: 16), + width: widget.buttonSize == ButtonSize.large ? double.infinity : null, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(4)), + color: buttonColor, + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 175), + switchInCurve: Curves.easeInOutExpo, + switchOutCurve: Curves.easeInOutExpo, + child: executionState == ExecutionState.idle || + !widget.shouldSurfaceExecutionStates + ? widget.buttonType.hasTrailingIcon + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + widget.labelText == null + ? const SizedBox.shrink() + : Flexible( + child: Padding( + padding: widget.icon == null + ? const EdgeInsets.symmetric( + horizontal: 8, + ) + : const EdgeInsets.only(right: 16), + child: Text( + widget.labelText!, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: labelStyle, + ), + ), + ), + widget.icon == null + ? const SizedBox.shrink() + : Icon( + widget.icon, + size: 20, + color: iconColor, + ), + ], + ) + : Builder( + builder: (context) { + SchedulerBinding.instance.addPostFrameCallback( + (timeStamp) { + final box = + context.findRenderObject() as RenderBox; + widthOfButton = box.size.width; + }, + ); + return Row( + mainAxisSize: + widget.buttonSize == ButtonSize.large + ? MainAxisSize.max + : MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + widget.icon == null + ? const SizedBox.shrink() + : Icon( + widget.icon, + size: 20, + color: iconColor, + ), + widget.icon == null || widget.labelText == null + ? const SizedBox.shrink() + : const SizedBox(width: 8), + widget.labelText == null + ? const SizedBox.shrink() + : Flexible( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + ), + child: Text( + widget.labelText!, + style: labelStyle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ); + }, + ) + : executionState == ExecutionState.inProgress + ? SizedBox( + width: widthOfButton, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + progressStatus == null + ? const SizedBox.shrink() + : ValueListenableBuilder( + valueListenable: progressStatus!, + builder: ( + BuildContext context, + String value, + Widget? child, + ) { + return Padding( + padding: + const EdgeInsets.only(right: 8.0), + child: Text( + value, + style: lightTextTheme.smallBold, + ), + ); + }, + ), + EnteLoadingWidget( + padding: 3, + color: loadingIconColor, + ), + ], + ), + ) + : executionState == ExecutionState.successful + ? SizedBox( + width: widthOfButton, + child: Icon( + Icons.check_outlined, + size: 20, + color: checkIconColor, + ), + ) + : const SizedBox.shrink(), //fallback + ), + ), + ), + ), + ); + } + + void _setButtonTheme() { + progressStatus = widget.progressStatus; + checkIconColor = widget.buttonStyle.checkIconColor ?? + widget.buttonStyle.defaultIconColor; + loadingIconColor = widget.buttonStyle.defaultIconColor; + if (widget.isDisabled) { + buttonColor = widget.buttonStyle.disabledButtonColor ?? + widget.buttonStyle.defaultButtonColor; + borderColor = widget.buttonStyle.disabledBorderColor ?? + widget.buttonStyle.defaultBorderColor; + iconColor = widget.buttonStyle.disabledIconColor ?? + widget.buttonStyle.defaultIconColor; + labelStyle = widget.buttonStyle.disabledLabelStyle ?? + widget.buttonStyle.defaultLabelStyle; + } else { + buttonColor = widget.buttonStyle.defaultButtonColor; + borderColor = widget.buttonStyle.defaultBorderColor; + iconColor = widget.buttonStyle.defaultIconColor; + labelStyle = widget.buttonStyle.defaultLabelStyle; + } + } + + bool get _shouldRegisterGestures => + !widget.isDisabled && executionState == ExecutionState.idle; + + void _onTap() async { + if (widget.onTap != null) { + _debouncer.run( + () => Future(() { + setState(() { + executionState = ExecutionState.inProgress; + }); + }), + ); + await widget.onTap!.call().then( + (value) { + _exception = null; + }, + onError: (error, stackTrace) { + executionState = ExecutionState.error; + _exception = error as Exception; + _debouncer.cancelDebounce(); + }, + ); + widget.shouldShowSuccessConfirmation && _debouncer.isActive() + ? executionState = ExecutionState.successful + : null; + _debouncer.cancelDebounce(); + if (executionState == ExecutionState.successful) { + setState(() {}); + } + + // when the time taken by widget.onTap is approximately equal to the debounce + // time, the callback is getting executed when/after the if condition + // below is executing/executed which results in execution state stuck at + // idle state. This Future is for delaying the execution of the if + // condition so that the calback in the debouncer finishes execution before. + await Future.delayed(const Duration(milliseconds: 5)); + } + if (executionState == ExecutionState.inProgress || + executionState == ExecutionState.error) { + if (executionState == ExecutionState.inProgress) { + if (mounted) { + setState(() { + executionState = ExecutionState.successful; + Future.delayed( + Duration( + seconds: widget.shouldSurfaceExecutionStates + ? (widget.isInAlert ? 1 : 2) + : 0, + ), () { + widget.isInAlert + ? _popWithButtonAction( + context, + buttonAction: widget.buttonAction, + ) + : null; + if (mounted) { + setState(() { + executionState = ExecutionState.idle; + }); + } + }); + }); + } + } + if (executionState == ExecutionState.error) { + setState(() { + executionState = ExecutionState.idle; + widget.isInAlert + ? Future.delayed( + const Duration(seconds: 0), + () => _popWithButtonAction( + context, + buttonAction: ButtonAction.error, + exception: _exception, + ), + ) + : null; + }); + } + } else { + if (widget.isInAlert) { + Future.delayed( + Duration(seconds: widget.shouldShowSuccessConfirmation ? 1 : 0), + () => + _popWithButtonAction(context, buttonAction: widget.buttonAction), + ); + } + } + } + + void _popWithButtonAction( + BuildContext context, { + required ButtonAction? buttonAction, + Exception? exception, + }) { + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(ButtonResult(widget.buttonAction, exception)); + } else if (exception != null) { + //This is to show the execution was unsuccessful if the dialog is manually + //closed before the execution completes. + showGenericErrorDialog( + context: context, + error: exception, + ); + } + } + + void _onTapDown(details) { + setState(() { + buttonColor = widget.buttonStyle.pressedButtonColor ?? + widget.buttonStyle.defaultButtonColor; + borderColor = widget.buttonStyle.pressedBorderColor ?? + widget.buttonStyle.defaultBorderColor; + iconColor = widget.buttonStyle.pressedIconColor ?? + widget.buttonStyle.defaultIconColor; + labelStyle = widget.buttonStyle.pressedLabelStyle ?? + widget.buttonStyle.defaultLabelStyle; + }); + } + + void _onTapUp(details) { + Future.delayed( + const Duration(milliseconds: 84), + () => setState(() { + setAllStylesToDefault(); + }), + ); + } + + void _onTapCancel() { + setState(() { + setAllStylesToDefault(); + }); + } + + void setAllStylesToDefault() { + buttonColor = widget.buttonStyle.defaultButtonColor; + borderColor = widget.buttonStyle.defaultBorderColor; + iconColor = widget.buttonStyle.defaultIconColor; + labelStyle = widget.buttonStyle.defaultLabelStyle; + } +} diff --git a/mobile/packages/ui/lib/components/buttons/dynamic_fab.dart b/mobile/packages/ui/lib/components/buttons/dynamic_fab.dart new file mode 100644 index 0000000000..a6e9be9990 --- /dev/null +++ b/mobile/packages/ui/lib/components/buttons/dynamic_fab.dart @@ -0,0 +1,90 @@ +import 'dart:math' as math; + +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_ui/theme/ente_theme_data.dart'; +import 'package:flutter/material.dart'; + +class DynamicFAB extends StatelessWidget { + final bool? isKeypadOpen; + final bool? isFormValid; + final String? buttonText; + final Function? onPressedFunction; + + const DynamicFAB({ + super.key, + this.isKeypadOpen, + this.buttonText, + this.isFormValid, + this.onPressedFunction, + }); + + @override + Widget build(BuildContext context) { + if (isKeypadOpen!) { + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.surface, + spreadRadius: 200, + blurRadius: 100, + offset: const Offset(0, 230), + ), + ], + ), + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FloatingActionButton( + heroTag: 'FAB', + backgroundColor: + Theme.of(context).colorScheme.dynamicFABBackgroundColor, + foregroundColor: getEnteColorScheme(context).dynamicFABTextColor, + onPressed: isFormValid! + ? onPressedFunction as void Function()? + : () { + FocusScope.of(context).unfocus(); + }, + child: Transform.rotate( + angle: isFormValid! ? 0 : math.pi / 2, + child: const Icon( + Icons.chevron_right, + size: 36, + ), + ), //keypad down here + ), + ], + ), + ); + } else { + return Container( + width: double.infinity, + height: 56, + padding: const EdgeInsets.symmetric(horizontal: 20), + child: OutlinedButton( + onPressed: + isFormValid! ? onPressedFunction as void Function()? : null, + child: Text(buttonText!), + ), + ); + } + } +} + +class NoScalingAnimation extends FloatingActionButtonAnimator { + @override + Offset getOffset({Offset? begin, required Offset end, double? progress}) { + return end; + } + + @override + Animation getRotationAnimation({required Animation parent}) { + return Tween(begin: 1.0, end: 1.0).animate(parent); + } + + @override + Animation getScaleAnimation({required Animation parent}) { + return Tween(begin: 1.0, end: 1.0).animate(parent); + } +} diff --git a/mobile/packages/ui/lib/components/buttons/gradient_button.dart b/mobile/packages/ui/lib/components/buttons/gradient_button.dart new file mode 100644 index 0000000000..f5b6d9eeb9 --- /dev/null +++ b/mobile/packages/ui/lib/components/buttons/gradient_button.dart @@ -0,0 +1,83 @@ +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +class GradientButton extends StatelessWidget { + final Function? onTap; + + // text is ignored if child is specified + final String text; + + // nullable + final IconData? iconData; + + // padding between the text and icon + final double paddingValue; + + const GradientButton({ + super.key, + this.onTap, + this.text = '', + this.iconData, + this.paddingValue = 0.0, + }); + + @override + Widget build(BuildContext context) { + Widget buttonContent; + if (iconData == null) { + buttonContent = Text( + text, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontFamily: 'Inter-SemiBold', + fontSize: 18, + ), + ); + } else { + buttonContent = Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + iconData, + size: 20, + color: Colors.white, + ), + const Padding(padding: EdgeInsets.symmetric(horizontal: 6)), + Text( + text, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontFamily: 'Inter-SemiBold', + fontSize: 18, + ), + ), + ], + ); + } + return InkWell( + onTap: onTap as void Function()?, + child: ClipRRect( + borderRadius: BorderRadius.circular(4), + child: Container( + height: 56, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: const Alignment(0.1, -0.9), + end: const Alignment(-0.6, 0.9), + colors: onTap != null + ? getEnteColorScheme(context).gradientButtonBgColors + : [ + getEnteColorScheme(context).fillMuted, + getEnteColorScheme(context).fillMuted, + ], + ), + ), + child: Center(child: buttonContent), + ), + ), + ); + } +} diff --git a/mobile/packages/ui/lib/components/buttons/icon_button_widget.dart b/mobile/packages/ui/lib/components/buttons/icon_button_widget.dart new file mode 100644 index 0000000000..922e69e7f0 --- /dev/null +++ b/mobile/packages/ui/lib/components/buttons/icon_button_widget.dart @@ -0,0 +1,109 @@ +import 'package:ente_ui/theme/colors.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +enum IconButtonType { + primary, + secondary, + rounded, +} + +class IconButtonWidget extends StatefulWidget { + final IconButtonType iconButtonType; + final IconData icon; + final bool disableGestureDetector; + final VoidCallback? onTap; + final Color? defaultColor; + final Color? pressedColor; + final Color? iconColor; + const IconButtonWidget({ + super.key, + required this.icon, + required this.iconButtonType, + this.disableGestureDetector = false, + this.onTap, + this.defaultColor, + this.pressedColor, + this.iconColor, + }); + + @override + State createState() => _IconButtonWidgetState(); +} + +class _IconButtonWidgetState extends State { + Color? iconStateColor; + @override + void didChangeDependencies() { + setState(() { + iconStateColor = null; + }); + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + final bool hasPressedState = widget.onTap != null; + final colorTheme = getEnteColorScheme(context); + iconStateColor ?? + (iconStateColor = widget.defaultColor ?? + (widget.iconButtonType == IconButtonType.rounded + ? colorTheme.fillFaint + : null)); + return widget.disableGestureDetector + ? _iconButton(colorTheme) + : GestureDetector( + onTapDown: hasPressedState ? _onTapDown : null, + onTapUp: hasPressedState ? _onTapUp : null, + onTapCancel: hasPressedState ? _onTapCancel : null, + onTap: widget.onTap, + child: _iconButton(colorTheme), + ); + } + + Widget _iconButton(EnteColorScheme colorTheme) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: AnimatedContainer( + duration: const Duration(milliseconds: 20), + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: iconStateColor, + ), + child: Icon( + widget.icon, + color: widget.iconColor ?? + (widget.iconButtonType == IconButtonType.secondary + ? colorTheme.strokeMuted + : colorTheme.strokeBase), + size: 24, + ), + ), + ); + } + + _onTapDown(details) { + final colorTheme = getEnteColorScheme(context); + setState(() { + iconStateColor = widget.pressedColor ?? + (widget.iconButtonType == IconButtonType.rounded + ? colorTheme.fillMuted + : colorTheme.fillFaint); + }); + } + + _onTapUp(details) { + Future.delayed(const Duration(milliseconds: 100), () { + setState(() { + iconStateColor = null; + }); + }); + } + + _onTapCancel() { + setState(() { + iconStateColor = null; + }); + } +} diff --git a/mobile/packages/ui/lib/components/buttons/models/button_result.dart b/mobile/packages/ui/lib/components/buttons/models/button_result.dart new file mode 100644 index 0000000000..7560768dc1 --- /dev/null +++ b/mobile/packages/ui/lib/components/buttons/models/button_result.dart @@ -0,0 +1,11 @@ +import 'package:ente_ui/components/buttons/button_widget.dart'; + +class ButtonResult { + ///action can be null when action for the button that is returned when popping + ///the widget (dialog, actionSheet) which uses a ButtonWidget isn't + ///relevant/useful and so is not assigned a value when an instance of + ///ButtonWidget is created. + final ButtonAction? action; + final Exception? exception; + ButtonResult([this.action, this.exception]); +} diff --git a/mobile/packages/ui/lib/components/buttons/models/button_type.dart b/mobile/packages/ui/lib/components/buttons/models/button_type.dart new file mode 100644 index 0000000000..a6cb8d95eb --- /dev/null +++ b/mobile/packages/ui/lib/components/buttons/models/button_type.dart @@ -0,0 +1,205 @@ +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/theme/colors.dart'; +import 'package:ente_ui/theme/text_style.dart'; +import 'package:flutter/material.dart'; + +enum ButtonType { + primary, + secondary, + neutral, + trailingIcon, + critical, + tertiaryCritical, + trailingIconPrimary, + trailingIconSecondary, + tertiary; + + bool get isPrimary => + this == ButtonType.primary || this == ButtonType.trailingIconPrimary; + + bool get hasTrailingIcon => + this == ButtonType.trailingIcon || + this == ButtonType.trailingIconPrimary || + this == ButtonType.trailingIconSecondary; + + bool get isSecondary => + this == ButtonType.secondary || this == ButtonType.trailingIconSecondary; + + bool get isCritical => + this == ButtonType.critical || this == ButtonType.tertiaryCritical; + + bool get isNeutral => + this == ButtonType.neutral || this == ButtonType.trailingIcon; + + Color defaultButtonColor(EnteColorScheme colorScheme) { + if (isPrimary) { + return colorScheme.primary400; + } + if (isSecondary) { + return colorScheme.fillFaint; + } + if (this == ButtonType.neutral || this == ButtonType.trailingIcon) { + return colorScheme.fillBase; + } + if (this == ButtonType.critical) { + return colorScheme.warning700; + } + if (this == ButtonType.tertiaryCritical) { + return Colors.transparent; + } + return Colors.transparent; + } + + //Returning null to fallback to default color + Color? pressedButtonColor(EnteColorScheme colorScheme) { + if (isPrimary) { + return colorScheme.primary700; + } + if (isSecondary) { + return colorScheme.fillFaintPressed; + } + if (isNeutral) { + return colorScheme.fillBasePressed; + } + if (this == ButtonType.critical) { + return colorScheme.warning800; + } + return null; + } + + //Returning null to fallback to default color + Color? disabledButtonColor( + EnteColorScheme colorScheme, + ButtonSize buttonSize, + ) { + if (buttonSize == ButtonSize.small && + (this == ButtonType.primary || + this == ButtonType.neutral || + this == ButtonType.critical)) { + return colorScheme.fillMuted; + } + if (isPrimary || this == ButtonType.critical || isNeutral) { + return colorScheme.fillFaint; + } + return null; + } + + Color defaultBorderColor(EnteColorScheme colorScheme, ButtonSize buttonSize) { + if (this == ButtonType.tertiaryCritical && buttonSize == ButtonSize.large) { + return colorScheme.warning700; + } + return Colors.transparent; + } + + //Returning null to fallback to default color + Color? pressedBorderColor({ + required EnteColorScheme colorScheme, + required ButtonSize buttonSize, + }) { + if (this == ButtonType.tertiaryCritical && buttonSize == ButtonSize.large) { + return colorScheme.warning700; + } + return null; + } + + //Returning null to fallback to default color + Color? disabledBorderColor( + EnteColorScheme colorScheme, + ButtonSize buttonSize, + ) { + if (this == ButtonType.tertiaryCritical && buttonSize == ButtonSize.large) { + return colorScheme.strokeMuted; + } + return null; + } + + Color defaultIconColor({ + required EnteColorScheme colorScheme, + required EnteColorScheme inverseColorScheme, + }) { + if (isPrimary || this == ButtonType.critical) { + return strokeBaseDark; + } + if (this == ButtonType.neutral || this == ButtonType.trailingIcon) { + return inverseColorScheme.strokeBase; + } + if (this == ButtonType.tertiaryCritical) { + return colorScheme.warning500; + } + //fallback + return colorScheme.strokeBase; + } + + //Returning null to fallback to default color + Color? pressedIconColor(EnteColorScheme colorScheme, ButtonSize buttonSize) { + if (this == ButtonType.tertiaryCritical) { + return colorScheme.warning700; + } + if (this == ButtonType.tertiary && buttonSize == ButtonSize.small) { + return colorScheme.fillBasePressed; + } + return null; + } + + //Returning null to fallback to default color + Color? disabledIconColor(EnteColorScheme colorScheme, ButtonSize buttonSize) { + if (isPrimary || + isSecondary || + isNeutral || + buttonSize == ButtonSize.small) { + return colorScheme.strokeMuted; + } + if (isCritical) { + return colorScheme.strokeFaint; + } + return null; + } + + TextStyle defaultLabelStyle({ + required EnteTextTheme textTheme, + required EnteTextTheme inverseTextTheme, + }) { + if (isPrimary || this == ButtonType.critical) { + return textTheme.bodyBold.copyWith(color: textBaseDark); + } + if (this == ButtonType.neutral || this == ButtonType.trailingIcon) { + return inverseTextTheme.bodyBold; + } + if (this == ButtonType.tertiaryCritical) { + return textTheme.bodyBold.copyWith(color: warning500); + } + //fallback + return textTheme.bodyBold; + } + + //Returning null to fallback to default color + TextStyle? pressedLabelStyle( + EnteTextTheme textTheme, + EnteColorScheme colorScheme, + ButtonSize buttonSize, + ) { + if (this == ButtonType.tertiaryCritical) { + return textTheme.bodyBold.copyWith(color: colorScheme.warning700); + } + if (this == ButtonType.tertiary && buttonSize == ButtonSize.small) { + return textTheme.bodyBold.copyWith(color: colorScheme.fillBasePressed); + } + return null; + } + + //Returning null to fallback to default color + TextStyle? disabledLabelStyle( + EnteTextTheme textTheme, + EnteColorScheme colorScheme, + ) { + return textTheme.bodyBold.copyWith(color: colorScheme.textFaint); + } + + //Returning null to fallback to default color + Color? checkIconColor(EnteColorScheme colorScheme) { + if (isSecondary) { + return colorScheme.primary500; + } + return null; + } +} diff --git a/mobile/packages/ui/lib/components/buttons/models/custom_button_style.dart b/mobile/packages/ui/lib/components/buttons/models/custom_button_style.dart new file mode 100644 index 0000000000..e30504ab3b --- /dev/null +++ b/mobile/packages/ui/lib/components/buttons/models/custom_button_style.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class CustomButtonStyle { + Color defaultButtonColor; + Color? pressedButtonColor; + Color? disabledButtonColor; + Color defaultBorderColor; + Color? pressedBorderColor; + Color? disabledBorderColor; + Color defaultIconColor; + Color? pressedIconColor; + Color? disabledIconColor; + TextStyle defaultLabelStyle; + TextStyle? pressedLabelStyle; + TextStyle? disabledLabelStyle; + Color? checkIconColor; + + CustomButtonStyle({ + required this.defaultButtonColor, + this.pressedButtonColor, + this.disabledButtonColor, + required this.defaultBorderColor, + this.pressedBorderColor, + this.disabledBorderColor, + required this.defaultIconColor, + this.pressedIconColor, + this.disabledIconColor, + required this.defaultLabelStyle, + this.pressedLabelStyle, + this.disabledLabelStyle, + this.checkIconColor, + }); +} diff --git a/mobile/packages/ui/lib/components/captioned_text_widget.dart b/mobile/packages/ui/lib/components/captioned_text_widget.dart new file mode 100644 index 0000000000..bc9a9708d3 --- /dev/null +++ b/mobile/packages/ui/lib/components/captioned_text_widget.dart @@ -0,0 +1,57 @@ +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +class CaptionedTextWidget extends StatelessWidget { + final String title; + final String? subTitle; + final TextStyle? textStyle; + final bool makeTextBold; + final Color? textColor; + const CaptionedTextWidget({ + required this.title, + this.subTitle, + this.textStyle, + this.makeTextBold = false, + this.textColor, + super.key, + }); + + @override + Widget build(BuildContext context) { + final enteColorScheme = getEnteColorScheme(context); + final enteTextTheme = getEnteTextTheme(context); + + return Flexible( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 2), + child: Row( + children: [ + Flexible( + child: RichText( + text: TextSpan( + style: textStyle ?? + (makeTextBold + ? enteTextTheme.bodyBold.copyWith(color: textColor) + : enteTextTheme.body.copyWith(color: textColor)), + children: [ + TextSpan( + text: title, + ), + subTitle != null + ? TextSpan( + text: ' \u2022 $subTitle', + style: enteTextTheme.small.copyWith( + color: enteColorScheme.textMuted, + ), + ) + : const TextSpan(text: ''), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/packages/ui/lib/components/components_constants.dart b/mobile/packages/ui/lib/components/components_constants.dart new file mode 100644 index 0000000000..2f8cbf52b1 --- /dev/null +++ b/mobile/packages/ui/lib/components/components_constants.dart @@ -0,0 +1,4 @@ +const double mobileSmallThreshold = 336; + +//Screen width of iPhone 14 pro max in points is taken as maximum +const double restrictedMaxWidth = 430; diff --git a/mobile/packages/ui/lib/components/developer_settings_widget.dart b/mobile/packages/ui/lib/components/developer_settings_widget.dart new file mode 100644 index 0000000000..408c4d7111 --- /dev/null +++ b/mobile/packages/ui/lib/components/developer_settings_widget.dart @@ -0,0 +1,32 @@ +import 'package:ente_configuration/base_configuration.dart'; +import 'package:ente_configuration/constants.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:flutter/material.dart'; + +class DeveloperSettingsWidget extends StatelessWidget { + final BaseConfiguration configuration; + + const DeveloperSettingsWidget( + this.configuration, { + super.key, + }); + + @override + Widget build(BuildContext context) { + final endpoint = configuration.getHttpEndpoint(); + if (endpoint != kDefaultProductionEndpoint) { + final endpointURI = Uri.parse(endpoint); + return Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Text( + context.strings.customEndpoint( + "${endpointURI.host}:${endpointURI.port}", + ), + style: Theme.of(context).textTheme.bodySmall, + ), + ); + } else { + return const SizedBox.shrink(); + } + } +} diff --git a/mobile/packages/ui/lib/components/dialog_widget.dart b/mobile/packages/ui/lib/components/dialog_widget.dart new file mode 100644 index 0000000000..36d849a719 --- /dev/null +++ b/mobile/packages/ui/lib/components/dialog_widget.dart @@ -0,0 +1,293 @@ +import 'dart:math'; + +import 'package:ente_base/typedefs.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_result.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/components/components_constants.dart'; +import 'package:ente_ui/components/separators.dart'; +import 'package:ente_ui/components/text_input_widget.dart'; +import "package:ente_ui/theme/colors.dart"; +import "package:ente_ui/theme/effects.dart"; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +///Will return null if dismissed by tapping outside +Future showDialogWidget({ + required BuildContext context, + required String title, + String? body, + required List buttons, + IconData? icon, + bool isDismissible = true, + bool useRootNavigator = false, +}) { + return showDialog( + useRootNavigator: useRootNavigator, + barrierDismissible: isDismissible, + barrierColor: backdropFaintDark, + context: context, + builder: (context) { + final widthOfScreen = MediaQuery.of(context).size.width; + final isMobileSmall = widthOfScreen <= mobileSmallThreshold; + return Padding( + padding: EdgeInsets.symmetric(horizontal: isMobileSmall ? 8 : 0), + child: Dialog( + insetPadding: EdgeInsets.zero, + child: DialogWidget( + title: title, + body: body, + buttons: buttons, + icon: icon, + ), + ), + ); + }, + ); +} + +class DialogWidget extends StatelessWidget { + final String title; + final String? body; + final List buttons; + final IconData? icon; + const DialogWidget({ + required this.title, + this.body, + required this.buttons, + this.icon, + super.key, + }); + + @override + Widget build(BuildContext context) { + final widthOfScreen = MediaQuery.of(context).size.width; + final isMobileSmall = widthOfScreen <= mobileSmallThreshold; + final colorScheme = getEnteColorScheme(context); + return Container( + width: min(widthOfScreen, 320), + padding: isMobileSmall + ? const EdgeInsets.all(0) + : const EdgeInsets.fromLTRB(6, 8, 6, 6), + decoration: BoxDecoration( + color: colorScheme.backgroundElevated, + boxShadow: shadowFloatLight, + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + child: Material( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ContentContainer( + title: title, + body: body, + icon: icon, + ), + const SizedBox(height: 36), + Actions(buttons), + ], + ), + ), + ), + ), + ); + } +} + +class ContentContainer extends StatelessWidget { + final String title; + final String? body; + final IconData? icon; + const ContentContainer({ + required this.title, + this.body, + this.icon, + super.key, + }); + + @override + Widget build(BuildContext context) { + final textTheme = getEnteTextTheme(context); + final colorScheme = getEnteColorScheme(context); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + icon == null + ? const SizedBox.shrink() + : Row( + children: [ + Icon( + icon, + size: 32, + ), + ], + ), + icon == null ? const SizedBox.shrink() : const SizedBox(height: 19), + Text(title, style: textTheme.largeBold), + body != null ? const SizedBox(height: 19) : const SizedBox.shrink(), + body != null + ? Text( + body!, + style: textTheme.body.copyWith(color: colorScheme.textMuted), + ) + : const SizedBox.shrink(), + ], + ); + } +} + +class Actions extends StatelessWidget { + final List buttons; + const Actions(this.buttons, {super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: addSeparators( + buttons, + const SizedBox( + // In figma this white space is of height 8pts. But the Button + // component has 1pts of invisible border by default in code. So two + // 1pts borders will visually make the whitespace 8pts. + // Height of button component in figma = 48, in code = 50 (2pts for + // top + bottom border) + height: 6, + ), + ), + ); + } +} + +class TextInputDialog extends StatefulWidget { + final String title; + final String? body; + final String submitButtonLabel; + final IconData? icon; + final String? label; + final String? message; + final FutureVoidCallbackParamStr onSubmit; + final String? hintText; + final IconData? prefixIcon; + final String? initialValue; + final Alignment? alignMessage; + final int? maxLength; + final bool showOnlyLoadingState; + final TextCapitalization? textCapitalization; + final bool alwaysShowSuccessState; + final bool isPasswordInput; + const TextInputDialog({ + required this.title, + this.body, + required this.submitButtonLabel, + required this.onSubmit, + this.icon, + this.label, + this.message, + this.hintText, + this.prefixIcon, + this.initialValue, + this.alignMessage, + this.maxLength, + this.textCapitalization, + this.showOnlyLoadingState = false, + this.alwaysShowSuccessState = false, + this.isPasswordInput = false, + super.key, + }); + + @override + State createState() => _TextInputDialogState(); +} + +class _TextInputDialogState extends State { + //the value of this ValueNotifier has no significance + final _submitNotifier = ValueNotifier(false); + + @override + void dispose() { + _submitNotifier.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final widthOfScreen = MediaQuery.of(context).size.width; + final isMobileSmall = widthOfScreen <= mobileSmallThreshold; + final colorScheme = getEnteColorScheme(context); + return Container( + width: min(widthOfScreen, 320), + padding: isMobileSmall + ? const EdgeInsets.all(0) + : const EdgeInsets.fromLTRB(6, 8, 6, 6), + decoration: BoxDecoration( + color: colorScheme.backgroundElevated, + boxShadow: shadowFloatLight, + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ContentContainer( + title: widget.title, + body: widget.body, + icon: widget.icon, + ), + Padding( + padding: const EdgeInsets.only(top: 19), + child: TextInputWidget( + label: widget.label, + message: widget.message, + hintText: widget.hintText, + prefixIcon: widget.prefixIcon, + initialValue: widget.initialValue, + alignMessage: widget.alignMessage, + autoFocus: true, + maxLength: widget.maxLength, + submitNotifier: _submitNotifier, + onSubmit: widget.onSubmit, + popNavAfterSubmission: true, + showOnlyLoadingState: widget.showOnlyLoadingState, + textCapitalization: widget.textCapitalization, + alwaysShowSuccessState: widget.alwaysShowSuccessState, + isPasswordInput: widget.isPasswordInput, + ), + ), + const SizedBox(height: 36), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: ButtonWidget( + buttonType: ButtonType.secondary, + buttonSize: ButtonSize.small, + labelText: context.strings.cancel, + isInAlert: true, + ), + ), + const SizedBox(width: 8), + Expanded( + child: ButtonWidget( + buttonSize: ButtonSize.small, + buttonType: ButtonType.neutral, + labelText: widget.submitButtonLabel, + onTap: () async { + _submitNotifier.value = !_submitNotifier.value; + }, + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/mobile/packages/ui/lib/components/dialogs.dart b/mobile/packages/ui/lib/components/dialogs.dart new file mode 100644 index 0000000000..1bb7071eef --- /dev/null +++ b/mobile/packages/ui/lib/components/dialogs.dart @@ -0,0 +1,124 @@ +import 'package:ente_base/typedefs.dart'; +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_result.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/components/dialog_widget.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +enum DialogUserChoice { firstChoice, secondChoice } + +enum ActionType { + confirm, + critical, +} + +// if dialog is dismissed by tapping outside, this will return null +Future showChoiceDialogOld( + BuildContext context, + String title, + String content, { + String firstAction = 'Ok', + Color? firstActionColor, + String secondAction = 'Cancel', + Color? secondActionColor, + ActionType actionType = ActionType.confirm, +}) { + final AlertDialog alert = AlertDialog( + title: Text( + title, + style: TextStyle( + color: actionType == ActionType.critical + ? Colors.red + : getEnteColorScheme(context).primary500, + ), + ), + content: Text( + content, + style: const TextStyle( + height: 1.4, + ), + ), + actions: [ + TextButton( + child: Text( + firstAction, + style: TextStyle( + color: firstActionColor ?? + (actionType == ActionType.critical + ? Colors.red + : getEnteColorScheme(context).surface), + ), + ), + onPressed: () { + Navigator.of(context, rootNavigator: true) + .pop(DialogUserChoice.firstChoice); + }, + ), + TextButton( + child: Text( + secondAction, + style: TextStyle( + color: secondActionColor ?? + getEnteColorScheme(context).alternativeColor, + ), + ), + onPressed: () { + Navigator.of(context, rootNavigator: true) + .pop(DialogUserChoice.secondChoice); + }, + ), + ], + ); + + return showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, + barrierColor: Colors.black87, + ); +} + +///Will return null if dismissed by tapping outside +Future showChoiceDialog( + BuildContext context, { + required String title, + String? body, + required String firstButtonLabel, + String secondButtonLabel = "Cancel", + ButtonType firstButtonType = ButtonType.neutral, + ButtonType secondButtonType = ButtonType.secondary, + ButtonAction firstButtonAction = ButtonAction.first, + ButtonAction secondButtonAction = ButtonAction.cancel, + FutureVoidCallback? firstButtonOnTap, + FutureVoidCallback? secondButtonOnTap, + bool isCritical = false, + IconData? icon, + bool isDismissible = true, +}) async { + final buttons = [ + ButtonWidget( + buttonType: isCritical ? ButtonType.critical : firstButtonType, + labelText: firstButtonLabel, + isInAlert: true, + onTap: firstButtonOnTap, + buttonAction: firstButtonAction, + ), + ButtonWidget( + buttonType: secondButtonType, + labelText: secondButtonLabel, + isInAlert: true, + onTap: secondButtonOnTap, + buttonAction: secondButtonAction, + ), + ]; + return showDialogWidget( + context: context, + title: title, + body: body, + buttons: buttons, + icon: icon, + isDismissible: isDismissible, + ); +} diff --git a/mobile/packages/ui/lib/components/divider_widget.dart b/mobile/packages/ui/lib/components/divider_widget.dart new file mode 100644 index 0000000000..71d8165cf0 --- /dev/null +++ b/mobile/packages/ui/lib/components/divider_widget.dart @@ -0,0 +1,69 @@ +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +enum DividerType { + solid, + menu, + menuNoIcon, + bottomBar, +} + +class DividerWidget extends StatelessWidget { + final DividerType dividerType; + final Color bgColor; + final bool divColorHasBlur; + final EdgeInsets? padding; + const DividerWidget({ + super.key, + required this.dividerType, + this.bgColor = Colors.transparent, + this.divColorHasBlur = true, + this.padding, + }); + + @override + Widget build(BuildContext context) { + final dividerColor = divColorHasBlur + ? getEnteColorScheme(context).blurStrokeFaint + : getEnteColorScheme(context).strokeFaint; + + if (dividerType == DividerType.solid) { + return Container( + color: getEnteColorScheme(context).strokeFaint, + width: double.infinity, + height: 1, + ); + } + if (dividerType == DividerType.bottomBar) { + return Container( + color: dividerColor, + width: double.infinity, + height: 1, + ); + } + + return Container( + color: bgColor, + padding: padding ?? EdgeInsets.zero, + child: Row( + children: [ + SizedBox( + width: dividerType == DividerType.menu + ? 48 + : dividerType == DividerType.menuNoIcon + ? 16 + : 0, + height: 1, + ), + Expanded( + child: Container( + color: dividerColor, + height: 1, + width: double.infinity, + ), + ), + ], + ), + ); + } +} diff --git a/mobile/packages/ui/lib/components/loading_widget.dart b/mobile/packages/ui/lib/components/loading_widget.dart new file mode 100644 index 0000000000..113d9e199f --- /dev/null +++ b/mobile/packages/ui/lib/components/loading_widget.dart @@ -0,0 +1,33 @@ +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +class EnteLoadingWidget extends StatelessWidget { + final Color? color; + final double size; + final double padding; + final Alignment alignment; + const EnteLoadingWidget({ + this.color, + this.size = 14, + this.padding = 5, + this.alignment = Alignment.center, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Align( + alignment: alignment, + child: Padding( + padding: EdgeInsets.all(padding), + child: SizedBox.fromSize( + size: Size.square(size), + child: CircularProgressIndicator( + strokeWidth: 2, + color: color ?? getEnteColorScheme(context).strokeBase, + ), + ), + ), + ); + } +} diff --git a/mobile/packages/ui/lib/components/menu_item_child_widgets.dart b/mobile/packages/ui/lib/components/menu_item_child_widgets.dart new file mode 100644 index 0000000000..67b2937ed0 --- /dev/null +++ b/mobile/packages/ui/lib/components/menu_item_child_widgets.dart @@ -0,0 +1,173 @@ +import 'package:ente_ui/components/loading_widget.dart'; +import 'package:ente_ui/models/execution_states.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +class TrailingWidget extends StatefulWidget { + final ValueNotifier executionStateNotifier; + final IconData? trailingIcon; + final Color? trailingIconColor; + final Widget? trailingWidget; + final bool trailingIconIsMuted; + final double trailingExtraMargin; + final bool showExecutionStates; + const TrailingWidget({ + super.key, + required this.executionStateNotifier, + this.trailingIcon, + this.trailingIconColor, + this.trailingWidget, + required this.trailingIconIsMuted, + required this.trailingExtraMargin, + required this.showExecutionStates, + }); + @override + State createState() => _TrailingWidgetState(); +} + +class _TrailingWidgetState extends State { + Widget? trailingWidget; + @override + void initState() { + widget.showExecutionStates + ? widget.executionStateNotifier.addListener(_executionStateListener) + : null; + super.initState(); + } + + @override + void dispose() { + widget.executionStateNotifier.removeListener(_executionStateListener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (trailingWidget == null || !widget.showExecutionStates) { + _setTrailingIcon(); + } + return AnimatedSwitcher( + duration: const Duration(milliseconds: 175), + switchInCurve: Curves.easeInExpo, + switchOutCurve: Curves.easeOutExpo, + child: trailingWidget, + ); + } + + void _executionStateListener() { + final colorScheme = getEnteColorScheme(context); + setState(() { + if (widget.executionStateNotifier.value == ExecutionState.idle) { + _setTrailingIcon(); + } else if (widget.executionStateNotifier.value == + ExecutionState.inProgress) { + trailingWidget = EnteLoadingWidget( + color: colorScheme.strokeMuted, + ); + } else if (widget.executionStateNotifier.value == + ExecutionState.successful) { + trailingWidget = Icon( + Icons.check_outlined, + size: 22, + color: colorScheme.primary500, + ); + } else { + trailingWidget = const SizedBox.shrink(); + } + }); + } + + void _setTrailingIcon() { + if (widget.trailingIcon != null) { + trailingWidget = Padding( + padding: EdgeInsets.only( + right: widget.trailingExtraMargin, + ), + child: Icon( + widget.trailingIcon, + color: widget.trailingIconIsMuted + ? getEnteColorScheme(context).strokeMuted + : widget.trailingIconColor, + ), + ); + } else { + trailingWidget = widget.trailingWidget ?? const SizedBox.shrink(); + } + } +} + +class ExpansionTrailingIcon extends StatelessWidget { + final bool isExpanded; + final IconData? trailingIcon; + final Color? trailingIconColor; + const ExpansionTrailingIcon({ + super.key, + required this.isExpanded, + this.trailingIcon, + this.trailingIconColor, + }); + + @override + Widget build(BuildContext context) { + return AnimatedOpacity( + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + opacity: isExpanded ? 0 : 1, + child: AnimatedSwitcher( + transitionBuilder: (child, animation) { + return ScaleTransition(scale: animation, child: child); + }, + duration: const Duration(milliseconds: 200), + switchInCurve: Curves.easeOut, + child: isExpanded + ? const SizedBox.shrink() + : Icon( + trailingIcon, + color: trailingIconColor, + ), + ), + ); + } +} + +class LeadingWidget extends StatelessWidget { + final IconData? leadingIcon; + final Color? leadingIconColor; + + final Widget? leadingIconWidget; + // leadIconSize deafult value is 20. + final double leadingIconSize; + const LeadingWidget({ + super.key, + required this.leadingIconSize, + this.leadingIcon, + this.leadingIconColor, + this.leadingIconWidget, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(right: 10), + child: SizedBox( + height: leadingIconSize, + width: leadingIconSize, + child: leadingIcon == null + ? (leadingIconWidget != null + ? FittedBox( + fit: BoxFit.contain, + child: leadingIconWidget, + ) + : const SizedBox.shrink()) + : FittedBox( + fit: BoxFit.contain, + child: Icon( + leadingIcon, + color: leadingIconColor ?? + getEnteColorScheme(context).strokeBase, + ), + ), + ), + ); + } +} diff --git a/mobile/packages/ui/lib/components/menu_item_widget.dart b/mobile/packages/ui/lib/components/menu_item_widget.dart new file mode 100644 index 0000000000..31ce408331 --- /dev/null +++ b/mobile/packages/ui/lib/components/menu_item_widget.dart @@ -0,0 +1,297 @@ +import 'package:ente_base/typedefs.dart'; +import 'package:ente_ui/components/menu_item_child_widgets.dart'; +import 'package:ente_ui/models/execution_states.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_utils/debouncer.dart'; +import "package:expandable/expandable.dart"; +import 'package:flutter/material.dart'; + +class MenuItemWidget extends StatefulWidget { + final Widget captionedTextWidget; + final bool isExpandable; + + /// leading icon can be passed without specifing size of icon, + /// this component sets size to 20x20 irrespective of passed icon's size + final IconData? leadingIcon; + final Color? leadingIconColor; + + final Widget? leadingIconWidget; + + // leadIconSize deafult value is 20. + final double leadingIconSize; + + /// trailing icon can be passed without size as default size set by + /// flutter is what this component expects + final IconData? trailingIcon; + final Color? trailingIconColor; + final Widget? trailingWidget; + final bool trailingIconIsMuted; + + /// If provided, add this much extra spacing to the right of the trailing icon. + final double trailingExtraMargin; + final FutureVoidCallback? onTap; + final VoidCallback? onDoubleTap; + final VoidCallback? onLongPress; + final Color? menuItemColor; + final bool alignCaptionedTextToLeft; + + // singleBorderRadius is applied to the border when it's a standalone menu item. + // Widget will apply singleBorderRadius if value of both isTopBorderRadiusRemoved + // and isBottomBorderRadiusRemoved is false. Otherwise, multipleBorderRadius will + // be applied + final double singleBorderRadius; + final double multipleBorderRadius; + final Color? pressedColor; + final ExpandableController? expandableController; + final bool isBottomBorderRadiusRemoved; + final bool isTopBorderRadiusRemoved; + + /// disable gesture detector if not used + final bool isGestureDetectorDisabled; + + ///Success state will not be shown if this flag is set to true, only idle and + ///loading state + final bool showOnlyLoadingState; + + final bool surfaceExecutionStates; + + ///To show success state even when execution time < debouce time, set this + ///flag to true. If the loading state needs to be shown and success state not, + ///set the showOnlyLoadingState flag to true, setting this flag to false won't + ///help. + final bool alwaysShowSuccessState; + + const MenuItemWidget({ + required this.captionedTextWidget, + this.isExpandable = false, + this.leadingIcon, + this.leadingIconColor, + this.leadingIconSize = 20.0, + this.leadingIconWidget, + this.trailingIcon, + this.trailingIconColor, + this.trailingWidget, + this.trailingIconIsMuted = false, + this.trailingExtraMargin = 0.0, + this.onTap, + this.onDoubleTap, + this.onLongPress, + this.menuItemColor, + this.alignCaptionedTextToLeft = false, + this.singleBorderRadius = 4.0, + this.multipleBorderRadius = 8.0, + this.pressedColor, + this.expandableController, + this.isBottomBorderRadiusRemoved = false, + this.isTopBorderRadiusRemoved = false, + this.isGestureDetectorDisabled = false, + this.showOnlyLoadingState = false, + this.surfaceExecutionStates = true, + this.alwaysShowSuccessState = false, + super.key, + }); + + @override + State createState() => _MenuItemWidgetState(); +} + +class _MenuItemWidgetState extends State { + final _debouncer = Debouncer(const Duration(milliseconds: 300)); + ValueNotifier executionStateNotifier = + ValueNotifier(ExecutionState.idle); + + Color? menuItemColor; + late double borderRadius; + + @override + void initState() { + menuItemColor = widget.menuItemColor; + borderRadius = + (widget.isBottomBorderRadiusRemoved || widget.isTopBorderRadiusRemoved) + ? widget.multipleBorderRadius + : widget.singleBorderRadius; + if (widget.expandableController != null) { + widget.expandableController!.addListener(() { + setState(() {}); + }); + } + super.initState(); + } + + @override + void didChangeDependencies() { + menuItemColor = widget.menuItemColor; + super.didChangeDependencies(); + } + + @override + void didUpdateWidget(covariant MenuItemWidget oldWidget) { + menuItemColor = widget.menuItemColor; + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + if (widget.expandableController != null) { + widget.expandableController!.dispose(); + } + executionStateNotifier.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.isExpandable || widget.isGestureDetectorDisabled + ? menuItemWidget(context) + : GestureDetector( + onTap: _onTap, + onDoubleTap: widget.onDoubleTap, + onLongPress: widget.onLongPress, + onTapDown: _onTapDown, + onTapUp: _onTapUp, + onTapCancel: _onCancel, + child: menuItemWidget(context), + ); + } + + Widget menuItemWidget(BuildContext context) { + final circularRadius = Radius.circular(borderRadius); + final isExpanded = widget.expandableController?.value; + final bottomBorderRadius = + (isExpanded != null && isExpanded) || widget.isBottomBorderRadiusRemoved + ? const Radius.circular(0) + : circularRadius; + final topBorderRadius = widget.isTopBorderRadiusRemoved + ? const Radius.circular(0) + : circularRadius; + return AnimatedContainer( + duration: const Duration(milliseconds: 20), + width: double.infinity, + padding: const EdgeInsets.only(left: 16, right: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: topBorderRadius, + topRight: topBorderRadius, + bottomLeft: bottomBorderRadius, + bottomRight: bottomBorderRadius, + ), + color: menuItemColor, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + widget.alignCaptionedTextToLeft && widget.leadingIcon == null + ? const SizedBox.shrink() + : LeadingWidget( + leadingIconSize: widget.leadingIconSize, + leadingIcon: widget.leadingIcon, + leadingIconColor: widget.leadingIconColor, + leadingIconWidget: widget.leadingIconWidget, + ), + widget.captionedTextWidget, + if (widget.expandableController != null) + ExpansionTrailingIcon( + isExpanded: isExpanded!, + trailingIcon: widget.trailingIcon, + trailingIconColor: widget.trailingIconColor, + ) + else + TrailingWidget( + executionStateNotifier: executionStateNotifier, + trailingIcon: widget.trailingIcon, + trailingIconColor: widget.trailingIconColor, + trailingWidget: widget.trailingWidget, + trailingIconIsMuted: widget.trailingIconIsMuted, + trailingExtraMargin: widget.trailingExtraMargin, + showExecutionStates: widget.surfaceExecutionStates, + key: ValueKey(widget.trailingIcon.hashCode), + ), + ], + ), + ); + } + + Future _onTap() async { + if (executionStateNotifier.value == ExecutionState.inProgress || + executionStateNotifier.value == ExecutionState.successful) { + return; + } + _debouncer.run( + () => Future( + () { + executionStateNotifier.value = ExecutionState.inProgress; + }, + ), + ); + await widget.onTap?.call().then( + (value) { + widget.alwaysShowSuccessState + ? executionStateNotifier.value = ExecutionState.successful + : null; + }, + onError: (error, stackTrace) => _debouncer.cancelDebounce(), + ); + _debouncer.cancelDebounce(); + if (widget.alwaysShowSuccessState) { + Future.delayed(const Duration(seconds: 2), () { + executionStateNotifier.value = ExecutionState.idle; + }); + return; + } + if (executionStateNotifier.value == ExecutionState.inProgress) { + if (widget.showOnlyLoadingState) { + executionStateNotifier.value = ExecutionState.idle; + } else { + executionStateNotifier.value = ExecutionState.successful; + Future.delayed(const Duration(seconds: 2), () { + executionStateNotifier.value = ExecutionState.idle; + }); + } + } + } + + void _onTapDown(details) { + if (executionStateNotifier.value == ExecutionState.inProgress || + executionStateNotifier.value == ExecutionState.successful) { + return; + } + setState(() { + if (widget.pressedColor == null) { + hasPassedGestureCallbacks() + ? menuItemColor = getEnteColorScheme(context).fillFaintPressed + : menuItemColor = widget.menuItemColor; + } else { + menuItemColor = widget.pressedColor; + } + }); + } + + bool hasPassedGestureCallbacks() { + return widget.onDoubleTap != null || + widget.onTap != null || + widget.onLongPress != null; + } + + void _onTapUp(details) { + if (executionStateNotifier.value == ExecutionState.inProgress || + executionStateNotifier.value == ExecutionState.successful) { + return; + } + Future.delayed( + const Duration(milliseconds: 100), + () => setState(() { + menuItemColor = widget.menuItemColor; + }), + ); + } + + void _onCancel() { + if (executionStateNotifier.value == ExecutionState.inProgress || + executionStateNotifier.value == ExecutionState.successful) { + return; + } + setState(() { + menuItemColor = widget.menuItemColor; + }); + } +} diff --git a/mobile/packages/ui/lib/components/progress_dialog.dart b/mobile/packages/ui/lib/components/progress_dialog.dart new file mode 100644 index 0000000000..adabff2ee0 --- /dev/null +++ b/mobile/packages/ui/lib/components/progress_dialog.dart @@ -0,0 +1,289 @@ +import 'package:flutter/material.dart'; + +enum ProgressDialogType { normal, download } + +String _dialogMessage = "Loading..."; +double _progress = 0.0, _maxProgress = 100.0; + +Widget? _customBody; + +TextAlign _textAlign = TextAlign.left; +Alignment _progressWidgetAlignment = Alignment.centerLeft; + +TextDirection _direction = TextDirection.ltr; + +bool _isShowing = false; +BuildContext? _context, _dismissingContext; +ProgressDialogType? _progressDialogType; +bool _barrierDismissible = true, _showLogs = false; +Color? _barrierColor; + +TextStyle _progressTextStyle = const TextStyle( + color: Colors.black, + fontSize: 12.0, + fontWeight: FontWeight.w400, + ), + _messageStyle = const TextStyle( + color: Colors.black, + fontSize: 18.0, + fontWeight: FontWeight.w600, + ); + +double _dialogElevation = 8.0, _borderRadius = 8.0; +Color _backgroundColor = Colors.white; +Curve _insetAnimCurve = Curves.easeInOut; +EdgeInsets _dialogPadding = const EdgeInsets.all(8.0); + +Widget _progressWidget = Image.asset( + 'assets/double_ring_loading_io.gif', + package: 'progress_dialog', +); + +class ProgressDialog { + _Body? _dialog; + + ProgressDialog( + BuildContext context, { + ProgressDialogType? type, + bool? isDismissible, + bool? showLogs, + TextDirection? textDirection, + Widget? customBody, + Color? barrierColor, + }) { + _context = context; + _progressDialogType = type ?? ProgressDialogType.normal; + _barrierDismissible = isDismissible ?? true; + _showLogs = showLogs ?? false; + _customBody = customBody; + _direction = textDirection ?? TextDirection.ltr; + _barrierColor = barrierColor ?? barrierColor; + } + + void style({ + Widget? child, + double? progress, + double? maxProgress, + String? message, + Widget? progressWidget, + Color? backgroundColor, + TextStyle? progressTextStyle, + TextStyle? messageTextStyle, + double? elevation, + TextAlign? textAlign, + double? borderRadius, + Curve? insetAnimCurve, + EdgeInsets? padding, + Alignment? progressWidgetAlignment, + }) { + if (_isShowing) return; + if (_progressDialogType == ProgressDialogType.download) { + _progress = progress ?? _progress; + } + + _dialogMessage = message ?? _dialogMessage; + _maxProgress = maxProgress ?? _maxProgress; + _progressWidget = progressWidget ?? _progressWidget; + _backgroundColor = backgroundColor ?? _backgroundColor; + _messageStyle = messageTextStyle ?? _messageStyle; + _progressTextStyle = progressTextStyle ?? _progressTextStyle; + _dialogElevation = elevation ?? _dialogElevation; + _borderRadius = borderRadius ?? _borderRadius; + _insetAnimCurve = insetAnimCurve ?? _insetAnimCurve; + _textAlign = textAlign ?? _textAlign; + _progressWidget = child ?? _progressWidget; + _dialogPadding = padding ?? _dialogPadding; + _progressWidgetAlignment = + progressWidgetAlignment ?? _progressWidgetAlignment; + } + + void update({ + double? progress, + double? maxProgress, + String? message, + Widget? progressWidget, + TextStyle? progressTextStyle, + TextStyle? messageTextStyle, + }) { + if (_progressDialogType == ProgressDialogType.download) { + _progress = progress ?? _progress; + } + + _dialogMessage = message ?? _dialogMessage; + _maxProgress = maxProgress ?? _maxProgress; + _progressWidget = progressWidget ?? _progressWidget; + _messageStyle = messageTextStyle ?? _messageStyle; + _progressTextStyle = progressTextStyle ?? _progressTextStyle; + + if (_isShowing) _dialog!.update(); + } + + bool isShowing() { + return _isShowing; + } + + Future hide() async { + try { + if (_isShowing) { + _isShowing = false; + if (_dismissingContext != null) { + Navigator.of(_dismissingContext!).pop(); + } + if (_showLogs) debugPrint('ProgressDialog dismissed'); + return Future.value(true); + } else { + if (_showLogs) debugPrint('ProgressDialog already dismissed'); + return Future.value(false); + } + } catch (err) { + debugPrint('Seems there is an issue hiding dialog'); + debugPrint(err.toString()); + return Future.value(false); + } + } + + Future show() async { + try { + if (!_isShowing) { + _dialog = _Body(); + // ignore: unawaited_futures + showDialog( + context: _context!, + barrierDismissible: _barrierDismissible, + barrierColor: _barrierColor, + builder: (BuildContext context) { + _dismissingContext = context; + return PopScope( + canPop: _barrierDismissible, + child: Dialog( + backgroundColor: _backgroundColor, + insetAnimationCurve: _insetAnimCurve, + insetAnimationDuration: const Duration(milliseconds: 100), + elevation: _dialogElevation, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.all(Radius.circular(_borderRadius)), + ), + child: _dialog, + ), + ); + }, + ); + // Delaying the function for 200 milliseconds + // [Default transitionDuration of DialogRoute] + await Future.delayed(const Duration(milliseconds: 200)); + if (_showLogs) debugPrint('ProgressDialog shown'); + _isShowing = true; + return true; + } else { + if (_showLogs) debugPrint("ProgressDialog already shown/showing"); + return false; + } + } catch (err) { + _isShowing = false; + debugPrint('Exception while showing the dialog'); + debugPrint(err.toString()); + return false; + } + } +} + +// ignore: must_be_immutable +class _Body extends StatefulWidget { + final _BodyState _dialog = _BodyState(); + + update() { + _dialog.update(); + } + + @override + State createState() { + // ignore: no_logic_in_create_state + return _dialog; + } +} + +class _BodyState extends State<_Body> { + update() { + setState(() {}); + } + + @override + void dispose() { + _isShowing = false; + if (_showLogs) debugPrint('ProgressDialog dismissed by back button'); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final loader = Align( + alignment: _progressWidgetAlignment, + child: SizedBox( + width: 60.0, + height: 60.0, + child: _progressWidget, + ), + ); + + final text = Expanded( + child: _progressDialogType == ProgressDialogType.normal + ? Text( + _dialogMessage, + textAlign: _textAlign, + style: _messageStyle, + textDirection: _direction, + ) + : Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8.0), + Row( + children: [ + Expanded( + child: Text( + _dialogMessage, + style: _messageStyle, + textDirection: _direction, + ), + ), + ], + ), + const SizedBox(height: 4.0), + Align( + alignment: Alignment.bottomRight, + child: Text( + "$_progress/$_maxProgress", + style: _progressTextStyle, + textDirection: _direction, + ), + ), + ], + ), + ), + ); + + return _customBody ?? + Container( + padding: _dialogPadding, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // row body + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(width: 8.0), + _direction == TextDirection.ltr ? loader : text, + const SizedBox(width: 8.0), + _direction == TextDirection.rtl ? loader : text, + const SizedBox(width: 8.0), + ], + ), + ], + ), + ); + } +} diff --git a/mobile/packages/ui/lib/components/separators.dart b/mobile/packages/ui/lib/components/separators.dart new file mode 100644 index 0000000000..bc4b1346ad --- /dev/null +++ b/mobile/packages/ui/lib/components/separators.dart @@ -0,0 +1,13 @@ +//This method returns a newly declared list with separators. It will not +//modify the original list +import 'package:flutter/widgets.dart'; + +List addSeparators(List listOfWidgets, Widget separator) { + final int initialLength = listOfWidgets.length; + final listOfWidgetsWithSeparators = []; + listOfWidgetsWithSeparators.addAll(listOfWidgets); + for (var i = 1; i < initialLength; i++) { + listOfWidgetsWithSeparators.insert((2 * i) - 1, separator); + } + return listOfWidgetsWithSeparators; +} diff --git a/mobile/packages/ui/lib/components/text_input_widget.dart b/mobile/packages/ui/lib/components/text_input_widget.dart new file mode 100644 index 0000000000..b310f31a08 --- /dev/null +++ b/mobile/packages/ui/lib/components/text_input_widget.dart @@ -0,0 +1,406 @@ +import 'package:ente_base/typedefs.dart'; +import 'package:ente_logging/logging.dart'; +import 'package:ente_ui/components/loading_widget.dart'; +import 'package:ente_ui/components/separators.dart'; +import 'package:ente_ui/models/execution_states.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_utils/debouncer.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class TextInputWidget extends StatefulWidget { + final String? label; + final String? message; + final String? hintText; + final IconData? prefixIcon; + final String? initialValue; + final Alignment? alignMessage; + final bool? autoFocus; + final int? maxLength; + + ///TextInputWidget will listen to this notifier and executes onSubmit when + ///notified. + final ValueNotifier? submitNotifier; + final bool alwaysShowSuccessState; + final bool showOnlyLoadingState; + final FutureVoidCallbackParamStr? onSubmit; + final VoidCallbackParamStr? onChange; + final bool popNavAfterSubmission; + final bool shouldSurfaceExecutionStates; + final TextCapitalization? textCapitalization; + final bool isPasswordInput; + final bool cancellable; + final bool shouldUnfocusOnCancelOrSubmit; + const TextInputWidget({ + this.onSubmit, + this.onChange, + this.label, + this.message, + this.hintText, + this.prefixIcon, + this.initialValue, + this.alignMessage, + this.autoFocus, + this.maxLength, + this.submitNotifier, + this.alwaysShowSuccessState = false, + this.showOnlyLoadingState = false, + this.popNavAfterSubmission = false, + this.shouldSurfaceExecutionStates = true, + this.textCapitalization = TextCapitalization.none, + this.isPasswordInput = false, + this.cancellable = false, + this.shouldUnfocusOnCancelOrSubmit = false, + super.key, + }); + + @override + State createState() => _TextInputWidgetState(); +} + +class _TextInputWidgetState extends State { + final _logger = Logger("TextInputWidget"); + ExecutionState executionState = ExecutionState.idle; + final _textController = TextEditingController(); + final _debouncer = Debouncer(const Duration(milliseconds: 300)); + late final ValueNotifier _obscureTextNotifier; + + ///This is to pass if the TextInputWidget is in a dialog and an error is + ///thrown in executing onSubmit by passing it as arg in Navigator.pop() + Exception? _exception; + bool _incorrectPassword = false; + @override + void initState() { + widget.submitNotifier?.addListener(_onSubmit); + + if (widget.initialValue != null) { + _textController.value = TextEditingValue( + text: widget.initialValue!, + selection: TextSelection.collapsed(offset: widget.initialValue!.length), + ); + } + if (widget.onChange != null) { + _textController.addListener(() { + widget.onChange!.call(_textController.text); + }); + } + _obscureTextNotifier = ValueNotifier(widget.isPasswordInput); + _obscureTextNotifier.addListener(_safeRefresh); + super.initState(); + } + + @override + void dispose() { + widget.submitNotifier?.removeListener(_onSubmit); + _obscureTextNotifier.dispose(); + _textController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (executionState == ExecutionState.successful) { + Future.delayed(Duration(seconds: widget.popNavAfterSubmission ? 1 : 2), + () { + setState(() { + executionState = ExecutionState.idle; + }); + }); + } + final colorScheme = getEnteColorScheme(context); + final textTheme = getEnteTextTheme(context); + var textInputChildren = []; + if (widget.label != null) { + textInputChildren.add(Text(widget.label!)); + } + textInputChildren.add( + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Material( + child: TextFormField( + textCapitalization: widget.textCapitalization!, + autofocus: widget.autoFocus ?? false, + controller: _textController, + inputFormatters: widget.maxLength != null + ? [LengthLimitingTextInputFormatter(50)] + : null, + obscureText: _obscureTextNotifier.value, + decoration: InputDecoration( + hintText: widget.hintText, + hintStyle: textTheme.body.copyWith(color: colorScheme.textMuted), + filled: true, + fillColor: colorScheme.fillFaint, + contentPadding: const EdgeInsets.fromLTRB( + 12, + 12, + 0, + 12, + ), + border: const UnderlineInputBorder( + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: _incorrectPassword + ? colorScheme.warning500 + : colorScheme.strokeFaint, + ), + borderRadius: BorderRadius.circular(8), + ), + suffixIcon: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 175), + switchInCurve: Curves.easeInExpo, + switchOutCurve: Curves.easeOutExpo, + child: SuffixIconWidget( + key: ValueKey(executionState), + executionState: executionState, + shouldSurfaceExecutionStates: + widget.shouldSurfaceExecutionStates, + obscureTextNotifier: _obscureTextNotifier, + isPasswordInput: widget.isPasswordInput, + textController: _textController, + isCancellable: widget.cancellable, + shouldUnfocusOnCancelOrSubmit: + widget.shouldUnfocusOnCancelOrSubmit, + ), + ), + ), + prefixIconConstraints: const BoxConstraints( + maxHeight: 44, + maxWidth: 44, + minHeight: 44, + minWidth: 44, + ), + suffixIconConstraints: const BoxConstraints( + maxHeight: 24, + maxWidth: 48, + minHeight: 24, + minWidth: 48, + ), + prefixIcon: widget.prefixIcon != null + ? Icon( + widget.prefixIcon, + color: colorScheme.strokeMuted, + ) + : null, + ), + onEditingComplete: () { + _onSubmit(); + }, + ), + ), + ), + ); + if (widget.message != null) { + textInputChildren.add( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Align( + alignment: widget.alignMessage ?? Alignment.centerLeft, + child: Text( + widget.message!, + style: textTheme.small.copyWith(color: colorScheme.textMuted), + ), + ), + ), + ); + } + textInputChildren = + addSeparators(textInputChildren, const SizedBox(height: 4)); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: textInputChildren, + ); + } + + void _safeRefresh() { + if (mounted) { + setState(() {}); + } + } + + void _onSubmit() async { + _debouncer.run( + () => Future(() { + setState(() { + executionState = ExecutionState.inProgress; + }); + }), + ); + if (widget.shouldUnfocusOnCancelOrSubmit) { + FocusScope.of(context).unfocus(); + } + try { + await widget.onSubmit!.call(_textController.text); + } catch (e) { + executionState = ExecutionState.error; + _debouncer.cancelDebounce(); + _exception = e as Exception; + if (e.toString().contains("Incorrect password")) { + _logger.warning("Incorrect password"); + _surfaceWrongPasswordState(); + } + if (!widget.popNavAfterSubmission) { + rethrow; + } + } + widget.alwaysShowSuccessState && _debouncer.isActive() + ? executionState = ExecutionState.successful + : null; + _debouncer.cancelDebounce(); + if (executionState == ExecutionState.successful) { + setState(() {}); + } + + // when the time taken by widget.onSubmit is approximately equal to the debounce + // time, the callback is getting executed when/after the if condition + // below is executing/executed which results in execution state stuck at + // idle state. This Future is for delaying the execution of the if + // condition so that the calback in the debouncer finishes execution before. + await Future.delayed(const Duration(milliseconds: 5)); + if (executionState == ExecutionState.inProgress || + executionState == ExecutionState.error) { + if (executionState == ExecutionState.inProgress) { + if (mounted) { + if (widget.showOnlyLoadingState) { + setState(() { + executionState = ExecutionState.idle; + }); + _popNavigatorStack(context); + } else { + setState(() { + executionState = ExecutionState.successful; + Future.delayed( + Duration( + seconds: widget.shouldSurfaceExecutionStates + ? (widget.popNavAfterSubmission ? 1 : 2) + : 0, + ), () { + widget.popNavAfterSubmission + ? _popNavigatorStack(context) + : null; + if (mounted) { + setState(() { + executionState = ExecutionState.idle; + }); + } + }); + }); + } + } + } + if (executionState == ExecutionState.error) { + setState(() { + executionState = ExecutionState.idle; + widget.popNavAfterSubmission + ? Future.delayed( + const Duration(seconds: 0), + () => _popNavigatorStack(context, e: _exception), + ) + : null; + }); + } + } else { + if (widget.popNavAfterSubmission) { + Future.delayed( + Duration(seconds: widget.alwaysShowSuccessState ? 1 : 0), + () => _popNavigatorStack(context), + ); + } + } + } + + void _popNavigatorStack(BuildContext context, {Exception? e}) { + Navigator.of(context).canPop() ? Navigator.of(context).pop(e) : null; + } + + void _surfaceWrongPasswordState() { + setState(() { + _incorrectPassword = true; + HapticFeedback.vibrate(); + Future.delayed(const Duration(seconds: 1), () { + if (mounted) { + setState(() { + _incorrectPassword = false; + }); + } + }); + }); + } +} + +//todo: Add clear and custom icon for suffic icon +class SuffixIconWidget extends StatelessWidget { + final ExecutionState executionState; + final bool shouldSurfaceExecutionStates; + final TextEditingController textController; + final ValueNotifier? obscureTextNotifier; + final bool isPasswordInput; + final bool isCancellable; + final bool shouldUnfocusOnCancelOrSubmit; + + const SuffixIconWidget({ + required this.executionState, + required this.shouldSurfaceExecutionStates, + required this.textController, + this.obscureTextNotifier, + this.isPasswordInput = false, + this.isCancellable = false, + this.shouldUnfocusOnCancelOrSubmit = false, + super.key, + }); + + @override + Widget build(BuildContext context) { + final Widget trailingWidget; + final colorScheme = getEnteColorScheme(context); + if (executionState == ExecutionState.idle || + !shouldSurfaceExecutionStates) { + if (isCancellable) { + trailingWidget = GestureDetector( + onTap: () { + textController.clear(); + if (shouldUnfocusOnCancelOrSubmit) { + FocusScope.of(context).unfocus(); + } + }, + child: Icon( + Icons.cancel_rounded, + color: colorScheme.strokeMuted, + ), + ); + } else if (isPasswordInput) { + assert(obscureTextNotifier != null); + trailingWidget = GestureDetector( + onTap: () { + obscureTextNotifier!.value = !obscureTextNotifier!.value; + }, + child: Icon( + obscureTextNotifier!.value + ? Icons.visibility_off_outlined + : Icons.visibility, + color: obscureTextNotifier!.value ? colorScheme.strokeMuted : null, + ), + ); + } else { + trailingWidget = const SizedBox.shrink(); + } + } else if (executionState == ExecutionState.inProgress) { + trailingWidget = EnteLoadingWidget( + color: colorScheme.strokeMuted, + ); + } else if (executionState == ExecutionState.successful) { + trailingWidget = Icon( + Icons.check_outlined, + size: 22, + color: colorScheme.primary500, + ); + } else { + trailingWidget = const SizedBox.shrink(); + } + return trailingWidget; + } +} diff --git a/mobile/packages/ui/lib/components/title_bar_title_widget.dart b/mobile/packages/ui/lib/components/title_bar_title_widget.dart new file mode 100644 index 0000000000..184c5726a1 --- /dev/null +++ b/mobile/packages/ui/lib/components/title_bar_title_widget.dart @@ -0,0 +1,55 @@ +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +class TitleBarTitleWidget extends StatelessWidget { + final String? title; + final bool isTitleH2; + final IconData? icon; + const TitleBarTitleWidget({ + super.key, + this.title, + this.isTitleH2 = false, + this.icon, + }); + + @override + Widget build(BuildContext context) { + final textTheme = getEnteTextTheme(context); + final colorTheme = getEnteColorScheme(context); + if (title != null) { + if (icon != null) { + return Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + title!, + style: textTheme.h3Bold, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + const SizedBox(width: 8), + Icon(icon, size: 20, color: colorTheme.strokeMuted), + ], + ); + } + if (isTitleH2) { + return Text( + title!, + style: textTheme.h2Bold, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ); + } else { + return Text( + title!, + style: textTheme.h3Bold, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ); + } + } + + return const SizedBox.shrink(); + } +} diff --git a/mobile/packages/ui/lib/components/title_bar_widget.dart b/mobile/packages/ui/lib/components/title_bar_widget.dart new file mode 100644 index 0000000000..ab60e0531c --- /dev/null +++ b/mobile/packages/ui/lib/components/title_bar_widget.dart @@ -0,0 +1,152 @@ +import 'package:ente_ui/components/buttons/icon_button_widget.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +class TitleBarWidget extends StatelessWidget { + final IconButtonWidget? leading; + final String? title; + final String? caption; + final Widget? flexibleSpaceTitle; + final String? flexibleSpaceCaption; + final List? actionIcons; + final bool isTitleH2WithoutLeading; + final bool isFlexibleSpaceDisabled; + final bool isOnTopOfScreen; + final Color? backgroundColor; + const TitleBarWidget({ + super.key, + this.leading, + this.title, + this.caption, + this.flexibleSpaceTitle, + this.flexibleSpaceCaption, + this.actionIcons, + this.isTitleH2WithoutLeading = false, + this.isFlexibleSpaceDisabled = false, + this.isOnTopOfScreen = true, + this.backgroundColor, + }); + + @override + Widget build(BuildContext context) { + const toolbarHeight = 48.0; + final textTheme = getEnteTextTheme(context); + final colorTheme = getEnteColorScheme(context); + return SliverAppBar( + backgroundColor: backgroundColor, + primary: isOnTopOfScreen ? true : false, + toolbarHeight: toolbarHeight, + leadingWidth: 48, + automaticallyImplyLeading: false, + pinned: true, + expandedHeight: isFlexibleSpaceDisabled ? toolbarHeight : 102, + centerTitle: false, + titleSpacing: 4, + title: Padding( + padding: EdgeInsets.only(left: isTitleH2WithoutLeading ? 16 : 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + title == null + ? const SizedBox.shrink() + : Text( + title!, + style: isTitleH2WithoutLeading + ? textTheme.h2Bold + : textTheme.largeBold, + ), + caption == null || isTitleH2WithoutLeading + ? const SizedBox.shrink() + : Text( + caption!, + style: textTheme.mini.copyWith(color: colorTheme.textMuted), + ), + ], + ), + ), + actions: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Row( + children: _actionsWithPaddingInBetween(), + ), + ), + ], + leading: isTitleH2WithoutLeading + ? null + : leading ?? + IconButtonWidget( + icon: Icons.arrow_back_outlined, + iconButtonType: IconButtonType.primary, + onTap: () { + Navigator.pop(context); + }, + ), + flexibleSpace: isFlexibleSpaceDisabled + ? null + : FlexibleSpaceBar( + background: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: toolbarHeight), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 4, + horizontal: 16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + flexibleSpaceTitle == null + ? const SizedBox.shrink() + : flexibleSpaceTitle!, + flexibleSpaceCaption == null + ? const SizedBox.shrink() + : Text( + flexibleSpaceCaption!, + style: textTheme.small.copyWith( + color: colorTheme.textMuted, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + _actionsWithPaddingInBetween() { + if (actionIcons == null) { + return [const SizedBox.shrink()]; + } + final actions = []; + bool addWhiteSpace = false; + final length = actionIcons!.length; + int index = 0; + if (length == 0) { + return [const SizedBox.shrink()]; + } + if (length == 1) { + return actionIcons; + } + while (index < length) { + if (!addWhiteSpace) { + actions.add(actionIcons![index]); + index++; + addWhiteSpace = true; + } else { + actions.add(const SizedBox(width: 4)); + addWhiteSpace = false; + } + } + return actions; + } +} diff --git a/mobile/packages/ui/lib/components/toggle_switch_widget.dart b/mobile/packages/ui/lib/components/toggle_switch_widget.dart new file mode 100644 index 0000000000..438bcf177f --- /dev/null +++ b/mobile/packages/ui/lib/components/toggle_switch_widget.dart @@ -0,0 +1,136 @@ +import 'package:ente_base/typedefs.dart'; +import 'package:ente_ui/components/loading_widget.dart'; +import 'package:ente_ui/models/execution_states.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_utils/debouncer.dart'; +import 'package:flutter/material.dart'; + +typedef OnChangedCallBack = void Function(bool); + +class ToggleSwitchWidget extends StatefulWidget { + final BoolCallBack value; + final FutureVoidCallback onChanged; + const ToggleSwitchWidget({ + required this.value, + required this.onChanged, + super.key, + }); + + @override + State createState() => _ToggleSwitchWidgetState(); +} + +class _ToggleSwitchWidgetState extends State { + bool? toggleValue; + ExecutionState executionState = ExecutionState.idle; + final _debouncer = Debouncer(const Duration(milliseconds: 300)); + + @override + void initState() { + toggleValue = widget.value.call(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final enteColorScheme = getEnteColorScheme(context); + final Widget stateIcon = _stateIcon(enteColorScheme); + + return Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 2), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 175), + switchInCurve: Curves.easeInExpo, + switchOutCurve: Curves.easeOutExpo, + child: stateIcon, + ), + ), + SizedBox( + height: 31, + child: FittedBox( + fit: BoxFit.contain, + child: Switch.adaptive( + activeColor: enteColorScheme.primary400, + activeTrackColor: enteColorScheme.primary300, + inactiveTrackColor: enteColorScheme.fillMuted, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: toggleValue ?? false, + onChanged: (negationOfToggleValue) async { + setState(() { + toggleValue = negationOfToggleValue; + //start showing inProgress statu icons if toggle takes more than debounce time + _debouncer.run( + () => Future( + () { + setState(() { + executionState = ExecutionState.inProgress; + }); + }, + ), + ); + }); + final Stopwatch stopwatch = Stopwatch()..start(); + await widget.onChanged.call().onError( + (error, stackTrace) => _debouncer.cancelDebounce(), + ); + //for toggle feedback on short unsuccessful onChanged + await _feedbackOnUnsuccessfulToggle(stopwatch); + //debouncer gets canceled if onChanged takes less than debounce time + _debouncer.cancelDebounce(); + + final newValue = widget.value.call(); + setState(() { + if (toggleValue == newValue) { + if (executionState == ExecutionState.inProgress) { + executionState = ExecutionState.successful; + Future.delayed(const Duration(seconds: 2), () { + setState(() { + executionState = ExecutionState.idle; + }); + }); + } + } else { + toggleValue = !toggleValue!; + executionState = ExecutionState.idle; + } + }); + }, + ), + ), + ), + ], + ); + } + + Widget _stateIcon(enteColorScheme) { + if (executionState == ExecutionState.idle) { + return const SizedBox(width: 24); + } else if (executionState == ExecutionState.inProgress) { + return EnteLoadingWidget( + color: enteColorScheme.strokeMuted, + ); + } else if (executionState == ExecutionState.successful) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 1), + child: Icon( + Icons.check_outlined, + size: 22, + color: enteColorScheme.primary500, + ), + ); + } else { + return const SizedBox(width: 24); + } + } + + Future _feedbackOnUnsuccessfulToggle(Stopwatch stopwatch) async { + final timeElapsed = stopwatch.elapsedMilliseconds; + if (timeElapsed < 200) { + await Future.delayed( + Duration(milliseconds: 200 - timeElapsed), + ); + } + } +} diff --git a/mobile/packages/ui/lib/lifecycle_event_handler.dart b/mobile/packages/ui/lib/lifecycle_event_handler.dart new file mode 100644 index 0000000000..da5dfb3d97 --- /dev/null +++ b/mobile/packages/ui/lib/lifecycle_event_handler.dart @@ -0,0 +1,31 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +class LifecycleEventHandler extends WidgetsBindingObserver { + final AsyncCallback? resumeCallBack; + final AsyncCallback? suspendingCallBack; + + LifecycleEventHandler({ + this.resumeCallBack, + this.suspendingCallBack, + }); + + @override + Future didChangeAppLifecycleState(AppLifecycleState state) async { + switch (state) { + case AppLifecycleState.resumed: + if (resumeCallBack != null) { + await resumeCallBack!(); + } + break; + case AppLifecycleState.inactive: + case AppLifecycleState.hidden: + case AppLifecycleState.paused: + case AppLifecycleState.detached: + if (suspendingCallBack != null) { + await suspendingCallBack!(); + } + break; + } + } +} diff --git a/mobile/packages/ui/lib/models/execution_states.dart b/mobile/packages/ui/lib/models/execution_states.dart new file mode 100644 index 0000000000..9522030c14 --- /dev/null +++ b/mobile/packages/ui/lib/models/execution_states.dart @@ -0,0 +1,6 @@ +enum ExecutionState { + idle, + inProgress, + error, + successful +} diff --git a/mobile/packages/ui/lib/pages/base_home_page.dart b/mobile/packages/ui/lib/pages/base_home_page.dart new file mode 100644 index 0000000000..73f08c3db2 --- /dev/null +++ b/mobile/packages/ui/lib/pages/base_home_page.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +abstract class BaseHomePage extends StatefulWidget { + const BaseHomePage({super.key}); +} + +abstract class BaseHomePageState extends State {} diff --git a/mobile/packages/ui/lib/pages/developer_settings_page.dart b/mobile/packages/ui/lib/pages/developer_settings_page.dart new file mode 100644 index 0000000000..0b3131631d --- /dev/null +++ b/mobile/packages/ui/lib/pages/developer_settings_page.dart @@ -0,0 +1,92 @@ +import 'package:dio/dio.dart'; +import "package:ente_configuration/base_configuration.dart"; +import "package:ente_logging/logging.dart"; +import "package:ente_strings/ente_strings.dart"; +import 'package:ente_ui/components/buttons/gradient_button.dart'; +import 'package:ente_ui/utils/dialog_util.dart'; +import 'package:ente_ui/utils/toast_util.dart'; +import 'package:flutter/material.dart'; + +class DeveloperSettingsPage extends StatefulWidget { + final BaseConfiguration config; + + const DeveloperSettingsPage(this.config, {super.key}); + + @override + State createState() => _DeveloperSettingsPageState(); +} + +class _DeveloperSettingsPageState extends State { + final _logger = Logger('DeveloperSettingsPage'); + final _urlController = TextEditingController(); + + @override + void dispose() { + _urlController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + _logger.info( + "Current endpoint is: ${widget.config.getHttpEndpoint()}", + ); + return Scaffold( + appBar: AppBar( + title: Text(context.strings.developerSettings), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + TextField( + controller: _urlController, + decoration: InputDecoration( + labelText: context.strings.serverEndpoint, + hintText: widget.config.getHttpEndpoint(), + ), + autofocus: true, + ), + const SizedBox(height: 40), + GradientButton( + onTap: () async { + final String url = _urlController.text; + _logger.info("Entered endpoint: $url"); + try { + final uri = Uri.parse(url); + if ((uri.scheme == "http" || uri.scheme == "https")) { + await _ping(url); + await widget.config.setHttpEndpoint(url); + showToast(context, context.strings.endpointUpdatedMessage); + Navigator.of(context).pop(); + } else { + throw const FormatException(); + } + } catch (e) { + // ignore: unawaited_futures + showErrorDialog( + context, + context.strings.invalidEndpoint, + context.strings.invalidEndpointMessage, + ); + } + }, + text: context.strings.save, + ), + ], + ), + ), + ); + } + + Future _ping(String endpoint) async { + try { + final response = await Dio().get('$endpoint/ping'); + if (response.data['message'] != 'pong') { + throw Exception('Invalid response'); + } + } catch (e) { + throw Exception('Error occurred: $e'); + } + } +} diff --git a/mobile/packages/ui/lib/pages/log_file_viewer.dart b/mobile/packages/ui/lib/pages/log_file_viewer.dart new file mode 100644 index 0000000000..bfbfeda15a --- /dev/null +++ b/mobile/packages/ui/lib/pages/log_file_viewer.dart @@ -0,0 +1,65 @@ +import 'dart:io'; + +import 'package:ente_ui/components/loading_widget.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class LogFileViewer extends StatefulWidget { + final File file; + const LogFileViewer(this.file, {super.key}); + + @override + State createState() => _LogFileViewerState(); +} + +class _LogFileViewerState extends State { + String? _logs; + @override + void initState() { + widget.file.readAsString().then((logs) { + setState(() { + _logs = logs; + }); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0, + title: const Text("Today's logs"), + ), + body: _getBody(), + ); + } + + Widget _getBody() { + if (_logs == null) { + return const EnteLoadingWidget(); + } + return Container( + padding: const EdgeInsets.only(left: 12, top: 8, right: 12), + child: SingleChildScrollView( + child: SelectableRegion( + focusNode: FocusNode(), + selectionControls: Platform.isAndroid + ? materialTextSelectionControls + : Platform.isIOS + ? cupertinoTextSelectionControls + : desktopTextSelectionControls, + child: Text( + _logs!, + style: const TextStyle( + fontFeatures: [ + FontFeature.tabularFigures(), + ], + height: 1.2, + ), + ), + ), + ), + ); + } +} diff --git a/mobile/packages/ui/lib/pages/web_page.dart b/mobile/packages/ui/lib/pages/web_page.dart new file mode 100644 index 0000000000..e93f8b153e --- /dev/null +++ b/mobile/packages/ui/lib/pages/web_page.dart @@ -0,0 +1,43 @@ +import 'package:ente_ui/components/loading_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; + +class WebPage extends StatefulWidget { + final String title; + final String url; + + const WebPage(this.title, this.url, {super.key}); + + @override + State createState() => _WebPageState(); +} + +class _WebPageState extends State { + bool _hasLoadedPage = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + // force dark theme for appBar till website/family plans add supports for light theme + backgroundColor: const Color.fromRGBO(10, 20, 20, 1.0), + foregroundColor: Colors.white, + iconTheme: const IconThemeData(color: Colors.white), + title: Text(widget.title), + actions: [_hasLoadedPage ? Container() : const EnteLoadingWidget()], + ), + backgroundColor: Colors.black, + body: InAppWebView( + initialUrlRequest: URLRequest(url: WebUri(widget.url)), + initialSettings: InAppWebViewSettings( + transparentBackground: true, + ), + onLoadStop: (c, url) { + setState(() { + _hasLoadedPage = true; + }); + }, + ), + ); + } +} diff --git a/mobile/packages/ui/lib/theme/colors.dart b/mobile/packages/ui/lib/theme/colors.dart new file mode 100644 index 0000000000..ac4bc24138 --- /dev/null +++ b/mobile/packages/ui/lib/theme/colors.dart @@ -0,0 +1,879 @@ +import "package:ente_ui/theme/theme_config.dart"; +import 'package:flutter/material.dart'; + +/// This color scheme provides all the colors needed for a modern Flutter app, +/// including background, text, fill, stroke, and accent colors for both light +/// and dark themes. +/// +/// Apps can easily customize the primary colors using the factory constructors: +/// +/// ```dart +/// // Create a light theme with custom primary colors +/// final customLightScheme = EnteColorScheme.light( +/// primary700: Color(0xFF1976D2), +/// primary500: Color(0xFF2196F3), +/// primary400: Color(0xFF42A5F5), +/// primary300: Color(0xFF64B5F6), +/// ); +/// +/// // Create a dark theme with custom primary colors +/// final customDarkScheme = EnteColorScheme.dark( +/// primary700: Color(0xFF1976D2), +/// primary500: Color(0xFF2196F3), +/// primary400: Color(0xFF42A5F5), +/// primary300: Color(0xFF64B5F6), +/// ); +/// ``` +class EnteColorScheme extends ThemeExtension { + factory EnteColorScheme.fromApp( + EnteApp app, { + Brightness brightness = Brightness.light, + }) { + final appColors = switch (app) { + EnteApp.auth => ( + primary700: const Color.fromARGB(255, 164, 0, 182), + primary500: const Color.fromARGB(255, 204, 10, 101), + primary400: const Color.fromARGB(255, 122, 41, 193), + primary300: const Color.fromARGB(255, 152, 77, 244), + gradientButtonBgColor: const Color(0xFF531DAB), + gradientButtonBgColors: const [ + Color.fromARGB(255, 122, 41, 193), + Color.fromARGB(255, 122, 41, 193), + ], + ), + EnteApp.locker => ( + primary700: const Color.fromARGB(255, 0, 122, 255), + primary400: const Color.fromARGB(255, 52, 152, 255), + primary500: const Color.fromARGB(255, 102, 178, 255), + primary300: const Color.fromARGB(255, 153, 204, 255), + gradientButtonBgColor: const Color.fromRGBO(0, 122, 255, 1), + gradientButtonBgColors: const [ + Color.fromRGBO(0, 122, 255, 1), + Color.fromRGBO(52, 152, 255, 1), + ], + ), + }; + + return brightness == Brightness.light + ? EnteColorScheme.light( + primary700: appColors.primary700, + primary500: appColors.primary500, + primary400: appColors.primary400, + primary300: appColors.primary300, + gradientButtonBgColor: appColors.gradientButtonBgColor, + gradientButtonBgColors: appColors.gradientButtonBgColors, + ) + : EnteColorScheme.dark( + primary700: appColors.primary700, + primary500: appColors.primary500, + primary400: appColors.primary400, + primary300: appColors.primary300, + gradientButtonBgColor: appColors.gradientButtonBgColor, + gradientButtonBgColors: appColors.gradientButtonBgColors, + ); + } + + // Background Colors + final Color backgroundBase; + final Color backgroundElevated; + final Color backgroundElevated2; + + // Backdrop Colors + final Color backdropBase; + final Color backdropBaseMute; + final Color backdropFaint; + + // Text Colors + final Color textBase; + final Color textMuted; + final Color textFaint; + + // Fill Colors + final Color fillBase; + final Color fillBasePressed; + final Color fillMuted; + final Color fillFaint; + final Color fillFaintPressed; + + // Stroke Colors + final Color strokeBase; + final Color strokeMuted; + final Color strokeFaint; + final Color strokeFainter; + final Color blurStrokeBase; + final Color blurStrokeFaint; + final Color blurStrokePressed; + + // Fixed Colors + final Color primary700; + final Color primary500; + final Color primary400; + final Color primary300; + + final Color iconButtonColor; + + final Color warning700; + final Color warning500; + final Color warning400; + final Color warning800; + + final Color caution500; + + // Gradient Button + final Color gradientButtonBgColor; + final List gradientButtonBgColors; + + // Additional colors from ente_theme_data + final Color fabForegroundColor; + final Color fabBackgroundColor; + final Color boxSelectColor; + final Color boxUnSelectColor; + final Color alternativeColor; + final Color dynamicFABBackgroundColor; + final Color dynamicFABTextColor; + final Color recoveryKeyBoxColor; + final Color frostyBlurBackdropFilterColor; + final Color iconColor; + final Color bgColorForQuestions; + final Color greenText; + final Color cupertinoPickerTopColor; + final Color stepProgressUnselectedColor; + final Color gNavBackgroundColor; + final Color gNavBarActiveColor; + final Color gNavIconColor; + final Color gNavActiveIconColor; + final Color galleryThumbBackgroundColor; + final Color galleryThumbDrawColor; + final Color backupEnabledBgColor; + final Color dotsIndicatorActiveColor; + final Color dotsIndicatorInactiveColor; + final Color toastTextColor; + final Color toastBackgroundColor; + final Color subTextColor; + final Color themeSwitchInactiveIconColor; + final Color searchResultsColor; + final Color mutedTextColor; + final Color searchResultsBackgroundColor; + final Color codeCardBackgroundColor; + final Color primaryColor; + final Color surface; + + bool get isLightTheme => backgroundBase == backgroundBaseLight; + + const EnteColorScheme( + this.backgroundBase, + this.backgroundElevated, + this.backgroundElevated2, + this.backdropBase, + this.backdropBaseMute, + this.backdropFaint, + this.textBase, + this.textMuted, + this.textFaint, + this.fillBase, + this.fillBasePressed, + this.fillMuted, + this.fillFaint, + this.fillFaintPressed, + this.strokeBase, + this.strokeMuted, + this.strokeFaint, + this.strokeFainter, + this.blurStrokeBase, + this.blurStrokeFaint, + this.blurStrokePressed, + this.iconButtonColor, + this.gradientButtonBgColor, + this.gradientButtonBgColors, + this.primary700, + this.primary500, + this.primary400, + this.primary300, { + this.warning700 = _warning700, + this.warning800 = _warning800, + this.warning500 = _warning500, + this.warning400 = _warning700, + this.caution500 = _caution500, + this.fabForegroundColor = _defaultFabForegroundColor, + this.fabBackgroundColor = _defaultFabBackgroundColor, + this.boxSelectColor = _defaultBoxSelectColor, + this.boxUnSelectColor = _defaultBoxUnSelectColor, + this.alternativeColor = _defaultAlternativeColor, + this.dynamicFABBackgroundColor = _defaultDynamicFABBackgroundColor, + this.dynamicFABTextColor = _defaultDynamicFABTextColor, + this.recoveryKeyBoxColor = _defaultRecoveryKeyBoxColor, + this.frostyBlurBackdropFilterColor = _defaultFrostyBlurBackdropFilterColor, + this.iconColor = _defaultIconColor, + this.bgColorForQuestions = _defaultBgColorForQuestions, + this.greenText = _defaultGreenText, + this.cupertinoPickerTopColor = _defaultCupertinoPickerTopColor, + this.stepProgressUnselectedColor = _defaultStepProgressUnselectedColor, + this.gNavBackgroundColor = _defaultGNavBackgroundColor, + this.gNavBarActiveColor = _defaultGNavBarActiveColor, + this.gNavIconColor = _defaultGNavIconColor, + this.gNavActiveIconColor = _defaultGNavActiveIconColor, + this.galleryThumbBackgroundColor = _defaultGalleryThumbBackgroundColor, + this.galleryThumbDrawColor = _defaultGalleryThumbDrawColor, + this.backupEnabledBgColor = _defaultBackupEnabledBgColor, + this.dotsIndicatorActiveColor = _defaultDotsIndicatorActiveColor, + this.dotsIndicatorInactiveColor = _defaultDotsIndicatorInactiveColor, + this.toastTextColor = _defaultToastTextColor, + this.toastBackgroundColor = _defaultToastBackgroundColor, + this.subTextColor = _defaultSubTextColor, + this.themeSwitchInactiveIconColor = _defaultThemeSwitchInactiveIconColor, + this.searchResultsColor = _defaultSearchResultsColor, + this.mutedTextColor = _defaultMutedTextColor, + this.searchResultsBackgroundColor = _defaultSearchResultsBackgroundColor, + this.codeCardBackgroundColor = _defaultCodeCardBackgroundColor, + this.primaryColor = _defaultPrimaryColor, + this.surface = _defaultPrimaryColor, + }); + + /// Factory constructor for light theme with customizable primary colors + factory EnteColorScheme.light({ + Color? primary700, + Color? primary500, + Color? primary400, + Color? primary300, + Color? iconButtonColor, + Color? gradientButtonBgColor, + List? gradientButtonBgColors, + Color? warning700, + Color? warning500, + Color? warning400, + Color? warning800, + Color? caution500, + }) { + return EnteColorScheme( + backgroundBaseLight, + backgroundElevatedLight, + backgroundElevated2Light, + backdropBaseLight, + backdropMutedLight, + backdropFaintLight, + textBaseLight, + textMutedLight, + textFaintLight, + fillBaseLight, + fillBasePressedLight, + fillMutedLight, + fillFaintLight, + fillFaintPressedLight, + strokeBaseLight, + strokeMutedLight, + strokeFaintLight, + strokeFainterLight, + blurStrokeBaseLight, + blurStrokeFaintLight, + blurStrokePressedLight, + iconButtonColor ?? _defaultIconButtonColor, + gradientButtonBgColor ?? _defaultGradientButtonBgColor, + gradientButtonBgColors ?? _defaultGradientButtonBgColors, + primary700 ?? _defaultPrimary700, + primary500 ?? _defaultPrimary500, + primary400 ?? _defaultPrimary400, + primary300 ?? _defaultPrimary300, + alternativeColor: primary400 ?? _defaultAlternativeColor, + warning700: warning700 ?? _warning700, + warning800: warning800 ?? _warning800, + warning500: warning500 ?? _warning500, + warning400: warning400 ?? _warning700, + caution500: caution500 ?? _caution500, + ); + } + + /// Factory constructor for dark theme with customizable primary colors + factory EnteColorScheme.dark({ + Color? primary700, + Color? primary500, + Color? primary400, + Color? primary300, + Color? iconButtonColor, + Color? gradientButtonBgColor, + List? gradientButtonBgColors, + Color? warning700, + Color? warning500, + Color? warning400, + Color? warning800, + Color? caution500, + }) { + return EnteColorScheme( + backgroundBaseDark, + backgroundElevatedDark, + backgroundElevated2Dark, + backdropBaseDark, + backdropMutedDark, + backdropFaintDark, + textBaseDark, + textMutedDark, + textFaintDark, + fillBaseDark, + fillBasePressedDark, + fillMutedDark, + fillFaintDark, + fillFaintPressedDark, + strokeBaseDark, + strokeMutedDark, + strokeFaintDark, + strokeFainterDark, + blurStrokeBaseDark, + blurStrokeFaintDark, + blurStrokePressedDark, + iconButtonColor ?? _defaultIconButtonColor, + gradientButtonBgColor ?? _defaultGradientButtonBgColor, + gradientButtonBgColors ?? _defaultGradientButtonBgColors, + primary700 ?? _defaultPrimary700, + primary500 ?? _defaultPrimary500, + primary400 ?? _defaultPrimary400, + primary300 ?? _defaultPrimary300, + alternativeColor: primary400 ?? _defaultAlternativeColor, + warning700: warning700 ?? _warning700, + warning800: warning800 ?? _warning800, + warning500: warning500 ?? _warning500, + warning400: warning400 ?? _warning700, + caution500: caution500 ?? _caution500, + ); + } + + get inverseEnteTheme => null; + + @override + EnteColorScheme copyWith({ + Color? backgroundBase, + Color? backgroundElevated, + Color? backgroundElevated2, + Color? backdropBase, + Color? backdropBaseMute, + Color? backdropFaint, + Color? textBase, + Color? textMuted, + Color? textFaint, + Color? fillBase, + Color? fillBasePressed, + Color? fillMuted, + Color? fillFaint, + Color? fillFaintPressed, + Color? strokeBase, + Color? strokeMuted, + Color? strokeFaint, + Color? strokeFainter, + Color? blurStrokeBase, + Color? blurStrokeFaint, + Color? blurStrokePressed, + Color? primary700, + Color? primary500, + Color? primary400, + Color? primary300, + Color? iconButtonColor, + Color? warning700, + Color? warning500, + Color? warning400, + Color? warning800, + Color? caution500, + Color? gradientButtonBgColor, + List? gradientButtonBgColors, + Color? fabForegroundColor, + Color? fabBackgroundColor, + Color? boxSelectColor, + Color? boxUnSelectColor, + Color? alternativeColor, + Color? dynamicFABBackgroundColor, + Color? dynamicFABTextColor, + Color? recoveryKeyBoxColor, + Color? frostyBlurBackdropFilterColor, + Color? iconColor, + Color? bgColorForQuestions, + Color? greenText, + Color? cupertinoPickerTopColor, + Color? stepProgressUnselectedColor, + Color? gNavBackgroundColor, + Color? gNavBarActiveColor, + Color? gNavIconColor, + Color? gNavActiveIconColor, + Color? galleryThumbBackgroundColor, + Color? galleryThumbDrawColor, + Color? backupEnabledBgColor, + Color? dotsIndicatorActiveColor, + Color? dotsIndicatorInactiveColor, + Color? toastTextColor, + Color? toastBackgroundColor, + Color? subTextColor, + Color? themeSwitchInactiveIconColor, + Color? searchResultsColor, + Color? mutedTextColor, + Color? searchResultsBackgroundColor, + Color? codeCardBackgroundColor, + Color? primaryColor, + }) { + return EnteColorScheme( + backgroundBase ?? this.backgroundBase, + backgroundElevated ?? this.backgroundElevated, + backgroundElevated2 ?? this.backgroundElevated2, + backdropBase ?? this.backdropBase, + backdropBaseMute ?? this.backdropBaseMute, + backdropFaint ?? this.backdropFaint, + textBase ?? this.textBase, + textMuted ?? this.textMuted, + textFaint ?? this.textFaint, + fillBase ?? this.fillBase, + fillBasePressed ?? this.fillBasePressed, + fillMuted ?? this.fillMuted, + fillFaint ?? this.fillFaint, + fillFaintPressed ?? this.fillFaintPressed, + strokeBase ?? this.strokeBase, + strokeMuted ?? this.strokeMuted, + strokeFaint ?? this.strokeFaint, + strokeFainter ?? this.strokeFainter, + blurStrokeBase ?? this.blurStrokeBase, + blurStrokeFaint ?? this.blurStrokeFaint, + blurStrokePressed ?? this.blurStrokePressed, + iconButtonColor ?? this.iconButtonColor, + gradientButtonBgColor ?? this.gradientButtonBgColor, + gradientButtonBgColors ?? this.gradientButtonBgColors, + primary700 ?? this.primary700, + primary500 ?? this.primary500, + primary400 ?? this.primary400, + primary300 ?? this.primary300, + warning700: warning700 ?? this.warning700, + warning800: warning800 ?? this.warning800, + warning500: warning500 ?? this.warning500, + warning400: warning400 ?? this.warning400, + caution500: caution500 ?? this.caution500, + fabForegroundColor: fabForegroundColor ?? this.fabForegroundColor, + fabBackgroundColor: fabBackgroundColor ?? this.fabBackgroundColor, + boxSelectColor: boxSelectColor ?? this.boxSelectColor, + boxUnSelectColor: boxUnSelectColor ?? this.boxUnSelectColor, + alternativeColor: alternativeColor ?? this.alternativeColor, + dynamicFABBackgroundColor: + dynamicFABBackgroundColor ?? this.dynamicFABBackgroundColor, + dynamicFABTextColor: dynamicFABTextColor ?? this.dynamicFABTextColor, + recoveryKeyBoxColor: recoveryKeyBoxColor ?? this.recoveryKeyBoxColor, + frostyBlurBackdropFilterColor: + frostyBlurBackdropFilterColor ?? this.frostyBlurBackdropFilterColor, + iconColor: iconColor ?? this.iconColor, + bgColorForQuestions: bgColorForQuestions ?? this.bgColorForQuestions, + greenText: greenText ?? this.greenText, + cupertinoPickerTopColor: + cupertinoPickerTopColor ?? this.cupertinoPickerTopColor, + stepProgressUnselectedColor: + stepProgressUnselectedColor ?? this.stepProgressUnselectedColor, + gNavBackgroundColor: gNavBackgroundColor ?? this.gNavBackgroundColor, + gNavBarActiveColor: gNavBarActiveColor ?? this.gNavBarActiveColor, + gNavIconColor: gNavIconColor ?? this.gNavIconColor, + gNavActiveIconColor: gNavActiveIconColor ?? this.gNavActiveIconColor, + galleryThumbBackgroundColor: + galleryThumbBackgroundColor ?? this.galleryThumbBackgroundColor, + galleryThumbDrawColor: + galleryThumbDrawColor ?? this.galleryThumbDrawColor, + backupEnabledBgColor: backupEnabledBgColor ?? this.backupEnabledBgColor, + dotsIndicatorActiveColor: + dotsIndicatorActiveColor ?? this.dotsIndicatorActiveColor, + dotsIndicatorInactiveColor: + dotsIndicatorInactiveColor ?? this.dotsIndicatorInactiveColor, + toastTextColor: toastTextColor ?? this.toastTextColor, + toastBackgroundColor: toastBackgroundColor ?? this.toastBackgroundColor, + subTextColor: subTextColor ?? this.subTextColor, + themeSwitchInactiveIconColor: + themeSwitchInactiveIconColor ?? this.themeSwitchInactiveIconColor, + searchResultsColor: searchResultsColor ?? this.searchResultsColor, + mutedTextColor: mutedTextColor ?? this.mutedTextColor, + searchResultsBackgroundColor: + searchResultsBackgroundColor ?? this.searchResultsBackgroundColor, + codeCardBackgroundColor: + codeCardBackgroundColor ?? this.codeCardBackgroundColor, + primaryColor: primaryColor ?? this.primaryColor, + ); + } + + @override + EnteColorScheme lerp(ThemeExtension? other, double t) { + if (other is! EnteColorScheme) { + return this; + } + + return EnteColorScheme( + Color.lerp(backgroundBase, other.backgroundBase, t)!, + Color.lerp(backgroundElevated, other.backgroundElevated, t)!, + Color.lerp(backgroundElevated2, other.backgroundElevated2, t)!, + Color.lerp(backdropBase, other.backdropBase, t)!, + Color.lerp(backdropBaseMute, other.backdropBaseMute, t)!, + Color.lerp(backdropFaint, other.backdropFaint, t)!, + Color.lerp(textBase, other.textBase, t)!, + Color.lerp(textMuted, other.textMuted, t)!, + Color.lerp(textFaint, other.textFaint, t)!, + Color.lerp(fillBase, other.fillBase, t)!, + Color.lerp(fillBasePressed, other.fillBasePressed, t)!, + Color.lerp(fillMuted, other.fillMuted, t)!, + Color.lerp(fillFaint, other.fillFaint, t)!, + Color.lerp(fillFaintPressed, other.fillFaintPressed, t)!, + Color.lerp(strokeBase, other.strokeBase, t)!, + Color.lerp(strokeMuted, other.strokeMuted, t)!, + Color.lerp(strokeFaint, other.strokeFaint, t)!, + Color.lerp(strokeFainter, other.strokeFainter, t)!, + Color.lerp(blurStrokeBase, other.blurStrokeBase, t)!, + Color.lerp(blurStrokeFaint, other.blurStrokeFaint, t)!, + Color.lerp(blurStrokePressed, other.blurStrokePressed, t)!, + Color.lerp(iconButtonColor, other.iconButtonColor, t)!, + Color.lerp(gradientButtonBgColor, other.gradientButtonBgColor, t)!, + _lerpColorList(gradientButtonBgColors, other.gradientButtonBgColors, t), + Color.lerp(primary700, other.primary700, t)!, + Color.lerp(primary500, other.primary500, t)!, + Color.lerp(primary400, other.primary400, t)!, + Color.lerp(primary300, other.primary300, t)!, + warning700: Color.lerp(warning700, other.warning700, t)!, + warning800: Color.lerp(warning800, other.warning800, t)!, + warning500: Color.lerp(warning500, other.warning500, t)!, + warning400: Color.lerp(warning400, other.warning400, t)!, + caution500: Color.lerp(caution500, other.caution500, t)!, + ); + } + + /// Helper method to lerp between two color lists + List _lerpColorList(List a, List b, double t) { + if (a.length != b.length) { + return t < 0.5 ? a : b; + } + return List.generate( + a.length, + (index) => Color.lerp(a[index], b[index], t)!, + ); + } +} + +const EnteColorScheme lightScheme = EnteColorScheme( + backgroundBaseLight, + backgroundElevatedLight, + backgroundElevated2Light, + backdropBaseLight, + backdropMutedLight, + backdropFaintLight, + textBaseLight, + textMutedLight, + textFaintLight, + fillBaseLight, + fillBasePressedLight, + fillMutedLight, + fillFaintLight, + fillFaintPressedLight, + strokeBaseLight, + strokeMutedLight, + strokeFaintLight, + strokeFainterLight, + blurStrokeBaseLight, + blurStrokeFaintLight, + blurStrokePressedLight, + _defaultIconButtonColor, + _defaultGradientButtonBgColor, + _defaultGradientButtonBgColors, + _defaultPrimary700, + _defaultPrimary500, + _defaultPrimary400, + _defaultPrimary300, +); + +const EnteColorScheme darkScheme = EnteColorScheme( + backgroundBaseDark, + backgroundElevatedDark, + backgroundElevated2Dark, + backdropBaseDark, + backdropMutedDark, + backdropFaintDark, + textBaseDark, + textMutedDark, + textFaintDark, + fillBaseDark, + fillBasePressedDark, + fillMutedDark, + fillFaintDark, + fillFaintPressedDark, + strokeBaseDark, + strokeMutedDark, + strokeFaintDark, + strokeFainterDark, + blurStrokeBaseDark, + blurStrokeFaintDark, + blurStrokePressedDark, + _defaultIconButtonColor, + _defaultGradientButtonBgColor, + _defaultGradientButtonBgColors, + _defaultPrimary700, + _defaultPrimary500, + _defaultPrimary400, + _defaultPrimary300, +); + +// Background Colors +const Color backgroundBaseLight = Color.fromRGBO(255, 255, 255, 1); +const Color backgroundElevatedLight = Color.fromRGBO(255, 255, 255, 1); +const Color backgroundElevated2Light = Color.fromRGBO(251, 251, 251, 1); + +const Color backgroundBaseDark = Color.fromRGBO(0, 0, 0, 1); +const Color backgroundElevatedDark = Color.fromRGBO(27, 27, 27, 1); +const Color backgroundElevated2Dark = Color.fromRGBO(37, 37, 37, 1); + +// Backdrop Colors +const Color backdropBaseLight = Color.fromRGBO(255, 255, 255, 0.92); +const Color backdropMutedLight = Color.fromRGBO(255, 255, 255, 0.75); +const Color backdropFaintLight = Color.fromRGBO(255, 255, 255, 0.30); + +const Color backdropBaseDark = Color.fromRGBO(0, 0, 0, 0.90); +const Color backdropMutedDark = Color.fromRGBO(0, 0, 0, 0.65); +const Color backdropFaintDark = Color.fromRGBO(0, 0, 0, 0.20); + +// Text Colors +const Color textBaseLight = Color.fromRGBO(0, 0, 0, 1); +const Color textMutedLight = Color.fromRGBO(0, 0, 0, 0.6); +const Color textFaintLight = Color.fromRGBO(0, 0, 0, 0.5); + +const Color textBaseDark = Color.fromRGBO(255, 255, 255, 1); +const Color textMutedDark = Color.fromRGBO(255, 255, 255, 0.7); +const Color textFaintDark = Color.fromRGBO(255, 255, 255, 0.5); + +// Fill Colors +const Color fillBaseLight = Color.fromRGBO(0, 0, 0, 1); +const Color fillBasePressedLight = Color.fromRGBO(0, 0, 0, 0.87); +const Color fillMutedLight = Color.fromRGBO(0, 0, 0, 0.12); +const Color fillFaintLight = Color.fromRGBO(0, 0, 0, 0.04); +const Color fillFaintPressedLight = Color.fromRGBO(0, 0, 0, 0.08); + +const Color fillBaseDark = Color.fromRGBO(255, 255, 255, 1); +const Color fillBasePressedDark = Color.fromRGBO(255, 255, 255, 0.9); +const Color fillMutedDark = Color.fromRGBO(255, 255, 255, 0.16); +const Color fillFaintDark = Color.fromRGBO(255, 255, 255, 0.12); +const Color fillFaintPressedDark = Color.fromRGBO(255, 255, 255, 0.06); + +// Stroke Colors +const Color strokeBaseLight = Color.fromRGBO(0, 0, 0, 1); +const Color strokeMutedLight = Color.fromRGBO(0, 0, 0, 0.24); +const Color strokeFaintLight = Color.fromRGBO(0, 0, 0, 0.04); +const Color strokeFainterLight = Color.fromRGBO(0, 0, 0, 0.06); +const Color blurStrokeBaseLight = Color.fromRGBO(0, 0, 0, 0.65); +const Color blurStrokeFaintLight = Color.fromRGBO(0, 0, 0, 0.08); +const Color blurStrokePressedLight = Color.fromRGBO(0, 0, 0, 0.50); + +const Color strokeBaseDark = Color.fromRGBO(255, 255, 255, 1); +const Color strokeMutedDark = Color.fromRGBO(255, 255, 255, 0.24); +const Color strokeFaintDark = Color.fromRGBO(255, 255, 255, 0.16); +const Color strokeFainterDark = Color.fromRGBO(255, 255, 255, 0.08); +const Color blurStrokeBaseDark = Color.fromRGBO(255, 255, 255, 0.90); +const Color blurStrokeFaintDark = Color.fromRGBO(255, 255, 255, 0.06); +const Color blurStrokePressedDark = Color.fromRGBO(255, 255, 255, 0.50); + +// Default Primary Colors +const Color _defaultPrimary700 = Color.fromRGBO(0, 122, 255, 1); +const Color _defaultPrimary500 = Color.fromRGBO(52, 152, 255, 1); +const Color _defaultPrimary400 = Color.fromRGBO(102, 178, 255, 1); +const Color _defaultPrimary300 = Color.fromRGBO(153, 204, 255, 1); + +// Default Gradient Colors +const Color _defaultGradientButtonBgColor = Color.fromRGBO(0, 122, 255, 1); +const List _defaultGradientButtonBgColors = [ + Color.fromRGBO(0, 122, 255, 1), + Color.fromRGBO(52, 152, 255, 1), +]; + +// Default Icon Button Color +const Color _defaultIconButtonColor = Color.fromRGBO(0, 122, 255, 1); + +// Warning Colors +const Color _warning700 = Color.fromRGBO(245, 52, 52, 1); +const Color _warning500 = Color.fromRGBO(255, 101, 101, 1); +const Color _warning800 = Color(0xFFF53434); +const Color warning500 = Color.fromRGBO(255, 101, 101, 1); +// ignore: unused_element +const Color _warning400 = Color.fromRGBO(255, 111, 111, 1); + +// Caution Colors +const Color _caution500 = Color.fromRGBO(255, 194, 71, 1); + +// Additional default colors from ente_theme_data +const Color _defaultPrimaryColor = Color(0xFF9610D6); + +// FAB Colors - based on brightness-dependent logic from ente_theme_data +const Color _defaultFabForegroundColor = Color.fromRGBO(255, 255, 255, 1); +const Color _defaultFabBackgroundColor = Color.fromRGBO(40, 40, 40, 1); + +// Box selection colors +const Color _defaultBoxSelectColor = Color.fromRGBO(67, 186, 108, 1); +const Color _defaultBoxUnSelectColor = Color.fromRGBO(240, 240, 240, 1); + +// Alternative color +const Color _defaultAlternativeColor = Color.fromARGB(255, 152, 77, 244); + +// Dynamic FAB colors +const Color _defaultDynamicFABBackgroundColor = Color.fromRGBO(0, 0, 0, 1); +const Color _defaultDynamicFABTextColor = Color.fromRGBO(255, 255, 255, 1); + +// Recovery key box color +const Color _defaultRecoveryKeyBoxColor = Color.fromARGB(51, 150, 0, 220); + +// Frosty blur backdrop filter color +const Color _defaultFrostyBlurBackdropFilterColor = + Color.fromRGBO(238, 238, 238, 0.5); + +// Default Icon Color +const Color _defaultIconColor = Color.fromRGBO(0, 0, 0, 0.75); + +// Default Background Color For Questions +const Color _defaultBgColorForQuestions = Color.fromRGBO(255, 255, 255, 1); + +// Default Green Text Color +const Color _defaultGreenText = Color.fromARGB(255, 40, 190, 113); + +// Default Cupertino Picker Top Color +const Color _defaultCupertinoPickerTopColor = + Color.fromARGB(255, 238, 238, 238); + +// Default Step Progress Unselected Color +const Color _defaultStepProgressUnselectedColor = + Color.fromRGBO(196, 196, 196, 0.6); + +// Default Navigation Colors +const Color _defaultGNavBackgroundColor = Color.fromRGBO(196, 196, 196, 0.6); +const Color _defaultGNavBarActiveColor = Color.fromRGBO(255, 255, 255, 0.6); +const Color _defaultGNavIconColor = Color.fromRGBO(0, 0, 0, 0.8); +const Color _defaultGNavActiveIconColor = Color.fromRGBO(0, 0, 0, 0.8); + +// Default Gallery Thumb Colors +const Color _defaultGalleryThumbBackgroundColor = + Color.fromRGBO(240, 240, 240, 1); +const Color _defaultGalleryThumbDrawColor = Color.fromRGBO(0, 0, 0, 0.8); + +// Default Backup Enabled Background Color +const Color _defaultBackupEnabledBgColor = Color.fromRGBO(230, 230, 230, 0.95); + +// Default Dots Indicator Colors +const Color _defaultDotsIndicatorActiveColor = Color.fromRGBO(0, 0, 0, 0.5); +const Color _defaultDotsIndicatorInactiveColor = Color.fromRGBO(0, 0, 0, 0.12); + +// Default Toast Colors +const Color _defaultToastTextColor = Color.fromRGBO(255, 255, 255, 1); +const Color _defaultToastBackgroundColor = Color.fromRGBO(24, 24, 24, 0.95); + +// Default Sub Text Color +const Color _defaultSubTextColor = Color.fromRGBO(180, 180, 180, 1); + +// Default Theme Switch Inactive Icon Color +const Color _defaultThemeSwitchInactiveIconColor = Color.fromRGBO(0, 0, 0, 0.5); + +// Default Search Results Colors +const Color _defaultSearchResultsColor = Color.fromRGBO(245, 245, 245, 1.0); +const Color _defaultMutedTextColor = Color.fromRGBO(80, 80, 80, 1); +const Color _defaultSearchResultsBackgroundColor = + Color.fromRGBO(0, 0, 0, 0.32); + +// Default Code Card Background Color +const Color _defaultCodeCardBackgroundColor = Color.fromRGBO(246, 246, 246, 1); + +/// Utility class to help apps create custom color schemes with their brand colors. +/// +/// This class provides convenient methods to generate complete color schemes +/// from a base primary color, automatically calculating the different shades +/// and variations needed for the app. +class ColorSchemeBuilder { + /// Creates light and dark color schemes from a single primary color. + /// + /// The primary color is used as the base (primary500), and other shades + /// are automatically calculated: + /// - primary700: Darker shade for emphasis + /// - primary400: Lighter shade for secondary elements + /// - primary300: Lightest shade for subtle accents + /// + /// Example: + /// ```dart + /// final schemes = ColorSchemeBuilder.fromPrimaryColor( + /// Color(0xFF2196F3), // Material Blue + /// ); + /// final lightScheme = schemes.light; + /// final darkScheme = schemes.dark; + /// ``` + static ({EnteColorScheme light, EnteColorScheme dark}) fromPrimaryColor( + Color primaryColor, + ) { + // Calculate different shades of the primary color + final HSLColor hsl = HSLColor.fromColor(primaryColor); + + final primary700 = + hsl.withLightness((hsl.lightness - 0.1).clamp(0.0, 1.0)).toColor(); + final primary500 = primaryColor; + final primary400 = + hsl.withLightness((hsl.lightness + 0.1).clamp(0.0, 1.0)).toColor(); + final primary300 = + hsl.withLightness((hsl.lightness + 0.2).clamp(0.0, 1.0)).toColor(); + + // Create gradient colors from the primary color + final gradientColors = [primary700, primary500]; + + final lightScheme = EnteColorScheme.light( + primary700: primary700, + primary500: primary500, + primary400: primary400, + primary300: primary300, + iconButtonColor: primary500, + gradientButtonBgColor: primary500, + gradientButtonBgColors: gradientColors, + ); + + final darkScheme = EnteColorScheme.dark( + primary700: primary700, + primary500: primary500, + primary400: primary400, + primary300: primary300, + iconButtonColor: primary500, + gradientButtonBgColor: primary500, + gradientButtonBgColors: gradientColors, + ); + + return (light: lightScheme, dark: darkScheme); + } + + /// Creates light and dark color schemes with fully custom primary colors. + /// + /// Use this method when you need complete control over all primary color shades. + /// + /// Example: + /// ```dart + /// final schemes = ColorSchemeBuilder.fromCustomColors( + /// primary700: Color(0xFF1565C0), + /// primary500: Color(0xFF2196F3), + /// primary400: Color(0xFF42A5F5), + /// primary300: Color(0xFF90CAF9), + /// ); + /// ``` + static ({EnteColorScheme light, EnteColorScheme dark}) fromCustomColors({ + required Color primary700, + required Color primary500, + required Color primary400, + required Color primary300, + Color? iconButtonColor, + Color? gradientButtonBgColor, + List? gradientButtonBgColors, + }) { + final effectiveIconButtonColor = iconButtonColor ?? primary500; + final effectiveGradientBgColor = gradientButtonBgColor ?? primary500; + final effectiveGradientColors = + gradientButtonBgColors ?? [primary700, primary500]; + + final lightScheme = EnteColorScheme.light( + primary700: primary700, + primary500: primary500, + primary400: primary400, + primary300: primary300, + iconButtonColor: effectiveIconButtonColor, + gradientButtonBgColor: effectiveGradientBgColor, + gradientButtonBgColors: effectiveGradientColors, + ); + + final darkScheme = EnteColorScheme.dark( + primary700: primary700, + primary500: primary500, + primary400: primary400, + primary300: primary300, + iconButtonColor: effectiveIconButtonColor, + gradientButtonBgColor: effectiveGradientBgColor, + gradientButtonBgColors: effectiveGradientColors, + ); + + return (light: lightScheme, dark: darkScheme); + } +} diff --git a/mobile/packages/ui/lib/theme/effects.dart b/mobile/packages/ui/lib/theme/effects.dart new file mode 100644 index 0000000000..f97ec06723 --- /dev/null +++ b/mobile/packages/ui/lib/theme/effects.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +const blurBase = 96.0; +const blurMuted = 48.0; +const blurFaint = 24.0; + +List shadowFloatLight = const [ + BoxShadow(blurRadius: 10, color: Color.fromRGBO(0, 0, 0, 0.25)), +]; + +List shadowFloatFaintLight = const [ + BoxShadow(blurRadius: 10, color: Color.fromRGBO(0, 0, 0, 0.12)), +]; + +List shadowFloatFaintestLight = const [ + BoxShadow(blurRadius: 1, color: Color.fromRGBO(0, 0, 0, 0.25)), +]; + +List shadowMenuLight = const [ + BoxShadow(blurRadius: 6, color: Color.fromRGBO(0, 0, 0, 0.16)), + BoxShadow( + blurRadius: 6, + color: Color.fromRGBO(0, 0, 0, 0.12), + offset: Offset(0, 3), + ), +]; + +List shadowButtonLight = const [ + BoxShadow( + blurRadius: 4, + color: Color.fromRGBO(0, 0, 0, 0.25), + offset: Offset(0, 4), + ), +]; + +List shadowFloatDark = const [ + BoxShadow( + blurRadius: 12, + color: Color.fromRGBO(0, 0, 0, 0.75), + offset: Offset(0, 2), + ), +]; + +List shadowMenuDark = const [ + BoxShadow(blurRadius: 6, color: Color.fromRGBO(0, 0, 0, 0.50)), + BoxShadow( + blurRadius: 6, + color: Color.fromRGBO(0, 0, 0, 0.25), + offset: Offset(0, 3), + ), +]; + +List shadowButtonDark = const [ + BoxShadow( + blurRadius: 4, + color: Color.fromRGBO(0, 0, 0, 0.75), + offset: Offset(0, 4), + ), +]; diff --git a/mobile/packages/ui/lib/theme/ente_theme.dart b/mobile/packages/ui/lib/theme/ente_theme.dart new file mode 100644 index 0000000000..3fb07b8022 --- /dev/null +++ b/mobile/packages/ui/lib/theme/ente_theme.dart @@ -0,0 +1,86 @@ +import 'package:ente_ui/theme/colors.dart'; +import 'package:ente_ui/theme/effects.dart'; +import "package:ente_ui/theme/ente_theme_data.dart"; +import 'package:ente_ui/theme/text_style.dart'; +import "package:ente_ui/theme/theme_config.dart"; +import 'package:flutter/material.dart'; + +class EnteTheme { + final EnteTextTheme textTheme; + final EnteColorScheme colorScheme; + final List shadowFloat; + final List shadowMenu; + final List shadowButton; + + const EnteTheme( + this.textTheme, + this.colorScheme, { + required this.shadowFloat, + required this.shadowMenu, + required this.shadowButton, + }); + + bool isDark(BuildContext context) { + return Theme.of(context).brightness == Brightness.dark; + } +} + +EnteTheme lightTheme = EnteTheme( + lightTextTheme, + lightScheme, + shadowFloat: shadowFloatLight, + shadowMenu: shadowMenuLight, + shadowButton: shadowButtonLight, +); + +EnteTheme darkTheme = EnteTheme( + darkTextTheme, + darkScheme, + shadowFloat: shadowFloatDark, + shadowMenu: shadowMenuDark, + shadowButton: shadowButtonDark, +); + +EnteColorScheme getEnteColorScheme( + BuildContext context, { + bool inverse = false, +}) { + final colorScheme = Theme.of(context).extension(); + if (colorScheme != null) { + return colorScheme; + } + + final brightness = Theme.of(context).brightness; + + return EnteColorScheme.fromApp( + AppThemeConfig.currentApp, + brightness: brightness, + ); +} + +EnteTextTheme getEnteTextTheme( + BuildContext context, { + bool inverse = false, +}) { + return inverse + ? Theme.of(context).colorScheme.inverseEnteTheme.textTheme + : Theme.of(context).colorScheme.enteTheme.textTheme; +} + +/// Get theme-aware shadow for floating elements (dialogs, modals, etc.) +List getEnteShadowFloat(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return isDark ? shadowFloatDark : shadowFloatLight; +} + +/// Get theme-aware shadow for menu elements +List getEnteShadowMenu(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return isDark ? shadowMenuDark : shadowMenuLight; +} + +/// Get theme-aware shadow for button elements +List getEnteShadowButton(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return isDark ? shadowButtonDark : shadowButtonLight; +} diff --git a/mobile/packages/ui/lib/theme/ente_theme_data.dart b/mobile/packages/ui/lib/theme/ente_theme_data.dart new file mode 100644 index 0000000000..0151407250 --- /dev/null +++ b/mobile/packages/ui/lib/theme/ente_theme_data.dart @@ -0,0 +1,542 @@ +import 'package:ente_ui/theme/colors.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; + +final lightThemeData = ThemeData( + fontFamily: 'Inter', + brightness: Brightness.light, + dividerTheme: const DividerThemeData( + color: Colors.black12, + ), + hintColor: const Color.fromRGBO(158, 158, 158, 1), + primaryColor: const Color.fromRGBO(255, 110, 64, 1), + primaryColorLight: const Color.fromRGBO(0, 0, 0, 0.541), + iconTheme: const IconThemeData(color: Colors.black), + primaryIconTheme: + const IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0), + buttonTheme: const ButtonThemeData(), + outlinedButtonTheme: buildOutlinedButtonThemeData( + bgDisabled: const Color.fromRGBO(158, 158, 158, 1), + bgEnabled: const Color.fromRGBO(0, 0, 0, 1), + fgDisabled: const Color.fromRGBO(255, 255, 255, 1), + fgEnabled: const Color.fromRGBO(255, 255, 255, 1), + ), + elevatedButtonTheme: buildElevatedButtonThemeData( + onPrimary: const Color.fromRGBO(255, 255, 255, 1), + primary: const Color.fromRGBO(0, 0, 0, 1), + ), + scaffoldBackgroundColor: const Color.fromRGBO(255, 255, 255, 1), + appBarTheme: const AppBarTheme().copyWith( + backgroundColor: Colors.white, + foregroundColor: Colors.black, + iconTheme: const IconThemeData(color: Colors.black), + elevation: 0, + ), + //https://api.flutter.dev/flutter/material/TextTheme-class.html + textTheme: _buildTextTheme(const Color.fromRGBO(0, 0, 0, 1)), + primaryTextTheme: const TextTheme().copyWith( + bodyMedium: const TextStyle(color: Colors.yellow), + bodyLarge: const TextStyle(color: Colors.orange), + ), + cardColor: const Color.fromRGBO(250, 250, 250, 1.0), + dialogTheme: const DialogThemeData().copyWith( + backgroundColor: const Color.fromRGBO(250, 250, 250, 1.0), // + titleTextStyle: const TextStyle( + color: Colors.black, + fontSize: 24, + fontWeight: FontWeight.w600, + ), + contentTextStyle: const TextStyle( + fontFamily: 'Inter-Medium', + color: Colors.black, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + checkboxTheme: CheckboxThemeData( + side: const BorderSide( + color: Colors.black, + width: 2, + ), + fillColor: WidgetStateProperty.resolveWith((states) { + return states.contains(WidgetState.selected) + ? const Color.fromRGBO(0, 0, 0, 1) + : const Color.fromRGBO(255, 255, 255, 1); + }), + checkColor: WidgetStateProperty.resolveWith((states) { + return states.contains(WidgetState.selected) + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(0, 0, 0, 1); + }), + ), + + radioTheme: RadioThemeData( + fillColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + ), + switchTheme: SwitchThemeData( + thumbColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + trackColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + ), + colorScheme: const ColorScheme.light( + primary: Colors.black, + secondary: Color.fromARGB(255, 163, 163, 163), + ).copyWith(surface: const Color.fromRGBO(255, 255, 255, 1)), +); + +final darkThemeData = ThemeData( + fontFamily: 'Inter', + brightness: Brightness.dark, + dividerTheme: const DividerThemeData( + color: Colors.white12, + ), + primaryColorLight: const Color.fromRGBO(255, 255, 255, 0.702), + iconTheme: const IconThemeData(color: Colors.white), + primaryIconTheme: + const IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0), + hintColor: const Color.fromRGBO(158, 158, 158, 1), + buttonTheme: const ButtonThemeData().copyWith( + buttonColor: const Color.fromRGBO(45, 194, 98, 1.0), + height: 56, + ), + textTheme: _buildTextTheme(const Color.fromRGBO(255, 255, 255, 1)), + outlinedButtonTheme: buildOutlinedButtonThemeData( + bgDisabled: const Color.fromRGBO(158, 158, 158, 1), + bgEnabled: const Color.fromRGBO(255, 255, 255, 1), + fgDisabled: const Color.fromRGBO(255, 255, 255, 1), + fgEnabled: const Color.fromRGBO(0, 0, 0, 1), + ), + elevatedButtonTheme: buildElevatedButtonThemeData( + onPrimary: const Color.fromRGBO(0, 0, 0, 1), + primary: const Color.fromRGBO(255, 255, 255, 1), + ), + scaffoldBackgroundColor: const Color.fromRGBO(0, 0, 0, 1), + appBarTheme: const AppBarTheme().copyWith( + color: Colors.black, + elevation: 0, + ), + cardColor: const Color.fromRGBO(10, 15, 15, 1.0), + dialogTheme: const DialogThemeData().copyWith( + backgroundColor: const Color.fromRGBO(15, 15, 15, 1.0), + titleTextStyle: const TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.w600, + ), + contentTextStyle: const TextStyle( + fontFamily: 'Inter-Medium', + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + checkboxTheme: CheckboxThemeData( + side: const BorderSide( + color: Colors.grey, + width: 2, + ), + fillColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return const Color.fromRGBO(158, 158, 158, 1); + } else { + return const Color.fromRGBO(0, 0, 0, 1); + } + }), + checkColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return const Color.fromRGBO(0, 0, 0, 1); + } else { + return const Color.fromRGBO(158, 158, 158, 1); + } + }), + ), + radioTheme: RadioThemeData( + fillColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + ), + switchTheme: SwitchThemeData( + thumbColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + trackColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + ), + colorScheme: const ColorScheme.dark(primary: Colors.white) + .copyWith(surface: const Color.fromRGBO(0, 0, 0, 1)), +); + +TextTheme _buildTextTheme(Color textColor) { + return const TextTheme().copyWith( + headlineMedium: TextStyle( + color: textColor, + fontSize: 32, + fontWeight: FontWeight.w600, + fontFamily: 'Inter', + ), + headlineSmall: TextStyle( + color: textColor, + fontSize: 24, + fontWeight: FontWeight.w600, + fontFamily: 'Inter', + ), + titleLarge: TextStyle( + color: textColor, + fontSize: 18, + fontFamily: 'Inter', + fontWeight: FontWeight.w600, + ), + titleMedium: TextStyle( + color: textColor, + fontFamily: 'Inter', + fontSize: 16, + fontWeight: FontWeight.w500, + ), + titleSmall: TextStyle( + color: textColor, + fontFamily: 'Inter', + fontSize: 14, + fontWeight: FontWeight.w500, + ), + bodyLarge: TextStyle( + fontFamily: 'Inter', + color: textColor, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + bodyMedium: TextStyle( + fontFamily: 'Inter', + color: textColor, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + bodySmall: TextStyle( + color: textColor.withOpacity(0.4), + fontSize: 10, + fontWeight: FontWeight.w500, + ), + labelSmall: TextStyle( + fontFamily: 'Inter', + color: textColor, + fontSize: 14, + fontWeight: FontWeight.w500, + decoration: TextDecoration.underline, + ), + ); +} + +extension CustomColorScheme on ColorScheme { + Color get defaultBackgroundColor => + brightness == Brightness.light ? backgroundBaseLight : backgroundBaseDark; + + Color get inverseBackgroundColor => + brightness != Brightness.light ? backgroundBaseLight : backgroundBaseDark; + + Color get fabForegroundColor => brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(40, 40, 40, 1); + + Color get fabBackgroundColor => brightness != Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(40, 40, 40, 1); + + Color get defaultTextColor => + brightness == Brightness.light ? textBaseLight : textBaseDark; + + Color get inverseTextColor => + brightness != Brightness.light ? textBaseLight : textBaseDark; + + Color get boxSelectColor => brightness == Brightness.light + ? const Color.fromRGBO(67, 186, 108, 1) + : const Color.fromRGBO(16, 32, 32, 1); + + Color get boxUnSelectColor => brightness == Brightness.light + ? const Color.fromRGBO(240, 240, 240, 1) + : const Color.fromRGBO(8, 18, 18, 0.4); + + Color get alternativeColor => const Color.fromARGB(255, 152, 77, 244); + + Color get dynamicFABBackgroundColor => brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 1) + : const Color.fromRGBO(48, 48, 48, 1); + + Color get dynamicFABTextColor => + const Color.fromRGBO(255, 255, 255, 1); //same for both themes + + // todo: use brightness == Brightness.light for changing color for dark/light + // theme + ButtonStyle? get optionalActionButtonStyle => buildElevatedButtonThemeData( + onPrimary: const Color(0xFF777777), + primary: const Color(0xFFF0F0F0), + elevation: 0, + ).style; + + Color get recoveryKeyBoxColor => brightness == Brightness.light + ? const Color.fromARGB(51, 150, 0, 220) + : const Color.fromARGB(255, 174, 56, 247); + + Color get frostyBlurBackdropFilterColor => brightness == Brightness.light + ? const Color.fromRGBO(238, 238, 238, 0.5) + : const Color.fromRGBO(48, 48, 48, 0.5); + + Color get iconColor => brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 1).withOpacity(0.75) + : const Color.fromRGBO(255, 255, 255, 1); + + Color get bgColorForQuestions => brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(10, 15, 15, 1.0); + + Color get greenText => const Color.fromARGB(255, 40, 190, 113); + + Color get cupertinoPickerTopColor => brightness == Brightness.light + ? const Color.fromARGB(255, 238, 238, 238) + : const Color.fromRGBO(255, 255, 255, 1).withOpacity(0.1); + + Color get stepProgressUnselectedColor => brightness == Brightness.light + ? const Color.fromRGBO(196, 196, 196, 0.6) + : const Color.fromRGBO(255, 255, 255, 0.7); + + Color get gNavBackgroundColor => brightness == Brightness.light + ? const Color.fromRGBO(196, 196, 196, 0.6) + : const Color.fromRGBO(40, 40, 40, 0.6); + + Color get gNavBarActiveColor => brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 0.6) + : const Color.fromRGBO(255, 255, 255, 0.9); + + Color get gNavIconColor => brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 0.8) + : const Color.fromRGBO(255, 255, 255, 0.8); + + Color get gNavActiveIconColor => brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 0.8) + : const Color.fromRGBO(0, 0, 0, 0.8); + + Color get galleryThumbBackgroundColor => brightness == Brightness.light + ? const Color.fromRGBO(240, 240, 240, 1) + : const Color.fromRGBO(20, 20, 20, 1); + + Color get galleryThumbDrawColor => brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 1).withOpacity(0.8) + : const Color.fromRGBO(255, 255, 255, 1).withOpacity(0.5); + + Color get backupEnabledBgColor => brightness == Brightness.light + ? const Color.fromRGBO(230, 230, 230, 0.95) + : const Color.fromRGBO(10, 40, 40, 0.3); + + Color get dotsIndicatorActiveColor => brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 1).withOpacity(0.5) + : const Color.fromRGBO(255, 255, 255, 1).withOpacity(0.5); + + Color get dotsIndicatorInactiveColor => brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 1).withOpacity(0.12) + : const Color.fromRGBO(255, 255, 255, 1).withOpacity(0.12); + + Color get toastTextColor => brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(0, 0, 0, 1); + + Color get toastBackgroundColor => brightness == Brightness.light + ? const Color.fromRGBO(24, 24, 24, 0.95) + : const Color.fromRGBO(255, 255, 255, 0.95); + + Color get subTextColor => brightness == Brightness.light + ? const Color.fromRGBO(180, 180, 180, 1) + : const Color.fromRGBO(100, 100, 100, 1); + + Color get themeSwitchInactiveIconColor => brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 1).withOpacity(0.5) + : const Color.fromRGBO(255, 255, 255, 1).withOpacity(0.5); + + Color get searchResultsColor => brightness == Brightness.light + ? const Color.fromRGBO(245, 245, 245, 1.0) + : const Color.fromRGBO(30, 30, 30, 1.0); + + Color get mutedTextColor => brightness == Brightness.light + ? const Color.fromRGBO(80, 80, 80, 1) + : const Color.fromRGBO(150, 150, 150, 1); + + Color get searchResultsBackgroundColor => brightness == Brightness.light + ? Colors.black.withOpacity(0.32) + : Colors.black.withOpacity(0.64); + + Color get codeCardBackgroundColor => brightness == Brightness.light + ? const Color.fromRGBO(246, 246, 246, 1) + : const Color.fromRGBO(40, 40, 40, 0.6); + + Color get primaryColor => brightness == Brightness.light + ? const Color(0xFF9610D6) + : const Color(0xFF9610D6); + + Color get onBoardingBodyColor => brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 0.38) + : const Color.fromRGBO(255, 255, 255, 0.38); + + EnteTheme get enteTheme => + brightness == Brightness.light ? lightTheme : darkTheme; + + EnteTheme get inverseEnteTheme => + brightness == Brightness.light ? darkTheme : lightTheme; +} + +OutlinedButtonThemeData buildOutlinedButtonThemeData({ + required Color bgDisabled, + required Color bgEnabled, + required Color fgDisabled, + required Color fgEnabled, +}) { + return OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + fixedSize: const Size.fromHeight(56), + alignment: Alignment.center, + padding: const EdgeInsets.fromLTRB(50, 16, 50, 16), + textStyle: const TextStyle( + fontWeight: FontWeight.w600, + fontFamily: 'Inter-SemiBold', + fontSize: 18, + ), + ).copyWith( + backgroundColor: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.disabled)) { + return bgDisabled; + } + return bgEnabled; + }, + ), + foregroundColor: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.disabled)) { + return fgDisabled; + } + return fgEnabled; + }, + ), + alignment: Alignment.center, + ), + ); +} + +ElevatedButtonThemeData buildElevatedButtonThemeData({ + required Color onPrimary, // text button color + required Color primary, + double elevation = 2, // background color of button +}) { + return ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + foregroundColor: onPrimary, + backgroundColor: primary, + elevation: elevation, + alignment: Alignment.center, + textStyle: const TextStyle( + fontWeight: FontWeight.w600, + fontFamily: 'Inter-SemiBold', + fontSize: 18, + ), + padding: const EdgeInsets.symmetric(vertical: 8), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4)), + ), + ), + ); +} + +// Helper function to create ThemeData that works with the new color system +ThemeData createAppThemeData({ + required Brightness brightness, + EnteColorScheme? colorScheme, +}) { + final effectiveColorScheme = colorScheme ?? + (brightness == Brightness.light ? lightScheme : darkScheme); + + final baseThemeData = + brightness == Brightness.light ? lightThemeData : darkThemeData; + + // Create platform-specific typography to ensure consistent font sizes + final typography = Typography.material2021( + platform: + TargetPlatform.android, // Force Android typography for consistency + ); + + return baseThemeData.copyWith( + extensions: [effectiveColorScheme], + primaryColor: effectiveColorScheme.primary500, + scaffoldBackgroundColor: effectiveColorScheme.backgroundBase, + typography: typography, + dialogTheme: baseThemeData.dialogTheme.copyWith( + backgroundColor: effectiveColorScheme.backgroundElevated, + ), + appBarTheme: baseThemeData.appBarTheme.copyWith( + backgroundColor: effectiveColorScheme.backgroundBase, + foregroundColor: effectiveColorScheme.textBase, + iconTheme: IconThemeData(color: effectiveColorScheme.textBase), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: effectiveColorScheme.primary500, + foregroundColor: effectiveColorScheme.backgroundBase, + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + backgroundColor: effectiveColorScheme.fillFaint, + foregroundColor: effectiveColorScheme.textBase, + side: BorderSide(color: effectiveColorScheme.strokeMuted), + ), + ), + ); +} diff --git a/mobile/packages/ui/lib/theme/multi_app_demo.dart b/mobile/packages/ui/lib/theme/multi_app_demo.dart new file mode 100644 index 0000000000..13f8cd9f70 --- /dev/null +++ b/mobile/packages/ui/lib/theme/multi_app_demo.dart @@ -0,0 +1,428 @@ +// Demo: Complete working example showing multi-app theme compatibility +// This file demonstrates how the reusable theme system works for different apps + +import 'package:ente_ui/theme/colors.dart'; +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:ente_ui/theme/ente_theme_data.dart'; +import 'package:flutter/material.dart'; + +/// App 1: E-commerce app with blue theme +class ECommerceApp { + static const Color brandBlue = Color(0xFF1976D2); + + static final schemes = ColorSchemeBuilder.fromPrimaryColor(brandBlue); + + static final lightTheme = createAppThemeData( + brightness: Brightness.light, + colorScheme: schemes.light, + ); + + static final darkTheme = createAppThemeData( + brightness: Brightness.dark, + colorScheme: schemes.dark, + ); +} + +/// App 2: Social media app with purple theme +class SocialMediaApp { + static const Color brandPurple = Color(0xFF9C27B0); + + static final schemes = ColorSchemeBuilder.fromPrimaryColor(brandPurple); + + static final lightTheme = createAppThemeData( + brightness: Brightness.light, + colorScheme: schemes.light, + ); + + static final darkTheme = createAppThemeData( + brightness: Brightness.dark, + colorScheme: schemes.dark, + ); +} + +/// App 3: Finance app with green theme +class FinanceApp { + static final schemes = ColorSchemeBuilder.fromCustomColors( + primary700: const Color(0xFF388E3C), + primary500: const Color(0xFF4CAF50), + primary400: const Color(0xFF66BB6A), + primary300: const Color(0xFF81C784), + gradientButtonBgColors: const [ + Color(0xFF388E3C), + Color(0xFF4CAF50), + Color(0xFF66BB6A), + ], + ); + + static final lightTheme = createAppThemeData( + brightness: Brightness.light, + colorScheme: schemes.light, + ); + + static final darkTheme = createAppThemeData( + brightness: Brightness.dark, + colorScheme: schemes.dark, + ); +} + +/// App 4: Gaming app with orange theme +class GamingApp { + static final customLightScheme = EnteColorScheme.light( + primary700: const Color(0xFFE65100), + primary500: const Color(0xFFFF9800), + primary400: const Color(0xFFFFB74D), + primary300: const Color(0xFFFFCC02), + iconButtonColor: const Color(0xFFFF6F00), + gradientButtonBgColors: const [ + Color(0xFFE65100), + Color(0xFFFF9800), + Color(0xFFFFB74D), + ], + warning500: const Color(0xFFF44336), // Custom warning for gaming + ); + + static final customDarkScheme = EnteColorScheme.dark( + primary700: const Color(0xFFE65100), + primary500: const Color(0xFFFF9800), + primary400: const Color(0xFFFFB74D), + primary300: const Color(0xFFFFCC02), + iconButtonColor: const Color(0xFFFF6F00), + gradientButtonBgColors: const [ + Color(0xFFE65100), + Color(0xFFFF9800), + Color(0xFFFFB74D), + ], + warning500: const Color(0xFFF44336), + ); + + static final lightTheme = createAppThemeData( + brightness: Brightness.light, + colorScheme: customLightScheme, + ); + + static final darkTheme = createAppThemeData( + brightness: Brightness.dark, + colorScheme: customDarkScheme, + ); +} + +/// Demo widget that shows how UI components adapt to different app themes +class MultiAppThemeDemo extends StatefulWidget { + const MultiAppThemeDemo({super.key}); + + @override + State createState() => _MultiAppThemeDemoState(); +} + +class _MultiAppThemeDemoState extends State { + int currentAppIndex = 0; + bool isDarkMode = false; + + final List<({String name, ThemeData light, ThemeData dark})> apps = [ + ( + name: "E-commerce", + light: ECommerceApp.lightTheme, + dark: ECommerceApp.darkTheme + ), + ( + name: "Social Media", + light: SocialMediaApp.lightTheme, + dark: SocialMediaApp.darkTheme + ), + (name: "Finance", light: FinanceApp.lightTheme, dark: FinanceApp.darkTheme), + (name: "Gaming", light: GamingApp.lightTheme, dark: GamingApp.darkTheme), + ]; + + @override + Widget build(BuildContext context) { + final currentApp = apps[currentAppIndex]; + final currentTheme = isDarkMode ? currentApp.dark : currentApp.light; + + return MaterialApp( + title: '${currentApp.name} App Demo', + theme: currentTheme, + home: DemoHomePage( + appName: currentApp.name, + onAppChanged: (index) => setState(() => currentAppIndex = index), + onThemeChanged: (dark) => setState(() => isDarkMode = dark), + currentAppIndex: currentAppIndex, + isDarkMode: isDarkMode, + appCount: apps.length, + ), + ); + } +} + +class DemoHomePage extends StatelessWidget { + final String appName; + final Function(int) onAppChanged; + final Function(bool) onThemeChanged; + final int currentAppIndex; + final bool isDarkMode; + final int appCount; + + const DemoHomePage({ + super.key, + required this.appName, + required this.onAppChanged, + required this.onThemeChanged, + required this.currentAppIndex, + required this.isDarkMode, + required this.appCount, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + + return Scaffold( + backgroundColor: colorScheme.backgroundBase, + appBar: AppBar( + title: Text('$appName Theme Demo'), + backgroundColor: colorScheme.backgroundElevated, + foregroundColor: colorScheme.textBase, + actions: [ + Switch( + value: isDarkMode, + onChanged: onThemeChanged, + activeColor: colorScheme.primary500, + ), + ], + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // App Selector + Text( + 'Switch App Theme:', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: colorScheme.textBase, + ), + ), + const SizedBox(height: 12), + Wrap( + spacing: 8, + children: List.generate(appCount, (index) { + final isSelected = index == currentAppIndex; + return FilterChip( + label: Text( + ['E-commerce', 'Social', 'Finance', 'Gaming'][index], + ), + selected: isSelected, + onSelected: (_) => onAppChanged(index), + backgroundColor: colorScheme.fillFaint, + selectedColor: colorScheme.primary400, + labelStyle: TextStyle( + color: isSelected + ? colorScheme.backgroundBase + : colorScheme.textBase, + ), + ); + }), + ), + const SizedBox(height: 32), + + // UI Components Demo + Text( + 'UI Components:', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: colorScheme.textBase, + ), + ), + const SizedBox(height: 16), + + // Background colors demo + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: colorScheme.backgroundElevated, + border: Border.all(color: colorScheme.strokeFaint), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Card with elevated background', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: colorScheme.textBase, + ), + ), + const SizedBox(height: 8), + Text( + 'This is secondary text that adapts to the theme.', + style: TextStyle(color: colorScheme.textMuted), + ), + Text( + 'This is faint text for hints and labels.', + style: TextStyle(color: colorScheme.textFaint), + ), + ], + ), + ), + const SizedBox(height: 16), + + // Buttons demo + Row( + children: [ + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: colorScheme.primary500, + foregroundColor: colorScheme.backgroundBase, + ), + onPressed: () {}, + child: const Text('Primary Button'), + ), + ), + const SizedBox(width: 12), + Expanded( + child: OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: colorScheme.fillFaint, + foregroundColor: colorScheme.textBase, + side: BorderSide(color: colorScheme.strokeMuted), + ), + onPressed: () {}, + child: const Text('Secondary'), + ), + ), + ], + ), + const SizedBox(height: 16), + + // Gradient button demo + Container( + width: double.infinity, + height: 48, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: colorScheme.gradientButtonBgColors, + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + 'Gradient Button', + style: TextStyle( + color: colorScheme.backgroundBase, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + ), + const SizedBox(height: 16), + + // Warning demo + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colorScheme.warning500.withOpacity(0.1), + border: Border.all(color: colorScheme.warning500), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon( + Icons.warning, + color: colorScheme.warning500, + size: 20, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Warning message with custom warning color', + style: TextStyle(color: colorScheme.warning700), + ), + ), + ], + ), + ), + const SizedBox(height: 32), + + // Color palette display + Text( + 'Color Palette:', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: colorScheme.textBase, + ), + ), + const SizedBox(height: 16), + _buildColorPalette(colorScheme), + ], + ), + ), + floatingActionButton: FloatingActionButton( + backgroundColor: colorScheme.iconButtonColor, + foregroundColor: colorScheme.backgroundBase, + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('$appName theme is working!'), + backgroundColor: colorScheme.primary500, + ), + ); + }, + child: const Icon(Icons.palette), + ), + ); + } + + Widget _buildColorPalette(EnteColorScheme colorScheme) { + final colors = [ + ('Primary 700', colorScheme.primary700), + ('Primary 500', colorScheme.primary500), + ('Primary 400', colorScheme.primary400), + ('Primary 300', colorScheme.primary300), + ('Warning', colorScheme.warning500), + ('Icon Button', colorScheme.iconButtonColor), + ]; + + return Wrap( + spacing: 8, + runSpacing: 8, + children: colors.map((color) { + return Column( + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: color.$2, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: colorScheme.strokeFaint), + ), + ), + const SizedBox(height: 4), + Text( + color.$1, + style: TextStyle( + fontSize: 10, + color: colorScheme.textMuted, + ), + textAlign: TextAlign.center, + ), + ], + ); + }).toList(), + ); + } +} + +// Example of how to use this in main.dart: +void main() { + runApp(const MultiAppThemeDemo()); +} diff --git a/mobile/packages/ui/lib/theme/platform_text_config.dart b/mobile/packages/ui/lib/theme/platform_text_config.dart new file mode 100644 index 0000000000..ffe476e40d --- /dev/null +++ b/mobile/packages/ui/lib/theme/platform_text_config.dart @@ -0,0 +1,75 @@ +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +/// Platform-specific text scaling configuration to ensure consistent +/// font sizes and appearance across Android and iOS platforms. +class PlatformTextConfig { + /// Android tends to render fonts slightly larger than iOS, so we apply + /// a small reduction factor to maintain visual consistency. + static const double androidFontScaleFactor = 0.95; + + /// iOS uses the default scaling (1.0) + static const double iosFontScaleFactor = 1.0; + + /// Get the appropriate font scale factor for the current platform + static double getPlatformFontScaleFactor() { + if (kIsWeb) return 1.0; + + switch (Platform.operatingSystem) { + case 'android': + return androidFontScaleFactor; + case 'ios': + return iosFontScaleFactor; + default: + return 1.0; + } + } + + /// Adjust font size based on platform to ensure consistency + static double adjustFontSize(double baseFontSize) { + return baseFontSize * getPlatformFontScaleFactor(); + } + + /// Create a TextStyle with platform-adjusted font size + static TextStyle createTextStyle({ + required double fontSize, + FontWeight? fontWeight, + String? fontFamily, + Color? color, + double? height, + TextDecoration? decoration, + }) { + return TextStyle( + fontSize: adjustFontSize(fontSize), + fontWeight: fontWeight, + fontFamily: fontFamily, + color: color, + height: height, + decoration: decoration, + ); + } + + /// Get platform-specific MediaQuery configuration for text scaling + static MediaQueryData adjustMediaQueryTextScaling(MediaQueryData data) { + // Clamp text scaling between 0.8 and 1.3 to prevent extreme scaling + // that can break UI layouts + final textScaleFactor = + (data.textScaler.scale(1.0) * getPlatformFontScaleFactor()) + .clamp(0.8, 1.3); + + return data.copyWith( + textScaler: TextScaler.linear(textScaleFactor), + ); + } +} + +/// Extension on BuildContext to easily access platform-adjusted text scaling +extension PlatformTextScaling on BuildContext { + /// Get MediaQuery with platform-adjusted text scaling + MediaQueryData get platformAdjustedMediaQuery { + return PlatformTextConfig.adjustMediaQueryTextScaling( + MediaQuery.of(this), + ); + } +} diff --git a/mobile/packages/ui/lib/theme/text_style.dart b/mobile/packages/ui/lib/theme/text_style.dart new file mode 100644 index 0000000000..f8b8caad7b --- /dev/null +++ b/mobile/packages/ui/lib/theme/text_style.dart @@ -0,0 +1,204 @@ +import 'package:ente_ui/theme/colors.dart'; +import 'package:ente_ui/theme/platform_text_config.dart'; +import 'package:flutter/material.dart'; + +const FontWeight _regularWeight = FontWeight.w500; +const FontWeight _boldWeight = FontWeight.w600; +const String _fontFamily = 'Inter'; + +final TextStyle brandStyleSmall = TextStyle( + fontWeight: FontWeight.bold, + fontFamily: 'Montserrat', + fontSize: PlatformTextConfig.adjustFontSize(21), +); + +final TextStyle brandStyleMedium = TextStyle( + fontWeight: FontWeight.bold, + fontFamily: 'Montserrat', + fontSize: PlatformTextConfig.adjustFontSize(24), +); + +final TextStyle h1 = TextStyle( + fontSize: PlatformTextConfig.adjustFontSize(48), + height: 48 / 28, + fontWeight: _regularWeight, + fontFamily: _fontFamily, +); +final TextStyle h2 = TextStyle( + fontSize: PlatformTextConfig.adjustFontSize(32), + height: 39 / 32.0, + fontWeight: _regularWeight, + fontFamily: _fontFamily, +); +final TextStyle h3 = TextStyle( + fontSize: PlatformTextConfig.adjustFontSize(24), + height: 29 / 24.0, + fontWeight: _regularWeight, + fontFamily: _fontFamily, +); +final TextStyle large = TextStyle( + fontSize: PlatformTextConfig.adjustFontSize(18), + height: 22 / 18.0, + fontWeight: _regularWeight, + fontFamily: _fontFamily, +); +final TextStyle body = TextStyle( + fontSize: PlatformTextConfig.adjustFontSize(16), + height: 20 / 16.0, + fontWeight: _regularWeight, + fontFamily: _fontFamily, +); +final TextStyle small = TextStyle( + fontSize: PlatformTextConfig.adjustFontSize(14), + height: 17 / 14.0, + fontWeight: _regularWeight, + fontFamily: _fontFamily, +); +final TextStyle mini = TextStyle( + fontSize: PlatformTextConfig.adjustFontSize(12), + height: 15 / 12.0, + fontWeight: _regularWeight, + fontFamily: _fontFamily, +); +final TextStyle tiny = TextStyle( + fontSize: PlatformTextConfig.adjustFontSize(10), + height: 12 / 10.0, + fontWeight: _regularWeight, + fontFamily: _fontFamily, +); + +class EnteTextTheme { + final TextStyle h1; + final TextStyle h1Bold; + final TextStyle h2; + final TextStyle h2Bold; + final TextStyle h3; + final TextStyle h3Bold; + final TextStyle large; + final TextStyle largeBold; + final TextStyle body; + final TextStyle bodyBold; + final TextStyle small; + final TextStyle smallBold; + final TextStyle mini; + final TextStyle miniBold; + final TextStyle tiny; + final TextStyle tinyBold; + final TextStyle brandSmall; + final TextStyle brandMedium; + + // textMuted variants + final TextStyle h1Muted; + final TextStyle h2Muted; + final TextStyle h3Muted; + final TextStyle largeMuted; + final TextStyle bodyMuted; + final TextStyle smallMuted; + final TextStyle miniMuted; + final TextStyle miniBoldMuted; + final TextStyle tinyMuted; + + // textFaint variants + final TextStyle h1Faint; + final TextStyle h2Faint; + final TextStyle h3Faint; + final TextStyle largeFaint; + final TextStyle bodyFaint; + final TextStyle smallFaint; + final TextStyle miniFaint; + final TextStyle tinyFaint; + + const EnteTextTheme({ + required this.h1, + required this.h1Bold, + required this.h2, + required this.h2Bold, + required this.h3, + required this.h3Bold, + required this.large, + required this.largeBold, + required this.body, + required this.bodyBold, + required this.small, + required this.smallBold, + required this.mini, + required this.miniBold, + required this.tiny, + required this.tinyBold, + required this.brandSmall, + required this.brandMedium, + required this.h1Muted, + required this.h2Muted, + required this.h3Muted, + required this.largeMuted, + required this.bodyMuted, + required this.smallMuted, + required this.miniMuted, + required this.miniBoldMuted, + required this.tinyMuted, + required this.h1Faint, + required this.h2Faint, + required this.h3Faint, + required this.largeFaint, + required this.bodyFaint, + required this.smallFaint, + required this.miniFaint, + required this.tinyFaint, + }); +} + +EnteTextTheme lightTextTheme = _buildEnteTextStyle( + textBaseLight, + textMutedLight, + textFaintLight, +); + +EnteTextTheme darkTextTheme = _buildEnteTextStyle( + textBaseDark, + textMutedDark, + textFaintDark, +); + +EnteTextTheme _buildEnteTextStyle( + Color color, + Color textMuted, + Color textFaint, +) { + return EnteTextTheme( + h1: h1.copyWith(color: color), + h1Bold: h1.copyWith(color: color, fontWeight: _boldWeight), + h2: h2.copyWith(color: color), + h2Bold: h2.copyWith(color: color, fontWeight: _boldWeight), + h3: h3.copyWith(color: color), + h3Bold: h3.copyWith(color: color, fontWeight: _boldWeight), + large: large.copyWith(color: color), + largeBold: large.copyWith(color: color, fontWeight: _boldWeight), + body: body.copyWith(color: color), + bodyBold: body.copyWith(color: color, fontWeight: _boldWeight), + small: small.copyWith(color: color), + smallBold: small.copyWith(color: color, fontWeight: _boldWeight), + mini: mini.copyWith(color: color), + miniBold: mini.copyWith(color: color, fontWeight: _boldWeight), + tiny: tiny.copyWith(color: color), + tinyBold: tiny.copyWith(color: color, fontWeight: _boldWeight), + brandSmall: brandStyleSmall.copyWith(color: color), + brandMedium: brandStyleMedium.copyWith(color: color), + h1Muted: h1.copyWith(color: textMuted), + h2Muted: h2.copyWith(color: textMuted), + h3Muted: h3.copyWith(color: textMuted), + largeMuted: large.copyWith(color: textMuted), + bodyMuted: body.copyWith(color: textMuted), + smallMuted: small.copyWith(color: textMuted), + miniMuted: mini.copyWith(color: textMuted), + miniBoldMuted: mini.copyWith(color: textMuted, fontWeight: _boldWeight), + tinyMuted: tiny.copyWith(color: textMuted), + h1Faint: h1.copyWith(color: textFaint), + h2Faint: h2.copyWith(color: textFaint), + h3Faint: h3.copyWith(color: textFaint), + largeFaint: large.copyWith(color: textFaint), + bodyFaint: body.copyWith(color: textFaint), + smallFaint: small.copyWith(color: textFaint), + miniFaint: mini.copyWith(color: textFaint), + tinyFaint: tiny.copyWith(color: textFaint), + ); +} diff --git a/mobile/packages/ui/lib/theme/theme_config.dart b/mobile/packages/ui/lib/theme/theme_config.dart new file mode 100644 index 0000000000..a008370504 --- /dev/null +++ b/mobile/packages/ui/lib/theme/theme_config.dart @@ -0,0 +1,14 @@ +enum EnteApp { + auth, + locker; +} + +class AppThemeConfig { + static EnteApp? _currentApp; + + static void initialize(EnteApp app) { + _currentApp = app; + } + + static EnteApp get currentApp => _currentApp ?? EnteApp.auth; +} diff --git a/mobile/packages/ui/lib/utils/dialog_util.dart b/mobile/packages/ui/lib/utils/dialog_util.dart new file mode 100644 index 0000000000..620f4e0cb7 --- /dev/null +++ b/mobile/packages/ui/lib/utils/dialog_util.dart @@ -0,0 +1,371 @@ +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:ente_base/typedefs.dart'; +import 'package:ente_strings/ente_strings.dart'; +import 'package:ente_ui/components/action_sheet_widget.dart'; +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_result.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/components/dialog_widget.dart'; +import 'package:ente_ui/components/loading_widget.dart'; +import 'package:ente_ui/components/progress_dialog.dart'; +import 'package:ente_ui/theme/colors.dart'; +import 'package:ente_utils/email_util.dart'; +import 'package:ente_utils/platform_util.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +typedef DialogBuilder = DialogWidget Function(BuildContext context); + +///Will return null if dismissed by tapping outside +Future showErrorDialog( + BuildContext context, + String title, + String? body, { + bool isDismissable = true, +}) async { + return showDialogWidget( + context: context, + title: title, + body: body, + isDismissible: isDismissable, + buttons: [ + ButtonWidget( + buttonType: ButtonType.primary, + labelText: context.strings.contactSupport, + isInAlert: true, + buttonAction: ButtonAction.first, + onTap: () async { + await openSupportPage(body, null); + }, + ), + const ButtonWidget( + buttonType: ButtonType.secondary, + labelText: "OK", + isInAlert: true, + buttonAction: ButtonAction.second, + ), + ], + ); +} + +String parseErrorForUI( + BuildContext context, + String genericError, { + Object? error, + bool surfaceError = kDebugMode, +}) { + try { + if (error == null) { + return genericError; + } + if (error is DioException) { + final DioException dioError = error; + if (dioError.type == DioExceptionType.unknown) { + if (dioError.error.toString().contains('Failed host lookup')) { + return context.strings.networkHostLookUpErr; + } else if (dioError.error.toString().contains('SocketException')) { + return context.strings.networkConnectionRefusedErr; + } + } + } + // return generic error if the user is not internal and the error is not in debug mode + if (!kDebugMode) { + return genericError; + } + String errorInfo = ""; + if (error is DioException) { + final DioException dioError = error; + if (dioError.type == DioExceptionType.badResponse) { + if (dioError.response?.data["code"] != null) { + errorInfo = "Reason: ${dioError.response!.data["code"]}"; + } else { + errorInfo = "Reason: ${dioError.response!.data.toString()}"; + } + } else if (dioError.type == DioExceptionType.badCertificate) { + errorInfo = "Reason: ${dioError.error.toString()}"; + } else { + errorInfo = "Reason: ${dioError.type.toString()}"; + } + } else { + if (kDebugMode) { + errorInfo = error.toString(); + } else { + errorInfo = error.toString().split('Source stack')[0]; + } + } + if (errorInfo.isNotEmpty) { + return "$genericError\n\n$errorInfo"; + } + return genericError; + } catch (e) { + return genericError; + } +} + +///Will return null if dismissed by tapping outside +Future showGenericErrorDialog({ + required BuildContext context, + bool isDismissible = true, + required Object? error, +}) async { + String errorBody = parseErrorForUI( + context, + context.strings.itLooksLikeSomethingWentWrongPleaseRetryAfterSome, + error: error, + ); + bool isWindowCertError = false; + if (Platform.isWindows && + error != null && + error.toString().contains("CERTIFICATE_VERIFY_FAILED")) { + isWindowCertError = true; + errorBody = + "Certificate verification failed. Please update your system certificates, & restart the app. If the issue persists, please contact support."; + } + + return showDialogWidget( + context: context, + title: context.strings.error, + icon: Icons.error_outline_outlined, + body: errorBody, + isDismissible: isDismissible, + buttons: [ + ButtonWidget( + buttonType: ButtonType.primary, + labelText: context.strings.ok, + buttonAction: ButtonAction.first, + isInAlert: true, + ), + if (isWindowCertError) + ButtonWidget( + buttonType: ButtonType.neutral, + labelText: 'Update Certificates', + buttonAction: ButtonAction.third, + isInAlert: true, + onTap: () async { + PlatformUtil.openWebView( + context, + context.strings.faq, + "https://help.ente.io/auth/troubleshooting/windows-login", + ); + }, + ), + ButtonWidget( + buttonType: ButtonType.secondary, + labelText: context.strings.contactSupport, + buttonAction: ButtonAction.second, + onTap: () async { + await sendLogs( + context, + context.strings.contactSupport, + postShare: () {}, + ); + }, + ), + ], + ); +} + +DialogWidget choiceDialog({ + required String title, + String? body, + required String firstButtonLabel, + String secondButtonLabel = "Cancel", + ButtonType firstButtonType = ButtonType.neutral, + ButtonType secondButtonType = ButtonType.secondary, + ButtonAction firstButtonAction = ButtonAction.first, + ButtonAction secondButtonAction = ButtonAction.cancel, + FutureVoidCallback? firstButtonOnTap, + FutureVoidCallback? secondButtonOnTap, + bool isCritical = false, + IconData? icon, +}) { + final buttons = [ + ButtonWidget( + buttonType: isCritical ? ButtonType.critical : firstButtonType, + labelText: firstButtonLabel, + isInAlert: true, + onTap: firstButtonOnTap, + buttonAction: firstButtonAction, + ), + ButtonWidget( + buttonType: secondButtonType, + labelText: secondButtonLabel, + isInAlert: true, + onTap: secondButtonOnTap, + buttonAction: secondButtonAction, + ), + ]; + + return DialogWidget(title: title, body: body, buttons: buttons, icon: icon); +} + +///Will return null if dismissed by tapping outside +Future showChoiceDialog( + BuildContext context, { + required String title, + String? body, + required String firstButtonLabel, + String? secondButtonLabel = "Cancel", + ButtonType firstButtonType = ButtonType.neutral, + ButtonType secondButtonType = ButtonType.secondary, + ButtonAction firstButtonAction = ButtonAction.first, + ButtonAction secondButtonAction = ButtonAction.cancel, + FutureVoidCallback? firstButtonOnTap, + FutureVoidCallback? secondButtonOnTap, + bool isCritical = false, + IconData? icon, + bool isDismissible = true, +}) async { + final buttons = [ + ButtonWidget( + buttonType: isCritical ? ButtonType.critical : firstButtonType, + labelText: firstButtonLabel, + isInAlert: true, + onTap: firstButtonOnTap, + buttonAction: firstButtonAction, + ), + if (secondButtonLabel != null) + ButtonWidget( + buttonType: secondButtonType, + labelText: secondButtonLabel, + isInAlert: true, + onTap: secondButtonOnTap, + buttonAction: secondButtonAction, + ), + ]; + return showDialogWidget( + context: context, + title: title, + body: body, + buttons: buttons, + icon: icon, + isDismissible: isDismissible, + ); +} + +///Will return null if dismissed by tapping outside +Future showChoiceActionSheet( + BuildContext context, { + required String title, + String? body, + required String firstButtonLabel, + String secondButtonLabel = "Cancel", + ButtonType firstButtonType = ButtonType.neutral, + ButtonType secondButtonType = ButtonType.secondary, + ButtonAction firstButtonAction = ButtonAction.first, + ButtonAction secondButtonAction = ButtonAction.cancel, + FutureVoidCallback? firstButtonOnTap, + FutureVoidCallback? secondButtonOnTap, + bool isCritical = false, + IconData? icon, + bool isDismissible = true, +}) async { + final buttons = [ + ButtonWidget( + buttonType: isCritical ? ButtonType.critical : firstButtonType, + labelText: firstButtonLabel, + isInAlert: true, + onTap: firstButtonOnTap, + buttonAction: firstButtonAction, + shouldStickToDarkTheme: true, + ), + ButtonWidget( + buttonType: secondButtonType, + labelText: secondButtonLabel, + isInAlert: true, + onTap: secondButtonOnTap, + buttonAction: secondButtonAction, + shouldStickToDarkTheme: true, + ), + ]; + return showActionSheet( + context: context, + title: title, + body: body, + buttons: buttons, + isDismissible: isDismissible, + ); +} + +ProgressDialog createProgressDialog( + BuildContext context, + String message, { + isDismissible = false, +}) { + final dialog = ProgressDialog( + context, + type: ProgressDialogType.normal, + isDismissible: isDismissible, + barrierColor: Colors.black12, + ); + dialog.style( + message: message, + messageTextStyle: Theme.of(context).textTheme.labelMedium, + backgroundColor: Theme.of(context).dialogTheme.backgroundColor, + progressWidget: const EnteLoadingWidget(), + borderRadius: 10, + elevation: 10.0, + insetAnimCurve: Curves.easeInOut, + ); + return dialog; +} + +//Can return ButtonResult? from ButtonWidget or Exception? from TextInputDialog +Future showTextInputDialog( + BuildContext context, { + required String title, + String? body, + required String submitButtonLabel, + IconData? icon, + String? label, + String? message, + String? hintText, + required FutureVoidCallbackParamStr onSubmit, + IconData? prefixIcon, + String? initialValue, + Alignment? alignMessage, + int? maxLength, + bool showOnlyLoadingState = false, + TextCapitalization textCapitalization = TextCapitalization.none, + bool alwaysShowSuccessState = false, + bool isPasswordInput = false, + bool useRootNavigator = false, +}) { + return showDialog( + barrierColor: backdropFaintDark, + useRootNavigator: useRootNavigator, + context: context, + builder: (context) { + final bottomInset = MediaQuery.of(context).viewInsets.bottom; + final isKeyboardUp = bottomInset > 100; + return Material( + color: Colors.transparent, + child: Center( + child: Padding( + padding: EdgeInsets.only(bottom: isKeyboardUp ? bottomInset : 0), + child: TextInputDialog( + title: title, + message: message, + label: label, + body: body, + icon: icon, + submitButtonLabel: submitButtonLabel, + onSubmit: onSubmit, + hintText: hintText, + prefixIcon: prefixIcon, + initialValue: initialValue, + alignMessage: alignMessage, + maxLength: maxLength, + showOnlyLoadingState: showOnlyLoadingState, + textCapitalization: textCapitalization, + alwaysShowSuccessState: alwaysShowSuccessState, + isPasswordInput: isPasswordInput, + ), + ), + ), + ); + }, + ); +} diff --git a/mobile/packages/ui/lib/utils/file_icon_utils.dart b/mobile/packages/ui/lib/utils/file_icon_utils.dart new file mode 100644 index 0000000000..bc1f238200 --- /dev/null +++ b/mobile/packages/ui/lib/utils/file_icon_utils.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; + +class FileIconConfig { + final IconData icon; + final Color color; + final Set extensions; + + const FileIconConfig({ + required this.icon, + required this.color, + required this.extensions, + }); +} + +class FileIconUtils { + // Centralized configuration - change icons and colors here only + static const Map _fileTypeConfigs = { + 'pdf': FileIconConfig( + extensions: {'.pdf'}, + icon: Icons.picture_as_pdf, + color: Colors.red, + ), + 'image': FileIconConfig( + extensions: {'.jpg', '.png', '.heic'}, + icon: Icons.image, + color: Colors.blue, + ), + 'presentation': FileIconConfig( + extensions: {'.pptx'}, + icon: Icons.slideshow, + color: Colors.orange, + ), + 'spreadsheet': FileIconConfig( + extensions: {'.xlsx'}, + icon: Icons.table_chart, + color: Colors.green, + ), + }; + + static const FileIconConfig _defaultConfig = FileIconConfig( + extensions: {}, + icon: Icons.insert_drive_file, + color: Colors.grey, + ); + + static FileIconConfig _getFileConfig(String fileName) { + final lowerFileName = fileName.toLowerCase(); + final lastDotIndex = lowerFileName.lastIndexOf('.'); + + if (lastDotIndex == -1) { + return _defaultConfig; // No extension found + } + + final extension = lowerFileName.substring(lastDotIndex); + + for (final config in _fileTypeConfigs.values) { + if (config.extensions.contains(extension)) { + return config; + } + } + + return _defaultConfig; + } + + static IconData getFileIcon(String fileName) { + return _getFileConfig(fileName).icon; + } + + static Color getFileIconColor(String fileName) { + return _getFileConfig(fileName).color; + } +} diff --git a/mobile/packages/ui/lib/utils/toast_util.dart b/mobile/packages/ui/lib/utils/toast_util.dart new file mode 100644 index 0000000000..336956232e --- /dev/null +++ b/mobile/packages/ui/lib/utils/toast_util.dart @@ -0,0 +1,52 @@ +import 'package:ente_ui/theme/ente_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +void showToast( + BuildContext context, + String message, { + toastLength = Toast.LENGTH_LONG, + iOSDismissOnTap = true, +}) async { + try { + await Fluttertoast.cancel(); + await Fluttertoast.showToast( + msg: message, + toastLength: toastLength, + gravity: ToastGravity.BOTTOM, + timeInSecForIosWeb: 1, + backgroundColor: getEnteColorScheme(context).toastBackgroundColor, + textColor: getEnteColorScheme(context).toastTextColor, + fontSize: 16.0, + ); + } on MissingPluginException catch (_) { + final toast = Container( + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(25.0), + color: getEnteColorScheme(context).toastBackgroundColor, + ), + child: Text( + message, + style: TextStyle( + color: getEnteColorScheme(context).toastTextColor, + fontSize: 16.0, + ), + ), + ); + + final fToast = FToast(); + fToast.init(context); + + fToast.showToast( + child: toast, + gravity: ToastGravity.BOTTOM, + toastDuration: const Duration(seconds: 2), + ); + } +} + +void showShortToast(context, String message) { + showToast(context, message, toastLength: Toast.LENGTH_SHORT); +} diff --git a/mobile/packages/ui/lib/utils/window_listener_service.dart b/mobile/packages/ui/lib/utils/window_listener_service.dart new file mode 100644 index 0000000000..0a1f391c50 --- /dev/null +++ b/mobile/packages/ui/lib/utils/window_listener_service.dart @@ -0,0 +1,41 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/widgets.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:window_manager/window_manager.dart'; + +class WindowListenerService { + static const double initialWindowHeight = 1200.0; + static const double initialWindowWidth = 800.0; + static const double maxWindowHeight = 8192.0; + static const double maxWindowWidth = 8192.0; + late SharedPreferences _preferences; + + WindowListenerService._privateConstructor(); + + static final WindowListenerService instance = + WindowListenerService._privateConstructor(); + + Future init() async { + _preferences = await SharedPreferences.getInstance(); + } + + Size getWindowSize() { + final double windowWidth = + _preferences.getDouble('windowWidth') ?? initialWindowWidth; + final double windowHeight = + _preferences.getDouble('windowHeight') ?? initialWindowHeight; + final w = windowWidth.clamp(200.0, maxWindowWidth); + final h = windowHeight.clamp(400.0, maxWindowHeight); + return Size(w, h); + } + + Future onWindowResize() async { + final width = (await windowManager.getSize()).width; + final height = (await windowManager.getSize()).height; + // Save the window size to shared preferences + await _preferences.setDouble('windowWidth', width); + await _preferences.setDouble('windowHeight', height); + } +} diff --git a/mobile/packages/ui/pubspec.lock b/mobile/packages/ui/pubspec.lock new file mode 100644 index 0000000000..ba4200875e --- /dev/null +++ b/mobile/packages/ui/pubspec.lock @@ -0,0 +1,1010 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + bip39: + dependency: transitive + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + dio: + dependency: "direct main" + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + email_validator: + dependency: transitive + description: + name: email_validator + sha256: b19aa5d92fdd76fbc65112060c94d45ba855105a28bb6e462de7ff03b12fa1fb + url: "https://pub.dev" + source: hosted + version: "3.0.0" + ente_base: + dependency: "direct main" + description: + path: "../base" + relative: true + source: path + version: "1.0.0" + ente_configuration: + dependency: "direct main" + description: + path: "../configuration" + relative: true + source: path + version: "1.0.0" + ente_crypto_dart: + dependency: transitive + description: + path: "." + ref: HEAD + resolved-ref: f91e1545f8263df127762240c4da54a0c42835b2 + url: "https://github.com/ente-io/ente_crypto_dart.git" + source: git + version: "1.0.0" + ente_events: + dependency: transitive + description: + path: "../events" + relative: true + source: path + version: "1.0.0" + ente_logging: + dependency: "direct main" + description: + path: "../logging" + relative: true + source: path + version: "1.0.0" + ente_strings: + dependency: "direct main" + description: + path: "../strings" + relative: true + source: path + version: "1.0.0" + ente_utils: + dependency: "direct main" + description: + path: "../utils" + relative: true + source: path + version: "1.0.0" + event_bus: + dependency: transitive + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + expandable: + dependency: "direct main" + description: + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_saver: + dependency: transitive + description: + name: file_saver + sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_email_sender: + dependency: transitive + description: + name: flutter_email_sender + sha256: d39eb5e91358fc19ec4050da69accec21f9d5b2b6bcf188aa246327b6ca2352c + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter_inappwebview: + dependency: "direct main" + description: + name: flutter_inappwebview + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_windows: + dependency: transitive + description: + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_secure_storage: + dependency: transitive + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + url: "https://pub.dev" + source: hosted + version: "8.2.12" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + modal_bottom_sheet: + dependency: "direct main" + description: + name: modal_bottom_sheet + sha256: eac66ef8cb0461bf069a38c5eb0fa728cee525a531a8304bd3f7b2185407c67e + url: "https://pub.dev" + source: hosted + version: "3.0.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_linux: + dependency: transitive + description: + name: screen_retriever_linux + sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_macos: + dependency: transitive + description: + name: screen_retriever_macos + sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_platform_interface: + dependency: transitive + description: + name: screen_retriever_platform_interface + sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_windows: + dependency: transitive + description: + name: screen_retriever_windows + sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + sentry: + dependency: transitive + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + sentry_flutter: + dependency: transitive + description: + name: sentry_flutter + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + share_plus: + dependency: transitive + description: + name: share_plus + sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 + url: "https://pub.dev" + source: hosted + version: "11.1.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + sodium: + dependency: transitive + description: + name: sodium + sha256: d9830a388e37c82891888e64cfd4c6764fa3ac716bed80ac6eab89ee42c3cd76 + url: "https://pub.dev" + source: hosted + version: "2.3.1+1" + sodium_libs: + dependency: transitive + description: + name: sodium_libs + sha256: aa764acd6ccc6113e119c2d99471aeeb4637a9a501639549b297d3a143ff49b3 + url: "https://pub.dev" + source: hosted + version: "2.2.1+6" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + url: "https://pub.dev" + source: hosted + version: "6.3.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + window_manager: + dependency: "direct main" + description: + name: window_manager + sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.24.0" diff --git a/mobile/packages/ui/pubspec.yaml b/mobile/packages/ui/pubspec.yaml new file mode 100644 index 0000000000..ce4ff14c20 --- /dev/null +++ b/mobile/packages/ui/pubspec.yaml @@ -0,0 +1,36 @@ +name: ente_ui +description: A Flutter package containing shared UI components, themes, and utilities for Ente apps +version: 1.0.0 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + dio: ^5.8.0+1 + ente_base: + path: ../../packages/base + ente_configuration: + path: ../../packages/configuration + ente_logging: + path: ../../packages/logging + ente_strings: + path: ../../packages/strings + ente_utils: + path: ../../packages/utils + expandable: ^5.0.1 + flutter: + sdk: flutter + flutter_inappwebview: ^6.1.4 + fluttertoast: ^8.1.1 + modal_bottom_sheet: ^3.0.0 + shared_preferences: ^2.5.3 + window_manager: ^0.5.0 + +dev_dependencies: + flutter_lints: ^5.0.0 + flutter_test: + sdk: flutter + +flutter: diff --git a/mobile/packages/utils/analysis_options.yaml b/mobile/packages/utils/analysis_options.yaml new file mode 100644 index 0000000000..1bd78bc1b0 --- /dev/null +++ b/mobile/packages/utils/analysis_options.yaml @@ -0,0 +1,72 @@ +# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html +# or https://pub.dev/packages/lint (Effective dart) +# use "flutter analyze ." or "dart analyze ." for running lint checks + +include: package:flutter_lints/flutter.yaml +linter: + rules: + # Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml + # Ref https://dart-lang.github.io/linter/lints/ + - avoid_print + - avoid_unnecessary_containers + - avoid_web_libraries_in_flutter + - no_logic_in_create_state + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_final_locals + - require_trailing_commas + - sized_box_for_whitespace + - use_full_hex_values_for_flutter_colors + - use_key_in_widget_constructors + - cancel_subscriptions + + + - avoid_empty_else + - exhaustive_cases + + # just style suggestions + - sort_pub_dependencies + - use_rethrow_when_possible + - prefer_double_quotes + - directives_ordering + - always_use_package_imports + - sort_child_properties_last + - unawaited_futures + +analyzer: + errors: + avoid_empty_else: error + exhaustive_cases: error + curly_braces_in_flow_control_structures: error + directives_ordering: error + require_trailing_commas: error + always_use_package_imports: warning + prefer_final_fields: error + unused_import: error + camel_case_types: error + prefer_is_empty: warning + use_rethrow_when_possible: info + unused_field: warning + use_key_in_widget_constructors: warning + sort_child_properties_last: warning + sort_pub_dependencies: warning + library_private_types_in_public_api: warning + constant_identifier_names: ignore + prefer_const_constructors: warning + prefer_const_declarations: warning + prefer_const_constructors_in_immutables: warning + prefer_final_locals: warning + unnecessary_const: error + cancel_subscriptions: error + unrelated_type_equality_checks: error + unnecessary_cast: info + + + unawaited_futures: warning # convert to warning after fixing existing issues + invalid_dependency: info + use_build_context_synchronously: ignore # experimental lint, requires many changes + prefer_interpolation_to_compose_strings: ignore # later too many warnings + prefer_double_quotes: ignore # too many warnings + avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides diff --git a/mobile/packages/utils/devtools_options.yaml b/mobile/packages/utils/devtools_options.yaml new file mode 100644 index 0000000000..fa0b357c4f --- /dev/null +++ b/mobile/packages/utils/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/mobile/packages/utils/lib/date_time_util.dart b/mobile/packages/utils/lib/date_time_util.dart new file mode 100644 index 0000000000..0d566904a4 --- /dev/null +++ b/mobile/packages/utils/lib/date_time_util.dart @@ -0,0 +1,217 @@ +import 'package:intl/intl.dart'; + +const Set monthWith31Days = {1, 3, 5, 7, 8, 10, 12}; +const Set monthWith30Days = {4, 6, 9, 11}; +Map _months = { + 1: "Jan", + 2: "Feb", + 3: "March", + 4: "April", + 5: "May", + 6: "Jun", + 7: "July", + 8: "Aug", + 9: "Sep", + 10: "Oct", + 11: "Nov", + 12: "Dec", +}; + +Map _fullMonths = { + 1: "January", + 2: "February", + 3: "March", + 4: "April", + 5: "May", + 6: "June", + 7: "July", + 8: "August", + 9: "September", + 10: "October", + 11: "November", + 12: "December", +}; + +Map _days = { + 1: "Mon", + 2: "Tue", + 3: "Wed", + 4: "Thu", + 5: "Fri", + 6: "Sat", + 7: "Sun", +}; + +final currentYear = int.parse(DateTime.now().year.toString()); +const searchStartYear = 1970; + +//Jun 2022 +String getMonthAndYear(DateTime dateTime) { + return "${_months[dateTime.month]!} ${dateTime.year}"; +} + +//Thu, 30 Jun +String getDayAndMonth(DateTime dateTime) { + return "${_days[dateTime.weekday]!}, ${dateTime.day} ${_months[dateTime.month]!}"; +} + +//30 Jun, 2022 +String getDateAndMonthAndYear(DateTime dateTime) { + return "${dateTime.day} ${_months[dateTime.month]!}, ${dateTime.year}"; +} + +String getDay(DateTime dateTime) { + return _days[dateTime.weekday]!; +} + +String getMonth(DateTime dateTime) { + return _months[dateTime.month]!; +} + +String getFullMonth(DateTime dateTime) { + return _fullMonths[dateTime.month]!; +} + +String getAbbreviationOfYear(DateTime dateTime) { + return (dateTime.year % 100).toString(); +} + +//14:32 +String getTime(DateTime dateTime) { + final hours = + dateTime.hour > 9 ? dateTime.hour.toString() : "0${dateTime.hour}"; + final minutes = + dateTime.minute > 9 ? dateTime.minute.toString() : "0${dateTime.minute}"; + return "$hours:$minutes"; +} + +//11:22 AM +String getTimeIn12hrFormat(DateTime dateTime) { + return DateFormat.jm().format(dateTime); +} + +//Thu, Jun 30, 2022 - 14:32 +String getFormattedTime(DateTime dateTime) { + return "${getDay(dateTime)}, ${getMonth(dateTime)} ${dateTime.day}, ${dateTime.year} - ${getTime(dateTime)}"; +} + +//30 Jun'22 +String getFormattedDate(DateTime dateTime) { + return "${dateTime.day} ${getMonth(dateTime)}'${getAbbreviationOfYear(dateTime)}"; +} + +String getFullDate(DateTime dateTime) { + return "${getDay(dateTime)}, ${getMonth(dateTime)} ${dateTime.day} ${dateTime.year}"; +} + +String daysLeft(int futureTime) { + final int daysLeft = ((futureTime - DateTime.now().microsecondsSinceEpoch) / + Duration.microsecondsPerDay) + .ceil(); + return '$daysLeft day${daysLeft <= 1 ? "" : "s"}'; +} + +String formatDuration(Duration position) { + final ms = position.inMilliseconds; + + int seconds = ms ~/ 1000; + final int hours = seconds ~/ 3600; + seconds = seconds % 3600; + final minutes = seconds ~/ 60; + seconds = seconds % 60; + + final hoursString = hours >= 10 + ? '$hours' + : hours == 0 + ? '00' + : '0$hours'; + + final minutesString = minutes >= 10 + ? '$minutes' + : minutes == 0 + ? '00' + : '0$minutes'; + + final secondsString = seconds >= 10 + ? '$seconds' + : seconds == 0 + ? '00' + : '0$seconds'; + + final formattedTime = + '${hoursString == '00' ? '' : '$hoursString:'}$minutesString:$secondsString'; + + return formattedTime; +} + +bool isLeapYear(DateTime dateTime) { + final year = dateTime.year; + if (year % 4 == 0) { + if (year % 100 == 0) { + if (year % 400 == 0) { + return true; + } else { + return false; + } + } else { + return true; + } + } else { + return false; + } +} + +String getDayTitle(int timestamp) { + final date = DateTime.fromMicrosecondsSinceEpoch(timestamp); + final now = DateTime.now(); + var title = getDayAndMonth(date); + if (date.year == now.year && date.month == now.month) { + if (date.day == now.day) { + title = "Today"; + } else if (date.day == now.day - 1) { + title = "Yesterday"; + } + } + if (date.year != DateTime.now().year) { + title += " ${date.year}"; + } + return title; +} + +String secondsToHHMMSS(int value) { + int h, m, s; + h = value ~/ 3600; + m = ((value - h * 3600)) ~/ 60; + s = value - (h * 3600) - (m * 60); + final String hourLeft = h.toString().length < 2 ? "0$h" : h.toString(); + + final String minuteLeft = m.toString().length < 2 ? "0$m" : m.toString(); + + final String secondsLeft = s.toString().length < 2 ? "0$s" : s.toString(); + + final String result = "$hourLeft:$minuteLeft:$secondsLeft"; + + return result; +} + +bool isValidDate({ + required int day, + required int month, + required int year, +}) { + if (day < 0 || day > 31 || month < 0 || month > 12 || year < 0) { + return false; + } + if (monthWith30Days.contains(month) && day > 30) { + return false; + } + if (month == 2) { + if (day > 29) { + return false; + } + if (day == 29 && year % 4 != 0) { + return false; + } + } + return true; +} diff --git a/mobile/packages/utils/lib/debouncer.dart b/mobile/packages/utils/lib/debouncer.dart new file mode 100644 index 0000000000..fe1d300cbf --- /dev/null +++ b/mobile/packages/utils/lib/debouncer.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class Debouncer { + final Duration _duration; + final ValueNotifier _debounceActiveNotifier = ValueNotifier(false); + Timer? _debounceTimer; + + Debouncer(this._duration); + + void run(Future Function() fn) { + if (isActive()) { + _debounceTimer!.cancel(); + } + _debounceTimer = Timer(_duration, () async { + await fn(); + _debounceActiveNotifier.value = false; + }); + _debounceActiveNotifier.value = true; + } + + void cancelDebounce() { + if (_debounceTimer != null) { + _debounceTimer!.cancel(); + } + } + + bool isActive() => _debounceTimer != null && _debounceTimer!.isActive; + + ValueNotifier get debounceActiveNotifier { + return _debounceActiveNotifier; + } +} diff --git a/mobile/packages/utils/lib/directory_utils.dart b/mobile/packages/utils/lib/directory_utils.dart new file mode 100644 index 0000000000..c3b36dba08 --- /dev/null +++ b/mobile/packages/utils/lib/directory_utils.dart @@ -0,0 +1,32 @@ +import 'dart:io'; + +import 'package:ente_logging/logging.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +class DirectoryUtils { + static final logger = Logger('DirectoryUtils'); + + static Future getDatabasePath(String databaseName) async { + String? directoryPath; + + directoryPath ??= (await getApplicationSupportDirectory()).path; + + return p.joinAll( + [ + directoryPath, + ".$databaseName", + ], + ); + } + + static Future getDirectoryForInit() async { + final directory = await getApplicationCacheDirectory(); + + return Directory(p.join(directory.path, "init")); + } + + static Future getTempsDir() async { + return await getTemporaryDirectory(); + } +} diff --git a/mobile/packages/utils/lib/email_util.dart b/mobile/packages/utils/lib/email_util.dart new file mode 100644 index 0000000000..ea1c5220ae --- /dev/null +++ b/mobile/packages/utils/lib/email_util.dart @@ -0,0 +1,309 @@ +import 'dart:io'; + +import 'package:archive/archive_io.dart'; +import 'package:email_validator/email_validator.dart'; +import 'package:ente_configuration/base_configuration.dart'; +import 'package:ente_logging/logging.dart'; +import 'package:ente_strings/extensions.dart'; +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/components/dialog_widget.dart'; +import 'package:ente_ui/pages/log_file_viewer.dart'; +import 'package:ente_utils/directory_utils.dart'; +import 'package:ente_utils/platform_util.dart'; +import 'package:ente_utils/share_utils.dart'; +import "package:file_saver/file_saver.dart"; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_email_sender/flutter_email_sender.dart'; +import "package:intl/intl.dart"; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; + +final Logger _logger = Logger('email_util'); + +bool isValidEmail(String? email) { + if (email == null) { + return false; + } + return EmailValidator.validate(email); +} + +Future sendLogs( + BuildContext context, + String toEmail, { + Function? postShare, + String? subject, + String? body, +}) async { + // ignore: unawaited_futures + showDialogWidget( + context: context, + title: context.strings.reportABug, + icon: Icons.bug_report_outlined, + body: context.strings.logsDialogBody, + buttons: [ + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.neutral, + labelText: context.strings.reportABug, + buttonAction: ButtonAction.first, + shouldSurfaceExecutionStates: false, + onTap: () async { + await _sendLogs(context, toEmail, subject, body); + if (postShare != null) { + postShare(); + } + }, + ), + //isInAlert is false here as we don't want to the dialog to dismiss + //on pressing this button + ButtonWidget( + buttonType: ButtonType.secondary, + labelText: context.strings.viewLogs, + buttonAction: ButtonAction.second, + onTap: () async { + // ignore: unawaited_futures + showDialog( + useRootNavigator: false, + context: context, + builder: (BuildContext context) { + return LogFileViewer(SuperLogging.logFile!); + }, + barrierColor: Colors.black87, + barrierDismissible: false, + ); + }, + ), + ButtonWidget( + buttonType: ButtonType.secondary, + labelText: context.strings.exportLogs, + buttonAction: ButtonAction.third, + onTap: () async { + final zipFilePath = await getZippedLogsFile(); + await exportLogs(context, zipFilePath); + }, + ), + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.secondary, + labelText: context.strings.cancel, + buttonAction: ButtonAction.cancel, + ), + ], + ); +} + +Future _sendLogs( + BuildContext context, + String toEmail, + String? subject, + String? body, +) async { + final String zipFilePath = await getZippedLogsFile(); + final Email email = Email( + recipients: [toEmail], + subject: subject ?? '', + body: body ?? '', + attachmentPaths: [zipFilePath], + isHTML: false, + ); + try { + await FlutterEmailSender.send(email); + } catch (e, s) { + _logger.severe('email sender failed', e, s); + Navigator.of(context).pop(); + await shareLogs(context, toEmail, zipFilePath); + } +} + +Future shareLogs( + BuildContext context, + String toEmail, + String zipFilePath, +) async { + final result = await showDialogWidget( + context: context, + title: context.strings.emailYourLogs, + body: context.strings.pleaseSendTheLogsTo(toEmail), + buttons: [ + ButtonWidget( + buttonType: ButtonType.neutral, + labelText: context.strings.copyEmailAddress, + isInAlert: true, + buttonAction: ButtonAction.first, + onTap: () async { + await Clipboard.setData(ClipboardData(text: toEmail)); + }, + shouldShowSuccessConfirmation: true, + ), + ButtonWidget( + buttonType: ButtonType.neutral, + labelText: context.strings.exportLogs, + isInAlert: true, + buttonAction: ButtonAction.second, + ), + ButtonWidget( + buttonType: ButtonType.secondary, + labelText: context.strings.cancel, + isInAlert: true, + buttonAction: ButtonAction.cancel, + ), + ], + ); + if (result?.action != null && result!.action == ButtonAction.second) { + await exportLogs(context, zipFilePath); + } +} + +Future openSupportPage( + String? subject, + String? body, +) async { + const url = "https://github.com/ente-io/ente/discussions/new?category=q-a"; + if (subject != null && body != null) { + await launchUrl( + Uri.parse( + "$url&title=$subject&body=$body", + ), + ); + } else { + await launchUrl(Uri.parse(url)); + } +} + +Future getZippedLogsFile({ + String logsSubPath = "logs", +}) async { + final logsPath = (await getApplicationSupportDirectory()).path; + final logsDirectory = Directory("$logsPath/$logsSubPath"); + final tempPath = (await DirectoryUtils.getTempsDir()).path; + final zipFilePath = "$tempPath/logs.zip"; + final encoder = ZipFileEncoder(); + encoder.create(zipFilePath); + await encoder.addDirectory(logsDirectory); + await encoder.close(); + return zipFilePath; +} + +Future exportLogs( + BuildContext context, + String zipFilePath, [ + bool isSharing = false, +]) async { + if (!isSharing) { + final DateTime now = DateTime.now().toUtc(); + final String shortMonthName = DateFormat('MMM').format(now); // Short month + final String logFileName = + 'ente-logs-${now.year}-$shortMonthName-${now.day}-${now.hour}-${now.minute}'; + + final bytes = await File(zipFilePath).readAsBytes(); + await PlatformUtil.shareFile( + logFileName, + 'zip', + bytes, + MimeType.zip, + ); + } else { + await shareFiles( + [XFile(zipFilePath, mimeType: 'application/zip')], + context: context, + ); + } +} + +Future sendLogsViaEmail( + String toEmail, + String? subject, + String? body, +) async { + final String zipFilePath = await getZippedLogsFile(); + final Email email = Email( + recipients: [toEmail], + subject: subject ?? '', + body: body ?? '', + attachmentPaths: [zipFilePath], + isHTML: false, + ); + try { + await FlutterEmailSender.send(email); + } catch (e, s) { + _logger.severe('email sender failed', e, s); + rethrow; + } +} + +Future sendEmail( + BuildContext context, { + required String to, + String? subject, + String? body, + BaseConfiguration? configuration, +}) async { + try { + final String clientDebugInfo = await _clientInfo(configuration); + final String subject0 = subject ?? '[Support]'; + final String body0 = (body ?? '') + clientDebugInfo; + + if (Platform.isAndroid) { + // Special handling due to issue in proton mail android client + // https://github.com/ente-io/frame/pull/253 + final Uri params = Uri( + scheme: 'mailto', + path: to, + query: 'subject=$subject0&body=$body0', + ); + if (await canLaunchUrl(params)) { + await launchUrl(params); + } else { + // this will trigger _showNoMailAppsDialog + throw Exception('Could not launch ${params.toString()}'); + } + } else { + _showNoMailAppsDialog(context, to); + } + } catch (e) { + _logger.severe("Failed to send email to $to", e); + _showNoMailAppsDialog(context, to); + } +} + +Future _clientInfo(BaseConfiguration? configuration) async { + final packageInfo = await PackageInfo.fromPlatform(); + final String debugInfo = + '\n\n\n\n ------------------- \nFollowing information can ' + 'help us in debugging if you are facing any issue ' + '\nRegistered email: ${configuration?.getEmail() ?? 'N/A'}' + '\nClient: ${packageInfo.packageName}' + '\nVersion : ${packageInfo.version}'; + return debugInfo; +} + +void _showNoMailAppsDialog(BuildContext context, String toEmail) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + icon: const Icon(Icons.email_outlined), + title: Text('Email us at $toEmail'), + actions: [ + TextButton( + onPressed: () async { + await Clipboard.setData(ClipboardData(text: toEmail)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Email address copied')), + ); + }, + child: const Text('Copy Email Address'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ); + }, + ); +} diff --git a/mobile/packages/utils/lib/ente_utils.dart b/mobile/packages/utils/lib/ente_utils.dart new file mode 100644 index 0000000000..d9e96ad0c7 --- /dev/null +++ b/mobile/packages/utils/lib/ente_utils.dart @@ -0,0 +1,7 @@ +export 'debouncer.dart'; +export 'directory_utils.dart'; +export 'email_util.dart'; +export 'fake_progress.dart'; +export 'navigation_util.dart'; +export 'platform_util.dart'; +export 'share_utils.dart'; diff --git a/mobile/packages/utils/lib/fake_progress.dart b/mobile/packages/utils/lib/fake_progress.dart new file mode 100644 index 0000000000..9f4a847918 --- /dev/null +++ b/mobile/packages/utils/lib/fake_progress.dart @@ -0,0 +1,44 @@ +import 'dart:async'; + +import "package:flutter/foundation.dart"; + +typedef FakeProgressCallback = void Function(int count); + +class FakePeriodicProgress { + final FakeProgressCallback? callback; + final Duration duration; + Timer? _timer; + bool _shouldRun = true; + int runCount = 0; + + FakePeriodicProgress({ + required this.callback, + required this.duration, + }); + + void start() { + assert(_shouldRun, "Cannot start a stopped FakePeriodicProgress"); + Future.delayed(duration, _invokePeriodically); + } + + void stop() { + if (_shouldRun) { + _shouldRun = false; + _timer?.cancel(); + } + } + + void _invokePeriodically() { + if (_shouldRun) { + try { + runCount++; + callback?.call(runCount); + } catch (e) { + debugPrint("Error in FakePeriodicProgress callback: $e"); + stop(); + return; + } + _timer = Timer(duration, _invokePeriodically); + } + } +} diff --git a/mobile/packages/utils/lib/navigation_util.dart b/mobile/packages/utils/lib/navigation_util.dart new file mode 100644 index 0000000000..58aa0fb852 --- /dev/null +++ b/mobile/packages/utils/lib/navigation_util.dart @@ -0,0 +1,149 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; + +Future routeToPage( + BuildContext context, + Widget page, { + bool forceCustomPageRoute = false, +}) { + if (Platform.isAndroid || forceCustomPageRoute) { + return Navigator.of(context).push( + _buildPageRoute(page), + ); + } else { + return Navigator.of(context).push( + SwipeableRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) { + return page; + }, + ), + ); + } +} + +void replacePage(BuildContext context, Widget page) { + Navigator.of(context).pushReplacement( + _buildPageRoute(page), + ); +} + +PageRouteBuilder _buildPageRoute(Widget page) { + return PageRouteBuilder( + pageBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return page; + }, + transitionsBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return Align( + child: FadeTransition( + opacity: animation, + child: child, + ), + ); + }, + transitionDuration: const Duration(milliseconds: 200), + opaque: false, + ); +} + +class SwipeableRouteBuilder extends PageRoute { + final RoutePageBuilder pageBuilder; + final PageTransitionsBuilder matchingBuilder = + const CupertinoPageTransitionsBuilder(); // Default iOS/macOS (to get the swipe right to go back gesture) + // final PageTransitionsBuilder matchingBuilder = const FadeUpwardsPageTransitionsBuilder(); // Default Android/Linux/Windows + + SwipeableRouteBuilder({required this.pageBuilder}); + + @override + Null get barrierColor => null; + + @override + Null get barrierLabel => null; + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return pageBuilder(context, animation, secondaryAnimation); + } + + @override + bool get maintainState => true; + + @override + Duration get transitionDuration => const Duration( + milliseconds: 300, + ); // Can give custom Duration, unlike in MaterialPageRoute + + @override + Widget buildTransitions( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return matchingBuilder.buildTransitions( + this, + context, + animation, + secondaryAnimation, + child, + ); + } + + @override + bool get opaque => false; +} + +class TransparentRoute extends PageRoute { + TransparentRoute({ + required this.builder, + super.settings, + }) : assert(builder != null), + super(fullscreenDialog: false); + + final WidgetBuilder? builder; + + @override + bool get opaque => false; + + @override + Null get barrierColor => null; + + @override + Null get barrierLabel => null; + + @override + bool get maintainState => true; + + @override + Duration get transitionDuration => const Duration(milliseconds: 200); + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + final result = builder!(context); + return FadeTransition( + opacity: Tween(begin: 0, end: 1).animate(animation), + child: Semantics( + scopesRoute: true, + explicitChildNodes: true, + child: result, + ), + ); + } +} diff --git a/mobile/packages/utils/lib/platform_util.dart b/mobile/packages/utils/lib/platform_util.dart new file mode 100644 index 0000000000..6ad91b0d88 --- /dev/null +++ b/mobile/packages/utils/lib/platform_util.dart @@ -0,0 +1,70 @@ +import 'dart:io'; + +import 'package:file_saver/file_saver.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher_string.dart'; +import 'package:window_manager/window_manager.dart'; + +class PlatformUtil { + static bool isDesktop() { + return !kIsWeb && + (Platform.isWindows || Platform.isLinux || Platform.isMacOS); + } + + static bool isMobile() { + return !kIsWeb && (Platform.isAndroid || Platform.isIOS); + } + + static bool isWeb() { + return kIsWeb; + } + + static TextSelectionControls get selectionControls => Platform.isAndroid + ? materialTextSelectionControls + : Platform.isIOS + ? cupertinoTextSelectionControls + : desktopTextSelectionControls; + + static openWebView(BuildContext context, String title, String url) async { + // For desktop, always open in external browser + // For mobile, open in external browser (apps can override this if they have web view) + await launchUrlString(url); + } + + static Future shareFile( + String fileName, + String extension, + Uint8List bytes, + MimeType type, + ) async { + try { + if (Platform.isAndroid || Platform.isIOS) { + await FileSaver.instance.saveAs( + name: fileName, + fileExtension: extension, + bytes: bytes, + mimeType: type, + ); + } else { + await FileSaver.instance.saveFile( + name: fileName, + fileExtension: extension, + bytes: bytes, + mimeType: type, + ); + } + } catch (_) {} + } + + // Needed to fix issue with local_auth on Windows + // https://github.com/flutter/flutter/issues/122322 + static Future refocusWindows() async { + if (!Platform.isWindows) return; + await windowManager.blur(); + await windowManager.focus(); + await windowManager.setAlwaysOnTop(true); + await windowManager.setAlwaysOnTop(false); + } +} diff --git a/mobile/packages/utils/lib/share_utils.dart b/mobile/packages/utils/lib/share_utils.dart new file mode 100644 index 0000000000..884901c442 --- /dev/null +++ b/mobile/packages/utils/lib/share_utils.dart @@ -0,0 +1,121 @@ +import 'dart:io'; + +import 'package:ente_strings/extensions.dart'; +import 'package:ente_ui/components/buttons/button_widget.dart'; +import 'package:ente_ui/components/buttons/models/button_type.dart'; +import 'package:ente_ui/components/dialog_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; +import 'package:share_plus/share_plus.dart'; + +Future shareDialog( + BuildContext context, + String title, { + required Function saveAction, + required Function sendAction, +}) async { + final l10n = context.strings; + await showDialogWidget( + context: context, + title: title, + body: Platform.isLinux || Platform.isWindows + ? l10n.saveOnlyDescription + : l10n.saveOrSendDescription, + buttons: [ + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.neutral, + labelText: l10n.save, + buttonAction: ButtonAction.first, + shouldSurfaceExecutionStates: false, + onTap: () async { + await saveAction(); + }, + ), + if (!Platform.isWindows && !Platform.isLinux) + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.secondary, + labelText: l10n.send, + buttonAction: ButtonAction.second, + onTap: () async { + await sendAction(); + }, + ), + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.secondary, + labelText: l10n.cancel, + buttonAction: ButtonAction.cancel, + ), + ], + ); +} + +Rect _sharePosOrigin(BuildContext? context, GlobalKey? key) { + late final Rect rect; + if (context != null) { + rect = shareButtonRect(context, key); + } else { + rect = const Offset(20.0, 20.0) & const Size(10, 10); + } + return rect; +} + +/// Returns the rect of button if context and key are not null +/// If key is null, returned rect will be at the center of the screen +Rect shareButtonRect(BuildContext context, GlobalKey? shareButtonKey) { + Size size = MediaQuery.sizeOf(context); + final RenderObject? renderObject = + shareButtonKey?.currentContext?.findRenderObject(); + RenderBox? renderBox; + if (renderObject != null && renderObject is RenderBox) { + renderBox = renderObject; + } + if (renderBox == null) { + return Rect.fromLTWH(0, 0, size.width, size.height / 2); + } + size = renderBox.size; + final Offset position = renderBox.localToGlobal(Offset.zero); + return Rect.fromCenter( + center: position + Offset(size.width / 2, size.height / 2), + width: size.width, + height: size.height, + ); +} + +Future shareText( + String text, { + BuildContext? context, + GlobalKey? key, +}) async { + try { + final sharePosOrigin = _sharePosOrigin(context, key); + return Share.share( + text, + sharePositionOrigin: sharePosOrigin, + ); + } catch (e, s) { + Logger("ShareUtil").severe("failed to share text", e, s); + return ShareResult.unavailable; + } +} + +Future shareFiles( + List files, { + BuildContext? context, + GlobalKey? key, + String? text, +}) async { + try { + final sharePosOrigin = _sharePosOrigin(context, key); + return Share.shareXFiles( + files, + text: text, + sharePositionOrigin: sharePosOrigin, + ); + } catch (e, s) { + Logger("ShareUtil").severe("failed to share files", e, s); + return ShareResult.unavailable; + } +} diff --git a/mobile/packages/utils/pubspec.lock b/mobile/packages/utils/pubspec.lock new file mode 100644 index 0000000000..154ea69750 --- /dev/null +++ b/mobile/packages/utils/pubspec.lock @@ -0,0 +1,1010 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: "direct main" + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + bip39: + dependency: transitive + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + dio: + dependency: transitive + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + email_validator: + dependency: "direct main" + description: + name: email_validator + sha256: b19aa5d92fdd76fbc65112060c94d45ba855105a28bb6e462de7ff03b12fa1fb + url: "https://pub.dev" + source: hosted + version: "3.0.0" + ente_base: + dependency: transitive + description: + path: "../base" + relative: true + source: path + version: "1.0.0" + ente_configuration: + dependency: "direct main" + description: + path: "../configuration" + relative: true + source: path + version: "1.0.0" + ente_crypto_dart: + dependency: transitive + description: + path: "." + ref: HEAD + resolved-ref: f91e1545f8263df127762240c4da54a0c42835b2 + url: "https://github.com/ente-io/ente_crypto_dart.git" + source: git + version: "1.0.0" + ente_events: + dependency: transitive + description: + path: "../events" + relative: true + source: path + version: "1.0.0" + ente_logging: + dependency: "direct main" + description: + path: "../logging" + relative: true + source: path + version: "1.0.0" + ente_strings: + dependency: "direct main" + description: + path: "../strings" + relative: true + source: path + version: "1.0.0" + ente_ui: + dependency: "direct main" + description: + path: "../ui" + relative: true + source: path + version: "1.0.0" + event_bus: + dependency: transitive + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + expandable: + dependency: transitive + description: + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_saver: + dependency: "direct main" + description: + name: file_saver + sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_email_sender: + dependency: "direct main" + description: + name: flutter_email_sender + sha256: d39eb5e91358fc19ec4050da69accec21f9d5b2b6bcf188aa246327b6ca2352c + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter_inappwebview: + dependency: transitive + description: + name: flutter_inappwebview + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_windows: + dependency: transitive + description: + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_secure_storage: + dependency: transitive + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: transitive + description: + name: fluttertoast + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + url: "https://pub.dev" + source: hosted + version: "8.2.12" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" + http: + dependency: transitive + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + logging: + dependency: "direct main" + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + modal_bottom_sheet: + dependency: transitive + description: + name: modal_bottom_sheet + sha256: eac66ef8cb0461bf069a38c5eb0fa728cee525a531a8304bd3f7b2185407c67e + url: "https://pub.dev" + source: hosted + version: "3.0.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + url: "https://pub.dev" + source: hosted + version: "8.3.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + posix: + dependency: transitive + description: + name: posix + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_linux: + dependency: transitive + description: + name: screen_retriever_linux + sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_macos: + dependency: transitive + description: + name: screen_retriever_macos + sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_platform_interface: + dependency: transitive + description: + name: screen_retriever_platform_interface + sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_windows: + dependency: transitive + description: + name: screen_retriever_windows + sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + sentry: + dependency: transitive + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + sentry_flutter: + dependency: transitive + description: + name: sentry_flutter + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 + url: "https://pub.dev" + source: hosted + version: "11.1.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "9f9f3d372d4304723e6136663bb291c0b93f5e4c8a4a6314347f481a33bda2b1" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + sodium: + dependency: transitive + description: + name: sodium + sha256: d9830a388e37c82891888e64cfd4c6764fa3ac716bed80ac6eab89ee42c3cd76 + url: "https://pub.dev" + source: hosted + version: "2.3.1+1" + sodium_libs: + dependency: transitive + description: + name: sodium_libs + sha256: aa764acd6ccc6113e119c2d99471aeeb4637a9a501639549b297d3a143ff49b3 + url: "https://pub.dev" + source: hosted + version: "2.2.1+6" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + url: "https://pub.dev" + source: hosted + version: "6.3.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + window_manager: + dependency: "direct main" + description: + name: window_manager + sha256: "7eb6d6c4164ec08e1bf978d6e733f3cebe792e2a23fb07cbca25c2872bfdbdcd" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.24.0" diff --git a/mobile/packages/utils/pubspec.yaml b/mobile/packages/utils/pubspec.yaml new file mode 100644 index 0000000000..e208662108 --- /dev/null +++ b/mobile/packages/utils/pubspec.yaml @@ -0,0 +1,40 @@ +name: ente_utils +description: A Flutter package containing shared utility functions for Ente apps +version: 1.0.0 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + archive: ^4.0.7 + email_validator: ^3.0.0 + ente_configuration: + path: ../../packages/configuration + ente_logging: + path: ../../packages/logging + ente_strings: + path: ../../packages/strings + ente_ui: + path: ../../packages/ui + file_saver: ^0.3.1 + flutter: + sdk: flutter + flutter_email_sender: ^7.0.0 + intl: ^0.20.2 + logging: ^1.3.0 + package_info_plus: ^8.1.1 + path: ^1.9.0 + path_provider: ^2.1.5 + share_plus: ^11.0.0 + url_launcher: ^6.3.1 + window_manager: ^0.5.0 + + +dev_dependencies: + flutter_lints: ^5.0.0 + flutter_test: + sdk: flutter + +flutter: diff --git a/server/config/compose.yaml b/server/config/compose.yaml index aa2189c545..6cb55c86cb 100644 --- a/server/config/compose.yaml +++ b/server/config/compose.yaml @@ -21,7 +21,7 @@ services: command: "TCP-LISTEN:3200,fork,reuseaddr TCP:minio:3200" postgres: - image: postgres:15 + image: postgres:15-trixie env_file: - .env # Wait for postgres to accept connections before starting museum. diff --git a/server/config/example.yaml b/server/config/example.yaml index 0fbd2f61b7..156f613d6e 100644 --- a/server/config/example.yaml +++ b/server/config/example.yaml @@ -23,12 +23,17 @@ s3: # Only path-style URL works if disabling are_local_buckets with MinIO use_path_style_urls: true b2-eu-cen: + # Uncomment the below configuration to override the top-level configuration + # are_local_buckets: true + # use_path_style_urls: true key: secret: endpoint: localhost:3200 region: eu-central-2 bucket: b2-eu-cen wasabi-eu-central-2-v3: + # are_local_buckets: true + # use_path_style_urls: true key: secret: endpoint: localhost:3200 @@ -36,6 +41,8 @@ s3: bucket: wasabi-eu-central-2-v3 compliance: false scw-eu-fr-v3: + # are_local_buckets: true + # use_path_style_urls: true key: secret: endpoint: localhost:3200 diff --git a/server/configurations/local.yaml b/server/configurations/local.yaml index 70d7589280..388c1a80a5 100644 --- a/server/configurations/local.yaml +++ b/server/configurations/local.yaml @@ -135,12 +135,17 @@ s3: # primary: b2-eu-cen # secondary: wasabi-eu-central-2-v3 b2-eu-cen: + # Uncomment to override top-level bucket configuration for URL addressing and external buckets + # are_local_buckets: true + # use_path_style_urls: true key: secret: endpoint: region: bucket: wasabi-eu-central-2-v3: + # are_local_buckets: true + # use_path_style_urls: true key: secret: endpoint: @@ -152,6 +157,8 @@ s3: # Currently this flag is only honoured for the Wasabi v3 bucket. compliance: true scw-eu-fr-v3: + # are_local_buckets: true + # use_path_style_urls: true key: secret: endpoint: diff --git a/server/quickstart.sh b/server/quickstart.sh index a656437917..6b042a728b 100755 --- a/server/quickstart.sh +++ b/server/quickstart.sh @@ -167,14 +167,23 @@ db: password: $pg_pass s3: + # Top-level configuration for buckets, you can override by specifying these configuration in the desired bucket. + # Set this to false if using external object storage bucket or bucket with SSL are_local_buckets: true + # Set this to false if using subdomain-style URL. This is set to true for ensuring compatibility with MinIO when SSL is enabled. + use_path_style_urls: true b2-eu-cen: + # Uncomment the below configuration to override the top-level configuration + # are_local_buckets: true + # use_path_style_urls: true key: $minio_user secret: $minio_pass endpoint: localhost:3200 region: eu-central-2 bucket: b2-eu-cen wasabi-eu-central-2-v3: + # are_local_buckets: true + # use_path_style_urls: true key: $minio_user secret: $minio_pass endpoint: localhost:3200 @@ -182,6 +191,8 @@ s3: bucket: wasabi-eu-central-2-v3 compliance: false scw-eu-fr-v3: + # are_local_buckets: true + # use_path_style_urls: true key: $minio_user secret: $minio_pass endpoint: localhost:3200 diff --git a/web/packages/base/locales/fr-FR/translation.json b/web/packages/base/locales/fr-FR/translation.json index 49522f9ec7..11b5a723c4 100644 --- a/web/packages/base/locales/fr-FR/translation.json +++ b/web/packages/base/locales/fr-FR/translation.json @@ -686,16 +686,16 @@ "shared_favorites": "Favoris partagés", "added_by_name": "Ajouté par {{name}}", "unowned_files_not_processed": "Les fichiers ajoutés par d'autres utilisateurs n'ont pas été traités", - "custom_domains": "", - "custom_domains_desc": "", - "link_your_domain": "", - "domain": "", - "domain_help": "", - "invalid_domain": "", - "already_linked_domain": "", - "add_dns_entry": "", - "add_dns_entry_hint": "", - "custom_domains_help": "", - "num_1": "", - "num_2": "" + "custom_domains": "Domaines personnalisés", + "custom_domains_desc": "Utiliser votre propre domaine lors du partage", + "link_your_domain": "Lier votre domaine", + "domain": "Domaine", + "domain_help": "N'importe quel domaine ou sous-domaine que vous possédez", + "invalid_domain": "Domaine non valide", + "already_linked_domain": "Le domaine déjà lié par un utilisateur", + "add_dns_entry": "Ajouter l'entrée DNS", + "add_dns_entry_hint": "Chez votre fournisseur DNS, ajoutez un CNAME de votre domaine {{host}}", + "custom_domains_help": "Pour plus d'informations, voir l'help", + "num_1": "1", + "num_2": "2" } diff --git a/web/packages/base/locales/lt-LT/translation.json b/web/packages/base/locales/lt-LT/translation.json index 924af32e1e..91d3494e06 100644 --- a/web/packages/base/locales/lt-LT/translation.json +++ b/web/packages/base/locales/lt-LT/translation.json @@ -686,16 +686,16 @@ "shared_favorites": "Bendrinami mėgstami", "added_by_name": "Įtraukė {{name}}", "unowned_files_not_processed": "Kiti naudotojai pridėti failai nebuvo apdoroti", - "custom_domains": "", - "custom_domains_desc": "", - "link_your_domain": "", - "domain": "", - "domain_help": "", - "invalid_domain": "", - "already_linked_domain": "", - "add_dns_entry": "", - "add_dns_entry_hint": "", - "custom_domains_help": "", - "num_1": "", - "num_2": "" + "custom_domains": "Pasirinktiniai domenai", + "custom_domains_desc": "Naudokite savo domeną, kai bendrinate.", + "link_your_domain": "Susieti savo domeną", + "domain": "Domenas", + "domain_help": "Bet kuris jūsų turimas domenas arba subdomenas", + "invalid_domain": "Netinkamas domenas.", + "already_linked_domain": "Domenas jau susietas su naudotoju.", + "add_dns_entry": "Pridėti DNS elementą", + "add_dns_entry_hint": "Savo DNS teikėjoje pridėkite „CNAME“ iš savo domeno į {{host}}.", + "custom_domains_help": "Dėl daugiau informacijos žiūrėkite pagalbą.", + "num_1": "1", + "num_2": "2" } diff --git a/web/packages/base/locales/ru-RU/translation.json b/web/packages/base/locales/ru-RU/translation.json index 7218c486f7..fe0728c1f1 100644 --- a/web/packages/base/locales/ru-RU/translation.json +++ b/web/packages/base/locales/ru-RU/translation.json @@ -686,16 +686,16 @@ "shared_favorites": "Общие избранные", "added_by_name": "Добавлено {{name}}", "unowned_files_not_processed": "Файлы, добавленные другими пользователями, не были обработаны", - "custom_domains": "", - "custom_domains_desc": "", - "link_your_domain": "", - "domain": "", - "domain_help": "", - "invalid_domain": "", - "already_linked_domain": "", - "add_dns_entry": "", - "add_dns_entry_hint": "", - "custom_domains_help": "", - "num_1": "", - "num_2": "" + "custom_domains": "Пользовательские домены", + "custom_domains_desc": "Используйте свой собственный домен при публикации", + "link_your_domain": "Привязать ваш домен", + "domain": "Домен", + "domain_help": "Любой домен или поддомен, которым вы владеете", + "invalid_domain": "Неверный домен", + "already_linked_domain": "Домен уже связан пользователем", + "add_dns_entry": "Добавить DNS-запись", + "add_dns_entry_hint": "На стороне вашего DNS-провайдера добавьте CNAME из вашего домена в {{host}}", + "custom_domains_help": "Для получения дополнительной информации см. помощь", + "num_1": "1", + "num_2": "2" } diff --git a/web/packages/base/locales/vi-VN/translation.json b/web/packages/base/locales/vi-VN/translation.json index 6cf4877fd5..af041df4a8 100644 --- a/web/packages/base/locales/vi-VN/translation.json +++ b/web/packages/base/locales/vi-VN/translation.json @@ -686,16 +686,16 @@ "shared_favorites": "Những mục thích đã chia sẻ", "added_by_name": "Được thêm bởi {{name}}", "unowned_files_not_processed": "Các tệp được thêm bởi người dùng khác không được xử lý", - "custom_domains": "", - "custom_domains_desc": "", - "link_your_domain": "", - "domain": "", - "domain_help": "", - "invalid_domain": "", - "already_linked_domain": "", - "add_dns_entry": "", - "add_dns_entry_hint": "", - "custom_domains_help": "", - "num_1": "", - "num_2": "" + "custom_domains": "Tùy chỉnh tên miền", + "custom_domains_desc": "Dùng tên miền của bạn khi chia sẻ", + "link_your_domain": "Liên kết với tên miền", + "domain": "Tên miền", + "domain_help": "Bất kỳ tên miền hoặc tên miền phụ nào của bạn", + "invalid_domain": "Tên miền không hợp lệ", + "already_linked_domain": "Tên miền đã được liên kết với người dùng khác", + "add_dns_entry": "Thêm entry DNS", + "add_dns_entry_hint": "Trên trình quản lý của nhà cung cấp DNS, thêm một CNAME từ tên miền của bạn đến {{host}}", + "custom_domains_help": "Để biết thêm chi tiết, xem trợ giúp", + "num_1": "1", + "num_2": "2" } diff --git a/web/packages/base/locales/zh-CN/translation.json b/web/packages/base/locales/zh-CN/translation.json index 655aeae950..16b109545f 100644 --- a/web/packages/base/locales/zh-CN/translation.json +++ b/web/packages/base/locales/zh-CN/translation.json @@ -1,5 +1,5 @@ { - "intro_slide_1_title": "私人备份
为您的回忆", + "intro_slide_1_title": "为您的回忆
私密备份", "intro_slide_1": "默认端到端加密", "intro_slide_2_title": "安全地存放
在一个掩护所中", "intro_slide_2": "经久耐用", @@ -686,16 +686,16 @@ "shared_favorites": "已共享的收藏", "added_by_name": "由{{name}}添加", "unowned_files_not_processed": "由其他用户添加的文件未被处理", - "custom_domains": "", - "custom_domains_desc": "", - "link_your_domain": "", - "domain": "", - "domain_help": "", - "invalid_domain": "", - "already_linked_domain": "", - "add_dns_entry": "", - "add_dns_entry_hint": "", - "custom_domains_help": "", - "num_1": "", - "num_2": "" + "custom_domains": "自定义域名", + "custom_domains_desc": "分享时使用您自己的域名", + "link_your_domain": "链接您的域名", + "domain": "域名", + "domain_help": "您拥有的任何域名或子域名", + "invalid_domain": "无效的域名", + "already_linked_domain": "域名已被其他用户链接", + "add_dns_entry": "添加 DNS 条目", + "add_dns_entry_hint": "在您的 DNS 提供商上,将您域中的 CNAME 添加到 {{host}}", + "custom_domains_help": "欲了解更多信息,请参阅 help", + "num_1": "1", + "num_2": "2" }