diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 78837ef..ebd538a 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -2,13 +2,14 @@ Skylight Wallet is an open-source and self-custody wallet for Monero created by FEATURES * A beautiful, modern interface available in light and dark themes. -* Fully self-custody. Only you have your private keys and seed, not us. +* Fully self-custody. Only you have your private keys and recovery phrase, not us. * Completely open-source, with reproducible builds. -* No local wallet scanning needed! Syncing can be handled by the LWS you specify. Save time, data, and your battery. Alternatively, you can choose to connect to a Monero node and sync locally. -* Built-in Tor support. Optionally connect to your LWS or node over Tor to protect your privacy. * Address book for managing contacts. -* Background notifications for incoming transactions, and background syncing if you use a remote node. -* OpenAlias, for easy and secure sending without cumbersome addresses. -* Subaddress support. +* No local wallet scanning needed. You can offload scanning to servers of your choice, or you can scan locally with a node connection if you prefer. +* Built-in Tor support. Optionally connect to services over Tor to protect your privacy. +* Protect your wallet with hardware security keys. When enabled, your spend key and recovery phrase have multiple layers of protection. +* Encrypted backups of your payment details and address book. They are securely encrypted with keys derived from your recovery phrase, so you don't need another backup password. +* Background notifications for incoming transactions and background syncing. +* OpenAlias and payment links, for easy and secure sending without cumbersome addresses. Skylight Wallet is developed by MAGIC Grants, a 501(c)(3) public charity. diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png new file mode 100644 index 0000000..7364e6e Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png new file mode 100644 index 0000000..7076133 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png new file mode 100644 index 0000000..f6de28e Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png new file mode 100644 index 0000000..43a6d57 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png new file mode 100644 index 0000000..2811bfa Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png new file mode 100644 index 0000000..3ed68fc Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png new file mode 100644 index 0000000..28345e4 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/08.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/08.png new file mode 100644 index 0000000..76e0379 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/08.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/09.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/09.png new file mode 100644 index 0000000..f0ff48a Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/09.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png deleted file mode 100644 index 760e8dc..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png deleted file mode 100644 index 7ac725e..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png deleted file mode 100644 index 8964837..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png deleted file mode 100644 index 8299782..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png deleted file mode 100644 index b932093..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png deleted file mode 100644 index 1c43aef..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png deleted file mode 100644 index d18615e..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png deleted file mode 100644 index da76697..0000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png and /dev/null differ diff --git a/integration_test/store_screenshots_test.dart b/integration_test/store_screenshots_test.dart new file mode 100644 index 0000000..2ef6b66 --- /dev/null +++ b/integration_test/store_screenshots_test.dart @@ -0,0 +1,288 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:provider/provider.dart'; +import 'package:skylight_wallet/main.dart' as app; +import 'package:skylight_wallet/models/contact_model.dart'; +import 'package:skylight_wallet/models/fiat_rate_model.dart'; +import 'package:skylight_wallet/services/tor_settings_service.dart'; +import 'package:skylight_wallet/wallet_core_glue.dart' show appCoins; +import 'package:wallet_domain/screenshots.dart'; +import 'package:wallet_backup/screenshots.dart'; +import 'package:wallet_backup_platform/wallet_backup_platform.dart' + show ICloudLocation, MetadataBackupSetup; +import 'package:wallet_domain/wallet_domain.dart' show WalletManager; +import 'package:wallet_fhse/screenshots.dart'; +import 'package:wallet_fhse/security_keys_ui.dart' show AdvancedSecurityScreen; +import 'package:wallet_fiat/wallet_fiat.dart' show FiatRates; +import 'package:wallet_monero/screenshots.dart'; + +/// The App Store and F-Droid screenshots: the real app on a sample wallet, +/// walked through the screens the listings show. +/// +/// Not a test to run on its own. wallet-core's `scripts/store-screenshots.sh` +/// runs it (through scripts/store-screenshots.sh here) and takes each picture +/// [StoreShots.capture] asks for. Nothing reaches a server: the wallet is +/// [SampleMoneroWallet], prices are [FiatRates.fixedRates], and the security +/// keys and the backup are samples that only tell their screens so. +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized().framePolicy = + LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; + + testWidgets('store screenshots', (tester) async { + // A test build is a debug build: no DEBUG ribbon, and typing goes straight + // to the field rather than raising the system keyboard over the screen. + WidgetsApp.debugAllowBannerOverride = false; + tester.testTextInput.register(); + FiatRates.fixedRates = const {'XMR': _xmrPrice}; + appCoins = () => [SampleMoneroWallet(_activity())]; + sampleSecurityKeysState = _securityKeys(); + MetadataBackupSetup.serviceForTesting = (locations) => SampleMetadataBackupService( + locations: locations, + on: {ICloudLocation.locationId, MetadataBackupSetup.autoBackupLocationId}, + payments: 4, + contacts: _contacts.length, + lastSync: _now.subtract(const Duration(minutes: 3)), + ); + // main() installs the app's own error logger; the test framework needs its + // own back before a failure reaches it, or the run hangs instead of ending. + final onError = FlutterError.onError; + app.main(); + try { + // Onboarding, from the welcome screen to a restored sample wallet. + await _tap(tester, find.text('Get Started')); + await _tap(tester, find.text('No Tor')); + await _tap(tester, find.text('Continue')); + await _tap(tester, find.text('Continue')); + await tester.enterText(find.byType(TextField).first, 'lws.example.com:18090'); + await _tap(tester, find.text('Test Connection')); + await _waitFor(tester, find.text('Connection works')); + await _tap(tester, find.text('Continue')); + await _tap(tester, find.text('Restore Existing Wallet')); + await Clipboard.setData(const ClipboardData(text: _seed)); + await _tap(tester, find.byTooltip('Paste')); + await _tap(tester, find.text('Restore').last); + // No backup to import: the sample wallet starts from its seed alone. + await _tap(tester, find.text('Skip')); + await _tap(tester, find.text('Continue')); + + // The wallet, behind Tor, with an address book. + await _waitFor(tester, find.text('Receive')); + final context = tester.element(find.text('Receive').first); + await _useExternalTor(context); + final manager = Provider.of(context, listen: false); + final xmr = manager.getWallet('XMR')!; + xmr.setConnection( + address: 'lws.example.com:18090', + proxyPort: '', + useTor: true, + connectionType: 'lws', + ); + await xmr.persistCurrentConnection(); + await manager.applyConnectionChange('XMR'); + await xmr.load(); + await _addContacts(tester, Provider.of(context, listen: false), { + for (final MapEntry(key: name, value: address) in _contacts.entries) name: {'XMR': address}, + }); + + // The pictures, in the order the app is walked; scripts/store-screenshots.sh + // puts them in the listing's order. History first: the home it returns + // to is a fresh one, whose security checklist reads the settings above. + await _tap(tester, find.text('History')); + await _shot(tester, 'history', light: false, dark: true); + await _tap(tester, find.text('Wallet')); + await _shot(tester, 'wallet_home', light: false, dark: true); + + await _tap(tester, find.text('Receive')); + await _waitFor(tester, find.text('Copy Address')); + await _shot(tester, 'receive'); + await _back(tester); + + await _tap(tester, find.text('Send')); + final fields = find.byType(TextField); + await tester.enterText(fields.at(0), _payee); + await tester.enterText(fields.at(1), '0.25'); + await _shot(tester, 'send'); + await _back(tester); + + await _tap(tester, find.text('Address Book')); + await _tap(tester, find.text('Alice')); + await _shot(tester, 'address_book'); + + await _tap(tester, find.text('Settings').last); + await _tap(tester, find.text('Tor Settings')); + await _shot(tester, 'tor_settings', light: false, dark: true); + await _back(tester); + await _tap(tester, find.text('Connection Settings')); + await _tap(tester, find.text('Test Connection')); + await _waitFor(tester, find.text('Connection works')); + await _shot(tester, 'connection_settings'); + await _back(tester); + + await _open(tester, '/metadata_backup'); + await _shot(tester, 'backup'); + await _back(tester); + // Not the app's own route: that one asks the device to unlock first. + await _push(tester, const AdvancedSecurityScreen()); + await _shot(tester, 'advanced_security'); + await _back(tester); + } catch (_) { + // Where it stopped, for whoever runs this next: the screen, as the + // script's -failure.png, and the text on it. + final texts = tester.widgetList(find.byType(Text)).map((t) => t.data); + debugPrint('store shots: stopped on: ${texts.nonNulls.join(' | ')}'); + await StoreShots.capture('_failure'); + rethrow; + } finally { + FlutterError.onError = onError; + } + }); +} + +// ----- The sample wallet ----- + +/// A throwaway wallet made for these pictures. It never held funds, and its +/// recovery phrase is public on purpose: it is in this file. +const _seed = + 'glass work jacket reason gas hat question eagle surround airport ' + 'document whale dwarf roast wealth immune'; + +const _xmrPrice = 521.38; + +/// Addresses of keys made for these pictures and thrown away: valid, and +/// unspendable by anyone. +const _contacts = { + 'Alice': '45JEhQFx1on7V9yXvTbmXfKJw9VyqDeW8N9YqtbbqimLPV89PJiocbSaUg8FyoUbktQ7c7YGHCgNzNY8Y9BWNEucD933fzZ', + 'Bob': '43cda4NMuFkcnhxHQd4h9VDTfPCBER8tkNoTPtKFrx27YwnSZJYJG7pEZm2moL1wyCCaW37nhXbYLPrBQoDDrMrNKCBVnjZ', + 'Charlie': '44GxkeKtSiiFSNk5WXA2qdRdwEugoDJwsKGFZz9ERGNSWR8HtmVLwEeZYNyeuo2Cg45AwMsGKiD5g9V6rTnacgnn1hC7QNA', + 'David': '41tDUaQwyAgTStMK4JRpPBEHRW8EFnYBu5q9QnvLroTVQpVX432zho58VSRrwAYW7haS46rcnwFPg4yJQVpatUzPTmT6hhd', + 'Eve': '4B1e8NPHWbaKShBH4hGeqEK8TwLJBrh1eGWYVeEff985XLgQQLKt87aVcFEdpgXJ8mQaQNZBNjcmPAdvb9zpPSk8AS9xwNM', +}; +const _payee = + '461qQuRxsfjAJawskpPKqo7qhBtZdm7uMXzca8U2Cp8gTz6D8fqG1HWJPqn4Y91jNL7fAbX8XjgR3LCJcoxCha1HVVf8K7T'; + +/// 9:41 today, the time the status bar shows. +DateTime get _now { + final now = DateTime.now(); + return DateTime(now.year, now.month, now.day, 9, 41); +} + +/// Two security keys set up three weeks ago, as Advanced Security shows them. +/// Nothing is set up: only the screen is told so. +SecurityKeysState _securityKeys() { + final added = _now.subtract(const Duration(days: 21)); + return SecurityKeysState( + available: true, + engaged: true, + keys: [ + SecurityKeyRecord(id: 'sample-1', name: 'YubiKey 5C NFC', addedAt: added, serial: 28417035), + SecurityKeyRecord(id: 'sample-2', name: 'Backup key', addedAt: added, serial: 28417291), + ], + ); +} + +SampleActivity _activity() { + return SampleActivity( + decimals: 12, + price: _xmrPrice, + balance: '8.2394', + fee: '0.00003074', + height: 3790200, + blockTime: const Duration(minutes: 2), + counterparty: _contacts['Alice']!, + now: _now, + txs: const [ + SampleTx(daysAgo: 0, at: (9, 34), incoming: true, fiat: 182, confirmations: 3), + SampleTx(daysAgo: 1, at: (18, 29), incoming: false, fiat: 47.5), + SampleTx(daysAgo: 4, at: (13, 52), incoming: true, fiat: 128), + SampleTx(daysAgo: 9, at: (11, 7), incoming: false, fiat: 12.75), + SampleTx(daysAgo: 16, at: (16, 45), incoming: true, fiat: 199), + SampleTx(daysAgo: 29, at: (10, 18), incoming: false, fiat: 310), + SampleTx(daysAgo: 43, at: (15, 26), incoming: true, fiat: 1250), + SampleTx(daysAgo: 61, at: (12, 3), incoming: true, fiat: 2400), + SampleTx(daysAgo: 78, at: (19, 40), incoming: false, fiat: 85), + ], + ); +} + +// ----- Driving the app ----- + +/// Adds [book] to the address book, once the model's own first read of the +/// stored (empty) book is over: one that lands after an add drops it. +Future _addContacts( + WidgetTester tester, + ContactModel contacts, + Map> book, +) async { + await _settle(tester, const Duration(seconds: 2)); + await contacts.load(); + for (final MapEntry(key: name, value: addresses) in book.entries) { + await contacts.addContact(name, addresses); + } + final missing = book.keys.where((name) => !contacts.contacts.any((c) => c.name == name)); + if (missing.isNotEmpty) throw StateError('Contacts not saved: ${missing.join(', ')}'); +} + +/// Tor as the listing shows it: the user's own (External, port 9050) for prices +/// and the server. Written straight into the saved settings, because the Tor +/// screens test that Tor first, and here there is none; nothing goes through it. +Future _useExternalTor(BuildContext context) async { + await TorSettingsService.sharedInstance.save(torMode: TorMode.external, socksPort: '9050'); + await FiatRateModel.saveFiatApiMode(FiatApiMode.torOnly); + if (!context.mounted) return; + await Provider.of(context, listen: false).startService(); +} + +Future _waitFor( + WidgetTester tester, + Finder finder, { + Duration timeout = const Duration(seconds: 60), +}) async { + final end = DateTime.now().add(timeout); + while (finder.evaluate().isEmpty) { + if (DateTime.now().isAfter(end)) throw StateError('Never appeared: $finder'); + await tester.pump(const Duration(milliseconds: 200)); + } +} + +Future _settle(WidgetTester tester, Duration duration) async { + final end = DateTime.now().add(duration); + while (DateTime.now().isBefore(end)) { + await tester.pump(const Duration(milliseconds: 100)); + } +} + +Future _tap(WidgetTester tester, Finder finder) async { + await _waitFor(tester, finder); + await tester.ensureVisible(finder.first); + await _settle(tester, const Duration(milliseconds: 300)); + await tester.tap(finder.first); + await _settle(tester, const Duration(milliseconds: 800)); +} + +Future _open(WidgetTester tester, String route) async { + tester.state(find.byType(Navigator).first).pushNamed(route); + await _settle(tester, const Duration(milliseconds: 1200)); +} + +Future _push(WidgetTester tester, Widget screen) async { + tester + .state(find.byType(Navigator).first) + .push(MaterialPageRoute(builder: (_) => screen)); + await _settle(tester, const Duration(milliseconds: 1200)); +} + +Future _back(WidgetTester tester) async { + await tester.state(find.byType(Navigator).first).maybePop(); + await _settle(tester, const Duration(milliseconds: 800)); +} + +/// Waits for the screen to come to rest, then has the host take it. +Future _shot(WidgetTester tester, String name, {bool light = true, bool dark = false}) async { + // No blinking cursor in a field just typed into. + FocusManager.instance.primaryFocus?.unfocus(); + await _settle(tester, const Duration(milliseconds: 1500)); + debugPrint('store shots: $name'); + await StoreShots.capture(name, light: light, dark: dark); +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0318568..0f997f9 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,423 +1,73 @@ { "@@locale": "en", - "scanQrUnexpectedCode": "That QR isn't a valid address or payment code.", - "deepLinkCoinNotConfigured": "Please configure your {chain} connection first.", - "@deepLinkCoinNotConfigured": { - "placeholders": { - "chain": { - "type": "String" - } - } - }, - "continueText": "Continue", - "cancel": "Cancel", - "done": "Done", - "close": "Close", - "back": "Back", - "unknownError": "Unknown error.", - "warning": "Warning", - "amount": "Amount", - "networkFee": "Network Fee", - "address": "Address", - "pending": "Pending", - "copy": "Copy", - "addressCopied": "Address copied to clipboard", - "fieldEmptyError": "This field cannot be empty.", "welcomeDescription": "Skylight Wallet is one of the simplest Monero wallets. We will help you set up a wallet and connect to a server.", "welcomeGetStarted": "Get Started", "welcomeAgreePrefix": "By continuing you agree to the ", "welcomeTermsLink": "Terms of Service", "welcomeAgreeMiddle": " and ", "welcomePrivacyLink": "Privacy Policy", - "settingsLicensesLabel": "Open-Source Licenses", - "lwsSetupTitle": "Connection Setup", "lwsSetupDescription": "Connect to a Monero light-wallet server (LWS) or your own full node. Only select a server you trust. Even if you use Tor, this server can learn information about you. With an LWS, your private view key and primary address will be shared with this server.", "lwsSetupProxyPortLabel": "HTTP Proxy Port (Optional)", "lwsSetupProxyPortHint": "4444 for I2P", - "lwsSetupUseTorLabel": "Use Tor", - "lwsSetupTestConnectionButton": "Test Connection", - "lwsSetupStartingTor": "Starting Tor...", - "lwsSetupContinueButton": "Continue", - "connectionTypeLws": "Light Wallet Server", - "connectionTypeNode": "Monero Node", - "connectionRemoteIpNotAllowed": "Connections to remote IP addresses aren't allowed. Use a domain name or a local IP address.", - "connectionLocalOverTorNotAllowed": "A local address can't be reached through Tor. Turn Tor off, or use the server's onion address.", - "connectionOnionNeedsTor": "An onion address can only be reached through Tor. Turn Tor on to use it.", - "connectionPathNotSupported": "This server is reached at its root, so its address can't include a path like /wallet. Remove the path.", - "lwsAwaitingApprovalAccount": "Your light wallet server hasn't approved this wallet yet. Ask the server's operator to approve it so you can see your balance and transactions.", - "lwsAwaitingApprovalRestoreHeight": "Your light wallet server hasn't approved scanning from your restore height yet, so older transactions may be missing. Ask the server's operator to approve it.", - "connectionTorDisabledWarning": "Tor is disabled, so this connection no longer works. Please reconfigure it.", - "connectionProtocolHttps": "Removing protocol. Using HTTPS for domains.", - "connectionProtocolHttp": "Removing protocol. Using HTTP for local addresses.", - "connectionProtocolHttpOnion": "Removing protocol. Onion addresses use HTTP over Tor.", - "connectionTestStop": "Stop", - "connectionTestingTitle": "Testing connection", - "connectionTestingDetail": "Checking whether the server answers.", - "connectionTestAgain": "Test again", - "connectionResultWorksTitle": "Connection works", - "connectionResultFailedTitle": "Could not reach this server", - "connectionResultFailedDetail": "Nothing answered. Check the address and port, and whether the server accepts your connection.", - "connectionReachedOverTor": "Reached over Tor", - "connectionReachedViaProxy": "Reached through your proxy", - "connectionReachedDirect": "Reached directly", "settingsConnectionSettingsLabel": "Connection Settings", "settingsConnectionServerLws": "LWS", "settingsConnectionServerNode": "Node", "settingsConnectionOverTor": "over Tor", "settingsConnectionOverClearnet": "over Clearnet", - "settingsConnectionInLocalNetwork": "in Local Network", - "homeBlocksRemaining": "{count, plural, =1{1 block left} other{{count} blocks left}}", - "@homeBlocksRemaining": { - "placeholders": { - "count": { - "type": "int", - "format": "decimalPattern" - } - } - }, - "fiatApiSetupTitle": "Price Display Setup", + "settingsConnectionInLocalNetwork": "in local network", "fiatApiSetupDescription": "Skylight Wallet can automatically fetch the latest Monero price. Your balances are not sent to the server. How do you want to fetch this price data?", - "fiatApiSettingsModeLabel": "Mode", - "fiatApiSettingsModeTorOnly": "Tor-Only", - "fiatApiSettingsModeClearnet": "Clearnet-Only (Not Private)", - "fiatApiSettingsModeDisabled": "Disabled", - "fiatModeTorOnlyDesc": "Prices fetched over Tor · recommended", - "fiatModeClearnetDesc": "Not private, the price server sees your IP address", - "fiatModeDisabledDesc": "No prices fetched, balances shown in crypto only", - "settingsOpenAliasSettingsLabel": "OpenAlias Settings", - "openAliasSettingsSheetSubtitle": "How names like name@example.com are looked up when you send.", - "openAliasModeTorOnlyDesc": "Names looked up over Tor · recommended", - "openAliasModeClearnetDesc": "Not private, DNS servers see your IP address and the name", - "openAliasModeDisabledDesc": "No lookups, send to addresses only", - "fiatApiSettingsDisplayCurrencyLabel": "Display Currency", - "createWalletTitle": "Wallet Setup", "createWalletDescription": "Would you like to create a new wallet or restore an existing wallet?", - "createWalletRestoreExistingButton": "Restore Existing", "createWalletRestoreExistingDesc": "Enter an existing Monero recovery phrase", - "createWalletCreateNewButton": "Create New", "createWalletCreateNewDesc": "Skylight Wallet generates a new Polyseed recovery phrase", - "createWalletPasswordTitle": "Create Wallet Password", - "createWalletPasswordDescription": "Create a password to protect your wallet. This password will be required to unlock your wallet.", - "createWalletPasswordHint": "Enter your password", - "createWalletConfirmPasswordHint": "Confirm your password", - "passwordTooShortError": "Password must be at least 8 characters long.", - "passwordsDoNotMatchError": "Passwords do not match.", - "generateSeedTitle": "Write these down, in order", - "generateSeedTitleCovered": "Recovery Phrase", "generateSeedSubtitleCovered": "These words, in this order, are your wallet. Write them down and keep them in a physical safe. If you lose these words or if you share them with anyone else, you will lose your money permanently. Careful planning now avoids a potential disaster later.", - "generateSeedSubtitleRevealed": "Securely save these. Do not share them.", - "generateSeedScreenshotNote": "Screenshots are blocked on this screen. Make sure nobody is looking over your shoulder.", - "generateSeedReveal": "Tap to reveal", - "generateSeedConfirm": "I have written down all the words and stored them somewhere only I can reach.", - "generateSeedBirthdayLabel": "Wallet birthday", - "generateSeedBirthdayReason": "Where a future restore starts scanning", - "onboardingTorNotePrivacy": "Connections over Tor can be slower.", - "onboardingTorNoteChangeable": "You can change this later in Settings.", - "onboardingPriceNoteRatesOnly": "Only prices are requested. Your addresses and balances are not sent.", - "onboardingPriceNoteTor": "Prices are fetched over Tor by default.", "onboardingConnectionNoteServer": "Use a light-wallet server (LWS) or your own Monero node.", "onboardingConnectionNoteChangeable": "You can change this later in Settings.", - "onboardingWalletNoteGenerated": "Skylight Wallet generates your recovery phrase on this device.", - "onboardingWalletNoteRestore": "When restoring, a scan-from date makes syncing faster.", - "onboardingSeedNotePassword": "Skylight Wallet asks for your password before showing these words again.", - "onboardingPasswordNoteLaunch": "You need this password to open your wallet and to view your recovery phrase.", - "onboardingPasswordNoteNotCloud": "This password can't be recovered. If you lose it, restore your wallet from your recovery phrase.", - "onboardingPasswordStrong": "Strong", - "onboardingPasswordMatch": "Passwords match", "lwsDetailsTitle": "Wallet Details", - "lwsDetailsDescription": "If your Monero light wallet server (LWS) requires registration, you can use these details to add this wallet to that server. Not all servers require registration.", "lwsDetailsPrimaryAddressLabel": "Primary Address", "lwsDetailsSecretViewKeyLabel": "Secret View Key", "lwsDetailsRestoreHeightLabel": "Restore Height", - "restoreWalletTitle": "Restore Wallet", "restoreWalletDescription": "Input your Monero recovery phrase below. We will check common formats.", - "restoreWalletScanFrom": "Scan from", - "restoreWalletScanFromReason": "Skip irrelevant history to save time", - "restoreWalletNotSet": "Not set", - "restoreScanTitle": "When did this wallet first receive funds?", "restoreScanDescription": "Skylight Wallet can skip irrelevant history to save you time. Either pick the first month that you used the wallet or select I'm not sure to check everything. It's okay to pick a month that is too early, but it's bad to pick a month that is too late.", - "restoreScanPickMonth": "Pick a month", - "restoreScanNotSure": "I'm not sure", - "restoreScanNotSureDesc": "Scan everything. Slower this first setup but not any slower later. Always complete.", - "restoreScanFromStart": "Genesis", - "restoreScanDone": "Done", - "restoreWalletRestoreButton": "Restore", "restoreWalletInvalidMnemonic": "Invalid recovery phrase.", - "restoreWalletSeedLength": "Number of words", - "restoreWalletPaste": "Paste", "restoreWalletSeedTypePolyseed": "Polyseed", "restoreWalletSeedTypeBip39": "BIP39", "restoreWalletSeedTypeLegacy": "Legacy", - "restoreWalletBadWord": "Word {position} isn't a BIP39 word.", - "@restoreWalletBadWord": { - "placeholders": { - "position": { - "type": "int" - } - } - }, - "restoreWalletDidYouMean": "Did you mean {word}?", - "@restoreWalletDidYouMean": { - "placeholders": { - "word": { - "type": "String" - } - } - }, - "restoreWalletChecksumError": "This isn't a valid recovery phrase — check the words and their order.", "navigationBarWallet": "Wallet", - "historyTitle": "History", - "historyExportCsv": "Export CSV", - "historyExported": "History exported", - "historyExportFailed": "Could not export the history", - "navigationBarHistory": "History", - "historyFilterType": "Type", - "@historyFilterType": { - "description": "History filter: received or sent." - }, "historyFilterAddress": "Address", "@historyFilterAddress": { "description": "History filter: transactions received at, or sent to, one address." }, - "historyFilterAddressAny": "Any address", + "historyFilterAddressAny": "Any Address", "historyFilterAddressHint": "Transactions received at this address, or sent to it.", "historyFilterAddressInvalid": "Not a Monero address", - "historyFilterReset": "Reset", - "navigationBarSettings": "Settings", - "unlockButton": "Unlock", - "unlockReason": "Unlock Wallet", - "unlockUnableToAuthError": "Unable to authenticate.", - "unlockWithFaceId": "Unlock with Face ID", - "unlockWithTouchId": "Unlock with Touch ID", "unlockTitle": "Unlock Wallet", - "unlockPasswordHint": "Enter your password", - "unlockIncorrectPasswordError": "Incorrect password. Please try again.", - "homeSyncing": "Syncing", - "homeSynced": "Synced", - "homeTorConnected": "Tor · connected", - "homeTorConnecting": "Tor · connecting", - "homeTorDisconnected": "Tor · disconnected", - "homeTorOff": "Tor · off", - "homeTorExternalConnected": "External Tor · connected", - "homeTorExternalConnecting": "External Tor · connecting", - "homeTorExternalDisconnected": "External Tor · disconnected", - "homeReceive": "Receive", - "homeSend": "Send", - "homeNoTransactions": "No Transactions", - "homeFiatApiError": "Error connecting to price server", "homeDisconnected": "Disconnected", - "coinHomeActivityTitle": "Activity", - "coinHomeReceived": "Received", - "coinHomeSent": "Sent", - "receiveTitle": "Receive", - "receivePrimaryAddressWarn": "Warning: Unless you know what you're doing, please use subaddresses for better privacy.", - "receiveServerNoSubaddressesWarn": "Warning: This server does not support subaddresses. For better privacy, consider using a server that supports them.", - "receiveSubaddressUnavailableWarn": "Warning: You need to configure Monero to enable subaddresses, which better protect your privacy.", - "receiveMaxSubaddressesReachedWarn": "You have reached the maximum number of subaddresses supported by this server. This is a used address.", - "receiveSubaddressTab": "Subaddress", - "receivePrimaryTab": "Primary address", - "receiveCopyAddress": "Copy address", "receiveQrHint": "Scan this code to send Monero to this wallet.", - "receiveEnlargeQr": "Tap to enlarge and brighten", - "receiveShrinkQr": "Tap to shrink", - "receiveShareError": "Could not open the share sheet", - "receiveAddressHeading": "Your {coin} address", - "@receiveAddressHeading": { - "placeholders": { - "coin": { - "type": "String" - } - } - }, - "sendTitle": "Send", - "sendSendButton": "Send", - "sendTransactionSuccessfullySent": "Transaction successfully sent!", - "sendOpenAliasResolveError": "Invalid OpenAlias.", - "sendOpenAliasDisabledError": "OpenAlias is turned off. Turn it on in Settings.", "sendInvalidAddressError": "Invalid address.", - "sendInsufficientBalanceError": "Insufficient balance.", - "sendInsufficientBalanceToCoverFeeError": "Insufficient balance to cover the network fee.", - "settingsTitle": "Settings", - "settingsSectionGeneral": "General", - "settingsSectionSecurityPrivacy": "Security and privacy", - "settingsSectionData": "Data", - "settingsSectionBehaviour": "Behavior", - "settingsSectionWallet": "Wallet", - "settingsSectionAbout": "About", - "settingsNotifyNewTxsLabel": "Transaction Notifications", - "settingsNotifyNewTxsDescription": "Show a notification when you receive a transaction. Increase the sync frequency setting to see them sooner.", - "settingsSyncFrequencyLabel": "Sync Frequency", "syncFrequencySheetSubtitle": "How often Monero syncs while the app is closed.", - "syncFrequencyModeLabel": "Frequency", - "syncFrequencyContinuous": "Continuous", "syncFrequencyContinuousDesc": "Continuously sync Monero in the background so it's always up to date when you open the app. Uses more battery and requires a persistent notification.", - "syncFrequencyPeriodic": "Periodic", "syncFrequencyPeriodicDesc": "Periodically sync Monero in the background so it's usually up to date when you open the app. With a Monero node, it only syncs while charging and on WiFi.", "syncFrequencyPeriodicDescIos": "Periodically sync Monero in the background so it's usually up to date when you open the app. With a Monero node, it only syncs while charging.", - "syncFrequencyOff": "Off", - "syncFrequencyOffDesc": "Only syncs while the app is open.", - "settingsAppLockUnlockReason": "Unlock Wallet", - "settingsLoggingLabel": "Diagnostic Logs", - "settingsLoggingDescription": "Log wallet operations to a text file in the app's data folder for debugging purposes.", - "settingsLoggingDescriptionIos": "Log wallet operations and allow the logs to be exported to a text file.", - "settingsExportLogsLabel": "Export Logs", - "settingsExportLogsButton": "Export", - "settingsExportLogsError": "No logs found to export.", - "settingsExportLogsFailed": "Could not export the log file", - "settingsThemeLabel": "Theme", - "settingsThemeSystem": "System", - "settingsThemeLight": "Light", - "settingsThemeDark": "Dark", "settingsThemeLightDesc": "Light and clean", - "settingsThemeDarkDesc": "Dark ground, easier at night", - "settingsThemeSystemDesc": "Follows your phone", - "settingsThemeSheetSubtitle": "Pick a theme to match your style.", - "settingsLanguageLabel": "Language", - "settingsLanguageSheetSubtitle": "Pick your language and localization.", - "settingsFiatApiSettingsLabel": "Price Display Settings", "settingsLwsViewKeysLabel": "LWS View Keys", - "settingsLwsViewKeysButton": "View", - "revealSeedAuthReason": "Confirm it's you to view your recovery phrase", "settingsSecretKeysLabel": "Secret Restore Keys", "settingsViewLwsKeysDialogText": "Only share this information with your light-wallet server. These keys allow the holder to permanently see all transactions related to your wallets. Sharing these with an untrusted person will significantly harm your privacy.", "settingsViewLwsKeysDialogRevealButton": "Reveal", "settingsViewSecretKeysDialogText": "Do not share these keys with anyone, including anyone claiming to be support. If you receive a request to provide these, you are being scammed. If you provide this information to another person, you will lose your money and it cannot be recovered.", "settingsViewSecretKeysDialogRevealButton": "Reveal", - "settingsDeleteWalletButton": "Delete Wallet", - "settingsDeleteWalletDialogText": "Are you sure you want to delete your wallet? You will lose access to your funds unless you have backed up your recovery phrase. This will not remove your address book and app settings.", - "settingsDeleteWalletDialogDeleteButton": "Delete", - "settingsDeleteAddressBookButton": "Delete Address Book", - "settingsDeleteAddressBookDialogText": "Are you sure you want to delete your address book? If automatic backup is turned on, it will also delete the address book in these automatic backups.", - "settingsDeleteAddressBookDialogDeleteButton": "Delete", - "settingsAddressBookDeleted": "Address book deleted", - "txDetailsTitle": "Transaction Details", - "txDetailsHashLabel": "Hash", - "txDetailsTimeAndDateLabel": "Time and Date", - "txDetailsConfirmationHeightLabel": "Confirmation Height", - "txDetailsConfirmationsLabel": "Confirmations", - "txDetailsViewKeyLabel": "View Key", - "txDetailsRecipientsLabel": "Recipients", - "txDetailsReceivedAtLabel": "Received At", - "txDetailsChangeRecipientLabel": "Change Recipient", - "unconfirmed": "Unconfirmed", - "txDetailsCopyHint": "tap any value to copy", - "txDetailsFailed": "This transaction failed. The funds were not sent.", - "txDetailsUnknownStatus": "This transaction was not confirmed as sent. Check before sending again.", - "copiedToClipboard": "Copied to clipboard", - "lwsKeysTitle": "LWS Keys", - "lwsKeysPrimaryAddress": "Primary Address", - "lwsKeysRestoreHeight": "Restore Height", - "lwsKeysSecretViewKey": "Secret View Key", - "lwsKeysWarning": "Screenshots are blocked on this screen. Make sure nobody is looking over your shoulder.", "secretKeysTitle": "Secret Restore Keys", "secretKeysDescription": "These recovery phrases and keys restore full control of your wallet. Anyone who sees them can spend your funds.", - "secretKeysMnemonic": "Recovery phrase", + "secretKeysMnemonic": "Recovery Phrase", "secretKeysPublicSpendKey": "Public Spend Key", "secretKeysSecretSpendKey": "Secret Spend Key", "secretKeysPublicViewKey": "Public View Key", "secretKeysWarning": "Never share these or enter them into any website. Store them offline.", - "scanQrTitle": "Scan QR Code", - "confirmSendTitle": "Confirm Send", - "confirmSendDescription": "Transactions are irreversible, so make sure that these details match exactly.", - "confirmSendHighFeeWarning": "The network fee is {percent} of the amount you are sending.", - "@confirmSendHighFeeWarning": { - "placeholders": { - "percent": { - "type": "String" - } - } - }, - "addressBookTitle": "Address Book", - "addressBookAddContact": "Add Contact", - "addressBookEditContact": "Edit Contact", - "addressBookDeleteContact": "Delete Contact", - "addressBookDeleteContactConfirmation": "Are you sure you want to delete \"{contactName}\"?", - "@addressBookDeleteContactConfirmation": { - "placeholders": { - "contactName": { - "type": "String" - } - } - }, - "addressBookDelete": "Delete", - "addressBookSearchHint": "Search contacts...", - "addressBookNoContacts": "No Contacts Yet", - "addressBookNoContactsDescription": "Add your first contact by tapping the + button", - "addressBookNoSearchResults": "No Contacts Found", - "addressBookEdit": "Edit", - "addressBookContactName": "Contact Name", - "addressBookNameHint": "Name", "addressBookAddDescription": "A name and a Monero address to pay them on.", "addressBookEditDescription": "Update this contact's name or address.", - "addressBookUpdate": "Update", - "addressBookSave": "Save", - "sendPriorityLow": "Low", - "sendPriorityNormal": "Normal", - "sendPriorityHigh": "High", - "sendContactsButton": "Contacts", - "sendToLabel": "To", - "sendPasteButton": "Paste", - "sendScanButton": "Scan", - "sendMaxButton": "MAX", - "sendNetworkFee": "Network fee", - "sendPriorityHeading": "Priority", - "sendPickContactTitle": "Send to a contact", - "sendAvailableSuffix": "available", "sendIrreversibleNote": "Transactions are irreversible, so make sure the address and amount are correct.", - "sendSwitchUnit": "Switch amount unit", - "sendFailedToGetFeesError": "Failed to get fees.", - "sendNoConnectionError": "Couldn't reach the server. Check your connection and try again.", - "torSettingsTitle": "Tor Settings", - "torSettingsModeBuiltIn": "Built-in Tor", - "torSettingsModeExternal": "External Tor", - "torSettingsModeDisabled": "No Tor", - "torChoiceBuiltInDesc": "Bundled with Skylight Wallet · recommended", - "torChoiceExternalDesc": "Orbot, or a daemon you run yourself", - "torChoiceNoTorDesc": "Servers you connect to can see your IP address", - "torChoiceConnected": "Connected to Tor", - "torChoiceTestFailed": "Test failed", - "torChoiceTitle": "Tor Connection Setup", "torChoiceSubtitle": "Tor can hide your IP address from servers you connect to. This does not reduce the information that is stored on public blockchains. In general, how do you want Skylight Wallet to handle Tor connections?", - "torSettingsSocksPortLabel": "SOCKS Port", - "torSettingsUseOrbotLabel": "Use Orbot/InviZible", - "torSettingsUseOrbotLabelIos": "Use Orbot", - "torSettingsSaveButton": "Save", - "torSettingsTestConnectionButton": "Test Connection", - "torDisabledWalletsWarningTitle": "Disable Tor?", - "torDisabledWalletsWarningBody": "Some wallets are set to connect over Tor. Disabling Tor will disconnect them, and they will stay disconnected until you reconfigure their connection.", - "torDisabledWalletsWarningConfirm": "Disable Tor", - "settingsTorSettingsLabel": "Tor Settings", - "lwsSetupTorDisabledError": "Tor is disabled. Please go back and enable it.", - "coinHomeSwap": "Swap", - "coinHomeSwapComingSoon": "Coming soon!", - "verifySeedTitle": "Verify Recovery Phrase", - "homeIncomingTitle": "Incoming", - "homeIncomingUnlocksIn": "Unlocks in about {minutes, plural, =1{1 minute} other{{minutes} minutes}} · {done} of {needed}", - "@homeIncomingUnlocksIn": {"placeholders": {"minutes": {"type": "int"}, "done": {"type": "int"}, "needed": {"type": "int"}}}, - "homeIncomingConfirmations": "{done} of {needed} confirmations", - "@homeIncomingConfirmations": {"placeholders": {"done": {"type": "int"}, "needed": {"type": "int"}}}, - "homeChartTitle": "Balance · 90 days", - "homeChartToday": "Today", - "homeChartEachCoin": "Each coin's balance", - "homeChartNew": "New", - "homeChartEmpty": "Your balance over time shows here after your first transaction.", - "homeChecklistTitle": "Secure your wallet", - "homeChecklistProgress": "{done} of {total}", - "@homeChecklistProgress": {"placeholders": {"done": {"type": "int"}, "total": {"type": "int"}}}, - "homeChecklistVerifySeed": "Verify your recovery phrase", - "homeChecklistAutoBackup": "Turn on automatic backups", - "homeChecklistTor": "Connect through Tor", - "homeChecklistPrivateLookups": "Use Tor for price and OpenAlias lookups", - "homeChecklistSecurityKeys": "Set up a security key", - "verifySeedSubtitle": "Input words {first}, {second} and {third} of your recovery phrase below.", - "@verifySeedSubtitle": {"placeholders": {"first": {"type": "int"}, "second": {"type": "int"}, "third": {"type": "int"}}}, - "verifySeedButton": "Verify", - "verifySeedWrongTitle": "Those words don't match", - "verifySeedWrongBody": "Check the recovery phrase you wrote down, then enter the three new words asked for. {count, plural, =1{1 try} other{{count} tries}} left before the app locks.", - "@verifySeedWrongBody": {"placeholders": {"count": {"type": "int"}}}, - "verifySeedTryAgain": "Try again", - "verifySeedUnavailable": "Your recovery phrase can't be read right now. Try again after unlocking the wallet.", - "verifySeedDone": "Recovery phrase verified", - "homeIncomingTitleMore": "Incoming (+{count, plural, =1{1 Other} other{{count} Others}})", - "@homeIncomingTitleMore": {"placeholders": {"count": {"type": "int"}}} + "torSettingsSheetSubtitle": "Tor can hide your IP address from servers you connect to. This does not reduce the information that is stored on public blockchains. Changing this reconnects your wallet.", + "@torSettingsSheetSubtitle": { + "description": "Tor Settings sheet description, under its title. Sentence case. Spice's says every chain; Skylight has one wallet." + } } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index ab6e765..daa95c9 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -93,102 +93,6 @@ abstract class AppLocalizations { /// A list of this localizations delegate's supported locales. static const List supportedLocales = [Locale('en'), Locale('pt')]; - /// No description provided for @scanQrUnexpectedCode. - /// - /// In en, this message translates to: - /// **'That QR isn\'t a valid address or payment code.'** - String get scanQrUnexpectedCode; - - /// No description provided for @deepLinkCoinNotConfigured. - /// - /// In en, this message translates to: - /// **'Please configure your {chain} connection first.'** - String deepLinkCoinNotConfigured(String chain); - - /// No description provided for @continueText. - /// - /// In en, this message translates to: - /// **'Continue'** - String get continueText; - - /// No description provided for @cancel. - /// - /// In en, this message translates to: - /// **'Cancel'** - String get cancel; - - /// No description provided for @done. - /// - /// In en, this message translates to: - /// **'Done'** - String get done; - - /// No description provided for @close. - /// - /// In en, this message translates to: - /// **'Close'** - String get close; - - /// No description provided for @back. - /// - /// In en, this message translates to: - /// **'Back'** - String get back; - - /// No description provided for @unknownError. - /// - /// In en, this message translates to: - /// **'Unknown error.'** - String get unknownError; - - /// No description provided for @warning. - /// - /// In en, this message translates to: - /// **'Warning'** - String get warning; - - /// No description provided for @amount. - /// - /// In en, this message translates to: - /// **'Amount'** - String get amount; - - /// No description provided for @networkFee. - /// - /// In en, this message translates to: - /// **'Network Fee'** - String get networkFee; - - /// No description provided for @address. - /// - /// In en, this message translates to: - /// **'Address'** - String get address; - - /// No description provided for @pending. - /// - /// In en, this message translates to: - /// **'Pending'** - String get pending; - - /// No description provided for @copy. - /// - /// In en, this message translates to: - /// **'Copy'** - String get copy; - - /// No description provided for @addressCopied. - /// - /// In en, this message translates to: - /// **'Address copied to clipboard'** - String get addressCopied; - - /// No description provided for @fieldEmptyError. - /// - /// In en, this message translates to: - /// **'This field cannot be empty.'** - String get fieldEmptyError; - /// No description provided for @welcomeDescription. /// /// In en, this message translates to: @@ -225,18 +129,6 @@ abstract class AppLocalizations { /// **'Privacy Policy'** String get welcomePrivacyLink; - /// No description provided for @settingsLicensesLabel. - /// - /// In en, this message translates to: - /// **'Open-Source Licenses'** - String get settingsLicensesLabel; - - /// No description provided for @lwsSetupTitle. - /// - /// In en, this message translates to: - /// **'Connection Setup'** - String get lwsSetupTitle; - /// No description provided for @lwsSetupDescription. /// /// In en, this message translates to: @@ -255,1991 +147,335 @@ abstract class AppLocalizations { /// **'4444 for I2P'** String get lwsSetupProxyPortHint; - /// No description provided for @lwsSetupUseTorLabel. - /// - /// In en, this message translates to: - /// **'Use Tor'** - String get lwsSetupUseTorLabel; - - /// No description provided for @lwsSetupTestConnectionButton. - /// - /// In en, this message translates to: - /// **'Test Connection'** - String get lwsSetupTestConnectionButton; - - /// No description provided for @lwsSetupStartingTor. - /// - /// In en, this message translates to: - /// **'Starting Tor...'** - String get lwsSetupStartingTor; - - /// No description provided for @lwsSetupContinueButton. - /// - /// In en, this message translates to: - /// **'Continue'** - String get lwsSetupContinueButton; - - /// No description provided for @connectionTypeLws. + /// No description provided for @settingsConnectionSettingsLabel. /// /// In en, this message translates to: - /// **'Light Wallet Server'** - String get connectionTypeLws; + /// **'Connection Settings'** + String get settingsConnectionSettingsLabel; - /// No description provided for @connectionTypeNode. + /// No description provided for @settingsConnectionServerLws. /// /// In en, this message translates to: - /// **'Monero Node'** - String get connectionTypeNode; + /// **'LWS'** + String get settingsConnectionServerLws; - /// No description provided for @connectionRemoteIpNotAllowed. + /// No description provided for @settingsConnectionServerNode. /// /// In en, this message translates to: - /// **'Connections to remote IP addresses aren\'t allowed. Use a domain name or a local IP address.'** - String get connectionRemoteIpNotAllowed; + /// **'Node'** + String get settingsConnectionServerNode; - /// No description provided for @connectionLocalOverTorNotAllowed. + /// No description provided for @settingsConnectionOverTor. /// /// In en, this message translates to: - /// **'A local address can\'t be reached through Tor. Turn Tor off, or use the server\'s onion address.'** - String get connectionLocalOverTorNotAllowed; + /// **'over Tor'** + String get settingsConnectionOverTor; - /// No description provided for @connectionOnionNeedsTor. + /// No description provided for @settingsConnectionOverClearnet. /// /// In en, this message translates to: - /// **'An onion address can only be reached through Tor. Turn Tor on to use it.'** - String get connectionOnionNeedsTor; + /// **'over Clearnet'** + String get settingsConnectionOverClearnet; - /// No description provided for @connectionPathNotSupported. + /// No description provided for @settingsConnectionInLocalNetwork. /// /// In en, this message translates to: - /// **'This server is reached at its root, so its address can\'t include a path like /wallet. Remove the path.'** - String get connectionPathNotSupported; + /// **'in local network'** + String get settingsConnectionInLocalNetwork; - /// No description provided for @lwsAwaitingApprovalAccount. + /// No description provided for @fiatApiSetupDescription. /// /// In en, this message translates to: - /// **'Your light wallet server hasn\'t approved this wallet yet. Ask the server\'s operator to approve it so you can see your balance and transactions.'** - String get lwsAwaitingApprovalAccount; + /// **'Skylight Wallet can automatically fetch the latest Monero price. Your balances are not sent to the server. How do you want to fetch this price data?'** + String get fiatApiSetupDescription; - /// No description provided for @lwsAwaitingApprovalRestoreHeight. + /// No description provided for @createWalletDescription. /// /// In en, this message translates to: - /// **'Your light wallet server hasn\'t approved scanning from your restore height yet, so older transactions may be missing. Ask the server\'s operator to approve it.'** - String get lwsAwaitingApprovalRestoreHeight; + /// **'Would you like to create a new wallet or restore an existing wallet?'** + String get createWalletDescription; - /// No description provided for @connectionTorDisabledWarning. + /// No description provided for @createWalletRestoreExistingDesc. /// /// In en, this message translates to: - /// **'Tor is disabled, so this connection no longer works. Please reconfigure it.'** - String get connectionTorDisabledWarning; + /// **'Enter an existing Monero recovery phrase'** + String get createWalletRestoreExistingDesc; - /// No description provided for @connectionProtocolHttps. + /// No description provided for @createWalletCreateNewDesc. /// /// In en, this message translates to: - /// **'Removing protocol. Using HTTPS for domains.'** - String get connectionProtocolHttps; + /// **'Skylight Wallet generates a new Polyseed recovery phrase'** + String get createWalletCreateNewDesc; - /// No description provided for @connectionProtocolHttp. + /// No description provided for @generateSeedSubtitleCovered. /// /// In en, this message translates to: - /// **'Removing protocol. Using HTTP for local addresses.'** - String get connectionProtocolHttp; + /// **'These words, in this order, are your wallet. Write them down and keep them in a physical safe. If you lose these words or if you share them with anyone else, you will lose your money permanently. Careful planning now avoids a potential disaster later.'** + String get generateSeedSubtitleCovered; - /// No description provided for @connectionProtocolHttpOnion. + /// No description provided for @onboardingConnectionNoteServer. /// /// In en, this message translates to: - /// **'Removing protocol. Onion addresses use HTTP over Tor.'** - String get connectionProtocolHttpOnion; + /// **'Use a light-wallet server (LWS) or your own Monero node.'** + String get onboardingConnectionNoteServer; - /// No description provided for @connectionTestStop. + /// No description provided for @onboardingConnectionNoteChangeable. /// /// In en, this message translates to: - /// **'Stop'** - String get connectionTestStop; + /// **'You can change this later in Settings.'** + String get onboardingConnectionNoteChangeable; - /// No description provided for @connectionTestingTitle. + /// No description provided for @lwsDetailsTitle. /// /// In en, this message translates to: - /// **'Testing connection'** - String get connectionTestingTitle; + /// **'Wallet Details'** + String get lwsDetailsTitle; - /// No description provided for @connectionTestingDetail. + /// No description provided for @lwsDetailsPrimaryAddressLabel. /// /// In en, this message translates to: - /// **'Checking whether the server answers.'** - String get connectionTestingDetail; + /// **'Primary Address'** + String get lwsDetailsPrimaryAddressLabel; - /// No description provided for @connectionTestAgain. + /// No description provided for @lwsDetailsSecretViewKeyLabel. /// /// In en, this message translates to: - /// **'Test again'** - String get connectionTestAgain; + /// **'Secret View Key'** + String get lwsDetailsSecretViewKeyLabel; - /// No description provided for @connectionResultWorksTitle. + /// No description provided for @lwsDetailsRestoreHeightLabel. /// /// In en, this message translates to: - /// **'Connection works'** - String get connectionResultWorksTitle; + /// **'Restore Height'** + String get lwsDetailsRestoreHeightLabel; - /// No description provided for @connectionResultFailedTitle. + /// No description provided for @restoreWalletDescription. /// /// In en, this message translates to: - /// **'Could not reach this server'** - String get connectionResultFailedTitle; + /// **'Input your Monero recovery phrase below. We will check common formats.'** + String get restoreWalletDescription; - /// No description provided for @connectionResultFailedDetail. + /// No description provided for @restoreScanDescription. /// /// In en, this message translates to: - /// **'Nothing answered. Check the address and port, and whether the server accepts your connection.'** - String get connectionResultFailedDetail; + /// **'Skylight Wallet can skip irrelevant history to save you time. Either pick the first month that you used the wallet or select I\'m not sure to check everything. It\'s okay to pick a month that is too early, but it\'s bad to pick a month that is too late.'** + String get restoreScanDescription; - /// No description provided for @connectionReachedOverTor. + /// No description provided for @restoreWalletInvalidMnemonic. /// /// In en, this message translates to: - /// **'Reached over Tor'** - String get connectionReachedOverTor; + /// **'Invalid recovery phrase.'** + String get restoreWalletInvalidMnemonic; - /// No description provided for @connectionReachedViaProxy. + /// No description provided for @restoreWalletSeedTypePolyseed. /// /// In en, this message translates to: - /// **'Reached through your proxy'** - String get connectionReachedViaProxy; + /// **'Polyseed'** + String get restoreWalletSeedTypePolyseed; - /// No description provided for @connectionReachedDirect. + /// No description provided for @restoreWalletSeedTypeBip39. /// /// In en, this message translates to: - /// **'Reached directly'** - String get connectionReachedDirect; + /// **'BIP39'** + String get restoreWalletSeedTypeBip39; - /// No description provided for @settingsConnectionSettingsLabel. + /// No description provided for @restoreWalletSeedTypeLegacy. /// /// In en, this message translates to: - /// **'Connection Settings'** - String get settingsConnectionSettingsLabel; + /// **'Legacy'** + String get restoreWalletSeedTypeLegacy; - /// No description provided for @settingsConnectionServerLws. + /// No description provided for @navigationBarWallet. /// /// In en, this message translates to: - /// **'LWS'** - String get settingsConnectionServerLws; + /// **'Wallet'** + String get navigationBarWallet; - /// No description provided for @settingsConnectionServerNode. + /// History filter: transactions received at, or sent to, one address. /// /// In en, this message translates to: - /// **'Node'** - String get settingsConnectionServerNode; + /// **'Address'** + String get historyFilterAddress; - /// No description provided for @settingsConnectionOverTor. + /// No description provided for @historyFilterAddressAny. /// /// In en, this message translates to: - /// **'over Tor'** - String get settingsConnectionOverTor; + /// **'Any Address'** + String get historyFilterAddressAny; - /// No description provided for @settingsConnectionOverClearnet. + /// No description provided for @historyFilterAddressHint. /// /// In en, this message translates to: - /// **'over Clearnet'** - String get settingsConnectionOverClearnet; + /// **'Transactions received at this address, or sent to it.'** + String get historyFilterAddressHint; - /// No description provided for @settingsConnectionInLocalNetwork. + /// No description provided for @historyFilterAddressInvalid. /// /// In en, this message translates to: - /// **'in Local Network'** - String get settingsConnectionInLocalNetwork; + /// **'Not a Monero address'** + String get historyFilterAddressInvalid; - /// No description provided for @homeBlocksRemaining. + /// No description provided for @unlockTitle. /// /// In en, this message translates to: - /// **'{count, plural, =1{1 block left} other{{count} blocks left}}'** - String homeBlocksRemaining(int count); + /// **'Unlock Wallet'** + String get unlockTitle; - /// No description provided for @fiatApiSetupTitle. + /// No description provided for @homeDisconnected. /// /// In en, this message translates to: - /// **'Price Display Setup'** - String get fiatApiSetupTitle; + /// **'Disconnected'** + String get homeDisconnected; - /// No description provided for @fiatApiSetupDescription. + /// No description provided for @receiveQrHint. /// /// In en, this message translates to: - /// **'Skylight Wallet can automatically fetch the latest Monero price. Your balances are not sent to the server. How do you want to fetch this price data?'** - String get fiatApiSetupDescription; + /// **'Scan this code to send Monero to this wallet.'** + String get receiveQrHint; - /// No description provided for @fiatApiSettingsModeLabel. + /// No description provided for @sendInvalidAddressError. /// /// In en, this message translates to: - /// **'Mode'** - String get fiatApiSettingsModeLabel; + /// **'Invalid address.'** + String get sendInvalidAddressError; - /// No description provided for @fiatApiSettingsModeTorOnly. + /// No description provided for @syncFrequencySheetSubtitle. /// /// In en, this message translates to: - /// **'Tor-Only'** - String get fiatApiSettingsModeTorOnly; + /// **'How often Monero syncs while the app is closed.'** + String get syncFrequencySheetSubtitle; - /// No description provided for @fiatApiSettingsModeClearnet. + /// No description provided for @syncFrequencyContinuousDesc. /// /// In en, this message translates to: - /// **'Clearnet-Only (Not Private)'** - String get fiatApiSettingsModeClearnet; + /// **'Continuously sync Monero in the background so it\'s always up to date when you open the app. Uses more battery and requires a persistent notification.'** + String get syncFrequencyContinuousDesc; - /// No description provided for @fiatApiSettingsModeDisabled. + /// No description provided for @syncFrequencyPeriodicDesc. /// /// In en, this message translates to: - /// **'Disabled'** - String get fiatApiSettingsModeDisabled; + /// **'Periodically sync Monero in the background so it\'s usually up to date when you open the app. With a Monero node, it only syncs while charging and on WiFi.'** + String get syncFrequencyPeriodicDesc; - /// No description provided for @fiatModeTorOnlyDesc. + /// No description provided for @syncFrequencyPeriodicDescIos. /// /// In en, this message translates to: - /// **'Prices fetched over Tor · recommended'** - String get fiatModeTorOnlyDesc; + /// **'Periodically sync Monero in the background so it\'s usually up to date when you open the app. With a Monero node, it only syncs while charging.'** + String get syncFrequencyPeriodicDescIos; - /// No description provided for @fiatModeClearnetDesc. + /// No description provided for @settingsThemeLightDesc. /// /// In en, this message translates to: - /// **'Not private, the price server sees your IP address'** - String get fiatModeClearnetDesc; + /// **'Light and clean'** + String get settingsThemeLightDesc; - /// No description provided for @fiatModeDisabledDesc. + /// No description provided for @settingsLwsViewKeysLabel. /// /// In en, this message translates to: - /// **'No prices fetched, balances shown in crypto only'** - String get fiatModeDisabledDesc; + /// **'LWS View Keys'** + String get settingsLwsViewKeysLabel; - /// No description provided for @settingsOpenAliasSettingsLabel. + /// No description provided for @settingsSecretKeysLabel. /// /// In en, this message translates to: - /// **'OpenAlias Settings'** - String get settingsOpenAliasSettingsLabel; + /// **'Secret Restore Keys'** + String get settingsSecretKeysLabel; - /// No description provided for @openAliasSettingsSheetSubtitle. + /// No description provided for @settingsViewLwsKeysDialogText. /// /// In en, this message translates to: - /// **'How names like name@example.com are looked up when you send.'** - String get openAliasSettingsSheetSubtitle; + /// **'Only share this information with your light-wallet server. These keys allow the holder to permanently see all transactions related to your wallets. Sharing these with an untrusted person will significantly harm your privacy.'** + String get settingsViewLwsKeysDialogText; - /// No description provided for @openAliasModeTorOnlyDesc. + /// No description provided for @settingsViewLwsKeysDialogRevealButton. /// /// In en, this message translates to: - /// **'Names looked up over Tor · recommended'** - String get openAliasModeTorOnlyDesc; + /// **'Reveal'** + String get settingsViewLwsKeysDialogRevealButton; - /// No description provided for @openAliasModeClearnetDesc. + /// No description provided for @settingsViewSecretKeysDialogText. /// /// In en, this message translates to: - /// **'Not private, DNS servers see your IP address and the name'** - String get openAliasModeClearnetDesc; + /// **'Do not share these keys with anyone, including anyone claiming to be support. If you receive a request to provide these, you are being scammed. If you provide this information to another person, you will lose your money and it cannot be recovered.'** + String get settingsViewSecretKeysDialogText; - /// No description provided for @openAliasModeDisabledDesc. + /// No description provided for @settingsViewSecretKeysDialogRevealButton. /// /// In en, this message translates to: - /// **'No lookups, send to addresses only'** - String get openAliasModeDisabledDesc; + /// **'Reveal'** + String get settingsViewSecretKeysDialogRevealButton; - /// No description provided for @fiatApiSettingsDisplayCurrencyLabel. + /// No description provided for @secretKeysTitle. /// /// In en, this message translates to: - /// **'Display Currency'** - String get fiatApiSettingsDisplayCurrencyLabel; + /// **'Secret Restore Keys'** + String get secretKeysTitle; - /// No description provided for @createWalletTitle. + /// No description provided for @secretKeysDescription. /// /// In en, this message translates to: - /// **'Wallet Setup'** - String get createWalletTitle; + /// **'These recovery phrases and keys restore full control of your wallet. Anyone who sees them can spend your funds.'** + String get secretKeysDescription; - /// No description provided for @createWalletDescription. + /// No description provided for @secretKeysMnemonic. /// /// In en, this message translates to: - /// **'Would you like to create a new wallet or restore an existing wallet?'** - String get createWalletDescription; + /// **'Recovery Phrase'** + String get secretKeysMnemonic; - /// No description provided for @createWalletRestoreExistingButton. + /// No description provided for @secretKeysPublicSpendKey. /// /// In en, this message translates to: - /// **'Restore Existing'** - String get createWalletRestoreExistingButton; + /// **'Public Spend Key'** + String get secretKeysPublicSpendKey; - /// No description provided for @createWalletRestoreExistingDesc. + /// No description provided for @secretKeysSecretSpendKey. /// /// In en, this message translates to: - /// **'Enter an existing Monero recovery phrase'** - String get createWalletRestoreExistingDesc; + /// **'Secret Spend Key'** + String get secretKeysSecretSpendKey; - /// No description provided for @createWalletCreateNewButton. + /// No description provided for @secretKeysPublicViewKey. /// /// In en, this message translates to: - /// **'Create New'** - String get createWalletCreateNewButton; + /// **'Public View Key'** + String get secretKeysPublicViewKey; - /// No description provided for @createWalletCreateNewDesc. + /// No description provided for @secretKeysWarning. /// /// In en, this message translates to: - /// **'Skylight Wallet generates a new Polyseed recovery phrase'** - String get createWalletCreateNewDesc; + /// **'Never share these or enter them into any website. Store them offline.'** + String get secretKeysWarning; - /// No description provided for @createWalletPasswordTitle. + /// No description provided for @addressBookAddDescription. /// /// In en, this message translates to: - /// **'Create Wallet Password'** - String get createWalletPasswordTitle; + /// **'A name and a Monero address to pay them on.'** + String get addressBookAddDescription; - /// No description provided for @createWalletPasswordDescription. + /// No description provided for @addressBookEditDescription. /// /// In en, this message translates to: - /// **'Create a password to protect your wallet. This password will be required to unlock your wallet.'** - String get createWalletPasswordDescription; + /// **'Update this contact\'s name or address.'** + String get addressBookEditDescription; - /// No description provided for @createWalletPasswordHint. + /// No description provided for @sendIrreversibleNote. /// /// In en, this message translates to: - /// **'Enter your password'** - String get createWalletPasswordHint; + /// **'Transactions are irreversible, so make sure the address and amount are correct.'** + String get sendIrreversibleNote; - /// No description provided for @createWalletConfirmPasswordHint. - /// - /// In en, this message translates to: - /// **'Confirm your password'** - String get createWalletConfirmPasswordHint; - - /// No description provided for @passwordTooShortError. - /// - /// In en, this message translates to: - /// **'Password must be at least 8 characters long.'** - String get passwordTooShortError; - - /// No description provided for @passwordsDoNotMatchError. - /// - /// In en, this message translates to: - /// **'Passwords do not match.'** - String get passwordsDoNotMatchError; - - /// No description provided for @generateSeedTitle. - /// - /// In en, this message translates to: - /// **'Write these down, in order'** - String get generateSeedTitle; - - /// No description provided for @generateSeedTitleCovered. - /// - /// In en, this message translates to: - /// **'Recovery Phrase'** - String get generateSeedTitleCovered; - - /// No description provided for @generateSeedSubtitleCovered. - /// - /// In en, this message translates to: - /// **'These words, in this order, are your wallet. Write them down and keep them in a physical safe. If you lose these words or if you share them with anyone else, you will lose your money permanently. Careful planning now avoids a potential disaster later.'** - String get generateSeedSubtitleCovered; - - /// No description provided for @generateSeedSubtitleRevealed. - /// - /// In en, this message translates to: - /// **'Securely save these. Do not share them.'** - String get generateSeedSubtitleRevealed; - - /// No description provided for @generateSeedScreenshotNote. - /// - /// In en, this message translates to: - /// **'Screenshots are blocked on this screen. Make sure nobody is looking over your shoulder.'** - String get generateSeedScreenshotNote; - - /// No description provided for @generateSeedReveal. - /// - /// In en, this message translates to: - /// **'Tap to reveal'** - String get generateSeedReveal; - - /// No description provided for @generateSeedConfirm. - /// - /// In en, this message translates to: - /// **'I have written down all the words and stored them somewhere only I can reach.'** - String get generateSeedConfirm; - - /// No description provided for @generateSeedBirthdayLabel. - /// - /// In en, this message translates to: - /// **'Wallet birthday'** - String get generateSeedBirthdayLabel; - - /// No description provided for @generateSeedBirthdayReason. - /// - /// In en, this message translates to: - /// **'Where a future restore starts scanning'** - String get generateSeedBirthdayReason; - - /// No description provided for @onboardingTorNotePrivacy. - /// - /// In en, this message translates to: - /// **'Connections over Tor can be slower.'** - String get onboardingTorNotePrivacy; - - /// No description provided for @onboardingTorNoteChangeable. - /// - /// In en, this message translates to: - /// **'You can change this later in Settings.'** - String get onboardingTorNoteChangeable; - - /// No description provided for @onboardingPriceNoteRatesOnly. - /// - /// In en, this message translates to: - /// **'Only prices are requested. Your addresses and balances are not sent.'** - String get onboardingPriceNoteRatesOnly; - - /// No description provided for @onboardingPriceNoteTor. - /// - /// In en, this message translates to: - /// **'Prices are fetched over Tor by default.'** - String get onboardingPriceNoteTor; - - /// No description provided for @onboardingConnectionNoteServer. - /// - /// In en, this message translates to: - /// **'Use a light-wallet server (LWS) or your own Monero node.'** - String get onboardingConnectionNoteServer; - - /// No description provided for @onboardingConnectionNoteChangeable. - /// - /// In en, this message translates to: - /// **'You can change this later in Settings.'** - String get onboardingConnectionNoteChangeable; - - /// No description provided for @onboardingWalletNoteGenerated. - /// - /// In en, this message translates to: - /// **'Skylight Wallet generates your recovery phrase on this device.'** - String get onboardingWalletNoteGenerated; - - /// No description provided for @onboardingWalletNoteRestore. - /// - /// In en, this message translates to: - /// **'When restoring, a scan-from date makes syncing faster.'** - String get onboardingWalletNoteRestore; - - /// No description provided for @onboardingSeedNotePassword. - /// - /// In en, this message translates to: - /// **'Skylight Wallet asks for your password before showing these words again.'** - String get onboardingSeedNotePassword; - - /// No description provided for @onboardingPasswordNoteLaunch. - /// - /// In en, this message translates to: - /// **'You need this password to open your wallet and to view your recovery phrase.'** - String get onboardingPasswordNoteLaunch; - - /// No description provided for @onboardingPasswordNoteNotCloud. - /// - /// In en, this message translates to: - /// **'This password can\'t be recovered. If you lose it, restore your wallet from your recovery phrase.'** - String get onboardingPasswordNoteNotCloud; - - /// No description provided for @onboardingPasswordStrong. - /// - /// In en, this message translates to: - /// **'Strong'** - String get onboardingPasswordStrong; - - /// No description provided for @onboardingPasswordMatch. - /// - /// In en, this message translates to: - /// **'Passwords match'** - String get onboardingPasswordMatch; - - /// No description provided for @lwsDetailsTitle. - /// - /// In en, this message translates to: - /// **'Wallet Details'** - String get lwsDetailsTitle; - - /// No description provided for @lwsDetailsDescription. - /// - /// In en, this message translates to: - /// **'If your Monero light wallet server (LWS) requires registration, you can use these details to add this wallet to that server. Not all servers require registration.'** - String get lwsDetailsDescription; - - /// No description provided for @lwsDetailsPrimaryAddressLabel. - /// - /// In en, this message translates to: - /// **'Primary Address'** - String get lwsDetailsPrimaryAddressLabel; - - /// No description provided for @lwsDetailsSecretViewKeyLabel. - /// - /// In en, this message translates to: - /// **'Secret View Key'** - String get lwsDetailsSecretViewKeyLabel; - - /// No description provided for @lwsDetailsRestoreHeightLabel. - /// - /// In en, this message translates to: - /// **'Restore Height'** - String get lwsDetailsRestoreHeightLabel; - - /// No description provided for @restoreWalletTitle. - /// - /// In en, this message translates to: - /// **'Restore Wallet'** - String get restoreWalletTitle; - - /// No description provided for @restoreWalletDescription. - /// - /// In en, this message translates to: - /// **'Input your Monero recovery phrase below. We will check common formats.'** - String get restoreWalletDescription; - - /// No description provided for @restoreWalletScanFrom. - /// - /// In en, this message translates to: - /// **'Scan from'** - String get restoreWalletScanFrom; - - /// No description provided for @restoreWalletScanFromReason. - /// - /// In en, this message translates to: - /// **'Skip irrelevant history to save time'** - String get restoreWalletScanFromReason; - - /// No description provided for @restoreWalletNotSet. - /// - /// In en, this message translates to: - /// **'Not set'** - String get restoreWalletNotSet; - - /// No description provided for @restoreScanTitle. - /// - /// In en, this message translates to: - /// **'When did this wallet first receive funds?'** - String get restoreScanTitle; - - /// No description provided for @restoreScanDescription. - /// - /// In en, this message translates to: - /// **'Skylight Wallet can skip irrelevant history to save you time. Either pick the first month that you used the wallet or select I\'m not sure to check everything. It\'s okay to pick a month that is too early, but it\'s bad to pick a month that is too late.'** - String get restoreScanDescription; - - /// No description provided for @restoreScanPickMonth. - /// - /// In en, this message translates to: - /// **'Pick a month'** - String get restoreScanPickMonth; - - /// No description provided for @restoreScanNotSure. - /// - /// In en, this message translates to: - /// **'I\'m not sure'** - String get restoreScanNotSure; - - /// No description provided for @restoreScanNotSureDesc. - /// - /// In en, this message translates to: - /// **'Scan everything. Slower this first setup but not any slower later. Always complete.'** - String get restoreScanNotSureDesc; - - /// No description provided for @restoreScanFromStart. - /// - /// In en, this message translates to: - /// **'Genesis'** - String get restoreScanFromStart; - - /// No description provided for @restoreScanDone. - /// - /// In en, this message translates to: - /// **'Done'** - String get restoreScanDone; - - /// No description provided for @restoreWalletRestoreButton. - /// - /// In en, this message translates to: - /// **'Restore'** - String get restoreWalletRestoreButton; - - /// No description provided for @restoreWalletInvalidMnemonic. - /// - /// In en, this message translates to: - /// **'Invalid recovery phrase.'** - String get restoreWalletInvalidMnemonic; - - /// No description provided for @restoreWalletSeedLength. - /// - /// In en, this message translates to: - /// **'Number of words'** - String get restoreWalletSeedLength; - - /// No description provided for @restoreWalletPaste. - /// - /// In en, this message translates to: - /// **'Paste'** - String get restoreWalletPaste; - - /// No description provided for @restoreWalletSeedTypePolyseed. - /// - /// In en, this message translates to: - /// **'Polyseed'** - String get restoreWalletSeedTypePolyseed; - - /// No description provided for @restoreWalletSeedTypeBip39. - /// - /// In en, this message translates to: - /// **'BIP39'** - String get restoreWalletSeedTypeBip39; - - /// No description provided for @restoreWalletSeedTypeLegacy. - /// - /// In en, this message translates to: - /// **'Legacy'** - String get restoreWalletSeedTypeLegacy; - - /// No description provided for @restoreWalletBadWord. - /// - /// In en, this message translates to: - /// **'Word {position} isn\'t a BIP39 word.'** - String restoreWalletBadWord(int position); - - /// No description provided for @restoreWalletDidYouMean. - /// - /// In en, this message translates to: - /// **'Did you mean {word}?'** - String restoreWalletDidYouMean(String word); - - /// No description provided for @restoreWalletChecksumError. - /// - /// In en, this message translates to: - /// **'This isn\'t a valid recovery phrase — check the words and their order.'** - String get restoreWalletChecksumError; - - /// No description provided for @navigationBarWallet. - /// - /// In en, this message translates to: - /// **'Wallet'** - String get navigationBarWallet; - - /// No description provided for @historyTitle. - /// - /// In en, this message translates to: - /// **'History'** - String get historyTitle; - - /// No description provided for @historyExportCsv. - /// - /// In en, this message translates to: - /// **'Export CSV'** - String get historyExportCsv; - - /// No description provided for @historyExported. - /// - /// In en, this message translates to: - /// **'History exported'** - String get historyExported; - - /// No description provided for @historyExportFailed. - /// - /// In en, this message translates to: - /// **'Could not export the history'** - String get historyExportFailed; - - /// No description provided for @navigationBarHistory. - /// - /// In en, this message translates to: - /// **'History'** - String get navigationBarHistory; - - /// History filter: received or sent. - /// - /// In en, this message translates to: - /// **'Type'** - String get historyFilterType; - - /// History filter: transactions received at, or sent to, one address. - /// - /// In en, this message translates to: - /// **'Address'** - String get historyFilterAddress; - - /// No description provided for @historyFilterAddressAny. - /// - /// In en, this message translates to: - /// **'Any address'** - String get historyFilterAddressAny; - - /// No description provided for @historyFilterAddressHint. - /// - /// In en, this message translates to: - /// **'Transactions received at this address, or sent to it.'** - String get historyFilterAddressHint; - - /// No description provided for @historyFilterAddressInvalid. - /// - /// In en, this message translates to: - /// **'Not a Monero address'** - String get historyFilterAddressInvalid; - - /// No description provided for @historyFilterReset. - /// - /// In en, this message translates to: - /// **'Reset'** - String get historyFilterReset; - - /// No description provided for @navigationBarSettings. - /// - /// In en, this message translates to: - /// **'Settings'** - String get navigationBarSettings; - - /// No description provided for @unlockButton. - /// - /// In en, this message translates to: - /// **'Unlock'** - String get unlockButton; - - /// No description provided for @unlockReason. - /// - /// In en, this message translates to: - /// **'Unlock Wallet'** - String get unlockReason; - - /// No description provided for @unlockUnableToAuthError. - /// - /// In en, this message translates to: - /// **'Unable to authenticate.'** - String get unlockUnableToAuthError; - - /// No description provided for @unlockWithFaceId. - /// - /// In en, this message translates to: - /// **'Unlock with Face ID'** - String get unlockWithFaceId; - - /// No description provided for @unlockWithTouchId. - /// - /// In en, this message translates to: - /// **'Unlock with Touch ID'** - String get unlockWithTouchId; - - /// No description provided for @unlockTitle. - /// - /// In en, this message translates to: - /// **'Unlock Wallet'** - String get unlockTitle; - - /// No description provided for @unlockPasswordHint. - /// - /// In en, this message translates to: - /// **'Enter your password'** - String get unlockPasswordHint; - - /// No description provided for @unlockIncorrectPasswordError. - /// - /// In en, this message translates to: - /// **'Incorrect password. Please try again.'** - String get unlockIncorrectPasswordError; - - /// No description provided for @homeSyncing. - /// - /// In en, this message translates to: - /// **'Syncing'** - String get homeSyncing; - - /// No description provided for @homeSynced. - /// - /// In en, this message translates to: - /// **'Synced'** - String get homeSynced; - - /// No description provided for @homeTorConnected. - /// - /// In en, this message translates to: - /// **'Tor · connected'** - String get homeTorConnected; - - /// No description provided for @homeTorConnecting. - /// - /// In en, this message translates to: - /// **'Tor · connecting'** - String get homeTorConnecting; - - /// No description provided for @homeTorDisconnected. - /// - /// In en, this message translates to: - /// **'Tor · disconnected'** - String get homeTorDisconnected; - - /// No description provided for @homeTorOff. - /// - /// In en, this message translates to: - /// **'Tor · off'** - String get homeTorOff; - - /// No description provided for @homeTorExternalConnected. - /// - /// In en, this message translates to: - /// **'External Tor · connected'** - String get homeTorExternalConnected; - - /// No description provided for @homeTorExternalConnecting. - /// - /// In en, this message translates to: - /// **'External Tor · connecting'** - String get homeTorExternalConnecting; - - /// No description provided for @homeTorExternalDisconnected. - /// - /// In en, this message translates to: - /// **'External Tor · disconnected'** - String get homeTorExternalDisconnected; - - /// No description provided for @homeReceive. - /// - /// In en, this message translates to: - /// **'Receive'** - String get homeReceive; - - /// No description provided for @homeSend. - /// - /// In en, this message translates to: - /// **'Send'** - String get homeSend; - - /// No description provided for @homeNoTransactions. - /// - /// In en, this message translates to: - /// **'No Transactions'** - String get homeNoTransactions; - - /// No description provided for @homeFiatApiError. - /// - /// In en, this message translates to: - /// **'Error connecting to price server'** - String get homeFiatApiError; - - /// No description provided for @homeDisconnected. - /// - /// In en, this message translates to: - /// **'Disconnected'** - String get homeDisconnected; - - /// No description provided for @coinHomeActivityTitle. - /// - /// In en, this message translates to: - /// **'Activity'** - String get coinHomeActivityTitle; - - /// No description provided for @coinHomeReceived. - /// - /// In en, this message translates to: - /// **'Received'** - String get coinHomeReceived; - - /// No description provided for @coinHomeSent. - /// - /// In en, this message translates to: - /// **'Sent'** - String get coinHomeSent; - - /// No description provided for @receiveTitle. - /// - /// In en, this message translates to: - /// **'Receive'** - String get receiveTitle; - - /// No description provided for @receivePrimaryAddressWarn. - /// - /// In en, this message translates to: - /// **'Warning: Unless you know what you\'re doing, please use subaddresses for better privacy.'** - String get receivePrimaryAddressWarn; - - /// No description provided for @receiveServerNoSubaddressesWarn. - /// - /// In en, this message translates to: - /// **'Warning: This server does not support subaddresses. For better privacy, consider using a server that supports them.'** - String get receiveServerNoSubaddressesWarn; - - /// No description provided for @receiveSubaddressUnavailableWarn. - /// - /// In en, this message translates to: - /// **'Warning: You need to configure Monero to enable subaddresses, which better protect your privacy.'** - String get receiveSubaddressUnavailableWarn; - - /// No description provided for @receiveMaxSubaddressesReachedWarn. - /// - /// In en, this message translates to: - /// **'You have reached the maximum number of subaddresses supported by this server. This is a used address.'** - String get receiveMaxSubaddressesReachedWarn; - - /// No description provided for @receiveSubaddressTab. - /// - /// In en, this message translates to: - /// **'Subaddress'** - String get receiveSubaddressTab; - - /// No description provided for @receivePrimaryTab. - /// - /// In en, this message translates to: - /// **'Primary address'** - String get receivePrimaryTab; - - /// No description provided for @receiveCopyAddress. - /// - /// In en, this message translates to: - /// **'Copy address'** - String get receiveCopyAddress; - - /// No description provided for @receiveQrHint. - /// - /// In en, this message translates to: - /// **'Scan this code to send Monero to this wallet.'** - String get receiveQrHint; - - /// No description provided for @receiveEnlargeQr. - /// - /// In en, this message translates to: - /// **'Tap to enlarge and brighten'** - String get receiveEnlargeQr; - - /// No description provided for @receiveShrinkQr. - /// - /// In en, this message translates to: - /// **'Tap to shrink'** - String get receiveShrinkQr; - - /// No description provided for @receiveShareError. - /// - /// In en, this message translates to: - /// **'Could not open the share sheet'** - String get receiveShareError; - - /// No description provided for @receiveAddressHeading. - /// - /// In en, this message translates to: - /// **'Your {coin} address'** - String receiveAddressHeading(String coin); - - /// No description provided for @sendTitle. - /// - /// In en, this message translates to: - /// **'Send'** - String get sendTitle; - - /// No description provided for @sendSendButton. - /// - /// In en, this message translates to: - /// **'Send'** - String get sendSendButton; - - /// No description provided for @sendTransactionSuccessfullySent. - /// - /// In en, this message translates to: - /// **'Transaction successfully sent!'** - String get sendTransactionSuccessfullySent; - - /// No description provided for @sendOpenAliasResolveError. - /// - /// In en, this message translates to: - /// **'Invalid OpenAlias.'** - String get sendOpenAliasResolveError; - - /// No description provided for @sendOpenAliasDisabledError. - /// - /// In en, this message translates to: - /// **'OpenAlias is turned off. Turn it on in Settings.'** - String get sendOpenAliasDisabledError; - - /// No description provided for @sendInvalidAddressError. - /// - /// In en, this message translates to: - /// **'Invalid address.'** - String get sendInvalidAddressError; - - /// No description provided for @sendInsufficientBalanceError. - /// - /// In en, this message translates to: - /// **'Insufficient balance.'** - String get sendInsufficientBalanceError; - - /// No description provided for @sendInsufficientBalanceToCoverFeeError. - /// - /// In en, this message translates to: - /// **'Insufficient balance to cover the network fee.'** - String get sendInsufficientBalanceToCoverFeeError; - - /// No description provided for @settingsTitle. - /// - /// In en, this message translates to: - /// **'Settings'** - String get settingsTitle; - - /// No description provided for @settingsSectionGeneral. - /// - /// In en, this message translates to: - /// **'General'** - String get settingsSectionGeneral; - - /// No description provided for @settingsSectionSecurityPrivacy. - /// - /// In en, this message translates to: - /// **'Security and privacy'** - String get settingsSectionSecurityPrivacy; - - /// No description provided for @settingsSectionData. - /// - /// In en, this message translates to: - /// **'Data'** - String get settingsSectionData; - - /// No description provided for @settingsSectionBehaviour. - /// - /// In en, this message translates to: - /// **'Behavior'** - String get settingsSectionBehaviour; - - /// No description provided for @settingsSectionWallet. - /// - /// In en, this message translates to: - /// **'Wallet'** - String get settingsSectionWallet; - - /// No description provided for @settingsSectionAbout. - /// - /// In en, this message translates to: - /// **'About'** - String get settingsSectionAbout; - - /// No description provided for @settingsNotifyNewTxsLabel. - /// - /// In en, this message translates to: - /// **'Transaction Notifications'** - String get settingsNotifyNewTxsLabel; - - /// No description provided for @settingsNotifyNewTxsDescription. - /// - /// In en, this message translates to: - /// **'Show a notification when you receive a transaction. Increase the sync frequency setting to see them sooner.'** - String get settingsNotifyNewTxsDescription; - - /// No description provided for @settingsSyncFrequencyLabel. - /// - /// In en, this message translates to: - /// **'Sync Frequency'** - String get settingsSyncFrequencyLabel; - - /// No description provided for @syncFrequencySheetSubtitle. - /// - /// In en, this message translates to: - /// **'How often Monero syncs while the app is closed.'** - String get syncFrequencySheetSubtitle; - - /// No description provided for @syncFrequencyModeLabel. - /// - /// In en, this message translates to: - /// **'Frequency'** - String get syncFrequencyModeLabel; - - /// No description provided for @syncFrequencyContinuous. - /// - /// In en, this message translates to: - /// **'Continuous'** - String get syncFrequencyContinuous; - - /// No description provided for @syncFrequencyContinuousDesc. - /// - /// In en, this message translates to: - /// **'Continuously sync Monero in the background so it\'s always up to date when you open the app. Uses more battery and requires a persistent notification.'** - String get syncFrequencyContinuousDesc; - - /// No description provided for @syncFrequencyPeriodic. - /// - /// In en, this message translates to: - /// **'Periodic'** - String get syncFrequencyPeriodic; - - /// No description provided for @syncFrequencyPeriodicDesc. - /// - /// In en, this message translates to: - /// **'Periodically sync Monero in the background so it\'s usually up to date when you open the app. With a Monero node, it only syncs while charging and on WiFi.'** - String get syncFrequencyPeriodicDesc; - - /// No description provided for @syncFrequencyPeriodicDescIos. - /// - /// In en, this message translates to: - /// **'Periodically sync Monero in the background so it\'s usually up to date when you open the app. With a Monero node, it only syncs while charging.'** - String get syncFrequencyPeriodicDescIos; - - /// No description provided for @syncFrequencyOff. - /// - /// In en, this message translates to: - /// **'Off'** - String get syncFrequencyOff; - - /// No description provided for @syncFrequencyOffDesc. - /// - /// In en, this message translates to: - /// **'Only syncs while the app is open.'** - String get syncFrequencyOffDesc; - - /// No description provided for @settingsAppLockUnlockReason. - /// - /// In en, this message translates to: - /// **'Unlock Wallet'** - String get settingsAppLockUnlockReason; - - /// No description provided for @settingsLoggingLabel. - /// - /// In en, this message translates to: - /// **'Diagnostic Logs'** - String get settingsLoggingLabel; - - /// No description provided for @settingsLoggingDescription. - /// - /// In en, this message translates to: - /// **'Log wallet operations to a text file in the app\'s data folder for debugging purposes.'** - String get settingsLoggingDescription; - - /// No description provided for @settingsLoggingDescriptionIos. - /// - /// In en, this message translates to: - /// **'Log wallet operations and allow the logs to be exported to a text file.'** - String get settingsLoggingDescriptionIos; - - /// No description provided for @settingsExportLogsLabel. - /// - /// In en, this message translates to: - /// **'Export Logs'** - String get settingsExportLogsLabel; - - /// No description provided for @settingsExportLogsButton. - /// - /// In en, this message translates to: - /// **'Export'** - String get settingsExportLogsButton; - - /// No description provided for @settingsExportLogsError. - /// - /// In en, this message translates to: - /// **'No logs found to export.'** - String get settingsExportLogsError; - - /// No description provided for @settingsExportLogsFailed. - /// - /// In en, this message translates to: - /// **'Could not export the log file'** - String get settingsExportLogsFailed; - - /// No description provided for @settingsThemeLabel. - /// - /// In en, this message translates to: - /// **'Theme'** - String get settingsThemeLabel; - - /// No description provided for @settingsThemeSystem. - /// - /// In en, this message translates to: - /// **'System'** - String get settingsThemeSystem; - - /// No description provided for @settingsThemeLight. - /// - /// In en, this message translates to: - /// **'Light'** - String get settingsThemeLight; - - /// No description provided for @settingsThemeDark. - /// - /// In en, this message translates to: - /// **'Dark'** - String get settingsThemeDark; - - /// No description provided for @settingsThemeLightDesc. - /// - /// In en, this message translates to: - /// **'Light and clean'** - String get settingsThemeLightDesc; - - /// No description provided for @settingsThemeDarkDesc. - /// - /// In en, this message translates to: - /// **'Dark ground, easier at night'** - String get settingsThemeDarkDesc; - - /// No description provided for @settingsThemeSystemDesc. - /// - /// In en, this message translates to: - /// **'Follows your phone'** - String get settingsThemeSystemDesc; - - /// No description provided for @settingsThemeSheetSubtitle. - /// - /// In en, this message translates to: - /// **'Pick a theme to match your style.'** - String get settingsThemeSheetSubtitle; - - /// No description provided for @settingsLanguageLabel. - /// - /// In en, this message translates to: - /// **'Language'** - String get settingsLanguageLabel; - - /// No description provided for @settingsLanguageSheetSubtitle. - /// - /// In en, this message translates to: - /// **'Pick your language and localization.'** - String get settingsLanguageSheetSubtitle; - - /// No description provided for @settingsFiatApiSettingsLabel. - /// - /// In en, this message translates to: - /// **'Price Display Settings'** - String get settingsFiatApiSettingsLabel; - - /// No description provided for @settingsLwsViewKeysLabel. - /// - /// In en, this message translates to: - /// **'LWS View Keys'** - String get settingsLwsViewKeysLabel; - - /// No description provided for @settingsLwsViewKeysButton. - /// - /// In en, this message translates to: - /// **'View'** - String get settingsLwsViewKeysButton; - - /// No description provided for @revealSeedAuthReason. - /// - /// In en, this message translates to: - /// **'Confirm it\'s you to view your recovery phrase'** - String get revealSeedAuthReason; - - /// No description provided for @settingsSecretKeysLabel. - /// - /// In en, this message translates to: - /// **'Secret Restore Keys'** - String get settingsSecretKeysLabel; - - /// No description provided for @settingsViewLwsKeysDialogText. - /// - /// In en, this message translates to: - /// **'Only share this information with your light-wallet server. These keys allow the holder to permanently see all transactions related to your wallets. Sharing these with an untrusted person will significantly harm your privacy.'** - String get settingsViewLwsKeysDialogText; - - /// No description provided for @settingsViewLwsKeysDialogRevealButton. - /// - /// In en, this message translates to: - /// **'Reveal'** - String get settingsViewLwsKeysDialogRevealButton; - - /// No description provided for @settingsViewSecretKeysDialogText. - /// - /// In en, this message translates to: - /// **'Do not share these keys with anyone, including anyone claiming to be support. If you receive a request to provide these, you are being scammed. If you provide this information to another person, you will lose your money and it cannot be recovered.'** - String get settingsViewSecretKeysDialogText; - - /// No description provided for @settingsViewSecretKeysDialogRevealButton. - /// - /// In en, this message translates to: - /// **'Reveal'** - String get settingsViewSecretKeysDialogRevealButton; - - /// No description provided for @settingsDeleteWalletButton. - /// - /// In en, this message translates to: - /// **'Delete Wallet'** - String get settingsDeleteWalletButton; - - /// No description provided for @settingsDeleteWalletDialogText. - /// - /// In en, this message translates to: - /// **'Are you sure you want to delete your wallet? You will lose access to your funds unless you have backed up your recovery phrase. This will not remove your address book and app settings.'** - String get settingsDeleteWalletDialogText; - - /// No description provided for @settingsDeleteWalletDialogDeleteButton. - /// - /// In en, this message translates to: - /// **'Delete'** - String get settingsDeleteWalletDialogDeleteButton; - - /// No description provided for @settingsDeleteAddressBookButton. - /// - /// In en, this message translates to: - /// **'Delete Address Book'** - String get settingsDeleteAddressBookButton; - - /// No description provided for @settingsDeleteAddressBookDialogText. - /// - /// In en, this message translates to: - /// **'Are you sure you want to delete your address book? If automatic backup is turned on, it will also delete the address book in these automatic backups.'** - String get settingsDeleteAddressBookDialogText; - - /// No description provided for @settingsDeleteAddressBookDialogDeleteButton. - /// - /// In en, this message translates to: - /// **'Delete'** - String get settingsDeleteAddressBookDialogDeleteButton; - - /// No description provided for @settingsAddressBookDeleted. - /// - /// In en, this message translates to: - /// **'Address book deleted'** - String get settingsAddressBookDeleted; - - /// No description provided for @txDetailsTitle. - /// - /// In en, this message translates to: - /// **'Transaction Details'** - String get txDetailsTitle; - - /// No description provided for @txDetailsHashLabel. - /// - /// In en, this message translates to: - /// **'Hash'** - String get txDetailsHashLabel; - - /// No description provided for @txDetailsTimeAndDateLabel. - /// - /// In en, this message translates to: - /// **'Time and Date'** - String get txDetailsTimeAndDateLabel; - - /// No description provided for @txDetailsConfirmationHeightLabel. - /// - /// In en, this message translates to: - /// **'Confirmation Height'** - String get txDetailsConfirmationHeightLabel; - - /// No description provided for @txDetailsConfirmationsLabel. - /// - /// In en, this message translates to: - /// **'Confirmations'** - String get txDetailsConfirmationsLabel; - - /// No description provided for @txDetailsViewKeyLabel. - /// - /// In en, this message translates to: - /// **'View Key'** - String get txDetailsViewKeyLabel; - - /// No description provided for @txDetailsRecipientsLabel. - /// - /// In en, this message translates to: - /// **'Recipients'** - String get txDetailsRecipientsLabel; - - /// No description provided for @txDetailsReceivedAtLabel. - /// - /// In en, this message translates to: - /// **'Received At'** - String get txDetailsReceivedAtLabel; - - /// No description provided for @txDetailsChangeRecipientLabel. - /// - /// In en, this message translates to: - /// **'Change Recipient'** - String get txDetailsChangeRecipientLabel; - - /// No description provided for @unconfirmed. - /// - /// In en, this message translates to: - /// **'Unconfirmed'** - String get unconfirmed; - - /// No description provided for @txDetailsCopyHint. - /// - /// In en, this message translates to: - /// **'tap any value to copy'** - String get txDetailsCopyHint; - - /// No description provided for @txDetailsFailed. - /// - /// In en, this message translates to: - /// **'This transaction failed. The funds were not sent.'** - String get txDetailsFailed; - - /// No description provided for @txDetailsUnknownStatus. - /// - /// In en, this message translates to: - /// **'This transaction was not confirmed as sent. Check before sending again.'** - String get txDetailsUnknownStatus; - - /// No description provided for @copiedToClipboard. - /// - /// In en, this message translates to: - /// **'Copied to clipboard'** - String get copiedToClipboard; - - /// No description provided for @lwsKeysTitle. - /// - /// In en, this message translates to: - /// **'LWS Keys'** - String get lwsKeysTitle; - - /// No description provided for @lwsKeysPrimaryAddress. - /// - /// In en, this message translates to: - /// **'Primary Address'** - String get lwsKeysPrimaryAddress; - - /// No description provided for @lwsKeysRestoreHeight. - /// - /// In en, this message translates to: - /// **'Restore Height'** - String get lwsKeysRestoreHeight; - - /// No description provided for @lwsKeysSecretViewKey. - /// - /// In en, this message translates to: - /// **'Secret View Key'** - String get lwsKeysSecretViewKey; - - /// No description provided for @lwsKeysWarning. - /// - /// In en, this message translates to: - /// **'Screenshots are blocked on this screen. Make sure nobody is looking over your shoulder.'** - String get lwsKeysWarning; - - /// No description provided for @secretKeysTitle. - /// - /// In en, this message translates to: - /// **'Secret Restore Keys'** - String get secretKeysTitle; - - /// No description provided for @secretKeysDescription. - /// - /// In en, this message translates to: - /// **'These recovery phrases and keys restore full control of your wallet. Anyone who sees them can spend your funds.'** - String get secretKeysDescription; - - /// No description provided for @secretKeysMnemonic. - /// - /// In en, this message translates to: - /// **'Recovery phrase'** - String get secretKeysMnemonic; - - /// No description provided for @secretKeysPublicSpendKey. - /// - /// In en, this message translates to: - /// **'Public Spend Key'** - String get secretKeysPublicSpendKey; - - /// No description provided for @secretKeysSecretSpendKey. - /// - /// In en, this message translates to: - /// **'Secret Spend Key'** - String get secretKeysSecretSpendKey; - - /// No description provided for @secretKeysPublicViewKey. - /// - /// In en, this message translates to: - /// **'Public View Key'** - String get secretKeysPublicViewKey; - - /// No description provided for @secretKeysWarning. - /// - /// In en, this message translates to: - /// **'Never share these or enter them into any website. Store them offline.'** - String get secretKeysWarning; - - /// No description provided for @scanQrTitle. - /// - /// In en, this message translates to: - /// **'Scan QR Code'** - String get scanQrTitle; - - /// No description provided for @confirmSendTitle. - /// - /// In en, this message translates to: - /// **'Confirm Send'** - String get confirmSendTitle; - - /// No description provided for @confirmSendDescription. - /// - /// In en, this message translates to: - /// **'Transactions are irreversible, so make sure that these details match exactly.'** - String get confirmSendDescription; - - /// No description provided for @confirmSendHighFeeWarning. - /// - /// In en, this message translates to: - /// **'The network fee is {percent} of the amount you are sending.'** - String confirmSendHighFeeWarning(String percent); - - /// No description provided for @addressBookTitle. - /// - /// In en, this message translates to: - /// **'Address Book'** - String get addressBookTitle; - - /// No description provided for @addressBookAddContact. - /// - /// In en, this message translates to: - /// **'Add Contact'** - String get addressBookAddContact; - - /// No description provided for @addressBookEditContact. - /// - /// In en, this message translates to: - /// **'Edit Contact'** - String get addressBookEditContact; - - /// No description provided for @addressBookDeleteContact. - /// - /// In en, this message translates to: - /// **'Delete Contact'** - String get addressBookDeleteContact; - - /// No description provided for @addressBookDeleteContactConfirmation. - /// - /// In en, this message translates to: - /// **'Are you sure you want to delete \"{contactName}\"?'** - String addressBookDeleteContactConfirmation(String contactName); - - /// No description provided for @addressBookDelete. - /// - /// In en, this message translates to: - /// **'Delete'** - String get addressBookDelete; - - /// No description provided for @addressBookSearchHint. - /// - /// In en, this message translates to: - /// **'Search contacts...'** - String get addressBookSearchHint; - - /// No description provided for @addressBookNoContacts. - /// - /// In en, this message translates to: - /// **'No Contacts Yet'** - String get addressBookNoContacts; - - /// No description provided for @addressBookNoContactsDescription. - /// - /// In en, this message translates to: - /// **'Add your first contact by tapping the + button'** - String get addressBookNoContactsDescription; - - /// No description provided for @addressBookNoSearchResults. - /// - /// In en, this message translates to: - /// **'No Contacts Found'** - String get addressBookNoSearchResults; - - /// No description provided for @addressBookEdit. - /// - /// In en, this message translates to: - /// **'Edit'** - String get addressBookEdit; - - /// No description provided for @addressBookContactName. - /// - /// In en, this message translates to: - /// **'Contact Name'** - String get addressBookContactName; - - /// No description provided for @addressBookNameHint. - /// - /// In en, this message translates to: - /// **'Name'** - String get addressBookNameHint; - - /// No description provided for @addressBookAddDescription. - /// - /// In en, this message translates to: - /// **'A name and a Monero address to pay them on.'** - String get addressBookAddDescription; - - /// No description provided for @addressBookEditDescription. - /// - /// In en, this message translates to: - /// **'Update this contact\'s name or address.'** - String get addressBookEditDescription; - - /// No description provided for @addressBookUpdate. - /// - /// In en, this message translates to: - /// **'Update'** - String get addressBookUpdate; - - /// No description provided for @addressBookSave. - /// - /// In en, this message translates to: - /// **'Save'** - String get addressBookSave; - - /// No description provided for @sendPriorityLow. - /// - /// In en, this message translates to: - /// **'Low'** - String get sendPriorityLow; - - /// No description provided for @sendPriorityNormal. - /// - /// In en, this message translates to: - /// **'Normal'** - String get sendPriorityNormal; - - /// No description provided for @sendPriorityHigh. - /// - /// In en, this message translates to: - /// **'High'** - String get sendPriorityHigh; - - /// No description provided for @sendContactsButton. - /// - /// In en, this message translates to: - /// **'Contacts'** - String get sendContactsButton; - - /// No description provided for @sendToLabel. - /// - /// In en, this message translates to: - /// **'To'** - String get sendToLabel; - - /// No description provided for @sendPasteButton. - /// - /// In en, this message translates to: - /// **'Paste'** - String get sendPasteButton; - - /// No description provided for @sendScanButton. - /// - /// In en, this message translates to: - /// **'Scan'** - String get sendScanButton; - - /// No description provided for @sendMaxButton. - /// - /// In en, this message translates to: - /// **'MAX'** - String get sendMaxButton; - - /// No description provided for @sendNetworkFee. - /// - /// In en, this message translates to: - /// **'Network fee'** - String get sendNetworkFee; - - /// No description provided for @sendPriorityHeading. - /// - /// In en, this message translates to: - /// **'Priority'** - String get sendPriorityHeading; - - /// No description provided for @sendPickContactTitle. - /// - /// In en, this message translates to: - /// **'Send to a contact'** - String get sendPickContactTitle; - - /// No description provided for @sendAvailableSuffix. - /// - /// In en, this message translates to: - /// **'available'** - String get sendAvailableSuffix; - - /// No description provided for @sendIrreversibleNote. - /// - /// In en, this message translates to: - /// **'Transactions are irreversible, so make sure the address and amount are correct.'** - String get sendIrreversibleNote; - - /// No description provided for @sendSwitchUnit. - /// - /// In en, this message translates to: - /// **'Switch amount unit'** - String get sendSwitchUnit; - - /// No description provided for @sendFailedToGetFeesError. - /// - /// In en, this message translates to: - /// **'Failed to get fees.'** - String get sendFailedToGetFeesError; - - /// No description provided for @sendNoConnectionError. - /// - /// In en, this message translates to: - /// **'Couldn\'t reach the server. Check your connection and try again.'** - String get sendNoConnectionError; - - /// No description provided for @torSettingsTitle. - /// - /// In en, this message translates to: - /// **'Tor Settings'** - String get torSettingsTitle; - - /// No description provided for @torSettingsModeBuiltIn. - /// - /// In en, this message translates to: - /// **'Built-in Tor'** - String get torSettingsModeBuiltIn; - - /// No description provided for @torSettingsModeExternal. - /// - /// In en, this message translates to: - /// **'External Tor'** - String get torSettingsModeExternal; - - /// No description provided for @torSettingsModeDisabled. - /// - /// In en, this message translates to: - /// **'No Tor'** - String get torSettingsModeDisabled; - - /// No description provided for @torChoiceBuiltInDesc. - /// - /// In en, this message translates to: - /// **'Bundled with Skylight Wallet · recommended'** - String get torChoiceBuiltInDesc; - - /// No description provided for @torChoiceExternalDesc. - /// - /// In en, this message translates to: - /// **'Orbot, or a daemon you run yourself'** - String get torChoiceExternalDesc; - - /// No description provided for @torChoiceNoTorDesc. - /// - /// In en, this message translates to: - /// **'Servers you connect to can see your IP address'** - String get torChoiceNoTorDesc; - - /// No description provided for @torChoiceConnected. - /// - /// In en, this message translates to: - /// **'Connected to Tor'** - String get torChoiceConnected; - - /// No description provided for @torChoiceTestFailed. - /// - /// In en, this message translates to: - /// **'Test failed'** - String get torChoiceTestFailed; - - /// No description provided for @torChoiceTitle. - /// - /// In en, this message translates to: - /// **'Tor Connection Setup'** - String get torChoiceTitle; - - /// No description provided for @torChoiceSubtitle. + /// No description provided for @torChoiceSubtitle. /// /// In en, this message translates to: /// **'Tor can hide your IP address from servers you connect to. This does not reduce the information that is stored on public blockchains. In general, how do you want Skylight Wallet to handle Tor connections?'** String get torChoiceSubtitle; - /// No description provided for @torSettingsSocksPortLabel. - /// - /// In en, this message translates to: - /// **'SOCKS Port'** - String get torSettingsSocksPortLabel; - - /// No description provided for @torSettingsUseOrbotLabel. - /// - /// In en, this message translates to: - /// **'Use Orbot/InviZible'** - String get torSettingsUseOrbotLabel; - - /// No description provided for @torSettingsUseOrbotLabelIos. - /// - /// In en, this message translates to: - /// **'Use Orbot'** - String get torSettingsUseOrbotLabelIos; - - /// No description provided for @torSettingsSaveButton. - /// - /// In en, this message translates to: - /// **'Save'** - String get torSettingsSaveButton; - - /// No description provided for @torSettingsTestConnectionButton. - /// - /// In en, this message translates to: - /// **'Test Connection'** - String get torSettingsTestConnectionButton; - - /// No description provided for @torDisabledWalletsWarningTitle. - /// - /// In en, this message translates to: - /// **'Disable Tor?'** - String get torDisabledWalletsWarningTitle; - - /// No description provided for @torDisabledWalletsWarningBody. - /// - /// In en, this message translates to: - /// **'Some wallets are set to connect over Tor. Disabling Tor will disconnect them, and they will stay disconnected until you reconfigure their connection.'** - String get torDisabledWalletsWarningBody; - - /// No description provided for @torDisabledWalletsWarningConfirm. - /// - /// In en, this message translates to: - /// **'Disable Tor'** - String get torDisabledWalletsWarningConfirm; - - /// No description provided for @settingsTorSettingsLabel. - /// - /// In en, this message translates to: - /// **'Tor Settings'** - String get settingsTorSettingsLabel; - - /// No description provided for @lwsSetupTorDisabledError. - /// - /// In en, this message translates to: - /// **'Tor is disabled. Please go back and enable it.'** - String get lwsSetupTorDisabledError; - - /// No description provided for @coinHomeSwap. - /// - /// In en, this message translates to: - /// **'Swap'** - String get coinHomeSwap; - - /// No description provided for @coinHomeSwapComingSoon. - /// - /// In en, this message translates to: - /// **'Coming soon!'** - String get coinHomeSwapComingSoon; - - /// No description provided for @verifySeedTitle. - /// - /// In en, this message translates to: - /// **'Verify Recovery Phrase'** - String get verifySeedTitle; - - /// No description provided for @homeIncomingTitle. - /// - /// In en, this message translates to: - /// **'Incoming'** - String get homeIncomingTitle; - - /// No description provided for @homeIncomingUnlocksIn. - /// - /// In en, this message translates to: - /// **'Unlocks in about {minutes, plural, =1{1 minute} other{{minutes} minutes}} · {done} of {needed}'** - String homeIncomingUnlocksIn(int minutes, int done, int needed); - - /// No description provided for @homeIncomingConfirmations. - /// - /// In en, this message translates to: - /// **'{done} of {needed} confirmations'** - String homeIncomingConfirmations(int done, int needed); - - /// No description provided for @homeChartTitle. - /// - /// In en, this message translates to: - /// **'Balance · 90 days'** - String get homeChartTitle; - - /// No description provided for @homeChartToday. - /// - /// In en, this message translates to: - /// **'Today'** - String get homeChartToday; - - /// No description provided for @homeChartEachCoin. - /// - /// In en, this message translates to: - /// **'Each coin\'s balance'** - String get homeChartEachCoin; - - /// No description provided for @homeChartNew. - /// - /// In en, this message translates to: - /// **'New'** - String get homeChartNew; - - /// No description provided for @homeChartEmpty. - /// - /// In en, this message translates to: - /// **'Your balance over time shows here after your first transaction.'** - String get homeChartEmpty; - - /// No description provided for @homeChecklistTitle. - /// - /// In en, this message translates to: - /// **'Secure your wallet'** - String get homeChecklistTitle; - - /// No description provided for @homeChecklistProgress. - /// - /// In en, this message translates to: - /// **'{done} of {total}'** - String homeChecklistProgress(int done, int total); - - /// No description provided for @homeChecklistVerifySeed. - /// - /// In en, this message translates to: - /// **'Verify your recovery phrase'** - String get homeChecklistVerifySeed; - - /// No description provided for @homeChecklistAutoBackup. - /// - /// In en, this message translates to: - /// **'Turn on automatic backups'** - String get homeChecklistAutoBackup; - - /// No description provided for @homeChecklistTor. - /// - /// In en, this message translates to: - /// **'Connect through Tor'** - String get homeChecklistTor; - - /// No description provided for @homeChecklistPrivateLookups. - /// - /// In en, this message translates to: - /// **'Use Tor for price and OpenAlias lookups'** - String get homeChecklistPrivateLookups; - - /// No description provided for @homeChecklistSecurityKeys. - /// - /// In en, this message translates to: - /// **'Set up a security key'** - String get homeChecklistSecurityKeys; - - /// No description provided for @verifySeedSubtitle. - /// - /// In en, this message translates to: - /// **'Input words {first}, {second} and {third} of your recovery phrase below.'** - String verifySeedSubtitle(int first, int second, int third); - - /// No description provided for @verifySeedButton. - /// - /// In en, this message translates to: - /// **'Verify'** - String get verifySeedButton; - - /// No description provided for @verifySeedWrongTitle. - /// - /// In en, this message translates to: - /// **'Those words don\'t match'** - String get verifySeedWrongTitle; - - /// No description provided for @verifySeedWrongBody. - /// - /// In en, this message translates to: - /// **'Check the recovery phrase you wrote down, then enter the three new words asked for. {count, plural, =1{1 try} other{{count} tries}} left before the app locks.'** - String verifySeedWrongBody(int count); - - /// No description provided for @verifySeedTryAgain. - /// - /// In en, this message translates to: - /// **'Try again'** - String get verifySeedTryAgain; - - /// No description provided for @verifySeedUnavailable. - /// - /// In en, this message translates to: - /// **'Your recovery phrase can\'t be read right now. Try again after unlocking the wallet.'** - String get verifySeedUnavailable; - - /// No description provided for @verifySeedDone. - /// - /// In en, this message translates to: - /// **'Recovery phrase verified'** - String get verifySeedDone; - - /// No description provided for @homeIncomingTitleMore. + /// Tor Settings sheet description, under its title. Sentence case. Spice's says every chain; Skylight has one wallet. /// /// In en, this message translates to: - /// **'Incoming (+{count, plural, =1{1 Other} other{{count} Others}})'** - String homeIncomingTitleMore(int count); + /// **'Tor can hide your IP address from servers you connect to. This does not reduce the information that is stored on public blockchains. Changing this reconnects your wallet.'** + String get torSettingsSheetSubtitle; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 15bc859..290a22f 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1,6 +1,5 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; - import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9,56 +8,6 @@ import 'app_localizations.dart'; class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); - @override - String get scanQrUnexpectedCode => 'That QR isn\'t a valid address or payment code.'; - - @override - String deepLinkCoinNotConfigured(String chain) { - return 'Please configure your $chain connection first.'; - } - - @override - String get continueText => 'Continue'; - - @override - String get cancel => 'Cancel'; - - @override - String get done => 'Done'; - - @override - String get close => 'Close'; - - @override - String get back => 'Back'; - - @override - String get unknownError => 'Unknown error.'; - - @override - String get warning => 'Warning'; - - @override - String get amount => 'Amount'; - - @override - String get networkFee => 'Network Fee'; - - @override - String get address => 'Address'; - - @override - String get pending => 'Pending'; - - @override - String get copy => 'Copy'; - - @override - String get addressCopied => 'Address copied to clipboard'; - - @override - String get fieldEmptyError => 'This field cannot be empty.'; - @override String get welcomeDescription => 'Skylight Wallet is one of the simplest Monero wallets. We will help you set up a wallet and connect to a server.'; @@ -78,12 +27,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get welcomePrivacyLink => 'Privacy Policy'; - @override - String get settingsLicensesLabel => 'Open-Source Licenses'; - - @override - String get lwsSetupTitle => 'Connection Setup'; - @override String get lwsSetupDescription => 'Connect to a Monero light-wallet server (LWS) or your own full node. Only select a server you trust. Even if you use Tor, this server can learn information about you. With an LWS, your private view key and primary address will be shared with this server.'; @@ -94,92 +37,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get lwsSetupProxyPortHint => '4444 for I2P'; - @override - String get lwsSetupUseTorLabel => 'Use Tor'; - - @override - String get lwsSetupTestConnectionButton => 'Test Connection'; - - @override - String get lwsSetupStartingTor => 'Starting Tor...'; - - @override - String get lwsSetupContinueButton => 'Continue'; - - @override - String get connectionTypeLws => 'Light Wallet Server'; - - @override - String get connectionTypeNode => 'Monero Node'; - - @override - String get connectionRemoteIpNotAllowed => - 'Connections to remote IP addresses aren\'t allowed. Use a domain name or a local IP address.'; - - @override - String get connectionLocalOverTorNotAllowed => - 'A local address can\'t be reached through Tor. Turn Tor off, or use the server\'s onion address.'; - - @override - String get connectionOnionNeedsTor => - 'An onion address can only be reached through Tor. Turn Tor on to use it.'; - - @override - String get connectionPathNotSupported => - 'This server is reached at its root, so its address can\'t include a path like /wallet. Remove the path.'; - - @override - String get lwsAwaitingApprovalAccount => - 'Your light wallet server hasn\'t approved this wallet yet. Ask the server\'s operator to approve it so you can see your balance and transactions.'; - - @override - String get lwsAwaitingApprovalRestoreHeight => - 'Your light wallet server hasn\'t approved scanning from your restore height yet, so older transactions may be missing. Ask the server\'s operator to approve it.'; - - @override - String get connectionTorDisabledWarning => - 'Tor is disabled, so this connection no longer works. Please reconfigure it.'; - - @override - String get connectionProtocolHttps => 'Removing protocol. Using HTTPS for domains.'; - - @override - String get connectionProtocolHttp => 'Removing protocol. Using HTTP for local addresses.'; - - @override - String get connectionProtocolHttpOnion => 'Removing protocol. Onion addresses use HTTP over Tor.'; - - @override - String get connectionTestStop => 'Stop'; - - @override - String get connectionTestingTitle => 'Testing connection'; - - @override - String get connectionTestingDetail => 'Checking whether the server answers.'; - - @override - String get connectionTestAgain => 'Test again'; - - @override - String get connectionResultWorksTitle => 'Connection works'; - - @override - String get connectionResultFailedTitle => 'Could not reach this server'; - - @override - String get connectionResultFailedDetail => - 'Nothing answered. Check the address and port, and whether the server accepts your connection.'; - - @override - String get connectionReachedOverTor => 'Reached over Tor'; - - @override - String get connectionReachedViaProxy => 'Reached through your proxy'; - - @override - String get connectionReachedDirect => 'Reached directly'; - @override String get settingsConnectionSettingsLabel => 'Connection Settings'; @@ -196,152 +53,27 @@ class AppLocalizationsEn extends AppLocalizations { String get settingsConnectionOverClearnet => 'over Clearnet'; @override - String get settingsConnectionInLocalNetwork => 'in Local Network'; - - @override - String homeBlocksRemaining(int count) { - final intl.NumberFormat countNumberFormat = intl.NumberFormat.decimalPattern(localeName); - final String countString = countNumberFormat.format(count); - - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: '$countString blocks left', - one: '1 block left', - ); - return '$_temp0'; - } - - @override - String get fiatApiSetupTitle => 'Price Display Setup'; + String get settingsConnectionInLocalNetwork => 'in local network'; @override String get fiatApiSetupDescription => 'Skylight Wallet can automatically fetch the latest Monero price. Your balances are not sent to the server. How do you want to fetch this price data?'; - @override - String get fiatApiSettingsModeLabel => 'Mode'; - - @override - String get fiatApiSettingsModeTorOnly => 'Tor-Only'; - - @override - String get fiatApiSettingsModeClearnet => 'Clearnet-Only (Not Private)'; - - @override - String get fiatApiSettingsModeDisabled => 'Disabled'; - - @override - String get fiatModeTorOnlyDesc => 'Prices fetched over Tor · recommended'; - - @override - String get fiatModeClearnetDesc => 'Not private, the price server sees your IP address'; - - @override - String get fiatModeDisabledDesc => 'No prices fetched, balances shown in crypto only'; - - @override - String get settingsOpenAliasSettingsLabel => 'OpenAlias Settings'; - - @override - String get openAliasSettingsSheetSubtitle => - 'How names like name@example.com are looked up when you send.'; - - @override - String get openAliasModeTorOnlyDesc => 'Names looked up over Tor · recommended'; - - @override - String get openAliasModeClearnetDesc => - 'Not private, DNS servers see your IP address and the name'; - - @override - String get openAliasModeDisabledDesc => 'No lookups, send to addresses only'; - - @override - String get fiatApiSettingsDisplayCurrencyLabel => 'Display Currency'; - - @override - String get createWalletTitle => 'Wallet Setup'; - @override String get createWalletDescription => 'Would you like to create a new wallet or restore an existing wallet?'; - @override - String get createWalletRestoreExistingButton => 'Restore Existing'; - @override String get createWalletRestoreExistingDesc => 'Enter an existing Monero recovery phrase'; - @override - String get createWalletCreateNewButton => 'Create New'; - @override String get createWalletCreateNewDesc => 'Skylight Wallet generates a new Polyseed recovery phrase'; - @override - String get createWalletPasswordTitle => 'Create Wallet Password'; - - @override - String get createWalletPasswordDescription => - 'Create a password to protect your wallet. This password will be required to unlock your wallet.'; - - @override - String get createWalletPasswordHint => 'Enter your password'; - - @override - String get createWalletConfirmPasswordHint => 'Confirm your password'; - - @override - String get passwordTooShortError => 'Password must be at least 8 characters long.'; - - @override - String get passwordsDoNotMatchError => 'Passwords do not match.'; - - @override - String get generateSeedTitle => 'Write these down, in order'; - - @override - String get generateSeedTitleCovered => 'Recovery Phrase'; - @override String get generateSeedSubtitleCovered => 'These words, in this order, are your wallet. Write them down and keep them in a physical safe. If you lose these words or if you share them with anyone else, you will lose your money permanently. Careful planning now avoids a potential disaster later.'; - @override - String get generateSeedSubtitleRevealed => 'Securely save these. Do not share them.'; - - @override - String get generateSeedScreenshotNote => - 'Screenshots are blocked on this screen. Make sure nobody is looking over your shoulder.'; - - @override - String get generateSeedReveal => 'Tap to reveal'; - - @override - String get generateSeedConfirm => - 'I have written down all the words and stored them somewhere only I can reach.'; - - @override - String get generateSeedBirthdayLabel => 'Wallet birthday'; - - @override - String get generateSeedBirthdayReason => 'Where a future restore starts scanning'; - - @override - String get onboardingTorNotePrivacy => 'Connections over Tor can be slower.'; - - @override - String get onboardingTorNoteChangeable => 'You can change this later in Settings.'; - - @override - String get onboardingPriceNoteRatesOnly => - 'Only prices are requested. Your addresses and balances are not sent.'; - - @override - String get onboardingPriceNoteTor => 'Prices are fetched over Tor by default.'; - @override String get onboardingConnectionNoteServer => 'Use a light-wallet server (LWS) or your own Monero node.'; @@ -349,39 +81,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get onboardingConnectionNoteChangeable => 'You can change this later in Settings.'; - @override - String get onboardingWalletNoteGenerated => - 'Skylight Wallet generates your recovery phrase on this device.'; - - @override - String get onboardingWalletNoteRestore => - 'When restoring, a scan-from date makes syncing faster.'; - - @override - String get onboardingSeedNotePassword => - 'Skylight Wallet asks for your password before showing these words again.'; - - @override - String get onboardingPasswordNoteLaunch => - 'You need this password to open your wallet and to view your recovery phrase.'; - - @override - String get onboardingPasswordNoteNotCloud => - 'This password can\'t be recovered. If you lose it, restore your wallet from your recovery phrase.'; - - @override - String get onboardingPasswordStrong => 'Strong'; - - @override - String get onboardingPasswordMatch => 'Passwords match'; - @override String get lwsDetailsTitle => 'Wallet Details'; - @override - String get lwsDetailsDescription => - 'If your Monero light wallet server (LWS) requires registration, you can use these details to add this wallet to that server. Not all servers require registration.'; - @override String get lwsDetailsPrimaryAddressLabel => 'Primary Address'; @@ -391,57 +93,17 @@ class AppLocalizationsEn extends AppLocalizations { @override String get lwsDetailsRestoreHeightLabel => 'Restore Height'; - @override - String get restoreWalletTitle => 'Restore Wallet'; - @override String get restoreWalletDescription => 'Input your Monero recovery phrase below. We will check common formats.'; - @override - String get restoreWalletScanFrom => 'Scan from'; - - @override - String get restoreWalletScanFromReason => 'Skip irrelevant history to save time'; - - @override - String get restoreWalletNotSet => 'Not set'; - - @override - String get restoreScanTitle => 'When did this wallet first receive funds?'; - @override String get restoreScanDescription => 'Skylight Wallet can skip irrelevant history to save you time. Either pick the first month that you used the wallet or select I\'m not sure to check everything. It\'s okay to pick a month that is too early, but it\'s bad to pick a month that is too late.'; - @override - String get restoreScanPickMonth => 'Pick a month'; - - @override - String get restoreScanNotSure => 'I\'m not sure'; - - @override - String get restoreScanNotSureDesc => - 'Scan everything. Slower this first setup but not any slower later. Always complete.'; - - @override - String get restoreScanFromStart => 'Genesis'; - - @override - String get restoreScanDone => 'Done'; - - @override - String get restoreWalletRestoreButton => 'Restore'; - @override String get restoreWalletInvalidMnemonic => 'Invalid recovery phrase.'; - @override - String get restoreWalletSeedLength => 'Number of words'; - - @override - String get restoreWalletPaste => 'Paste'; - @override String get restoreWalletSeedTypePolyseed => 'Polyseed'; @@ -451,46 +113,14 @@ class AppLocalizationsEn extends AppLocalizations { @override String get restoreWalletSeedTypeLegacy => 'Legacy'; - @override - String restoreWalletBadWord(int position) { - return 'Word $position isn\'t a BIP39 word.'; - } - - @override - String restoreWalletDidYouMean(String word) { - return 'Did you mean $word?'; - } - - @override - String get restoreWalletChecksumError => - 'This isn\'t a valid recovery phrase — check the words and their order.'; - @override String get navigationBarWallet => 'Wallet'; - @override - String get historyTitle => 'History'; - - @override - String get historyExportCsv => 'Export CSV'; - - @override - String get historyExported => 'History exported'; - - @override - String get historyExportFailed => 'Could not export the history'; - - @override - String get navigationBarHistory => 'History'; - - @override - String get historyFilterType => 'Type'; - @override String get historyFilterAddress => 'Address'; @override - String get historyFilterAddressAny => 'Any address'; + String get historyFilterAddressAny => 'Any Address'; @override String get historyFilterAddressHint => 'Transactions received at this address, or sent to it.'; @@ -499,696 +129,93 @@ class AppLocalizationsEn extends AppLocalizations { String get historyFilterAddressInvalid => 'Not a Monero address'; @override - String get historyFilterReset => 'Reset'; + String get unlockTitle => 'Unlock Wallet'; @override - String get navigationBarSettings => 'Settings'; + String get homeDisconnected => 'Disconnected'; @override - String get unlockButton => 'Unlock'; + String get receiveQrHint => 'Scan this code to send Monero to this wallet.'; @override - String get unlockReason => 'Unlock Wallet'; + String get sendInvalidAddressError => 'Invalid address.'; @override - String get unlockUnableToAuthError => 'Unable to authenticate.'; + String get syncFrequencySheetSubtitle => 'How often Monero syncs while the app is closed.'; @override - String get unlockWithFaceId => 'Unlock with Face ID'; + String get syncFrequencyContinuousDesc => + 'Continuously sync Monero in the background so it\'s always up to date when you open the app. Uses more battery and requires a persistent notification.'; @override - String get unlockWithTouchId => 'Unlock with Touch ID'; + String get syncFrequencyPeriodicDesc => + 'Periodically sync Monero in the background so it\'s usually up to date when you open the app. With a Monero node, it only syncs while charging and on WiFi.'; @override - String get unlockTitle => 'Unlock Wallet'; + String get syncFrequencyPeriodicDescIos => + 'Periodically sync Monero in the background so it\'s usually up to date when you open the app. With a Monero node, it only syncs while charging.'; @override - String get unlockPasswordHint => 'Enter your password'; + String get settingsThemeLightDesc => 'Light and clean'; @override - String get unlockIncorrectPasswordError => 'Incorrect password. Please try again.'; + String get settingsLwsViewKeysLabel => 'LWS View Keys'; @override - String get homeSyncing => 'Syncing'; + String get settingsSecretKeysLabel => 'Secret Restore Keys'; @override - String get homeSynced => 'Synced'; + String get settingsViewLwsKeysDialogText => + 'Only share this information with your light-wallet server. These keys allow the holder to permanently see all transactions related to your wallets. Sharing these with an untrusted person will significantly harm your privacy.'; @override - String get homeTorConnected => 'Tor · connected'; + String get settingsViewLwsKeysDialogRevealButton => 'Reveal'; @override - String get homeTorConnecting => 'Tor · connecting'; + String get settingsViewSecretKeysDialogText => + 'Do not share these keys with anyone, including anyone claiming to be support. If you receive a request to provide these, you are being scammed. If you provide this information to another person, you will lose your money and it cannot be recovered.'; @override - String get homeTorDisconnected => 'Tor · disconnected'; + String get settingsViewSecretKeysDialogRevealButton => 'Reveal'; @override - String get homeTorOff => 'Tor · off'; + String get secretKeysTitle => 'Secret Restore Keys'; @override - String get homeTorExternalConnected => 'External Tor · connected'; + String get secretKeysDescription => + 'These recovery phrases and keys restore full control of your wallet. Anyone who sees them can spend your funds.'; @override - String get homeTorExternalConnecting => 'External Tor · connecting'; + String get secretKeysMnemonic => 'Recovery Phrase'; @override - String get homeTorExternalDisconnected => 'External Tor · disconnected'; + String get secretKeysPublicSpendKey => 'Public Spend Key'; @override - String get homeReceive => 'Receive'; + String get secretKeysSecretSpendKey => 'Secret Spend Key'; @override - String get homeSend => 'Send'; + String get secretKeysPublicViewKey => 'Public View Key'; @override - String get homeNoTransactions => 'No Transactions'; + String get secretKeysWarning => + 'Never share these or enter them into any website. Store them offline.'; @override - String get homeFiatApiError => 'Error connecting to price server'; + String get addressBookAddDescription => 'A name and a Monero address to pay them on.'; @override - String get homeDisconnected => 'Disconnected'; + String get addressBookEditDescription => 'Update this contact\'s name or address.'; @override - String get coinHomeActivityTitle => 'Activity'; - - @override - String get coinHomeReceived => 'Received'; - - @override - String get coinHomeSent => 'Sent'; - - @override - String get receiveTitle => 'Receive'; - - @override - String get receivePrimaryAddressWarn => - 'Warning: Unless you know what you\'re doing, please use subaddresses for better privacy.'; - - @override - String get receiveServerNoSubaddressesWarn => - 'Warning: This server does not support subaddresses. For better privacy, consider using a server that supports them.'; - - @override - String get receiveSubaddressUnavailableWarn => - 'Warning: You need to configure Monero to enable subaddresses, which better protect your privacy.'; - - @override - String get receiveMaxSubaddressesReachedWarn => - 'You have reached the maximum number of subaddresses supported by this server. This is a used address.'; - - @override - String get receiveSubaddressTab => 'Subaddress'; - - @override - String get receivePrimaryTab => 'Primary address'; - - @override - String get receiveCopyAddress => 'Copy address'; - - @override - String get receiveQrHint => 'Scan this code to send Monero to this wallet.'; - - @override - String get receiveEnlargeQr => 'Tap to enlarge and brighten'; - - @override - String get receiveShrinkQr => 'Tap to shrink'; - - @override - String get receiveShareError => 'Could not open the share sheet'; - - @override - String receiveAddressHeading(String coin) { - return 'Your $coin address'; - } - - @override - String get sendTitle => 'Send'; - - @override - String get sendSendButton => 'Send'; - - @override - String get sendTransactionSuccessfullySent => 'Transaction successfully sent!'; - - @override - String get sendOpenAliasResolveError => 'Invalid OpenAlias.'; - - @override - String get sendOpenAliasDisabledError => 'OpenAlias is turned off. Turn it on in Settings.'; - - @override - String get sendInvalidAddressError => 'Invalid address.'; - - @override - String get sendInsufficientBalanceError => 'Insufficient balance.'; - - @override - String get sendInsufficientBalanceToCoverFeeError => - 'Insufficient balance to cover the network fee.'; - - @override - String get settingsTitle => 'Settings'; - - @override - String get settingsSectionGeneral => 'General'; - - @override - String get settingsSectionSecurityPrivacy => 'Security and privacy'; - - @override - String get settingsSectionData => 'Data'; - - @override - String get settingsSectionBehaviour => 'Behavior'; - - @override - String get settingsSectionWallet => 'Wallet'; - - @override - String get settingsSectionAbout => 'About'; - - @override - String get settingsNotifyNewTxsLabel => 'Transaction Notifications'; - - @override - String get settingsNotifyNewTxsDescription => - 'Show a notification when you receive a transaction. Increase the sync frequency setting to see them sooner.'; - - @override - String get settingsSyncFrequencyLabel => 'Sync Frequency'; - - @override - String get syncFrequencySheetSubtitle => 'How often Monero syncs while the app is closed.'; - - @override - String get syncFrequencyModeLabel => 'Frequency'; - - @override - String get syncFrequencyContinuous => 'Continuous'; - - @override - String get syncFrequencyContinuousDesc => - 'Continuously sync Monero in the background so it\'s always up to date when you open the app. Uses more battery and requires a persistent notification.'; - - @override - String get syncFrequencyPeriodic => 'Periodic'; - - @override - String get syncFrequencyPeriodicDesc => - 'Periodically sync Monero in the background so it\'s usually up to date when you open the app. With a Monero node, it only syncs while charging and on WiFi.'; - - @override - String get syncFrequencyPeriodicDescIos => - 'Periodically sync Monero in the background so it\'s usually up to date when you open the app. With a Monero node, it only syncs while charging.'; - - @override - String get syncFrequencyOff => 'Off'; - - @override - String get syncFrequencyOffDesc => 'Only syncs while the app is open.'; - - @override - String get settingsAppLockUnlockReason => 'Unlock Wallet'; - - @override - String get settingsLoggingLabel => 'Diagnostic Logs'; - - @override - String get settingsLoggingDescription => - 'Log wallet operations to a text file in the app\'s data folder for debugging purposes.'; - - @override - String get settingsLoggingDescriptionIos => - 'Log wallet operations and allow the logs to be exported to a text file.'; - - @override - String get settingsExportLogsLabel => 'Export Logs'; - - @override - String get settingsExportLogsButton => 'Export'; - - @override - String get settingsExportLogsError => 'No logs found to export.'; - - @override - String get settingsExportLogsFailed => 'Could not export the log file'; - - @override - String get settingsThemeLabel => 'Theme'; - - @override - String get settingsThemeSystem => 'System'; - - @override - String get settingsThemeLight => 'Light'; - - @override - String get settingsThemeDark => 'Dark'; - - @override - String get settingsThemeLightDesc => 'Light and clean'; - - @override - String get settingsThemeDarkDesc => 'Dark ground, easier at night'; - - @override - String get settingsThemeSystemDesc => 'Follows your phone'; - - @override - String get settingsThemeSheetSubtitle => 'Pick a theme to match your style.'; - - @override - String get settingsLanguageLabel => 'Language'; - - @override - String get settingsLanguageSheetSubtitle => 'Pick your language and localization.'; - - @override - String get settingsFiatApiSettingsLabel => 'Price Display Settings'; - - @override - String get settingsLwsViewKeysLabel => 'LWS View Keys'; - - @override - String get settingsLwsViewKeysButton => 'View'; - - @override - String get revealSeedAuthReason => 'Confirm it\'s you to view your recovery phrase'; - - @override - String get settingsSecretKeysLabel => 'Secret Restore Keys'; - - @override - String get settingsViewLwsKeysDialogText => - 'Only share this information with your light-wallet server. These keys allow the holder to permanently see all transactions related to your wallets. Sharing these with an untrusted person will significantly harm your privacy.'; - - @override - String get settingsViewLwsKeysDialogRevealButton => 'Reveal'; - - @override - String get settingsViewSecretKeysDialogText => - 'Do not share these keys with anyone, including anyone claiming to be support. If you receive a request to provide these, you are being scammed. If you provide this information to another person, you will lose your money and it cannot be recovered.'; - - @override - String get settingsViewSecretKeysDialogRevealButton => 'Reveal'; - - @override - String get settingsDeleteWalletButton => 'Delete Wallet'; - - @override - String get settingsDeleteWalletDialogText => - 'Are you sure you want to delete your wallet? You will lose access to your funds unless you have backed up your recovery phrase. This will not remove your address book and app settings.'; - - @override - String get settingsDeleteWalletDialogDeleteButton => 'Delete'; - - @override - String get settingsDeleteAddressBookButton => 'Delete Address Book'; - - @override - String get settingsDeleteAddressBookDialogText => - 'Are you sure you want to delete your address book? If automatic backup is turned on, it will also delete the address book in these automatic backups.'; - - @override - String get settingsDeleteAddressBookDialogDeleteButton => 'Delete'; - - @override - String get settingsAddressBookDeleted => 'Address book deleted'; - - @override - String get txDetailsTitle => 'Transaction Details'; - - @override - String get txDetailsHashLabel => 'Hash'; - - @override - String get txDetailsTimeAndDateLabel => 'Time and Date'; - - @override - String get txDetailsConfirmationHeightLabel => 'Confirmation Height'; - - @override - String get txDetailsConfirmationsLabel => 'Confirmations'; - - @override - String get txDetailsViewKeyLabel => 'View Key'; - - @override - String get txDetailsRecipientsLabel => 'Recipients'; - - @override - String get txDetailsReceivedAtLabel => 'Received At'; - - @override - String get txDetailsChangeRecipientLabel => 'Change Recipient'; - - @override - String get unconfirmed => 'Unconfirmed'; - - @override - String get txDetailsCopyHint => 'tap any value to copy'; - - @override - String get txDetailsFailed => 'This transaction failed. The funds were not sent.'; - - @override - String get txDetailsUnknownStatus => - 'This transaction was not confirmed as sent. Check before sending again.'; - - @override - String get copiedToClipboard => 'Copied to clipboard'; - - @override - String get lwsKeysTitle => 'LWS Keys'; - - @override - String get lwsKeysPrimaryAddress => 'Primary Address'; - - @override - String get lwsKeysRestoreHeight => 'Restore Height'; - - @override - String get lwsKeysSecretViewKey => 'Secret View Key'; - - @override - String get lwsKeysWarning => - 'Screenshots are blocked on this screen. Make sure nobody is looking over your shoulder.'; - - @override - String get secretKeysTitle => 'Secret Restore Keys'; - - @override - String get secretKeysDescription => - 'These recovery phrases and keys restore full control of your wallet. Anyone who sees them can spend your funds.'; - - @override - String get secretKeysMnemonic => 'Recovery phrase'; - - @override - String get secretKeysPublicSpendKey => 'Public Spend Key'; - - @override - String get secretKeysSecretSpendKey => 'Secret Spend Key'; - - @override - String get secretKeysPublicViewKey => 'Public View Key'; - - @override - String get secretKeysWarning => - 'Never share these or enter them into any website. Store them offline.'; - - @override - String get scanQrTitle => 'Scan QR Code'; - - @override - String get confirmSendTitle => 'Confirm Send'; - - @override - String get confirmSendDescription => - 'Transactions are irreversible, so make sure that these details match exactly.'; - - @override - String confirmSendHighFeeWarning(String percent) { - return 'The network fee is $percent of the amount you are sending.'; - } - - @override - String get addressBookTitle => 'Address Book'; - - @override - String get addressBookAddContact => 'Add Contact'; - - @override - String get addressBookEditContact => 'Edit Contact'; - - @override - String get addressBookDeleteContact => 'Delete Contact'; - - @override - String addressBookDeleteContactConfirmation(String contactName) { - return 'Are you sure you want to delete \"$contactName\"?'; - } - - @override - String get addressBookDelete => 'Delete'; - - @override - String get addressBookSearchHint => 'Search contacts...'; - - @override - String get addressBookNoContacts => 'No Contacts Yet'; - - @override - String get addressBookNoContactsDescription => 'Add your first contact by tapping the + button'; - - @override - String get addressBookNoSearchResults => 'No Contacts Found'; - - @override - String get addressBookEdit => 'Edit'; - - @override - String get addressBookContactName => 'Contact Name'; - - @override - String get addressBookNameHint => 'Name'; - - @override - String get addressBookAddDescription => 'A name and a Monero address to pay them on.'; - - @override - String get addressBookEditDescription => 'Update this contact\'s name or address.'; - - @override - String get addressBookUpdate => 'Update'; - - @override - String get addressBookSave => 'Save'; - - @override - String get sendPriorityLow => 'Low'; - - @override - String get sendPriorityNormal => 'Normal'; - - @override - String get sendPriorityHigh => 'High'; - - @override - String get sendContactsButton => 'Contacts'; - - @override - String get sendToLabel => 'To'; - - @override - String get sendPasteButton => 'Paste'; - - @override - String get sendScanButton => 'Scan'; - - @override - String get sendMaxButton => 'MAX'; - - @override - String get sendNetworkFee => 'Network fee'; - - @override - String get sendPriorityHeading => 'Priority'; - - @override - String get sendPickContactTitle => 'Send to a contact'; - - @override - String get sendAvailableSuffix => 'available'; - - @override - String get sendIrreversibleNote => - 'Transactions are irreversible, so make sure the address and amount are correct.'; - - @override - String get sendSwitchUnit => 'Switch amount unit'; - - @override - String get sendFailedToGetFeesError => 'Failed to get fees.'; - - @override - String get sendNoConnectionError => - 'Couldn\'t reach the server. Check your connection and try again.'; - - @override - String get torSettingsTitle => 'Tor Settings'; - - @override - String get torSettingsModeBuiltIn => 'Built-in Tor'; - - @override - String get torSettingsModeExternal => 'External Tor'; - - @override - String get torSettingsModeDisabled => 'No Tor'; - - @override - String get torChoiceBuiltInDesc => 'Bundled with Skylight Wallet · recommended'; - - @override - String get torChoiceExternalDesc => 'Orbot, or a daemon you run yourself'; - - @override - String get torChoiceNoTorDesc => 'Servers you connect to can see your IP address'; - - @override - String get torChoiceConnected => 'Connected to Tor'; - - @override - String get torChoiceTestFailed => 'Test failed'; - - @override - String get torChoiceTitle => 'Tor Connection Setup'; + String get sendIrreversibleNote => + 'Transactions are irreversible, so make sure the address and amount are correct.'; @override String get torChoiceSubtitle => 'Tor can hide your IP address from servers you connect to. This does not reduce the information that is stored on public blockchains. In general, how do you want Skylight Wallet to handle Tor connections?'; @override - String get torSettingsSocksPortLabel => 'SOCKS Port'; - - @override - String get torSettingsUseOrbotLabel => 'Use Orbot/InviZible'; - - @override - String get torSettingsUseOrbotLabelIos => 'Use Orbot'; - - @override - String get torSettingsSaveButton => 'Save'; - - @override - String get torSettingsTestConnectionButton => 'Test Connection'; - - @override - String get torDisabledWalletsWarningTitle => 'Disable Tor?'; - - @override - String get torDisabledWalletsWarningBody => - 'Some wallets are set to connect over Tor. Disabling Tor will disconnect them, and they will stay disconnected until you reconfigure their connection.'; - - @override - String get torDisabledWalletsWarningConfirm => 'Disable Tor'; - - @override - String get settingsTorSettingsLabel => 'Tor Settings'; - - @override - String get lwsSetupTorDisabledError => 'Tor is disabled. Please go back and enable it.'; - - @override - String get coinHomeSwap => 'Swap'; - - @override - String get coinHomeSwapComingSoon => 'Coming soon!'; - - @override - String get verifySeedTitle => 'Verify Recovery Phrase'; - - @override - String get homeIncomingTitle => 'Incoming'; - - @override - String homeIncomingUnlocksIn(int minutes, int done, int needed) { - String _temp0 = intl.Intl.pluralLogic( - minutes, - locale: localeName, - other: '$minutes minutes', - one: '1 minute', - ); - return 'Unlocks in about $_temp0 · $done of $needed'; - } - - @override - String homeIncomingConfirmations(int done, int needed) { - return '$done of $needed confirmations'; - } - - @override - String get homeChartTitle => 'Balance · 90 days'; - - @override - String get homeChartToday => 'Today'; - - @override - String get homeChartEachCoin => 'Each coin\'s balance'; - - @override - String get homeChartNew => 'New'; - - @override - String get homeChartEmpty => 'Your balance over time shows here after your first transaction.'; - - @override - String get homeChecklistTitle => 'Secure your wallet'; - - @override - String homeChecklistProgress(int done, int total) { - return '$done of $total'; - } - - @override - String get homeChecklistVerifySeed => 'Verify your recovery phrase'; - - @override - String get homeChecklistAutoBackup => 'Turn on automatic backups'; - - @override - String get homeChecklistTor => 'Connect through Tor'; - - @override - String get homeChecklistPrivateLookups => 'Use Tor for price and OpenAlias lookups'; - - @override - String get homeChecklistSecurityKeys => 'Set up a security key'; - - @override - String verifySeedSubtitle(int first, int second, int third) { - return 'Input words $first, $second and $third of your recovery phrase below.'; - } - - @override - String get verifySeedButton => 'Verify'; - - @override - String get verifySeedWrongTitle => 'Those words don\'t match'; - - @override - String verifySeedWrongBody(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: '$count tries', - one: '1 try', - ); - return 'Check the recovery phrase you wrote down, then enter the three new words asked for. $_temp0 left before the app locks.'; - } - - @override - String get verifySeedTryAgain => 'Try again'; - - @override - String get verifySeedUnavailable => - 'Your recovery phrase can\'t be read right now. Try again after unlocking the wallet.'; - - @override - String get verifySeedDone => 'Recovery phrase verified'; - - @override - String homeIncomingTitleMore(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: '$count Others', - one: '1 Other', - ); - return 'Incoming (+$_temp0)'; - } + String get torSettingsSheetSubtitle => + 'Tor can hide your IP address from servers you connect to. This does not reduce the information that is stored on public blockchains. Changing this reconnects your wallet.'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index be6a0c0..99f562d 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1,6 +1,5 @@ // ignore: unused_import import 'package:intl/intl.dart' as intl; - import 'app_localizations.dart'; // ignore_for_file: type=lint @@ -9,56 +8,6 @@ import 'app_localizations.dart'; class AppLocalizationsPt extends AppLocalizations { AppLocalizationsPt([String locale = 'pt']) : super(locale); - @override - String get scanQrUnexpectedCode => 'Esse QR não é um endereço ou código de pagamento válido.'; - - @override - String deepLinkCoinNotConfigured(String chain) { - return 'Por favor, configure sua conexão $chain primeiro.'; - } - - @override - String get continueText => 'Continuar'; - - @override - String get cancel => 'Cancelar'; - - @override - String get done => 'Concluir'; - - @override - String get close => 'Fechar'; - - @override - String get back => 'Voltar'; - - @override - String get unknownError => 'Erro desconhecido.'; - - @override - String get warning => 'Atenção'; - - @override - String get amount => 'Valor'; - - @override - String get networkFee => 'Taxa da Rede'; - - @override - String get address => 'Endereço'; - - @override - String get pending => 'Pendente'; - - @override - String get copy => 'Copiar'; - - @override - String get addressCopied => 'Endereço copiado para a área de transferência'; - - @override - String get fieldEmptyError => 'Este campo não pode ficar vazio.'; - @override String get welcomeDescription => 'A Skylight Wallet é uma das mais simples carteiras de Monero. Nós o ajudaremos a configurar uma carteira e se conectar à um servidor.'; @@ -78,109 +27,16 @@ class AppLocalizationsPt extends AppLocalizations { @override String get welcomePrivacyLink => 'Política de Privacidade'; - @override - String get settingsLicensesLabel => 'Licenças de Código Aberto'; - - @override - String get lwsSetupTitle => 'Configuração da conexão'; - @override String get lwsSetupDescription => 'Conecte-se a um servidor light-wallet Monero (LWS) ou ao seu próprio nó completo. Selecione apenas um servidor em que você confia. Mesmo se você usar o Tor, este servidor pode obter informações sobre você. Com um LWS, sua chave privada de visualização e seu endereço primário serão compartilhados com este servidor.'; @override - String get lwsSetupProxyPortLabel => 'Porta do Proxy HTTP (opcional)'; + String get lwsSetupProxyPortLabel => 'Porta do Proxy HTTP (Opcional)'; @override String get lwsSetupProxyPortHint => '4444 para I2P'; - @override - String get lwsSetupUseTorLabel => 'Usar Tor'; - - @override - String get lwsSetupTestConnectionButton => 'Testar Conexão'; - - @override - String get lwsSetupStartingTor => 'Iniciando Tor...'; - - @override - String get lwsSetupContinueButton => 'Continuar'; - - @override - String get connectionTypeLws => 'Servidor Light Wallet'; - - @override - String get connectionTypeNode => 'Nó Monero'; - - @override - String get connectionRemoteIpNotAllowed => - 'Conexões com endereços IP remotos não são permitidas. Use um nome de domínio ou um endereço IP local.'; - - @override - String get connectionLocalOverTorNotAllowed => - 'Um endereço local não pode ser acessado via Tor. Desative o Tor ou use o endereço onion do servidor.'; - - @override - String get connectionOnionNeedsTor => - 'Um endereço onion só pode ser acessado via Tor. Ative o Tor para usá-lo.'; - - @override - String get connectionPathNotSupported => - 'Este servidor é acessado pela raiz, então o endereço não pode incluir um caminho como /wallet. Remova o caminho.'; - - @override - String get lwsAwaitingApprovalAccount => - 'Seu servidor de carteira leve ainda não aprovou esta carteira. Peça ao operador do servidor para aprová-la para ver seu saldo e suas transações.'; - - @override - String get lwsAwaitingApprovalRestoreHeight => - 'Seu servidor de carteira leve ainda não aprovou a verificação a partir da sua altura de restauração, então transações mais antigas podem estar faltando. Peça ao operador do servidor para aprová-la.'; - - @override - String get connectionTorDisabledWarning => - 'O Tor está desativado, então esta conexão não funciona mais. Reconfigure-a.'; - - @override - String get connectionProtocolHttps => 'Removendo protocolo. Usando HTTPS para domínios.'; - - @override - String get connectionProtocolHttp => 'Removendo protocolo. Usando HTTP para endereços locais.'; - - @override - String get connectionProtocolHttpOnion => - 'Removendo protocolo. Endereços onion usam HTTP via Tor.'; - - @override - String get connectionTestStop => 'Parar'; - - @override - String get connectionTestingTitle => 'Testando conexão'; - - @override - String get connectionTestingDetail => 'Verificando se o servidor responde.'; - - @override - String get connectionTestAgain => 'Testar novamente'; - - @override - String get connectionResultWorksTitle => 'A conexão funciona'; - - @override - String get connectionResultFailedTitle => 'Não foi possível acessar este servidor'; - - @override - String get connectionResultFailedDetail => - 'Nada respondeu. Verifique o endereço e a porta, e se o servidor aceita sua conexão.'; - - @override - String get connectionReachedOverTor => 'Acessado via Tor'; - - @override - String get connectionReachedViaProxy => 'Acessado pelo seu proxy'; - - @override - String get connectionReachedDirect => 'Acessado diretamente'; - @override String get settingsConnectionSettingsLabel => 'Configurações de conexão'; @@ -199,150 +55,25 @@ class AppLocalizationsPt extends AppLocalizations { @override String get settingsConnectionInLocalNetwork => 'na rede local'; - @override - String homeBlocksRemaining(int count) { - final intl.NumberFormat countNumberFormat = intl.NumberFormat.decimalPattern(localeName); - final String countString = countNumberFormat.format(count); - - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: '$countString blocos restantes', - one: '1 bloco restante', - ); - return '$_temp0'; - } - - @override - String get fiatApiSetupTitle => 'Configuração de Exibição de Preços'; - @override String get fiatApiSetupDescription => 'A Skylight Wallet pode buscar automaticamente o preço mais recente do Monero. Seus saldos não são enviados ao servidor. Como você quer buscar esses dados de preço?'; - @override - String get fiatApiSettingsModeLabel => 'Modo'; - - @override - String get fiatApiSettingsModeTorOnly => 'Somente Tor'; - - @override - String get fiatApiSettingsModeClearnet => 'Somente Clearnet (não privado)'; - - @override - String get fiatApiSettingsModeDisabled => 'Desativado'; - - @override - String get fiatModeTorOnlyDesc => 'Preços buscados pelo Tor · recomendado'; - - @override - String get fiatModeClearnetDesc => 'Não privado, o servidor de preços vê seu endereço IP'; - - @override - String get fiatModeDisabledDesc => 'Sem preços; saldos exibidos apenas em cripto'; - - @override - String get settingsOpenAliasSettingsLabel => 'Configurações do OpenAlias'; - - @override - String get openAliasSettingsSheetSubtitle => - 'Como nomes como nome@exemplo.com são consultados quando você envia.'; - - @override - String get openAliasModeTorOnlyDesc => 'Nomes consultados pelo Tor · recomendado'; - - @override - String get openAliasModeClearnetDesc => - 'Não privado, os servidores DNS veem seu endereço IP e o nome'; - - @override - String get openAliasModeDisabledDesc => 'Sem consultas; envie apenas para endereços'; - - @override - String get fiatApiSettingsDisplayCurrencyLabel => 'Moeda de Exibição'; - - @override - String get createWalletTitle => 'Configuração da Carteira'; - @override String get createWalletDescription => 'Você quer criar uma nova carteira ou restaurar uma carteira existente?'; - @override - String get createWalletRestoreExistingButton => 'Restaurar Existente'; - @override String get createWalletRestoreExistingDesc => 'Insira uma frase de recuperação Monero existente'; - @override - String get createWalletCreateNewButton => 'Criar Nova'; - @override String get createWalletCreateNewDesc => 'A Skylight Wallet gera uma nova frase de recuperação Polyseed'; - @override - String get createWalletPasswordTitle => 'Criar Senha da Carteira'; - - @override - String get createWalletPasswordDescription => - 'Crie uma senha para proteger sua carteira. Esta senha será necessária para desbloquear sua carteira.'; - - @override - String get createWalletPasswordHint => 'Digite sua senha'; - - @override - String get createWalletConfirmPasswordHint => 'Confirme sua senha'; - - @override - String get passwordTooShortError => 'A senha deve ter pelo menos 8 caracteres.'; - - @override - String get passwordsDoNotMatchError => 'As senhas não coincidem.'; - - @override - String get generateSeedTitle => 'Anote-as, em ordem'; - - @override - String get generateSeedTitleCovered => 'Frase de Recuperação'; - @override String get generateSeedSubtitleCovered => 'Estas palavras, nesta ordem, são a sua carteira. Anote-as e guarde-as em um cofre físico. Se você perder estas palavras ou compartilhá-las com outra pessoa, perderá seu dinheiro permanentemente. Um planejamento cuidadoso agora evita um possível desastre depois.'; - @override - String get generateSeedSubtitleRevealed => 'Guarde-as em segurança. Não as compartilhe.'; - - @override - String get generateSeedScreenshotNote => - 'As capturas de tela estão bloqueadas nesta tela. Certifique-se de que ninguém está olhando por cima do seu ombro.'; - - @override - String get generateSeedReveal => 'Toque para revelar'; - - @override - String get generateSeedConfirm => - 'Anotei todas as palavras e as guardei em um lugar que só eu posso acessar.'; - - @override - String get generateSeedBirthdayLabel => 'Aniversário da carteira'; - - @override - String get generateSeedBirthdayReason => 'Onde uma futura restauração começa a escanear'; - - @override - String get onboardingTorNotePrivacy => 'Conexões pelo Tor podem ser mais lentas.'; - - @override - String get onboardingTorNoteChangeable => 'Você pode alterar isso depois nas Configurações.'; - - @override - String get onboardingPriceNoteRatesOnly => - 'Apenas os preços são solicitados. Seus endereços e saldos não são enviados.'; - - @override - String get onboardingPriceNoteTor => 'Os preços são buscados pelo Tor por padrão.'; - @override String get onboardingConnectionNoteServer => 'Use um servidor light-wallet (LWS) ou seu próprio nó Monero.'; @@ -351,39 +82,9 @@ class AppLocalizationsPt extends AppLocalizations { String get onboardingConnectionNoteChangeable => 'Você pode alterar isso depois nas Configurações.'; - @override - String get onboardingWalletNoteGenerated => - 'A Skylight Wallet gera sua frase de recuperação neste dispositivo.'; - - @override - String get onboardingWalletNoteRestore => - 'Ao restaurar, uma data para escanear a partir dela torna a sincronização mais rápida.'; - - @override - String get onboardingSeedNotePassword => - 'A Skylight Wallet pede sua senha antes de mostrar estas palavras novamente.'; - - @override - String get onboardingPasswordNoteLaunch => - 'Você precisa desta senha para abrir sua carteira e para ver sua frase de recuperação.'; - - @override - String get onboardingPasswordNoteNotCloud => - 'Esta senha não pode ser recuperada. Se você a perder, restaure sua carteira a partir da sua frase de recuperação.'; - - @override - String get onboardingPasswordStrong => 'Forte'; - - @override - String get onboardingPasswordMatch => 'As senhas coincidem'; - @override String get lwsDetailsTitle => 'Detalhes da Carteira'; - @override - String get lwsDetailsDescription => - 'Se o seu servidor Light Wallet Monero (LWS) exigir registro, você pode usar estes dados para adicionar esta carteira a esse servidor. Nem todos os servidores exigem registro.'; - @override String get lwsDetailsPrimaryAddressLabel => 'Endereço Primário'; @@ -393,57 +94,17 @@ class AppLocalizationsPt extends AppLocalizations { @override String get lwsDetailsRestoreHeightLabel => 'Bloco de Restauração'; - @override - String get restoreWalletTitle => 'Restaurar Carteira'; - @override String get restoreWalletDescription => 'Insira sua frase de recuperação Monero abaixo. Verificaremos os formatos comuns.'; - @override - String get restoreWalletScanFrom => 'Escanear a partir de'; - - @override - String get restoreWalletScanFromReason => 'Ignore o histórico irrelevante para economizar tempo'; - - @override - String get restoreWalletNotSet => 'Não definido'; - - @override - String get restoreScanTitle => 'Quando esta carteira recebeu fundos pela primeira vez?'; - @override String get restoreScanDescription => 'A Skylight Wallet pode ignorar o histórico irrelevante para economizar seu tempo. Escolha o primeiro mês em que você usou a carteira ou selecione Não tenho certeza para verificar tudo. Não há problema em escolher um mês muito antigo, mas é ruim escolher um mês muito recente.'; - @override - String get restoreScanPickMonth => 'Escolher um mês'; - - @override - String get restoreScanNotSure => 'Não tenho certeza'; - - @override - String get restoreScanNotSureDesc => - 'Escanear tudo. Mais lento nesta configuração inicial, mas não depois. Sempre completo.'; - - @override - String get restoreScanFromStart => 'Genesis'; - - @override - String get restoreScanDone => 'Concluir'; - - @override - String get restoreWalletRestoreButton => 'Restaurar'; - @override String get restoreWalletInvalidMnemonic => 'Frase de recuperação inválida.'; - @override - String get restoreWalletSeedLength => 'Número de palavras'; - - @override - String get restoreWalletPaste => 'Colar'; - @override String get restoreWalletSeedTypePolyseed => 'Polyseed'; @@ -453,46 +114,14 @@ class AppLocalizationsPt extends AppLocalizations { @override String get restoreWalletSeedTypeLegacy => 'Legado'; - @override - String restoreWalletBadWord(int position) { - return 'A palavra $position não é uma palavra BIP39.'; - } - - @override - String restoreWalletDidYouMean(String word) { - return 'Você quis dizer $word?'; - } - - @override - String get restoreWalletChecksumError => - 'Esta não é uma frase de recuperação válida — verifique as palavras e sua ordem.'; - @override String get navigationBarWallet => 'Carteira'; - @override - String get historyTitle => 'Histórico'; - - @override - String get historyExportCsv => 'Exportar CSV'; - - @override - String get historyExported => 'Histórico exportado'; - - @override - String get historyExportFailed => 'Não foi possível exportar o histórico'; - - @override - String get navigationBarHistory => 'Histórico'; - - @override - String get historyFilterType => 'Tipo'; - @override String get historyFilterAddress => 'Endereço'; @override - String get historyFilterAddressAny => 'Qualquer endereço'; + String get historyFilterAddressAny => 'Qualquer Endereço'; @override String get historyFilterAddressHint => @@ -502,699 +131,94 @@ class AppLocalizationsPt extends AppLocalizations { String get historyFilterAddressInvalid => 'Não é um endereço Monero'; @override - String get historyFilterReset => 'Redefinir'; + String get unlockTitle => 'Desbloquear Carteira'; @override - String get navigationBarSettings => 'Configurações'; + String get homeDisconnected => 'Desconectado'; @override - String get unlockButton => 'Desbloquear'; + String get receiveQrHint => 'Escaneie este código para enviar Monero para esta carteira.'; @override - String get unlockReason => 'Desbloquear carteira'; + String get sendInvalidAddressError => 'Endereço inválido.'; @override - String get unlockUnableToAuthError => 'Não foi possível autenticar.'; + String get syncFrequencySheetSubtitle => + 'Com que frequência o Monero sincroniza enquanto o app está fechado.'; @override - String get unlockWithFaceId => 'Desbloquear com Face ID'; + String get syncFrequencyContinuousDesc => + 'Sincroniza o Monero continuamente em segundo plano para que esteja sempre atualizado ao abrir o app. Usa mais bateria e exige uma notificação persistente.'; @override - String get unlockWithTouchId => 'Desbloquear com Touch ID'; + String get syncFrequencyPeriodicDesc => + 'Sincroniza o Monero periodicamente em segundo plano para que geralmente esteja atualizado ao abrir o app. Com um nó Monero, só sincroniza enquanto carrega e no WiFi.'; @override - String get unlockTitle => 'Desbloquear Carteira'; + String get syncFrequencyPeriodicDescIos => + 'Sincroniza o Monero periodicamente em segundo plano para que geralmente esteja atualizado ao abrir o app. Com um nó Monero, só sincroniza enquanto carrega.'; @override - String get unlockPasswordHint => 'Digite sua senha'; + String get settingsThemeLightDesc => 'Claro e limpo'; @override - String get unlockIncorrectPasswordError => 'Senha incorreta. Tente novamente.'; + String get settingsLwsViewKeysLabel => 'Chaves de Visualização do LWS'; @override - String get homeSyncing => 'Sincronizando'; + String get settingsSecretKeysLabel => 'Chaves Privadas de Restauração'; @override - String get homeSynced => 'Sincronizado'; + String get settingsViewLwsKeysDialogText => + 'Somente compartilhe estas informações com o seu servidor light-wallet. Essas chaves permitem que o portador veja permanentemente todas as transações relacionadas às suas carteiras. Compartilhá-las com uma pessoa não confiável prejudicará significativamente sua privacidade.'; @override - String get homeTorConnected => 'Tor · conectado'; + String get settingsViewLwsKeysDialogRevealButton => 'Revelar'; @override - String get homeTorConnecting => 'Tor · conectando'; + String get settingsViewSecretKeysDialogText => + 'Não compartilhe essas chaves com ninguém, incluindo pessoas que aleguem ser suporte. Se você receber um pedido para fornecê-las, está sendo vítima de um golpe. Se você fornecer essas informações a outra pessoa, perderá seu dinheiro e ele não poderá ser recuperado.'; @override - String get homeTorDisconnected => 'Tor · desconectado'; + String get settingsViewSecretKeysDialogRevealButton => 'Revelar'; @override - String get homeTorOff => 'Tor · desligado'; + String get secretKeysTitle => 'Chaves Privadas de Restauração'; @override - String get homeTorExternalConnected => 'Tor externo · conectado'; + String get secretKeysDescription => + 'Estas frases de recuperação e chaves restauram o controle total da sua carteira. Qualquer pessoa que as veja pode gastar seus fundos.'; @override - String get homeTorExternalConnecting => 'Tor externo · conectando'; + String get secretKeysMnemonic => 'Frase de Recuperação'; @override - String get homeTorExternalDisconnected => 'Tor externo · desconectado'; + String get secretKeysPublicSpendKey => 'Chave Pública de Gasto'; @override - String get homeReceive => 'Receber'; + String get secretKeysSecretSpendKey => 'Chave Privada de Gasto'; @override - String get homeSend => 'Enviar'; + String get secretKeysPublicViewKey => 'Chave Pública de Visualização'; @override - String get homeNoTransactions => 'Sem transações'; + String get secretKeysWarning => + 'Nunca as compartilhe ou insira em qualquer site. Guarde-as offline.'; @override - String get homeFiatApiError => 'Erro ao conectar ao servidor de preços'; + String get addressBookAddDescription => 'Um nome e um endereço Monero para pagá-lo.'; @override - String get homeDisconnected => 'Desconectado'; - - @override - String get coinHomeActivityTitle => 'Atividade'; - - @override - String get coinHomeReceived => 'Recebido'; - - @override - String get coinHomeSent => 'Enviado'; - - @override - String get receiveTitle => 'Receber'; - - @override - String get receivePrimaryAddressWarn => - 'Aviso: A menos que saiba o que está fazendo, por favor use subendereços para melhor privacidade.'; - - @override - String get receiveServerNoSubaddressesWarn => - 'Aviso: Este servidor não suporta subendereços. Para melhor privacidade, considere usar um servidor que os suporte.'; - - @override - String get receiveSubaddressUnavailableWarn => - 'Aviso: Você precisa configurar o Monero para ativar subendereços, que protegem melhor sua privacidade.'; - - @override - String get receiveMaxSubaddressesReachedWarn => - 'Você atingiu o número máximo de subendereços suportados por este servidor. Este é um endereço já usado.'; - - @override - String get receiveSubaddressTab => 'Subendereço'; - - @override - String get receivePrimaryTab => 'Endereço primário'; - - @override - String get receiveCopyAddress => 'Copiar endereço'; - - @override - String get receiveQrHint => 'Escaneie este código para enviar Monero para esta carteira.'; - - @override - String get receiveEnlargeQr => 'Toque para ampliar e aumentar o brilho'; - - @override - String get receiveShrinkQr => 'Toque para reduzir'; - - @override - String get receiveShareError => 'Não foi possível abrir a janela de compartilhamento'; - - @override - String receiveAddressHeading(String coin) { - return 'Seu endereço $coin'; - } - - @override - String get sendTitle => 'Enviar'; - - @override - String get sendSendButton => 'Enviar'; - - @override - String get sendTransactionSuccessfullySent => 'Transação enviada com sucesso!'; - - @override - String get sendOpenAliasResolveError => 'OpenAlias inválido.'; - - @override - String get sendOpenAliasDisabledError => 'O OpenAlias está desligado. Ligue-o nas Configurações.'; - - @override - String get sendInvalidAddressError => 'Endereço inválido.'; - - @override - String get sendInsufficientBalanceError => 'Saldo insuficiente.'; - - @override - String get sendInsufficientBalanceToCoverFeeError => - 'Saldo insuficiente para cobrir a taxa da rede.'; - - @override - String get settingsTitle => 'Configurações'; - - @override - String get settingsSectionGeneral => 'Geral'; - - @override - String get settingsSectionSecurityPrivacy => 'Segurança e privacidade'; - - @override - String get settingsSectionData => 'Dados'; - - @override - String get settingsSectionBehaviour => 'Comportamento'; - - @override - String get settingsSectionWallet => 'Carteira'; - - @override - String get settingsSectionAbout => 'Sobre'; - - @override - String get settingsNotifyNewTxsLabel => 'Notificações de Transações'; - - @override - String get settingsNotifyNewTxsDescription => - 'Mostrar uma notificação quando você receber uma transação. Aumente a frequência de sincronização para vê-las mais cedo.'; - - @override - String get settingsSyncFrequencyLabel => 'Frequência de sincronização'; - - @override - String get syncFrequencySheetSubtitle => - 'Com que frequência o Monero sincroniza enquanto o app está fechado.'; - - @override - String get syncFrequencyModeLabel => 'Frequência'; - - @override - String get syncFrequencyContinuous => 'Contínua'; - - @override - String get syncFrequencyContinuousDesc => - 'Sincroniza o Monero continuamente em segundo plano para que esteja sempre atualizado ao abrir o app. Usa mais bateria e exige uma notificação persistente.'; - - @override - String get syncFrequencyPeriodic => 'Periódica'; - - @override - String get syncFrequencyPeriodicDesc => - 'Sincroniza o Monero periodicamente em segundo plano para que geralmente esteja atualizado ao abrir o app. Com um nó Monero, só sincroniza enquanto carrega e no WiFi.'; - - @override - String get syncFrequencyPeriodicDescIos => - 'Sincroniza o Monero periodicamente em segundo plano para que geralmente esteja atualizado ao abrir o app. Com um nó Monero, só sincroniza enquanto carrega.'; - - @override - String get syncFrequencyOff => 'Desligada'; - - @override - String get syncFrequencyOffDesc => 'Só sincroniza enquanto o app está aberto.'; - - @override - String get settingsAppLockUnlockReason => 'Desbloquear carteira'; - - @override - String get settingsLoggingLabel => 'Logs de Diagnóstico'; - - @override - String get settingsLoggingDescription => - 'Registrar operações da carteira em um arquivo de texto na pasta de dados do app para fins de depuração.'; - - @override - String get settingsLoggingDescriptionIos => - 'Registrar operações da carteira e permitir exportar os logs para um arquivo de texto.'; - - @override - String get settingsExportLogsLabel => 'Exportar Logs'; - - @override - String get settingsExportLogsButton => 'Exportar'; - - @override - String get settingsExportLogsError => 'Nenhum log encontrado para exportar.'; - - @override - String get settingsExportLogsFailed => 'Não foi possível exportar o arquivo de log'; - - @override - String get settingsThemeLabel => 'Tema'; - - @override - String get settingsThemeSystem => 'Sistema'; - - @override - String get settingsThemeLight => 'Claro'; - - @override - String get settingsThemeDark => 'Escuro'; - - @override - String get settingsThemeLightDesc => 'Claro e limpo'; - - @override - String get settingsThemeDarkDesc => 'Fundo escuro, melhor à noite'; - - @override - String get settingsThemeSystemDesc => 'Segue o seu telefone'; - - @override - String get settingsThemeSheetSubtitle => 'Escolha um tema que combine com seu estilo.'; - - @override - String get settingsLanguageLabel => 'Idioma'; - - @override - String get settingsLanguageSheetSubtitle => 'Escolha seu idioma e formato regional.'; - - @override - String get settingsFiatApiSettingsLabel => 'Configurações de Exibição de Preços'; - - @override - String get settingsLwsViewKeysLabel => 'Chaves de Visualização do LWS'; - - @override - String get settingsLwsViewKeysButton => 'Ver'; - - @override - String get revealSeedAuthReason => 'Confirme sua identidade para ver a frase de recuperação'; - - @override - String get settingsSecretKeysLabel => 'Chaves Privadas de Restauração'; - - @override - String get settingsViewLwsKeysDialogText => - 'Somente compartilhe estas informações com o seu servidor light-wallet. Essas chaves permitem que o portador veja permanentemente todas as transações relacionadas às suas carteiras. Compartilhá-las com uma pessoa não confiável prejudicará significativamente sua privacidade.'; - - @override - String get settingsViewLwsKeysDialogRevealButton => 'Revelar'; - - @override - String get settingsViewSecretKeysDialogText => - 'Não compartilhe essas chaves com ninguém, incluindo pessoas que aleguem ser suporte. Se você receber um pedido para fornecê-las, está sendo vítima de um golpe. Se você fornecer essas informações a outra pessoa, perderá seu dinheiro e ele não poderá ser recuperado.'; - - @override - String get settingsViewSecretKeysDialogRevealButton => 'Revelar'; - - @override - String get settingsDeleteWalletButton => 'Excluir Carteira'; - - @override - String get settingsDeleteWalletDialogText => - 'Tem certeza que deseja excluir sua carteira? Você perderá acesso à seus fundos, a menos que tenha anotado sua frase de recuperação. Isso não remove sua lista de contatos nem as configurações do app.'; - - @override - String get settingsDeleteWalletDialogDeleteButton => 'Excluir'; - - @override - String get settingsDeleteAddressBookButton => 'Excluir Lista de Contatos'; - - @override - String get settingsDeleteAddressBookDialogText => - 'Tem certeza que deseja excluir sua lista de contatos? Se o backup automático estiver ligado, ela também será excluída desses backups automáticos.'; - - @override - String get settingsDeleteAddressBookDialogDeleteButton => 'Excluir'; - - @override - String get settingsAddressBookDeleted => 'Lista de contatos excluída'; - - @override - String get txDetailsTitle => 'Detalhes da transação'; - - @override - String get txDetailsHashLabel => 'Hash'; - - @override - String get txDetailsTimeAndDateLabel => 'Data e Hora'; - - @override - String get txDetailsConfirmationHeightLabel => 'Bloco de Confirmação'; - - @override - String get txDetailsConfirmationsLabel => 'Confirmações'; - - @override - String get txDetailsViewKeyLabel => 'Chave de Visualização'; - - @override - String get txDetailsRecipientsLabel => 'Destinatários'; - - @override - String get txDetailsReceivedAtLabel => 'Recebido em'; - - @override - String get txDetailsChangeRecipientLabel => 'Destinatário de troco'; - - @override - String get unconfirmed => 'Não confirmado'; - - @override - String get txDetailsCopyHint => 'toque em qualquer valor para copiar'; - - @override - String get txDetailsFailed => 'Esta transação falhou. Os fundos não foram enviados.'; - - @override - String get txDetailsUnknownStatus => - 'Esta transação não foi confirmada como enviada. Verifique antes de enviar novamente.'; - - @override - String get copiedToClipboard => 'Copiado para a área de transferência'; - - @override - String get lwsKeysTitle => 'Chaves do LWS'; - - @override - String get lwsKeysPrimaryAddress => 'Endereço Primário'; - - @override - String get lwsKeysRestoreHeight => 'Bloco de Restauração'; - - @override - String get lwsKeysSecretViewKey => 'Chave Privada de Visualização'; - - @override - String get lwsKeysWarning => - 'As capturas de tela estão bloqueadas nesta tela. Certifique-se de que ninguém está olhando por cima do seu ombro.'; - - @override - String get secretKeysTitle => 'Chaves Privadas de Restauração'; - - @override - String get secretKeysDescription => - 'Estas frases de recuperação e chaves restauram o controle total da sua carteira. Qualquer pessoa que as veja pode gastar seus fundos.'; - - @override - String get secretKeysMnemonic => 'Frase de recuperação'; - - @override - String get secretKeysPublicSpendKey => 'Chave Pública de Gasto'; - - @override - String get secretKeysSecretSpendKey => 'Chave Privada de Gasto'; - - @override - String get secretKeysPublicViewKey => 'Chave Pública de Visualização'; - - @override - String get secretKeysWarning => - 'Nunca as compartilhe ou insira em qualquer site. Guarde-as offline.'; - - @override - String get scanQrTitle => 'Escanear QR Code'; - - @override - String get confirmSendTitle => 'Confirmar Envio'; - - @override - String get confirmSendDescription => - 'As transações são irreversíveis, então verifique se estes detalhes correspondem exatamente.'; - - @override - String confirmSendHighFeeWarning(String percent) { - return 'A taxa de rede é $percent do valor que você está enviando.'; - } - - @override - String get addressBookTitle => 'Lista de Contatos'; - - @override - String get addressBookAddContact => 'Adicionar Contato'; - - @override - String get addressBookEditContact => 'Editar Contato'; - - @override - String get addressBookDeleteContact => 'Excluir Contato'; - - @override - String addressBookDeleteContactConfirmation(String contactName) { - return 'Tem certeza que deseja excluir \"$contactName\"?'; - } - - @override - String get addressBookDelete => 'Excluir'; - - @override - String get addressBookSearchHint => 'Pesquisar contatos...'; - - @override - String get addressBookNoContacts => 'Nenhum contato ainda'; - - @override - String get addressBookNoContactsDescription => 'Adicione seu primeiro contato tocando no botão +'; - - @override - String get addressBookNoSearchResults => 'Nenhum contato encontrado'; - - @override - String get addressBookEdit => 'Editar'; - - @override - String get addressBookContactName => 'Nome do Contato'; - - @override - String get addressBookNameHint => 'Nome'; - - @override - String get addressBookAddDescription => 'Um nome e um endereço Monero para pagá-lo.'; - - @override - String get addressBookEditDescription => 'Atualize o nome ou o endereço deste contato.'; - - @override - String get addressBookUpdate => 'Atualizar'; - - @override - String get addressBookSave => 'Salvar'; - - @override - String get sendPriorityLow => 'Baixa'; - - @override - String get sendPriorityNormal => 'Normal'; - - @override - String get sendPriorityHigh => 'Alta'; - - @override - String get sendContactsButton => 'Contatos'; - - @override - String get sendToLabel => 'Para'; - - @override - String get sendPasteButton => 'Colar'; - - @override - String get sendScanButton => 'Escanear'; - - @override - String get sendMaxButton => 'MÁX'; - - @override - String get sendNetworkFee => 'Taxa de rede'; - - @override - String get sendPriorityHeading => 'Prioridade'; - - @override - String get sendPickContactTitle => 'Enviar para um contato'; - - @override - String get sendAvailableSuffix => 'disponível'; + String get addressBookEditDescription => 'Atualize o nome ou o endereço deste contato.'; @override String get sendIrreversibleNote => 'As transações são irreversíveis, então verifique se o endereço e o valor estão corretos.'; - @override - String get sendSwitchUnit => 'Alternar unidade do valor'; - - @override - String get sendFailedToGetFeesError => 'Não foi possível carregar taxas.'; - - @override - String get sendNoConnectionError => - 'Não foi possível conectar ao servidor. Verifique sua conexão e tente novamente.'; - - @override - String get torSettingsTitle => 'Configurações do Tor'; - - @override - String get torSettingsModeBuiltIn => 'Tor Integrado'; - - @override - String get torSettingsModeExternal => 'Tor Externo'; - - @override - String get torSettingsModeDisabled => 'Sem Tor'; - - @override - String get torChoiceBuiltInDesc => 'Incluído na Skylight Wallet · recomendado'; - - @override - String get torChoiceExternalDesc => 'Orbot, ou um daemon que você mesmo executa'; - - @override - String get torChoiceNoTorDesc => - 'Os servidores aos quais você se conecta podem ver seu endereço IP'; - - @override - String get torChoiceConnected => 'Conectado ao Tor'; - - @override - String get torChoiceTestFailed => 'Falha no teste'; - - @override - String get torChoiceTitle => 'Configuração da Conexão Tor'; - @override String get torChoiceSubtitle => 'O Tor pode ocultar seu endereço IP dos servidores aos quais você se conecta. Isso não reduz as informações que ficam armazenadas em blockchains públicas. De modo geral, como você quer que a Skylight Wallet lide com as conexões Tor?'; @override - String get torSettingsSocksPortLabel => 'Porta SOCKS'; - - @override - String get torSettingsUseOrbotLabel => 'Usar Orbot/InviZible'; - - @override - String get torSettingsUseOrbotLabelIos => 'Usar Orbot'; - - @override - String get torSettingsSaveButton => 'Salvar'; - - @override - String get torSettingsTestConnectionButton => 'Testar Conexão'; - - @override - String get torDisabledWalletsWarningTitle => 'Desativar o Tor?'; - - @override - String get torDisabledWalletsWarningBody => - 'Algumas carteiras estão configuradas para conectar via Tor. Desativar o Tor irá desconectá-las, e elas permanecerão desconectadas até que você reconfigure a conexão.'; - - @override - String get torDisabledWalletsWarningConfirm => 'Desativar o Tor'; - - @override - String get settingsTorSettingsLabel => 'Configurações do Tor'; - - @override - String get lwsSetupTorDisabledError => 'O Tor está desativado. Por favor, volte e ative-o.'; - - @override - String get coinHomeSwap => 'Trocar'; - - @override - String get coinHomeSwapComingSoon => 'Em breve!'; - - @override - String get verifySeedTitle => 'Verificar Frase de Recuperação'; - - @override - String get homeIncomingTitle => 'A receber'; - - @override - String homeIncomingUnlocksIn(int minutes, int done, int needed) { - String _temp0 = intl.Intl.pluralLogic( - minutes, - locale: localeName, - other: '$minutes minutos', - one: '1 minuto', - ); - return 'Desbloqueia em cerca de $_temp0 · $done de $needed'; - } - - @override - String homeIncomingConfirmations(int done, int needed) { - return '$done de $needed confirmações'; - } - - @override - String get homeChartTitle => 'Saldo · 90 dias'; - - @override - String get homeChartToday => 'Hoje'; - - @override - String get homeChartEachCoin => 'Saldo de cada moeda'; - - @override - String get homeChartNew => 'Novo'; - - @override - String get homeChartEmpty => - 'Seu saldo ao longo do tempo aparece aqui após sua primeira transação.'; - - @override - String get homeChecklistTitle => 'Proteja sua carteira'; - - @override - String homeChecklistProgress(int done, int total) { - return '$done de $total'; - } - - @override - String get homeChecklistVerifySeed => 'Verifique sua frase de recuperação'; - - @override - String get homeChecklistAutoBackup => 'Ative os backups automáticos'; - - @override - String get homeChecklistTor => 'Conecte-se pelo Tor'; - - @override - String get homeChecklistPrivateLookups => 'Use o Tor nas consultas de preço e OpenAlias'; - - @override - String get homeChecklistSecurityKeys => 'Configure uma chave de segurança'; - - @override - String verifySeedSubtitle(int first, int second, int third) { - return 'Insira as palavras $first, $second e $third da sua frase de recuperação abaixo.'; - } - - @override - String get verifySeedButton => 'Verificar'; - - @override - String get verifySeedWrongTitle => 'Essas palavras não conferem'; - - @override - String verifySeedWrongBody(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: 'Restam $count tentativas', - one: 'Resta 1 tentativa', - ); - return 'Confira a frase de recuperação que você anotou e insira as três novas palavras pedidas. $_temp0 antes de o app bloquear.'; - } - - @override - String get verifySeedTryAgain => 'Tentar novamente'; - - @override - String get verifySeedUnavailable => - 'Não é possível ler sua frase de recuperação agora. Tente novamente depois de desbloquear a carteira.'; - - @override - String get verifySeedDone => 'Frase de recuperação verificada'; - - @override - String homeIncomingTitleMore(int count) { - String _temp0 = intl.Intl.pluralLogic( - count, - locale: localeName, - other: '$count Outras', - one: '1 Outra', - ); - return 'A receber (+$_temp0)'; - } + String get torSettingsSheetSubtitle => + 'O Tor pode ocultar seu endereço IP dos servidores aos quais você se conecta. Isso não reduz as informações que ficam armazenadas em blockchains públicas. Alterar isto reconecta sua carteira.'; } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 2f4f563..895c57b 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -1,411 +1,67 @@ { "@@locale": "pt", - "scanQrUnexpectedCode": "Esse QR não é um endereço ou código de pagamento válido.", - "deepLinkCoinNotConfigured": "Por favor, configure sua conexão {chain} primeiro.", - "@deepLinkCoinNotConfigured": { - "placeholders": { - "chain": { - "type": "String" - } - } - }, - "continueText": "Continuar", - "cancel": "Cancelar", - "done": "Concluir", - "close": "Fechar", - "back": "Voltar", - "unknownError": "Erro desconhecido.", - "warning": "Atenção", - "amount": "Valor", - "networkFee": "Taxa da Rede", - "address": "Endereço", - "pending": "Pendente", - "copy": "Copiar", - "addressCopied": "Endereço copiado para a área de transferência", - "fieldEmptyError": "Este campo não pode ficar vazio.", "welcomeDescription": "A Skylight Wallet é uma das mais simples carteiras de Monero. Nós o ajudaremos a configurar uma carteira e se conectar à um servidor.", "welcomeGetStarted": "Começar", "welcomeAgreePrefix": "Ao continuar, você concorda com os ", "welcomeTermsLink": "Termos de Serviço", "welcomeAgreeMiddle": " e a ", "welcomePrivacyLink": "Política de Privacidade", - "settingsLicensesLabel": "Licenças de Código Aberto", - "lwsSetupTitle": "Configuração da conexão", "lwsSetupDescription": "Conecte-se a um servidor light-wallet Monero (LWS) ou ao seu próprio nó completo. Selecione apenas um servidor em que você confia. Mesmo se você usar o Tor, este servidor pode obter informações sobre você. Com um LWS, sua chave privada de visualização e seu endereço primário serão compartilhados com este servidor.", - "lwsSetupProxyPortLabel": "Porta do Proxy HTTP (opcional)", + "lwsSetupProxyPortLabel": "Porta do Proxy HTTP (Opcional)", "lwsSetupProxyPortHint": "4444 para I2P", - "lwsSetupUseTorLabel": "Usar Tor", - "lwsSetupTestConnectionButton": "Testar Conexão", - "lwsSetupStartingTor": "Iniciando Tor...", - "lwsSetupContinueButton": "Continuar", - "connectionTypeLws": "Servidor Light Wallet", - "connectionTypeNode": "Nó Monero", - "connectionRemoteIpNotAllowed": "Conexões com endereços IP remotos não são permitidas. Use um nome de domínio ou um endereço IP local.", - "connectionLocalOverTorNotAllowed": "Um endereço local não pode ser acessado via Tor. Desative o Tor ou use o endereço onion do servidor.", - "connectionOnionNeedsTor": "Um endereço onion só pode ser acessado via Tor. Ative o Tor para usá-lo.", - "connectionPathNotSupported": "Este servidor é acessado pela raiz, então o endereço não pode incluir um caminho como /wallet. Remova o caminho.", - "lwsAwaitingApprovalAccount": "Seu servidor de carteira leve ainda não aprovou esta carteira. Peça ao operador do servidor para aprová-la para ver seu saldo e suas transações.", - "lwsAwaitingApprovalRestoreHeight": "Seu servidor de carteira leve ainda não aprovou a verificação a partir da sua altura de restauração, então transações mais antigas podem estar faltando. Peça ao operador do servidor para aprová-la.", - "connectionTorDisabledWarning": "O Tor está desativado, então esta conexão não funciona mais. Reconfigure-a.", - "connectionProtocolHttps": "Removendo protocolo. Usando HTTPS para domínios.", - "connectionProtocolHttp": "Removendo protocolo. Usando HTTP para endereços locais.", - "connectionProtocolHttpOnion": "Removendo protocolo. Endereços onion usam HTTP via Tor.", - "connectionTestStop": "Parar", - "connectionTestingTitle": "Testando conexão", - "connectionTestingDetail": "Verificando se o servidor responde.", - "connectionTestAgain": "Testar novamente", - "connectionResultWorksTitle": "A conexão funciona", - "connectionResultFailedTitle": "Não foi possível acessar este servidor", - "connectionResultFailedDetail": "Nada respondeu. Verifique o endereço e a porta, e se o servidor aceita sua conexão.", - "connectionReachedOverTor": "Acessado via Tor", - "connectionReachedViaProxy": "Acessado pelo seu proxy", - "connectionReachedDirect": "Acessado diretamente", "settingsConnectionSettingsLabel": "Configurações de conexão", "settingsConnectionServerLws": "LWS", "settingsConnectionServerNode": "Nó", "settingsConnectionOverTor": "via Tor", "settingsConnectionOverClearnet": "via Clearnet", "settingsConnectionInLocalNetwork": "na rede local", - "homeBlocksRemaining": "{count, plural, =1{1 bloco restante} other{{count} blocos restantes}}", - "@homeBlocksRemaining": { - "placeholders": { - "count": { - "type": "int", - "format": "decimalPattern" - } - } - }, - "fiatApiSetupTitle": "Configuração de Exibição de Preços", "fiatApiSetupDescription": "A Skylight Wallet pode buscar automaticamente o preço mais recente do Monero. Seus saldos não são enviados ao servidor. Como você quer buscar esses dados de preço?", - "fiatApiSettingsModeLabel": "Modo", - "fiatApiSettingsModeTorOnly": "Somente Tor", - "fiatApiSettingsModeClearnet": "Somente Clearnet (não privado)", - "fiatApiSettingsModeDisabled": "Desativado", - "fiatModeTorOnlyDesc": "Preços buscados pelo Tor · recomendado", - "fiatModeClearnetDesc": "Não privado, o servidor de preços vê seu endereço IP", - "fiatModeDisabledDesc": "Sem preços; saldos exibidos apenas em cripto", - "settingsOpenAliasSettingsLabel": "Configurações do OpenAlias", - "openAliasSettingsSheetSubtitle": "Como nomes como nome@exemplo.com são consultados quando você envia.", - "openAliasModeTorOnlyDesc": "Nomes consultados pelo Tor · recomendado", - "openAliasModeClearnetDesc": "Não privado, os servidores DNS veem seu endereço IP e o nome", - "openAliasModeDisabledDesc": "Sem consultas; envie apenas para endereços", - "fiatApiSettingsDisplayCurrencyLabel": "Moeda de Exibição", - "createWalletTitle": "Configuração da Carteira", "createWalletDescription": "Você quer criar uma nova carteira ou restaurar uma carteira existente?", - "createWalletRestoreExistingButton": "Restaurar Existente", "createWalletRestoreExistingDesc": "Insira uma frase de recuperação Monero existente", - "createWalletCreateNewButton": "Criar Nova", "createWalletCreateNewDesc": "A Skylight Wallet gera uma nova frase de recuperação Polyseed", - "createWalletPasswordTitle": "Criar Senha da Carteira", - "createWalletPasswordDescription": "Crie uma senha para proteger sua carteira. Esta senha será necessária para desbloquear sua carteira.", - "createWalletPasswordHint": "Digite sua senha", - "createWalletConfirmPasswordHint": "Confirme sua senha", - "passwordTooShortError": "A senha deve ter pelo menos 8 caracteres.", - "passwordsDoNotMatchError": "As senhas não coincidem.", - "generateSeedTitle": "Anote-as, em ordem", - "generateSeedTitleCovered": "Frase de Recuperação", "generateSeedSubtitleCovered": "Estas palavras, nesta ordem, são a sua carteira. Anote-as e guarde-as em um cofre físico. Se você perder estas palavras ou compartilhá-las com outra pessoa, perderá seu dinheiro permanentemente. Um planejamento cuidadoso agora evita um possível desastre depois.", - "generateSeedSubtitleRevealed": "Guarde-as em segurança. Não as compartilhe.", - "generateSeedScreenshotNote": "As capturas de tela estão bloqueadas nesta tela. Certifique-se de que ninguém está olhando por cima do seu ombro.", - "generateSeedReveal": "Toque para revelar", - "generateSeedConfirm": "Anotei todas as palavras e as guardei em um lugar que só eu posso acessar.", - "generateSeedBirthdayLabel": "Aniversário da carteira", - "generateSeedBirthdayReason": "Onde uma futura restauração começa a escanear", - "onboardingTorNotePrivacy": "Conexões pelo Tor podem ser mais lentas.", - "onboardingTorNoteChangeable": "Você pode alterar isso depois nas Configurações.", - "onboardingPriceNoteRatesOnly": "Apenas os preços são solicitados. Seus endereços e saldos não são enviados.", - "onboardingPriceNoteTor": "Os preços são buscados pelo Tor por padrão.", "onboardingConnectionNoteServer": "Use um servidor light-wallet (LWS) ou seu próprio nó Monero.", "onboardingConnectionNoteChangeable": "Você pode alterar isso depois nas Configurações.", - "onboardingWalletNoteGenerated": "A Skylight Wallet gera sua frase de recuperação neste dispositivo.", - "onboardingWalletNoteRestore": "Ao restaurar, uma data para escanear a partir dela torna a sincronização mais rápida.", - "onboardingSeedNotePassword": "A Skylight Wallet pede sua senha antes de mostrar estas palavras novamente.", - "onboardingPasswordNoteLaunch": "Você precisa desta senha para abrir sua carteira e para ver sua frase de recuperação.", - "onboardingPasswordNoteNotCloud": "Esta senha não pode ser recuperada. Se você a perder, restaure sua carteira a partir da sua frase de recuperação.", - "onboardingPasswordStrong": "Forte", - "onboardingPasswordMatch": "As senhas coincidem", "lwsDetailsTitle": "Detalhes da Carteira", - "lwsDetailsDescription": "Se o seu servidor Light Wallet Monero (LWS) exigir registro, você pode usar estes dados para adicionar esta carteira a esse servidor. Nem todos os servidores exigem registro.", "lwsDetailsPrimaryAddressLabel": "Endereço Primário", "lwsDetailsSecretViewKeyLabel": "Chave Privada de Visualização", "lwsDetailsRestoreHeightLabel": "Bloco de Restauração", - "restoreWalletTitle": "Restaurar Carteira", "restoreWalletDescription": "Insira sua frase de recuperação Monero abaixo. Verificaremos os formatos comuns.", - "restoreWalletScanFrom": "Escanear a partir de", - "restoreWalletScanFromReason": "Ignore o histórico irrelevante para economizar tempo", - "restoreWalletNotSet": "Não definido", - "restoreScanTitle": "Quando esta carteira recebeu fundos pela primeira vez?", "restoreScanDescription": "A Skylight Wallet pode ignorar o histórico irrelevante para economizar seu tempo. Escolha o primeiro mês em que você usou a carteira ou selecione Não tenho certeza para verificar tudo. Não há problema em escolher um mês muito antigo, mas é ruim escolher um mês muito recente.", - "restoreScanPickMonth": "Escolher um mês", - "restoreScanNotSure": "Não tenho certeza", - "restoreScanNotSureDesc": "Escanear tudo. Mais lento nesta configuração inicial, mas não depois. Sempre completo.", - "restoreScanFromStart": "Genesis", - "restoreScanDone": "Concluir", - "restoreWalletRestoreButton": "Restaurar", "restoreWalletInvalidMnemonic": "Frase de recuperação inválida.", - "restoreWalletSeedLength": "Número de palavras", - "restoreWalletPaste": "Colar", "restoreWalletSeedTypePolyseed": "Polyseed", "restoreWalletSeedTypeBip39": "BIP39", "restoreWalletSeedTypeLegacy": "Legado", - "restoreWalletBadWord": "A palavra {position} não é uma palavra BIP39.", - "@restoreWalletBadWord": { - "placeholders": { - "position": { - "type": "int" - } - } - }, - "restoreWalletDidYouMean": "Você quis dizer {word}?", - "@restoreWalletDidYouMean": { - "placeholders": { - "word": { - "type": "String" - } - } - }, - "restoreWalletChecksumError": "Esta não é uma frase de recuperação válida — verifique as palavras e sua ordem.", "navigationBarWallet": "Carteira", - "historyTitle": "Histórico", - "historyExportCsv": "Exportar CSV", - "historyExported": "Histórico exportado", - "historyExportFailed": "Não foi possível exportar o histórico", - "navigationBarHistory": "Histórico", - "historyFilterType": "Tipo", "historyFilterAddress": "Endereço", - "historyFilterAddressAny": "Qualquer endereço", + "historyFilterAddressAny": "Qualquer Endereço", "historyFilterAddressHint": "Transações recebidas neste endereço ou enviadas para ele.", "historyFilterAddressInvalid": "Não é um endereço Monero", - "historyFilterReset": "Redefinir", - "navigationBarSettings": "Configurações", - "unlockButton": "Desbloquear", - "unlockReason": "Desbloquear carteira", - "unlockUnableToAuthError": "Não foi possível autenticar.", - "unlockWithFaceId": "Desbloquear com Face ID", - "unlockWithTouchId": "Desbloquear com Touch ID", "unlockTitle": "Desbloquear Carteira", - "unlockPasswordHint": "Digite sua senha", - "unlockIncorrectPasswordError": "Senha incorreta. Tente novamente.", - "homeSyncing": "Sincronizando", - "homeSynced": "Sincronizado", - "homeTorConnected": "Tor · conectado", - "homeTorConnecting": "Tor · conectando", - "homeTorDisconnected": "Tor · desconectado", - "homeTorOff": "Tor · desligado", - "homeTorExternalConnected": "Tor externo · conectado", - "homeTorExternalConnecting": "Tor externo · conectando", - "homeTorExternalDisconnected": "Tor externo · desconectado", - "homeReceive": "Receber", - "homeSend": "Enviar", - "homeNoTransactions": "Sem transações", - "homeFiatApiError": "Erro ao conectar ao servidor de preços", "homeDisconnected": "Desconectado", - "coinHomeActivityTitle": "Atividade", - "coinHomeReceived": "Recebido", - "coinHomeSent": "Enviado", - "receiveTitle": "Receber", - "receivePrimaryAddressWarn": "Aviso: A menos que saiba o que está fazendo, por favor use subendereços para melhor privacidade.", - "receiveServerNoSubaddressesWarn": "Aviso: Este servidor não suporta subendereços. Para melhor privacidade, considere usar um servidor que os suporte.", - "receiveSubaddressUnavailableWarn": "Aviso: Você precisa configurar o Monero para ativar subendereços, que protegem melhor sua privacidade.", - "receiveMaxSubaddressesReachedWarn": "Você atingiu o número máximo de subendereços suportados por este servidor. Este é um endereço já usado.", - "receiveSubaddressTab": "Subendereço", - "receivePrimaryTab": "Endereço primário", - "receiveCopyAddress": "Copiar endereço", "receiveQrHint": "Escaneie este código para enviar Monero para esta carteira.", - "receiveEnlargeQr": "Toque para ampliar e aumentar o brilho", - "receiveShrinkQr": "Toque para reduzir", - "receiveShareError": "Não foi possível abrir a janela de compartilhamento", - "receiveAddressHeading": "Seu endereço {coin}", - "@receiveAddressHeading": { - "placeholders": { - "coin": { - "type": "String" - } - } - }, - "sendTitle": "Enviar", - "sendSendButton": "Enviar", - "sendTransactionSuccessfullySent": "Transação enviada com sucesso!", - "sendOpenAliasResolveError": "OpenAlias inválido.", - "sendOpenAliasDisabledError": "O OpenAlias está desligado. Ligue-o nas Configurações.", "sendInvalidAddressError": "Endereço inválido.", - "sendInsufficientBalanceError": "Saldo insuficiente.", - "sendInsufficientBalanceToCoverFeeError": "Saldo insuficiente para cobrir a taxa da rede.", - "settingsTitle": "Configurações", - "settingsSectionGeneral": "Geral", - "settingsSectionSecurityPrivacy": "Segurança e privacidade", - "settingsSectionData": "Dados", - "settingsSectionBehaviour": "Comportamento", - "settingsSectionWallet": "Carteira", - "settingsSectionAbout": "Sobre", - "settingsNotifyNewTxsLabel": "Notificações de Transações", - "settingsNotifyNewTxsDescription": "Mostrar uma notificação quando você receber uma transação. Aumente a frequência de sincronização para vê-las mais cedo.", - "settingsSyncFrequencyLabel": "Frequência de sincronização", "syncFrequencySheetSubtitle": "Com que frequência o Monero sincroniza enquanto o app está fechado.", - "syncFrequencyModeLabel": "Frequência", - "syncFrequencyContinuous": "Contínua", "syncFrequencyContinuousDesc": "Sincroniza o Monero continuamente em segundo plano para que esteja sempre atualizado ao abrir o app. Usa mais bateria e exige uma notificação persistente.", - "syncFrequencyPeriodic": "Periódica", "syncFrequencyPeriodicDesc": "Sincroniza o Monero periodicamente em segundo plano para que geralmente esteja atualizado ao abrir o app. Com um nó Monero, só sincroniza enquanto carrega e no WiFi.", "syncFrequencyPeriodicDescIos": "Sincroniza o Monero periodicamente em segundo plano para que geralmente esteja atualizado ao abrir o app. Com um nó Monero, só sincroniza enquanto carrega.", - "syncFrequencyOff": "Desligada", - "syncFrequencyOffDesc": "Só sincroniza enquanto o app está aberto.", - "settingsAppLockUnlockReason": "Desbloquear carteira", - "settingsLoggingLabel": "Logs de Diagnóstico", - "settingsLoggingDescription": "Registrar operações da carteira em um arquivo de texto na pasta de dados do app para fins de depuração.", - "settingsLoggingDescriptionIos": "Registrar operações da carteira e permitir exportar os logs para um arquivo de texto.", - "settingsExportLogsLabel": "Exportar Logs", - "settingsExportLogsButton": "Exportar", - "settingsExportLogsError": "Nenhum log encontrado para exportar.", - "settingsExportLogsFailed": "Não foi possível exportar o arquivo de log", - "settingsThemeLabel": "Tema", - "settingsThemeSystem": "Sistema", - "settingsThemeLight": "Claro", - "settingsThemeDark": "Escuro", "settingsThemeLightDesc": "Claro e limpo", - "settingsThemeDarkDesc": "Fundo escuro, melhor à noite", - "settingsThemeSystemDesc": "Segue o seu telefone", - "settingsThemeSheetSubtitle": "Escolha um tema que combine com seu estilo.", - "settingsLanguageLabel": "Idioma", - "settingsLanguageSheetSubtitle": "Escolha seu idioma e formato regional.", - "settingsFiatApiSettingsLabel": "Configurações de Exibição de Preços", "settingsLwsViewKeysLabel": "Chaves de Visualização do LWS", - "settingsLwsViewKeysButton": "Ver", - "revealSeedAuthReason": "Confirme sua identidade para ver a frase de recuperação", "settingsSecretKeysLabel": "Chaves Privadas de Restauração", "settingsViewLwsKeysDialogText": "Somente compartilhe estas informações com o seu servidor light-wallet. Essas chaves permitem que o portador veja permanentemente todas as transações relacionadas às suas carteiras. Compartilhá-las com uma pessoa não confiável prejudicará significativamente sua privacidade.", "settingsViewLwsKeysDialogRevealButton": "Revelar", "settingsViewSecretKeysDialogText": "Não compartilhe essas chaves com ninguém, incluindo pessoas que aleguem ser suporte. Se você receber um pedido para fornecê-las, está sendo vítima de um golpe. Se você fornecer essas informações a outra pessoa, perderá seu dinheiro e ele não poderá ser recuperado.", "settingsViewSecretKeysDialogRevealButton": "Revelar", - "settingsDeleteWalletButton": "Excluir Carteira", - "settingsDeleteWalletDialogText": "Tem certeza que deseja excluir sua carteira? Você perderá acesso à seus fundos, a menos que tenha anotado sua frase de recuperação. Isso não remove sua lista de contatos nem as configurações do app.", - "settingsDeleteWalletDialogDeleteButton": "Excluir", - "settingsDeleteAddressBookButton": "Excluir Lista de Contatos", - "settingsDeleteAddressBookDialogText": "Tem certeza que deseja excluir sua lista de contatos? Se o backup automático estiver ligado, ela também será excluída desses backups automáticos.", - "settingsDeleteAddressBookDialogDeleteButton": "Excluir", - "settingsAddressBookDeleted": "Lista de contatos excluída", - "txDetailsTitle": "Detalhes da transação", - "txDetailsHashLabel": "Hash", - "txDetailsTimeAndDateLabel": "Data e Hora", - "txDetailsConfirmationHeightLabel": "Bloco de Confirmação", - "txDetailsConfirmationsLabel": "Confirmações", - "txDetailsViewKeyLabel": "Chave de Visualização", - "txDetailsRecipientsLabel": "Destinatários", - "txDetailsReceivedAtLabel": "Recebido em", - "txDetailsChangeRecipientLabel": "Destinatário de troco", - "unconfirmed": "Não confirmado", - "txDetailsCopyHint": "toque em qualquer valor para copiar", - "txDetailsFailed": "Esta transação falhou. Os fundos não foram enviados.", - "txDetailsUnknownStatus": "Esta transação não foi confirmada como enviada. Verifique antes de enviar novamente.", - "copiedToClipboard": "Copiado para a área de transferência", - "lwsKeysTitle": "Chaves do LWS", - "lwsKeysPrimaryAddress": "Endereço Primário", - "lwsKeysRestoreHeight": "Bloco de Restauração", - "lwsKeysSecretViewKey": "Chave Privada de Visualização", - "lwsKeysWarning": "As capturas de tela estão bloqueadas nesta tela. Certifique-se de que ninguém está olhando por cima do seu ombro.", "secretKeysTitle": "Chaves Privadas de Restauração", "secretKeysDescription": "Estas frases de recuperação e chaves restauram o controle total da sua carteira. Qualquer pessoa que as veja pode gastar seus fundos.", - "secretKeysMnemonic": "Frase de recuperação", + "secretKeysMnemonic": "Frase de Recuperação", "secretKeysPublicSpendKey": "Chave Pública de Gasto", "secretKeysSecretSpendKey": "Chave Privada de Gasto", "secretKeysPublicViewKey": "Chave Pública de Visualização", "secretKeysWarning": "Nunca as compartilhe ou insira em qualquer site. Guarde-as offline.", - "scanQrTitle": "Escanear QR Code", - "confirmSendTitle": "Confirmar Envio", - "confirmSendDescription": "As transações são irreversíveis, então verifique se estes detalhes correspondem exatamente.", - "confirmSendHighFeeWarning": "A taxa de rede é {percent} do valor que você está enviando.", - "@confirmSendHighFeeWarning": { - "placeholders": { - "percent": { - "type": "String" - } - } - }, - "addressBookTitle": "Lista de Contatos", - "addressBookAddContact": "Adicionar Contato", - "addressBookEditContact": "Editar Contato", - "addressBookDeleteContact": "Excluir Contato", - "addressBookDeleteContactConfirmation": "Tem certeza que deseja excluir \"{contactName}\"?", - "@addressBookDeleteContactConfirmation": { - "placeholders": { - "contactName": { - "type": "String" - } - } - }, - "addressBookDelete": "Excluir", - "addressBookSearchHint": "Pesquisar contatos...", - "addressBookNoContacts": "Nenhum contato ainda", - "addressBookNoContactsDescription": "Adicione seu primeiro contato tocando no botão +", - "addressBookNoSearchResults": "Nenhum contato encontrado", - "addressBookEdit": "Editar", - "addressBookContactName": "Nome do Contato", - "addressBookNameHint": "Nome", "addressBookAddDescription": "Um nome e um endereço Monero para pagá-lo.", "addressBookEditDescription": "Atualize o nome ou o endereço deste contato.", - "addressBookUpdate": "Atualizar", - "addressBookSave": "Salvar", - "sendPriorityLow": "Baixa", - "sendPriorityNormal": "Normal", - "sendPriorityHigh": "Alta", - "sendContactsButton": "Contatos", - "sendToLabel": "Para", - "sendPasteButton": "Colar", - "sendScanButton": "Escanear", - "sendMaxButton": "MÁX", - "sendNetworkFee": "Taxa de rede", - "sendPriorityHeading": "Prioridade", - "sendPickContactTitle": "Enviar para um contato", - "sendAvailableSuffix": "disponível", "sendIrreversibleNote": "As transações são irreversíveis, então verifique se o endereço e o valor estão corretos.", - "sendSwitchUnit": "Alternar unidade do valor", - "sendFailedToGetFeesError": "Não foi possível carregar taxas.", - "sendNoConnectionError": "Não foi possível conectar ao servidor. Verifique sua conexão e tente novamente.", - "torSettingsTitle": "Configurações do Tor", - "torSettingsModeBuiltIn": "Tor Integrado", - "torSettingsModeExternal": "Tor Externo", - "torSettingsModeDisabled": "Sem Tor", - "torChoiceBuiltInDesc": "Incluído na Skylight Wallet · recomendado", - "torChoiceExternalDesc": "Orbot, ou um daemon que você mesmo executa", - "torChoiceNoTorDesc": "Os servidores aos quais você se conecta podem ver seu endereço IP", - "torChoiceConnected": "Conectado ao Tor", - "torChoiceTestFailed": "Falha no teste", - "torChoiceTitle": "Configuração da Conexão Tor", "torChoiceSubtitle": "O Tor pode ocultar seu endereço IP dos servidores aos quais você se conecta. Isso não reduz as informações que ficam armazenadas em blockchains públicas. De modo geral, como você quer que a Skylight Wallet lide com as conexões Tor?", - "torSettingsSocksPortLabel": "Porta SOCKS", - "torSettingsUseOrbotLabel": "Usar Orbot/InviZible", - "torSettingsUseOrbotLabelIos": "Usar Orbot", - "torSettingsSaveButton": "Salvar", - "torSettingsTestConnectionButton": "Testar Conexão", - "torDisabledWalletsWarningTitle": "Desativar o Tor?", - "torDisabledWalletsWarningBody": "Algumas carteiras estão configuradas para conectar via Tor. Desativar o Tor irá desconectá-las, e elas permanecerão desconectadas até que você reconfigure a conexão.", - "torDisabledWalletsWarningConfirm": "Desativar o Tor", - "settingsTorSettingsLabel": "Configurações do Tor", - "lwsSetupTorDisabledError": "O Tor está desativado. Por favor, volte e ative-o.", - "coinHomeSwap": "Trocar", - "coinHomeSwapComingSoon": "Em breve!", - "verifySeedTitle": "Verificar Frase de Recuperação", - "homeIncomingTitle": "A receber", - "homeIncomingUnlocksIn": "Desbloqueia em cerca de {minutes, plural, =1{1 minuto} other{{minutes} minutos}} · {done} de {needed}", - "homeIncomingConfirmations": "{done} de {needed} confirmações", - "homeChartTitle": "Saldo · 90 dias", - "homeChartToday": "Hoje", - "homeChartEachCoin": "Saldo de cada moeda", - "homeChartNew": "Novo", - "homeChartEmpty": "Seu saldo ao longo do tempo aparece aqui após sua primeira transação.", - "homeChecklistTitle": "Proteja sua carteira", - "homeChecklistProgress": "{done} de {total}", - "homeChecklistVerifySeed": "Verifique sua frase de recuperação", - "homeChecklistAutoBackup": "Ative os backups automáticos", - "homeChecklistTor": "Conecte-se pelo Tor", - "homeChecklistPrivateLookups": "Use o Tor nas consultas de preço e OpenAlias", - "homeChecklistSecurityKeys": "Configure uma chave de segurança", - "verifySeedSubtitle": "Insira as palavras {first}, {second} e {third} da sua frase de recuperação abaixo.", - "verifySeedButton": "Verificar", - "verifySeedWrongTitle": "Essas palavras não conferem", - "verifySeedWrongBody": "Confira a frase de recuperação que você anotou e insira as três novas palavras pedidas. {count, plural, =1{Resta 1 tentativa} other{Restam {count} tentativas}} antes de o app bloquear.", - "verifySeedTryAgain": "Tentar novamente", - "verifySeedUnavailable": "Não é possível ler sua frase de recuperação agora. Tente novamente depois de desbloquear a carteira.", - "verifySeedDone": "Frase de recuperação verificada", - "homeIncomingTitleMore": "A receber (+{count, plural, =1{1 Outra} other{{count} Outras}})" + "torSettingsSheetSubtitle": "O Tor pode ocultar seu endereço IP dos servidores aos quais você se conecta. Isso não reduz as informações que ficam armazenadas em blockchains públicas. Alterar isto reconecta sua carteira." } diff --git a/lib/l10n/shared_strings.dart b/lib/l10n/shared_strings.dart new file mode 100644 index 0000000..7f2713b --- /dev/null +++ b/lib/l10n/shared_strings.dart @@ -0,0 +1,15 @@ +import 'package:flutter/widgets.dart' show Locale; +import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:wallet_ui/wallet_ui.dart' show WalletUiLocalizations, lookupWalletUiLocalizations; + +/// The app's name where a shared string says it (`{appName}`). +const appShortName = 'Skylight'; + +/// The strings both apps word the same live in wallet-core +/// ([WalletUiLocalizations]); `i18n.shared` reads them in the app's own +/// locale, so a screen that holds [AppLocalizations] needs nothing else. +/// What goes there and what stays here: wallet-core's docs/strings.md. +extension SharedStrings on AppLocalizations { + WalletUiLocalizations get shared => + lookupWalletUiLocalizations(Locale(localeName.split(RegExp('[_-]')).first)); +} diff --git a/lib/main.dart b/lib/main.dart index e087453..27bdcbd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,17 +8,15 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/models/contact_model.dart'; -import 'package:skylight_wallet/services/tor_settings_service.dart'; import 'package:skylight_wallet/screens/lws_details.dart'; import 'package:skylight_wallet/screens/lws_keys.dart'; import 'package:skylight_wallet/screens/scan_qr.dart'; import 'package:skylight_wallet/screens/secret_keys.dart'; -import 'package:skylight_wallet/services/tor_service.dart'; import 'package:skylight_wallet/models/language_model.dart'; import 'package:skylight_wallet/models/theme_model.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/screens/settings.dart'; -import 'package:skylight_wallet/models/app_wallet.dart'; import 'package:skylight_wallet/screens/connection_setup.dart'; import 'package:skylight_wallet/screens/fiat_api_setup_screen.dart'; import 'package:skylight_wallet/screens/generate_seed.dart'; @@ -32,9 +30,8 @@ import 'package:skylight_wallet/screens/wallet_home.dart'; import 'package:skylight_wallet/screens/welcome.dart'; import 'package:skylight_wallet/screens/tor_settings.dart'; import 'package:skylight_wallet/screens/address_book.dart'; +import 'package:skylight_wallet/screens/desktop/home_shell.dart' show subPageChrome; import 'package:skylight_wallet/screens/history.dart'; -import 'package:skylight_wallet/screens/privacy_policy.dart'; -import 'package:skylight_wallet/screens/terms_of_service.dart'; import 'package:skylight_wallet/screens/unlock.dart'; import 'package:skylight_wallet/services/notifications_service.dart'; import 'package:skylight_wallet/services/shared_preferences_service.dart'; @@ -142,17 +139,12 @@ class _AppRoot extends StatefulWidget { } class _AppRootState extends State<_AppRoot> - with WidgetsBindingObserver, AppLockLifecycle<_AppRoot> { + with WidgetsBindingObserver, AppLockLifecycle<_AppRoot>, WalletAppServices<_AppRoot> { // Started once, on the first build. Building it inside the builder would // re-run it on every theme/language change, opening the wallet again — a // second wallet on the same file, with its own sync loop, while the first is // left running. Future>? _startup; - // Services that must fire once the startup work is done, not on every build. - var _startedServices = false; - // Desktop-only foreground announce: listens for tx-history growth (see below). - AppWallet? _announceWallet; - int _lastAnnouncedTxCount = 0; // Brightness-flip repaint: screens read BrandColors globally (not via // Theme.of), so a theme change doesn't dirty cached routes on its own. On an @@ -196,11 +188,11 @@ class _AppRootState extends State<_AppRoot> '/wallet_home': (context) => WalletHomeScreen(), '/settings': (context) => SettingsScreen(), '/lws_keys': (context) => AppLockGate( - reason: AppLocalizations.of(context)!.settingsAppLockUnlockReason, + reason: AppLocalizations.of(context)!.shared.unlockWalletReason, child: LwsKeysScreen(), ), '/secret_keys': (context) => AppLockGate( - reason: AppLocalizations.of(context)!.revealSeedAuthReason, + reason: AppLocalizations.of(context)!.shared.revealSeedAuthReason, child: SecretKeysScreen(), ), '/send': (context) => SendScreen(), @@ -208,8 +200,18 @@ class _AppRootState extends State<_AppRoot> '/receive': (context) => ReceiveScreen(), '/address_book': (context) => AddressBookScreen(), '/history': (context) => const HistoryScreen(), - '/terms_of_service': (context) => TermsOfService(), - '/privacy_policy': (context) => PrivacyPolicy(), + '/terms_of_service': (context) => TermsOfServiceScreen( + title: AppLocalizations.of(context)!.welcomeTermsLink, + chrome: subPageChrome(context), + ), + '/privacy_policy': (context) => PrivacyPolicyScreen( + title: AppLocalizations.of(context)!.welcomePrivacyLink, + chrome: subPageChrome(context), + ), + '/open_source_licenses': (context) => OpenSourceLicensesScreen( + title: AppLocalizations.of(context)!.shared.settingsLicensesLabel, + chrome: subPageChrome(context), + ), }; Route? _onGenerateRoute(RouteSettings settings) { @@ -256,10 +258,16 @@ class _AppRootState extends State<_AppRoot> WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(_routeLinkBlocker); _paymentLinks.dispose(); - _announceWallet?.removeListener(_announceNewTxsOnGrowth); super.dispose(); } + // The services beside the screens (WalletAppServices) use the app's own. + @override + WalletManager get appWalletManager => Provider.of(context, listen: false); + + @override + FiatRateModel get appFiatRates => Provider.of(context, listen: false); + /// Opens the Send form for a payment link the gate let through: the user is /// past every lock and on a screen behind them. It only pre-fills the form, /// which reviews and confirms the payment as always. @@ -278,7 +286,7 @@ class _AppRootState extends State<_AppRoot> final name = wallet?.blockchainName ?? request.coinSymbol; showBrandToastOnOverlay( overlay, - AppLocalizations.of(overlay.context)!.deepLinkCoinNotConfigured(name), + AppLocalizations.of(overlay.context)!.shared.deepLinkCoinNotConfigured(name), ); return; } @@ -289,7 +297,7 @@ class _AppRootState extends State<_AppRoot> arguments: SendScreenArgs( destinationAddress: request.address, amount: request.amount, - backLabel: overlayContext == null ? null : AppLocalizations.of(overlayContext)?.back, + backLabel: overlayContext == null ? null : AppLocalizations.of(overlayContext)?.shared.back, ), ); } @@ -310,23 +318,7 @@ class _AppRootState extends State<_AppRoot> /// announce. Marks only synced history (hash-based), so an unsynced /// receipt is still announced later. @override - void onMobilePaused() { - unawaited(appWalletOf(context, listen: false).notifyNewIncomingTxs(announce: false)); - } - - // Desktop has no background isolate to announce incoming txs, so the - // foreground announces when the wallet's history grows. notifyNewIncomingTxs - // is the decider (hash-based, net-receipt only, respects the notifications - // toggle); the count is a cheap gate so unrelated notifications (connectivity, - // balance) don't hit the keystore. - void _announceNewTxsOnGrowth() { - final wallet = _announceWallet; - if (wallet == null) return; - final count = wallet.txHistory.length; - if (count <= _lastAnnouncedTxCount) return; - _lastAnnouncedTxCount = count; - unawaited(wallet.notifyNewIncomingTxs()); - } + void onMobilePaused() => markIncomingTxsSeen(); Future> _runStartup() { // Wallet existence drives the initial route; done quickly without a full load. @@ -340,8 +332,6 @@ class _AppRootState extends State<_AppRoot> return Consumer2( builder: (context, languageProvider, themeProvider, child) { - final fiatRate = Provider.of(context, listen: false); - return FutureBuilder( future: _startup, builder: (context, snapshot) { @@ -355,33 +345,8 @@ class _AppRootState extends State<_AppRoot> // Linux and Windows). With security keys on, a key comes next. final initialRoute = walletExists ? '/unlock' : '/welcome'; - if (!_startedServices) { - _startedServices = true; - // Only the built-in Tor is ours to start; external/disabled must - // not spin up an embedded Tor, whose status would then misreport - // the external proxy. - unawaited(() async { - await TorSettingsService.sharedInstance.loadSettings(); - if (TorSettingsService.sharedInstance.torMode == TorMode.builtIn) { - await TorService.sharedInstance.start(); - } - }()); - - // Attach the manager once so the (multicoin) fiat model knows to - // fetch XMR; every startService() afterwards can stay argless. - attachFiatWalletManager(context); - if (walletExists) { - fiatRate.startService(); - } - - // Desktop has no background isolate to announce incoming txs, so - // the foreground announces on tx-history growth. Mobile announces - // from its background isolates. - if (isDesktopOS) { - _announceWallet = appWalletOf(context, listen: false) - ..addListener(_announceNewTxsOnGrowth); - } - } + // Tor, fiat rates and desktop announcements, once (WalletAppServices). + startWalletServices(walletExists: walletExists); return MaterialApp( navigatorKey: _navigatorKey, @@ -391,6 +356,7 @@ class _AppRootState extends State<_AppRoot> ...AppLocalizations.localizationsDelegates, FhseLocalizations.delegate, BackupLocalizations.delegate, + WalletUiLocalizations.delegate, ], supportedLocales: AppLocalizations.supportedLocales, theme: brandLightTheme(), diff --git a/lib/models/fiat_rate_model.dart b/lib/models/fiat_rate_model.dart index f6fae62..ac3872d 100644 --- a/lib/models/fiat_rate_model.dart +++ b/lib/models/fiat_rate_model.dart @@ -1,7 +1,7 @@ // FiatRateModel lives in wallet-core (`wallet_fiat`, multicoin — skylight uses // the XMR result via rateFor('XMR')). Kept under the same import path so call -// sites are unchanged; the app supplies the Tor proxy via FiatRates.install and -// attaches the WalletManager via attachFiatWalletManager in wallet_core_glue.dart. +// sites are unchanged; the app supplies the Tor proxy via FiatRates.install in +// wallet_core_glue.dart, and WalletAppServices attaches the WalletManager at launch. // The fiatAutoDisabledByTor auto-disable/restore stays app-side (tor_settings_form). export 'package:wallet_fiat/wallet_fiat.dart' show FiatRateModel, FiatApiMode, FiatCurrency, FiatQuote; diff --git a/lib/route_policy.dart b/lib/route_policy.dart index 9780d26..835a6cb 100644 --- a/lib/route_policy.dart +++ b/lib/route_policy.dart @@ -36,4 +36,5 @@ const routePolicy = RoutePolicy({ '/advanced_security': RouteAccess.unlocked, '/metadata_backup': RouteAccess.unlocked, '/verify_seed': RouteAccess.unlocked, + '/open_source_licenses': RouteAccess.unlocked, }); diff --git a/lib/screens/address_book.dart b/lib/screens/address_book.dart index 9ccef07..0866be0 100644 --- a/lib/screens/address_book.dart +++ b/lib/screens/address_book.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/contact_model.dart'; import 'package:skylight_wallet/screens/desktop/home_shell.dart'; import 'package:skylight_wallet/screens/send.dart'; @@ -80,19 +81,22 @@ class _AddressBookScreenState extends State { color: BrandColors.error, ), const SizedBox(width: 11), - Text(i18n.addressBookDeleteContact, style: BrandText.sheetTitle), + Text(i18n.shared.addressBookDeleteContact, style: BrandText.sheetTitle), ], ), const SizedBox(height: 7), Text( - i18n.addressBookDeleteContactConfirmation(contact.name), + i18n.shared.addressBookDeleteContactConfirmation(contact.name), style: BrandText.bodyMuted.copyWith(fontSize: 13, height: 1.5), ), const SizedBox(height: 18), - BrandButton(label: i18n.cancel, onPressed: () => Navigator.pop(sheetContext)), + BrandButton( + label: i18n.shared.cancel, + onPressed: () => Navigator.pop(sheetContext), + ), const SizedBox(height: 4), BrandButton.ghost( - label: i18n.addressBookDelete, + label: i18n.shared.addressBookDelete, color: BrandColors.error, onPressed: () { Provider.of(context, listen: false).deleteContact(contact.id); @@ -151,9 +155,9 @@ class _AddressBookScreenState extends State { children: [ Row( children: [ - Expanded(child: Text(i18n.addressBookTitle, style: desktopTitleStyle)), + Expanded(child: Text(i18n.shared.addressBookTitle, style: desktopTitleStyle)), BrandButton.secondary( - label: i18n.addressBookAddContact, + label: i18n.shared.addressBookAddContact, icon: Icons.add, expand: false, onPressed: _showAddContactDialog, @@ -190,7 +194,7 @@ class _AddressBookScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), child: BrandScreenHeader( center: Text( - i18n.addressBookTitle, + i18n.shared.addressBookTitle, style: BrandText.appBar.copyWith(fontSize: 16), ), ), @@ -263,7 +267,7 @@ class _SearchField extends StatelessWidget { decoration: InputDecoration( isCollapsed: true, border: InputBorder.none, - hintText: i18n.addressBookSearchHint, + hintText: i18n.shared.addressBookSearchHint, hintStyle: TextStyle(fontSize: 14, color: BrandColors.inkFaint), ), ), @@ -290,14 +294,16 @@ class _EmptyState extends StatelessWidget { Icon(Icons.people_outline, size: 56, color: BrandColors.inkDisabled), const SizedBox(height: 16), Text( - searching ? i18n.addressBookNoSearchResults : i18n.addressBookNoContacts, + searching + ? i18n.shared.addressBookNoSearchResults + : i18n.shared.addressBookNoContacts, textAlign: TextAlign.center, style: BrandText.body, ), if (!searching) ...[ const SizedBox(height: 6), Text( - i18n.addressBookNoContactsDescription, + i18n.shared.addressBookNoContactsDescription, textAlign: TextAlign.center, style: BrandText.bodyMuted, ), @@ -443,7 +449,7 @@ class _ContactTile extends StatelessWidget { children: [ Expanded( child: BrandButton.secondary( - label: i18n.addressBookEdit, + label: i18n.shared.addressBookEdit, dense: true, onPressed: onEdit, ), @@ -451,7 +457,7 @@ class _ContactTile extends StatelessWidget { const SizedBox(width: 10), Expanded( child: BrandButton.ghost( - label: i18n.addressBookDelete, + label: i18n.shared.addressBookDelete, color: BrandColors.error, dense: true, onPressed: onDelete, @@ -477,7 +483,7 @@ class _AddressRow extends StatelessWidget { final i18n = AppLocalizations.of(context)!; // Treat as sensitive (auto-cleared) like other address/key copies. SecureClipboard.copy(_xmrAddress(contact)); - showCopyToast(context, i18n.addressCopied); + showCopyToast(context, i18n.shared.addressCopied); } @override @@ -505,7 +511,7 @@ class _AddressRow extends StatelessWidget { _IconSquare(icon: Icons.copy_outlined, onTap: () => _copy(context)), const SizedBox(width: 8), BrandButton( - label: i18n.homeSend, + label: i18n.shared.homeSend, icon: Icons.north_east, dense: true, expand: false, @@ -515,7 +521,7 @@ class _AddressRow extends StatelessWidget { arguments: SendScreenArgs( destinationAddress: _xmrAddress(contact), contact: contact, - backLabel: i18n.addressBookTitle, + backLabel: i18n.shared.addressBookTitle, ), ), ), @@ -625,7 +631,7 @@ class _ContactSheetState extends State<_ContactSheet> { final name = _nameController.text.trim(); final address = _address; if (name.isEmpty || address == null) { - setState(() => _error = i18n.fieldEmptyError); + setState(() => _error = i18n.shared.fieldEmptyError); return; } @@ -645,7 +651,7 @@ class _ContactSheetState extends State<_ContactSheet> { if (mounted) Navigator.pop(context); } catch (_) { if (mounted) { - showBrandToast(context, i18n.unknownError); + showBrandToast(context, i18n.shared.unknownError); setState(() => _saving = false); } } @@ -686,7 +692,9 @@ class _ContactSheetState extends State<_ContactSheet> { ), const SizedBox(width: 11), Text( - _isEditing ? i18n.addressBookEditContact : i18n.addressBookAddContact, + _isEditing + ? i18n.shared.addressBookEditContact + : i18n.shared.addressBookAddContact, style: BrandText.sheetTitle, ), ], @@ -706,7 +714,7 @@ class _ContactSheetState extends State<_ContactSheet> { crossAxisAlignment: CrossAxisAlignment.start, children: [ SectionHeader( - label: i18n.addressBookContactName, + label: i18n.shared.addressBookContactName, padding: const EdgeInsets.only(left: 4, bottom: 8), ), _nameField(name), @@ -729,13 +737,13 @@ class _ContactSheetState extends State<_ContactSheet> { child: Column( children: [ BrandButton( - label: _isEditing ? i18n.addressBookUpdate : i18n.addressBookSave, + label: _isEditing ? i18n.shared.addressBookUpdate : i18n.shared.save, loading: _saving, onPressed: (_saving || name.isEmpty || _address == null) ? null : _save, ), const SizedBox(height: 4), BrandButton.ghost( - label: i18n.cancel, + label: i18n.shared.cancel, color: BrandColors.inkMuted, onPressed: () => Navigator.pop(context), ), @@ -749,7 +757,9 @@ class _ContactSheetState extends State<_ContactSheet> { } String _addressHeaderLabel(AppLocalizations i18n) { - return _address == null ? '${i18n.address} · ${i18n.restoreWalletNotSet}' : i18n.address; + return _address == null + ? '${i18n.shared.address} · ${i18n.shared.restoreWalletNotSet}' + : i18n.shared.address; } Widget _nameField(String name) { @@ -789,7 +799,7 @@ class _ContactSheetState extends State<_ContactSheet> { decoration: InputDecoration( isCollapsed: true, border: InputBorder.none, - hintText: i18n.addressBookNameHint, + hintText: i18n.shared.addressBookNameHint, hintStyle: TextStyle(fontSize: 14.5, color: BrandColors.inkFaint), ), ), @@ -856,7 +866,7 @@ class _ContactSheetState extends State<_ContactSheet> { bordered: true, color: BrandColors.card, icon: Icons.content_paste_outlined, - label: i18n.sendPasteButton, + label: i18n.shared.sendPasteButton, onTap: _paste, ), if (Platform.isAndroid || Platform.isIOS) ...[ @@ -865,7 +875,7 @@ class _ContactSheetState extends State<_ContactSheet> { bordered: true, color: BrandColors.card, icon: Icons.qr_code_scanner, - label: i18n.sendScanButton, + label: i18n.shared.sendScanButton, onTap: _scan, ), ], diff --git a/lib/screens/connection_setup.dart b/lib/screens/connection_setup.dart index a98972c..3869b4d 100644 --- a/lib/screens/connection_setup.dart +++ b/lib/screens/connection_setup.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/screens/desktop/connection_view.dart'; import 'package:skylight_wallet/util/platform.dart'; import 'package:skylight_wallet/widgets/connection_settings_form.dart'; @@ -19,11 +20,11 @@ class ConnectionSetupScreen extends StatelessWidget { if (isDesktop) { return DesktopConnectionView( - title: i18n.lwsSetupTitle, + title: i18n.shared.connectionSetupTitle, description: i18n.lwsSetupDescription, noteServer: i18n.onboardingConnectionNoteServer, noteChangeable: i18n.onboardingConnectionNoteChangeable, - saveButtonLabel: i18n.lwsSetupContinueButton, + saveButtonLabel: i18n.shared.continueText, onSaved: onSaved, onBack: () => Navigator.pop(context), ); @@ -46,14 +47,14 @@ class ConnectionSetupScreen extends StatelessWidget { center: const StepDots(count: 6, index: 2), ), const SizedBox(height: BrandSpacing.lg), - Text(i18n.lwsSetupTitle, style: BrandText.title), + Text(i18n.shared.connectionSetupTitle, style: BrandText.title), const SizedBox(height: BrandSpacing.sm), Text(i18n.lwsSetupDescription, style: BrandText.bodyMuted), const SizedBox(height: BrandSpacing.xl), Expanded( child: SingleChildScrollView( child: ConnectionSettingsForm( - saveButtonLabel: i18n.lwsSetupContinueButton, + saveButtonLabel: i18n.shared.continueText, onSaved: onSaved, ), ), diff --git a/lib/screens/create_wallet.dart b/lib/screens/create_wallet.dart index c03e0c0..06d449c 100644 --- a/lib/screens/create_wallet.dart +++ b/lib/screens/create_wallet.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/screens/desktop/wallet_setup_view.dart'; import 'package:skylight_wallet/util/platform.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart'; @@ -44,11 +45,11 @@ class _CreateWalletScreenState extends State { final i18n = AppLocalizations.of(context)!; final labels = CreateWalletLabels( - title: i18n.createWalletTitle, + title: i18n.shared.createWalletTitle, subtitle: i18n.createWalletDescription, - createNew: i18n.createWalletCreateNewButton, + createNew: i18n.shared.createNewWalletChoice, createNewDesc: i18n.createWalletCreateNewDesc, - restore: i18n.createWalletRestoreExistingButton, + restore: i18n.shared.restoreExistingWalletChoice, restoreDesc: i18n.createWalletRestoreExistingDesc, ); void createNew() => Navigator.pushNamed(context, '/generate_seed'); @@ -56,9 +57,9 @@ class _CreateWalletScreenState extends State { if (isDesktop) { return DesktopWalletSetupView( labels: labels, - continueText: i18n.continueText, - noteGenerated: i18n.onboardingWalletNoteGenerated, - noteRestore: i18n.onboardingWalletNoteRestore, + continueText: i18n.shared.continueText, + noteGenerated: i18n.shared.onboardingWalletNoteGenerated(appShortName), + noteRestore: i18n.shared.onboardingWalletNoteRestore, onCreateNew: createNew, onRestore: _restoreExisting, onBack: () => Navigator.pop(context), diff --git a/lib/screens/create_wallet_password.dart b/lib/screens/create_wallet_password.dart index ba7defe..5405097 100644 --- a/lib/screens/create_wallet_password.dart +++ b/lib/screens/create_wallet_password.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/screens/desktop/create_password_view.dart'; import 'package:skylight_wallet/util/logging.dart'; @@ -66,14 +67,14 @@ class _CreateWalletPasswordScreenState extends State final args = ModalRoute.of(context)?.settings.arguments as CreateWalletPasswordArgs?; final labels = CreatePasswordLabels( - title: i18n.createWalletPasswordTitle, - description: i18n.createWalletPasswordDescription, - passwordHint: i18n.createWalletPasswordHint, - confirmPasswordHint: i18n.createWalletConfirmPasswordHint, - submit: i18n.continueText, - fieldEmptyError: i18n.fieldEmptyError, - tooShortError: i18n.passwordTooShortError, - doNotMatchError: i18n.passwordsDoNotMatchError, + title: i18n.shared.createWalletPasswordTitle, + description: i18n.shared.createWalletPasswordDescription, + passwordHint: i18n.shared.createWalletPasswordHint, + confirmPasswordHint: i18n.shared.createWalletConfirmPasswordHint, + submit: i18n.shared.continueText, + fieldEmptyError: i18n.shared.fieldEmptyError, + tooShortError: i18n.shared.passwordTooShortError, + doNotMatchError: i18n.shared.passwordsDoNotMatchError, ); void submit(String password) { @@ -83,11 +84,11 @@ class _CreateWalletPasswordScreenState extends State if (isDesktop) { return DesktopCreatePasswordView( labels: labels, - continueText: i18n.continueText, - noteLaunch: i18n.onboardingPasswordNoteLaunch, - noteNotCloud: i18n.onboardingPasswordNoteNotCloud, - strongLabel: i18n.onboardingPasswordStrong, - matchLabel: i18n.onboardingPasswordMatch, + continueText: i18n.shared.continueText, + noteLaunch: i18n.shared.onboardingPasswordNoteLaunch, + noteNotCloud: i18n.shared.onboardingPasswordNoteNotCloud, + strongLabel: i18n.shared.onboardingPasswordStrong, + matchLabel: i18n.shared.onboardingPasswordMatch, loading: _isLoading, onSubmit: submit, onBack: () => Navigator.pop(context), diff --git a/lib/screens/desktop/home_shell.dart b/lib/screens/desktop/home_shell.dart index d034b1a..9e13b86 100644 --- a/lib/screens/desktop/home_shell.dart +++ b/lib/screens/desktop/home_shell.dart @@ -3,62 +3,22 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/screens/settings.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart'; /// The top-level desktop destinations, mirroring the mobile navigation bar. enum DesktopNav { home, history, addressBook, settings } -/// Shared style for a desktop screen's large title (Settings, Contacts, …). -TextStyle get desktopTitleStyle => TextStyle( - fontFamily: 'Ubuntu', - fontSize: 26, - height: 1.2, - fontWeight: FontWeight.w700, - color: BrandColors.ink, -); - -/// The "‹ label" back link at the top of a desktop sub-screen (receive, send, -/// ToS, …) — the shared back affordance in place of a mobile app bar. -class DesktopBackLink extends StatelessWidget { - final String label; - final VoidCallback onTap; - - const DesktopBackLink({super.key, required this.label, required this.onTap}); - - @override - Widget build(BuildContext context) { - // Sized to its content (left-aligned) so the hover/ripple doesn't span the - // full parent width; a little right padding keeps the ripple off the label. - return Align( - alignment: Alignment.centerLeft, - child: InkWell( - mouseCursor: WidgetStateMouseCursor.clickable, - onTap: onTap, - borderRadius: BorderRadius.circular(8), - child: Padding( - padding: const EdgeInsets.fromLTRB(0, 4, 4, 4), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.chevron_left, size: 18, color: BrandColors.primary), - const SizedBox(width: 2), - Text( - label, - style: TextStyle( - fontFamily: 'Ubuntu', - fontSize: 12.5, - height: 1, - fontWeight: FontWeight.w500, - color: BrandColors.primary, - ), - ), - ], - ), - ), - ), - ); - } +/// The chrome for wallet_ui's shared sub-pages (the legal pages): the back +/// links' strings, and Settings' place in the sidebar when opened from there. +SubPageChrome subPageChrome(BuildContext context) { + final i18n = AppLocalizations.of(context)!; + return SubPageChrome( + back: i18n.shared.back, + parent: i18n.shared.navigationBarSettings, + desktopShell: (page) => DesktopShell(active: DesktopNav.settings, child: page), + ); } /// Two-pane desktop chrome: a fixed left sidebar (brand, nav, Tor status, @@ -181,32 +141,37 @@ class _DesktopShellState extends State { '/wallet_home', ), const SizedBox(height: 2), - _navItem(DesktopNav.history, Icons.history, i18n.navigationBarHistory, '/history'), + _navItem( + DesktopNav.history, + Icons.history, + i18n.shared.navigationBarHistory, + '/history', + ), const SizedBox(height: 2), _navItem( DesktopNav.addressBook, Icons.people_outline, - i18n.addressBookTitle, + i18n.shared.addressBookTitle, '/address_book', ), const Spacer(), // Settings opens as a modal over the current screen (not a page). _navTile( icon: Icons.tune, - label: i18n.navigationBarSettings, + label: i18n.shared.navigationBarSettings, active: false, onTap: () => showSettingsSheet(context), ), const SizedBox(height: 6), TorStatusPill( labels: TorStatusLabels( - connected: i18n.homeTorConnected, - connecting: i18n.homeTorConnecting, - disconnected: i18n.homeTorDisconnected, - off: i18n.homeTorOff, - externalConnected: i18n.homeTorExternalConnected, - externalConnecting: i18n.homeTorExternalConnecting, - externalDisconnected: i18n.homeTorExternalDisconnected, + connected: i18n.shared.homeTorConnected, + connecting: i18n.shared.homeTorConnecting, + disconnected: i18n.shared.homeTorDisconnected, + off: i18n.shared.homeTorOff, + externalConnected: i18n.shared.homeTorExternalConnected, + externalConnecting: i18n.shared.homeTorExternalConnecting, + externalDisconnected: i18n.shared.homeTorExternalDisconnected, ), ), if (_version.isNotEmpty) diff --git a/lib/screens/desktop/receive_view.dart b/lib/screens/desktop/receive_view.dart index 52d6516..cb61e23 100644 --- a/lib/screens/desktop/receive_view.dart +++ b/lib/screens/desktop/receive_view.dart @@ -151,13 +151,7 @@ class DesktopReceiveView extends StatelessWidget { ), ), ), - if (warning != null) ...[ - const SizedBox(height: 12), - Text( - warning!, - style: BrandText.caption.copyWith(color: BrandColors.warning, height: 1.4), - ), - ], + if (warning != null) ...[const SizedBox(height: 12), WarningCard.text(warning!)], const SizedBox(height: 16), Align( alignment: Alignment.centerLeft, diff --git a/lib/screens/desktop/tor_choice_view.dart b/lib/screens/desktop/tor_choice_view.dart index 324fcd4..72e2f8c 100644 --- a/lib/screens/desktop/tor_choice_view.dart +++ b/lib/screens/desktop/tor_choice_view.dart @@ -35,7 +35,8 @@ class DesktopTorChoiceView extends StatefulWidget { class _DesktopTorChoiceViewState extends State { static const _builtIn = 0, _external = 1, _noTor = 2; - int? _selected; + // Built-in, the recommended mode, starts selected, as on the phone screen. + int _selected = _builtIn; final _portController = TextEditingController(text: '9050'); final bool _useOrbot = false; bool _testing = false; @@ -47,7 +48,7 @@ class _DesktopTorChoiceViewState extends State { super.dispose(); } - bool get _canContinue => _selected != null && (_selected != _external || _testOk == true); + bool get _canContinue => _selected != _external || _testOk == true; Future _runTest() async { setState(() { @@ -74,13 +75,11 @@ class _DesktopTorChoiceViewState extends State { continueLabel: l.continueText, continueEnabled: _canContinue, onBack: widget.onBack, - onContinue: _selected == null - ? null - : () => widget.onContinue( - modeIndex: _selected!, - port: _portController.text.trim(), - useOrbot: _useOrbot, - ), + onContinue: () => widget.onContinue( + modeIndex: _selected, + port: _portController.text.trim(), + useOrbot: _useOrbot, + ), notes: [ OnboardingNote(Icons.lock_outline, widget.notePrivacy), OnboardingNote(Icons.tune, widget.noteChangeable), diff --git a/lib/screens/fiat_api_setup_screen.dart b/lib/screens/fiat_api_setup_screen.dart index 2cbf3dc..c83e5d2 100644 --- a/lib/screens/fiat_api_setup_screen.dart +++ b/lib/screens/fiat_api_setup_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/screens/desktop/fiat_setup_view.dart'; import 'package:skylight_wallet/services/shared_preferences_service.dart'; @@ -44,23 +45,23 @@ class _FiatApiSetupScreenState extends State { final i18n = AppLocalizations.of(context)!; final labels = FiatSetupLabels( - title: i18n.fiatApiSetupTitle, + title: i18n.shared.fiatApiSetupTitle, subtitle: i18n.fiatApiSetupDescription, - torOnly: i18n.fiatApiSettingsModeTorOnly, - torOnlyDesc: i18n.fiatModeTorOnlyDesc, - clearnet: i18n.fiatApiSettingsModeClearnet, - clearnetDesc: i18n.fiatModeClearnetDesc, - disabled: i18n.fiatApiSettingsModeDisabled, - disabledDesc: i18n.fiatModeDisabledDesc, - currencyLabel: i18n.fiatApiSettingsDisplayCurrencyLabel, - continueText: i18n.lwsSetupContinueButton, + torOnly: i18n.shared.fiatApiSettingsModeTorOnly, + torOnlyDesc: i18n.shared.fiatModeTorOnlyDesc, + clearnet: i18n.shared.clearnetOnlyChoice, + clearnetDesc: i18n.shared.fiatModeClearnetDesc, + disabled: i18n.shared.fiatApiSettingsModeDisabled, + disabledDesc: i18n.shared.fiatModeDisabledDesc, + currencyLabel: i18n.shared.fiatApiSettingsDisplayCurrencyLabel, + continueText: i18n.shared.continueText, ); if (isDesktop) { return DesktopFiatSetupView( labels: labels, - noteRatesOnly: i18n.onboardingPriceNoteRatesOnly, - noteTor: i18n.onboardingPriceNoteTor, + noteRatesOnly: i18n.shared.onboardingPriceNoteRatesOnly, + noteTor: i18n.shared.onboardingPriceNoteTor, modeIndex: _fiatMode.index, currency: _fiatCurrency, offerTorOnly: !_globalTorDisabled, diff --git a/lib/screens/generate_seed.dart b/lib/screens/generate_seed.dart index ca99498..cad99aa 100644 --- a/lib/screens/generate_seed.dart +++ b/lib/screens/generate_seed.dart @@ -5,6 +5,7 @@ import 'package:provider/provider.dart'; import 'package:wallet_domain/wallet_domain.dart' show SeedSource; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/screens/create_wallet_password.dart'; import 'package:skylight_wallet/screens/desktop/onboarding_steps.dart'; @@ -100,20 +101,21 @@ class _GenerateSeedScreenState extends State with SecureScre logo: SvgPicture.asset('assets/logo_nobg.svg', height: 52), step: 5, totalSteps: desktopOnboardingSteps, - title: i18n.generateSeedTitle, - description: i18n.generateSeedSubtitleRevealed, + title: i18n.shared.generateSeedTitle, + description: i18n.shared.generateSeedSubtitleRevealed, seedWords: _seed ?? const [], - birthdayLabel: i18n.generateSeedBirthdayLabel, - birthdayReason: i18n.generateSeedBirthdayReason, + birthdayLabel: i18n.shared.generateSeedBirthdayLabel, + birthdayReason: i18n.shared.generateSeedBirthdayReason, birthdayValue: _generated != null - ? DateFormat.yMMM( - Localizations.localeOf(context).toString(), - ).format(_generated!.restoreDate) + ? DateFormat.yMMM(Localizations.localeOf(context).toString()) + .format(_generated!.restoreDate) : null, - confirmLabel: i18n.generateSeedConfirm, - passwordNote: typesWalletPassword ? i18n.onboardingSeedNotePassword : null, - revealLabel: i18n.generateSeedReveal, - continueText: i18n.continueText, + confirmLabel: i18n.shared.generateSeedConfirm(_seed?.length ?? 0), + passwordNote: typesWalletPassword + ? i18n.shared.generateSeedPasswordNote(appShortName) + : null, + revealLabel: i18n.shared.generateSeedReveal, + continueText: i18n.shared.continueText, loading: _committing, onContinue: _continue, onBack: () => Navigator.pop(context), @@ -128,14 +130,14 @@ class _GenerateSeedScreenState extends State with SecureScre onContinue: _continue, continueLoading: _committing, labels: GenerateSeedLabels( - titleCovered: i18n.generateSeedTitleCovered, - titleRevealed: i18n.generateSeedTitle, + titleCovered: i18n.shared.generateSeedTitleCovered, + titleRevealed: i18n.shared.generateSeedTitle, subtitleCovered: i18n.generateSeedSubtitleCovered, - subtitleRevealed: i18n.generateSeedSubtitleRevealed, - reveal: i18n.generateSeedReveal, - screenshotNote: i18n.generateSeedScreenshotNote, - confirm: i18n.generateSeedConfirm, - continueText: i18n.continueText, + subtitleRevealed: i18n.shared.generateSeedSubtitleRevealed, + reveal: i18n.shared.generateSeedReveal, + screenshotNote: i18n.shared.generateSeedScreenshotNote, + confirm: i18n.shared.generateSeedConfirm(_seed?.length ?? 0), + continueText: i18n.shared.continueText, ), ); } diff --git a/lib/screens/history.dart b/lib/screens/history.dart index 48e1c6b..dbd305a 100644 --- a/lib/screens/history.dart +++ b/lib/screens/history.dart @@ -7,6 +7,7 @@ import 'package:provider/provider.dart'; import 'package:skylight_wallet/consts.dart' as consts; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/screens/desktop/home_shell.dart'; import 'package:skylight_wallet/util/platform.dart'; @@ -110,10 +111,10 @@ class _HistoryScreenState extends State { mimeType: 'text/csv', sharePositionOrigin: origin, ); - if (saved && mounted) showBrandToast(context, i18n.historyExported); + if (saved && mounted) showBrandToast(context, i18n.shared.historyExported); } catch (e) { log(LogLevel.error, '[History] CSV export failed: $e'); - if (mounted) showBrandToast(context, i18n.historyExportFailed); + if (mounted) showBrandToast(context, i18n.shared.historyExportFailed); } } @@ -140,13 +141,13 @@ class _HistoryScreenState extends State { final typeOptions = [ _Option( '${consts.txDirectionIncoming}', - i18n.coinHomeReceived, + i18n.shared.coinHomeReceived, _typeIcon(Icons.south, BrandColors.success, BrandColors.successBg), typeCount(consts.txDirectionIncoming), ), _Option( '${consts.txDirectionOutgoing}', - i18n.coinHomeSent, + i18n.shared.coinHomeSent, _typeIcon(Icons.north, BrandColors.primary, BrandColors.surfaceAccent), typeCount(consts.txDirectionOutgoing), ), @@ -157,7 +158,7 @@ class _HistoryScreenState extends State { final typeCountBadge = typesChecked.length == typeOptions.length ? null : typesChecked.length; final list = entries.isEmpty || asset == null - ? Center(child: Text(i18n.homeNoTransactions, style: BrandText.bodyMuted)) + ? Center(child: Text(i18n.shared.noTransactions, style: BrandText.bodyMuted)) : _Timeline( entries: entries, asset: asset, @@ -179,7 +180,7 @@ class _HistoryScreenState extends State { child: Row( children: [ _FilterPill( - label: i18n.historyFilterType, + label: i18n.shared.historyFilterType, count: typeCountBadge, open: _open == _Filter.type, onTap: () => _toggleOpen(_Filter.type), @@ -196,7 +197,7 @@ class _HistoryScreenState extends State { ), if (_open == _Filter.type) _FilterPanel( - header: i18n.historyFilterType, + header: i18n.shared.historyFilterType, options: typeOptions, isSelected: (v) => !_typesHidden.contains(int.parse(v)), gutter: gutter, @@ -236,10 +237,10 @@ class _HistoryScreenState extends State { padding: const EdgeInsets.fromLTRB(44, 30, 44, 20), child: Row( children: [ - Expanded(child: Text(i18n.historyTitle, style: desktopTitleStyle)), + Expanded(child: Text(i18n.shared.historyTitle, style: desktopTitleStyle)), Builder( builder: (anchor) => BrandButton.secondary( - label: i18n.historyExportCsv, + label: i18n.shared.historyExportCsv, icon: Icons.file_download_outlined, expand: false, onPressed: canExport ? () => export(anchor) : null, @@ -268,9 +269,12 @@ class _HistoryScreenState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), child: BrandScreenHeader( - center: Text(i18n.historyTitle, style: BrandText.appBar.copyWith(fontSize: 16)), + center: Text( + i18n.shared.historyTitle, + style: BrandText.appBar.copyWith(fontSize: 16), + ), action: Tooltip( - message: i18n.historyExportCsv, + message: i18n.shared.historyExportCsv, child: Opacity( opacity: canExport ? 1 : 0.4, child: Builder( @@ -357,7 +361,10 @@ class _Timeline extends StatelessWidget { tx: tx, asset: asset, showCoinIcon: false, - labels: TxActivityLabels(received: i18n.coinHomeReceived, sent: i18n.coinHomeSent), + labels: TxActivityLabels( + received: i18n.shared.coinHomeReceived, + sent: i18n.shared.coinHomeSent, + ), fiatRate: fiatRate, showDivider: next is TxDetails, onTap: () => onTapTx(tx), @@ -462,7 +469,7 @@ class _AddressPanel extends StatelessWidget { MiniActionButton( bordered: true, icon: Icons.content_paste_outlined, - label: i18n.sendPasteButton, + label: i18n.shared.sendPasteButton, onTap: onPaste, ), if (Platform.isAndroid || Platform.isIOS) ...[ @@ -470,7 +477,7 @@ class _AddressPanel extends StatelessWidget { MiniActionButton( bordered: true, icon: Icons.qr_code_scanner, - label: i18n.sendScanButton, + label: i18n.shared.sendScanButton, onTap: onScan, ), ], @@ -577,12 +584,12 @@ class _PanelFooter extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: Text( - i18n.historyFilterReset, + i18n.shared.historyFilterReset, style: TextStyle(fontSize: 13, color: BrandColors.primary), ), ), ), - BrandButton(label: i18n.done, onPressed: onDone, expand: false, dense: true), + BrandButton(label: i18n.shared.done, onPressed: onDone, expand: false, dense: true), ], ), ); diff --git a/lib/screens/lws_details.dart b/lib/screens/lws_details.dart index cf3c4b2..8ba8654 100644 --- a/lib/screens/lws_details.dart +++ b/lib/screens/lws_details.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/util/platform.dart'; import 'package:skylight_wallet/util/secure_clipboard.dart'; import 'package:skylight_wallet/util/secure_screen.dart'; @@ -34,7 +35,7 @@ class _LwsDetailsScreenState extends State with SecureScreenMi if (value.isEmpty) return; SecureClipboard.copy(value); final i18n = AppLocalizations.of(context)!; - showCopyToast(context, i18n.copiedToClipboard); + showCopyToast(context, i18n.shared.copiedToClipboard); } @override @@ -45,12 +46,12 @@ class _LwsDetailsScreenState extends State with SecureScreenMi final labels = LwsKeysLabels( title: i18n.lwsDetailsTitle, - description: i18n.lwsDetailsDescription, + description: i18n.shared.lwsDetailsDescription, primaryAddressLabel: i18n.lwsDetailsPrimaryAddressLabel, viewKeyLabel: i18n.lwsDetailsSecretViewKeyLabel, restoreHeightLabel: i18n.lwsDetailsRestoreHeightLabel, - reveal: i18n.generateSeedReveal, - warning: i18n.lwsKeysWarning, + reveal: i18n.shared.generateSeedReveal, + warning: i18n.shared.lwsKeysWarning, ); void goHome() => @@ -63,12 +64,12 @@ class _LwsDetailsScreenState extends State with SecureScreenMi showSteps: false, logo: SvgPicture.asset('assets/logo_nobg.svg', height: 52), title: i18n.lwsDetailsTitle, - description: i18n.lwsDetailsDescription, + description: i18n.shared.lwsDetailsDescription, step: 0, totalSteps: 0, - continueLabel: i18n.continueText, + continueLabel: i18n.shared.continueText, onContinue: goHome, - notes: [OnboardingNote(Icons.visibility_off_outlined, i18n.lwsKeysWarning)], + notes: [OnboardingNote(Icons.visibility_off_outlined, i18n.shared.lwsKeysWarning)], content: LwsKeysView( fieldsOnly: true, labels: labels, @@ -87,7 +88,7 @@ class _LwsDetailsScreenState extends State with SecureScreenMi secretViewKey: _secretViewKey, restoreHeight: restoreHeight.toString(), onCopy: _copy, - footer: BrandButton(label: i18n.continueText, onPressed: goHome), + footer: BrandButton(label: i18n.shared.continueText, onPressed: goHome), ); } } diff --git a/lib/screens/lws_keys.dart b/lib/screens/lws_keys.dart index 2304a14..3db548f 100644 --- a/lib/screens/lws_keys.dart +++ b/lib/screens/lws_keys.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/util/secure_clipboard.dart'; import 'package:skylight_wallet/util/secure_screen.dart'; import 'package:skylight_wallet/wallet_core_glue.dart'; @@ -11,7 +12,7 @@ import 'package:wallet_fhse/security_keys_ui.dart' show passAppLock; /// passes ([passAppLock]): a sheet is not a route, so the `/lws_keys` /// AppLockGate does not cover it. Mobile keeps that route. Future showLwsKeysSheet(BuildContext context) async { - final reason = AppLocalizations.of(context)!.settingsAppLockUnlockReason; + final reason = AppLocalizations.of(context)!.shared.unlockWalletReason; if (!await passAppLock(context, reason: reason) || !context.mounted) return; return showBrandSheet( context: context, @@ -57,7 +58,7 @@ class _LwsKeysScreenState extends State with SecureScreenMixin { if (value.isEmpty) return; SecureClipboard.copy(value); final i18n = AppLocalizations.of(context)!; - showCopyToast(context, i18n.copiedToClipboard); + showCopyToast(context, i18n.shared.copiedToClipboard); } @override @@ -67,13 +68,13 @@ class _LwsKeysScreenState extends State with SecureScreenMixin { return LwsKeysView( labels: LwsKeysLabels( - title: i18n.lwsKeysTitle, - description: i18n.lwsDetailsDescription, - primaryAddressLabel: i18n.lwsKeysPrimaryAddress, - viewKeyLabel: i18n.lwsKeysSecretViewKey, - restoreHeightLabel: i18n.lwsKeysRestoreHeight, - reveal: i18n.generateSeedReveal, - warning: i18n.lwsKeysWarning, + title: i18n.shared.lwsKeysTitle, + description: i18n.shared.lwsDetailsDescription, + primaryAddressLabel: i18n.shared.lwsKeysPrimaryAddress, + viewKeyLabel: i18n.shared.lwsKeysSecretViewKey, + restoreHeightLabel: i18n.shared.lwsKeysRestoreHeight, + reveal: i18n.shared.generateSeedReveal, + warning: i18n.shared.lwsKeysWarning, ), primaryAddress: primaryAddress, secretViewKey: _secretViewKey, diff --git a/lib/screens/privacy_policy.dart b/lib/screens/privacy_policy.dart deleted file mode 100644 index 903c5ca..0000000 --- a/lib/screens/privacy_policy.dart +++ /dev/null @@ -1,177 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:skylight_wallet/l10n/app_localizations.dart'; -import 'package:skylight_wallet/screens/desktop/home_shell.dart'; -import 'package:skylight_wallet/util/platform.dart'; -import 'package:skylight_wallet/widgets/ui/ui.dart'; - -class BulletList extends StatelessWidget { - final List items; - const BulletList({super.key, required this.items}); - - Widget _buildBullet(String text) { - return Padding( - padding: EdgeInsets.symmetric(vertical: 4.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(top: 6.0, right: 8.0), - child: Icon(Icons.circle, size: 8), - ), - Expanded(child: Text(text, style: TextStyle(fontSize: 14))), - ], - ), - ); - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.only(bottom: 10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: items.map(_buildBullet).toList(), - ), - ); - } -} - -class PrivacyPolicy extends StatelessWidget { - const PrivacyPolicy({super.key}); - - TextStyle get headingStyle => TextStyle(fontSize: 20, fontWeight: FontWeight.bold); - - TextStyle get subheadingStyle => TextStyle(fontSize: 16, fontWeight: FontWeight.w600); - - TextStyle get bodyStyle => TextStyle(fontSize: 14, height: 1.4); - - TextStyle get monospaceStyle => TextStyle(fontSize: 13, fontFamily: 'Ubuntu Mono', height: 1.4); - - Widget sectionHeading(String text) => Padding( - padding: EdgeInsets.only(top: 18.0, bottom: 6.0), - child: Text(text, style: subheadingStyle), - ); - - Widget paragraph(String text) => Padding( - padding: EdgeInsets.only(bottom: 10.0), - child: Text(text, style: bodyStyle), - ); - - @override - Widget build(BuildContext context) { - final i18n = AppLocalizations.of(context)!; - final scroll = SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Updated 2026-09-03', style: bodyStyle), - SizedBox(height: 32), - - paragraph( - 'MAGIC Grants (“we”) respect your privacy and are committed to protecting it through our compliance with this policy.', - ), - paragraph( - 'This policy applies to information we collect through your direct use of the Skylight Wallet and/or Spice Wallet app (the "App"). It does not apply to information we collect by any other means. This policy is subject to change, so make sure to periodically review this policy.', - ), - - sectionHeading('Information We Do Not Collect'), - paragraph( - 'We do not collect any usage data. The App does not send any private keys, including private spend keys, mnemonic seeds, and private view keys to MAGIC Grants. The App does not connect to any servers run by MAGIC Grants.', - ), - paragraph('You do not need to make an account with MAGIC Grants to use the app.'), - paragraph( - 'MAGIC Grants may be able to review information provided by the App Store that you are using, if you download this app from an App Store.', - ), - - sectionHeading('Information You May Share with Third Parties'), - paragraph( - 'When using the App, you need to specify a server (or servers) to connect to. The information shared with a server will depend on the cryptocurrency network that you are configuring and connecting to, but it could include sensitive information including your cryptocurrency address(es), your private view key(s), and your transaction data. This is necessary for the core functionality of the App.', - ), - paragraph( - 'You can choose to only connect to the cryptocurrency networks that you choose. It is not necessary to connect to any particular cryptocurrency network to use the App.', - ), - paragraph( - 'MAGIC Grants does not provide default servers. You must research the servers that you want to use and any terms of service or privacy policies associated with those servers before you connect to those servers.', - ), - paragraph( - 'In many cases, you may choose to avoid using a third party by connecting to servers that you operate. For example, instead of connecting to a node operated by a third party, you can connect to a node operated by you.', - ), - - sectionHeading('Other Connections to Third Parties'), - paragraph( - 'The App optionally connects to services provided by third parties for your convenience. These include:', - ), - BulletList( - items: [ - 'Block explorers, to display network and transaction information.', - 'Nodes, to display network and transaction information.', - 'Price quotes, to display richer information about your transactions.', - ], - ), - paragraph( - 'Within the App, connections to these services can be configured to be routed over the Tor network, which helps improve your privacy. However, MAGIC Grants does not warrant that these connections are bug-free, or that these protections are fit for a particular purpose. Please be mindful of the third party connections that you make when using the App. The App may contain links to websites that, when clicked, open outside of Tor in your default web browser.', - ), - paragraph( - 'Some third party connections, such as the price quotes, can be disabled in App settings.', - ), - paragraph( - 'When you connect to third parties, you must abide by their terms of service and privacy policies.', - ), - - sectionHeading('When You Contact Us'), - paragraph( - 'There is no mechanism for contacting us within the App. If you contact us through a different means, then we may learn and retain information about you, such as your email address, account identifier, and the contents of your communications.', - ), - paragraph( - 'Before sending any app diagnostic logs, make sure to review their contents for any potentially sensitive information. Remove this information before sending any information to us.', - ), - ], - ), - ); - - final inShell = ModalRoute.of(context)?.settings.arguments == true; - final header = isDesktop - ? Padding( - padding: const EdgeInsets.fromLTRB(16, 10, 16, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - DesktopBackLink( - label: inShell ? i18n.navigationBarSettings : i18n.back, - onTap: () => Navigator.pop(context), - ), - const SizedBox(height: 12), - Text(i18n.welcomePrivacyLink, style: desktopTitleStyle), - ], - ), - ) - : Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), - child: BrandScreenHeader( - onBack: () => Navigator.pop(context), - center: Text(i18n.welcomePrivacyLink, style: BrandText.appBar.copyWith(fontSize: 16)), - ), - ); - final body = SafeArea( - child: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 520), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - header, - Expanded(child: scroll), - ], - ), - ), - ), - ); - - if (isDesktop && inShell) { - return DesktopShell(active: DesktopNav.settings, child: body); - } - return Scaffold(backgroundColor: BrandColors.paper, body: body); - } -} diff --git a/lib/screens/receive.dart b/lib/screens/receive.dart index be5f4de..c9915ae 100644 --- a/lib/screens/receive.dart +++ b/lib/screens/receive.dart @@ -5,6 +5,7 @@ import 'package:share_plus/share_plus.dart'; import 'package:screen_brightness/screen_brightness.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/app_wallet.dart'; import 'package:skylight_wallet/screens/desktop/home_shell.dart'; import 'package:skylight_wallet/screens/desktop/receive_view.dart'; @@ -77,7 +78,7 @@ class _ReceiveScreenState extends State { void _copyAddressToClipboard(String address) { final i18n = AppLocalizations.of(context)!; SecureClipboard.copy(address); - showCopyToast(context, i18n.addressCopied); + showCopyToast(context, i18n.shared.addressCopied); } /// Opens the system share sheet on [address], anchored at [origin]. @@ -93,7 +94,7 @@ class _ReceiveScreenState extends State { await SharePlus.instance.share(ShareParams(text: address, sharePositionOrigin: origin)); } catch (error) { log(LogLevel.error, 'Address share failed (origin=${origin ?? 'none'}): $error'); - toast.show(i18n.receiveShareError); + toast.show(i18n.shared.receiveShareError); } } @@ -123,21 +124,23 @@ class _ReceiveScreenState extends State { final ready = address != null; final warning = _warning(i18n, wallet, isDemoMode, subSupported, sub); final qrHeading = canToggle && _showSubaddress - ? '${i18n.receiveSubaddressTab} #${sub.index}' - : i18n.receiveAddressHeading('Monero'); + ? '${i18n.shared.receiveSubaddressTab} #${sub.index}' + : i18n.shared.receiveAddressHeading('Monero'); if (isDesktop) { return DesktopShell( active: DesktopNav.home, child: DesktopReceiveView( - title: i18n.receiveTitle, + title: i18n.shared.receiveTitle, backLabel: i18n.navigationBarWallet, - copyLabel: i18n.receiveCopyAddress, + copyLabel: i18n.shared.receiveCopyAddress, qrHint: i18n.receiveQrHint, ready: ready, address: address ?? '', qrHeading: ready ? qrHeading : '', - tabLabels: canToggle ? [i18n.receiveSubaddressTab, i18n.receivePrimaryTab] : null, + tabLabels: canToggle + ? [i18n.shared.receiveSubaddressTab, i18n.shared.receivePrimaryTab] + : null, selectedTab: _showSubaddress ? 0 : 1, onSelectTab: (index) => setState(() => _showSubaddress = index == 0), warning: warning, @@ -149,10 +152,10 @@ class _ReceiveScreenState extends State { return ReceiveView( labels: ReceiveLabels( - title: i18n.receiveTitle, - copyAddress: i18n.receiveCopyAddress, - enlargeQr: i18n.receiveEnlargeQr, - shrinkQr: i18n.receiveShrinkQr, + title: i18n.shared.receiveTitle, + copyAddress: i18n.shared.receiveCopyAddress, + enlargeQr: i18n.shared.receiveEnlargeQr, + shrinkQr: i18n.shared.receiveShrinkQr, ), onBack: () => Navigator.of(context).pop(), onShare: _isMobile ? (origin) => _share(address!, origin) : null, @@ -162,7 +165,9 @@ class _ReceiveScreenState extends State { iconAsset: 'assets/icons/monero.svg', coinName: null, blockchainSubtitle: null, - tabLabels: canToggle ? [i18n.receiveSubaddressTab, i18n.receivePrimaryTab] : null, + tabLabels: canToggle + ? [i18n.shared.receiveSubaddressTab, i18n.shared.receivePrimaryTab] + : null, selectedTab: _showSubaddress ? 0 : 1, onSelectTab: (index) => setState(() => _showSubaddress = index == 0), address: address ?? '', @@ -184,11 +189,11 @@ class _ReceiveScreenState extends State { ({int index, String address})? sub, ) { if (isDemoMode) return null; - if (subSupported == false) return i18n.receiveServerNoSubaddressesWarn; - if (sub == null) return i18n.receiveSubaddressUnavailableWarn; - if (!_showSubaddress) return i18n.receivePrimaryAddressWarn; + if (subSupported == false) return i18n.shared.receiveServerNoSubaddressesWarn; + if (sub == null) return i18n.shared.receiveSubaddressUnavailableWarn; + if (!_showSubaddress) return i18n.shared.receivePrimaryAddressWarn; if (wallet.unusedSubaddressIndexIsSupported == false) { - return i18n.receiveMaxSubaddressesReachedWarn; + return i18n.shared.receiveMaxSubaddressesReachedWarn; } return null; } diff --git a/lib/screens/restore_wallet.dart b/lib/screens/restore_wallet.dart index edd4529..8286741 100644 --- a/lib/screens/restore_wallet.dart +++ b/lib/screens/restore_wallet.dart @@ -8,6 +8,7 @@ import 'package:polyseed/polyseed.dart'; import 'package:provider/provider.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/screens/create_wallet_password.dart'; import 'package:skylight_wallet/screens/desktop/onboarding_steps.dart'; @@ -88,12 +89,12 @@ class _RestoreWalletScreenState extends State with SecureSc initial: _restoreDate, chosen: _scanChosen, labels: ScanFromSheetLabels( - title: i18n.restoreScanTitle, + title: i18n.shared.restoreScanTitle, description: i18n.restoreScanDescription, - pickMonth: i18n.restoreScanPickMonth, - notSure: i18n.restoreScanNotSure, - notSureDesc: i18n.restoreScanNotSureDesc, - done: i18n.restoreScanDone, + pickMonth: i18n.shared.restoreScanPickMonth, + notSure: i18n.shared.restoreScanNotSure, + notSureDesc: i18n.shared.restoreScanNotSureDesc, + done: i18n.shared.restoreScanDone, locale: Localizations.localeOf(context).toString(), ), ); @@ -159,14 +160,16 @@ class _RestoreWalletScreenState extends State with SecureSc final errorMsg = error.toString().replaceFirst('Exception: ', ''); showBrandToast( context, - errorMsg == 'Invalid mnemonic.' ? i18n.restoreWalletInvalidMnemonic : i18n.unknownError, + errorMsg == 'Invalid mnemonic.' + ? i18n.restoreWalletInvalidMnemonic + : i18n.shared.unknownError, ); return; } catch (error) { if (!mounted) return; log(LogLevel.error, error.toString()); setState(() => _committing = false); - showBrandToast(context, i18n.unknownError); + showBrandToast(context, i18n.shared.unknownError); return; } if (!mounted) return; @@ -193,13 +196,13 @@ class _RestoreWalletScreenState extends State with SecureSc final String scanValue; final TextStyle scanStyle; if (!_scanChosen) { - scanValue = i18n.restoreWalletNotSet; + scanValue = i18n.shared.restoreWalletNotSet; scanStyle = BrandText.body.copyWith(color: BrandColors.inkFaint); } else if (_restoreDate != null) { scanValue = DateFormat.yMMM(Localizations.localeOf(context).toString()).format(_restoreDate!); scanStyle = BrandText.amount; } else { - scanValue = i18n.restoreScanFromStart; + scanValue = i18n.shared.restoreScanFromStart; scanStyle = BrandText.body; } @@ -219,13 +222,13 @@ class _RestoreWalletScreenState extends State with SecureSc // carries its own birthday to derive the restore height from. canRestore: () => _scanChosen || _seedTypeId == 'polyseed', labels: RestoreWalletLabels( - title: i18n.restoreWalletTitle, + title: i18n.shared.restoreWalletTitle, subtitle: i18n.restoreWalletDescription, - seedLength: i18n.restoreWalletSeedLength, - paste: i18n.restoreWalletPaste, - restoreButton: i18n.restoreWalletRestoreButton, - badWord: (position) => i18n.restoreWalletBadWord(position), - didYouMean: (word) => i18n.restoreWalletDidYouMean(word), + seedLength: i18n.shared.restoreWalletSeedLength, + paste: i18n.shared.restoreWalletPaste, + restoreButton: i18n.shared.restoreWalletRestoreButton, + badWord: (position) => i18n.shared.restoreWalletBadWord(position), + didYouMean: (word) => i18n.shared.restoreWalletDidYouMean(word), ), seedTypes: [ SeedTypeOption( @@ -240,13 +243,13 @@ class _RestoreWalletScreenState extends State with SecureSc defaultLength: 12, isValidWord: _validWord, mnemonicError: (mnemonic) => - bip39.validateMnemonic(mnemonic) ? null : i18n.restoreWalletChecksumError, + bip39.validateMnemonic(mnemonic) ? null : i18n.shared.restoreWalletChecksumError, ), SeedTypeOption(id: 'legacy', label: i18n.restoreWalletSeedTypeLegacy, fixedWordCount: 25), ], restorePointFields: ScanFromCard( - label: i18n.restoreWalletScanFrom, - reason: i18n.restoreWalletScanFromReason, + label: i18n.shared.restoreWalletScanFrom, + reason: i18n.shared.restoreWalletScanFromReason, value: scanValue, valueStyle: scanStyle, onTap: _openScanFrom, @@ -261,11 +264,11 @@ class _RestoreWalletScreenState extends State with SecureSc valueListenable: _restoreWalletController.canRestore, builder: (context, valid, _) => DesktopOnboardingScaffold( logo: SvgPicture.asset('assets/logo_nobg.svg', height: 52), - title: i18n.restoreWalletTitle, + title: i18n.shared.restoreWalletTitle, description: i18n.restoreWalletDescription, step: 5, totalSteps: desktopOnboardingSteps, - continueLabel: i18n.restoreWalletRestoreButton, + continueLabel: i18n.shared.restoreWalletRestoreButton, continueEnabled: valid, loading: _committing, onBack: () => Navigator.pop(context), diff --git a/lib/screens/scan_qr.dart b/lib/screens/scan_qr.dart index 2c0cccf..f6e4ead 100644 --- a/lib/screens/scan_qr.dart +++ b/lib/screens/scan_qr.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart'; /// Optional arguments for the scan route. When [accept] is given, the scanner @@ -22,7 +23,7 @@ class ScanQrScreen extends StatelessWidget { final args = ModalRoute.of(context)?.settings.arguments as ScanQrArgs?; return ScanQrView( - title: i18n.scanQrTitle, + title: i18n.shared.scanQrTitle, accept: args?.accept, invalidMessage: args?.invalidMessage, onResult: (text) => Navigator.pop(context, text), diff --git a/lib/screens/secret_keys.dart b/lib/screens/secret_keys.dart index 96dce29..1bd8cd1 100644 --- a/lib/screens/secret_keys.dart +++ b/lib/screens/secret_keys.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/util/secure_clipboard.dart'; import 'package:skylight_wallet/util/secure_screen.dart'; import 'package:skylight_wallet/wallet_core_glue.dart'; @@ -11,7 +12,7 @@ import 'package:wallet_fhse/security_keys_ui.dart' show passAppLock; /// Lock passes ([passAppLock]): a sheet is not a route, so the `/secret_keys` /// AppLockGate does not cover it. Mobile keeps that route. Future showSecretKeysSheet(BuildContext context) async { - final reason = AppLocalizations.of(context)!.revealSeedAuthReason; + final reason = AppLocalizations.of(context)!.shared.revealSeedAuthReason; if (!await passAppLock(context, reason: reason) || !context.mounted) return; return showBrandSheet( context: context, @@ -77,7 +78,7 @@ class _SecretKeysScreenState extends State with SecureScreenMi if (value.isEmpty) return; SecureClipboard.copy(value); final i18n = AppLocalizations.of(context)!; - showCopyToast(context, i18n.copiedToClipboard); + showCopyToast(context, i18n.shared.copiedToClipboard); } @override @@ -103,7 +104,7 @@ class _SecretKeysScreenState extends State with SecureScreenMi title: i18n.secretKeysTitle, description: i18n.secretKeysDescription, warning: i18n.secretKeysWarning, - revealLabel: i18n.generateSeedReveal, + revealLabel: i18n.shared.generateSeedReveal, onCopy: _copy, asModal: widget.asModal, onBack: widget.asModal ? null : () => Navigator.pop(context), diff --git a/lib/screens/send.dart b/lib/screens/send.dart index 15f5024..c739d99 100644 --- a/lib/screens/send.dart +++ b/lib/screens/send.dart @@ -16,6 +16,7 @@ import 'package:wallet_infra/wallet_infra.dart' show RequestNotSentException; import 'package:wallet_infra/wallet_infra.dart' show StoreReview; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/app_wallet.dart'; import 'package:skylight_wallet/models/contact_model.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; @@ -175,7 +176,7 @@ class _SendScreenState extends State { final result = await Navigator.pushNamed( context, '/scan_qr', - arguments: ScanQrArgs(accept: accept, invalidMessage: i18n.scanQrUnexpectedCode), + arguments: ScanQrArgs(accept: accept, invalidMessage: i18n.shared.scanQrUnexpectedCode), ); if (result == null || result is! String) return; @@ -217,11 +218,11 @@ class _SendScreenState extends State { final contact = await showContactPickerSheet( context: context, labels: ContactPickerLabels( - title: i18n.sendPickContactTitle, - searchHint: i18n.addressBookSearchHint, - cancel: i18n.cancel, - noContacts: i18n.addressBookNoContacts, - noResults: i18n.addressBookNoSearchResults, + title: i18n.shared.sendPickContactTitle, + searchHint: i18n.shared.addressBookSearchHint, + cancel: i18n.shared.cancel, + noContacts: i18n.shared.addressBookNoContacts, + noResults: i18n.shared.addressBookNoSearchResults, ), headerIcon: SheetIcon( icon: Icons.people, @@ -334,8 +335,8 @@ class _SendScreenState extends State { if (setErrors) { setState(() { _destinationAddressError = _openAliasOff - ? i18n.sendOpenAliasDisabledError - : i18n.sendOpenAliasResolveError; + ? i18n.shared.sendOpenAliasDisabledError + : i18n.shared.sendOpenAliasResolveError; }); } return false; @@ -352,7 +353,7 @@ class _SendScreenState extends State { if (_amount.baseUnits > (wallet.unlockedBalanceBaseUnits ?? BigInt.zero)) { if (setErrors) { setState(() { - _amountError = i18n.sendInsufficientBalanceError; + _amountError = i18n.shared.sendInsufficientBalanceError; }); return false; } @@ -432,7 +433,7 @@ class _SendScreenState extends State { _isLoadingFees = false; }); - showBrandToast(context, i18n.sendFailedToGetFeesError); + showBrandToast(context, i18n.shared.sendFailedToGetFeesError); } } } @@ -474,8 +475,8 @@ class _SendScreenState extends State { setState(() { _isLoading = false; _destinationAddressError = _openAliasOff - ? i18n.sendOpenAliasDisabledError - : i18n.sendOpenAliasResolveError; + ? i18n.shared.sendOpenAliasDisabledError + : i18n.shared.sendOpenAliasResolveError; }); return; } @@ -520,16 +521,16 @@ class _SendScreenState extends State { final approxAmount = double.tryParse(_amount.coinText) ?? 0; if (wallet.unlockedBalance! > approxAmount) { setState(() { - _amountError = i18n.sendInsufficientBalanceToCoverFeeError; + _amountError = i18n.shared.sendInsufficientBalanceToCoverFeeError; }); } else { setState(() { - _amountError = i18n.sendInsufficientBalanceError; + _amountError = i18n.shared.sendInsufficientBalanceError; }); } } else { if (mounted) { - showBrandToast(context, i18n.unknownError); + showBrandToast(context, i18n.shared.unknownError); } } } @@ -567,13 +568,13 @@ class _SendScreenState extends State { final committed = await showConfirmSendSheet( context: context, labels: ConfirmSendLabels( - title: i18n.confirmSendTitle, - description: i18n.confirmSendDescription, - amount: i18n.amount, - networkFee: i18n.networkFee, - address: i18n.address, - send: i18n.sendSendButton, - cancel: i18n.cancel, + title: i18n.shared.confirmSendTitle, + description: i18n.shared.confirmSendDescription, + amount: i18n.shared.amount, + networkFee: i18n.shared.networkFee, + address: i18n.shared.address, + send: i18n.shared.sendSendButton, + cancel: i18n.shared.cancel, ), coinSymbol: 'XMR', amountText: '${tx.amount.toStringAsFixed(12)} XMR', @@ -586,7 +587,9 @@ class _SendScreenState extends State { contactName: contactName, contactAddress: contactAddress, showHighFeeWarning: showHighFeeWarning, - highFeeWarning: showHighFeeWarning ? i18n.confirmSendHighFeeWarning(_highFeeToken) : null, + highFeeWarning: showHighFeeWarning + ? i18n.shared.confirmSendHighFeeWarning(_highFeeToken) + : null, highFeeToken: _highFeeToken, highFeePercent: showHighFeeWarning ? '${(feeRatio * 100).round()}%' : null, onConfirm: () => _commitTx( @@ -639,16 +642,16 @@ class _SendScreenState extends State { color: BrandColors.warning, ), const SizedBox(width: 11), - Expanded(child: Text(i18n.warning, style: BrandText.sheetTitle)), + Expanded(child: Text(i18n.shared.warning, style: BrandText.sheetTitle)), ], ), const SizedBox(height: 7), Text( - i18n.txDetailsUnknownStatus, + i18n.shared.txDetailsUnknownStatus, style: BrandText.bodyMuted.copyWith(fontSize: 13, height: 1.5), ), const SizedBox(height: 18), - BrandButton(label: i18n.close, onPressed: () => Navigator.pop(sheetContext)), + BrandButton(label: i18n.shared.close, onPressed: () => Navigator.pop(sheetContext)), ], ), ), @@ -675,11 +678,11 @@ class _SendScreenState extends State { onUnresolved(); return; } - if (mounted) showBrandToast(context, i18n.unknownError); + if (mounted) showBrandToast(context, i18n.shared.unknownError); rethrow; } on RequestNotSentException { // Nothing was sent: show a connectivity error and close the sheet. - if (mounted) showBrandToast(context, i18n.sendNoConnectionError); + if (mounted) showBrandToast(context, i18n.shared.sendNoConnectionError); onConnectionFailed(); return; } on FormatException catch (error) { @@ -694,7 +697,7 @@ class _SendScreenState extends State { } catch (error) { log(LogLevel.error, error.toString()); if (mounted) { - showBrandToast(context, i18n.unknownError); + showBrandToast(context, i18n.shared.unknownError); } rethrow; } @@ -789,20 +792,24 @@ class _SendScreenState extends State { return SendView( labels: SendLabels( - title: i18n.sendTitle, - toLabel: i18n.sendToLabel, - amount: i18n.amount, - priorityHeading: i18n.sendPriorityHeading, - networkFee: i18n.sendNetworkFee, - sendButton: i18n.sendSendButton, - cancel: i18n.cancel, - pasteButton: i18n.sendPasteButton, - scanButton: i18n.sendScanButton, - contactsButton: i18n.sendContactsButton, - maxButton: i18n.sendMaxButton, - addressHint: i18n.address, - priorityLabels: [i18n.sendPriorityLow, i18n.sendPriorityNormal, i18n.sendPriorityHigh], - switchUnit: i18n.sendSwitchUnit, + title: i18n.shared.sendTitle, + toLabel: i18n.shared.sendToLabel, + amount: i18n.shared.amount, + priorityHeading: i18n.shared.sendPriorityHeading, + networkFee: i18n.shared.sendNetworkFee, + sendButton: i18n.shared.sendSendButton, + cancel: i18n.shared.cancel, + pasteButton: i18n.shared.sendPasteButton, + scanButton: i18n.shared.sendScanButton, + contactsButton: i18n.shared.sendContactsButton, + maxButton: i18n.shared.sendMaxButton, + addressHint: i18n.shared.address, + priorityLabels: [ + i18n.shared.sendPriorityLow, + i18n.shared.sendPriorityNormal, + i18n.shared.sendPriorityHigh, + ], + switchUnit: i18n.shared.sendSwitchUnit, ), onBack: () => Navigator.of(context).pop(), addressController: _destinationAddressController, @@ -819,7 +826,7 @@ class _SendScreenState extends State { amount: _amount, amountError: _amountError, onMax: _setBalanceAsSendAmount, - availableText: '${_amountText(available)} ${i18n.sendAvailableSuffix}', + availableText: '${_amountText(available)} ${i18n.shared.sendAvailableSuffix}', availableLeading: xmr != null ? CoinMark(coinSymbol: xmr.coinSymbol, iconAsset: xmr.iconAsset, size: 16) : const SizedBox(width: 16, height: 16), @@ -850,7 +857,7 @@ class _SendScreenState extends State { onTap: () => Navigator.pop(context), ), const SizedBox(height: 16), - Text(i18n.sendTitle, style: desktopTitleStyle), + Text(i18n.shared.sendTitle, style: desktopTitleStyle), const SizedBox(height: 24), _sendForm(context, i18n, quote, available, canSend), ], @@ -867,10 +874,10 @@ class _SendScreenState extends State { final formColumn = Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - _dField(i18n.sendToLabel, _dToCard(i18n)), + _dField(i18n.shared.sendToLabel, _dToCard(i18n)), if (_destinationAddressError.isNotEmpty) _dError(_destinationAddressError), const SizedBox(height: 16), - _dField(i18n.amount, _dAmountCard(i18n, available)), + _dField(i18n.shared.amount, _dAmountCard(i18n, available)), if (_amountError.isNotEmpty) _dError(_amountError), ], ); @@ -878,12 +885,16 @@ class _SendScreenState extends State { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _dField( - i18n.sendPriorityHeading, + i18n.shared.sendPriorityHeading, Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ BrandSegmented( - labels: [i18n.sendPriorityLow, i18n.sendPriorityNormal, i18n.sendPriorityHigh], + labels: [ + i18n.shared.sendPriorityLow, + i18n.shared.sendPriorityNormal, + i18n.shared.sendPriorityHigh, + ], selectedIndex: _selectedPriority, onSelect: _setPriority, ), @@ -892,7 +903,7 @@ class _SendScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - i18n.sendNetworkFee, + i18n.shared.sendNetworkFee, style: TextStyle( fontFamily: 'Ubuntu', fontSize: 12.5, @@ -907,12 +918,12 @@ class _SendScreenState extends State { ), const SizedBox(height: 18), BrandButton( - label: i18n.sendSendButton, + label: i18n.shared.sendSendButton, loading: _isLoading, onPressed: canSend ? _send : null, ), const SizedBox(height: 9), - BrandButton.ghost(label: i18n.cancel, onPressed: () => Navigator.pop(context)), + BrandButton.ghost(label: i18n.shared.cancel, onPressed: () => Navigator.pop(context)), const SizedBox(height: 16), Text( i18n.sendIrreversibleNote, @@ -1028,7 +1039,7 @@ class _SendScreenState extends State { color: BrandColors.ink, ), decoration: InputDecoration.collapsed( - hintText: i18n.address, + hintText: i18n.shared.address, hintStyle: TextStyle( fontFamily: 'Ubuntu Mono', fontSize: 13.5, @@ -1040,9 +1051,9 @@ class _SendScreenState extends State { const SizedBox(height: 14), Row( children: [ - _dPill(Icons.content_paste, i18n.sendPasteButton, _pasteAddressFromClipboard), + _dPill(Icons.content_paste, i18n.shared.sendPasteButton, _pasteAddressFromClipboard), const SizedBox(width: 8), - _dPill(Icons.people_outline, i18n.sendContactsButton, _showContactPicker), + _dPill(Icons.people_outline, i18n.shared.sendContactsButton, _showContactPicker), ], ), ], @@ -1056,10 +1067,10 @@ class _SendScreenState extends State { listenable: _amount, builder: (_, _) => SendAmountCard( amount: _amount, - maxLabel: i18n.sendMaxButton, - switchUnitLabel: i18n.sendSwitchUnit, + maxLabel: i18n.shared.sendMaxButton, + switchUnitLabel: i18n.shared.sendSwitchUnit, onMax: _setBalanceAsSendAmount, - availableText: '${_amountText(available)} ${i18n.sendAvailableSuffix}', + availableText: '${_amountText(available)} ${i18n.shared.sendAvailableSuffix}', onAvailableTap: _setBalanceAsSendAmount, ), ), diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index c313ae4..b33f44b 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -5,6 +5,7 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/app_wallet.dart'; import 'package:skylight_wallet/models/contact_model.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; @@ -177,7 +178,7 @@ class _SettingsScreenState extends State { if (logFiles.isEmpty) { if (mounted) { - showBrandToast(context, i18n.settingsExportLogsError); + showBrandToast(context, i18n.shared.settingsExportLogsError); } return; } @@ -187,9 +188,9 @@ class _SettingsScreenState extends State { context, logFiles, ExportLogsLabels( - title: i18n.settingsExportLogsLabel, - cancel: i18n.cancel, - exportError: i18n.settingsExportLogsFailed, + title: i18n.shared.settingsExportLogsLabel, + cancel: i18n.shared.cancel, + exportError: i18n.shared.settingsExportLogsFailed, ), ); } @@ -197,7 +198,7 @@ class _SettingsScreenState extends State { // Not "no logs found": the listing itself failed, which is a different // thing to tell the user than an empty list. if (mounted) { - showBrandToast(context, i18n.settingsExportLogsFailed); + showBrandToast(context, i18n.shared.settingsExportLogsFailed); } } } @@ -209,10 +210,10 @@ class _SettingsScreenState extends State { icon: Icons.delete_outline, iconBg: BrandColors.errorBg, iconColor: BrandColors.error, - title: i18n.settingsDeleteWalletButton, - body: i18n.settingsDeleteWalletDialogText, - confirmLabel: i18n.settingsDeleteWalletDialogDeleteButton, - cancelLabel: i18n.cancel, + title: i18n.shared.settingsDeleteWalletButton, + body: i18n.shared.settingsDeleteWalletDialogText, + confirmLabel: i18n.shared.settingsDeleteWalletDialogDeleteButton, + cancelLabel: i18n.shared.cancel, onConfirm: _deleteWallet, ); } @@ -224,10 +225,10 @@ class _SettingsScreenState extends State { icon: Icons.delete_outline, iconBg: BrandColors.errorBg, iconColor: BrandColors.error, - title: i18n.settingsDeleteAddressBookButton, - body: i18n.settingsDeleteAddressBookDialogText, - confirmLabel: i18n.settingsDeleteAddressBookDialogDeleteButton, - cancelLabel: i18n.cancel, + title: i18n.shared.settingsDeleteAddressBookButton, + body: i18n.shared.settingsDeleteAddressBookDialogText, + confirmLabel: i18n.shared.settingsDeleteAddressBookDialogDeleteButton, + cancelLabel: i18n.shared.cancel, onConfirm: _deleteAddressBook, ); } @@ -238,7 +239,7 @@ class _SettingsScreenState extends State { Future _deleteAddressBook() async { final i18n = AppLocalizations.of(context)!; await Provider.of(context, listen: false).deleteAll(); - if (mounted) showBrandToast(context, i18n.settingsAddressBookDeleted); + if (mounted) showBrandToast(context, i18n.shared.settingsAddressBookDeleted); } Future _showViewLwsKeysDialog() async { @@ -248,10 +249,10 @@ class _SettingsScreenState extends State { icon: Icons.warning_amber_rounded, iconBg: BrandColors.warningBg, iconColor: BrandColors.warning, - title: i18n.warning, + title: i18n.shared.warning, body: i18n.settingsViewLwsKeysDialogText, confirmLabel: i18n.settingsViewLwsKeysDialogRevealButton, - cancelLabel: i18n.cancel, + cancelLabel: i18n.shared.cancel, confirmColor: BrandColors.warning, ); if (ok != true || !mounted) return; @@ -275,10 +276,10 @@ class _SettingsScreenState extends State { icon: Icons.warning_amber_rounded, iconBg: BrandColors.errorBg, iconColor: BrandColors.error, - title: i18n.warning, + title: i18n.shared.warning, body: i18n.settingsViewSecretKeysDialogText, confirmLabel: i18n.settingsViewSecretKeysDialogRevealButton, - cancelLabel: i18n.cancel, + cancelLabel: i18n.shared.cancel, ); if (ok != true || !mounted) return; if (isDesktop) { @@ -321,9 +322,10 @@ class _SettingsScreenState extends State { icon: Icons.dns, title: i18n.settingsConnectionSettingsLabel, form: ConnectionSettingsForm( - saveButtonLabel: i18n.torSettingsSaveButton, + saveButtonLabel: i18n.shared.save, onSaved: onSaved, isInDialog: true, + onCancel: () => Navigator.pop(context), ), ); // The connection address subtitle follows the wallet, but the Tor mode row @@ -340,9 +342,9 @@ class _SettingsScreenState extends State { } String _openAliasModeLabel(AppLocalizations i18n) => switch (_openAliasMode) { - OpenAliasMode.torOnly => i18n.fiatApiSettingsModeTorOnly, - OpenAliasMode.clearnet => i18n.fiatApiSettingsModeClearnet, - OpenAliasMode.disabled => i18n.fiatApiSettingsModeDisabled, + OpenAliasMode.torOnly => i18n.shared.fiatApiSettingsModeTorOnly, + OpenAliasMode.clearnet => i18n.shared.clearnetOnlyChoice, + OpenAliasMode.disabled => i18n.shared.fiatApiSettingsModeDisabled, }; Future _refreshFiatMode() async { @@ -352,15 +354,15 @@ class _SettingsScreenState extends State { String _torModeLabel(AppLocalizations i18n) => switch (TorSettingsService.sharedInstance.torMode) { - TorMode.builtIn => i18n.torSettingsModeBuiltIn, - TorMode.external => i18n.torSettingsModeExternal, - TorMode.disabled => i18n.torSettingsModeDisabled, + TorMode.builtIn => i18n.shared.torSettingsModeBuiltIn, + TorMode.external => i18n.shared.torSettingsModeExternal, + TorMode.disabled => i18n.shared.torSettingsModeDisabled, }; String _fiatModeLabel(AppLocalizations i18n) => switch (_fiatMode) { - FiatApiMode.torOnly => i18n.fiatApiSettingsModeTorOnly, - FiatApiMode.clearnet => i18n.fiatApiSettingsModeClearnet, - FiatApiMode.disabled => i18n.fiatApiSettingsModeDisabled, + FiatApiMode.torOnly => i18n.shared.fiatApiSettingsModeTorOnly, + FiatApiMode.clearnet => i18n.shared.clearnetOnlyChoice, + FiatApiMode.disabled => i18n.shared.fiatApiSettingsModeDisabled, }; /// A plain-language description of the active connection, e.g. "LWS in Local @@ -380,9 +382,9 @@ class _SettingsScreenState extends State { } String _themeLabel(AppLocalizations i18n, String value) => switch (value) { - 'light' => i18n.settingsThemeLight, - 'dark' => i18n.settingsThemeDark, - _ => i18n.settingsThemeSystem, + 'light' => i18n.shared.settingsThemeLight, + 'dark' => i18n.shared.settingsThemeDark, + _ => i18n.shared.settingsThemeSystem, }; void _showThemePicker() { @@ -391,14 +393,14 @@ class _SettingsScreenState extends State { showThemePickerSheet( context, labels: SettingsPickerLabels( - title: i18n.settingsThemeLabel, - subtitle: i18n.settingsThemeSheetSubtitle, - done: i18n.done, + title: i18n.shared.settingsThemeLabel, + subtitle: i18n.shared.settingsThemeSheetSubtitle, + done: i18n.shared.done, ), options: [ ThemePickerOption( value: 'light', - label: i18n.settingsThemeLight, + label: i18n.shared.settingsThemeLight, description: i18n.settingsThemeLightDesc, swatch: const ThemeSwatchSpec( ground: Color(0xFFF2F6FA), @@ -408,8 +410,8 @@ class _SettingsScreenState extends State { ), ThemePickerOption( value: 'dark', - label: i18n.settingsThemeDark, - description: i18n.settingsThemeDarkDesc, + label: i18n.shared.settingsThemeDark, + description: i18n.shared.settingsThemeDarkDesc, swatch: const ThemeSwatchSpec( ground: Color(0xFF03090F), barColor: Color(0xFF1A2837), @@ -418,8 +420,8 @@ class _SettingsScreenState extends State { ), ThemePickerOption( value: 'system', - label: i18n.settingsThemeSystem, - description: i18n.settingsThemeSystemDesc, + label: i18n.shared.settingsThemeSystem, + description: i18n.shared.settingsThemeSystemDesc, swatch: const ThemeSwatchSpec( gradient: LinearGradient( colors: [Color(0xFFF2F6FA), Color(0xFF102A4C)], @@ -453,34 +455,34 @@ class _SettingsScreenState extends State { final groups = [ SettingsGroup( - label: i18n.settingsSectionGeneral, + label: i18n.shared.settingsSectionGeneral, tiles: [ SettingsNavTile( - title: i18n.settingsThemeLabel, + title: i18n.shared.settingsThemeLabel, value: _themeLabel(i18n, theme.theme), onTap: _showThemePicker, ), SettingsNavTile( - title: i18n.settingsLanguageLabel, + title: i18n.shared.settingsLanguageLabel, value: languageNames[language.language]?.$1 ?? language.language.toUpperCase(), onTap: () => showLanguageSheet(context), ), SettingsLinkTile( title: i18n.settingsConnectionSettingsLabel, subtitle: connectionSubtitle, - linkLabel: i18n.settingsLwsViewKeysButton, + linkLabel: i18n.shared.settingsLwsViewKeysButton, onTap: _showConnectionSettings, ), SettingsLinkTile( - title: i18n.settingsFiatApiSettingsLabel, + title: i18n.shared.settingsFiatApiSettingsLabel, subtitle: fiatSubtitle, - linkLabel: i18n.settingsLwsViewKeysButton, + linkLabel: i18n.shared.settingsLwsViewKeysButton, onTap: _showFiatApiSettings, ), ], ), SettingsGroup( - label: i18n.settingsSectionSecurityPrivacy, + label: i18n.shared.settingsSectionSecurityPrivacy, tiles: [ // Always on: how long the app may be away before it asks again. const AppLockTile(), @@ -492,31 +494,31 @@ class _SettingsScreenState extends State { onTap: _openAdvancedSecurity, ), SettingsLinkTile( - title: i18n.settingsTorSettingsLabel, + title: i18n.shared.settingsTorSettingsLabel, subtitle: _torModeLabel(i18n), - linkLabel: i18n.settingsLwsViewKeysButton, + linkLabel: i18n.shared.settingsLwsViewKeysButton, onTap: _showTorSettings, ), SettingsLinkTile( - title: i18n.settingsOpenAliasSettingsLabel, + title: i18n.shared.settingsOpenAliasSettingsLabel, subtitle: _openAliasModeLabel(i18n), - linkLabel: i18n.settingsLwsViewKeysButton, + linkLabel: i18n.shared.settingsLwsViewKeysButton, onTap: _showOpenAliasSettings, ), ], ), SettingsGroup( - label: i18n.settingsSectionData, + label: i18n.shared.settingsSectionData, tiles: [ SettingsNavTile( title: BackupLocalizations.of(context).backupTitle, onTap: () => Navigator.pushNamed(context, '/metadata_backup'), ), SettingsToggleTile( - title: i18n.settingsLoggingLabel, + title: i18n.shared.settingsLoggingLabel, description: Platform.isIOS - ? i18n.settingsLoggingDescriptionIos - : i18n.settingsLoggingDescription, + ? i18n.shared.settingsLoggingDescriptionIos + : i18n.shared.settingsLoggingDescription, value: _loggingEnabled, onChanged: _setLoggingEnabled, animate: _animateToggles, @@ -525,12 +527,12 @@ class _SettingsScreenState extends State { // logging is off rather than disabling it. if (Platform.isIOS && _loggingEnabled) SettingsLinkTile( - title: i18n.settingsExportLogsLabel, - linkLabel: i18n.settingsExportLogsButton, + title: i18n.shared.settingsExportLogsLabel, + linkLabel: i18n.shared.settingsExportLogsButton, onTap: _exportLogs, ), SettingsLinkTile( - title: i18n.settingsDeleteAddressBookButton, + title: i18n.shared.settingsDeleteAddressBookButton, titleColor: BrandColors.error, onTap: _showDeleteAddressBookDialog, ), @@ -538,16 +540,16 @@ class _SettingsScreenState extends State { ), if (showNotifyToggle) SettingsGroup( - label: i18n.settingsSectionBehaviour, + label: i18n.shared.settingsSectionBehaviour, tiles: [ SettingsNavTile( - title: i18n.settingsSyncFrequencyLabel, + title: i18n.shared.settingsSyncFrequencyLabel, value: syncFrequencyLabels(i18n).nameOf(_syncFrequency), onTap: _showSyncFrequency, ), SettingsToggleTile( - title: i18n.settingsNotifyNewTxsLabel, - description: i18n.settingsNotifyNewTxsDescription, + title: i18n.shared.settingsNotifyNewTxsLabel, + description: i18n.shared.settingsNotifyNewTxsDescription, value: _newTxNotificationsEnabled, onChanged: _setTxNotificationsEnabled, animate: _animateToggles, @@ -555,7 +557,7 @@ class _SettingsScreenState extends State { ], ), SettingsGroup( - label: i18n.settingsSectionWallet, + label: i18n.shared.settingsSectionWallet, tiles: [ SettingsLinkTile( title: i18n.settingsLwsViewKeysLabel, @@ -568,14 +570,14 @@ class _SettingsScreenState extends State { onTap: _showViewSecretKeysDialog, ), SettingsLinkTile( - title: i18n.settingsDeleteWalletButton, + title: i18n.shared.settingsDeleteWalletButton, titleColor: BrandColors.error, onTap: _showDeleteWalletDialog, ), ], ), SettingsGroup( - label: i18n.settingsSectionAbout, + label: i18n.shared.settingsSectionAbout, tiles: [ SettingsNavTile( title: i18n.welcomeTermsLink, @@ -586,12 +588,8 @@ class _SettingsScreenState extends State { onTap: () => Navigator.pushNamed(context, '/privacy_policy', arguments: true), ), SettingsNavTile( - title: i18n.settingsLicensesLabel, - onTap: () => showLicensePage( - context: context, - applicationName: 'Skylight Wallet', - applicationVersion: _appVersion, - ), + title: i18n.shared.settingsLicensesLabel, + onTap: () => Navigator.pushNamed(context, '/open_source_licenses', arguments: true), ), ], ), @@ -627,7 +625,7 @@ class _SettingsScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), child: BrandScreenHeader( center: Text( - i18n.settingsTitle, + i18n.shared.settingsTitle, style: BrandText.appBar.copyWith(fontSize: 16), ), ), @@ -670,7 +668,7 @@ class _SettingsScreenState extends State { ), const SizedBox(width: 13), Text( - i18n.settingsTitle, + i18n.shared.settingsTitle, style: TextStyle( fontFamily: 'Ubuntu', fontSize: 21, diff --git a/lib/screens/terms_of_service.dart b/lib/screens/terms_of_service.dart deleted file mode 100644 index 0054e58..0000000 --- a/lib/screens/terms_of_service.dart +++ /dev/null @@ -1,196 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:skylight_wallet/l10n/app_localizations.dart'; -import 'package:skylight_wallet/screens/desktop/home_shell.dart'; -import 'package:skylight_wallet/util/platform.dart'; -import 'package:skylight_wallet/widgets/ui/ui.dart'; - -class TermsOfService extends StatelessWidget { - const TermsOfService({super.key}); - - TextStyle get headingStyle => TextStyle(fontSize: 20, fontWeight: FontWeight.bold); - - TextStyle get subheadingStyle => TextStyle(fontSize: 16, fontWeight: FontWeight.w600); - - TextStyle get bodyStyle => TextStyle(fontSize: 14, height: 1.4); - - TextStyle get monospaceStyle => TextStyle(fontSize: 13, fontFamily: 'Ubuntu Mono', height: 1.4); - - Widget sectionHeading(String text) => Padding( - padding: EdgeInsets.only(top: 18.0, bottom: 6.0), - child: Text(text, style: subheadingStyle), - ); - - Widget paragraph(String text) => Padding( - padding: EdgeInsets.only(bottom: 10.0), - child: Text(text, style: bodyStyle), - ); - - @override - Widget build(BuildContext context) { - final i18n = AppLocalizations.of(context)!; - final scroll = SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Updated 2026-09-03', style: bodyStyle), - SizedBox(height: 12), - - sectionHeading('Acceptance'), - paragraph( - 'These Terms of Service (ToS) are entered into by and between You and MAGIC Grants, a Colorado nonprofit corporation (“we” or “us”). These terms govern your access to and use of Skylight Wallet and Spice Wallet (the “App”).', - ), - paragraph( - 'Please read the Terms of Service carefully before you start to use the App. By using the App you accept and agree to be bound and abide by these Terms of Service and our Privacy Policy. If you do not agree to these Terms of Service or the Privacy Policy, then you must not access or use the App.', - ), - - sectionHeading('Updates to the Terms of Service'), - paragraph( - 'We may revise and update these Terms of Service from time to time in our sole discretion. All changes are effective immediately when we post them, and apply to all access to and use of the App thereafter.', - ), - paragraph( - 'Your continued use of the App following the posting of revised Terms of Service means that you accept and agree to the changes. You are expected to check this page from time to time so you are aware of any changes, as they are binding on you.', - ), - - sectionHeading('All MIT License Terms Apply'), - paragraph( - 'The App is released under the MIT open source license permission notice, incorporated below:', - ), - - Container( - width: double.infinity, - margin: EdgeInsets.only(bottom: 10.0), - decoration: BoxDecoration(borderRadius: BorderRadius.circular(6.0)), - child: Text( - 'THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.', - style: monospaceStyle, - ), - ), - - sectionHeading('No Financial or Legal Advice'), - paragraph( - 'MAGIC Grants does not provide any financial or legal advice in connection with the App. You are responsible for all of your actions when using the App.', - ), - - sectionHeading('Security and Network Risks'), - paragraph( - 'You are fully responsible for the security of your funds when using the App. You agree that MAGIC Grants is not liable for your loss of funds, including due to any bugs that are present in the application. You agree that MAGIC Grants has not warranted that the App is secure for any particular purpose, including your desired purpose. You acknowledge that MAGIC Grants has not claimed that the App is bug-free.', - ), - paragraph( - 'You acknowledge that MAGIC Grants is not responsible for any loss of funds or other harms that could arise from related networks and software, including but not limited to the cryptocurrency networks that you connect to. These networks may have bugs (including severe ones which could lead to loss of funds or dilution of value), and infrastructure that you connect to can be malicious. Cryptocurrency networks may charge you fees, and you can lose value through value fluctuations and volatility, impermanent loss, slippage, and other market factors which may be very complex. You agree that MAGIC Grants is not liable for any of these issues. By interacting with these cryptocurrency networks, you agree that you are accepting all of the risks of these networks and accepting full responsibility for understanding them.', - ), - paragraph( - 'You acknowledge that MAGIC Grants offers no warranty of any kind, without limitation, for performance or security of the App or these cryptocurrency networks, even cryptocurrency networks that MAGIC Grants has commissioned security reviews for and cryptocurrency networks that MAGIC Grants runs our own nodes for.', - ), - - sectionHeading('No Custody by MAGIC Grants'), - paragraph( - 'MAGIC Grants does not have custody of your funds while you are using the App. The App is software that intends to allow you to manage your self-custody wallets. MAGIC Grants does not transmit funds for you. MAGIC Grants is not an exchange or money services business (MSB).', - ), - paragraph( - 'If you use any feature in the App named swap, trade, exchange, add liquidity, remove liquidity, bridge, or similar, you acknowledge that MAGIC Grants is not involved with nor performing these activities/actions whatsoever. The App may include features that you may use to interact with decentralized exchanges; however, MAGIC Grants is not a party nor counterparty to these transactions. The App is solely software that allows you to connect to relevant cryptocurrency networks through mechanisms that you specify, and you have full, independent control over how you decide to connect.', - ), - paragraph( - 'MAGIC Grants may run our own nodes for various cryptocurrency networks, but MAGIC Grants does not include our nodes or any default nodes in the App. Even in cases where you create transactions in the App that are processed or reviewed in any way by MAGIC Grants nodes (which is a standard function of nodes on a cryptocurrency network), you agree that MAGIC Grants is not a party to your transaction, this action does not create any binding liability for MAGIC Grants, and MAGIC Grants is not responsible for performing any service to you.', - ), - paragraph( - 'Cryptocurrencies exist only by virtue of the ownership record maintained in their respective networks. Any transfer of title in cryptocurrency occurs within a decentralized cryptocurrency network.', - ), - - sectionHeading('Restricted Use'), - paragraph( - 'You may not use the App if it is prohibited in your jurisdiction. You may not use the App in connection with an illegal purpose. You are responsible for compliance with local laws and regulations.', - ), - - sectionHeading('Third Party Services'), - paragraph( - 'The App includes links to other sites and resources provided by third parties, and the App incorporates information provided by third parties for convenience. MAGIC Grants does not guarantee that these sites, resources, and information are accurate or fit for Your desired purpose. We have no control over the contents of those sites or resources, and you agree that we have no responsibility for them or for any loss or damage that may arise from your use of them. If you decide to access any of the third-party services linked in this App, you do so entirely at your own risk and subject to the terms and conditions of use for such Apps.', - ), - - sectionHeading('Tax Matters'), - paragraph( - 'The users of the App are solely responsible in determining what, if any, taxes apply to their cryptocurrency transactions. We are not responsible for determining any taxes that apply to such transactions.', - ), - paragraph( - 'If you donate to MAGIC Grants, a 501(c)(3) public charity, your donation may qualify for a tax deduction. Additional paperwork, especially for cryptocurrency donations, may be required. If you choose to donate to MAGIC Grants as a part of any other activity in the App, such as any transaction you make that includes a donation to MAGIC Grants, you acknowledge that the sole involvement by MAGIC Grants for such transactions is to receive your donation, and that MAGIC Grants is not otherwise party to nor responsible for your transaction in any other way.', - ), - - sectionHeading('Indemnification'), - paragraph( - "You agree to defend, indemnify, and hold harmless MAGIC Grants, its affiliates, licensors, and service providers, and its and their respective officers, directors, employees, contractors, agents, licensors, suppliers, successors, and assigns from and against any claims, liabilities, damages, judgments, awards, losses, costs, expenses, or fees (including reasonable attorneys' fees) arising out of or relating to your violation of these Terms of Service or your use of the App, including, but not limited to, any use of the App's content, services, and products other than as expressly authorized in these Terms of Service, or your use of any information obtained from the App.", - ), - - sectionHeading('Governing Law and Jurisdiction'), - paragraph( - 'All matters relating to the App and these Terms of Service, and any dispute or claim arising therefrom or related thereto (in each case, including non-contractual disputes or claims), shall be governed by and construed in accordance with the internal laws of the State of Colorado without giving effect to any choice or conflict of law provision or rule (whether of the State of Colorado or any other jurisdiction).', - ), - paragraph( - 'Any legal suit, action, or proceeding arising out of, or related to, these Terms of Service or the App shall be instituted exclusively in the federal courts of the United States or the courts of the State of Colorado, although we retain the right to bring any suit, action, or proceeding against you for breach of these Terms of Service in your country of residence or any other relevant country. You waive any and all objections to the exercise of jurisdiction over you by such courts and to venue in such courts.', - ), - - sectionHeading('Arbitration'), - paragraph( - 'At our sole discretion, we may require You to submit any disputes arising from these Terms of Service or use of the App, including disputes arising from or concerning their interpretation, violation, invalidity, non-performance, or termination, to final and binding arbitration under the Rules of Arbitration of the American Arbitration Association applying Colorado law.', - ), - - sectionHeading('Waiver and Severability'), - paragraph( - 'No waiver by MAGIC Grants of any term or condition set out in these Terms of Service shall be deemed a further or continuing waiver of such term or condition or a waiver of any other term or condition, and any failure of MAGIC Grants to assert a right or provision under these Terms of Service shall not constitute a waiver of such right or provision.', - ), - paragraph( - 'If any provision of these Terms of Service is held by a court or other tribunal of competent jurisdiction to be invalid, illegal, or unenforceable for any reason, such provision shall be eliminated or limited to the minimum extent such that the remaining provisions of the Terms of Service will continue in full force and effect.', - ), - - sectionHeading('Entire Agreement'), - paragraph( - 'The Terms of Service and our Privacy Policy constitute the sole and entire agreement between You and MAGIC Grants regarding the App and supersede all prior and contemporaneous understandings, agreements, representations, and warranties, both written and oral, regarding the App.', - ), - ], - ), - ); - - final inShell = ModalRoute.of(context)?.settings.arguments == true; - final header = isDesktop - ? Padding( - padding: const EdgeInsets.fromLTRB(16, 10, 16, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - DesktopBackLink( - label: inShell ? i18n.navigationBarSettings : i18n.back, - onTap: () => Navigator.pop(context), - ), - const SizedBox(height: 12), - Text(i18n.welcomeTermsLink, style: desktopTitleStyle), - ], - ), - ) - : Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), - child: BrandScreenHeader( - onBack: () => Navigator.pop(context), - center: Text(i18n.welcomeTermsLink, style: BrandText.appBar.copyWith(fontSize: 16)), - ), - ); - final body = SafeArea( - child: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 520), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - header, - Expanded(child: scroll), - ], - ), - ), - ), - ); - - if (isDesktop && inShell) { - return DesktopShell(active: DesktopNav.settings, child: body); - } - return Scaffold(backgroundColor: BrandColors.paper, body: body); - } -} diff --git a/lib/screens/tor_settings.dart b/lib/screens/tor_settings.dart index 12b3faf..04bde87 100644 --- a/lib/screens/tor_settings.dart +++ b/lib/screens/tor_settings.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/screens/desktop/tor_choice_view.dart'; import 'package:skylight_wallet/services/tor_settings_service.dart'; import 'package:skylight_wallet/util/platform.dart'; @@ -10,8 +11,11 @@ import 'package:skylight_wallet/util/socks_http.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart'; /// Onboarding "Tor choice" — a three-way decision (Built-in / External / No Tor) -/// made before any connection. First-run only, so it skips the disable-Tor -/// warning / fiat-restore dance the Settings dialog performs. +/// made before any connection. Built-in, the recommended mode, starts selected; +/// External needs a passing connection test before Continue. Only Continue +/// saves the choice, and only then does a chosen Built-in start. +/// First-run only, so it skips the disable-Tor warning / fiat-restore dance +/// the Settings dialog performs. class TorSettingsScreen extends StatelessWidget { const TorSettingsScreen({super.key}); @@ -54,20 +58,20 @@ class TorSettingsScreen extends StatelessWidget { final i18n = AppLocalizations.of(context)!; final labels = TorChoiceLabels( - title: i18n.torChoiceTitle, + title: i18n.shared.torChoiceTitle, subtitle: i18n.torChoiceSubtitle, - builtIn: i18n.torSettingsModeBuiltIn, - builtInDesc: i18n.torChoiceBuiltInDesc, - external: i18n.torSettingsModeExternal, - externalDesc: i18n.torChoiceExternalDesc, - noTor: i18n.torSettingsModeDisabled, - noTorDesc: i18n.torChoiceNoTorDesc, - socksPortLabel: i18n.torSettingsSocksPortLabel, - orbotLabel: Platform.isIOS ? i18n.torSettingsUseOrbotLabelIos : i18n.torSettingsUseOrbotLabel, - testButton: i18n.torSettingsTestConnectionButton, - connected: i18n.torChoiceConnected, - testFailed: i18n.torChoiceTestFailed, - continueText: i18n.continueText, + builtIn: i18n.shared.torSettingsModeBuiltIn, + builtInDesc: i18n.shared.torChoiceBuiltInDesc(appShortName), + external: i18n.shared.torSettingsModeExternal, + externalDesc: i18n.shared.torChoiceExternalDesc, + noTor: i18n.shared.torSettingsModeDisabled, + noTorDesc: i18n.shared.torChoiceNoTorDesc, + socksPortLabel: i18n.shared.torSettingsSocksPortLabel, + orbotLabel: Platform.isIOS ? i18n.shared.torUseOrbotLabelIos : i18n.shared.torUseOrbotLabel, + testButton: i18n.shared.torSettingsTestConnectionButton, + connected: i18n.shared.torChoiceConnected, + testFailed: i18n.shared.torChoiceTestFailed, + continueText: i18n.shared.continueText, ); void onContinue({required int modeIndex, required String port, required bool useOrbot}) => @@ -76,8 +80,8 @@ class TorSettingsScreen extends StatelessWidget { if (isDesktop) { return DesktopTorChoiceView( labels: labels, - notePrivacy: i18n.onboardingTorNotePrivacy, - noteChangeable: i18n.onboardingTorNoteChangeable, + notePrivacy: i18n.shared.onboardingTorNotePrivacy, + noteChangeable: i18n.shared.onboardingTorNoteChangeable, onTest: _test, onContinue: onContinue, ); @@ -85,7 +89,6 @@ class TorSettingsScreen extends StatelessWidget { return TorChoiceView( labels: labels, - initialModeIndex: 0, isMobile: isMobile, onTest: _test, onContinue: onContinue, diff --git a/lib/screens/unlock.dart b/lib/screens/unlock.dart index b8fc505..ec29da3 100644 --- a/lib/screens/unlock.dart +++ b/lib/screens/unlock.dart @@ -1,10 +1,12 @@ import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:local_auth/local_auth.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/util/platform.dart' show typesWalletPassword; import 'package:skylight_wallet/widgets/floating_bob.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart'; @@ -80,8 +82,8 @@ class _UnlockScreenState extends State { // A passcode alone keeps the generic "Unlock". if (types.isEmpty) return; final label = types.contains(BiometricType.face) - ? i18n.unlockWithFaceId - : i18n.unlockWithTouchId; + ? i18n.shared.unlockWithFaceId + : i18n.shared.unlockWithTouchId; if (mounted) setState(() => _biometricLabel = label); } catch (_) { // Leave the generic label. @@ -95,8 +97,10 @@ class _UnlockScreenState extends State { } Future _promptUnlock() async { - final i18n = AppLocalizations.of(context)!; - final outcome = await unlockAppLockWithDevice(context, reason: i18n.unlockReason); + final outcome = await unlockAppLockWithDevice( + context, + reason: AppLocalizations.of(context)!.shared.unlockWalletReason, + ); if (mounted) _show(outcome); } @@ -109,7 +113,7 @@ class _UnlockScreenState extends State { Future _unlockWithPassword() async { final i18n = AppLocalizations.of(context)!; if (_passwordController.text.isEmpty) { - setState(() => _error = i18n.fieldEmptyError); + setState(() => _error = i18n.shared.fieldEmptyError); return; } setState(() { @@ -143,13 +147,13 @@ class _UnlockScreenState extends State { break; case AppLockUnlock.failed: if (typesWalletPassword) { - setState(() => _error = i18n.unlockUnableToAuthError); + setState(() => _error = i18n.shared.unlockUnableToAuthError); return; } setState(() => _offerRecoveryPhrase = true); - showBrandToast(context, i18n.unlockUnableToAuthError); + showBrandToast(context, i18n.shared.unlockUnableToAuthError); case AppLockUnlock.wrongPassword: - setState(() => _error = i18n.unlockIncorrectPasswordError); + setState(() => _error = i18n.shared.unlockIncorrectPasswordError); case AppLockUnlock.boundToAnotherPc: setState(() => _error = FhseLocalizations.of(context).unlockBoundToAnotherPc); } @@ -163,8 +167,8 @@ class _UnlockScreenState extends State { logo: FloatingBob(child: SvgPicture.asset('assets/logo_nobg.svg', width: 100, height: 100)), labels: UnlockLabels( title: i18n.unlockTitle, - passwordHint: i18n.unlockPasswordHint, - unlockButton: i18n.unlockButton, + passwordHint: i18n.shared.unlockPasswordHint, + unlockButton: i18n.shared.unlockButton, ), isDesktop: _isDesktop, version: _version.isEmpty ? null : _version, diff --git a/lib/screens/verify_seed.dart b/lib/screens/verify_seed.dart index ed42ce5..4287f5c 100644 --- a/lib/screens/verify_seed.dart +++ b/lib/screens/verify_seed.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:wallet_fhse/security_keys_ui.dart' show lockWalletAction; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/util/secure_screen.dart'; import 'package:skylight_wallet/wallet_core_glue.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart'; @@ -57,18 +58,18 @@ class _VerifySeedScreenState extends State with SecureScreenMi final i18n = AppLocalizations.of(context)!; return SeedVerifyView( labels: SeedVerifyLabels( - title: i18n.verifySeedTitle, - subtitle: (p) => i18n.verifySeedSubtitle(p[0], p[1], p[2]), - verifyButton: i18n.verifySeedButton, - wrongTitle: i18n.verifySeedWrongTitle, - wrongBody: i18n.verifySeedWrongBody, - tryAgain: i18n.verifySeedTryAgain, - unavailable: i18n.verifySeedUnavailable, + title: i18n.shared.verifySeedTitle, + subtitle: (p) => i18n.shared.verifySeedSubtitle(p[0], p[1], p[2]), + verifyButton: i18n.shared.verifySeedButton, + wrongTitle: i18n.shared.verifySeedWrongTitle, + wrongBody: i18n.shared.verifySeedWrongBody, + tryAgain: i18n.shared.verifySeedTryAgain, + unavailable: i18n.shared.verifySeedUnavailable, ), verifier: _verifier, embedded: widget.embedded, onVerified: () { - showBrandToast(context, i18n.verifySeedDone); + showBrandToast(context, i18n.shared.verifySeedDone); Navigator.pop(context); }, onLocked: _lock, diff --git a/lib/screens/wallet_home.dart b/lib/screens/wallet_home.dart index 32ae04a..fb03785 100644 --- a/lib/screens/wallet_home.dart +++ b/lib/screens/wallet_home.dart @@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/app_wallet.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/periodic_tasks.dart' show applyBackgroundTaskRegistration; @@ -40,9 +41,10 @@ typedef _HomeValues = ({ _HomeValues _deriveHome(AppWallet wallet, FiatRateModel fiatRate) { final unlockedBalance = wallet.unlockedBalance ?? 0; - final unlockedBalanceFiat = fiatRate.rateFor('XMR') is double && wallet.unlockedBalance is double - ? wallet.unlockedBalance! * fiatRate.rateFor('XMR')! - : null; + // Priced as soon as the rate is in, like the coin amount: $0 while the + // balance is still syncing, not the placeholder. + final rate = fiatRate.rateFor('XMR'); + final unlockedBalanceFiat = rate != null ? unlockedBalance * rate : null; var status = StatusIconStatus.fail; if (wallet.isFullySynced) { @@ -143,7 +145,7 @@ class _WalletHomeScreenState extends State with WidgetsBinding void _showTxSuccessToast() { final i18n = AppLocalizations.of(context)!; - showBrandToast(context, i18n.sendTransactionSuccessfullySent); + showBrandToast(context, i18n.shared.sendTransactionSuccessfullySent); } Future _openStep(SecurityStep step) => switch (step) { @@ -172,19 +174,21 @@ class _WalletHomeScreenState extends State with WidgetsBinding fiatRate: fiatRate, fiatTotal: v.unlockedBalanceFiat, coinText: '${_amountText(v.unlockedBalance)} XMR', - fiatErrorTooltip: i18n.homeFiatApiError, + fiatErrorTooltip: i18n.shared.homeFiatApiError, footer: footer, ); void receive() => Navigator.pushNamed(context, '/receive'); void send() => Navigator.pushNamed(context, '/send'); - void swap() => showBrandToast(context, i18n.coinHomeSwapComingSoon); + void swap() => showBrandToast(context, i18n.shared.coinHomeSwapComingSoon); final incoming = sections.incoming(coins); final chart = sections.chart(coins); // The server still waiting to approve this wallet, said where it matters. final notice = switch (wallet.lwsPendingApproval) { null => null, - LwsPendingApproval.account => WarningCallout(i18n.lwsAwaitingApprovalAccount), - LwsPendingApproval.restoreHeight => WarningCallout(i18n.lwsAwaitingApprovalRestoreHeight), + LwsPendingApproval.account => WarningCallout(i18n.shared.lwsAwaitingApprovalAccount), + LwsPendingApproval.restoreHeight => WarningCallout( + i18n.shared.lwsAwaitingApprovalRestoreHeight, + ), }; final checklist = securityChecklistCard( checklist: _checklist, @@ -201,9 +205,9 @@ class _WalletHomeScreenState extends State with WidgetsBinding : null, balance: hero(configured ? ConnectionPills(wallet: cw) : null), actions: DesktopHomeActions( - receiveLabel: i18n.homeReceive, - sendLabel: i18n.homeSend, - swapLabel: i18n.coinHomeSwap, + receiveLabel: i18n.shared.homeReceive, + sendLabel: i18n.shared.homeSend, + swapLabel: i18n.shared.coinHomeSwap, onReceive: receive, onSend: send, onSwap: swap, @@ -225,9 +229,9 @@ class _WalletHomeScreenState extends State with WidgetsBinding notice: notice, balance: hero(_ConnectionRow(wallet: wallet, disconnected: v.disconnected)), actions: HomeActionRow( - receiveLabel: i18n.homeReceive, - sendLabel: i18n.homeSend, - swapLabel: i18n.coinHomeSwap, + receiveLabel: i18n.shared.homeReceive, + sendLabel: i18n.shared.homeSend, + swapLabel: i18n.shared.coinHomeSwap, onReceive: receive, onSend: send, onSwap: swap, @@ -257,7 +261,7 @@ class _ConnectionRow extends StatelessWidget { if (cw == null || cw.connectionAddress.isEmpty) return const SizedBox.shrink(); final blocks = wallet.syncBlocksRemaining; final trailing = blocks != null - ? i18n.homeBlocksRemaining(blocks) + ? i18n.shared.homeBlocksRemaining(blocks) : disconnected ? i18n.homeDisconnected : null; @@ -298,10 +302,10 @@ class _SyncStatusPill extends StatelessWidget { final i18n = AppLocalizations.of(context)!; final blocks = blocksRemaining; final (Color dot, String text) = switch (status) { - StatusIconStatus.complete => (BrandColors.success, i18n.homeSynced), + StatusIconStatus.complete => (BrandColors.success, i18n.shared.homeSynced), StatusIconStatus.loading => ( BrandColors.warning, - blocks != null ? i18n.homeBlocksRemaining(blocks) : i18n.homeSyncing, + blocks != null ? i18n.shared.homeBlocksRemaining(blocks) : i18n.shared.homeSyncing, ), StatusIconStatus.fail => (BrandColors.error, i18n.homeDisconnected), }; diff --git a/lib/wallet_core_glue.dart b/lib/wallet_core_glue.dart index 1fe1d8b..fe13ec9 100644 --- a/lib/wallet_core_glue.dart +++ b/lib/wallet_core_glue.dart @@ -6,7 +6,6 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; import 'package:skylight_wallet/models/app_wallet.dart'; -import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/models/monero_wallet_adapter.dart'; import 'package:skylight_wallet/widgets/tx_details.dart' show TxDetailsDialog; import 'package:skylight_wallet/periodic_tasks.dart' @@ -15,7 +14,6 @@ import 'package:skylight_wallet/services/foreground_sync_service.dart' show foregroundSyncCallback, stopSyncAndDeleteWallets, restartForegroundSyncIfRunning; import 'package:skylight_wallet/services/notifications_service.dart'; import 'package:skylight_wallet/services/shared_preferences_service.dart'; -import 'package:skylight_wallet/services/tor_service.dart'; import 'package:skylight_wallet/services/tor_settings_service.dart'; import 'package:wallet_infra/wallet_infra.dart' as wcore; @@ -78,7 +76,7 @@ void installWalletCore() { coins: () => [MoneroWallet()], workmanagerCallback: backgroundDispatcher, foregroundCallback: foregroundSyncCallback, - ensureTorConnected: _ensureTorConnected, + ensureTorConnected: TorSettingsService.sharedInstance.ensureConnected, iosBundleId: 'org.magicgrants.skylightwallet', foregroundTitle: 'Skylight Wallet', foregroundIcon: const NotificationIcon( @@ -120,36 +118,14 @@ void installWalletCore() { }; } -/// Brings skylight's Tor up and reports whether it connected — the seam -/// `wallet_background` uses so a background isolate starts the *same* Tor the -/// wallet connects through. (Background open + the node/Tor gate now live inside -/// `wallet_background`.) -Future _ensureTorConnected() async { - final settings = TorSettingsService.sharedInstance; - await settings.ensureLoaded(); - if (settings.torMode != TorMode.builtIn) { - // External: the proxy is the user's, and `getProxy` fails closed if it is - // not usable. Disabled: there is no Tor to report. - return settings.torMode == TorMode.external; - } - - await TorService.sharedInstance.start(); - return TorService.sharedInstance.waitUntilConnected(timeout: const Duration(minutes: 2)); -} +/// The wallets the app's [WalletManager] holds. Only the store-screenshot test +/// (integration_test/store_screenshots_test.dart) swaps it, for sample ones. +@visibleForTesting +List Function() appCoins = () => [MoneroWallet()]; /// The wallet-core [WalletManager] provider. ChangeNotifierProvider walletManagerProvider() => - ChangeNotifierProvider(create: (_) => WalletManager(coins: () => [MoneroWallet()])); - -/// Attaches the [WalletManager] to the fiat model so it fetches rates for the -/// active coins (XMR). Call once at startup; the model stays attached, so every -/// `FiatRateModel.startService()` afterwards needs no manager argument. -void attachFiatWalletManager(BuildContext context) { - Provider.of( - context, - listen: false, - ).attachWalletManager(Provider.of(context, listen: false)); -} + ChangeNotifierProvider(create: (_) => WalletManager(coins: appCoins)); /// Startup for the wallet-core stack: whether a wallet exists (for the initial /// route) and, on mobile, opening + starting its sync. diff --git a/lib/widgets/connection_settings_form.dart b/lib/widgets/connection_settings_form.dart index d829397..436b93d 100644 --- a/lib/widgets/connection_settings_form.dart +++ b/lib/widgets/connection_settings_form.dart @@ -6,6 +6,7 @@ import 'package:skylight_wallet/services/tor_settings_service.dart'; import 'package:skylight_wallet/util/logging.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/wallet_core_glue.dart'; import 'package:skylight_wallet/services/tor_service.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart'; @@ -43,6 +44,9 @@ class ConnectionSettingsForm extends StatefulWidget { final bool embedded; final ConnectionFormController? controller; + /// In a settings sheet: a Cancel beside Save, as on the other settings sheets. + final VoidCallback? onCancel; + const ConnectionSettingsForm({ super.key, required this.saveButtonLabel, @@ -51,6 +55,7 @@ class ConnectionSettingsForm extends StatefulWidget { this.onBeforeSave, this.embedded = false, this.controller, + this.onCancel, }); @override @@ -142,14 +147,14 @@ class _ConnectionSettingsFormState extends State { setState(() { _hasTested = false; - _errorMessage = input.isRemoteIp ? i18n.connectionRemoteIpNotAllowed : null; + _errorMessage = input.isRemoteIp ? i18n.shared.connectionRemoteIpNotAllowed : null; }); if (input.hadProtocol) { showBrandToast(context, switch (input.protocolHint) { - AddressProtocolHint.onion => i18n.connectionProtocolHttpOnion, - AddressProtocolHint.secure => i18n.connectionProtocolHttps, - AddressProtocolHint.plain => i18n.connectionProtocolHttp, + AddressProtocolHint.onion => i18n.shared.connectionProtocolHttpOnion, + AddressProtocolHint.secure => i18n.shared.connectionProtocolHttps, + AddressProtocolHint.plain => i18n.shared.connectionProtocolHttp, }); } } @@ -231,15 +236,15 @@ class _ConnectionSettingsFormState extends State { } String _connectionTypeLabel(AppLocalizations i18n, String type) { - return type == 'node' ? i18n.connectionTypeNode : i18n.connectionTypeLws; + return type == 'node' ? i18n.shared.connectionTypeNode : i18n.shared.connectionTypeLws; } String _connectionAddressErrorMessage(AppLocalizations i18n, ConnectionAddressError error) => switch (error) { - ConnectionAddressError.remoteIp => i18n.connectionRemoteIpNotAllowed, - ConnectionAddressError.pathNotSupported => i18n.connectionPathNotSupported, - ConnectionAddressError.localOverTor => i18n.connectionLocalOverTorNotAllowed, - ConnectionAddressError.onionNeedsTor => i18n.connectionOnionNeedsTor, + ConnectionAddressError.remoteIp => i18n.shared.connectionRemoteIpNotAllowed, + ConnectionAddressError.pathNotSupported => i18n.shared.connectionPathNotSupported, + ConnectionAddressError.localOverTor => i18n.shared.connectionLocalOverTorNotAllowed, + ConnectionAddressError.onionNeedsTor => i18n.shared.connectionOnionNeedsTor, }; /// Resolves the SOCKS proxy port to pass to `wallet.testConnection`. When Tor @@ -286,7 +291,7 @@ class _ConnectionSettingsFormState extends State { } if (_useTor && TorSettingsService.sharedInstance.torMode == TorMode.disabled) { - showBrandToast(context, i18n.lwsSetupTorDisabledError); + showBrandToast(context, i18n.shared.lwsSetupTorDisabledError); return; } @@ -374,9 +379,11 @@ class _ConnectionSettingsFormState extends State { /// How the successful probe reached the server (the one server fact we can /// state from an unauthenticated test). String _successDetail(AppLocalizations i18n) { - if (_useTor) return i18n.connectionReachedOverTor; - if (_customProxyPortController.text.trim().isNotEmpty) return i18n.connectionReachedViaProxy; - return i18n.connectionReachedDirect; + if (_useTor) return i18n.shared.connectionReachedOverTor; + if (_customProxyPortController.text.trim().isNotEmpty) { + return i18n.shared.connectionReachedViaProxy; + } + return i18n.shared.connectionReachedDirect; } /// Maps this wrapper's flags onto the shared test-card state: starting-Tor @@ -411,18 +418,18 @@ class _ConnectionSettingsFormState extends State { labels: ConnectionFormLabels( proxyPortLabel: i18n.lwsSetupProxyPortLabel, proxyPortHint: i18n.lwsSetupProxyPortHint, - useTorLabel: i18n.lwsSetupUseTorLabel, - startingTorTitle: i18n.lwsSetupStartingTor, - testButton: i18n.lwsSetupTestConnectionButton, - testStop: i18n.connectionTestStop, - testingTitle: i18n.connectionTestingTitle, - testingDetail: i18n.connectionTestingDetail, - testAgain: i18n.connectionTestAgain, - resultWorksTitle: i18n.connectionResultWorksTitle, - resultFailedTitle: i18n.connectionResultFailedTitle, - resultFailedDetail: i18n.connectionResultFailedDetail, + useTorLabel: i18n.shared.lwsSetupUseTorLabel, + startingTorTitle: i18n.shared.lwsSetupStartingTor, + testButton: i18n.shared.lwsSetupTestConnectionButton, + testStop: i18n.shared.connectionTestStop, + testingTitle: i18n.shared.connectionTestingTitle, + testingDetail: i18n.shared.connectionTestingDetail, + testAgain: i18n.shared.connectionTestAgain, + resultWorksTitle: i18n.shared.connectionResultWorksTitle, + resultFailedTitle: i18n.shared.connectionResultFailedTitle, + resultFailedDetail: i18n.shared.connectionResultFailedDetail, ), - addressLabel: i18n.address, + addressLabel: i18n.shared.address, addressHint: addressHint, addressController: _addressController, onAddressChanged: _onAddressChange, @@ -430,7 +437,7 @@ class _ConnectionSettingsFormState extends State { errorMessage: _errorMessage, // The saved connection used Tor, which is now disabled: prompt to reconfigure. warning: (_savedConnectionUsedTor && torMode == TorMode.disabled) - ? i18n.connectionTorDisabledWarning + ? i18n.shared.connectionTorDisabledWarning : null, proxyController: _customProxyPortController, onProxyChanged: _onProxyPortChange, @@ -453,6 +460,8 @@ class _ConnectionSettingsFormState extends State { canSave: canSave, onSave: _saveConnection, showSave: !widget.embedded, + cancelLabel: widget.onCancel == null ? null : i18n.shared.cancel, + onCancel: widget.onCancel, ); } } diff --git a/lib/widgets/fiat_api_settings_form.dart b/lib/widgets/fiat_api_settings_form.dart index eaba7cd..29ac5f6 100644 --- a/lib/widgets/fiat_api_settings_form.dart +++ b/lib/widgets/fiat_api_settings_form.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/services/shared_preferences_service.dart'; import 'package:skylight_wallet/services/tor_settings_service.dart'; @@ -10,7 +11,15 @@ class FiatApiSettingsForm extends StatefulWidget { final String saveButtonLabel; final Future Function() onSaved; - const FiatApiSettingsForm({super.key, required this.saveButtonLabel, required this.onSaved}); + /// In a settings sheet: a Cancel beside Save, as on the other settings sheets. + final VoidCallback? onCancel; + + const FiatApiSettingsForm({ + super.key, + required this.saveButtonLabel, + required this.onSaved, + this.onCancel, + }); @override State createState() => _FiatApiSettingsFormState(); @@ -62,27 +71,38 @@ class _FiatApiSettingsFormState extends State { if (!_loaded) { return const SizedBox(height: 120, child: Center(child: CircularProgressIndicator())); } + final save = BrandButton(label: widget.saveButtonLabel, onPressed: _save); return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ FiatModesView( - modeLabel: i18n.fiatApiSettingsModeLabel, - torOnly: i18n.fiatApiSettingsModeTorOnly, - torOnlyDesc: i18n.fiatModeTorOnlyDesc, - clearnet: i18n.fiatApiSettingsModeClearnet, - clearnetDesc: i18n.fiatModeClearnetDesc, - disabled: i18n.fiatApiSettingsModeDisabled, - disabledDesc: i18n.fiatModeDisabledDesc, + modeLabel: i18n.shared.fiatApiSettingsModeLabel, + torOnly: i18n.shared.fiatApiSettingsModeTorOnly, + torOnlyDesc: i18n.shared.fiatModeTorOnlyDesc, + clearnet: i18n.shared.clearnetOnlyChoice, + clearnetDesc: i18n.shared.fiatModeClearnetDesc, + disabled: i18n.shared.fiatApiSettingsModeDisabled, + disabledDesc: i18n.shared.fiatModeDisabledDesc, offerTorOnly: !_globalTorDisabled, modeIndex: _mode.index, onModeChanged: (i) => setState(() => _mode = FiatApiMode.values[i]), - currencyLabel: i18n.fiatApiSettingsDisplayCurrencyLabel, + currencyLabel: i18n.shared.fiatApiSettingsDisplayCurrencyLabel, currency: _currency, onCurrencyChanged: (c) => setState(() => _currency = c), ), const SizedBox(height: 18), - BrandButton(label: widget.saveButtonLabel, onPressed: _save), + if (widget.onCancel == null) + save + else + SheetActions( + primary: save, + secondary: BrandButton.ghost( + label: i18n.shared.cancel, + color: BrandColors.inkMuted, + onPressed: widget.onCancel, + ), + ), ], ); } diff --git a/lib/widgets/home_labels.dart b/lib/widgets/home_labels.dart index e411eb8..97567fe 100644 --- a/lib/widgets/home_labels.dart +++ b/lib/widgets/home_labels.dart @@ -1,26 +1,27 @@ import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart'; /// The home cards' strings. HomeLabels homeLabels(AppLocalizations i18n) => HomeLabels( - incomingTitle: i18n.homeIncomingTitle, - incomingMore: i18n.homeIncomingTitleMore, - unlocksIn: i18n.homeIncomingUnlocksIn, - confirmations: i18n.homeIncomingConfirmations, - chartTitle: i18n.homeChartTitle, - chartToday: i18n.homeChartToday, - chartEachCoin: i18n.homeChartEachCoin, - chartNew: i18n.homeChartNew, - chartEmpty: i18n.homeChartEmpty, + incomingTitle: i18n.shared.homeIncomingTitle, + incomingMore: i18n.shared.homeIncomingTitleMore, + unlocksIn: i18n.shared.homeIncomingUnlocksIn, + confirmations: i18n.shared.homeIncomingConfirmations, + chartTitle: i18n.shared.homeChartTitle, + chartToday: i18n.shared.homeChartToday, + chartEachCoin: i18n.shared.homeChartEachCoin, + chartNew: i18n.shared.homeChartNew, + chartEmpty: i18n.shared.homeChartEmpty, ); /// The security checklist's strings. SecurityChecklistLabels securityChecklistLabels(AppLocalizations i18n) => SecurityChecklistLabels( - title: i18n.homeChecklistTitle, - progress: i18n.homeChecklistProgress, - verifySeed: i18n.homeChecklistVerifySeed, - autoBackup: i18n.homeChecklistAutoBackup, - tor: i18n.homeChecklistTor, - privateLookups: i18n.homeChecklistPrivateLookups, - securityKeys: i18n.homeChecklistSecurityKeys, + title: i18n.shared.homeChecklistTitle, + progress: i18n.shared.homeChecklistProgress, + verifySeed: i18n.shared.homeChecklistVerifySeed, + autoBackup: i18n.shared.homeChecklistAutoBackup, + tor: i18n.shared.homeChecklistTor, + privateLookups: i18n.shared.homeChecklistPrivateLookups, + securityKeys: i18n.shared.homeChecklistSecurityKeys, ); diff --git a/lib/widgets/language_sheet.dart b/lib/widgets/language_sheet.dart index 60b047f..fcecfd4 100644 --- a/lib/widgets/language_sheet.dart +++ b/lib/widgets/language_sheet.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/language_model.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart'; @@ -22,9 +23,9 @@ Future showLanguageSheet(BuildContext context) { labels: (context) { final i18n = AppLocalizations.of(context)!; return SettingsPickerLabels( - title: i18n.settingsLanguageLabel, - subtitle: i18n.settingsLanguageSheetSubtitle, - done: i18n.done, + title: i18n.shared.settingsLanguageLabel, + subtitle: i18n.shared.settingsLanguageSheetSubtitle, + done: i18n.shared.done, ); }, options: [ diff --git a/lib/widgets/privacy_settings_sheets.dart b/lib/widgets/privacy_settings_sheets.dart index eff8fdd..1d6e180 100644 --- a/lib/widgets/privacy_settings_sheets.dart +++ b/lib/widgets/privacy_settings_sheets.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import 'package:wallet_domain/wallet_domain.dart' show OpenAliasMode; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/wallet_core_glue.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/widgets/fiat_api_settings_form.dart'; @@ -18,12 +19,13 @@ double _sheetMaxHeight(BuildContext sheetContext) { } /// Settings popup chrome, matching Spice's sheets: a handle, an icon + title -/// header, then the form scrolling below (the form carries its own save -/// button and pops on success). +/// header, then the form scrolling below (the form carries its own Save and +/// Cancel buttons, given an `onCancel`, and pops on success). Future showSettingsFormSheet( BuildContext context, { required IconData icon, required String title, + String? subtitle, required Widget form, }) { return showBrandSheet( @@ -45,15 +47,27 @@ Future showSettingsFormSheet( const SheetHandle(), Padding( padding: EdgeInsets.fromLTRB(hpad, 0, hpad, 16), - child: Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SheetIcon( - icon: icon, - bg: BrandColors.surfaceTinted, - color: BrandColors.primaryDeep, + Row( + children: [ + SheetIcon( + icon: icon, + bg: BrandColors.surfaceTinted, + color: BrandColors.primaryDeep, + ), + const SizedBox(width: 11), + Expanded(child: Text(title, style: BrandText.sheetTitle)), + ], ), - const SizedBox(width: 11), - Expanded(child: Text(title, style: BrandText.sheetTitle)), + if (subtitle != null) ...[ + const SizedBox(height: 7), + Text( + subtitle, + style: BrandText.bodyMuted.copyWith(fontSize: 13, height: 1.5), + ), + ], ], ), ), @@ -79,15 +93,17 @@ Future showTorSettingsSheet(BuildContext context) { return showSettingsFormSheet( context, icon: Icons.public, - title: i18n.torSettingsTitle, + title: i18n.shared.torSettingsTitle, + subtitle: i18n.torSettingsSheetSubtitle, form: Builder( builder: (formContext) => TorSettingsForm( - saveButtonLabel: i18n.torSettingsSaveButton, + saveButtonLabel: i18n.shared.save, onSaved: () { appWalletOf(formContext).load(); Provider.of(formContext, listen: false).startService(); Navigator.pop(formContext); }, + onCancel: () => Navigator.pop(formContext), ), ), ); @@ -99,14 +115,16 @@ Future showFiatApiSettingsSheet(BuildContext context) { return showSettingsFormSheet( context, icon: Icons.attach_money, - title: i18n.settingsFiatApiSettingsLabel, + title: i18n.shared.settingsFiatApiSettingsLabel, + subtitle: i18n.shared.fiatApiSettingsSheetSubtitle, form: Builder( builder: (formContext) => FiatApiSettingsForm( - saveButtonLabel: i18n.torSettingsSaveButton, + saveButtonLabel: i18n.shared.save, onSaved: () async { await Provider.of(formContext, listen: false).startService(); if (formContext.mounted) Navigator.pop(formContext); }, + onCancel: () => Navigator.pop(formContext), ), ), ); @@ -118,17 +136,17 @@ Future showOpenAliasSheet(BuildContext context) { return showOpenAliasSettingsSheet( context, labels: OpenAliasSettingsLabels( - title: i18n.settingsOpenAliasSettingsLabel, - subtitle: i18n.openAliasSettingsSheetSubtitle, - modeLabel: i18n.fiatApiSettingsModeLabel, - torOnly: i18n.fiatApiSettingsModeTorOnly, - torOnlyDesc: i18n.openAliasModeTorOnlyDesc, - clearnet: i18n.fiatApiSettingsModeClearnet, - clearnetDesc: i18n.openAliasModeClearnetDesc, - disabled: i18n.fiatApiSettingsModeDisabled, - disabledDesc: i18n.openAliasModeDisabledDesc, - save: i18n.torSettingsSaveButton, - cancel: i18n.cancel, + title: i18n.shared.settingsOpenAliasSettingsLabel, + subtitle: i18n.shared.openAliasSettingsSheetSubtitle, + modeLabel: i18n.shared.fiatApiSettingsModeLabel, + torOnly: i18n.shared.fiatApiSettingsModeTorOnly, + torOnlyDesc: i18n.shared.openAliasModeTorOnlyDesc, + clearnet: i18n.shared.clearnetOnlyChoice, + clearnetDesc: i18n.shared.openAliasModeClearnetDesc, + disabled: i18n.shared.fiatApiSettingsModeDisabled, + disabledDesc: i18n.shared.openAliasModeDisabledDesc, + save: i18n.shared.save, + cancel: i18n.shared.cancel, ), ); } diff --git a/lib/widgets/sync_frequency_sheet.dart b/lib/widgets/sync_frequency_sheet.dart index 86559dd..d1ca9ef 100644 --- a/lib/widgets/sync_frequency_sheet.dart +++ b/lib/widgets/sync_frequency_sheet.dart @@ -6,23 +6,23 @@ import 'package:wallet_domain/wallet_domain.dart' show WalletManager; import 'package:wallet_infra/wallet_infra.dart' show SyncFrequency; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/services/foreground_sync_service.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart'; /// The Sync Frequency sheet's strings. Periodic names the Monero node's limits, /// which differ by platform: iOS can only wait for a charger, not for WiFi. SyncFrequencyLabels syncFrequencyLabels(AppLocalizations i18n) => SyncFrequencyLabels( - title: i18n.settingsSyncFrequencyLabel, + title: i18n.shared.settingsSyncFrequencyLabel, subtitle: i18n.syncFrequencySheetSubtitle, - modeLabel: i18n.syncFrequencyModeLabel, - continuous: i18n.syncFrequencyContinuous, + continuous: i18n.shared.syncFrequencyContinuous, continuousDesc: i18n.syncFrequencyContinuousDesc, - periodic: i18n.syncFrequencyPeriodic, + periodic: i18n.shared.syncFrequencyPeriodic, periodicDesc: Platform.isIOS ? i18n.syncFrequencyPeriodicDescIos : i18n.syncFrequencyPeriodicDesc, - off: i18n.syncFrequencyOff, - offDesc: i18n.syncFrequencyOffDesc, - save: i18n.torSettingsSaveButton, - cancel: i18n.cancel, + off: i18n.shared.syncFrequencyOff, + offDesc: i18n.shared.syncFrequencyOffDesc, + save: i18n.shared.save, + cancel: i18n.shared.cancel, ); /// Opens Sync Frequency and applies a saved choice: the view-only material, the diff --git a/lib/widgets/tor_settings_form.dart b/lib/widgets/tor_settings_form.dart index 66493aa..c3b65cb 100644 --- a/lib/widgets/tor_settings_form.dart +++ b/lib/widgets/tor_settings_form.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/wallet_core_glue.dart'; import 'package:skylight_wallet/services/shared_preferences_service.dart'; @@ -20,7 +21,15 @@ class TorSettingsForm extends StatefulWidget { final String saveButtonLabel; final VoidCallback onSaved; - const TorSettingsForm({super.key, required this.saveButtonLabel, required this.onSaved}); + /// In a settings sheet: a Cancel beside Save, as on the other settings sheets. + final VoidCallback? onCancel; + + const TorSettingsForm({ + super.key, + required this.saveButtonLabel, + required this.onSaved, + this.onCancel, + }); @override State createState() => _TorSettingsFormState(); @@ -112,10 +121,10 @@ class _TorSettingsFormState extends State { icon: Icons.warning_amber_rounded, iconBg: BrandColors.errorBg, iconColor: BrandColors.error, - title: i18n.torDisabledWalletsWarningTitle, - body: i18n.torDisabledWalletsWarningBody, - confirmLabel: i18n.torDisabledWalletsWarningConfirm, - cancelLabel: i18n.cancel, + title: i18n.shared.torDisabledWalletsWarningTitle, + body: i18n.shared.torDisabledWalletsWarningBody, + confirmLabel: i18n.shared.torDisabledWalletsWarningConfirm, + cancelLabel: i18n.shared.cancel, ); } @@ -189,22 +198,26 @@ class _TorSettingsFormState extends State { final canSave = _selectedMode != TorMode.external || (_connectionSuccess && _hasTested && !_isTestingConnection); + final save = BrandButton( + label: widget.saveButtonLabel, + onPressed: canSave ? _onSavePressed : null, + ); return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ModeSelectCard( - title: i18n.torSettingsModeBuiltIn, - description: i18n.torChoiceBuiltInDesc, + title: i18n.shared.torSettingsModeBuiltIn, + description: i18n.shared.torChoiceBuiltInDesc(appShortName), selected: _selectedMode == TorMode.builtIn, radioLeading: true, onTap: () => _select(TorMode.builtIn), ), const SizedBox(height: 9), ModeSelectCard( - title: i18n.torSettingsModeExternal, - description: i18n.torChoiceExternalDesc, + title: i18n.shared.torSettingsModeExternal, + description: i18n.shared.torChoiceExternalDesc, selected: _selectedMode == TorMode.external, radioLeading: true, onTap: () => _select(TorMode.external), @@ -212,14 +225,24 @@ class _TorSettingsFormState extends State { ), const SizedBox(height: 9), ModeSelectCard( - title: i18n.torSettingsModeDisabled, - description: i18n.torChoiceNoTorDesc, + title: i18n.shared.torSettingsModeDisabled, + description: i18n.shared.torChoiceNoTorDesc, selected: _selectedMode == TorMode.disabled, radioLeading: true, onTap: () => _select(TorMode.disabled), ), const SizedBox(height: BrandSpacing.lg), - BrandButton(label: widget.saveButtonLabel, onPressed: canSave ? _onSavePressed : null), + if (widget.onCancel == null) + save + else + SheetActions( + primary: save, + secondary: BrandButton.ghost( + label: i18n.shared.cancel, + color: BrandColors.inkMuted, + onPressed: widget.onCancel, + ), + ), ], ); } @@ -231,7 +254,7 @@ class _TorSettingsFormState extends State { children: [ TorPortField( controller: _socksPortController, - label: i18n.torSettingsSocksPortLabel, + label: i18n.shared.torSettingsSocksPortLabel, enabled: portEnabled, onChanged: () => setState(() { _hasTested = false; @@ -241,9 +264,7 @@ class _TorSettingsFormState extends State { if (isMobile) _OrbotCheck( value: _useOrbot, - label: Platform.isIOS - ? i18n.torSettingsUseOrbotLabelIos - : i18n.torSettingsUseOrbotLabel, + label: Platform.isIOS ? i18n.shared.torUseOrbotLabelIos : i18n.shared.torUseOrbotLabel, onChanged: (v) => setState(() { _useOrbot = v; if (v) _socksPortController.text = '9050'; @@ -259,13 +280,13 @@ class _TorSettingsFormState extends State { testing: _isTestingConnection, tested: _hasTested, ok: _connectionSuccess, - connectedLabel: i18n.torChoiceConnected, - failedLabel: i18n.torChoiceTestFailed, + connectedLabel: i18n.shared.torChoiceConnected, + failedLabel: i18n.shared.torChoiceTestFailed, ), ), const SizedBox(width: BrandSpacing.md), TorTestChip( - label: i18n.torSettingsTestConnectionButton, + label: i18n.shared.torSettingsTestConnectionButton, onTap: _isTestingConnection ? null : _testConnection, ), ], diff --git a/lib/widgets/tx_details.dart b/lib/widgets/tx_details.dart index 56b0329..dda555c 100644 --- a/lib/widgets/tx_details.dart +++ b/lib/widgets/tx_details.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:wallet_domain/wallet_domain.dart' show CryptoWallet, TxDetails; import 'package:wallet_ui/wallet_ui.dart' show TxDetailsSheetLabels, showTxDetailsSheet; @@ -16,25 +17,25 @@ class TxDetailsDialog { wallet: wallet, tx: txDetails, labels: TxDetailsSheetLabels( - title: i18n.txDetailsTitle, - hash: i18n.txDetailsHashLabel, - amount: i18n.amount, - networkFee: i18n.networkFee, - timeAndDate: i18n.txDetailsTimeAndDateLabel, - confirmationHeight: i18n.txDetailsConfirmationHeightLabel, - confirmations: i18n.txDetailsConfirmationsLabel, - viewKey: i18n.txDetailsViewKeyLabel, - recipients: i18n.txDetailsRecipientsLabel, - changeRecipient: i18n.txDetailsChangeRecipientLabel, - close: i18n.close, - copied: i18n.copiedToClipboard, - received: i18n.coinHomeReceived, - sent: i18n.coinHomeSent, - copyHint: i18n.txDetailsCopyHint, - failed: i18n.txDetailsFailed, - unknownStatus: i18n.txDetailsUnknownStatus, - receivedAt: i18n.txDetailsReceivedAtLabel, - unconfirmed: i18n.unconfirmed, + title: i18n.shared.txDetailsTitle, + hash: i18n.shared.txDetailsHashLabel, + amount: i18n.shared.amount, + networkFee: i18n.shared.networkFee, + timeAndDate: i18n.shared.txDetailsTimeAndDateLabel, + confirmationHeight: i18n.shared.txDetailsConfirmationHeightLabel, + confirmations: i18n.shared.txDetailsConfirmationsLabel, + viewKey: i18n.shared.txDetailsViewKeyLabel, + recipients: i18n.shared.txDetailsRecipientsLabel, + changeRecipient: i18n.shared.txDetailsChangeRecipientLabel, + close: i18n.shared.close, + copied: i18n.shared.copiedToClipboard, + received: i18n.shared.coinHomeReceived, + sent: i18n.shared.coinHomeSent, + copyHint: i18n.shared.txDetailsCopyHint, + failed: i18n.shared.txDetailsFailed, + unknownStatus: i18n.shared.txDetailsUnknownStatus, + receivedAt: i18n.shared.txDetailsReceivedAtLabel, + unconfirmed: i18n.shared.unconfirmed, ), ); } diff --git a/lib/widgets/wallet_navigation_bar.dart b/lib/widgets/wallet_navigation_bar.dart index ae5fc80..de8fa25 100644 --- a/lib/widgets/wallet_navigation_bar.dart +++ b/lib/widgets/wallet_navigation_bar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart'; /// Brand bottom nav — flat icon+label tabs on the paper ground with a hairline @@ -23,9 +24,9 @@ class WalletNavigationBar extends StatelessWidget { final i18n = AppLocalizations.of(context)!; final items = [ (Icons.account_balance_wallet_outlined, i18n.navigationBarWallet), - (Icons.history, i18n.navigationBarHistory), - (Icons.people_outline, i18n.addressBookTitle), - (Icons.tune, i18n.navigationBarSettings), + (Icons.history, i18n.shared.navigationBarHistory), + (Icons.people_outline, i18n.shared.addressBookTitle), + (Icons.tune, i18n.shared.navigationBarSettings), ]; return Hero( diff --git a/pubspec.lock b/pubspec.lock index 17675f1..01fee82 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1225,8 +1225,8 @@ packages: dependency: "direct main" description: path: "packages/wallet_background" - ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" - resolved-ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 + resolved-ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 url: "https://github.com/MAGICGrants/wallet-core" source: git version: "0.1.0" @@ -1234,8 +1234,8 @@ packages: dependency: "direct main" description: path: "packages/wallet_backup" - ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" - resolved-ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 + resolved-ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 url: "https://github.com/MAGICGrants/wallet-core" source: git version: "0.1.0" @@ -1243,8 +1243,8 @@ packages: dependency: "direct main" description: path: "packages/wallet_backup_platform" - ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" - resolved-ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 + resolved-ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 url: "https://github.com/MAGICGrants/wallet-core" source: git version: "0.1.0" @@ -1252,8 +1252,8 @@ packages: dependency: "direct main" description: path: "packages/wallet_domain" - ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" - resolved-ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 + resolved-ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 url: "https://github.com/MAGICGrants/wallet-core" source: git version: "0.1.0" @@ -1261,8 +1261,8 @@ packages: dependency: "direct main" description: path: "packages/wallet_fhse" - ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" - resolved-ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 + resolved-ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 url: "https://github.com/MAGICGrants/wallet-core" source: git version: "0.1.0" @@ -1270,8 +1270,8 @@ packages: dependency: "direct main" description: path: "packages/wallet_fiat" - ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" - resolved-ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 + resolved-ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 url: "https://github.com/MAGICGrants/wallet-core" source: git version: "0.1.0" @@ -1279,8 +1279,8 @@ packages: dependency: "direct main" description: path: "packages/wallet_infra" - ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" - resolved-ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 + resolved-ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 url: "https://github.com/MAGICGrants/wallet-core" source: git version: "0.1.0" @@ -1288,8 +1288,8 @@ packages: dependency: "direct main" description: path: "packages/wallet_monero" - ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" - resolved-ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 + resolved-ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 url: "https://github.com/MAGICGrants/wallet-core" source: git version: "0.1.0" @@ -1297,8 +1297,8 @@ packages: dependency: "direct main" description: path: "packages/wallet_openalias" - ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" - resolved-ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 + resolved-ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 url: "https://github.com/MAGICGrants/wallet-core" source: git version: "0.1.0" @@ -1306,8 +1306,8 @@ packages: dependency: "direct main" description: path: "packages/wallet_ui" - ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" - resolved-ref: "583935b80f0ea28baf6cc4f6fea2635e485f8820" + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 + resolved-ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 url: "https://github.com/MAGICGrants/wallet-core" source: git version: "0.1.0" diff --git a/pubspec.yaml b/pubspec.yaml index 86d971b..150f9b8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.2.0+414 +version: 2.2.0+415 environment: sdk: 3.13.5 @@ -65,54 +65,54 @@ dependencies: wallet_infra: git: url: https://github.com/MAGICGrants/wallet-core - ref: 583935b80f0ea28baf6cc4f6fea2635e485f8820 + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 path: packages/wallet_infra wallet_domain: git: url: https://github.com/MAGICGrants/wallet-core - ref: 583935b80f0ea28baf6cc4f6fea2635e485f8820 + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 path: packages/wallet_domain wallet_monero: git: url: https://github.com/MAGICGrants/wallet-core - ref: 583935b80f0ea28baf6cc4f6fea2635e485f8820 + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 path: packages/wallet_monero wallet_openalias: git: url: https://github.com/MAGICGrants/wallet-core - ref: 583935b80f0ea28baf6cc4f6fea2635e485f8820 + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 path: packages/wallet_openalias # FHSE: security keys guarding the wallet password. wallet_fhse: git: url: https://github.com/MAGICGrants/wallet-core - ref: 583935b80f0ea28baf6cc4f6fea2635e485f8820 + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 path: packages/wallet_fhse # Metadata backup: payment destinations, tx keys and the address book. wallet_backup: git: url: https://github.com/MAGICGrants/wallet-core - ref: 583935b80f0ea28baf6cc4f6fea2635e485f8820 + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 path: packages/wallet_backup wallet_backup_platform: git: url: https://github.com/MAGICGrants/wallet-core - ref: 583935b80f0ea28baf6cc4f6fea2635e485f8820 + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 path: packages/wallet_backup_platform wallet_background: git: url: https://github.com/MAGICGrants/wallet-core - ref: 583935b80f0ea28baf6cc4f6fea2635e485f8820 + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 path: packages/wallet_background wallet_fiat: git: url: https://github.com/MAGICGrants/wallet-core - ref: 583935b80f0ea28baf6cc4f6fea2635e485f8820 + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 path: packages/wallet_fiat wallet_ui: git: url: https://github.com/MAGICGrants/wallet-core - ref: 583935b80f0ea28baf6cc4f6fea2635e485f8820 + ref: cd74d869e8e05c6f6a6ee7888228f73eacb502e8 path: packages/wallet_ui bip39: diff --git a/scripts/store-screenshots.sh b/scripts/store-screenshots.sh new file mode 100755 index 0000000..1eb7a38 --- /dev/null +++ b/scripts/store-screenshots.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# +# Skylight's App Store and F-Droid screenshots, taken from +# integration_test/store_screenshots_test.dart by wallet-core's +# scripts/store-screenshots.sh, in the sibling ../wallet-core checkout. See +# that script for the options, the devices and where the pictures go. +# +# scripts/store-screenshots.sh # App Store: iPhone and iPad +# scripts/store-screenshots.sh --device android --update-fdroid # F-Droid: a Pixel +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +# The listing's screenshots, most important first, as -: the +# order of each App Store set (which takes the first 10) and of F-Droid's. +# The design-system artifact's store-listing.md lists the same order; change +# both together. +export STORE_SHOTS=" + wallet_home-dark + history-dark + tor_settings-dark + advanced_security-light + backup-light + address_book-light + send-light + receive-light + connection_settings-light +" + +exec ../wallet-core/scripts/store-screenshots.sh "$@" diff --git a/test/language_sheet_test.dart b/test/language_sheet_test.dart index 1e35a92..b894ec2 100644 --- a/test/language_sheet_test.dart +++ b/test/language_sheet_test.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/language_model.dart'; import 'package:skylight_wallet/widgets/language_sheet.dart'; @@ -27,7 +28,7 @@ class _Root extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text('behind:${AppLocalizations.of(context)!.settingsLanguageLabel}'), + Text('behind:${AppLocalizations.of(context)!.shared.settingsLanguageLabel}'), ElevatedButton( onPressed: () => showLanguageSheet(context), child: const Text('open'), diff --git a/test/route_policy_test.dart b/test/route_policy_test.dart index b5e5b97..bd228b9 100644 --- a/test/route_policy_test.dart +++ b/test/route_policy_test.dart @@ -48,6 +48,7 @@ void main() { '/secret_keys', '/advanced_security', '/metadata_backup', + '/open_source_licenses', ]) { expect(routePolicy.requiresUnlock(name), isTrue, reason: name); expect(routePolicy.allows(name, unlocked: false), isFalse, reason: name); diff --git a/test/send_contact_paste_test.dart b/test/send_contact_paste_test.dart index 61dfe40..a8a5e8d 100644 --- a/test/send_contact_paste_test.dart +++ b/test/send_contact_paste_test.dart @@ -21,10 +21,11 @@ import 'package:wallet_infra/wallet_infra.dart'; import 'package:wallet_monero/wallet_monero.dart'; import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/l10n/shared_strings.dart'; import 'package:skylight_wallet/models/fiat_rate_model.dart'; import 'package:skylight_wallet/screens/send.dart'; import 'package:skylight_wallet/widgets/ui/ui.dart' - show BrandButton, ConfirmSendView, SendAmountCard; + show BrandButton, ConfirmSendView, SendAmountCard, WalletUiLocalizations; void main() { late Directory tmp; @@ -115,6 +116,7 @@ void main() { child: MaterialApp( localizationsDelegates: const [ AppLocalizations.delegate, + WalletUiLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, @@ -135,7 +137,7 @@ void main() { /// Picks Alice in Send's own contact picker. Future pickAlice(WidgetTester tester) async { - await tester.tap(find.text(l10n(tester).sendContactsButton)); + await tester.tap(find.text(l10n(tester).shared.sendContactsButton)); await settle(tester); await tester.tap(find.text('Alice')); await settle(tester); @@ -148,7 +150,7 @@ void main() { '1', ); await settle(tester); - final sendButton = find.widgetWithText(BrandButton, l10n(tester).sendSendButton); + final sendButton = find.widgetWithText(BrandButton, l10n(tester).shared.sendSendButton); expect(sendButton, findsOneWidget); await tester.tap(sendButton); await settle(tester); @@ -175,7 +177,7 @@ void main() { // Before: the contact card names Alice over her address, with Paste beside it. expect(find.text('Alice'), findsOneWidget); expect(find.text(short(alice)), findsOneWidget); - final paste = find.text(l10n(tester).sendPasteButton); + final paste = find.text(l10n(tester).shared.sendPasteButton); expect(paste, findsOneWidget); await tester.tap(paste); diff --git a/test/settings_sheet_cancel_test.dart b/test/settings_sheet_cancel_test.dart new file mode 100644 index 0000000..8e303e2 --- /dev/null +++ b/test/settings_sheet_cancel_test.dart @@ -0,0 +1,67 @@ +// Skylight's Tor and Price settings sheets carry Cancel beside Save, as Spice's +// and the other settings sheets do. Where no Cancel is asked for the form keeps +// Save alone. (The Connection sheet's buttons are wallet_ui's ConnectionFormView, +// tested there.) +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:wallet_infra/testing.dart'; +import 'package:wallet_infra/wallet_infra.dart'; + +import 'package:skylight_wallet/l10n/app_localizations.dart'; +import 'package:skylight_wallet/widgets/fiat_api_settings_form.dart'; +import 'package:skylight_wallet/widgets/tor_settings_form.dart'; +import 'package:skylight_wallet/widgets/ui/ui.dart' show SheetActions, WalletUiLocalizations; + +void main() { + setUp(() { + SharedPreferencesService.store = MemoryPreferenceStore(); + TorSettingsService.sharedInstance.resetForTesting(); + }); + + tearDown(() { + SharedPreferencesService.resetForTesting(); + TorSettingsService.sharedInstance.resetForTesting(); + }); + + Future pumpForm(WidgetTester tester, Widget form) async { + await tester.pumpWidget( + MaterialApp( + localizationsDelegates: const [ + AppLocalizations.delegate, + WalletUiLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold(body: SingleChildScrollView(child: form)), + ), + ); + await tester.pumpAndSettle(); + } + + final forms = { + 'Tor': (onCancel) => + TorSettingsForm(saveButtonLabel: 'Save', onSaved: () {}, onCancel: onCancel), + 'Price': (onCancel) => + FiatApiSettingsForm(saveButtonLabel: 'Save', onSaved: () async {}, onCancel: onCancel), + }; + + for (final MapEntry(key: name, value: build) in forms.entries) { + testWidgets('$name: a settings sheet gets Cancel beside Save', (tester) async { + var cancelled = 0; + await pumpForm(tester, build(() => cancelled++)); + expect(find.byType(SheetActions), findsOneWidget); + await tester.ensureVisible(find.text('Cancel')); + await tester.tap(find.text('Cancel')); + expect(cancelled, 1); + }); + + testWidgets('$name: without onCancel, Save stands alone', (tester) async { + await pumpForm(tester, build(null)); + expect(find.text('Save'), findsOneWidget); + expect(find.text('Cancel'), findsNothing); + }); + } +}