From 7bafec7d5230a6c3deea5072e7788591899901a1 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Mon, 20 Jul 2026 23:16:24 -0300 Subject: [PATCH 01/30] feat: localize drawer menu strings --- lib/features/drawer/screens/drawer_menu.dart | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/features/drawer/screens/drawer_menu.dart b/lib/features/drawer/screens/drawer_menu.dart index 1ea03951..4ac6ed65 100644 --- a/lib/features/drawer/screens/drawer_menu.dart +++ b/lib/features/drawer/screens/drawer_menu.dart @@ -6,6 +6,7 @@ import 'package:mostro/core/app_routes.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/trades/providers/trades_providers.dart' show orderBookNotificationCountProvider; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/shared/widgets/bottom_nav_bar.dart' show chatNotificationCountProvider; @@ -105,6 +106,7 @@ class _SidebarContent extends StatelessWidget { @override Widget build(BuildContext context) { final currentPath = GoRouterState.of(context).uri.path; + final l10n = AppLocalizations.of(context); return Container( color: cardBg, @@ -128,7 +130,7 @@ class _SidebarContent extends StatelessWidget { Row( children: [ Text( - 'MOSTRO', + l10n.drawerTitle, style: (theme.textTheme.headlineLarge ?? theme.textTheme.headlineMedium ?? const TextStyle( @@ -148,7 +150,7 @@ class _SidebarContent extends StatelessWidget { borderRadius: BorderRadius.circular(AppRadius.chip), ), child: Text( - 'Beta', + l10n.betaBadgeLabel, style: TextStyle( color: green, fontSize: 10, @@ -170,7 +172,7 @@ class _SidebarContent extends StatelessWidget { _NavItem( icon: Icons.list_alt_outlined, activeIcon: Icons.list_alt, - label: 'Order Book', + label: l10n.navOrderBook, isActive: currentPath == AppRoute.home || currentPath == '/', badgeCount: 0, @@ -180,7 +182,7 @@ class _SidebarContent extends StatelessWidget { _NavItem( icon: Icons.bolt_outlined, activeIcon: Icons.bolt, - label: 'My Trades', + label: l10n.navMyTrades, isActive: currentPath.startsWith(AppRoute.orderBook), badgeCount: tradesCount, green: green, @@ -189,7 +191,7 @@ class _SidebarContent extends StatelessWidget { _NavItem( icon: Icons.chat_bubble_outline, activeIcon: Icons.chat_bubble, - label: 'Chat', + label: l10n.navChat, isActive: currentPath.startsWith(AppRoute.chatList), badgeCount: chatCount, green: green, @@ -205,7 +207,7 @@ class _SidebarContent extends StatelessWidget { // ── Account / settings items ───────────────────────────────── _MenuItem( icon: Icons.key_outlined, - label: 'Account', + label: l10n.drawerAccountMenuItem, onTap: () { onNavigate?.call(); context.push(AppRoute.keyManagement); @@ -214,7 +216,7 @@ class _SidebarContent extends StatelessWidget { const SizedBox(height: AppSpacing.md), _MenuItem( icon: Icons.settings_outlined, - label: 'Settings', + label: l10n.drawerSettingsMenuItem, onTap: () { onNavigate?.call(); context.push(AppRoute.settings); @@ -223,7 +225,7 @@ class _SidebarContent extends StatelessWidget { const SizedBox(height: AppSpacing.md), _MenuItem( icon: Icons.info_outline, - label: 'About', + label: l10n.drawerAboutMenuItem, onTap: () { onNavigate?.call(); context.push(AppRoute.about); From 4aa901c67931038861b39e31a8603ef396f27d81 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Mon, 20 Jul 2026 23:22:15 -0300 Subject: [PATCH 02/30] feat: localize walkthrough screen strings Replace hardcoded English text in walkthrough slides with localized strings from AppLocalizations. Moves slide titles and bodies (6 slides), plus "Skip" and "Done" button labels, into l10n system. --- .../screens/walkthrough_screen.dart | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/lib/features/walkthrough/screens/walkthrough_screen.dart b/lib/features/walkthrough/screens/walkthrough_screen.dart index e5300a83..2a9f2797 100644 --- a/lib/features/walkthrough/screens/walkthrough_screen.dart +++ b/lib/features/walkthrough/screens/walkthrough_screen.dart @@ -7,6 +7,7 @@ import 'package:mostro/core/app_routes.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/walkthrough/providers/first_run_provider.dart'; import 'package:mostro/features/walkthrough/utils/highlight_config.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// First-run walkthrough. Six slides explaining Mostro concepts. /// @@ -20,6 +21,7 @@ class WalkthroughScreen extends ConsumerWidget { final theme = Theme.of(context); final colors = theme.extension(); final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); + final l10n = AppLocalizations.of(context); final screenWidth = MediaQuery.sizeOf(context).width; final bodyPadding = screenWidth * 0.06; @@ -51,11 +53,11 @@ class WalkthroughScreen extends ConsumerWidget { back: const Icon(Icons.arrow_back, color: Colors.white), next: const Icon(Icons.arrow_forward, color: Colors.white), skip: Text( - 'Skip', + l10n.skip, style: theme.textTheme.labelLarge!.copyWith(color: Colors.white), ), done: Text( - 'Done', + l10n.done, style: theme.textTheme.labelLarge!.copyWith( color: green, fontWeight: FontWeight.bold, @@ -82,41 +84,36 @@ class WalkthroughScreen extends ConsumerWidget { required TextStyle baseBodyStyle, required TextStyle titleStyle, }) { - const slides = [ + final l10n = AppLocalizations.of(context); + final slides = [ ( - title: 'Trade Bitcoin freely — no KYC', - body: - 'Mostro is a peer-to-peer exchange that lets you trade Bitcoin for any currency and payment method — no KYC, and no need to give your data to anyone. It\'s built on Nostr, which makes it censorship-resistant. No one can stop you from trading.', + title: l10n.walkthroughSlideOneTitle, + body: l10n.walkthroughSlideOneBody, image: 'assets/images/wt-1.png', ), ( - title: 'Privacy by default', - body: - 'Mostro generates a new identity for every exchange, so your trades can\'t be linked. You can also decide how private you want to be:\n• Reputation mode – Lets others see your successful trades and trust level.\n• Full privacy mode – No reputation is built, but your activity is completely anonymous.\nSwitch modes anytime from the Account screen, where you should also save your secret words — they\'re the only way to recover your account.', + title: l10n.walkthroughSlideTwoTitle, + body: l10n.walkthroughSlideTwoBody, image: 'assets/images/wt-2.png', ), ( - title: 'Security at every step', - body: - 'Mostro uses Hold Invoices: sats stay in the seller\'s wallet until the end of the trade. This protects both sides. The app is also designed to be intuitive and easy for all kinds of users.', + title: l10n.walkthroughSlideThreeTitle, + body: l10n.walkthroughSlideThreeBody, image: 'assets/images/wt-3.png', ), ( - title: 'Fully encrypted chat', - body: - 'Each trade has its own private chat, end-to-end encrypted. Only the two users involved can read it. In case of a dispute, you can give the shared key to an admin to help resolve the issue.', + title: l10n.walkthroughSlideFourTitle, + body: l10n.walkthroughSlideFourBody, image: 'assets/images/wt-4.png', ), ( - title: 'Take an offer', - body: - 'Browse the order book, choose an offer that works for you, and follow the trade flow step by step. You\'ll be able to check the other user\'s profile, chat securely, and complete the trade with ease.', + title: l10n.walkthroughSlideFiveTitle, + body: l10n.walkthroughSlideFiveBody, image: 'assets/images/wt-5.png', ), ( - title: 'Can\'t find what you need?', - body: - 'You can also create your own offer and wait for someone to take it. Set the amount and preferred payment method — Mostro handles the rest.', + title: l10n.walkthroughSlideSixTitle, + body: l10n.walkthroughSlideSixBody, image: 'assets/images/wt-6.png', ), ]; From 5f499b6952d32dd095700b13acf8cb35077e1cf6 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Mon, 20 Jul 2026 23:58:39 -0300 Subject: [PATCH 03/30] feat: localize account screen strings Replace all hardcoded English text in account_screen.dart with localized strings from AppLocalizations. Adds 18 new l10n keys across en/de/es/fr/it for error messages, dialog content, button labels, tooltips, and UI text including: --- .../account/screens/account_screen.dart | 103 +++++++++--------- lib/l10n/app_de.arb | 19 +++- lib/l10n/app_en.arb | 36 +++++- lib/l10n/app_es.arb | 19 +++- lib/l10n/app_fr.arb | 19 +++- lib/l10n/app_it.arb | 19 +++- lib/l10n/app_localizations.dart | 102 +++++++++++++++++ lib/l10n/app_localizations_de.dart | 60 ++++++++++ lib/l10n/app_localizations_en.dart | 59 ++++++++++ lib/l10n/app_localizations_es.dart | 60 ++++++++++ lib/l10n/app_localizations_fr.dart | 60 ++++++++++ lib/l10n/app_localizations_it.dart | 60 ++++++++++ 12 files changed, 560 insertions(+), 56 deletions(-) diff --git a/lib/features/account/screens/account_screen.dart b/lib/features/account/screens/account_screen.dart index c1005cb9..3434a727 100644 --- a/lib/features/account/screens/account_screen.dart +++ b/lib/features/account/screens/account_screen.dart @@ -37,6 +37,7 @@ class _AccountScreenState extends ConsumerState { Future _loadAndRevealWords() async { if (_loadingWords) return; setState(() => _loadingWords = true); + final l10n = AppLocalizations.of(context); try { final words = await IdentityService.getMnemonicWords(); @@ -44,8 +45,8 @@ class _AccountScreenState extends ConsumerState { if (!mounted) return; if (words.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('No identity found — try restarting the app.'), + SnackBar( + content: Text(l10n.noIdentityFoundMessage), ), ); return; @@ -65,7 +66,7 @@ class _AccountScreenState extends ConsumerState { content: Text( kDebugMode ? 'Failed to load secret words: $e' - : 'Failed to load secret words. Please try again.', + : l10n.failedToLoadSecretWordsMessage, ), ), ); @@ -76,6 +77,7 @@ class _AccountScreenState extends ConsumerState { } Future _confirmBackup() async { + final l10n = AppLocalizations.of(context); try { await ref.read(backupReminderProvider.notifier).confirmBackupComplete(); await ref.read(backupCompletedProvider.notifier).markCompleted(); @@ -88,7 +90,7 @@ class _AccountScreenState extends ConsumerState { content: Text( kDebugMode ? 'Failed to confirm backup: $e' - : 'Failed to confirm backup. Please try again.', + : l10n.failedToConfirmBackupMessage, ), ), ); @@ -107,12 +109,13 @@ class _AccountScreenState extends ConsumerState { final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230); final inputBg = colors?.backgroundInput ?? const Color(0xFF252A3A); final textSec = colors?.textSecondary ?? const Color(0xFFB0B3C6); + final l10n = AppLocalizations.of(context); final privacyMode = ref.watch(privacyModeProvider); final backupPending = ref.watch(backupReminderProvider); final backupDone = ref.watch(backupCompletedProvider); return Scaffold( - appBar: AppBar(title: const Text('Account')), + appBar: AppBar(title: Text(l10n.accountScreenTitle)), body: ListView( padding: const EdgeInsets.all(AppSpacing.lg), children: [ @@ -134,19 +137,18 @@ class _AccountScreenState extends ConsumerState { _CardHeader( icon: Icons.key, iconColor: green, - title: 'Secret Words', + title: l10n.secretWordsTitle, badge: backupDone ? _BackedUpBadge(green: green) : null, onInfo: () => _showInfoDialog( context, - 'Secret Words', - 'Your 12 secret words are the only way to recover your account. ' - 'Back them up in a safe place — never share them with anyone.', + l10n.secretWordsTitle, + l10n.secretWordsInfoContent, ), ), const SizedBox(height: AppSpacing.sm), Text( - 'To restore your account', + l10n.toRestoreYourAccount, style: theme.textTheme.bodySmall!.copyWith(color: textSec), ), const SizedBox(height: AppSpacing.md), @@ -203,7 +205,7 @@ class _AccountScreenState extends ConsumerState { color: green, ), label: Text( - _wordsVisible ? 'Hide' : 'Show', + _wordsVisible ? l10n.hideButtonLabel : l10n.showButtonLabel, style: TextStyle(color: green), ), ), @@ -238,19 +240,17 @@ class _AccountScreenState extends ConsumerState { _CardHeader( icon: Icons.shield_outlined, iconColor: green, - title: 'Privacy', + title: l10n.privacyCardTitle, onInfo: () => _showInfoDialog( context, - 'Privacy Modes', - 'Reputation mode lets others see your successful trades.\n\n' - 'Full privacy mode keeps your activity completely anonymous — ' - 'no reputation is built.', + l10n.privacyModesInfoTitle, + l10n.privacyModesInfoContent, ), ), const SizedBox(height: AppSpacing.sm), Text( - 'Control your privacy settings', + l10n.controlPrivacySettings, style: theme.textTheme.bodySmall!.copyWith(color: textSec), ), const SizedBox(height: AppSpacing.md), @@ -258,8 +258,8 @@ class _AccountScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ _PrivacyOption( - title: 'Reputation Mode', - subtitle: 'Standard privacy with reputation', + title: l10n.reputationMode, + subtitle: l10n.reputationModeSubtitle, selected: !privacyMode, green: green, onTap: @@ -269,8 +269,8 @@ class _AccountScreenState extends ConsumerState { ), const SizedBox(height: AppSpacing.sm), _PrivacyOption( - title: 'Full Privacy Mode', - subtitle: 'Maximum anonymity', + title: l10n.fullPrivacyMode, + subtitle: l10n.fullPrivacyModeSubtitle, selected: privacyMode, green: green, onTap: @@ -289,7 +289,7 @@ class _AccountScreenState extends ConsumerState { FilledButton.icon( onPressed: () => _confirmGenerateNewUser(context), icon: const Icon(Icons.person_add_outlined), - label: const Text('Generate New User'), + label: Text(l10n.generateNewUserButton), style: FilledButton.styleFrom( backgroundColor: green, foregroundColor: Colors.black, @@ -308,7 +308,7 @@ class _AccountScreenState extends ConsumerState { child: OutlinedButton.icon( onPressed: () => _showImportDialog(context), icon: const Icon(Icons.download_outlined), - label: const Text('Import Mostro User'), + label: Text(l10n.importMostroUserButton), style: OutlinedButton.styleFrom( foregroundColor: green, side: BorderSide(color: green), @@ -350,7 +350,7 @@ class _AccountScreenState extends ConsumerState { actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), - child: const Text('OK'), + child: Text(AppLocalizations.of(context).okButtonLabel), ), ], ), @@ -358,19 +358,17 @@ class _AccountScreenState extends ConsumerState { } void _confirmGenerateNewUser(BuildContext context) { + final l10n = AppLocalizations.of(context); showDialog( context: context, builder: (dialogContext) => AlertDialog( - title: const Text('Generate New User?'), - content: const Text( - 'This will create a brand-new identity. Your current secret words ' - 'will no longer work — make sure they are backed up before continuing.', - ), + title: Text(l10n.generateNewUserDialogTitle), + content: Text(l10n.generateNewUserDialogContent), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), - child: const Text('Cancel'), + child: Text(l10n.cancel), ), FilledButton( onPressed: () async { @@ -401,13 +399,13 @@ class _AccountScreenState extends ConsumerState { content: Text( kDebugMode ? 'Failed to generate identity: $e' - : 'Failed to generate identity. Please try again.', + : l10n.failedToGenerateIdentityMessage, ), ), ); } }, - child: const Text('Continue'), + child: Text(l10n.continueButtonLabel), ), ], ), @@ -425,6 +423,7 @@ class _AccountScreenState extends ConsumerState { } Future _importIdentity(BuildContext context, List words) async { + final l10n = AppLocalizations.of(context); try { await IdentityService.importAndStore(words); ref.read(sessionProvider.notifier).clearSession(); @@ -445,7 +444,7 @@ class _AccountScreenState extends ConsumerState { content: Text( kDebugMode ? 'Import failed: $e' - : 'Invalid mnemonic. Please check your words and try again.', + : l10n.invalidMnemonicMessage, ), ), ); @@ -453,19 +452,17 @@ class _AccountScreenState extends ConsumerState { } void _confirmRefresh(BuildContext context) { + final l10n = AppLocalizations.of(context); showDialog( context: context, builder: (dialogContext) => AlertDialog( - title: const Text('Refresh User?'), - content: const Text( - 'This will re-fetch your trades and orders from the Mostro instance. ' - 'Use this if you think your data is out of sync or orders are missing.', - ), + title: Text(l10n.refreshUserDialogTitle), + content: Text(l10n.refreshUserDialogContent), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), - child: const Text('Cancel'), + child: Text(l10n.cancel), ), FilledButton( onPressed: () async { @@ -474,7 +471,7 @@ class _AccountScreenState extends ConsumerState { await orders_api.restartOrdersSubscription(); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Order book refreshed')), + SnackBar(content: Text(l10n.orderBookRefreshedMessage)), ); } catch (e) { debugPrint('[account] refresh error: $e'); @@ -484,13 +481,13 @@ class _AccountScreenState extends ConsumerState { content: Text( kDebugMode ? 'Refresh failed: $e' - : 'Refresh failed', + : l10n.refreshFailedMessage, ), ), ); } }, - child: const Text('Refresh'), + child: Text(l10n.refreshButtonLabel), ), ], ), @@ -556,7 +553,7 @@ class _CardHeader extends StatelessWidget { IconButton( onPressed: onInfo, icon: const Icon(Icons.info_outline, size: 18), - tooltip: 'More information', + tooltip: AppLocalizations.of(context).moreInformationTooltip, padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), @@ -581,6 +578,7 @@ class _BackupRitualBanner extends StatelessWidget { final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230); final amber = colors?.warningAmber ?? const Color(0xFFE89C3C); final textSec = colors?.textSecondary ?? const Color(0xFFB0B3C6); + final l10n = AppLocalizations.of(context); return Material( color: cardBg, @@ -603,7 +601,7 @@ class _BackupRitualBanner extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Secure your reputation', + l10n.backupBannerTitle, style: theme.textTheme.bodyMedium!.copyWith( fontWeight: FontWeight.w600, color: amber, @@ -611,7 +609,7 @@ class _BackupRitualBanner extends StatelessWidget { ), const SizedBox(height: 2), Text( - 'Back up your 12 words — it takes 60 seconds.', + l10n.backupBannerSubtitle, style: theme.textTheme.bodySmall!.copyWith(color: textSec), ), @@ -648,7 +646,7 @@ class _BackedUpBadge extends StatelessWidget { Icon(Icons.check, size: 12, color: green), const SizedBox(width: 3), Text( - 'Backed up', + AppLocalizations.of(context).backedUpBadgeLabel, style: TextStyle( color: green, fontSize: 11, @@ -840,7 +838,9 @@ class _ImportMnemonicDialogState extends State<_ImportMnemonicDialog> { final validLength = words.length == 12 || words.length == 24; final validWords = words.every((w) => RegExp(r'^[a-zA-Z]+$').hasMatch(w)); if (!validLength || !validWords) { - setState(() => _error = 'Enter a valid 12 or 24 word phrase.'); + setState( + () => _error = AppLocalizations.of(context).enterValidMnemonicError, + ); return; } Navigator.pop(context); @@ -849,8 +849,9 @@ class _ImportMnemonicDialogState extends State<_ImportMnemonicDialog> { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); return AlertDialog( - title: const Text('Import Mnemonic'), + title: Text(l10n.importMnemonicDialogTitle), content: TextField( controller: _controller, maxLines: 3, @@ -858,7 +859,7 @@ class _ImportMnemonicDialogState extends State<_ImportMnemonicDialog> { enableSuggestions: false, enableIMEPersonalizedLearning: false, decoration: InputDecoration( - hintText: 'Enter your 12 or 24 word phrase...', + hintText: l10n.importMnemonicHintText, errorText: _error, ), onChanged: (_) { @@ -868,9 +869,9 @@ class _ImportMnemonicDialogState extends State<_ImportMnemonicDialog> { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + child: Text(l10n.cancel), ), - FilledButton(onPressed: _submit, child: const Text('Import')), + FilledButton(onPressed: _submit, child: Text(l10n.importButtonLabel)), ], ); } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 751d220e..48b8afc4 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -321,5 +321,22 @@ "payWithLightningWallet": "Mit Lightning-Wallet bezahlen", "noLightningWalletFound": "Keine Lightning-Wallet auf diesem Gerät gefunden", "orderNoLongerActive": "Diese Bestellung ist nicht mehr aktiv", - "sessionTimeoutMessage": "Keine Antwort erhalten; prüfe deine Verbindung und versuche es später erneut" + "sessionTimeoutMessage": "Keine Antwort erhalten; prüfe deine Verbindung und versuche es später erneut", + "noIdentityFoundMessage": "Keine Identität gefunden — versuche, die App neu zu starten.", + "failedToLoadSecretWordsMessage": "Geheime Wörter konnten nicht geladen werden. Bitte versuche es erneut.", + "failedToConfirmBackupMessage": "Sicherung konnte nicht bestätigt werden. Bitte versuche es erneut.", + "secretWordsInfoContent": "Deine 12 geheimen Wörter sind die einzige Möglichkeit, dein Konto wiederherzustellen. Sichere sie an einem sicheren Ort — teile sie niemals mit jemandem.", + "privacyModesInfoTitle": "Datenschutzmodi", + "privacyModesInfoContent": "Im Reputationsmodus können andere deine erfolgreichen Trades sehen.\n\nDer vollständige Datenschutzmodus hält deine Aktivität völlig anonym — es wird keine Reputation aufgebaut.", + "failedToGenerateIdentityMessage": "Identität konnte nicht generiert werden. Bitte versuche es erneut.", + "invalidMnemonicMessage": "Ungültige mnemonische Phrase. Bitte überprüfe deine Wörter und versuche es erneut.", + "enterValidMnemonicError": "Gib eine gültige 12- oder 24-Wort-Phrase ein.", + "orderBookRefreshedMessage": "Orderbuch aktualisiert", + "refreshFailedMessage": "Aktualisierung fehlgeschlagen", + "refreshButtonLabel": "Aktualisieren", + "okButtonLabel": "OK", + "moreInformationTooltip": "Weitere Informationen", + "backedUpBadgeLabel": "Gesichert", + "backupBannerTitle": "Sichere deine Reputation", + "backupBannerSubtitle": "Sichere deine 12 Wörter — es dauert 60 Sekunden." } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8e70c598..d33de21c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -658,5 +658,39 @@ "orderNoLongerActive": "This order is no longer active", "@orderNoLongerActive": {"description": "Neutral notice shown when the order reaches a terminal state (canceled, cooperatively canceled, canceled by admin, or expired) while the user is on the pay invoice screen"}, "sessionTimeoutMessage": "No response received, check your connection and try again later", - "@sessionTimeoutMessage": {"description": "Snackbar shown when the Mostro node does not respond to a new order within the timeout — the order was not created"} + "@sessionTimeoutMessage": {"description": "Snackbar shown when the Mostro node does not respond to a new order within the timeout — the order was not created"}, + "noIdentityFoundMessage": "No identity found — try restarting the app.", + "@noIdentityFoundMessage": {"description": "Snackbar shown on the Account screen when no stored identity can be loaded"}, + "failedToLoadSecretWordsMessage": "Failed to load secret words. Please try again.", + "@failedToLoadSecretWordsMessage": {"description": "Snackbar shown when loading the secret words fails on the Account screen"}, + "failedToConfirmBackupMessage": "Failed to confirm backup. Please try again.", + "@failedToConfirmBackupMessage": {"description": "Snackbar shown when confirming the backup fails on the Account screen"}, + "secretWordsInfoContent": "Your 12 secret words are the only way to recover your account. Back them up in a safe place — never share them with anyone.", + "@secretWordsInfoContent": {"description": "Body of the Secret Words info dialog on the Account screen"}, + "privacyModesInfoTitle": "Privacy Modes", + "@privacyModesInfoTitle": {"description": "Title of the privacy modes info dialog on the Account screen"}, + "privacyModesInfoContent": "Reputation mode lets others see your successful trades.\n\nFull privacy mode keeps your activity completely anonymous — no reputation is built.", + "@privacyModesInfoContent": {"description": "Body of the privacy modes info dialog on the Account screen"}, + "failedToGenerateIdentityMessage": "Failed to generate identity. Please try again.", + "@failedToGenerateIdentityMessage": {"description": "Snackbar shown when generating a new identity fails on the Account screen"}, + "invalidMnemonicMessage": "Invalid mnemonic. Please check your words and try again.", + "@invalidMnemonicMessage": {"description": "Snackbar shown when an imported mnemonic is invalid on the Account screen"}, + "enterValidMnemonicError": "Enter a valid 12 or 24 word phrase.", + "@enterValidMnemonicError": {"description": "Validation error shown in the import mnemonic dialog when the phrase is not 12 or 24 words"}, + "orderBookRefreshedMessage": "Order book refreshed", + "@orderBookRefreshedMessage": {"description": "Snackbar confirming the order book was refreshed from the Account screen"}, + "refreshFailedMessage": "Refresh failed", + "@refreshFailedMessage": {"description": "Snackbar shown when refreshing the order book fails on the Account screen"}, + "refreshButtonLabel": "Refresh", + "@refreshButtonLabel": {"description": "Label for the refresh action in the Refresh User dialog"}, + "okButtonLabel": "OK", + "@okButtonLabel": {"description": "Generic OK button label for info dialogs"}, + "moreInformationTooltip": "More information", + "@moreInformationTooltip": {"description": "Tooltip for the info icon buttons on the Account screen"}, + "backedUpBadgeLabel": "Backed up", + "@backedUpBadgeLabel": {"description": "Badge shown in the Secret Words card header once the backup is complete"}, + "backupBannerTitle": "Secure your reputation", + "@backupBannerTitle": {"description": "Title of the backup reminder banner on the Account screen"}, + "backupBannerSubtitle": "Back up your 12 words — it takes 60 seconds.", + "@backupBannerSubtitle": {"description": "Subtitle of the backup reminder banner on the Account screen"} } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 5acb3bc1..db7317cb 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -321,5 +321,22 @@ "payWithLightningWallet": "Pagar con wallet Lightning", "noLightningWalletFound": "No se encontró una wallet Lightning en este dispositivo", "orderNoLongerActive": "Esta orden ya no está activa", - "sessionTimeoutMessage": "No hubo respuesta, verifica tu conexión e inténtalo más tarde" + "sessionTimeoutMessage": "No hubo respuesta, verifica tu conexión e inténtalo más tarde", + "noIdentityFoundMessage": "No se encontró ninguna identidad — intenta reiniciar la app.", + "failedToLoadSecretWordsMessage": "No se pudieron cargar las palabras secretas. Inténtalo de nuevo.", + "failedToConfirmBackupMessage": "No se pudo confirmar el respaldo. Inténtalo de nuevo.", + "secretWordsInfoContent": "Tus 12 palabras secretas son la única forma de recuperar tu cuenta. Respáldalas en un lugar seguro — nunca las compartas con nadie.", + "privacyModesInfoTitle": "Modos de privacidad", + "privacyModesInfoContent": "El modo reputación permite que otros vean tus operaciones exitosas.\n\nEl modo privacidad total mantiene tu actividad completamente anónima — no se construye reputación.", + "failedToGenerateIdentityMessage": "No se pudo generar la identidad. Inténtalo de nuevo.", + "invalidMnemonicMessage": "Mnemónico inválido. Revisa tus palabras e inténtalo de nuevo.", + "enterValidMnemonicError": "Ingresa una frase válida de 12 o 24 palabras.", + "orderBookRefreshedMessage": "Libro de órdenes actualizado", + "refreshFailedMessage": "Error al actualizar", + "refreshButtonLabel": "Actualizar", + "okButtonLabel": "Aceptar", + "moreInformationTooltip": "Más información", + "backedUpBadgeLabel": "Respaldado", + "backupBannerTitle": "Asegura tu reputación", + "backupBannerSubtitle": "Respalda tus 12 palabras — toma 60 segundos." } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 0c263e00..12898070 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -321,5 +321,22 @@ "payWithLightningWallet": "Payer avec un portefeuille Lightning", "noLightningWalletFound": "Aucun portefeuille Lightning trouvé sur cet appareil", "orderNoLongerActive": "Cet ordre n'est plus actif", - "sessionTimeoutMessage": "Aucune réponse reçue, vérifiez votre connexion et réessayez plus tard" + "sessionTimeoutMessage": "Aucune réponse reçue, vérifiez votre connexion et réessayez plus tard", + "noIdentityFoundMessage": "Aucune identité trouvée — essayez de redémarrer l'application.", + "failedToLoadSecretWordsMessage": "Échec du chargement des mots secrets. Veuillez réessayer.", + "failedToConfirmBackupMessage": "Échec de la confirmation de la sauvegarde. Veuillez réessayer.", + "secretWordsInfoContent": "Vos 12 mots secrets sont le seul moyen de récupérer votre compte. Sauvegardez-les dans un endroit sûr — ne les partagez jamais avec personne.", + "privacyModesInfoTitle": "Modes de confidentialité", + "privacyModesInfoContent": "Le mode réputation permet aux autres de voir vos transactions réussies.\n\nLe mode confidentialité totale garde votre activité complètement anonyme — aucune réputation n'est construite.", + "failedToGenerateIdentityMessage": "Échec de la génération de l'identité. Veuillez réessayer.", + "invalidMnemonicMessage": "Phrase mnémonique invalide. Veuillez vérifier vos mots et réessayer.", + "enterValidMnemonicError": "Entrez une phrase valide de 12 ou 24 mots.", + "orderBookRefreshedMessage": "Carnet d'ordres actualisé", + "refreshFailedMessage": "Échec de l'actualisation", + "refreshButtonLabel": "Actualiser", + "okButtonLabel": "OK", + "moreInformationTooltip": "Plus d'informations", + "backedUpBadgeLabel": "Sauvegardé", + "backupBannerTitle": "Sécurisez votre réputation", + "backupBannerSubtitle": "Sauvegardez vos 12 mots — cela prend 60 secondes." } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index c74142fe..5abc303d 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -321,5 +321,22 @@ "payWithLightningWallet": "Paga con wallet Lightning", "noLightningWalletFound": "Nessun wallet Lightning trovato su questo dispositivo", "orderNoLongerActive": "Questo ordine non è più attivo", - "sessionTimeoutMessage": "Nessuna risposta ricevuta, verifica la tua connessione e riprova più tardi" + "sessionTimeoutMessage": "Nessuna risposta ricevuta, verifica la tua connessione e riprova più tardi", + "noIdentityFoundMessage": "Nessuna identità trovata — prova a riavviare l'app.", + "failedToLoadSecretWordsMessage": "Impossibile caricare le parole segrete. Riprova.", + "failedToConfirmBackupMessage": "Impossibile confermare il backup. Riprova.", + "secretWordsInfoContent": "Le tue 12 parole segrete sono l'unico modo per recuperare il tuo account. Salvale in un luogo sicuro — non condividerle mai con nessuno.", + "privacyModesInfoTitle": "Modalità di privacy", + "privacyModesInfoContent": "La modalità reputazione consente ad altri di vedere le tue operazioni riuscite.\n\nLa modalità privacy totale mantiene la tua attività completamente anonima — non viene costruita alcuna reputazione.", + "failedToGenerateIdentityMessage": "Impossibile generare l'identità. Riprova.", + "invalidMnemonicMessage": "Mnemonico non valido. Controlla le tue parole e riprova.", + "enterValidMnemonicError": "Inserisci una frase valida da 12 o 24 parole.", + "orderBookRefreshedMessage": "Book ordini aggiornato", + "refreshFailedMessage": "Aggiornamento non riuscito", + "refreshButtonLabel": "Aggiorna", + "okButtonLabel": "OK", + "moreInformationTooltip": "Maggiori informazioni", + "backedUpBadgeLabel": "Salvato", + "backupBannerTitle": "Proteggi la tua reputazione", + "backupBannerSubtitle": "Salva le tue 12 parole — bastano 60 secondi." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 480d3506..2262b422 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1897,6 +1897,108 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'No response received, check your connection and try again later'** String get sessionTimeoutMessage; + + /// Snackbar shown on the Account screen when no stored identity can be loaded + /// + /// In en, this message translates to: + /// **'No identity found — try restarting the app.'** + String get noIdentityFoundMessage; + + /// Snackbar shown when loading the secret words fails on the Account screen + /// + /// In en, this message translates to: + /// **'Failed to load secret words. Please try again.'** + String get failedToLoadSecretWordsMessage; + + /// Snackbar shown when confirming the backup fails on the Account screen + /// + /// In en, this message translates to: + /// **'Failed to confirm backup. Please try again.'** + String get failedToConfirmBackupMessage; + + /// Body of the Secret Words info dialog on the Account screen + /// + /// In en, this message translates to: + /// **'Your 12 secret words are the only way to recover your account. Back them up in a safe place — never share them with anyone.'** + String get secretWordsInfoContent; + + /// Title of the privacy modes info dialog on the Account screen + /// + /// In en, this message translates to: + /// **'Privacy Modes'** + String get privacyModesInfoTitle; + + /// Body of the privacy modes info dialog on the Account screen + /// + /// In en, this message translates to: + /// **'Reputation mode lets others see your successful trades.\n\nFull privacy mode keeps your activity completely anonymous — no reputation is built.'** + String get privacyModesInfoContent; + + /// Snackbar shown when generating a new identity fails on the Account screen + /// + /// In en, this message translates to: + /// **'Failed to generate identity. Please try again.'** + String get failedToGenerateIdentityMessage; + + /// Snackbar shown when an imported mnemonic is invalid on the Account screen + /// + /// In en, this message translates to: + /// **'Invalid mnemonic. Please check your words and try again.'** + String get invalidMnemonicMessage; + + /// Validation error shown in the import mnemonic dialog when the phrase is not 12 or 24 words + /// + /// In en, this message translates to: + /// **'Enter a valid 12 or 24 word phrase.'** + String get enterValidMnemonicError; + + /// Snackbar confirming the order book was refreshed from the Account screen + /// + /// In en, this message translates to: + /// **'Order book refreshed'** + String get orderBookRefreshedMessage; + + /// Snackbar shown when refreshing the order book fails on the Account screen + /// + /// In en, this message translates to: + /// **'Refresh failed'** + String get refreshFailedMessage; + + /// Label for the refresh action in the Refresh User dialog + /// + /// In en, this message translates to: + /// **'Refresh'** + String get refreshButtonLabel; + + /// Generic OK button label for info dialogs + /// + /// In en, this message translates to: + /// **'OK'** + String get okButtonLabel; + + /// Tooltip for the info icon buttons on the Account screen + /// + /// In en, this message translates to: + /// **'More information'** + String get moreInformationTooltip; + + /// Badge shown in the Secret Words card header once the backup is complete + /// + /// In en, this message translates to: + /// **'Backed up'** + String get backedUpBadgeLabel; + + /// Title of the backup reminder banner on the Account screen + /// + /// In en, this message translates to: + /// **'Secure your reputation'** + String get backupBannerTitle; + + /// Subtitle of the backup reminder banner on the Account screen + /// + /// In en, this message translates to: + /// **'Back up your 12 words — it takes 60 seconds.'** + String get backupBannerSubtitle; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index ebc42fd0..c31da7b3 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -996,4 +996,64 @@ class AppLocalizationsDe extends AppLocalizations { @override String get sessionTimeoutMessage => 'Keine Antwort erhalten; prüfe deine Verbindung und versuche es später erneut'; + + @override + String get noIdentityFoundMessage => + 'Keine Identität gefunden — versuche, die App neu zu starten.'; + + @override + String get failedToLoadSecretWordsMessage => + 'Geheime Wörter konnten nicht geladen werden. Bitte versuche es erneut.'; + + @override + String get failedToConfirmBackupMessage => + 'Sicherung konnte nicht bestätigt werden. Bitte versuche es erneut.'; + + @override + String get secretWordsInfoContent => + 'Deine 12 geheimen Wörter sind die einzige Möglichkeit, dein Konto wiederherzustellen. Sichere sie an einem sicheren Ort — teile sie niemals mit jemandem.'; + + @override + String get privacyModesInfoTitle => 'Datenschutzmodi'; + + @override + String get privacyModesInfoContent => + 'Im Reputationsmodus können andere deine erfolgreichen Trades sehen.\n\nDer vollständige Datenschutzmodus hält deine Aktivität völlig anonym — es wird keine Reputation aufgebaut.'; + + @override + String get failedToGenerateIdentityMessage => + 'Identität konnte nicht generiert werden. Bitte versuche es erneut.'; + + @override + String get invalidMnemonicMessage => + 'Ungültige mnemonische Phrase. Bitte überprüfe deine Wörter und versuche es erneut.'; + + @override + String get enterValidMnemonicError => + 'Gib eine gültige 12- oder 24-Wort-Phrase ein.'; + + @override + String get orderBookRefreshedMessage => 'Orderbuch aktualisiert'; + + @override + String get refreshFailedMessage => 'Aktualisierung fehlgeschlagen'; + + @override + String get refreshButtonLabel => 'Aktualisieren'; + + @override + String get okButtonLabel => 'OK'; + + @override + String get moreInformationTooltip => 'Weitere Informationen'; + + @override + String get backedUpBadgeLabel => 'Gesichert'; + + @override + String get backupBannerTitle => 'Sichere deine Reputation'; + + @override + String get backupBannerSubtitle => + 'Sichere deine 12 Wörter — es dauert 60 Sekunden.'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index ff8451ca..0430d9d6 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -982,4 +982,63 @@ class AppLocalizationsEn extends AppLocalizations { @override String get sessionTimeoutMessage => 'No response received, check your connection and try again later'; + + @override + String get noIdentityFoundMessage => + 'No identity found — try restarting the app.'; + + @override + String get failedToLoadSecretWordsMessage => + 'Failed to load secret words. Please try again.'; + + @override + String get failedToConfirmBackupMessage => + 'Failed to confirm backup. Please try again.'; + + @override + String get secretWordsInfoContent => + 'Your 12 secret words are the only way to recover your account. Back them up in a safe place — never share them with anyone.'; + + @override + String get privacyModesInfoTitle => 'Privacy Modes'; + + @override + String get privacyModesInfoContent => + 'Reputation mode lets others see your successful trades.\n\nFull privacy mode keeps your activity completely anonymous — no reputation is built.'; + + @override + String get failedToGenerateIdentityMessage => + 'Failed to generate identity. Please try again.'; + + @override + String get invalidMnemonicMessage => + 'Invalid mnemonic. Please check your words and try again.'; + + @override + String get enterValidMnemonicError => 'Enter a valid 12 or 24 word phrase.'; + + @override + String get orderBookRefreshedMessage => 'Order book refreshed'; + + @override + String get refreshFailedMessage => 'Refresh failed'; + + @override + String get refreshButtonLabel => 'Refresh'; + + @override + String get okButtonLabel => 'OK'; + + @override + String get moreInformationTooltip => 'More information'; + + @override + String get backedUpBadgeLabel => 'Backed up'; + + @override + String get backupBannerTitle => 'Secure your reputation'; + + @override + String get backupBannerSubtitle => + 'Back up your 12 words — it takes 60 seconds.'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 800f1854..d56750a2 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -996,4 +996,64 @@ class AppLocalizationsEs extends AppLocalizations { @override String get sessionTimeoutMessage => 'No hubo respuesta, verifica tu conexión e inténtalo más tarde'; + + @override + String get noIdentityFoundMessage => + 'No se encontró ninguna identidad — intenta reiniciar la app.'; + + @override + String get failedToLoadSecretWordsMessage => + 'No se pudieron cargar las palabras secretas. Inténtalo de nuevo.'; + + @override + String get failedToConfirmBackupMessage => + 'No se pudo confirmar el respaldo. Inténtalo de nuevo.'; + + @override + String get secretWordsInfoContent => + 'Tus 12 palabras secretas son la única forma de recuperar tu cuenta. Respáldalas en un lugar seguro — nunca las compartas con nadie.'; + + @override + String get privacyModesInfoTitle => 'Modos de privacidad'; + + @override + String get privacyModesInfoContent => + 'El modo reputación permite que otros vean tus operaciones exitosas.\n\nEl modo privacidad total mantiene tu actividad completamente anónima — no se construye reputación.'; + + @override + String get failedToGenerateIdentityMessage => + 'No se pudo generar la identidad. Inténtalo de nuevo.'; + + @override + String get invalidMnemonicMessage => + 'Mnemónico inválido. Revisa tus palabras e inténtalo de nuevo.'; + + @override + String get enterValidMnemonicError => + 'Ingresa una frase válida de 12 o 24 palabras.'; + + @override + String get orderBookRefreshedMessage => 'Libro de órdenes actualizado'; + + @override + String get refreshFailedMessage => 'Error al actualizar'; + + @override + String get refreshButtonLabel => 'Actualizar'; + + @override + String get okButtonLabel => 'Aceptar'; + + @override + String get moreInformationTooltip => 'Más información'; + + @override + String get backedUpBadgeLabel => 'Respaldado'; + + @override + String get backupBannerTitle => 'Asegura tu reputación'; + + @override + String get backupBannerSubtitle => + 'Respalda tus 12 palabras — toma 60 segundos.'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index c2618317..7a3cf2c1 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1000,4 +1000,64 @@ class AppLocalizationsFr extends AppLocalizations { @override String get sessionTimeoutMessage => 'Aucune réponse reçue, vérifiez votre connexion et réessayez plus tard'; + + @override + String get noIdentityFoundMessage => + 'Aucune identité trouvée — essayez de redémarrer l\'application.'; + + @override + String get failedToLoadSecretWordsMessage => + 'Échec du chargement des mots secrets. Veuillez réessayer.'; + + @override + String get failedToConfirmBackupMessage => + 'Échec de la confirmation de la sauvegarde. Veuillez réessayer.'; + + @override + String get secretWordsInfoContent => + 'Vos 12 mots secrets sont le seul moyen de récupérer votre compte. Sauvegardez-les dans un endroit sûr — ne les partagez jamais avec personne.'; + + @override + String get privacyModesInfoTitle => 'Modes de confidentialité'; + + @override + String get privacyModesInfoContent => + 'Le mode réputation permet aux autres de voir vos transactions réussies.\n\nLe mode confidentialité totale garde votre activité complètement anonyme — aucune réputation n\'est construite.'; + + @override + String get failedToGenerateIdentityMessage => + 'Échec de la génération de l\'identité. Veuillez réessayer.'; + + @override + String get invalidMnemonicMessage => + 'Phrase mnémonique invalide. Veuillez vérifier vos mots et réessayer.'; + + @override + String get enterValidMnemonicError => + 'Entrez une phrase valide de 12 ou 24 mots.'; + + @override + String get orderBookRefreshedMessage => 'Carnet d\'ordres actualisé'; + + @override + String get refreshFailedMessage => 'Échec de l\'actualisation'; + + @override + String get refreshButtonLabel => 'Actualiser'; + + @override + String get okButtonLabel => 'OK'; + + @override + String get moreInformationTooltip => 'Plus d\'informations'; + + @override + String get backedUpBadgeLabel => 'Sauvegardé'; + + @override + String get backupBannerTitle => 'Sécurisez votre réputation'; + + @override + String get backupBannerSubtitle => + 'Sauvegardez vos 12 mots — cela prend 60 secondes.'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 5763f346..63bebccd 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -993,4 +993,64 @@ class AppLocalizationsIt extends AppLocalizations { @override String get sessionTimeoutMessage => 'Nessuna risposta ricevuta, verifica la tua connessione e riprova più tardi'; + + @override + String get noIdentityFoundMessage => + 'Nessuna identità trovata — prova a riavviare l\'app.'; + + @override + String get failedToLoadSecretWordsMessage => + 'Impossibile caricare le parole segrete. Riprova.'; + + @override + String get failedToConfirmBackupMessage => + 'Impossibile confermare il backup. Riprova.'; + + @override + String get secretWordsInfoContent => + 'Le tue 12 parole segrete sono l\'unico modo per recuperare il tuo account. Salvale in un luogo sicuro — non condividerle mai con nessuno.'; + + @override + String get privacyModesInfoTitle => 'Modalità di privacy'; + + @override + String get privacyModesInfoContent => + 'La modalità reputazione consente ad altri di vedere le tue operazioni riuscite.\n\nLa modalità privacy totale mantiene la tua attività completamente anonima — non viene costruita alcuna reputazione.'; + + @override + String get failedToGenerateIdentityMessage => + 'Impossibile generare l\'identità. Riprova.'; + + @override + String get invalidMnemonicMessage => + 'Mnemonico non valido. Controlla le tue parole e riprova.'; + + @override + String get enterValidMnemonicError => + 'Inserisci una frase valida da 12 o 24 parole.'; + + @override + String get orderBookRefreshedMessage => 'Book ordini aggiornato'; + + @override + String get refreshFailedMessage => 'Aggiornamento non riuscito'; + + @override + String get refreshButtonLabel => 'Aggiorna'; + + @override + String get okButtonLabel => 'OK'; + + @override + String get moreInformationTooltip => 'Maggiori informazioni'; + + @override + String get backedUpBadgeLabel => 'Salvato'; + + @override + String get backupBannerTitle => 'Proteggi la tua reputazione'; + + @override + String get backupBannerSubtitle => + 'Salva le tue 12 parole — bastano 60 secondi.'; } From 6fabcf001173c10fb02ad54b106e076364a931cb Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 00:41:08 -0300 Subject: [PATCH 04/30] feat: localize backup ritual and trigger sheet strings Replace hardcoded English text in backup_ritual_screen.dart and backup_trigger_sheet.dart with localized strings from AppLocalizations. Adds 28 new l10n keys across en/de/es/fr/it for: --- .../account/screens/backup_ritual_screen.dart | 82 +++++----- .../account/widgets/backup_trigger_sheet.dart | 21 +-- lib/l10n/app_de.arb | 26 +++- lib/l10n/app_en.arb | 56 ++++++- lib/l10n/app_es.arb | 26 +++- lib/l10n/app_fr.arb | 26 +++- lib/l10n/app_it.arb | 26 +++- lib/l10n/app_localizations.dart | 144 ++++++++++++++++++ lib/l10n/app_localizations_de.dart | 86 +++++++++++ lib/l10n/app_localizations_en.dart | 82 ++++++++++ lib/l10n/app_localizations_es.dart | 82 ++++++++++ lib/l10n/app_localizations_fr.dart | 84 ++++++++++ lib/l10n/app_localizations_it.dart | 83 ++++++++++ 13 files changed, 771 insertions(+), 53 deletions(-) diff --git a/lib/features/account/screens/backup_ritual_screen.dart b/lib/features/account/screens/backup_ritual_screen.dart index e9dd234c..46cd5b38 100644 --- a/lib/features/account/screens/backup_ritual_screen.dart +++ b/lib/features/account/screens/backup_ritual_screen.dart @@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/core/services/identity_service.dart'; import 'package:mostro/features/account/providers/backup_reminder_provider.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Fallback decoy words for the verification step, used only in the /// (degenerate) case where the mnemonic itself does not contain enough @@ -79,8 +80,10 @@ class _BackupRitualScreenState extends ConsumerState { if (!mounted) return; if (words.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('No identity found — try restarting the app.'), + SnackBar( + content: Text( + AppLocalizations.of(context).noIdentityFoundMessage, + ), ), ); Navigator.of(context).pop(); @@ -92,8 +95,10 @@ class _BackupRitualScreenState extends ConsumerState { debugPrint('[backup-ritual] failed to load words: $e'); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Failed to load secret words. Please try again.'), + SnackBar( + content: Text( + AppLocalizations.of(context).failedToLoadSecretWordsMessage, + ), ), ); Navigator.of(context).pop(); @@ -172,8 +177,10 @@ class _BackupRitualScreenState extends ConsumerState { debugPrint('[backup-ritual] confirm error: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Failed to save backup status. Please try again.'), + SnackBar( + content: Text( + AppLocalizations.of(context).failedToSaveBackupStatusMessage, + ), ), ); } @@ -202,10 +209,11 @@ class _BackupRitualScreenState extends ConsumerState { final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); final elevated = colors?.backgroundElevated ?? const Color(0xFF2A2D35); + final l10n = AppLocalizations.of(context); final title = switch (_step) { - 0 => 'Step 1 of 3 · Write down your words', - 1 => 'Step 2 of 3 · Verify', - _ => 'Step 3 of 3 · Done', + 0 => l10n.backupRitualStep1Title, + 1 => l10n.backupRitualStep2Title, + _ => l10n.backupRitualStep3Title, }; return Scaffold( @@ -254,6 +262,7 @@ class _BackupRitualScreenState extends ConsumerState { // ── Step 1: show words ────────────────────────────────────────────────── Widget _buildShowWords(ThemeData theme, AppColors? colors) { + final l10n = AppLocalizations.of(context); final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230); final elevated = colors?.backgroundElevated ?? const Color(0xFF2A2D35); @@ -288,16 +297,13 @@ class _BackupRitualScreenState extends ConsumerState { const SizedBox(width: AppSpacing.sm), Expanded( child: Text.rich( - const TextSpan( - text: 'Write them on paper. ', - style: TextStyle(fontWeight: FontWeight.w700), + TextSpan( + text: l10n.backupRitualWarningTitle, + style: const TextStyle(fontWeight: FontWeight.w700), children: [ TextSpan( - text: - "Don't store them in photos, screenshots or the " - 'cloud — anyone with these 12 words can steal ' - 'your reputation.', - style: TextStyle(fontWeight: FontWeight.w400), + text: l10n.backupRitualWarningBody, + style: const TextStyle(fontWeight: FontWeight.w400), ), ], ), @@ -355,7 +361,7 @@ class _BackupRitualScreenState extends ConsumerState { const SizedBox(width: AppSpacing.xs), Flexible( child: Text( - 'These words will be hidden when you leave this screen', + l10n.wordsHiddenOnLeaveNote, style: theme.textTheme.bodySmall! .copyWith(color: textSec, fontSize: 12), ), @@ -371,9 +377,9 @@ class _BackupRitualScreenState extends ConsumerState { FilledButton.icon( onPressed: _startVerification, - icon: const Text( - 'I wrote them down — verify', - style: TextStyle(fontSize: 15, fontWeight: FontWeight.w700), + icon: Text( + l10n.wroteThemDownVerifyButton, + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w700), ), label: const Icon(Icons.arrow_forward, size: 18), style: FilledButton.styleFrom( @@ -393,6 +399,7 @@ class _BackupRitualScreenState extends ConsumerState { // ── Step 2: verify ────────────────────────────────────────────────────── Widget _buildVerify(ThemeData theme, AppColors? colors) { + final l10n = AppLocalizations.of(context); final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230); final elevated = colors?.backgroundElevated ?? const Color(0xFF2A2D35); @@ -409,14 +416,13 @@ class _BackupRitualScreenState extends ConsumerState { children: [ const SizedBox(height: AppSpacing.sm), Text( - 'Tap the correct words', + l10n.tapCorrectWordsTitle, style: theme.textTheme.titleLarge! .copyWith(fontWeight: FontWeight.w700), ), const SizedBox(height: AppSpacing.xs), Text( - 'We ask for 3 at random. If you get them right, we know ' - "they're safely written down.", + l10n.verifyInstructionsBody, style: theme.textTheme.bodySmall!.copyWith(color: textSec, height: 1.5), ), @@ -451,7 +457,7 @@ class _BackupRitualScreenState extends ConsumerState { // Options for the active slot if (hasActiveSlot) ...[ Text( - 'OPTIONS FOR WORD #${_challenge[_activeSlot] + 1}', + l10n.optionsForWordLabel(_challenge[_activeSlot] + 1), style: theme.textTheme.bodySmall!.copyWith( color: textSubtle, fontSize: 12, @@ -485,7 +491,7 @@ class _BackupRitualScreenState extends ConsumerState { if (_wrongPick != null) ...[ const SizedBox(height: AppSpacing.sm), Text( - 'Not quite — check your paper and try again.', + l10n.wrongPickMessage, style: theme.textTheme.bodySmall! .copyWith(color: red, fontSize: 12), ), @@ -497,7 +503,7 @@ class _BackupRitualScreenState extends ConsumerState { Icon(Icons.check_circle_outline, size: 16, color: green), const SizedBox(width: AppSpacing.xs), Text( - 'All 3 words correct!', + l10n.allWordsCorrectMessage, style: theme.textTheme.bodySmall!.copyWith(color: green), ), ], @@ -519,9 +525,9 @@ class _BackupRitualScreenState extends ConsumerState { borderRadius: BorderRadius.circular(AppRadius.card), ), ), - child: const Text( - 'Show words again', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), + child: Text( + l10n.showWordsAgainButton, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), ), ), @@ -545,9 +551,9 @@ class _BackupRitualScreenState extends ConsumerState { height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) - : const Text( - 'Confirm', - style: TextStyle( + : Text( + l10n.confirmButtonLabel, + style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w700, ), @@ -564,6 +570,7 @@ class _BackupRitualScreenState extends ConsumerState { // ── Step 3: done ──────────────────────────────────────────────────────── Widget _buildDone(ThemeData theme, AppColors? colors) { + final l10n = AppLocalizations.of(context); final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); final textSec = colors?.textSecondary ?? const Color(0xFFB0B3C6); @@ -585,15 +592,14 @@ class _BackupRitualScreenState extends ConsumerState { ), const SizedBox(height: AppSpacing.xl), Text( - 'Your account is backed up', + l10n.accountBackedUpTitle, textAlign: TextAlign.center, style: theme.textTheme.titleLarge!.copyWith(fontWeight: FontWeight.w700), ), const SizedBox(height: AppSpacing.sm), Text( - 'Your reputation is safe. If you ever lose your phone, ' - 'restore your account with your 12 words.', + l10n.accountBackedUpBody, textAlign: TextAlign.center, style: theme.textTheme.bodyMedium!.copyWith(color: textSec, height: 1.5), @@ -611,7 +617,7 @@ class _BackupRitualScreenState extends ConsumerState { borderRadius: BorderRadius.circular(14), ), ), - child: const Text('Done'), + child: Text(l10n.done), ), ], ), @@ -765,7 +771,7 @@ class _SlotRow extends StatelessWidget { SizedBox( width: 64, child: Text( - 'Word #$wordNumber', + AppLocalizations.of(context).wordNumberLabel(wordNumber), style: TextStyle( fontFamily: 'monospace', fontSize: 12, diff --git a/lib/features/account/widgets/backup_trigger_sheet.dart b/lib/features/account/widgets/backup_trigger_sheet.dart index f44ecddb..107522d1 100644 --- a/lib/features/account/widgets/backup_trigger_sheet.dart +++ b/lib/features/account/widgets/backup_trigger_sheet.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/account/providers/backup_reminder_provider.dart'; import 'package:mostro/features/account/screens/backup_ritual_screen.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Opens the backup ritual trigger bottom sheet. /// @@ -32,6 +33,7 @@ class BackupTriggerSheet extends ConsumerWidget { final textSec = colors?.textSecondary ?? const Color(0xFFB0B3C6); final textDisabled = colors?.textDisabled ?? const Color(0xFF6C757D); final amber = colors?.warningAmber ?? const Color(0xFFE89C3C); + final l10n = AppLocalizations.of(context); return Container( decoration: BoxDecoration( @@ -83,7 +85,7 @@ class BackupTriggerSheet extends ConsumerWidget { const SizedBox(height: AppSpacing.lg), Text( - 'Secure your reputation', + l10n.backupBannerTitle, textAlign: TextAlign.center, style: theme.textTheme.titleLarge! .copyWith(fontWeight: FontWeight.w700), @@ -91,11 +93,10 @@ class BackupTriggerSheet extends ConsumerWidget { const SizedBox(height: AppSpacing.sm), Text.rich( TextSpan( - text: 'Your reputation lives in a key only you hold. ' - 'If you lose your phone, you lose that reputation — ', + text: l10n.backupTriggerBody, children: [ TextSpan( - text: 'back it up in 60 seconds.', + text: l10n.backupTriggerBodyHighlight, style: TextStyle(color: green, fontWeight: FontWeight.w600), ), @@ -116,10 +117,10 @@ class BackupTriggerSheet extends ConsumerWidget { ), child: Column( children: [ - for (final (n, label) in const [ - (1, 'Write your 12 words down on paper'), - (2, 'We ask for 3 at random to confirm'), - (3, 'Done — your account is secured'), + for (final (n, label) in [ + (1, l10n.backupStepWriteDown), + (2, l10n.backupStepVerifyRandom), + (3, l10n.backupStepSecured), ]) ...[ if (n > 1) const SizedBox(height: AppSpacing.sm), Row( @@ -174,7 +175,7 @@ class BackupTriggerSheet extends ConsumerWidget { borderRadius: BorderRadius.circular(14), ), ), - child: const Text('Back up now'), + child: Text(l10n.backupNowButton), ), const SizedBox(height: AppSpacing.sm), @@ -191,7 +192,7 @@ class BackupTriggerSheet extends ConsumerWidget { minimumSize: const Size.fromHeight(44), textStyle: const TextStyle(fontSize: 13), ), - child: const Text('Remind me tomorrow'), + child: Text(l10n.remindMeTomorrowButton), ), ], ), diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 48b8afc4..b6031e18 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -338,5 +338,29 @@ "moreInformationTooltip": "Weitere Informationen", "backedUpBadgeLabel": "Gesichert", "backupBannerTitle": "Sichere deine Reputation", - "backupBannerSubtitle": "Sichere deine 12 Wörter — es dauert 60 Sekunden." + "backupBannerSubtitle": "Sichere deine 12 Wörter — es dauert 60 Sekunden.", + "failedToSaveBackupStatusMessage": "Sicherungsstatus konnte nicht gespeichert werden. Bitte versuche es erneut.", + "backupRitualStep1Title": "Schritt 1 von 3 · Schreibe deine Wörter auf", + "backupRitualStep2Title": "Schritt 2 von 3 · Überprüfen", + "backupRitualStep3Title": "Schritt 3 von 3 · Fertig", + "backupRitualWarningTitle": "Schreibe sie auf Papier. ", + "backupRitualWarningBody": "Speichere sie nicht in Fotos, Screenshots oder der Cloud — wer diese 12 Wörter hat, kann deine Reputation stehlen.", + "wordsHiddenOnLeaveNote": "Diese Wörter werden ausgeblendet, wenn du diesen Bildschirm verlässt", + "wroteThemDownVerifyButton": "Ich habe sie aufgeschrieben — überprüfen", + "tapCorrectWordsTitle": "Tippe auf die richtigen Wörter", + "verifyInstructionsBody": "Wir fragen 3 zufällig ab. Wenn du sie richtig hast, wissen wir, dass sie sicher aufgeschrieben sind.", + "optionsForWordLabel": "OPTIONEN FÜR WORT #{number}", + "wrongPickMessage": "Nicht ganz — überprüfe dein Papier und versuche es erneut.", + "allWordsCorrectMessage": "Alle 3 Wörter richtig!", + "showWordsAgainButton": "Wörter erneut anzeigen", + "accountBackedUpTitle": "Dein Konto ist gesichert", + "accountBackedUpBody": "Deine Reputation ist sicher. Falls du dein Telefon verlierst, stelle dein Konto mit deinen 12 Wörtern wieder her.", + "wordNumberLabel": "Wort #{number}", + "backupTriggerBody": "Deine Reputation liegt in einem Schlüssel, den nur du besitzt. Wenn du dein Telefon verlierst, verlierst du diese Reputation — ", + "backupTriggerBodyHighlight": "sichere ihn in 60 Sekunden.", + "backupStepWriteDown": "Schreibe deine 12 Wörter auf Papier", + "backupStepVerifyRandom": "Wir fragen 3 zufällig zur Bestätigung ab", + "backupStepSecured": "Fertig — dein Konto ist gesichert", + "backupNowButton": "Jetzt sichern", + "remindMeTomorrowButton": "Erinnere mich morgen" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d33de21c..aa3cfbd0 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -692,5 +692,59 @@ "backupBannerTitle": "Secure your reputation", "@backupBannerTitle": {"description": "Title of the backup reminder banner on the Account screen"}, "backupBannerSubtitle": "Back up your 12 words — it takes 60 seconds.", - "@backupBannerSubtitle": {"description": "Subtitle of the backup reminder banner on the Account screen"} + "@backupBannerSubtitle": {"description": "Subtitle of the backup reminder banner on the Account screen"}, + "failedToSaveBackupStatusMessage": "Failed to save backup status. Please try again.", + "@failedToSaveBackupStatusMessage": {"description": "Snackbar shown when persisting the backup-complete status fails in the backup ritual"}, + "backupRitualStep1Title": "Step 1 of 3 · Write down your words", + "@backupRitualStep1Title": {"description": "App bar title for step 1 of the backup ritual"}, + "backupRitualStep2Title": "Step 2 of 3 · Verify", + "@backupRitualStep2Title": {"description": "App bar title for step 2 of the backup ritual"}, + "backupRitualStep3Title": "Step 3 of 3 · Done", + "@backupRitualStep3Title": {"description": "App bar title for step 3 of the backup ritual"}, + "backupRitualWarningTitle": "Write them on paper. ", + "@backupRitualWarningTitle": {"description": "Bold lead-in of the warning card on the show-words step"}, + "backupRitualWarningBody": "Don't store them in photos, screenshots or the cloud — anyone with these 12 words can steal your reputation.", + "@backupRitualWarningBody": {"description": "Body of the warning card on the show-words step"}, + "wordsHiddenOnLeaveNote": "These words will be hidden when you leave this screen", + "@wordsHiddenOnLeaveNote": {"description": "Note reminding the user the words are hidden when leaving the ritual"}, + "wroteThemDownVerifyButton": "I wrote them down — verify", + "@wroteThemDownVerifyButton": {"description": "Primary button on the show-words step that starts verification"}, + "tapCorrectWordsTitle": "Tap the correct words", + "@tapCorrectWordsTitle": {"description": "Title of the verification step"}, + "verifyInstructionsBody": "We ask for 3 at random. If you get them right, we know they're safely written down.", + "@verifyInstructionsBody": {"description": "Instructions shown on the verification step"}, + "optionsForWordLabel": "OPTIONS FOR WORD #{number}", + "@optionsForWordLabel": { + "description": "Label above the option grid for the challenged word", + "placeholders": {"number": {"type": "int"}} + }, + "wrongPickMessage": "Not quite — check your paper and try again.", + "@wrongPickMessage": {"description": "Error shown when the user taps a wrong verification option"}, + "allWordsCorrectMessage": "All 3 words correct!", + "@allWordsCorrectMessage": {"description": "Success message shown when all challenged words are verified"}, + "showWordsAgainButton": "Show words again", + "@showWordsAgainButton": {"description": "Button that returns from verification to the show-words step"}, + "accountBackedUpTitle": "Your account is backed up", + "@accountBackedUpTitle": {"description": "Title of the final done step of the backup ritual"}, + "accountBackedUpBody": "Your reputation is safe. If you ever lose your phone, restore your account with your 12 words.", + "@accountBackedUpBody": {"description": "Body of the final done step of the backup ritual"}, + "wordNumberLabel": "Word #{number}", + "@wordNumberLabel": { + "description": "Label for a challenged word slot, showing its position in the mnemonic", + "placeholders": {"number": {"type": "int"}} + }, + "backupTriggerBody": "Your reputation lives in a key only you hold. If you lose your phone, you lose that reputation — ", + "@backupTriggerBody": {"description": "Lead-in body text of the backup trigger bottom sheet"}, + "backupTriggerBodyHighlight": "back it up in 60 seconds.", + "@backupTriggerBodyHighlight": {"description": "Highlighted trailing phrase of the backup trigger body"}, + "backupStepWriteDown": "Write your 12 words down on paper", + "@backupStepWriteDown": {"description": "Step 1 label in the backup trigger sheet preview"}, + "backupStepVerifyRandom": "We ask for 3 at random to confirm", + "@backupStepVerifyRandom": {"description": "Step 2 label in the backup trigger sheet preview"}, + "backupStepSecured": "Done — your account is secured", + "@backupStepSecured": {"description": "Step 3 label in the backup trigger sheet preview"}, + "backupNowButton": "Back up now", + "@backupNowButton": {"description": "Primary button in the backup trigger sheet"}, + "remindMeTomorrowButton": "Remind me tomorrow", + "@remindMeTomorrowButton": {"description": "Ghost button in the backup trigger sheet that snoozes the reminder"} } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index db7317cb..74c3560b 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -338,5 +338,29 @@ "moreInformationTooltip": "Más información", "backedUpBadgeLabel": "Respaldado", "backupBannerTitle": "Asegura tu reputación", - "backupBannerSubtitle": "Respalda tus 12 palabras — toma 60 segundos." + "backupBannerSubtitle": "Respalda tus 12 palabras — toma 60 segundos.", + "failedToSaveBackupStatusMessage": "No se pudo guardar el estado del respaldo. Inténtalo de nuevo.", + "backupRitualStep1Title": "Paso 1 de 3 · Anota tus palabras", + "backupRitualStep2Title": "Paso 2 de 3 · Verificar", + "backupRitualStep3Title": "Paso 3 de 3 · Listo", + "backupRitualWarningTitle": "Anótalas en papel. ", + "backupRitualWarningBody": "No las guardes en fotos, capturas de pantalla ni en la nube — cualquiera con estas 12 palabras puede robar tu reputación.", + "wordsHiddenOnLeaveNote": "Estas palabras se ocultarán cuando salgas de esta pantalla", + "wroteThemDownVerifyButton": "Las anoté — verificar", + "tapCorrectWordsTitle": "Toca las palabras correctas", + "verifyInstructionsBody": "Te pedimos 3 al azar. Si las aciertas, sabemos que están bien anotadas.", + "optionsForWordLabel": "OPCIONES PARA LA PALABRA #{number}", + "wrongPickMessage": "Casi — revisa tu papel e inténtalo de nuevo.", + "allWordsCorrectMessage": "¡Las 3 palabras correctas!", + "showWordsAgainButton": "Mostrar palabras de nuevo", + "accountBackedUpTitle": "Tu cuenta está respaldada", + "accountBackedUpBody": "Tu reputación está a salvo. Si alguna vez pierdes tu teléfono, restaura tu cuenta con tus 12 palabras.", + "wordNumberLabel": "Palabra #{number}", + "backupTriggerBody": "Tu reputación vive en una clave que solo tú posees. Si pierdes tu teléfono, pierdes esa reputación — ", + "backupTriggerBodyHighlight": "respáldala en 60 segundos.", + "backupStepWriteDown": "Anota tus 12 palabras en papel", + "backupStepVerifyRandom": "Te pedimos 3 al azar para confirmar", + "backupStepSecured": "Listo — tu cuenta está protegida", + "backupNowButton": "Respaldar ahora", + "remindMeTomorrowButton": "Recuérdamelo mañana" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 12898070..4fc21ec0 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -338,5 +338,29 @@ "moreInformationTooltip": "Plus d'informations", "backedUpBadgeLabel": "Sauvegardé", "backupBannerTitle": "Sécurisez votre réputation", - "backupBannerSubtitle": "Sauvegardez vos 12 mots — cela prend 60 secondes." + "backupBannerSubtitle": "Sauvegardez vos 12 mots — cela prend 60 secondes.", + "failedToSaveBackupStatusMessage": "Échec de l'enregistrement de l'état de la sauvegarde. Veuillez réessayer.", + "backupRitualStep1Title": "Étape 1 sur 3 · Notez vos mots", + "backupRitualStep2Title": "Étape 2 sur 3 · Vérifier", + "backupRitualStep3Title": "Étape 3 sur 3 · Terminé", + "backupRitualWarningTitle": "Notez-les sur papier. ", + "backupRitualWarningBody": "Ne les stockez pas dans des photos, des captures d'écran ou le cloud — quiconque possède ces 12 mots peut voler votre réputation.", + "wordsHiddenOnLeaveNote": "Ces mots seront masqués lorsque vous quitterez cet écran", + "wroteThemDownVerifyButton": "Je les ai notés — vérifier", + "tapCorrectWordsTitle": "Appuyez sur les mots corrects", + "verifyInstructionsBody": "Nous en demandons 3 au hasard. Si vous les trouvez, nous savons qu'ils sont bien notés.", + "optionsForWordLabel": "OPTIONS POUR LE MOT #{number}", + "wrongPickMessage": "Pas tout à fait — vérifiez votre papier et réessayez.", + "allWordsCorrectMessage": "Les 3 mots sont corrects !", + "showWordsAgainButton": "Afficher à nouveau les mots", + "accountBackedUpTitle": "Votre compte est sauvegardé", + "accountBackedUpBody": "Votre réputation est en sécurité. Si vous perdez un jour votre téléphone, restaurez votre compte avec vos 12 mots.", + "wordNumberLabel": "Mot #{number}", + "backupTriggerBody": "Votre réputation réside dans une clé que vous seul détenez. Si vous perdez votre téléphone, vous perdez cette réputation — ", + "backupTriggerBodyHighlight": "sauvegardez-la en 60 secondes.", + "backupStepWriteDown": "Notez vos 12 mots sur papier", + "backupStepVerifyRandom": "Nous en demandons 3 au hasard pour confirmer", + "backupStepSecured": "Terminé — votre compte est sécurisé", + "backupNowButton": "Sauvegarder maintenant", + "remindMeTomorrowButton": "Rappelez-moi demain" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 5abc303d..c4003809 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -338,5 +338,29 @@ "moreInformationTooltip": "Maggiori informazioni", "backedUpBadgeLabel": "Salvato", "backupBannerTitle": "Proteggi la tua reputazione", - "backupBannerSubtitle": "Salva le tue 12 parole — bastano 60 secondi." + "backupBannerSubtitle": "Salva le tue 12 parole — bastano 60 secondi.", + "failedToSaveBackupStatusMessage": "Impossibile salvare lo stato del backup. Riprova.", + "backupRitualStep1Title": "Passo 1 di 3 · Annota le tue parole", + "backupRitualStep2Title": "Passo 2 di 3 · Verifica", + "backupRitualStep3Title": "Passo 3 di 3 · Fatto", + "backupRitualWarningTitle": "Annotale su carta. ", + "backupRitualWarningBody": "Non salvarle in foto, screenshot o nel cloud — chiunque abbia queste 12 parole può rubare la tua reputazione.", + "wordsHiddenOnLeaveNote": "Queste parole verranno nascoste quando lasci questa schermata", + "wroteThemDownVerifyButton": "Le ho annotate — verifica", + "tapCorrectWordsTitle": "Tocca le parole corrette", + "verifyInstructionsBody": "Ne chiediamo 3 a caso. Se le indovini, sappiamo che sono annotate al sicuro.", + "optionsForWordLabel": "OPZIONI PER LA PAROLA #{number}", + "wrongPickMessage": "Non proprio — controlla il tuo foglio e riprova.", + "allWordsCorrectMessage": "Tutte e 3 le parole corrette!", + "showWordsAgainButton": "Mostra di nuovo le parole", + "accountBackedUpTitle": "Il tuo account è salvato", + "accountBackedUpBody": "La tua reputazione è al sicuro. Se dovessi perdere il telefono, ripristina il tuo account con le tue 12 parole.", + "wordNumberLabel": "Parola #{number}", + "backupTriggerBody": "La tua reputazione risiede in una chiave che possiedi solo tu. Se perdi il telefono, perdi quella reputazione — ", + "backupTriggerBodyHighlight": "salvala in 60 secondi.", + "backupStepWriteDown": "Annota le tue 12 parole su carta", + "backupStepVerifyRandom": "Ne chiediamo 3 a caso per confermare", + "backupStepSecured": "Fatto — il tuo account è protetto", + "backupNowButton": "Salva ora", + "remindMeTomorrowButton": "Ricordamelo domani" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 2262b422..aed4a3e5 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1999,6 +1999,150 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Back up your 12 words — it takes 60 seconds.'** String get backupBannerSubtitle; + + /// Snackbar shown when persisting the backup-complete status fails in the backup ritual + /// + /// In en, this message translates to: + /// **'Failed to save backup status. Please try again.'** + String get failedToSaveBackupStatusMessage; + + /// App bar title for step 1 of the backup ritual + /// + /// In en, this message translates to: + /// **'Step 1 of 3 · Write down your words'** + String get backupRitualStep1Title; + + /// App bar title for step 2 of the backup ritual + /// + /// In en, this message translates to: + /// **'Step 2 of 3 · Verify'** + String get backupRitualStep2Title; + + /// App bar title for step 3 of the backup ritual + /// + /// In en, this message translates to: + /// **'Step 3 of 3 · Done'** + String get backupRitualStep3Title; + + /// Bold lead-in of the warning card on the show-words step + /// + /// In en, this message translates to: + /// **'Write them on paper. '** + String get backupRitualWarningTitle; + + /// Body of the warning card on the show-words step + /// + /// In en, this message translates to: + /// **'Don\'t store them in photos, screenshots or the cloud — anyone with these 12 words can steal your reputation.'** + String get backupRitualWarningBody; + + /// Note reminding the user the words are hidden when leaving the ritual + /// + /// In en, this message translates to: + /// **'These words will be hidden when you leave this screen'** + String get wordsHiddenOnLeaveNote; + + /// Primary button on the show-words step that starts verification + /// + /// In en, this message translates to: + /// **'I wrote them down — verify'** + String get wroteThemDownVerifyButton; + + /// Title of the verification step + /// + /// In en, this message translates to: + /// **'Tap the correct words'** + String get tapCorrectWordsTitle; + + /// Instructions shown on the verification step + /// + /// In en, this message translates to: + /// **'We ask for 3 at random. If you get them right, we know they\'re safely written down.'** + String get verifyInstructionsBody; + + /// Label above the option grid for the challenged word + /// + /// In en, this message translates to: + /// **'OPTIONS FOR WORD #{number}'** + String optionsForWordLabel(int number); + + /// Error shown when the user taps a wrong verification option + /// + /// In en, this message translates to: + /// **'Not quite — check your paper and try again.'** + String get wrongPickMessage; + + /// Success message shown when all challenged words are verified + /// + /// In en, this message translates to: + /// **'All 3 words correct!'** + String get allWordsCorrectMessage; + + /// Button that returns from verification to the show-words step + /// + /// In en, this message translates to: + /// **'Show words again'** + String get showWordsAgainButton; + + /// Title of the final done step of the backup ritual + /// + /// In en, this message translates to: + /// **'Your account is backed up'** + String get accountBackedUpTitle; + + /// Body of the final done step of the backup ritual + /// + /// In en, this message translates to: + /// **'Your reputation is safe. If you ever lose your phone, restore your account with your 12 words.'** + String get accountBackedUpBody; + + /// Label for a challenged word slot, showing its position in the mnemonic + /// + /// In en, this message translates to: + /// **'Word #{number}'** + String wordNumberLabel(int number); + + /// Lead-in body text of the backup trigger bottom sheet + /// + /// In en, this message translates to: + /// **'Your reputation lives in a key only you hold. If you lose your phone, you lose that reputation — '** + String get backupTriggerBody; + + /// Highlighted trailing phrase of the backup trigger body + /// + /// In en, this message translates to: + /// **'back it up in 60 seconds.'** + String get backupTriggerBodyHighlight; + + /// Step 1 label in the backup trigger sheet preview + /// + /// In en, this message translates to: + /// **'Write your 12 words down on paper'** + String get backupStepWriteDown; + + /// Step 2 label in the backup trigger sheet preview + /// + /// In en, this message translates to: + /// **'We ask for 3 at random to confirm'** + String get backupStepVerifyRandom; + + /// Step 3 label in the backup trigger sheet preview + /// + /// In en, this message translates to: + /// **'Done — your account is secured'** + String get backupStepSecured; + + /// Primary button in the backup trigger sheet + /// + /// In en, this message translates to: + /// **'Back up now'** + String get backupNowButton; + + /// Ghost button in the backup trigger sheet that snoozes the reminder + /// + /// In en, this message translates to: + /// **'Remind me tomorrow'** + String get remindMeTomorrowButton; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index c31da7b3..8a0872a6 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1056,4 +1056,90 @@ class AppLocalizationsDe extends AppLocalizations { @override String get backupBannerSubtitle => 'Sichere deine 12 Wörter — es dauert 60 Sekunden.'; + + @override + String get failedToSaveBackupStatusMessage => + 'Sicherungsstatus konnte nicht gespeichert werden. Bitte versuche es erneut.'; + + @override + String get backupRitualStep1Title => + 'Schritt 1 von 3 · Schreibe deine Wörter auf'; + + @override + String get backupRitualStep2Title => 'Schritt 2 von 3 · Überprüfen'; + + @override + String get backupRitualStep3Title => 'Schritt 3 von 3 · Fertig'; + + @override + String get backupRitualWarningTitle => 'Schreibe sie auf Papier. '; + + @override + String get backupRitualWarningBody => + 'Speichere sie nicht in Fotos, Screenshots oder der Cloud — wer diese 12 Wörter hat, kann deine Reputation stehlen.'; + + @override + String get wordsHiddenOnLeaveNote => + 'Diese Wörter werden ausgeblendet, wenn du diesen Bildschirm verlässt'; + + @override + String get wroteThemDownVerifyButton => + 'Ich habe sie aufgeschrieben — überprüfen'; + + @override + String get tapCorrectWordsTitle => 'Tippe auf die richtigen Wörter'; + + @override + String get verifyInstructionsBody => + 'Wir fragen 3 zufällig ab. Wenn du sie richtig hast, wissen wir, dass sie sicher aufgeschrieben sind.'; + + @override + String optionsForWordLabel(int number) { + return 'OPTIONEN FÜR WORT #$number'; + } + + @override + String get wrongPickMessage => + 'Nicht ganz — überprüfe dein Papier und versuche es erneut.'; + + @override + String get allWordsCorrectMessage => 'Alle 3 Wörter richtig!'; + + @override + String get showWordsAgainButton => 'Wörter erneut anzeigen'; + + @override + String get accountBackedUpTitle => 'Dein Konto ist gesichert'; + + @override + String get accountBackedUpBody => + 'Deine Reputation ist sicher. Falls du dein Telefon verlierst, stelle dein Konto mit deinen 12 Wörtern wieder her.'; + + @override + String wordNumberLabel(int number) { + return 'Wort #$number'; + } + + @override + String get backupTriggerBody => + 'Deine Reputation liegt in einem Schlüssel, den nur du besitzt. Wenn du dein Telefon verlierst, verlierst du diese Reputation — '; + + @override + String get backupTriggerBodyHighlight => 'sichere ihn in 60 Sekunden.'; + + @override + String get backupStepWriteDown => 'Schreibe deine 12 Wörter auf Papier'; + + @override + String get backupStepVerifyRandom => + 'Wir fragen 3 zufällig zur Bestätigung ab'; + + @override + String get backupStepSecured => 'Fertig — dein Konto ist gesichert'; + + @override + String get backupNowButton => 'Jetzt sichern'; + + @override + String get remindMeTomorrowButton => 'Erinnere mich morgen'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 0430d9d6..97d47b68 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1041,4 +1041,86 @@ class AppLocalizationsEn extends AppLocalizations { @override String get backupBannerSubtitle => 'Back up your 12 words — it takes 60 seconds.'; + + @override + String get failedToSaveBackupStatusMessage => + 'Failed to save backup status. Please try again.'; + + @override + String get backupRitualStep1Title => 'Step 1 of 3 · Write down your words'; + + @override + String get backupRitualStep2Title => 'Step 2 of 3 · Verify'; + + @override + String get backupRitualStep3Title => 'Step 3 of 3 · Done'; + + @override + String get backupRitualWarningTitle => 'Write them on paper. '; + + @override + String get backupRitualWarningBody => + 'Don\'t store them in photos, screenshots or the cloud — anyone with these 12 words can steal your reputation.'; + + @override + String get wordsHiddenOnLeaveNote => + 'These words will be hidden when you leave this screen'; + + @override + String get wroteThemDownVerifyButton => 'I wrote them down — verify'; + + @override + String get tapCorrectWordsTitle => 'Tap the correct words'; + + @override + String get verifyInstructionsBody => + 'We ask for 3 at random. If you get them right, we know they\'re safely written down.'; + + @override + String optionsForWordLabel(int number) { + return 'OPTIONS FOR WORD #$number'; + } + + @override + String get wrongPickMessage => 'Not quite — check your paper and try again.'; + + @override + String get allWordsCorrectMessage => 'All 3 words correct!'; + + @override + String get showWordsAgainButton => 'Show words again'; + + @override + String get accountBackedUpTitle => 'Your account is backed up'; + + @override + String get accountBackedUpBody => + 'Your reputation is safe. If you ever lose your phone, restore your account with your 12 words.'; + + @override + String wordNumberLabel(int number) { + return 'Word #$number'; + } + + @override + String get backupTriggerBody => + 'Your reputation lives in a key only you hold. If you lose your phone, you lose that reputation — '; + + @override + String get backupTriggerBodyHighlight => 'back it up in 60 seconds.'; + + @override + String get backupStepWriteDown => 'Write your 12 words down on paper'; + + @override + String get backupStepVerifyRandom => 'We ask for 3 at random to confirm'; + + @override + String get backupStepSecured => 'Done — your account is secured'; + + @override + String get backupNowButton => 'Back up now'; + + @override + String get remindMeTomorrowButton => 'Remind me tomorrow'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index d56750a2..7d419852 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1056,4 +1056,86 @@ class AppLocalizationsEs extends AppLocalizations { @override String get backupBannerSubtitle => 'Respalda tus 12 palabras — toma 60 segundos.'; + + @override + String get failedToSaveBackupStatusMessage => + 'No se pudo guardar el estado del respaldo. Inténtalo de nuevo.'; + + @override + String get backupRitualStep1Title => 'Paso 1 de 3 · Anota tus palabras'; + + @override + String get backupRitualStep2Title => 'Paso 2 de 3 · Verificar'; + + @override + String get backupRitualStep3Title => 'Paso 3 de 3 · Listo'; + + @override + String get backupRitualWarningTitle => 'Anótalas en papel. '; + + @override + String get backupRitualWarningBody => + 'No las guardes en fotos, capturas de pantalla ni en la nube — cualquiera con estas 12 palabras puede robar tu reputación.'; + + @override + String get wordsHiddenOnLeaveNote => + 'Estas palabras se ocultarán cuando salgas de esta pantalla'; + + @override + String get wroteThemDownVerifyButton => 'Las anoté — verificar'; + + @override + String get tapCorrectWordsTitle => 'Toca las palabras correctas'; + + @override + String get verifyInstructionsBody => + 'Te pedimos 3 al azar. Si las aciertas, sabemos que están bien anotadas.'; + + @override + String optionsForWordLabel(int number) { + return 'OPCIONES PARA LA PALABRA #$number'; + } + + @override + String get wrongPickMessage => 'Casi — revisa tu papel e inténtalo de nuevo.'; + + @override + String get allWordsCorrectMessage => '¡Las 3 palabras correctas!'; + + @override + String get showWordsAgainButton => 'Mostrar palabras de nuevo'; + + @override + String get accountBackedUpTitle => 'Tu cuenta está respaldada'; + + @override + String get accountBackedUpBody => + 'Tu reputación está a salvo. Si alguna vez pierdes tu teléfono, restaura tu cuenta con tus 12 palabras.'; + + @override + String wordNumberLabel(int number) { + return 'Palabra #$number'; + } + + @override + String get backupTriggerBody => + 'Tu reputación vive en una clave que solo tú posees. Si pierdes tu teléfono, pierdes esa reputación — '; + + @override + String get backupTriggerBodyHighlight => 'respáldala en 60 segundos.'; + + @override + String get backupStepWriteDown => 'Anota tus 12 palabras en papel'; + + @override + String get backupStepVerifyRandom => 'Te pedimos 3 al azar para confirmar'; + + @override + String get backupStepSecured => 'Listo — tu cuenta está protegida'; + + @override + String get backupNowButton => 'Respaldar ahora'; + + @override + String get remindMeTomorrowButton => 'Recuérdamelo mañana'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 7a3cf2c1..7f6da884 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1060,4 +1060,88 @@ class AppLocalizationsFr extends AppLocalizations { @override String get backupBannerSubtitle => 'Sauvegardez vos 12 mots — cela prend 60 secondes.'; + + @override + String get failedToSaveBackupStatusMessage => + 'Échec de l\'enregistrement de l\'état de la sauvegarde. Veuillez réessayer.'; + + @override + String get backupRitualStep1Title => 'Étape 1 sur 3 · Notez vos mots'; + + @override + String get backupRitualStep2Title => 'Étape 2 sur 3 · Vérifier'; + + @override + String get backupRitualStep3Title => 'Étape 3 sur 3 · Terminé'; + + @override + String get backupRitualWarningTitle => 'Notez-les sur papier. '; + + @override + String get backupRitualWarningBody => + 'Ne les stockez pas dans des photos, des captures d\'écran ou le cloud — quiconque possède ces 12 mots peut voler votre réputation.'; + + @override + String get wordsHiddenOnLeaveNote => + 'Ces mots seront masqués lorsque vous quitterez cet écran'; + + @override + String get wroteThemDownVerifyButton => 'Je les ai notés — vérifier'; + + @override + String get tapCorrectWordsTitle => 'Appuyez sur les mots corrects'; + + @override + String get verifyInstructionsBody => + 'Nous en demandons 3 au hasard. Si vous les trouvez, nous savons qu\'ils sont bien notés.'; + + @override + String optionsForWordLabel(int number) { + return 'OPTIONS POUR LE MOT #$number'; + } + + @override + String get wrongPickMessage => + 'Pas tout à fait — vérifiez votre papier et réessayez.'; + + @override + String get allWordsCorrectMessage => 'Les 3 mots sont corrects !'; + + @override + String get showWordsAgainButton => 'Afficher à nouveau les mots'; + + @override + String get accountBackedUpTitle => 'Votre compte est sauvegardé'; + + @override + String get accountBackedUpBody => + 'Votre réputation est en sécurité. Si vous perdez un jour votre téléphone, restaurez votre compte avec vos 12 mots.'; + + @override + String wordNumberLabel(int number) { + return 'Mot #$number'; + } + + @override + String get backupTriggerBody => + 'Votre réputation réside dans une clé que vous seul détenez. Si vous perdez votre téléphone, vous perdez cette réputation — '; + + @override + String get backupTriggerBodyHighlight => 'sauvegardez-la en 60 secondes.'; + + @override + String get backupStepWriteDown => 'Notez vos 12 mots sur papier'; + + @override + String get backupStepVerifyRandom => + 'Nous en demandons 3 au hasard pour confirmer'; + + @override + String get backupStepSecured => 'Terminé — votre compte est sécurisé'; + + @override + String get backupNowButton => 'Sauvegarder maintenant'; + + @override + String get remindMeTomorrowButton => 'Rappelez-moi demain'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 63bebccd..ee2050ec 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1053,4 +1053,87 @@ class AppLocalizationsIt extends AppLocalizations { @override String get backupBannerSubtitle => 'Salva le tue 12 parole — bastano 60 secondi.'; + + @override + String get failedToSaveBackupStatusMessage => + 'Impossibile salvare lo stato del backup. Riprova.'; + + @override + String get backupRitualStep1Title => 'Passo 1 di 3 · Annota le tue parole'; + + @override + String get backupRitualStep2Title => 'Passo 2 di 3 · Verifica'; + + @override + String get backupRitualStep3Title => 'Passo 3 di 3 · Fatto'; + + @override + String get backupRitualWarningTitle => 'Annotale su carta. '; + + @override + String get backupRitualWarningBody => + 'Non salvarle in foto, screenshot o nel cloud — chiunque abbia queste 12 parole può rubare la tua reputazione.'; + + @override + String get wordsHiddenOnLeaveNote => + 'Queste parole verranno nascoste quando lasci questa schermata'; + + @override + String get wroteThemDownVerifyButton => 'Le ho annotate — verifica'; + + @override + String get tapCorrectWordsTitle => 'Tocca le parole corrette'; + + @override + String get verifyInstructionsBody => + 'Ne chiediamo 3 a caso. Se le indovini, sappiamo che sono annotate al sicuro.'; + + @override + String optionsForWordLabel(int number) { + return 'OPZIONI PER LA PAROLA #$number'; + } + + @override + String get wrongPickMessage => + 'Non proprio — controlla il tuo foglio e riprova.'; + + @override + String get allWordsCorrectMessage => 'Tutte e 3 le parole corrette!'; + + @override + String get showWordsAgainButton => 'Mostra di nuovo le parole'; + + @override + String get accountBackedUpTitle => 'Il tuo account è salvato'; + + @override + String get accountBackedUpBody => + 'La tua reputazione è al sicuro. Se dovessi perdere il telefono, ripristina il tuo account con le tue 12 parole.'; + + @override + String wordNumberLabel(int number) { + return 'Parola #$number'; + } + + @override + String get backupTriggerBody => + 'La tua reputazione risiede in una chiave che possiedi solo tu. Se perdi il telefono, perdi quella reputazione — '; + + @override + String get backupTriggerBodyHighlight => 'salvala in 60 secondi.'; + + @override + String get backupStepWriteDown => 'Annota le tue 12 parole su carta'; + + @override + String get backupStepVerifyRandom => 'Ne chiediamo 3 a caso per confermare'; + + @override + String get backupStepSecured => 'Fatto — il tuo account è protetto'; + + @override + String get backupNowButton => 'Salva ora'; + + @override + String get remindMeTomorrowButton => 'Ricordamelo domani'; } From 0e42539e8598365baa8b5423164b032b5605960b Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 00:58:51 -0300 Subject: [PATCH 05/30] feat: localize wallet connection screen strings Replace hardcoded English text in connect_wallet_screen.dart and wallet_settings_screen.dart with localized strings from AppLocalizations. Adds 16 new l10n keys across en/de/es/fr/it for error messages, button labels, field labels, and UI text including connection status, wallet info rows, and relay formatting. --- .../screens/connect_wallet_screen.dart | 31 ++++--- .../screens/wallet_settings_screen.dart | 42 +++++---- lib/l10n/app_de.arb | 17 +++- lib/l10n/app_en.arb | 35 +++++++- lib/l10n/app_es.arb | 17 +++- lib/l10n/app_fr.arb | 17 +++- lib/l10n/app_it.arb | 17 +++- lib/l10n/app_localizations.dart | 90 +++++++++++++++++++ lib/l10n/app_localizations_de.dart | 51 +++++++++++ lib/l10n/app_localizations_en.dart | 51 +++++++++++ lib/l10n/app_localizations_es.dart | 51 +++++++++++ lib/l10n/app_localizations_fr.dart | 51 +++++++++++ lib/l10n/app_localizations_it.dart | 51 +++++++++++ 13 files changed, 484 insertions(+), 37 deletions(-) diff --git a/lib/features/settings/screens/connect_wallet_screen.dart b/lib/features/settings/screens/connect_wallet_screen.dart index d7e1cfcb..879dce4b 100644 --- a/lib/features/settings/screens/connect_wallet_screen.dart +++ b/lib/features/settings/screens/connect_wallet_screen.dart @@ -67,8 +67,10 @@ class _ConnectWalletScreenState extends ConsumerState { if (!mounted) return; debugPrint('NWC connection error: $e'); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Connection failed. Please check your NWC URI and try again.'), + SnackBar( + content: Text( + AppLocalizations.of(context).nwcConnectionFailedMessage, + ), ), ); } finally { @@ -93,11 +95,12 @@ class _ConnectWalletScreenState extends ConsumerState { final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230); final inputBg = colors?.backgroundInput ?? const Color(0xFF252A3A); + final l10n = AppLocalizations.of(context); if (_showScanner) { return Scaffold( appBar: AppBar( - title: const Text('Scan QR Code'), + title: Text(l10n.scanQrCodeTitle), leading: BackButton(onPressed: () => setState(() => _showScanner = false)), ), body: PlatformAwareQrScanner( @@ -108,7 +111,7 @@ class _ConnectWalletScreenState extends ConsumerState { } return Scaffold( - appBar: AppBar(title: const Text('Connect Wallet')), + appBar: AppBar(title: Text(l10n.connectWalletTitle)), body: Padding( padding: const EdgeInsets.all(AppSpacing.lg), child: Column( @@ -126,7 +129,7 @@ class _ConnectWalletScreenState extends ConsumerState { ), const SizedBox(height: AppSpacing.md), Text( - 'Connect your Lightning wallet using a\nNostr Wallet Connect (NWC) URI.', + l10n.connectWalletDescription, textAlign: TextAlign.center, style: theme.textTheme.bodyMedium?.copyWith( color: colors?.textSecondary, @@ -153,7 +156,7 @@ class _ConnectWalletScreenState extends ConsumerState { enableIMEPersonalizedLearning: false, decoration: InputDecoration( hintText: 'nostr+walletconnect://...', - labelText: 'NWC URI', + labelText: l10n.nwcUriLabel, floatingLabelBehavior: FloatingLabelBehavior.auto, filled: true, fillColor: inputBg, @@ -185,17 +188,17 @@ class _ConnectWalletScreenState extends ConsumerState { } else { if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( + SnackBar( content: Text( - 'Clipboard does not contain a valid NWC URI.', + l10n.clipboardInvalidNwcUriMessage, ), - duration: Duration(seconds: 2), + duration: const Duration(seconds: 2), ), ); } }, icon: const Icon(Icons.paste, size: 16), - label: const Text('Paste'), + label: Text(l10n.pasteButtonLabel), style: TextButton.styleFrom( foregroundColor: colors?.textSecondary, ), @@ -203,7 +206,7 @@ class _ConnectWalletScreenState extends ConsumerState { TextButton.icon( onPressed: () => setState(() => _showScanner = true), icon: const Icon(Icons.qr_code_scanner, size: 16), - label: const Text('Scan QR'), + label: Text(l10n.scanQrButtonLabel), style: TextButton.styleFrom( foregroundColor: green, ), @@ -237,9 +240,9 @@ class _ConnectWalletScreenState extends ConsumerState { color: Colors.black54, ), ) - : const Text( - 'Connect', - style: TextStyle(fontWeight: FontWeight.bold), + : Text( + l10n.connectButtonLabel, + style: const TextStyle(fontWeight: FontWeight.bold), ), ), diff --git a/lib/features/settings/screens/wallet_settings_screen.dart b/lib/features/settings/screens/wallet_settings_screen.dart index 720d5ddb..0c9e9895 100644 --- a/lib/features/settings/screens/wallet_settings_screen.dart +++ b/lib/features/settings/screens/wallet_settings_screen.dart @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart'; import 'package:mostro/core/app_routes.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/settings/providers/nwc_provider.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/src/rust/api/nwc.dart' as nwc_api; /// Wallet Settings screen — Route `/wallet_settings`. @@ -17,6 +18,7 @@ class WalletSettingsScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final wallet = ref.watch(nwcProvider); + final l10n = AppLocalizations.of(context); final theme = Theme.of(context); final colors = theme.extension(); final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); @@ -24,7 +26,7 @@ class WalletSettingsScreen extends ConsumerWidget { return Scaffold( appBar: AppBar( - title: const Text('Wallet Configuration'), + title: Text(l10n.walletConfigurationTitle), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => @@ -56,7 +58,9 @@ class WalletSettingsScreen extends ConsumerWidget { ref.read(nwcProvider.notifier).setDisconnected(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Wallet disconnected')), + SnackBar( + content: Text(AppLocalizations.of(context).walletDisconnectedMessage), + ), ); } } @@ -83,6 +87,7 @@ class _ConnectedView extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -102,7 +107,7 @@ class _ConnectedView extends StatelessWidget { Icon(Icons.account_balance_wallet, color: green, size: 24), const SizedBox(width: AppSpacing.sm), Text( - wallet.walletName ?? 'NWC Wallet', + wallet.walletName ?? l10n.nwcWalletSettingTitle, style: theme.textTheme.titleMedium?.copyWith( color: colors?.textPrimary, fontWeight: FontWeight.bold, @@ -119,7 +124,7 @@ class _ConnectedView extends StatelessWidget { borderRadius: BorderRadius.circular(12), ), child: Text( - 'Connected', + l10n.connectedBadgeLabel, style: theme.textTheme.labelSmall?.copyWith( color: const Color(0xFF6EE7B7), fontWeight: FontWeight.w600, @@ -135,7 +140,7 @@ class _ConnectedView extends StatelessWidget { // Balance _InfoRow( - label: 'Balance', + label: l10n.balanceLabel, value: wallet.balanceSats != null ? '${wallet.balanceSats} sats' : '—', @@ -147,7 +152,7 @@ class _ConnectedView extends StatelessWidget { // Pubkey (truncated) _InfoRow( - label: 'Pubkey', + label: l10n.pubkeyLabel, value: _truncate(wallet.walletPubkey), colors: colors, theme: theme, @@ -158,8 +163,8 @@ class _ConnectedView extends StatelessWidget { // Relays _InfoRow( - label: wallet.relayUrls.length == 1 ? 'Relay' : 'Relays', - value: _formatRelays(wallet.relayUrls), + label: wallet.relayUrls.length == 1 ? l10n.relayLabel : l10n.relaysLabel, + value: _formatRelays(wallet.relayUrls, l10n), colors: colors, theme: theme, ), @@ -182,9 +187,9 @@ class _ConnectedView extends StatelessWidget { borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text( - 'Disconnect', - style: TextStyle(fontWeight: FontWeight.bold), + child: Text( + l10n.disconnectButtonLabel, + style: const TextStyle(fontWeight: FontWeight.bold), ), ), @@ -198,10 +203,10 @@ class _ConnectedView extends StatelessWidget { return '${s.substring(0, 8)}…${s.substring(s.length - 8)}'; } - String _formatRelays(List relays) { + String _formatRelays(List relays, AppLocalizations l10n) { if (relays.isEmpty) return '—'; if (relays.length == 1) return relays.first; - return '${relays.first} (+${relays.length - 1} more)'; + return '${relays.first} ${l10n.relaysMoreSuffix(relays.length - 1)}'; } } @@ -222,6 +227,7 @@ class _DisconnectedView extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -240,14 +246,14 @@ class _DisconnectedView extends StatelessWidget { ), const SizedBox(height: AppSpacing.md), Text( - 'No wallet connected', + l10n.noWalletConnectedTitle, style: theme.textTheme.titleMedium?.copyWith( color: colors?.textSecondary, ), ), const SizedBox(height: AppSpacing.sm), Text( - 'Connect a wallet to enable automatic Lightning payments.', + l10n.connectWalletPrompt, textAlign: TextAlign.center, style: theme.textTheme.bodySmall?.copyWith( color: colors?.textSubtle, @@ -269,9 +275,9 @@ class _DisconnectedView extends StatelessWidget { borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text( - 'Connect Wallet', - style: TextStyle(fontWeight: FontWeight.bold), + child: Text( + l10n.connectWalletTitle, + style: const TextStyle(fontWeight: FontWeight.bold), ), ), diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b6031e18..e3086d0b 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -362,5 +362,20 @@ "backupStepVerifyRandom": "Wir fragen 3 zufällig zur Bestätigung ab", "backupStepSecured": "Fertig — dein Konto ist gesichert", "backupNowButton": "Jetzt sichern", - "remindMeTomorrowButton": "Erinnere mich morgen" + "remindMeTomorrowButton": "Erinnere mich morgen", + "nwcConnectionFailedMessage": "Verbindung fehlgeschlagen. Bitte überprüfe deine NWC-URI und versuche es erneut.", + "connectWalletDescription": "Verbinde deine Lightning-Wallet über eine\nNostr Wallet Connect (NWC)-URI.", + "nwcUriLabel": "NWC URI", + "clipboardInvalidNwcUriMessage": "Die Zwischenablage enthält keine gültige NWC-URI.", + "scanQrButtonLabel": "QR scannen", + "connectButtonLabel": "Verbinden", + "walletConfigurationTitle": "Wallet-Konfiguration", + "walletDisconnectedMessage": "Wallet getrennt", + "connectedBadgeLabel": "Verbunden", + "balanceLabel": "Guthaben", + "relayLabel": "Relay", + "noWalletConnectedTitle": "Keine Wallet verbunden", + "connectWalletPrompt": "Verbinde eine Wallet, um automatische Lightning-Zahlungen zu aktivieren.", + "disconnectButtonLabel": "Trennen", + "relaysMoreSuffix": "(+{count} weitere)" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index aa3cfbd0..8135c8f7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -746,5 +746,38 @@ "backupNowButton": "Back up now", "@backupNowButton": {"description": "Primary button in the backup trigger sheet"}, "remindMeTomorrowButton": "Remind me tomorrow", - "@remindMeTomorrowButton": {"description": "Ghost button in the backup trigger sheet that snoozes the reminder"} + "@remindMeTomorrowButton": {"description": "Ghost button in the backup trigger sheet that snoozes the reminder"}, + "nwcConnectionFailedMessage": "Connection failed. Please check your NWC URI and try again.", + "@nwcConnectionFailedMessage": {"description": "Snackbar shown when connecting a wallet via NWC fails"}, + "connectWalletDescription": "Connect your Lightning wallet using a\nNostr Wallet Connect (NWC) URI.", + "@connectWalletDescription": {"description": "Description text on the Connect Wallet screen"}, + "nwcUriLabel": "NWC URI", + "@nwcUriLabel": {"description": "Field label for the NWC URI input"}, + "clipboardInvalidNwcUriMessage": "Clipboard does not contain a valid NWC URI.", + "@clipboardInvalidNwcUriMessage": {"description": "Snackbar shown when the clipboard has no valid NWC URI to paste"}, + "scanQrButtonLabel": "Scan QR", + "@scanQrButtonLabel": {"description": "Button that opens the QR scanner on the Connect Wallet screen"}, + "connectButtonLabel": "Connect", + "@connectButtonLabel": {"description": "Primary button on the Connect Wallet screen"}, + "walletConfigurationTitle": "Wallet Configuration", + "@walletConfigurationTitle": {"description": "App bar title of the Wallet Settings screen"}, + "walletDisconnectedMessage": "Wallet disconnected", + "@walletDisconnectedMessage": {"description": "Snackbar shown after disconnecting the wallet"}, + "connectedBadgeLabel": "Connected", + "@connectedBadgeLabel": {"description": "Badge indicating the wallet is connected"}, + "balanceLabel": "Balance", + "@balanceLabel": {"description": "Label for the wallet balance row"}, + "relayLabel": "Relay", + "@relayLabel": {"description": "Label for a single relay row on the Wallet Settings screen"}, + "noWalletConnectedTitle": "No wallet connected", + "@noWalletConnectedTitle": {"description": "Title shown when no wallet is connected"}, + "connectWalletPrompt": "Connect a wallet to enable automatic Lightning payments.", + "@connectWalletPrompt": {"description": "Prompt shown when no wallet is connected"}, + "disconnectButtonLabel": "Disconnect", + "@disconnectButtonLabel": {"description": "Button that disconnects the wallet"}, + "relaysMoreSuffix": "(+{count} more)", + "@relaysMoreSuffix": { + "description": "Suffix showing how many additional relays exist beyond the first", + "placeholders": {"count": {"type": "int"}} + } } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 74c3560b..c4c02401 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -362,5 +362,20 @@ "backupStepVerifyRandom": "Te pedimos 3 al azar para confirmar", "backupStepSecured": "Listo — tu cuenta está protegida", "backupNowButton": "Respaldar ahora", - "remindMeTomorrowButton": "Recuérdamelo mañana" + "remindMeTomorrowButton": "Recuérdamelo mañana", + "nwcConnectionFailedMessage": "La conexión falló. Revisa tu URI de NWC e inténtalo de nuevo.", + "connectWalletDescription": "Conecta tu wallet Lightning usando una\nURI de Nostr Wallet Connect (NWC).", + "nwcUriLabel": "NWC URI", + "clipboardInvalidNwcUriMessage": "El portapapeles no contiene una URI de NWC válida.", + "scanQrButtonLabel": "Escanear QR", + "connectButtonLabel": "Conectar", + "walletConfigurationTitle": "Configuración de wallet", + "walletDisconnectedMessage": "Wallet desconectada", + "connectedBadgeLabel": "Conectada", + "balanceLabel": "Saldo", + "relayLabel": "Relay", + "noWalletConnectedTitle": "No hay wallet conectada", + "connectWalletPrompt": "Conecta una wallet para habilitar pagos Lightning automáticos.", + "disconnectButtonLabel": "Desconectar", + "relaysMoreSuffix": "(+{count} más)" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 4fc21ec0..309bf61f 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -362,5 +362,20 @@ "backupStepVerifyRandom": "Nous en demandons 3 au hasard pour confirmer", "backupStepSecured": "Terminé — votre compte est sécurisé", "backupNowButton": "Sauvegarder maintenant", - "remindMeTomorrowButton": "Rappelez-moi demain" + "remindMeTomorrowButton": "Rappelez-moi demain", + "nwcConnectionFailedMessage": "La connexion a échoué. Veuillez vérifier votre URI NWC et réessayer.", + "connectWalletDescription": "Connectez votre portefeuille Lightning à l'aide d'une\nURI Nostr Wallet Connect (NWC).", + "nwcUriLabel": "NWC URI", + "clipboardInvalidNwcUriMessage": "Le presse-papiers ne contient pas d'URI NWC valide.", + "scanQrButtonLabel": "Scanner QR", + "connectButtonLabel": "Connecter", + "walletConfigurationTitle": "Configuration du portefeuille", + "walletDisconnectedMessage": "Portefeuille déconnecté", + "connectedBadgeLabel": "Connecté", + "balanceLabel": "Solde", + "relayLabel": "Relais", + "noWalletConnectedTitle": "Aucun portefeuille connecté", + "connectWalletPrompt": "Connectez un portefeuille pour activer les paiements Lightning automatiques.", + "disconnectButtonLabel": "Déconnecter", + "relaysMoreSuffix": "(+{count} de plus)" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index c4003809..e5dbb6c1 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -362,5 +362,20 @@ "backupStepVerifyRandom": "Ne chiediamo 3 a caso per confermare", "backupStepSecured": "Fatto — il tuo account è protetto", "backupNowButton": "Salva ora", - "remindMeTomorrowButton": "Ricordamelo domani" + "remindMeTomorrowButton": "Ricordamelo domani", + "nwcConnectionFailedMessage": "Connessione non riuscita. Controlla il tuo URI NWC e riprova.", + "connectWalletDescription": "Connetti il tuo wallet Lightning usando un\nURI Nostr Wallet Connect (NWC).", + "nwcUriLabel": "NWC URI", + "clipboardInvalidNwcUriMessage": "Gli appunti non contengono un URI NWC valido.", + "scanQrButtonLabel": "Scansiona QR", + "connectButtonLabel": "Connetti", + "walletConfigurationTitle": "Configurazione wallet", + "walletDisconnectedMessage": "Wallet disconnesso", + "connectedBadgeLabel": "Connesso", + "balanceLabel": "Saldo", + "relayLabel": "Relay", + "noWalletConnectedTitle": "Nessun wallet connesso", + "connectWalletPrompt": "Connetti un wallet per abilitare i pagamenti Lightning automatici.", + "disconnectButtonLabel": "Disconnetti", + "relaysMoreSuffix": "(+{count} altri)" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index aed4a3e5..8d9dc656 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2143,6 +2143,96 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Remind me tomorrow'** String get remindMeTomorrowButton; + + /// Snackbar shown when connecting a wallet via NWC fails + /// + /// In en, this message translates to: + /// **'Connection failed. Please check your NWC URI and try again.'** + String get nwcConnectionFailedMessage; + + /// Description text on the Connect Wallet screen + /// + /// In en, this message translates to: + /// **'Connect your Lightning wallet using a\nNostr Wallet Connect (NWC) URI.'** + String get connectWalletDescription; + + /// Field label for the NWC URI input + /// + /// In en, this message translates to: + /// **'NWC URI'** + String get nwcUriLabel; + + /// Snackbar shown when the clipboard has no valid NWC URI to paste + /// + /// In en, this message translates to: + /// **'Clipboard does not contain a valid NWC URI.'** + String get clipboardInvalidNwcUriMessage; + + /// Button that opens the QR scanner on the Connect Wallet screen + /// + /// In en, this message translates to: + /// **'Scan QR'** + String get scanQrButtonLabel; + + /// Primary button on the Connect Wallet screen + /// + /// In en, this message translates to: + /// **'Connect'** + String get connectButtonLabel; + + /// App bar title of the Wallet Settings screen + /// + /// In en, this message translates to: + /// **'Wallet Configuration'** + String get walletConfigurationTitle; + + /// Snackbar shown after disconnecting the wallet + /// + /// In en, this message translates to: + /// **'Wallet disconnected'** + String get walletDisconnectedMessage; + + /// Badge indicating the wallet is connected + /// + /// In en, this message translates to: + /// **'Connected'** + String get connectedBadgeLabel; + + /// Label for the wallet balance row + /// + /// In en, this message translates to: + /// **'Balance'** + String get balanceLabel; + + /// Label for a single relay row on the Wallet Settings screen + /// + /// In en, this message translates to: + /// **'Relay'** + String get relayLabel; + + /// Title shown when no wallet is connected + /// + /// In en, this message translates to: + /// **'No wallet connected'** + String get noWalletConnectedTitle; + + /// Prompt shown when no wallet is connected + /// + /// In en, this message translates to: + /// **'Connect a wallet to enable automatic Lightning payments.'** + String get connectWalletPrompt; + + /// Button that disconnects the wallet + /// + /// In en, this message translates to: + /// **'Disconnect'** + String get disconnectButtonLabel; + + /// Suffix showing how many additional relays exist beyond the first + /// + /// In en, this message translates to: + /// **'(+{count} more)'** + String relaysMoreSuffix(int count); } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 8a0872a6..940bab3b 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1142,4 +1142,55 @@ class AppLocalizationsDe extends AppLocalizations { @override String get remindMeTomorrowButton => 'Erinnere mich morgen'; + + @override + String get nwcConnectionFailedMessage => + 'Verbindung fehlgeschlagen. Bitte überprüfe deine NWC-URI und versuche es erneut.'; + + @override + String get connectWalletDescription => + 'Verbinde deine Lightning-Wallet über eine\nNostr Wallet Connect (NWC)-URI.'; + + @override + String get nwcUriLabel => 'NWC URI'; + + @override + String get clipboardInvalidNwcUriMessage => + 'Die Zwischenablage enthält keine gültige NWC-URI.'; + + @override + String get scanQrButtonLabel => 'QR scannen'; + + @override + String get connectButtonLabel => 'Verbinden'; + + @override + String get walletConfigurationTitle => 'Wallet-Konfiguration'; + + @override + String get walletDisconnectedMessage => 'Wallet getrennt'; + + @override + String get connectedBadgeLabel => 'Verbunden'; + + @override + String get balanceLabel => 'Guthaben'; + + @override + String get relayLabel => 'Relay'; + + @override + String get noWalletConnectedTitle => 'Keine Wallet verbunden'; + + @override + String get connectWalletPrompt => + 'Verbinde eine Wallet, um automatische Lightning-Zahlungen zu aktivieren.'; + + @override + String get disconnectButtonLabel => 'Trennen'; + + @override + String relaysMoreSuffix(int count) { + return '(+$count weitere)'; + } } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 97d47b68..f4168360 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1123,4 +1123,55 @@ class AppLocalizationsEn extends AppLocalizations { @override String get remindMeTomorrowButton => 'Remind me tomorrow'; + + @override + String get nwcConnectionFailedMessage => + 'Connection failed. Please check your NWC URI and try again.'; + + @override + String get connectWalletDescription => + 'Connect your Lightning wallet using a\nNostr Wallet Connect (NWC) URI.'; + + @override + String get nwcUriLabel => 'NWC URI'; + + @override + String get clipboardInvalidNwcUriMessage => + 'Clipboard does not contain a valid NWC URI.'; + + @override + String get scanQrButtonLabel => 'Scan QR'; + + @override + String get connectButtonLabel => 'Connect'; + + @override + String get walletConfigurationTitle => 'Wallet Configuration'; + + @override + String get walletDisconnectedMessage => 'Wallet disconnected'; + + @override + String get connectedBadgeLabel => 'Connected'; + + @override + String get balanceLabel => 'Balance'; + + @override + String get relayLabel => 'Relay'; + + @override + String get noWalletConnectedTitle => 'No wallet connected'; + + @override + String get connectWalletPrompt => + 'Connect a wallet to enable automatic Lightning payments.'; + + @override + String get disconnectButtonLabel => 'Disconnect'; + + @override + String relaysMoreSuffix(int count) { + return '(+$count more)'; + } } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 7d419852..c58c84fe 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1138,4 +1138,55 @@ class AppLocalizationsEs extends AppLocalizations { @override String get remindMeTomorrowButton => 'Recuérdamelo mañana'; + + @override + String get nwcConnectionFailedMessage => + 'La conexión falló. Revisa tu URI de NWC e inténtalo de nuevo.'; + + @override + String get connectWalletDescription => + 'Conecta tu wallet Lightning usando una\nURI de Nostr Wallet Connect (NWC).'; + + @override + String get nwcUriLabel => 'NWC URI'; + + @override + String get clipboardInvalidNwcUriMessage => + 'El portapapeles no contiene una URI de NWC válida.'; + + @override + String get scanQrButtonLabel => 'Escanear QR'; + + @override + String get connectButtonLabel => 'Conectar'; + + @override + String get walletConfigurationTitle => 'Configuración de wallet'; + + @override + String get walletDisconnectedMessage => 'Wallet desconectada'; + + @override + String get connectedBadgeLabel => 'Conectada'; + + @override + String get balanceLabel => 'Saldo'; + + @override + String get relayLabel => 'Relay'; + + @override + String get noWalletConnectedTitle => 'No hay wallet conectada'; + + @override + String get connectWalletPrompt => + 'Conecta una wallet para habilitar pagos Lightning automáticos.'; + + @override + String get disconnectButtonLabel => 'Desconectar'; + + @override + String relaysMoreSuffix(int count) { + return '(+$count más)'; + } } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 7f6da884..9b24958d 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1144,4 +1144,55 @@ class AppLocalizationsFr extends AppLocalizations { @override String get remindMeTomorrowButton => 'Rappelez-moi demain'; + + @override + String get nwcConnectionFailedMessage => + 'La connexion a échoué. Veuillez vérifier votre URI NWC et réessayer.'; + + @override + String get connectWalletDescription => + 'Connectez votre portefeuille Lightning à l\'aide d\'une\nURI Nostr Wallet Connect (NWC).'; + + @override + String get nwcUriLabel => 'NWC URI'; + + @override + String get clipboardInvalidNwcUriMessage => + 'Le presse-papiers ne contient pas d\'URI NWC valide.'; + + @override + String get scanQrButtonLabel => 'Scanner QR'; + + @override + String get connectButtonLabel => 'Connecter'; + + @override + String get walletConfigurationTitle => 'Configuration du portefeuille'; + + @override + String get walletDisconnectedMessage => 'Portefeuille déconnecté'; + + @override + String get connectedBadgeLabel => 'Connecté'; + + @override + String get balanceLabel => 'Solde'; + + @override + String get relayLabel => 'Relais'; + + @override + String get noWalletConnectedTitle => 'Aucun portefeuille connecté'; + + @override + String get connectWalletPrompt => + 'Connectez un portefeuille pour activer les paiements Lightning automatiques.'; + + @override + String get disconnectButtonLabel => 'Déconnecter'; + + @override + String relaysMoreSuffix(int count) { + return '(+$count de plus)'; + } } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index ee2050ec..840772ef 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1136,4 +1136,55 @@ class AppLocalizationsIt extends AppLocalizations { @override String get remindMeTomorrowButton => 'Ricordamelo domani'; + + @override + String get nwcConnectionFailedMessage => + 'Connessione non riuscita. Controlla il tuo URI NWC e riprova.'; + + @override + String get connectWalletDescription => + 'Connetti il tuo wallet Lightning usando un\nURI Nostr Wallet Connect (NWC).'; + + @override + String get nwcUriLabel => 'NWC URI'; + + @override + String get clipboardInvalidNwcUriMessage => + 'Gli appunti non contengono un URI NWC valido.'; + + @override + String get scanQrButtonLabel => 'Scansiona QR'; + + @override + String get connectButtonLabel => 'Connetti'; + + @override + String get walletConfigurationTitle => 'Configurazione wallet'; + + @override + String get walletDisconnectedMessage => 'Wallet disconnesso'; + + @override + String get connectedBadgeLabel => 'Connesso'; + + @override + String get balanceLabel => 'Saldo'; + + @override + String get relayLabel => 'Relay'; + + @override + String get noWalletConnectedTitle => 'Nessun wallet connesso'; + + @override + String get connectWalletPrompt => + 'Connetti un wallet per abilitare i pagamenti Lightning automatici.'; + + @override + String get disconnectButtonLabel => 'Disconnetti'; + + @override + String relaysMoreSuffix(int count) { + return '(+$count altri)'; + } } From 3241f927e6bec69a072d0b0e2fc39b96606421cb Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 01:01:40 -0300 Subject: [PATCH 06/30] feat: localize notification settings and currency selector strings Replace hardcoded English text in notification_settings_screen.dart and currency_selector_dialog.dart with localized strings from AppLocalizations. Adds 11 new l10n keys across en/de/es/fr/it for notification toggle titles/subtitles (trade updates, new messages, payment alerts, dispute updates), screen subtitle, and currency search UI (dialog title, search hint, no results message). --- .../screens/notification_settings_screen.dart | 22 ++++--- .../widgets/currency_selector_dialog.dart | 8 ++- lib/l10n/app_de.arb | 13 +++- lib/l10n/app_en.arb | 24 ++++++- lib/l10n/app_es.arb | 13 +++- lib/l10n/app_fr.arb | 13 +++- lib/l10n/app_it.arb | 13 +++- lib/l10n/app_localizations.dart | 66 +++++++++++++++++++ lib/l10n/app_localizations_de.dart | 38 +++++++++++ lib/l10n/app_localizations_en.dart | 38 +++++++++++ lib/l10n/app_localizations_es.dart | 37 +++++++++++ lib/l10n/app_localizations_fr.dart | 37 +++++++++++ lib/l10n/app_localizations_it.dart | 37 +++++++++++ 13 files changed, 341 insertions(+), 18 deletions(-) diff --git a/lib/features/settings/screens/notification_settings_screen.dart b/lib/features/settings/screens/notification_settings_screen.dart index 8fc000fc..79bd2c3b 100644 --- a/lib/features/settings/screens/notification_settings_screen.dart +++ b/lib/features/settings/screens/notification_settings_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Notification preferences screen. /// @@ -63,10 +64,11 @@ class _NotificationSettingsScreenState final colorsRaw = Theme.of(context).extension(); if (colorsRaw == null) throw StateError('AppColors theme extension must be registered'); final colors = colorsRaw; + final l10n = AppLocalizations.of(context); return Scaffold( appBar: AppBar( - title: const Text('Push Notifications'), + title: Text(l10n.pushNotificationsSettingTitle), ), body: ListView( padding: const EdgeInsets.all(AppSpacing.lg), @@ -74,7 +76,7 @@ class _NotificationSettingsScreenState Padding( padding: const EdgeInsets.only(bottom: AppSpacing.md), child: Text( - 'Choose which events trigger push notifications.', + l10n.chooseNotificationEventsSubtitle, style: Theme.of(context) .textTheme .bodyMedium @@ -85,8 +87,8 @@ class _NotificationSettingsScreenState context, colors, icon: Icons.swap_horiz, - title: 'Trade updates', - subtitle: 'Status changes in your active trades', + title: l10n.notifTradeUpdatesTitle, + subtitle: l10n.notifTradeUpdatesSubtitle, value: _tradeUpdates, onChanged: (v) { setState(() => _tradeUpdates = v); @@ -97,8 +99,8 @@ class _NotificationSettingsScreenState context, colors, icon: Icons.chat_bubble_outline, - title: 'New messages', - subtitle: 'Messages from your trade counterparty', + title: l10n.notifNewMessagesTitle, + subtitle: l10n.notifNewMessagesSubtitle, value: _newMessages, onChanged: (v) { setState(() => _newMessages = v); @@ -109,8 +111,8 @@ class _NotificationSettingsScreenState context, colors, icon: Icons.bolt, - title: 'Payment alerts', - subtitle: 'Lightning payment confirmations and failures', + title: l10n.notifPaymentAlertsTitle, + subtitle: l10n.notifPaymentAlertsSubtitle, value: _paymentAlerts, onChanged: (v) { setState(() => _paymentAlerts = v); @@ -121,8 +123,8 @@ class _NotificationSettingsScreenState context, colors, icon: Icons.gavel_outlined, - title: 'Dispute updates', - subtitle: 'Admin actions and dispute resolutions', + title: l10n.notifDisputeUpdatesTitle, + subtitle: l10n.notifDisputeUpdatesSubtitle, value: _disputeUpdates, onChanged: (v) { setState(() => _disputeUpdates = v); diff --git a/lib/features/settings/widgets/currency_selector_dialog.dart b/lib/features/settings/widgets/currency_selector_dialog.dart index 197ef840..66e4a590 100644 --- a/lib/features/settings/widgets/currency_selector_dialog.dart +++ b/lib/features/settings/widgets/currency_selector_dialog.dart @@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/settings/providers/settings_provider.dart'; +import 'package:mostro/l10n/app_localizations.dart'; // ── Model ───────────────────────────────────────────────────────────────────── @@ -99,10 +100,11 @@ class _CurrencySelectorDialogState Widget build(BuildContext context) { final colors = Theme.of(context).extension(); if (colors == null) throw StateError('AppColors theme extension must be registered'); + final l10n = AppLocalizations.of(context); return Scaffold( appBar: AppBar( - title: const Text('Select Currency'), + title: Text(l10n.selectCurrencyDialogTitle), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.of(context).pop(), @@ -115,7 +117,7 @@ class _CurrencySelectorDialogState child: TextField( controller: _searchController, decoration: InputDecoration( - hintText: 'Search currencies…', + hintText: l10n.searchCurrenciesHint, prefixIcon: Icon(Icons.search, color: colors.textSubtle), ), ), @@ -126,7 +128,7 @@ class _CurrencySelectorDialogState : _filtered.isEmpty ? Center( child: Text( - 'No currencies found', + l10n.noCurrenciesFoundMessage, style: Theme.of(context).textTheme.bodyMedium, ), ) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index e3086d0b..a88aa4fc 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -377,5 +377,16 @@ "noWalletConnectedTitle": "Keine Wallet verbunden", "connectWalletPrompt": "Verbinde eine Wallet, um automatische Lightning-Zahlungen zu aktivieren.", "disconnectButtonLabel": "Trennen", - "relaysMoreSuffix": "(+{count} weitere)" + "relaysMoreSuffix": "(+{count} weitere)", + "chooseNotificationEventsSubtitle": "Wähle, welche Ereignisse Push-Benachrichtigungen auslösen.", + "notifTradeUpdatesTitle": "Trade-Updates", + "notifTradeUpdatesSubtitle": "Statusänderungen bei deinen aktiven Trades", + "notifNewMessagesTitle": "Neue Nachrichten", + "notifNewMessagesSubtitle": "Nachrichten von deiner Handelsgegenpartei", + "notifPaymentAlertsTitle": "Zahlungshinweise", + "notifPaymentAlertsSubtitle": "Bestätigungen und Fehler von Lightning-Zahlungen", + "notifDisputeUpdatesTitle": "Streitfall-Updates", + "notifDisputeUpdatesSubtitle": "Admin-Aktionen und Streitbeilegungen", + "searchCurrenciesHint": "Währungen suchen…", + "noCurrenciesFoundMessage": "Keine Währungen gefunden" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8135c8f7..2433449c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -779,5 +779,27 @@ "@relaysMoreSuffix": { "description": "Suffix showing how many additional relays exist beyond the first", "placeholders": {"count": {"type": "int"}} - } + }, + "chooseNotificationEventsSubtitle": "Choose which events trigger push notifications.", + "@chooseNotificationEventsSubtitle": {"description": "Subtitle on the notification settings screen"}, + "notifTradeUpdatesTitle": "Trade updates", + "@notifTradeUpdatesTitle": {"description": "Title of the trade updates notification toggle"}, + "notifTradeUpdatesSubtitle": "Status changes in your active trades", + "@notifTradeUpdatesSubtitle": {"description": "Subtitle of the trade updates notification toggle"}, + "notifNewMessagesTitle": "New messages", + "@notifNewMessagesTitle": {"description": "Title of the new messages notification toggle"}, + "notifNewMessagesSubtitle": "Messages from your trade counterparty", + "@notifNewMessagesSubtitle": {"description": "Subtitle of the new messages notification toggle"}, + "notifPaymentAlertsTitle": "Payment alerts", + "@notifPaymentAlertsTitle": {"description": "Title of the payment alerts notification toggle"}, + "notifPaymentAlertsSubtitle": "Lightning payment confirmations and failures", + "@notifPaymentAlertsSubtitle": {"description": "Subtitle of the payment alerts notification toggle"}, + "notifDisputeUpdatesTitle": "Dispute updates", + "@notifDisputeUpdatesTitle": {"description": "Title of the dispute updates notification toggle"}, + "notifDisputeUpdatesSubtitle": "Admin actions and dispute resolutions", + "@notifDisputeUpdatesSubtitle": {"description": "Subtitle of the dispute updates notification toggle"}, + "searchCurrenciesHint": "Search currencies…", + "@searchCurrenciesHint": {"description": "Hint text of the currency search field"}, + "noCurrenciesFoundMessage": "No currencies found", + "@noCurrenciesFoundMessage": {"description": "Message shown when no currencies match the search"} } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index c4c02401..c4e8bd7d 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -377,5 +377,16 @@ "noWalletConnectedTitle": "No hay wallet conectada", "connectWalletPrompt": "Conecta una wallet para habilitar pagos Lightning automáticos.", "disconnectButtonLabel": "Desconectar", - "relaysMoreSuffix": "(+{count} más)" + "relaysMoreSuffix": "(+{count} más)", + "chooseNotificationEventsSubtitle": "Elige qué eventos activan las notificaciones push.", + "notifTradeUpdatesTitle": "Actualizaciones de operaciones", + "notifTradeUpdatesSubtitle": "Cambios de estado en tus operaciones activas", + "notifNewMessagesTitle": "Nuevos mensajes", + "notifNewMessagesSubtitle": "Mensajes de tu contraparte", + "notifPaymentAlertsTitle": "Alertas de pago", + "notifPaymentAlertsSubtitle": "Confirmaciones y fallos de pagos Lightning", + "notifDisputeUpdatesTitle": "Actualizaciones de disputas", + "notifDisputeUpdatesSubtitle": "Acciones de administradores y resoluciones de disputas", + "searchCurrenciesHint": "Buscar monedas…", + "noCurrenciesFoundMessage": "No se encontraron monedas" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 309bf61f..2d732a40 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -377,5 +377,16 @@ "noWalletConnectedTitle": "Aucun portefeuille connecté", "connectWalletPrompt": "Connectez un portefeuille pour activer les paiements Lightning automatiques.", "disconnectButtonLabel": "Déconnecter", - "relaysMoreSuffix": "(+{count} de plus)" + "relaysMoreSuffix": "(+{count} de plus)", + "chooseNotificationEventsSubtitle": "Choisissez quels événements déclenchent les notifications push.", + "notifTradeUpdatesTitle": "Mises à jour des transactions", + "notifTradeUpdatesSubtitle": "Changements de statut de vos transactions actives", + "notifNewMessagesTitle": "Nouveaux messages", + "notifNewMessagesSubtitle": "Messages de votre contrepartie", + "notifPaymentAlertsTitle": "Alertes de paiement", + "notifPaymentAlertsSubtitle": "Confirmations et échecs de paiements Lightning", + "notifDisputeUpdatesTitle": "Mises à jour des litiges", + "notifDisputeUpdatesSubtitle": "Actions des administrateurs et résolutions de litiges", + "searchCurrenciesHint": "Rechercher des devises…", + "noCurrenciesFoundMessage": "Aucune devise trouvée" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index e5dbb6c1..5635410e 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -377,5 +377,16 @@ "noWalletConnectedTitle": "Nessun wallet connesso", "connectWalletPrompt": "Connetti un wallet per abilitare i pagamenti Lightning automatici.", "disconnectButtonLabel": "Disconnetti", - "relaysMoreSuffix": "(+{count} altri)" + "relaysMoreSuffix": "(+{count} altri)", + "chooseNotificationEventsSubtitle": "Scegli quali eventi attivano le notifiche push.", + "notifTradeUpdatesTitle": "Aggiornamenti operazioni", + "notifTradeUpdatesSubtitle": "Cambiamenti di stato nelle tue operazioni attive", + "notifNewMessagesTitle": "Nuovi messaggi", + "notifNewMessagesSubtitle": "Messaggi dalla tua controparte", + "notifPaymentAlertsTitle": "Avvisi di pagamento", + "notifPaymentAlertsSubtitle": "Conferme ed errori dei pagamenti Lightning", + "notifDisputeUpdatesTitle": "Aggiornamenti dispute", + "notifDisputeUpdatesSubtitle": "Azioni degli amministratori e risoluzioni delle dispute", + "searchCurrenciesHint": "Cerca valute…", + "noCurrenciesFoundMessage": "Nessuna valuta trovata" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 8d9dc656..f2e35236 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2233,6 +2233,72 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'(+{count} more)'** String relaysMoreSuffix(int count); + + /// Subtitle on the notification settings screen + /// + /// In en, this message translates to: + /// **'Choose which events trigger push notifications.'** + String get chooseNotificationEventsSubtitle; + + /// Title of the trade updates notification toggle + /// + /// In en, this message translates to: + /// **'Trade updates'** + String get notifTradeUpdatesTitle; + + /// Subtitle of the trade updates notification toggle + /// + /// In en, this message translates to: + /// **'Status changes in your active trades'** + String get notifTradeUpdatesSubtitle; + + /// Title of the new messages notification toggle + /// + /// In en, this message translates to: + /// **'New messages'** + String get notifNewMessagesTitle; + + /// Subtitle of the new messages notification toggle + /// + /// In en, this message translates to: + /// **'Messages from your trade counterparty'** + String get notifNewMessagesSubtitle; + + /// Title of the payment alerts notification toggle + /// + /// In en, this message translates to: + /// **'Payment alerts'** + String get notifPaymentAlertsTitle; + + /// Subtitle of the payment alerts notification toggle + /// + /// In en, this message translates to: + /// **'Lightning payment confirmations and failures'** + String get notifPaymentAlertsSubtitle; + + /// Title of the dispute updates notification toggle + /// + /// In en, this message translates to: + /// **'Dispute updates'** + String get notifDisputeUpdatesTitle; + + /// Subtitle of the dispute updates notification toggle + /// + /// In en, this message translates to: + /// **'Admin actions and dispute resolutions'** + String get notifDisputeUpdatesSubtitle; + + /// Hint text of the currency search field + /// + /// In en, this message translates to: + /// **'Search currencies…'** + String get searchCurrenciesHint; + + /// Message shown when no currencies match the search + /// + /// In en, this message translates to: + /// **'No currencies found'** + String get noCurrenciesFoundMessage; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 940bab3b..e114cdb5 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1193,4 +1193,42 @@ class AppLocalizationsDe extends AppLocalizations { String relaysMoreSuffix(int count) { return '(+$count weitere)'; } + + @override + String get chooseNotificationEventsSubtitle => + 'Wähle, welche Ereignisse Push-Benachrichtigungen auslösen.'; + + @override + String get notifTradeUpdatesTitle => 'Trade-Updates'; + + @override + String get notifTradeUpdatesSubtitle => + 'Statusänderungen bei deinen aktiven Trades'; + + @override + String get notifNewMessagesTitle => 'Neue Nachrichten'; + + @override + String get notifNewMessagesSubtitle => + 'Nachrichten von deiner Handelsgegenpartei'; + + @override + String get notifPaymentAlertsTitle => 'Zahlungshinweise'; + + @override + String get notifPaymentAlertsSubtitle => + 'Bestätigungen und Fehler von Lightning-Zahlungen'; + + @override + String get notifDisputeUpdatesTitle => 'Streitfall-Updates'; + + @override + String get notifDisputeUpdatesSubtitle => + 'Admin-Aktionen und Streitbeilegungen'; + + @override + String get searchCurrenciesHint => 'Währungen suchen…'; + + @override + String get noCurrenciesFoundMessage => 'Keine Währungen gefunden'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f4168360..e45e7d8e 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1174,4 +1174,42 @@ class AppLocalizationsEn extends AppLocalizations { String relaysMoreSuffix(int count) { return '(+$count more)'; } + + @override + String get chooseNotificationEventsSubtitle => + 'Choose which events trigger push notifications.'; + + @override + String get notifTradeUpdatesTitle => 'Trade updates'; + + @override + String get notifTradeUpdatesSubtitle => + 'Status changes in your active trades'; + + @override + String get notifNewMessagesTitle => 'New messages'; + + @override + String get notifNewMessagesSubtitle => + 'Messages from your trade counterparty'; + + @override + String get notifPaymentAlertsTitle => 'Payment alerts'; + + @override + String get notifPaymentAlertsSubtitle => + 'Lightning payment confirmations and failures'; + + @override + String get notifDisputeUpdatesTitle => 'Dispute updates'; + + @override + String get notifDisputeUpdatesSubtitle => + 'Admin actions and dispute resolutions'; + + @override + String get searchCurrenciesHint => 'Search currencies…'; + + @override + String get noCurrenciesFoundMessage => 'No currencies found'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index c58c84fe..f4a4944d 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1189,4 +1189,41 @@ class AppLocalizationsEs extends AppLocalizations { String relaysMoreSuffix(int count) { return '(+$count más)'; } + + @override + String get chooseNotificationEventsSubtitle => + 'Elige qué eventos activan las notificaciones push.'; + + @override + String get notifTradeUpdatesTitle => 'Actualizaciones de operaciones'; + + @override + String get notifTradeUpdatesSubtitle => + 'Cambios de estado en tus operaciones activas'; + + @override + String get notifNewMessagesTitle => 'Nuevos mensajes'; + + @override + String get notifNewMessagesSubtitle => 'Mensajes de tu contraparte'; + + @override + String get notifPaymentAlertsTitle => 'Alertas de pago'; + + @override + String get notifPaymentAlertsSubtitle => + 'Confirmaciones y fallos de pagos Lightning'; + + @override + String get notifDisputeUpdatesTitle => 'Actualizaciones de disputas'; + + @override + String get notifDisputeUpdatesSubtitle => + 'Acciones de administradores y resoluciones de disputas'; + + @override + String get searchCurrenciesHint => 'Buscar monedas…'; + + @override + String get noCurrenciesFoundMessage => 'No se encontraron monedas'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 9b24958d..1f1967ec 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1195,4 +1195,41 @@ class AppLocalizationsFr extends AppLocalizations { String relaysMoreSuffix(int count) { return '(+$count de plus)'; } + + @override + String get chooseNotificationEventsSubtitle => + 'Choisissez quels événements déclenchent les notifications push.'; + + @override + String get notifTradeUpdatesTitle => 'Mises à jour des transactions'; + + @override + String get notifTradeUpdatesSubtitle => + 'Changements de statut de vos transactions actives'; + + @override + String get notifNewMessagesTitle => 'Nouveaux messages'; + + @override + String get notifNewMessagesSubtitle => 'Messages de votre contrepartie'; + + @override + String get notifPaymentAlertsTitle => 'Alertes de paiement'; + + @override + String get notifPaymentAlertsSubtitle => + 'Confirmations et échecs de paiements Lightning'; + + @override + String get notifDisputeUpdatesTitle => 'Mises à jour des litiges'; + + @override + String get notifDisputeUpdatesSubtitle => + 'Actions des administrateurs et résolutions de litiges'; + + @override + String get searchCurrenciesHint => 'Rechercher des devises…'; + + @override + String get noCurrenciesFoundMessage => 'Aucune devise trouvée'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 840772ef..7e2c4ec3 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1187,4 +1187,41 @@ class AppLocalizationsIt extends AppLocalizations { String relaysMoreSuffix(int count) { return '(+$count altri)'; } + + @override + String get chooseNotificationEventsSubtitle => + 'Scegli quali eventi attivano le notifiche push.'; + + @override + String get notifTradeUpdatesTitle => 'Aggiornamenti operazioni'; + + @override + String get notifTradeUpdatesSubtitle => + 'Cambiamenti di stato nelle tue operazioni attive'; + + @override + String get notifNewMessagesTitle => 'Nuovi messaggi'; + + @override + String get notifNewMessagesSubtitle => 'Messaggi dalla tua controparte'; + + @override + String get notifPaymentAlertsTitle => 'Avvisi di pagamento'; + + @override + String get notifPaymentAlertsSubtitle => + 'Conferme ed errori dei pagamenti Lightning'; + + @override + String get notifDisputeUpdatesTitle => 'Aggiornamenti dispute'; + + @override + String get notifDisputeUpdatesSubtitle => + 'Azioni degli amministratori e risoluzioni delle dispute'; + + @override + String get searchCurrenciesHint => 'Cerca valute…'; + + @override + String get noCurrenciesFoundMessage => 'Nessuna valuta trovata'; } From b91f37739fa45709f074c1e6def41130643bc801 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 01:08:40 -0300 Subject: [PATCH 07/30] feat: localize log report and Mostro node selector strings Replace hardcoded English text in log_report_screen.dart and mostro_node_selector.dart with localized strings from AppLocalizations. Adds 15 new l10n keys across en/de/es/fr/it for: --- .../settings/screens/log_report_screen.dart | 22 +++-- .../widgets/mostro_node_selector.dart | 33 +++++--- lib/l10n/app_de.arb | 17 +++- lib/l10n/app_en.arb | 32 +++++++- lib/l10n/app_es.arb | 17 +++- lib/l10n/app_fr.arb | 17 +++- lib/l10n/app_it.arb | 17 +++- lib/l10n/app_localizations.dart | 82 ++++++++++++++++++- lib/l10n/app_localizations_de.dart | 43 ++++++++++ lib/l10n/app_localizations_en.dart | 40 +++++++++ lib/l10n/app_localizations_es.dart | 42 ++++++++++ lib/l10n/app_localizations_fr.dart | 42 ++++++++++ lib/l10n/app_localizations_it.dart | 44 +++++++++- 13 files changed, 422 insertions(+), 26 deletions(-) diff --git a/lib/features/settings/screens/log_report_screen.dart b/lib/features/settings/screens/log_report_screen.dart index 5af6ddfc..980e5ecc 100644 --- a/lib/features/settings/screens/log_report_screen.dart +++ b/lib/features/settings/screens/log_report_screen.dart @@ -5,6 +5,7 @@ import 'package:share_plus/share_plus.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/settings/providers/log_provider.dart'; import 'package:mostro/features/settings/providers/settings_provider.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/src/rust/api/types.dart'; // ── Screen ──────────────────────────────────────────────────────────────────── @@ -23,18 +24,21 @@ class _LogReportScreenState extends ConsumerState { final colorsRaw = Theme.of(context).extension(); if (colorsRaw == null) throw StateError('AppColors theme extension must be registered'); final colors = colorsRaw; + final l10n = AppLocalizations.of(context); final logAsync = ref.watch(logEntriesProvider); final entries = logAsync.valueOrNull ?? const []; return Scaffold( appBar: AppBar( - title: const Text('Log Report'), + title: Text(l10n.logReportSettingTitle), actions: [ // Share logs — disabled when no entries exist. IconButton( icon: const Icon(Icons.share_outlined), - tooltip: entries.isNotEmpty ? 'Share logs' : 'No logs to share', + tooltip: entries.isNotEmpty + ? l10n.shareLogsTooltip + : l10n.noLogsToShareTooltip, onPressed: entries.isNotEmpty ? () => _shareLogs(entries) : null, ), // Toggle logging @@ -45,7 +49,9 @@ class _LogReportScreenState extends ConsumerState { : Icons.toggle_off_outlined, color: loggingEnabled ? colors.mostroGreen : colors.textDisabled, ), - tooltip: loggingEnabled ? 'Disable logging' : 'Enable logging', + tooltip: loggingEnabled + ? l10n.disableLoggingTooltip + : l10n.enableLoggingTooltip, onPressed: () { ref .read(settingsProvider.notifier) @@ -79,7 +85,9 @@ class _LogReportScreenState extends ConsumerState { ), const SizedBox(width: AppSpacing.sm), Text( - loggingEnabled ? 'Logging enabled' : 'Logging disabled', + loggingEnabled + ? l10n.loggingEnabledStatus + : l10n.loggingDisabledStatus, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: loggingEnabled ? colors.mostroGreen @@ -94,7 +102,7 @@ class _LogReportScreenState extends ConsumerState { child: entries.isEmpty ? Center( child: Text( - 'No log entries', + l10n.noLogEntriesMessage, style: Theme.of(context).textTheme.bodyMedium, ), ) @@ -133,7 +141,9 @@ class _LogReportScreenState extends ConsumerState { debugPrint('Failed to share logs: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Failed to share logs')), + SnackBar( + content: Text(AppLocalizations.of(context).failedToShareLogsMessage), + ), ); } } diff --git a/lib/features/settings/widgets/mostro_node_selector.dart b/lib/features/settings/widgets/mostro_node_selector.dart index 5b3ba740..42a1bb0a 100644 --- a/lib/features/settings/widgets/mostro_node_selector.dart +++ b/lib/features/settings/widgets/mostro_node_selector.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/core/mostro_defaults.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/src/rust/api/settings.dart' as settings_api; // ── Provider for current Mostro node pubkey ─────────────────────────────────── @@ -72,7 +73,9 @@ class _MostroNodeSelectorState extends ConsumerState { debugPrint('[MostroNodeSelector] setActiveMostroNode(default) failed: $e'); ref.read(mostroPubkeyProvider.notifier).state = previous; if (!mounted) return; - setState(() => _errorText = 'Failed to reset node'); + setState( + () => _errorText = AppLocalizations.of(context).failedToResetNodeMessage, + ); } } @@ -83,7 +86,9 @@ class _MostroNodeSelectorState extends ConsumerState { return; } if (!_hexRegex.hasMatch(input)) { - setState(() => _errorText = 'Must be a 64-character hex string'); + setState( + () => _errorText = AppLocalizations.of(context).invalidHexPubkey, + ); return; } final pubkey = input.toLowerCase(); @@ -97,7 +102,10 @@ class _MostroNodeSelectorState extends ConsumerState { debugPrint('[MostroNodeSelector] setActiveMostroNode failed: $e'); ref.read(mostroPubkeyProvider.notifier).state = previous; if (!mounted) return; - setState(() => _errorText = 'Invalid pubkey or bridge error'); + setState( + () => _errorText = + AppLocalizations.of(context).invalidPubkeyOrBridgeErrorMessage, + ); } } @@ -106,6 +114,7 @@ class _MostroNodeSelectorState extends ConsumerState { final currentPubkey = ref.watch(mostroPubkeyProvider); final colors = Theme.of(context).extension()!; final isDefault = currentPubkey == _defaultMostroPubkey; + final l10n = AppLocalizations.of(context); return Padding( padding: EdgeInsets.only( @@ -121,13 +130,13 @@ class _MostroNodeSelectorState extends ConsumerState { Row( children: [ Text( - 'Mostro Node', + l10n.mostroNodeTitle, style: Theme.of(context).textTheme.headlineSmall, ), const Spacer(), IconButton( icon: const Icon(Icons.close), - tooltip: 'Close', + tooltip: l10n.closeButtonLabel, onPressed: () => Navigator.of(context).pop(), ), ], @@ -147,14 +156,14 @@ class _MostroNodeSelectorState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Current Node', + l10n.currentNodeLabel, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: colors.textSubtle, ), ), const SizedBox(height: AppSpacing.xs), Semantics( - label: 'Current node public key', + label: l10n.currentNodePublicKeyLabel, value: currentPubkey, child: Text( truncatePubkey(currentPubkey), @@ -177,7 +186,7 @@ class _MostroNodeSelectorState extends ConsumerState { borderRadius: BorderRadius.circular(AppRadius.chip), ), child: Text( - 'Trusted', + l10n.trustedBadgeLabel, style: TextStyle( color: colors.mostroGreen, fontSize: 11, @@ -190,7 +199,7 @@ class _MostroNodeSelectorState extends ConsumerState { ), const SizedBox(height: AppSpacing.lg), Text( - 'Use a custom node pubkey', + l10n.useCustomNodePubkeyLabel, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colors.textSubtle, ), @@ -203,7 +212,7 @@ class _MostroNodeSelectorState extends ConsumerState { enableSuggestions: false, keyboardType: TextInputType.visiblePassword, decoration: InputDecoration( - hintText: 'Enter 64-char hex pubkey', + hintText: l10n.enterHexPubkeyHint, errorText: _errorText, counterText: '', ), @@ -217,14 +226,14 @@ class _MostroNodeSelectorState extends ConsumerState { Expanded( child: OutlinedButton( onPressed: _useDefault, - child: const Text('Use Default'), + child: Text(l10n.useDefaultButtonLabel), ), ), const SizedBox(width: AppSpacing.md), Expanded( child: FilledButton( onPressed: _confirm, - child: const Text('Confirm'), + child: Text(l10n.confirmButtonLabel), ), ), ], diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index a88aa4fc..2c2fdcd2 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -388,5 +388,20 @@ "notifDisputeUpdatesTitle": "Streitfall-Updates", "notifDisputeUpdatesSubtitle": "Admin-Aktionen und Streitbeilegungen", "searchCurrenciesHint": "Währungen suchen…", - "noCurrenciesFoundMessage": "Keine Währungen gefunden" + "noCurrenciesFoundMessage": "Keine Währungen gefunden", + "failedToResetNodeMessage": "Knoten konnte nicht zurückgesetzt werden", + "invalidPubkeyOrBridgeErrorMessage": "Ungültiger Public Key oder Bridge-Fehler", + "mostroNodeTitle": "Mostro-Knoten", + "currentNodePublicKeyLabel": "Öffentlicher Schlüssel des aktuellen Knotens", + "trustedBadgeLabel": "Vertrauenswürdig", + "useCustomNodePubkeyLabel": "Eigenen Knoten-Public-Key verwenden", + "enterHexPubkeyHint": "Gib einen 64-stelligen Hex-Public-Key ein", + "shareLogsTooltip": "Protokolle teilen", + "noLogsToShareTooltip": "Keine Protokolle zum Teilen", + "disableLoggingTooltip": "Protokollierung deaktivieren", + "enableLoggingTooltip": "Protokollierung aktivieren", + "loggingEnabledStatus": "Protokollierung aktiviert", + "loggingDisabledStatus": "Protokollierung deaktiviert", + "noLogEntriesMessage": "Keine Protokolleinträge", + "failedToShareLogsMessage": "Protokolle konnten nicht geteilt werden" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2433449c..b24d44f6 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -801,5 +801,35 @@ "searchCurrenciesHint": "Search currencies…", "@searchCurrenciesHint": {"description": "Hint text of the currency search field"}, "noCurrenciesFoundMessage": "No currencies found", - "@noCurrenciesFoundMessage": {"description": "Message shown when no currencies match the search"} + "@noCurrenciesFoundMessage": {"description": "Message shown when no currencies match the search"}, + "failedToResetNodeMessage": "Failed to reset node", + "@failedToResetNodeMessage": {"description": "Error shown when resetting to the default Mostro node fails"}, + "invalidPubkeyOrBridgeErrorMessage": "Invalid pubkey or bridge error", + "@invalidPubkeyOrBridgeErrorMessage": {"description": "Error shown when setting a custom node pubkey fails"}, + "mostroNodeTitle": "Mostro Node", + "@mostroNodeTitle": {"description": "Title of the Mostro node selector bottom sheet"}, + "currentNodePublicKeyLabel": "Current node public key", + "@currentNodePublicKeyLabel": {"description": "Accessibility label for the current node public key"}, + "trustedBadgeLabel": "Trusted", + "@trustedBadgeLabel": {"description": "Badge shown when the active node is the default trusted node"}, + "useCustomNodePubkeyLabel": "Use a custom node pubkey", + "@useCustomNodePubkeyLabel": {"description": "Label above the custom node pubkey input"}, + "enterHexPubkeyHint": "Enter 64-char hex pubkey", + "@enterHexPubkeyHint": {"description": "Hint text for the custom node pubkey input"}, + "shareLogsTooltip": "Share logs", + "@shareLogsTooltip": {"description": "Tooltip for the share logs action"}, + "noLogsToShareTooltip": "No logs to share", + "@noLogsToShareTooltip": {"description": "Tooltip for the share action when there are no logs"}, + "disableLoggingTooltip": "Disable logging", + "@disableLoggingTooltip": {"description": "Tooltip to disable logging"}, + "enableLoggingTooltip": "Enable logging", + "@enableLoggingTooltip": {"description": "Tooltip to enable logging"}, + "loggingEnabledStatus": "Logging enabled", + "@loggingEnabledStatus": {"description": "Status banner text when logging is enabled"}, + "loggingDisabledStatus": "Logging disabled", + "@loggingDisabledStatus": {"description": "Status banner text when logging is disabled"}, + "noLogEntriesMessage": "No log entries", + "@noLogEntriesMessage": {"description": "Message shown when there are no log entries"}, + "failedToShareLogsMessage": "Failed to share logs", + "@failedToShareLogsMessage": {"description": "Snackbar shown when sharing logs fails"} } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index c4e8bd7d..b19e3b80 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -388,5 +388,20 @@ "notifDisputeUpdatesTitle": "Actualizaciones de disputas", "notifDisputeUpdatesSubtitle": "Acciones de administradores y resoluciones de disputas", "searchCurrenciesHint": "Buscar monedas…", - "noCurrenciesFoundMessage": "No se encontraron monedas" + "noCurrenciesFoundMessage": "No se encontraron monedas", + "failedToResetNodeMessage": "No se pudo restablecer el nodo", + "invalidPubkeyOrBridgeErrorMessage": "Clave pública inválida o error del puente", + "mostroNodeTitle": "Nodo Mostro", + "currentNodePublicKeyLabel": "Clave pública del nodo actual", + "trustedBadgeLabel": "De confianza", + "useCustomNodePubkeyLabel": "Usar una clave pública de nodo personalizada", + "enterHexPubkeyHint": "Ingresa clave pública hex de 64 caracteres", + "shareLogsTooltip": "Compartir registros", + "noLogsToShareTooltip": "No hay registros para compartir", + "disableLoggingTooltip": "Desactivar registro", + "enableLoggingTooltip": "Activar registro", + "loggingEnabledStatus": "Registro activado", + "loggingDisabledStatus": "Registro desactivado", + "noLogEntriesMessage": "No hay entradas de registro", + "failedToShareLogsMessage": "No se pudieron compartir los registros" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 2d732a40..10ec50a8 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -388,5 +388,20 @@ "notifDisputeUpdatesTitle": "Mises à jour des litiges", "notifDisputeUpdatesSubtitle": "Actions des administrateurs et résolutions de litiges", "searchCurrenciesHint": "Rechercher des devises…", - "noCurrenciesFoundMessage": "Aucune devise trouvée" + "noCurrenciesFoundMessage": "Aucune devise trouvée", + "failedToResetNodeMessage": "Échec de la réinitialisation du nœud", + "invalidPubkeyOrBridgeErrorMessage": "Clé publique invalide ou erreur du pont", + "mostroNodeTitle": "Nœud Mostro", + "currentNodePublicKeyLabel": "Clé publique du nœud actuel", + "trustedBadgeLabel": "De confiance", + "useCustomNodePubkeyLabel": "Utiliser une clé publique de nœud personnalisée", + "enterHexPubkeyHint": "Entrez une clé publique hex de 64 caractères", + "shareLogsTooltip": "Partager les journaux", + "noLogsToShareTooltip": "Aucun journal à partager", + "disableLoggingTooltip": "Désactiver la journalisation", + "enableLoggingTooltip": "Activer la journalisation", + "loggingEnabledStatus": "Journalisation activée", + "loggingDisabledStatus": "Journalisation désactivée", + "noLogEntriesMessage": "Aucune entrée de journal", + "failedToShareLogsMessage": "Échec du partage des journaux" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 5635410e..18c659cf 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -388,5 +388,20 @@ "notifDisputeUpdatesTitle": "Aggiornamenti dispute", "notifDisputeUpdatesSubtitle": "Azioni degli amministratori e risoluzioni delle dispute", "searchCurrenciesHint": "Cerca valute…", - "noCurrenciesFoundMessage": "Nessuna valuta trovata" + "noCurrenciesFoundMessage": "Nessuna valuta trovata", + "failedToResetNodeMessage": "Impossibile reimpostare il nodo", + "invalidPubkeyOrBridgeErrorMessage": "Chiave pubblica non valida o errore del bridge", + "mostroNodeTitle": "Nodo Mostro", + "currentNodePublicKeyLabel": "Chiave pubblica del nodo attuale", + "trustedBadgeLabel": "Attendibile", + "useCustomNodePubkeyLabel": "Usa una chiave pubblica del nodo personalizzata", + "enterHexPubkeyHint": "Inserisci una chiave pubblica hex di 64 caratteri", + "shareLogsTooltip": "Condividi i log", + "noLogsToShareTooltip": "Nessun log da condividere", + "disableLoggingTooltip": "Disattiva la registrazione", + "enableLoggingTooltip": "Attiva la registrazione", + "loggingEnabledStatus": "Registrazione attivata", + "loggingDisabledStatus": "Registrazione disattivata", + "noLogEntriesMessage": "Nessuna voce di registro", + "failedToShareLogsMessage": "Impossibile condividere i log" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index f2e35236..46bff079 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -962,7 +962,7 @@ abstract class AppLocalizations { /// **'Paste or scan a QR code'** String get pasteOrScanQrCode; - /// Section title on the Mostro node settings screen + /// Title of the Mostro node selector bottom sheet /// /// In en, this message translates to: /// **'Mostro Node'** @@ -974,7 +974,7 @@ abstract class AppLocalizations { /// **'Current Node'** String get currentNodeLabel; - /// Badge shown on a verified/trusted Mostro node + /// Badge shown when the active node is the default trusted node /// /// In en, this message translates to: /// **'Trusted'** @@ -2299,6 +2299,84 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'No currencies found'** String get noCurrenciesFoundMessage; + + /// Error shown when resetting to the default Mostro node fails + /// + /// In en, this message translates to: + /// **'Failed to reset node'** + String get failedToResetNodeMessage; + + /// Error shown when setting a custom node pubkey fails + /// + /// In en, this message translates to: + /// **'Invalid pubkey or bridge error'** + String get invalidPubkeyOrBridgeErrorMessage; + + /// Accessibility label for the current node public key + /// + /// In en, this message translates to: + /// **'Current node public key'** + String get currentNodePublicKeyLabel; + + /// Label above the custom node pubkey input + /// + /// In en, this message translates to: + /// **'Use a custom node pubkey'** + String get useCustomNodePubkeyLabel; + + /// Hint text for the custom node pubkey input + /// + /// In en, this message translates to: + /// **'Enter 64-char hex pubkey'** + String get enterHexPubkeyHint; + + /// Tooltip for the share logs action + /// + /// In en, this message translates to: + /// **'Share logs'** + String get shareLogsTooltip; + + /// Tooltip for the share action when there are no logs + /// + /// In en, this message translates to: + /// **'No logs to share'** + String get noLogsToShareTooltip; + + /// Tooltip to disable logging + /// + /// In en, this message translates to: + /// **'Disable logging'** + String get disableLoggingTooltip; + + /// Tooltip to enable logging + /// + /// In en, this message translates to: + /// **'Enable logging'** + String get enableLoggingTooltip; + + /// Status banner text when logging is enabled + /// + /// In en, this message translates to: + /// **'Logging enabled'** + String get loggingEnabledStatus; + + /// Status banner text when logging is disabled + /// + /// In en, this message translates to: + /// **'Logging disabled'** + String get loggingDisabledStatus; + + /// Message shown when there are no log entries + /// + /// In en, this message translates to: + /// **'No log entries'** + String get noLogEntriesMessage; + + /// Snackbar shown when sharing logs fails + /// + /// In en, this message translates to: + /// **'Failed to share logs'** + String get failedToShareLogsMessage; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index e114cdb5..9b450576 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1231,4 +1231,47 @@ class AppLocalizationsDe extends AppLocalizations { @override String get noCurrenciesFoundMessage => 'Keine Währungen gefunden'; + + @override + String get failedToResetNodeMessage => + 'Knoten konnte nicht zurückgesetzt werden'; + + @override + String get invalidPubkeyOrBridgeErrorMessage => + 'Ungültiger Public Key oder Bridge-Fehler'; + + @override + String get currentNodePublicKeyLabel => + 'Öffentlicher Schlüssel des aktuellen Knotens'; + + @override + String get useCustomNodePubkeyLabel => 'Eigenen Knoten-Public-Key verwenden'; + + @override + String get enterHexPubkeyHint => 'Gib einen 64-stelligen Hex-Public-Key ein'; + + @override + String get shareLogsTooltip => 'Protokolle teilen'; + + @override + String get noLogsToShareTooltip => 'Keine Protokolle zum Teilen'; + + @override + String get disableLoggingTooltip => 'Protokollierung deaktivieren'; + + @override + String get enableLoggingTooltip => 'Protokollierung aktivieren'; + + @override + String get loggingEnabledStatus => 'Protokollierung aktiviert'; + + @override + String get loggingDisabledStatus => 'Protokollierung deaktiviert'; + + @override + String get noLogEntriesMessage => 'Keine Protokolleinträge'; + + @override + String get failedToShareLogsMessage => + 'Protokolle konnten nicht geteilt werden'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index e45e7d8e..52f5f6d7 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1212,4 +1212,44 @@ class AppLocalizationsEn extends AppLocalizations { @override String get noCurrenciesFoundMessage => 'No currencies found'; + + @override + String get failedToResetNodeMessage => 'Failed to reset node'; + + @override + String get invalidPubkeyOrBridgeErrorMessage => + 'Invalid pubkey or bridge error'; + + @override + String get currentNodePublicKeyLabel => 'Current node public key'; + + @override + String get useCustomNodePubkeyLabel => 'Use a custom node pubkey'; + + @override + String get enterHexPubkeyHint => 'Enter 64-char hex pubkey'; + + @override + String get shareLogsTooltip => 'Share logs'; + + @override + String get noLogsToShareTooltip => 'No logs to share'; + + @override + String get disableLoggingTooltip => 'Disable logging'; + + @override + String get enableLoggingTooltip => 'Enable logging'; + + @override + String get loggingEnabledStatus => 'Logging enabled'; + + @override + String get loggingDisabledStatus => 'Logging disabled'; + + @override + String get noLogEntriesMessage => 'No log entries'; + + @override + String get failedToShareLogsMessage => 'Failed to share logs'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index f4a4944d..8f363339 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1226,4 +1226,46 @@ class AppLocalizationsEs extends AppLocalizations { @override String get noCurrenciesFoundMessage => 'No se encontraron monedas'; + + @override + String get failedToResetNodeMessage => 'No se pudo restablecer el nodo'; + + @override + String get invalidPubkeyOrBridgeErrorMessage => + 'Clave pública inválida o error del puente'; + + @override + String get currentNodePublicKeyLabel => 'Clave pública del nodo actual'; + + @override + String get useCustomNodePubkeyLabel => + 'Usar una clave pública de nodo personalizada'; + + @override + String get enterHexPubkeyHint => 'Ingresa clave pública hex de 64 caracteres'; + + @override + String get shareLogsTooltip => 'Compartir registros'; + + @override + String get noLogsToShareTooltip => 'No hay registros para compartir'; + + @override + String get disableLoggingTooltip => 'Desactivar registro'; + + @override + String get enableLoggingTooltip => 'Activar registro'; + + @override + String get loggingEnabledStatus => 'Registro activado'; + + @override + String get loggingDisabledStatus => 'Registro desactivado'; + + @override + String get noLogEntriesMessage => 'No hay entradas de registro'; + + @override + String get failedToShareLogsMessage => + 'No se pudieron compartir los registros'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 1f1967ec..9824b38b 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1232,4 +1232,46 @@ class AppLocalizationsFr extends AppLocalizations { @override String get noCurrenciesFoundMessage => 'Aucune devise trouvée'; + + @override + String get failedToResetNodeMessage => 'Échec de la réinitialisation du nœud'; + + @override + String get invalidPubkeyOrBridgeErrorMessage => + 'Clé publique invalide ou erreur du pont'; + + @override + String get currentNodePublicKeyLabel => 'Clé publique du nœud actuel'; + + @override + String get useCustomNodePubkeyLabel => + 'Utiliser une clé publique de nœud personnalisée'; + + @override + String get enterHexPubkeyHint => + 'Entrez une clé publique hex de 64 caractères'; + + @override + String get shareLogsTooltip => 'Partager les journaux'; + + @override + String get noLogsToShareTooltip => 'Aucun journal à partager'; + + @override + String get disableLoggingTooltip => 'Désactiver la journalisation'; + + @override + String get enableLoggingTooltip => 'Activer la journalisation'; + + @override + String get loggingEnabledStatus => 'Journalisation activée'; + + @override + String get loggingDisabledStatus => 'Journalisation désactivée'; + + @override + String get noLogEntriesMessage => 'Aucune entrée de journal'; + + @override + String get failedToShareLogsMessage => 'Échec du partage des journaux'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 7e2c4ec3..9ecc8a01 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -490,7 +490,7 @@ class AppLocalizationsIt extends AppLocalizations { String get currentNodeLabel => 'Nodo attuale'; @override - String get trustedBadgeLabel => 'Affidabile'; + String get trustedBadgeLabel => 'Attendibile'; @override String get useDefaultButtonLabel => 'Usa predefinito'; @@ -1224,4 +1224,46 @@ class AppLocalizationsIt extends AppLocalizations { @override String get noCurrenciesFoundMessage => 'Nessuna valuta trovata'; + + @override + String get failedToResetNodeMessage => 'Impossibile reimpostare il nodo'; + + @override + String get invalidPubkeyOrBridgeErrorMessage => + 'Chiave pubblica non valida o errore del bridge'; + + @override + String get currentNodePublicKeyLabel => 'Chiave pubblica del nodo attuale'; + + @override + String get useCustomNodePubkeyLabel => + 'Usa una chiave pubblica del nodo personalizzata'; + + @override + String get enterHexPubkeyHint => + 'Inserisci una chiave pubblica hex di 64 caratteri'; + + @override + String get shareLogsTooltip => 'Condividi i log'; + + @override + String get noLogsToShareTooltip => 'Nessun log da condividere'; + + @override + String get disableLoggingTooltip => 'Disattiva la registrazione'; + + @override + String get enableLoggingTooltip => 'Attiva la registrazione'; + + @override + String get loggingEnabledStatus => 'Registrazione attivata'; + + @override + String get loggingDisabledStatus => 'Registrazione disattivata'; + + @override + String get noLogEntriesMessage => 'Nessuna voce di registro'; + + @override + String get failedToShareLogsMessage => 'Impossibile condividere i log'; } From b13e13dc2fa9f077a1787f77ae6a992b84c2c4fb Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 01:24:58 -0300 Subject: [PATCH 08/30] feat: localize trades screen and related widgets Replace hardcoded English text in trades feature with localized strings from AppLocalizations. Adds 29 new l10n keys across en/de/es/fr/it for: --- .../trades/providers/trades_providers.dart | 16 ++ .../trades/screens/trades_screen.dart | 21 ++- .../widgets/release_confirmation_dialog.dart | 10 +- .../trades/widgets/trade_info_cards.dart | 9 +- .../trades/widgets/trades_list_item.dart | 20 +-- lib/l10n/app_de.arb | 27 +++- lib/l10n/app_en.arb | 61 ++++++- lib/l10n/app_es.arb | 27 +++- lib/l10n/app_fr.arb | 27 +++- lib/l10n/app_it.arb | 27 +++- lib/l10n/app_localizations.dart | 150 ++++++++++++++++++ lib/l10n/app_localizations_de.dart | 83 ++++++++++ lib/l10n/app_localizations_en.dart | 83 ++++++++++ lib/l10n/app_localizations_es.dart | 84 ++++++++++ lib/l10n/app_localizations_fr.dart | 84 ++++++++++ lib/l10n/app_localizations_it.dart | 83 ++++++++++ 16 files changed, 782 insertions(+), 30 deletions(-) diff --git a/lib/features/trades/providers/trades_providers.dart b/lib/features/trades/providers/trades_providers.dart index 69b19ba7..d1b76306 100644 --- a/lib/features/trades/providers/trades_providers.dart +++ b/lib/features/trades/providers/trades_providers.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro/features/order/providers/trade_state_provider.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/shared/providers/nav_providers.dart'; import 'package:mostro/src/rust/api/orders.dart' as orders_api; import 'package:mostro/src/rust/api/types.dart' as rust_types; @@ -24,6 +25,21 @@ enum TradeStatusFilter { final String label; } +/// Localized display label for the status filter dropdown. +extension TradeStatusFilterL10n on TradeStatusFilter { + String localizedLabel(AppLocalizations l10n) => switch (this) { + TradeStatusFilter.all => l10n.tradeFilterAll, + TradeStatusFilter.pending => l10n.tradeFilterPending, + TradeStatusFilter.waitingInvoice => l10n.tradeFilterWaitingInvoice, + TradeStatusFilter.waitingPayment => l10n.tradeFilterWaitingPayment, + TradeStatusFilter.active => l10n.tradeFilterActive, + TradeStatusFilter.fiatSent => l10n.tradeFilterFiatSent, + TradeStatusFilter.success => l10n.tradeFilterSuccess, + TradeStatusFilter.canceled => l10n.tradeFilterCanceled, + TradeStatusFilter.dispute => l10n.tradeFilterDispute, + }; +} + // ── TradeRole ───────────────────────────────────────────────────────────────── /// Whether the local user created this trade (maker) or took it (taker). diff --git a/lib/features/trades/screens/trades_screen.dart b/lib/features/trades/screens/trades_screen.dart index f50df929..46249e2d 100644 --- a/lib/features/trades/screens/trades_screen.dart +++ b/lib/features/trades/screens/trades_screen.dart @@ -6,6 +6,7 @@ import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/drawer/screens/drawer_menu.dart'; import 'package:mostro/features/trades/providers/trades_providers.dart'; import 'package:mostro/features/trades/widgets/trades_list_item.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/shared/widgets/bottom_nav_bar.dart' show BottomNavBar; import 'package:mostro/shared/widgets/notification_bell.dart'; @@ -27,6 +28,7 @@ class _TradesScreenState extends ConsumerState { Widget build(BuildContext context) { final colors = Theme.of(context).extension(); if (colors == null) throw StateError('AppColors theme extension must be registered'); + final l10n = AppLocalizations.of(context); final tradesAsync = ref.watch(filteredTradesWithOrderStateProvider); final selectedFilter = ref.watch(selectedStatusFilterProvider); @@ -54,7 +56,7 @@ class _TradesScreenState extends ConsumerState { children: [ Expanded( child: Text( - 'My Trades', + l10n.navMyTrades, style: Theme.of(context).textTheme.headlineSmall?.copyWith( color: colors.textPrimary, fontWeight: FontWeight.bold, @@ -132,7 +134,7 @@ class _TradesScreenState extends ConsumerState { leading: IconButton( icon: const Icon(Icons.menu), onPressed: () => setState(() => _drawerOpen = true), - tooltip: 'Menu', + tooltip: l10n.menuTooltip, ), title: Image.asset( 'assets/images/mostro_logo.png', @@ -174,6 +176,7 @@ class _StatusFilterButton extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); return PopupMenuButton( initialValue: selected, onSelected: onChanged, @@ -181,7 +184,7 @@ class _StatusFilterButton extends StatelessWidget { .map( (f) => PopupMenuItem( value: f, - child: Text(f.label), + child: Text(f.localizedLabel(l10n)), ), ) .toList(), @@ -200,7 +203,7 @@ class _StatusFilterButton extends StatelessWidget { Icon(Icons.filter_list, size: 14, color: colors.textSecondary), const SizedBox(width: 4), Text( - 'Status | ${selected.label}', + '${l10n.tradeStatusFilterPrefix} | ${selected.localizedLabel(l10n)}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: colors.textSecondary, ), @@ -223,6 +226,7 @@ class _EmptyState extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); return Center( child: Column( mainAxisSize: MainAxisSize.min, @@ -230,14 +234,14 @@ class _EmptyState extends StatelessWidget { Icon(Icons.bolt_outlined, size: 64, color: colors.textSubtle), const SizedBox(height: AppSpacing.lg), Text( - 'No trades', + l10n.noTradesTitle, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colors.textSecondary, ), ), const SizedBox(height: AppSpacing.sm), Text( - 'Your active and completed trades will appear here.', + l10n.noTradesSubtitle, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: colors.textSubtle, ), @@ -259,6 +263,7 @@ class _ErrorState extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); return Center( child: Column( mainAxisSize: MainAxisSize.min, @@ -266,13 +271,13 @@ class _ErrorState extends StatelessWidget { Icon(Icons.error_outline, size: 48, color: colors.textSubtle), const SizedBox(height: AppSpacing.md), Text( - 'Could not load trades', + l10n.couldNotLoadTradesMessage, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colors.textSecondary, ), ), const SizedBox(height: AppSpacing.md), - TextButton(onPressed: onRetry, child: const Text('Retry')), + TextButton(onPressed: onRetry, child: Text(l10n.retry)), ], ), ); diff --git a/lib/features/trades/widgets/release_confirmation_dialog.dart b/lib/features/trades/widgets/release_confirmation_dialog.dart index 08669fe5..53291f49 100644 --- a/lib/features/trades/widgets/release_confirmation_dialog.dart +++ b/lib/features/trades/widgets/release_confirmation_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Shows the release confirmation dialog. /// @@ -21,6 +22,7 @@ class _ReleaseConfirmationDialog extends StatelessWidget { final colors = Theme.of(context).extension(); final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230); + final l10n = AppLocalizations.of(context); return Dialog( backgroundColor: cardBg, @@ -39,12 +41,12 @@ class _ReleaseConfirmationDialog extends StatelessWidget { ), const SizedBox(height: AppSpacing.lg), Text( - 'Release Bitcoin', + l10n.releaseBitcoinTitle, style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: AppSpacing.md), Text( - 'Are you sure you want to release the Satoshis to the buyer?', + l10n.releaseBitcoinConfirmation, style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center, ), @@ -64,7 +66,7 @@ class _ReleaseConfirmationDialog extends StatelessWidget { borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text('No'), + child: Text(l10n.noButtonLabel), ), ), const SizedBox(width: AppSpacing.md), @@ -79,7 +81,7 @@ class _ReleaseConfirmationDialog extends StatelessWidget { borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text('Yes'), + child: Text(l10n.yesButtonLabel), ), ), ], diff --git a/lib/features/trades/widgets/trade_info_cards.dart b/lib/features/trades/widgets/trade_info_cards.dart index 5241e73b..6db5b9ed 100644 --- a/lib/features/trades/widgets/trade_info_cards.dart +++ b/lib/features/trades/widgets/trade_info_cards.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Reusable info card container for trade detail. class TradeInfoCard extends StatelessWidget { @@ -49,14 +50,14 @@ class OrderIdCard extends StatelessWidget { onPressed: () { Clipboard.setData(ClipboardData(text: orderId)); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Order ID copied'), - duration: Duration(seconds: 1), + SnackBar( + content: Text(AppLocalizations.of(context).orderIdCopied), + duration: const Duration(seconds: 1), ), ); }, icon: const Icon(Icons.copy, size: 18), - tooltip: 'Copy order ID', + tooltip: AppLocalizations.of(context).copyOrderIdTooltip, padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), diff --git a/lib/features/trades/widgets/trades_list_item.dart b/lib/features/trades/widgets/trades_list_item.dart index b8ae02ac..b006824d 100644 --- a/lib/features/trades/widgets/trades_list_item.dart +++ b/lib/features/trades/widgets/trades_list_item.dart @@ -6,6 +6,7 @@ import 'package:mostro/core/app_routes.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/order/providers/trade_state_provider.dart'; import 'package:mostro/features/trades/providers/trades_providers.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/shared/widgets/status_chip.dart'; /// A single card row in the My Trades list. @@ -38,6 +39,7 @@ class TradesListItem extends ConsumerWidget { final colors = Theme.of(context).extension(); if (colors == null) throw StateError('AppColors theme extension must be registered'); final textTheme = Theme.of(context).textTheme; + final l10n = AppLocalizations.of(context); // Live status from the polling provider; falls back to the DB snapshot. final liveStatusAsync = ref.watch(tradeStatusProvider(trade.orderId)); @@ -46,12 +48,12 @@ class TradesListItem extends ConsumerWidget { ) ?? trade.status; - final titleText = trade.isSelling ? 'Selling Bitcoin' : 'Buying Bitcoin'; + final titleText = trade.isSelling ? l10n.sellingBitcoin : l10n.buyingBitcoin; final (statusBg, statusFg) = _statusColors(effectiveStatus); - final statusLabel = effectiveStatus.label; + final statusLabel = effectiveStatus.localizedLabel(l10n); final roleLabel = - trade.role == TradeRole.creator ? 'Created by you' : 'Taken by you'; - final timeAgo = _timeAgo(trade.createdAt); + trade.role == TradeRole.creator ? l10n.createdByYou : l10n.takenByYou; + final timeAgo = _timeAgo(trade.createdAt, l10n); return Card( margin: const EdgeInsets.symmetric( @@ -188,13 +190,13 @@ class TradesListItem extends ConsumerWidget { } /// Returns a compact "time ago" string from a unix timestamp (e.g. "4m", "2h", "3d"). - static String _timeAgo(int unixSeconds) { + static String _timeAgo(int unixSeconds, AppLocalizations l10n) { final dt = DateTime.fromMillisecondsSinceEpoch(unixSeconds * 1000); final diff = DateTime.now().difference(dt); - if (diff.isNegative || diff.inSeconds < 60) return 'now'; - if (diff.inMinutes < 60) return '${diff.inMinutes}m'; - if (diff.inHours < 24) return '${diff.inHours}h'; - return '${diff.inDays}d'; + if (diff.isNegative || diff.inSeconds < 60) return l10n.timeAgoNow; + if (diff.inMinutes < 60) return l10n.timeAgoMinutes(diff.inMinutes); + if (diff.inHours < 24) return l10n.timeAgoHours(diff.inHours); + return l10n.timeAgoDays(diff.inDays); } } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 2c2fdcd2..b23278ae 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -403,5 +403,30 @@ "loggingEnabledStatus": "Protokollierung aktiviert", "loggingDisabledStatus": "Protokollierung deaktiviert", "noLogEntriesMessage": "Keine Protokolleinträge", - "failedToShareLogsMessage": "Protokolle konnten nicht geteilt werden" + "failedToShareLogsMessage": "Protokolle konnten nicht geteilt werden", + "tradeFilterAll": "Alle", + "tradeFilterPending": "Ausstehend", + "tradeFilterWaitingInvoice": "Warte auf Rechnung", + "tradeFilterWaitingPayment": "Warte auf Zahlung", + "tradeFilterActive": "Aktiv", + "tradeFilterFiatSent": "Fiat gesendet", + "tradeFilterSuccess": "Erfolgreich", + "tradeFilterCanceled": "Storniert", + "tradeFilterDispute": "Streitfall", + "menuTooltip": "Menü", + "tradeStatusFilterPrefix": "Status", + "noTradesTitle": "Keine Trades", + "noTradesSubtitle": "Deine aktiven und abgeschlossenen Trades erscheinen hier.", + "couldNotLoadTradesMessage": "Trades konnten nicht geladen werden", + "releaseBitcoinTitle": "Bitcoin freigeben", + "releaseBitcoinConfirmation": "Möchtest du die Satoshis wirklich an den Käufer freigeben?", + "yesButtonLabel": "Ja", + "sellingBitcoin": "Bitcoin verkaufen", + "buyingBitcoin": "Bitcoin kaufen", + "createdByYou": "Von dir erstellt", + "takenByYou": "Von dir übernommen", + "timeAgoNow": "jetzt", + "timeAgoMinutes": "{count}Min", + "timeAgoHours": "{count}Std", + "timeAgoDays": "{count}T" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b24d44f6..2333b652 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -831,5 +831,64 @@ "noLogEntriesMessage": "No log entries", "@noLogEntriesMessage": {"description": "Message shown when there are no log entries"}, "failedToShareLogsMessage": "Failed to share logs", - "@failedToShareLogsMessage": {"description": "Snackbar shown when sharing logs fails"} + "@failedToShareLogsMessage": {"description": "Snackbar shown when sharing logs fails"}, + "tradeFilterAll": "All", + "@tradeFilterAll": {"description": "My Trades status filter: all"}, + "tradeFilterPending": "Pending", + "@tradeFilterPending": {"description": "My Trades status filter: pending"}, + "tradeFilterWaitingInvoice": "Waiting Invoice", + "@tradeFilterWaitingInvoice": {"description": "My Trades status filter: waiting invoice"}, + "tradeFilterWaitingPayment": "Waiting Payment", + "@tradeFilterWaitingPayment": {"description": "My Trades status filter: waiting payment"}, + "tradeFilterActive": "Active", + "@tradeFilterActive": {"description": "My Trades status filter: active"}, + "tradeFilterFiatSent": "Fiat Sent", + "@tradeFilterFiatSent": {"description": "My Trades status filter: fiat sent"}, + "tradeFilterSuccess": "Success", + "@tradeFilterSuccess": {"description": "My Trades status filter: success"}, + "tradeFilterCanceled": "Canceled", + "@tradeFilterCanceled": {"description": "My Trades status filter: canceled"}, + "tradeFilterDispute": "Dispute", + "@tradeFilterDispute": {"description": "My Trades status filter: dispute"}, + "menuTooltip": "Menu", + "@menuTooltip": {"description": "Tooltip for the drawer menu button"}, + "tradeStatusFilterPrefix": "Status", + "@tradeStatusFilterPrefix": {"description": "Prefix shown before the selected status filter"}, + "noTradesTitle": "No trades", + "@noTradesTitle": {"description": "Title of the empty My Trades state"}, + "noTradesSubtitle": "Your active and completed trades will appear here.", + "@noTradesSubtitle": {"description": "Subtitle of the empty My Trades state"}, + "couldNotLoadTradesMessage": "Could not load trades", + "@couldNotLoadTradesMessage": {"description": "Error shown when the trades list fails to load"}, + "releaseBitcoinTitle": "Release Bitcoin", + "@releaseBitcoinTitle": {"description": "Title of the release confirmation dialog"}, + "releaseBitcoinConfirmation": "Are you sure you want to release the Satoshis to the buyer?", + "@releaseBitcoinConfirmation": {"description": "Body of the release confirmation dialog"}, + "yesButtonLabel": "Yes", + "@yesButtonLabel": {"description": "Generic Yes button label"}, + "sellingBitcoin": "Selling Bitcoin", + "@sellingBitcoin": {"description": "Trade card title when the user is selling Bitcoin"}, + "buyingBitcoin": "Buying Bitcoin", + "@buyingBitcoin": {"description": "Trade card title when the user is buying Bitcoin"}, + "createdByYou": "Created by you", + "@createdByYou": {"description": "Role badge when the user created the order"}, + "takenByYou": "Taken by you", + "@takenByYou": {"description": "Role badge when the user took the order"}, + "timeAgoNow": "now", + "@timeAgoNow": {"description": "Compact relative time for just now"}, + "timeAgoMinutes": "{count}m", + "@timeAgoMinutes": { + "description": "Compact relative time in minutes", + "placeholders": {"count": {"type": "int"}} + }, + "timeAgoHours": "{count}h", + "@timeAgoHours": { + "description": "Compact relative time in hours", + "placeholders": {"count": {"type": "int"}} + }, + "timeAgoDays": "{count}d", + "@timeAgoDays": { + "description": "Compact relative time in days", + "placeholders": {"count": {"type": "int"}} + } } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index b19e3b80..99b8690b 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -403,5 +403,30 @@ "loggingEnabledStatus": "Registro activado", "loggingDisabledStatus": "Registro desactivado", "noLogEntriesMessage": "No hay entradas de registro", - "failedToShareLogsMessage": "No se pudieron compartir los registros" + "failedToShareLogsMessage": "No se pudieron compartir los registros", + "tradeFilterAll": "Todos", + "tradeFilterPending": "Pendiente", + "tradeFilterWaitingInvoice": "Esperando factura", + "tradeFilterWaitingPayment": "Esperando pago", + "tradeFilterActive": "Activo", + "tradeFilterFiatSent": "Fiat enviado", + "tradeFilterSuccess": "Exitoso", + "tradeFilterCanceled": "Cancelado", + "tradeFilterDispute": "Disputa", + "menuTooltip": "Menú", + "tradeStatusFilterPrefix": "Estado", + "noTradesTitle": "Sin operaciones", + "noTradesSubtitle": "Tus operaciones activas y completadas aparecerán aquí.", + "couldNotLoadTradesMessage": "No se pudieron cargar las operaciones", + "releaseBitcoinTitle": "Liberar Bitcoin", + "releaseBitcoinConfirmation": "¿Seguro que quieres liberar los Satoshis al comprador?", + "yesButtonLabel": "Sí", + "sellingBitcoin": "Vendiendo Bitcoin", + "buyingBitcoin": "Comprando Bitcoin", + "createdByYou": "Creada por ti", + "takenByYou": "Tomada por ti", + "timeAgoNow": "ahora", + "timeAgoMinutes": "{count}m", + "timeAgoHours": "{count}h", + "timeAgoDays": "{count}d" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 10ec50a8..b68f9fe8 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -403,5 +403,30 @@ "loggingEnabledStatus": "Journalisation activée", "loggingDisabledStatus": "Journalisation désactivée", "noLogEntriesMessage": "Aucune entrée de journal", - "failedToShareLogsMessage": "Échec du partage des journaux" + "failedToShareLogsMessage": "Échec du partage des journaux", + "tradeFilterAll": "Tous", + "tradeFilterPending": "En attente", + "tradeFilterWaitingInvoice": "En attente de facture", + "tradeFilterWaitingPayment": "En attente de paiement", + "tradeFilterActive": "Actif", + "tradeFilterFiatSent": "Fiat envoyé", + "tradeFilterSuccess": "Réussi", + "tradeFilterCanceled": "Annulé", + "tradeFilterDispute": "Litige", + "menuTooltip": "Menu", + "tradeStatusFilterPrefix": "Statut", + "noTradesTitle": "Aucune transaction", + "noTradesSubtitle": "Vos transactions actives et terminées apparaîtront ici.", + "couldNotLoadTradesMessage": "Impossible de charger les transactions", + "releaseBitcoinTitle": "Libérer les Bitcoin", + "releaseBitcoinConfirmation": "Êtes-vous sûr de vouloir libérer les Satoshis à l'acheteur ?", + "yesButtonLabel": "Oui", + "sellingBitcoin": "Vente de Bitcoin", + "buyingBitcoin": "Achat de Bitcoin", + "createdByYou": "Créé par vous", + "takenByYou": "Pris par vous", + "timeAgoNow": "maintenant", + "timeAgoMinutes": "{count}min", + "timeAgoHours": "{count}h", + "timeAgoDays": "{count}j" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 18c659cf..120119a9 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -403,5 +403,30 @@ "loggingEnabledStatus": "Registrazione attivata", "loggingDisabledStatus": "Registrazione disattivata", "noLogEntriesMessage": "Nessuna voce di registro", - "failedToShareLogsMessage": "Impossibile condividere i log" + "failedToShareLogsMessage": "Impossibile condividere i log", + "tradeFilterAll": "Tutti", + "tradeFilterPending": "In attesa", + "tradeFilterWaitingInvoice": "In attesa di fattura", + "tradeFilterWaitingPayment": "In attesa di pagamento", + "tradeFilterActive": "Attivo", + "tradeFilterFiatSent": "Fiat inviato", + "tradeFilterSuccess": "Riuscito", + "tradeFilterCanceled": "Annullato", + "tradeFilterDispute": "Disputa", + "menuTooltip": "Menu", + "tradeStatusFilterPrefix": "Stato", + "noTradesTitle": "Nessuna operazione", + "noTradesSubtitle": "Le tue operazioni attive e completate appariranno qui.", + "couldNotLoadTradesMessage": "Impossibile caricare le operazioni", + "releaseBitcoinTitle": "Rilascia Bitcoin", + "releaseBitcoinConfirmation": "Sei sicuro di voler rilasciare i Satoshi all'acquirente?", + "yesButtonLabel": "Sì", + "sellingBitcoin": "Vendita di Bitcoin", + "buyingBitcoin": "Acquisto di Bitcoin", + "createdByYou": "Creata da te", + "takenByYou": "Presa da te", + "timeAgoNow": "ora", + "timeAgoMinutes": "{count}min", + "timeAgoHours": "{count}h", + "timeAgoDays": "{count}g" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 46bff079..cec4a217 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2377,6 +2377,156 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Failed to share logs'** String get failedToShareLogsMessage; + + /// My Trades status filter: all + /// + /// In en, this message translates to: + /// **'All'** + String get tradeFilterAll; + + /// My Trades status filter: pending + /// + /// In en, this message translates to: + /// **'Pending'** + String get tradeFilterPending; + + /// My Trades status filter: waiting invoice + /// + /// In en, this message translates to: + /// **'Waiting Invoice'** + String get tradeFilterWaitingInvoice; + + /// My Trades status filter: waiting payment + /// + /// In en, this message translates to: + /// **'Waiting Payment'** + String get tradeFilterWaitingPayment; + + /// My Trades status filter: active + /// + /// In en, this message translates to: + /// **'Active'** + String get tradeFilterActive; + + /// My Trades status filter: fiat sent + /// + /// In en, this message translates to: + /// **'Fiat Sent'** + String get tradeFilterFiatSent; + + /// My Trades status filter: success + /// + /// In en, this message translates to: + /// **'Success'** + String get tradeFilterSuccess; + + /// My Trades status filter: canceled + /// + /// In en, this message translates to: + /// **'Canceled'** + String get tradeFilterCanceled; + + /// My Trades status filter: dispute + /// + /// In en, this message translates to: + /// **'Dispute'** + String get tradeFilterDispute; + + /// Tooltip for the drawer menu button + /// + /// In en, this message translates to: + /// **'Menu'** + String get menuTooltip; + + /// Prefix shown before the selected status filter + /// + /// In en, this message translates to: + /// **'Status'** + String get tradeStatusFilterPrefix; + + /// Title of the empty My Trades state + /// + /// In en, this message translates to: + /// **'No trades'** + String get noTradesTitle; + + /// Subtitle of the empty My Trades state + /// + /// In en, this message translates to: + /// **'Your active and completed trades will appear here.'** + String get noTradesSubtitle; + + /// Error shown when the trades list fails to load + /// + /// In en, this message translates to: + /// **'Could not load trades'** + String get couldNotLoadTradesMessage; + + /// Title of the release confirmation dialog + /// + /// In en, this message translates to: + /// **'Release Bitcoin'** + String get releaseBitcoinTitle; + + /// Body of the release confirmation dialog + /// + /// In en, this message translates to: + /// **'Are you sure you want to release the Satoshis to the buyer?'** + String get releaseBitcoinConfirmation; + + /// Generic Yes button label + /// + /// In en, this message translates to: + /// **'Yes'** + String get yesButtonLabel; + + /// Trade card title when the user is selling Bitcoin + /// + /// In en, this message translates to: + /// **'Selling Bitcoin'** + String get sellingBitcoin; + + /// Trade card title when the user is buying Bitcoin + /// + /// In en, this message translates to: + /// **'Buying Bitcoin'** + String get buyingBitcoin; + + /// Role badge when the user created the order + /// + /// In en, this message translates to: + /// **'Created by you'** + String get createdByYou; + + /// Role badge when the user took the order + /// + /// In en, this message translates to: + /// **'Taken by you'** + String get takenByYou; + + /// Compact relative time for just now + /// + /// In en, this message translates to: + /// **'now'** + String get timeAgoNow; + + /// Compact relative time in minutes + /// + /// In en, this message translates to: + /// **'{count}m'** + String timeAgoMinutes(int count); + + /// Compact relative time in hours + /// + /// In en, this message translates to: + /// **'{count}h'** + String timeAgoHours(int count); + + /// Compact relative time in days + /// + /// In en, this message translates to: + /// **'{count}d'** + String timeAgoDays(int count); } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 9b450576..7ad6e56d 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1274,4 +1274,87 @@ class AppLocalizationsDe extends AppLocalizations { @override String get failedToShareLogsMessage => 'Protokolle konnten nicht geteilt werden'; + + @override + String get tradeFilterAll => 'Alle'; + + @override + String get tradeFilterPending => 'Ausstehend'; + + @override + String get tradeFilterWaitingInvoice => 'Warte auf Rechnung'; + + @override + String get tradeFilterWaitingPayment => 'Warte auf Zahlung'; + + @override + String get tradeFilterActive => 'Aktiv'; + + @override + String get tradeFilterFiatSent => 'Fiat gesendet'; + + @override + String get tradeFilterSuccess => 'Erfolgreich'; + + @override + String get tradeFilterCanceled => 'Storniert'; + + @override + String get tradeFilterDispute => 'Streitfall'; + + @override + String get menuTooltip => 'Menü'; + + @override + String get tradeStatusFilterPrefix => 'Status'; + + @override + String get noTradesTitle => 'Keine Trades'; + + @override + String get noTradesSubtitle => + 'Deine aktiven und abgeschlossenen Trades erscheinen hier.'; + + @override + String get couldNotLoadTradesMessage => 'Trades konnten nicht geladen werden'; + + @override + String get releaseBitcoinTitle => 'Bitcoin freigeben'; + + @override + String get releaseBitcoinConfirmation => + 'Möchtest du die Satoshis wirklich an den Käufer freigeben?'; + + @override + String get yesButtonLabel => 'Ja'; + + @override + String get sellingBitcoin => 'Bitcoin verkaufen'; + + @override + String get buyingBitcoin => 'Bitcoin kaufen'; + + @override + String get createdByYou => 'Von dir erstellt'; + + @override + String get takenByYou => 'Von dir übernommen'; + + @override + String get timeAgoNow => 'jetzt'; + + @override + String timeAgoMinutes(int count) { + return '${count}Min'; + } + + @override + String timeAgoHours(int count) { + return '${count}Std'; + } + + @override + String timeAgoDays(int count) { + return '${count}T'; + } } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 52f5f6d7..b6ecd7c1 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1252,4 +1252,87 @@ class AppLocalizationsEn extends AppLocalizations { @override String get failedToShareLogsMessage => 'Failed to share logs'; + + @override + String get tradeFilterAll => 'All'; + + @override + String get tradeFilterPending => 'Pending'; + + @override + String get tradeFilterWaitingInvoice => 'Waiting Invoice'; + + @override + String get tradeFilterWaitingPayment => 'Waiting Payment'; + + @override + String get tradeFilterActive => 'Active'; + + @override + String get tradeFilterFiatSent => 'Fiat Sent'; + + @override + String get tradeFilterSuccess => 'Success'; + + @override + String get tradeFilterCanceled => 'Canceled'; + + @override + String get tradeFilterDispute => 'Dispute'; + + @override + String get menuTooltip => 'Menu'; + + @override + String get tradeStatusFilterPrefix => 'Status'; + + @override + String get noTradesTitle => 'No trades'; + + @override + String get noTradesSubtitle => + 'Your active and completed trades will appear here.'; + + @override + String get couldNotLoadTradesMessage => 'Could not load trades'; + + @override + String get releaseBitcoinTitle => 'Release Bitcoin'; + + @override + String get releaseBitcoinConfirmation => + 'Are you sure you want to release the Satoshis to the buyer?'; + + @override + String get yesButtonLabel => 'Yes'; + + @override + String get sellingBitcoin => 'Selling Bitcoin'; + + @override + String get buyingBitcoin => 'Buying Bitcoin'; + + @override + String get createdByYou => 'Created by you'; + + @override + String get takenByYou => 'Taken by you'; + + @override + String get timeAgoNow => 'now'; + + @override + String timeAgoMinutes(int count) { + return '${count}m'; + } + + @override + String timeAgoHours(int count) { + return '${count}h'; + } + + @override + String timeAgoDays(int count) { + return '${count}d'; + } } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 8f363339..1772e256 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1268,4 +1268,88 @@ class AppLocalizationsEs extends AppLocalizations { @override String get failedToShareLogsMessage => 'No se pudieron compartir los registros'; + + @override + String get tradeFilterAll => 'Todos'; + + @override + String get tradeFilterPending => 'Pendiente'; + + @override + String get tradeFilterWaitingInvoice => 'Esperando factura'; + + @override + String get tradeFilterWaitingPayment => 'Esperando pago'; + + @override + String get tradeFilterActive => 'Activo'; + + @override + String get tradeFilterFiatSent => 'Fiat enviado'; + + @override + String get tradeFilterSuccess => 'Exitoso'; + + @override + String get tradeFilterCanceled => 'Cancelado'; + + @override + String get tradeFilterDispute => 'Disputa'; + + @override + String get menuTooltip => 'Menú'; + + @override + String get tradeStatusFilterPrefix => 'Estado'; + + @override + String get noTradesTitle => 'Sin operaciones'; + + @override + String get noTradesSubtitle => + 'Tus operaciones activas y completadas aparecerán aquí.'; + + @override + String get couldNotLoadTradesMessage => + 'No se pudieron cargar las operaciones'; + + @override + String get releaseBitcoinTitle => 'Liberar Bitcoin'; + + @override + String get releaseBitcoinConfirmation => + '¿Seguro que quieres liberar los Satoshis al comprador?'; + + @override + String get yesButtonLabel => 'Sí'; + + @override + String get sellingBitcoin => 'Vendiendo Bitcoin'; + + @override + String get buyingBitcoin => 'Comprando Bitcoin'; + + @override + String get createdByYou => 'Creada por ti'; + + @override + String get takenByYou => 'Tomada por ti'; + + @override + String get timeAgoNow => 'ahora'; + + @override + String timeAgoMinutes(int count) { + return '${count}m'; + } + + @override + String timeAgoHours(int count) { + return '${count}h'; + } + + @override + String timeAgoDays(int count) { + return '${count}d'; + } } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 9824b38b..c62ccb82 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1274,4 +1274,88 @@ class AppLocalizationsFr extends AppLocalizations { @override String get failedToShareLogsMessage => 'Échec du partage des journaux'; + + @override + String get tradeFilterAll => 'Tous'; + + @override + String get tradeFilterPending => 'En attente'; + + @override + String get tradeFilterWaitingInvoice => 'En attente de facture'; + + @override + String get tradeFilterWaitingPayment => 'En attente de paiement'; + + @override + String get tradeFilterActive => 'Actif'; + + @override + String get tradeFilterFiatSent => 'Fiat envoyé'; + + @override + String get tradeFilterSuccess => 'Réussi'; + + @override + String get tradeFilterCanceled => 'Annulé'; + + @override + String get tradeFilterDispute => 'Litige'; + + @override + String get menuTooltip => 'Menu'; + + @override + String get tradeStatusFilterPrefix => 'Statut'; + + @override + String get noTradesTitle => 'Aucune transaction'; + + @override + String get noTradesSubtitle => + 'Vos transactions actives et terminées apparaîtront ici.'; + + @override + String get couldNotLoadTradesMessage => + 'Impossible de charger les transactions'; + + @override + String get releaseBitcoinTitle => 'Libérer les Bitcoin'; + + @override + String get releaseBitcoinConfirmation => + 'Êtes-vous sûr de vouloir libérer les Satoshis à l\'acheteur ?'; + + @override + String get yesButtonLabel => 'Oui'; + + @override + String get sellingBitcoin => 'Vente de Bitcoin'; + + @override + String get buyingBitcoin => 'Achat de Bitcoin'; + + @override + String get createdByYou => 'Créé par vous'; + + @override + String get takenByYou => 'Pris par vous'; + + @override + String get timeAgoNow => 'maintenant'; + + @override + String timeAgoMinutes(int count) { + return '${count}min'; + } + + @override + String timeAgoHours(int count) { + return '${count}h'; + } + + @override + String timeAgoDays(int count) { + return '${count}j'; + } } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 9ecc8a01..08614ff5 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1266,4 +1266,87 @@ class AppLocalizationsIt extends AppLocalizations { @override String get failedToShareLogsMessage => 'Impossibile condividere i log'; + + @override + String get tradeFilterAll => 'Tutti'; + + @override + String get tradeFilterPending => 'In attesa'; + + @override + String get tradeFilterWaitingInvoice => 'In attesa di fattura'; + + @override + String get tradeFilterWaitingPayment => 'In attesa di pagamento'; + + @override + String get tradeFilterActive => 'Attivo'; + + @override + String get tradeFilterFiatSent => 'Fiat inviato'; + + @override + String get tradeFilterSuccess => 'Riuscito'; + + @override + String get tradeFilterCanceled => 'Annullato'; + + @override + String get tradeFilterDispute => 'Disputa'; + + @override + String get menuTooltip => 'Menu'; + + @override + String get tradeStatusFilterPrefix => 'Stato'; + + @override + String get noTradesTitle => 'Nessuna operazione'; + + @override + String get noTradesSubtitle => + 'Le tue operazioni attive e completate appariranno qui.'; + + @override + String get couldNotLoadTradesMessage => 'Impossibile caricare le operazioni'; + + @override + String get releaseBitcoinTitle => 'Rilascia Bitcoin'; + + @override + String get releaseBitcoinConfirmation => + 'Sei sicuro di voler rilasciare i Satoshi all\'acquirente?'; + + @override + String get yesButtonLabel => 'Sì'; + + @override + String get sellingBitcoin => 'Vendita di Bitcoin'; + + @override + String get buyingBitcoin => 'Acquisto di Bitcoin'; + + @override + String get createdByYou => 'Creata da te'; + + @override + String get takenByYou => 'Presa da te'; + + @override + String get timeAgoNow => 'ora'; + + @override + String timeAgoMinutes(int count) { + return '${count}min'; + } + + @override + String timeAgoHours(int count) { + return '${count}h'; + } + + @override + String timeAgoDays(int count) { + return '${count}g'; + } } From 7cbf4718475b4cd3b837f458e330e9578e6bf3cf Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 01:40:03 -0300 Subject: [PATCH 09/30] feat: localize trade detail screen strings Replace hardcoded English text in trade_detail_screen.dart with localized strings from AppLocalizations. --- .../trades/screens/trade_detail_screen.dart | 194 ++++---- lib/l10n/app_de.arb | 72 ++- lib/l10n/app_en.arb | 163 ++++++- lib/l10n/app_es.arb | 72 ++- lib/l10n/app_fr.arb | 72 ++- lib/l10n/app_it.arb | 72 ++- lib/l10n/app_localizations.dart | 420 ++++++++++++++++++ lib/l10n/app_localizations_de.dart | 255 +++++++++++ lib/l10n/app_localizations_en.dart | 251 +++++++++++ lib/l10n/app_localizations_es.dart | 253 +++++++++++ lib/l10n/app_localizations_fr.dart | 254 +++++++++++ lib/l10n/app_localizations_it.dart | 254 +++++++++++ 12 files changed, 2237 insertions(+), 95 deletions(-) diff --git a/lib/features/trades/screens/trade_detail_screen.dart b/lib/features/trades/screens/trade_detail_screen.dart index 0bdae5ec..eb2d117d 100644 --- a/lib/features/trades/screens/trade_detail_screen.dart +++ b/lib/features/trades/screens/trade_detail_screen.dart @@ -65,6 +65,23 @@ enum TradeStatus { final String label; } +/// Localized display label for the trade status pill. +extension TradeStatusL10n on TradeStatus { + String localizedLabel(AppLocalizations l10n) => switch (this) { + TradeStatus.loading => l10n.tradeStatusLoading, + TradeStatus.pending => l10n.tradeFilterPending, + TradeStatus.waitingInvoice => l10n.tradeFilterWaitingInvoice, + TradeStatus.waitingPayment => l10n.tradeFilterWaitingPayment, + TradeStatus.active => l10n.tradeStatusActive, + TradeStatus.fiatSent => l10n.tradeStatusFiatSent, + TradeStatus.completed => l10n.tradeStatusCompleted, + TradeStatus.cancelled => l10n.tradeStatusCancelled, + TradeStatus.disputed => l10n.tradeStatusDisputed, + TradeStatus.pendingRating => l10n.tradeStatusRate, + TradeStatus.rated => l10n.tradeStatusRated, + }; +} + /// Overflow-menu actions (cancel / dispute / release collapsed behind ⋮). enum _MenuAction { cancel, dispute, release } @@ -248,40 +265,34 @@ class _TradeDetailScreenState extends ConsumerState { } if (isBuyer) { if (status == TradeStatus.active) { - return 'Once you have sent the money, mark it below. ' - 'Only open a dispute if the seller stops responding.'; + return l10n.tradeInstructionActiveBuyer; } else if (status == TradeStatus.fiatSent) { - return 'Fiat payment marked as sent. Waiting for the seller ' - 'to confirm receipt and release your sats.'; + return l10n.tradeInstructionFiatSentBuyer; } } else { // Seller if (status == TradeStatus.active) { - return 'Contact the buyer with payment instructions via the chat above.'; + return l10n.tradeInstructionActiveSeller; } else if (status == TradeStatus.fiatSent) { - return 'The buyer has confirmed they sent the fiat payment. ' - 'Once you verify receipt, release the sats.'; + return l10n.tradeInstructionFiatSentSeller; } } if (status == TradeStatus.disputed) { - return 'A dispute resolver has been assigned. ' - 'They will contact you through the app.'; + return l10n.tradeInstructionDisputed; } if (status == TradeStatus.pendingRating) { - return 'The trade completed successfully. ' - 'Rate your counterpart to help build trust in the community.'; + return l10n.tradeInstructionPendingRating; } if (status == TradeStatus.rated) { - return 'Thank you for your rating!'; + return l10n.tradeInstructionRated; } if (status == TradeStatus.pending) { - return 'Your order is published and waiting for a counterpart to take it. ' - 'You can cancel it at any time.'; + return l10n.tradeInstructionPending; } if (status == TradeStatus.cancelled) { - return 'This trade was cancelled. No funds were exchanged.'; + return l10n.tradeInstructionCancelled; } - return 'Trade in progress.'; + return l10n.tradeInstructionInProgress; } String _formatDuration(Duration d) { @@ -296,66 +307,64 @@ class _TradeDetailScreenState extends ConsumerState { /// Headline of the state strip — the one thing happening right now. String _headline(bool isBuyer, TradeStatus status, OrderItem? order) { + final l10n = AppLocalizations.of(context); final amount = order != null ? '${order.displayAmount} ${order.fiatCode}' - : 'the agreed amount'; + : l10n.theAgreedAmount; return switch (status) { - TradeStatus.pending => 'Waiting for someone to take your order', + TradeStatus.pending => l10n.tradeHeadlinePending, TradeStatus.waitingInvoice => isBuyer - ? 'Share a Lightning invoice to receive your sats' - : 'Waiting for the buyer to share an invoice', + ? l10n.tradeHeadlineWaitingInvoiceBuyer + : l10n.tradeHeadlineWaitingInvoiceSeller, TradeStatus.waitingPayment => isBuyer - ? 'Waiting for the seller to lock the sats' - : 'Pay the hold invoice to lock the sats', + ? l10n.tradeHeadlineWaitingPaymentBuyer + : l10n.tradeHeadlineWaitingPaymentSeller, TradeStatus.active => isBuyer - ? 'Send $amount to the seller' - : 'Waiting for the buyer to send $amount', + ? l10n.tradeHeadlineActiveBuyer(amount) + : l10n.tradeHeadlineActiveSeller(amount), TradeStatus.fiatSent => isBuyer - ? 'Waiting for the seller to release your sats' - : 'Confirm you received $amount', - TradeStatus.disputed => 'Dispute in progress', + ? l10n.tradeHeadlineFiatSentBuyer + : l10n.tradeHeadlineFiatSentSeller(amount), + TradeStatus.disputed => l10n.tradeHeadlineDisputed, TradeStatus.pendingRating || - TradeStatus.completed => 'Trade complete!', - TradeStatus.rated => 'Trade complete', - TradeStatus.cancelled => 'Order cancelled', - TradeStatus.loading => 'Loading trade…', + TradeStatus.completed => l10n.tradeHeadlineComplete, + TradeStatus.rated => l10n.tradeHeadlineCompleteRated, + TradeStatus.cancelled => l10n.tradeHeadlineCancelled, + TradeStatus.loading => l10n.tradeHeadlineLoading, }; } /// Contextual timer copy: what expires and what happens then. (String, String)? _timerContext(bool isBuyer, TradeStatus status) { + final l10n = AppLocalizations.of(context); return switch (status) { TradeStatus.pending => ( - 'Time for this order to stay in the book', - 'If it expires, the order is removed from the book. ' - "It won't affect your reputation.", + l10n.tradeTimerPendingLabel, + l10n.tradeTimerPendingConsequence, ), TradeStatus.waitingInvoice => ( isBuyer - ? 'Time to share your invoice' - : 'Time for the buyer to share an invoice', - 'If it expires, the trade is cancelled and the order ' - 'returns to the book.', + ? l10n.tradeTimerWaitingInvoiceLabelBuyer + : l10n.tradeTimerWaitingInvoiceLabelSeller, + l10n.tradeTimerWaitingInvoiceConsequence, ), TradeStatus.waitingPayment => ( isBuyer - ? 'Time for the seller to lock the sats' - : 'Time to pay the hold invoice', - 'If it expires, the trade is cancelled and the order ' - 'returns to the book.', + ? l10n.tradeTimerWaitingPaymentLabelBuyer + : l10n.tradeTimerWaitingPaymentLabelSeller, + l10n.tradeTimerWaitingInvoiceConsequence, ), TradeStatus.active => ( isBuyer - ? 'Time to send the fiat payment' - : 'Time for the buyer to send the fiat', - 'If it expires, the trade can be cancelled. ' - 'Coordinate in the chat if more time is needed.', + ? l10n.tradeTimerActiveLabelBuyer + : l10n.tradeTimerActiveLabelSeller, + l10n.tradeTimerActiveConsequence, ), TradeStatus.fiatSent => ( isBuyer - ? 'Time for the seller to confirm receipt' - : 'Time to confirm receipt and release', - 'If something looks wrong, open a dispute from the ⋮ menu.', + ? l10n.tradeTimerFiatSentLabelBuyer + : l10n.tradeTimerFiatSentLabelSeller, + l10n.tradeTimerFiatSentConsequence, ), _ => null, }; @@ -389,17 +398,16 @@ class _TradeDetailScreenState extends ConsumerState { /// Step labels. Lightning setup is one step because the invoice/hold-invoice /// order depends on which side made the order. - List _steps(bool isBuyer) => [ - 'Order taken', - isBuyer - ? 'You share an invoice · seller locks the sats' - : 'Buyer shares an invoice · you lock the sats', - isBuyer ? 'You send the fiat payment' : 'Buyer sends the fiat payment', - isBuyer - ? 'Seller confirms and releases your sats' - : 'You confirm receipt and release the sats', - 'Rate your counterpart', - ]; + List _steps(bool isBuyer) { + final l10n = AppLocalizations.of(context); + return [ + l10n.tradeStepOrderTaken, + isBuyer ? l10n.tradeStepInvoiceBuyer : l10n.tradeStepInvoiceSeller, + isBuyer ? l10n.tradeStepFiatBuyer : l10n.tradeStepFiatSeller, + isBuyer ? l10n.tradeStepReleaseBuyer : l10n.tradeStepReleaseSeller, + l10n.tradeStepRate, + ]; + } // ── Build ──────────────────────────────────────────────────────────────── @@ -409,6 +417,7 @@ class _TradeDetailScreenState extends ConsumerState { final colors = theme.extension(); final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); final textSec = colors?.textSecondary ?? const Color(0xFFB0B3C6); + final l10n = AppLocalizations.of(context); // Derive role: in-memory map (set by TakeOrderScreen in this session) takes // priority; fall back to the DB-backed provider so reopened trades after an @@ -445,7 +454,7 @@ class _TradeDetailScreenState extends ConsumerState { return Scaffold( appBar: AppBar( - title: Text(inFlight ? 'ACTIVE TRADE' : 'ORDER DETAILS'), + title: Text(inFlight ? l10n.activeTradeTitle : l10n.orderDetailsTitle), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => @@ -484,7 +493,7 @@ class _TradeDetailScreenState extends ConsumerState { children: [ Flexible( child: Text( - 'ID ${_shortId(widget.orderId)}', + l10n.tradeIdShortLabel(_shortId(widget.orderId)), style: TextStyle( color: textSec, fontSize: 12, @@ -498,9 +507,9 @@ class _TradeDetailScreenState extends ConsumerState { onTap: () { Clipboard.setData(ClipboardData(text: widget.orderId)); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Order ID copied'), - duration: Duration(seconds: 1), + SnackBar( + content: Text(l10n.orderIdCopied), + duration: const Duration(seconds: 1), ), ); }, @@ -509,7 +518,7 @@ class _TradeDetailScreenState extends ConsumerState { const Spacer(), if (order != null) Text( - 'created ${_formatDate(order.createdAt)}', + l10n.tradeCreatedAtLabel(_formatDate(order.createdAt)), style: TextStyle(color: textSec, fontSize: 12), ), ], @@ -528,6 +537,7 @@ class _TradeDetailScreenState extends ConsumerState { Widget _buildOverflowMenu( TradeStatus status, bool isBuyer, AppColors? colors) { final red = colors?.destructiveRed ?? const Color(0xFFD84D4D); + final l10n = AppLocalizations.of(context); final canCancel = const { TradeStatus.pending, @@ -554,12 +564,12 @@ class _TradeDetailScreenState extends ConsumerState { }, itemBuilder: (ctx) => [ if (canRelease) - const PopupMenuItem( + PopupMenuItem( value: _MenuAction.release, child: ListTile( contentPadding: EdgeInsets.zero, - leading: Icon(Icons.lock_open, size: 18), - title: Text('Release sats'), + leading: const Icon(Icons.lock_open, size: 18), + title: Text(l10n.releaseSatsMenuItem), dense: true, ), ), @@ -569,7 +579,7 @@ class _TradeDetailScreenState extends ConsumerState { child: ListTile( contentPadding: EdgeInsets.zero, leading: Icon(Icons.cancel_outlined, size: 18, color: red), - title: Text('Cancel order', style: TextStyle(color: red)), + title: Text(l10n.cancelOrderMenuItem, style: TextStyle(color: red)), dense: true, ), ), @@ -579,7 +589,7 @@ class _TradeDetailScreenState extends ConsumerState { child: ListTile( contentPadding: EdgeInsets.zero, leading: Icon(Icons.gavel, size: 18, color: red), - title: Text('Open dispute', style: TextStyle(color: red)), + title: Text(l10n.openDisputeMenuItem, style: TextStyle(color: red)), dense: true, ), ), @@ -603,6 +613,7 @@ class _TradeDetailScreenState extends ConsumerState { final totalSteps = _steps(isBuyer).length; final timerCtx = _timerContext(isBuyer, status); final showTimer = timerCtx != null && _remaining > Duration.zero; + final l10n = AppLocalizations.of(context); return Container( width: double.infinity, @@ -619,15 +630,15 @@ class _TradeDetailScreenState extends ConsumerState { if (currentStep >= 0) _Pill( label: currentStep >= totalSteps - ? 'DONE' - : 'STEP ${currentStep + 1} OF $totalSteps', + ? l10n.stepDoneLabel + : l10n.stepIndicator(currentStep + 1, totalSteps), background: colors?.backgroundElevated ?? const Color(0xFF2A2D35), foreground: textSec, ), if (currentStep >= 0) const SizedBox(width: AppSpacing.sm), _Pill( - label: status.label, + label: status.localizedLabel(l10n), background: pillBg, foreground: pillFg, ), @@ -739,6 +750,7 @@ class _TradeDetailScreenState extends ConsumerState { AppColors? colors, ) { final red = colors?.destructiveRed ?? const Color(0xFFD84D4D); + final l10n = AppLocalizations.of(context); FilledButton bigButton({ required String label, @@ -767,7 +779,7 @@ class _TradeDetailScreenState extends ConsumerState { case (TradeStatus.waitingInvoice, true): return [ bigButton( - label: 'Add Lightning invoice', + label: l10n.addLightningInvoiceButton, icon: Icons.receipt_long_outlined, onPressed: () => context.push(AppRoute.addInvoicePath(widget.orderId)), @@ -776,7 +788,7 @@ class _TradeDetailScreenState extends ConsumerState { case (TradeStatus.waitingPayment, false): return [ bigButton( - label: 'Pay hold invoice', + label: l10n.payHoldInvoiceButton, icon: Icons.bolt, onPressed: () => context.push(AppRoute.payInvoicePath(widget.orderId)), @@ -785,7 +797,7 @@ class _TradeDetailScreenState extends ConsumerState { case (TradeStatus.active, true): return [ MostroReactiveButton( - label: 'Mark fiat sent', + label: l10n.markFiatSentButton, backgroundColor: green, icon: Icons.check, onPressed: () async { @@ -805,7 +817,7 @@ class _TradeDetailScreenState extends ConsumerState { case (TradeStatus.fiatSent, false): return [ MostroReactiveButton( - label: 'Confirm & release sats', + label: l10n.confirmReleaseSatsButton, backgroundColor: green, icon: Icons.lock_open, onPressed: _releaseOrder, @@ -822,7 +834,7 @@ class _TradeDetailScreenState extends ConsumerState { case (TradeStatus.disputed, _): return [ bigButton( - label: 'View dispute', + label: l10n.viewDisputeButton, icon: Icons.gavel, background: red, foreground: Colors.white, @@ -847,7 +859,7 @@ class _TradeDetailScreenState extends ConsumerState { case (TradeStatus.pendingRating, _): return [ bigButton( - label: 'Rate your counterpart', + label: l10n.tradeStepRate, icon: Icons.star_outline, onPressed: () { if (ref.read(privacyModeProvider)) { @@ -871,21 +883,21 @@ class _TradeDetailScreenState extends ConsumerState { borderRadius: BorderRadius.circular(AppRadius.card), ), ), - child: const Text('CLOSE'), + child: Text(l10n.closeRatingButton), ), ]; // Waiting on the counterpart (or order still pending / loading): // disabled button with spinner so the "next action" is explicit. case (TradeStatus.waitingInvoice, false): - return [_waitingButton('Waiting for the buyer…', colors)]; + return [_waitingButton(l10n.waitingForBuyer, colors)]; case (TradeStatus.waitingPayment, true): - return [_waitingButton('Waiting for the seller…', colors)]; + return [_waitingButton(l10n.waitingForSeller, colors)]; case (TradeStatus.active, false): - return [_waitingButton('Waiting for the fiat payment…', colors)]; + return [_waitingButton(l10n.waitingForFiatPayment, colors)]; case (TradeStatus.fiatSent, true): - return [_waitingButton('Waiting for the seller…', colors)]; + return [_waitingButton(l10n.waitingForSeller, colors)]; case (TradeStatus.pending, _): - return [_waitingButton('Waiting for a counterpart…', colors)]; + return [_waitingButton(l10n.waitingForCounterpart, colors)]; default: return const []; } @@ -935,6 +947,7 @@ class _TradeDetailScreenState extends ConsumerState { final amber = colors?.warningAmber ?? const Color(0xFFE89C3C); final textSec = colors?.textSecondary ?? const Color(0xFFB0B3C6); final textSubtle = colors?.textSubtle ?? const Color(0xFF9A9A9C); + final l10n = AppLocalizations.of(context); final steps = _steps(isBuyer); final current = _currentStep(status); @@ -950,7 +963,7 @@ class _TradeDetailScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'YOUR TRADE', + l10n.yourTradeTimelineTitle, style: TextStyle( color: textSubtle, fontSize: 11, @@ -1065,11 +1078,12 @@ class _ChatChip extends ConsumerWidget { final colors = Theme.of(context).extension(); final purple = colors?.purpleButton ?? const Color(0xFF8359C2); final textSec = colors?.textSecondary ?? const Color(0xFFB0B3C6); + final l10n = AppLocalizations.of(context); final rooms = ref.watch(chatRoomsNotifierProvider); final room = rooms.where((r) => r.orderId == orderId).firstOrNull; - final handle = room?.peerHandle ?? 'your counterpart'; + final handle = room?.peerHandle ?? l10n.yourCounterpartFallback; final unread = room?.unreadCount ?? 0; return InkWell( @@ -1120,8 +1134,8 @@ class _ChatChip extends ConsumerWidget { ), Text( unread > 0 - ? 'Secure chat · $unread new' - : 'Secure chat · end-to-end encrypted', + ? l10n.secureChatUnread(unread) + : l10n.secureChatEncrypted, style: TextStyle(fontSize: 11, color: textSec), overflow: TextOverflow.ellipsis, ), diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b23278ae..0d53e38a 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -428,5 +428,75 @@ "timeAgoNow": "jetzt", "timeAgoMinutes": "{count}Min", "timeAgoHours": "{count}Std", - "timeAgoDays": "{count}T" + "timeAgoDays": "{count}T", + "tradeStatusLoading": "Wird geladen", + "tradeStatusRate": "Bewerten", + "tradeStatusRated": "Bewertet", + "tradeInstructionActiveBuyer": "Sobald du das Geld gesendet hast, markiere es unten. Öffne nur einen Streitfall, wenn der Verkäufer nicht mehr antwortet.", + "tradeInstructionFiatSentBuyer": "Fiat-Zahlung als gesendet markiert. Warte darauf, dass der Verkäufer den Empfang bestätigt und deine Sats freigibt.", + "tradeInstructionActiveSeller": "Kontaktiere den Käufer mit den Zahlungsanweisungen über den Chat oben.", + "tradeInstructionFiatSentSeller": "Der Käufer hat bestätigt, die Fiat-Zahlung gesendet zu haben. Sobald du den Empfang prüfst, gib die Sats frei.", + "tradeInstructionDisputed": "Ein Streitschlichter wurde zugewiesen. Er wird dich über die App kontaktieren.", + "tradeInstructionPendingRating": "Der Trade wurde erfolgreich abgeschlossen. Bewerte deine Gegenpartei, um Vertrauen in der Community aufzubauen.", + "tradeInstructionRated": "Danke für deine Bewertung!", + "tradeInstructionPending": "Deine Bestellung ist veröffentlicht und wartet auf eine Gegenpartei. Du kannst sie jederzeit stornieren.", + "tradeInstructionCancelled": "Dieser Trade wurde storniert. Es wurden keine Gelder ausgetauscht.", + "tradeInstructionInProgress": "Trade läuft.", + "theAgreedAmount": "den vereinbarten Betrag", + "tradeHeadlinePending": "Warte darauf, dass jemand deine Bestellung annimmt", + "tradeHeadlineWaitingInvoiceBuyer": "Teile eine Lightning-Rechnung, um deine Sats zu erhalten", + "tradeHeadlineWaitingInvoiceSeller": "Warte darauf, dass der Käufer eine Rechnung teilt", + "tradeHeadlineWaitingPaymentBuyer": "Warte darauf, dass der Verkäufer die Sats sperrt", + "tradeHeadlineWaitingPaymentSeller": "Bezahle die Hold-Invoice, um die Sats zu sperren", + "tradeHeadlineActiveBuyer": "Sende {amount} an den Verkäufer", + "tradeHeadlineActiveSeller": "Warte darauf, dass der Käufer {amount} sendet", + "tradeHeadlineFiatSentBuyer": "Warte darauf, dass der Verkäufer deine Sats freigibt", + "tradeHeadlineFiatSentSeller": "Bestätige, dass du {amount} erhalten hast", + "tradeHeadlineDisputed": "Streitfall läuft", + "tradeHeadlineComplete": "Trade abgeschlossen!", + "tradeHeadlineCompleteRated": "Trade abgeschlossen", + "tradeHeadlineCancelled": "Bestellung storniert", + "tradeHeadlineLoading": "Trade wird geladen…", + "tradeTimerPendingLabel": "Zeit, in der diese Bestellung im Buch bleibt", + "tradeTimerPendingConsequence": "Bei Ablauf wird die Bestellung aus dem Buch entfernt. Es wirkt sich nicht auf deine Reputation aus.", + "tradeTimerWaitingInvoiceLabelBuyer": "Zeit, deine Rechnung zu teilen", + "tradeTimerWaitingInvoiceLabelSeller": "Zeit für den Käufer, eine Rechnung zu teilen", + "tradeTimerWaitingInvoiceConsequence": "Bei Ablauf wird der Trade storniert und die Bestellung kehrt ins Buch zurück.", + "tradeTimerWaitingPaymentLabelBuyer": "Zeit für den Verkäufer, die Sats zu sperren", + "tradeTimerWaitingPaymentLabelSeller": "Zeit, die Hold-Invoice zu bezahlen", + "tradeTimerActiveLabelBuyer": "Zeit, die Fiat-Zahlung zu senden", + "tradeTimerActiveLabelSeller": "Zeit für den Käufer, das Fiat zu senden", + "tradeTimerActiveConsequence": "Bei Ablauf kann der Trade storniert werden. Stimmt euch im Chat ab, wenn mehr Zeit nötig ist.", + "tradeTimerFiatSentLabelBuyer": "Zeit für den Verkäufer, den Empfang zu bestätigen", + "tradeTimerFiatSentLabelSeller": "Zeit, den Empfang zu bestätigen und freizugeben", + "tradeTimerFiatSentConsequence": "Wenn etwas nicht stimmt, öffne einen Streitfall über das ⋮-Menü.", + "tradeStepOrderTaken": "Bestellung angenommen", + "tradeStepInvoiceBuyer": "Du teilst eine Rechnung · der Verkäufer sperrt die Sats", + "tradeStepInvoiceSeller": "Der Käufer teilt eine Rechnung · du sperrst die Sats", + "tradeStepFiatBuyer": "Du sendest die Fiat-Zahlung", + "tradeStepFiatSeller": "Der Käufer sendet die Fiat-Zahlung", + "tradeStepReleaseBuyer": "Der Verkäufer bestätigt und gibt deine Sats frei", + "tradeStepReleaseSeller": "Du bestätigst den Empfang und gibst die Sats frei", + "tradeStepRate": "Bewerte deine Gegenpartei", + "activeTradeTitle": "AKTIVER TRADE", + "tradeIdShortLabel": "ID {id}", + "tradeCreatedAtLabel": "erstellt {date}", + "releaseSatsMenuItem": "Sats freigeben", + "cancelOrderMenuItem": "Bestellung stornieren", + "openDisputeMenuItem": "Streitfall öffnen", + "stepDoneLabel": "FERTIG", + "stepIndicator": "SCHRITT {current} VON {total}", + "addLightningInvoiceButton": "Lightning-Rechnung hinzufügen", + "payHoldInvoiceButton": "Hold-Invoice bezahlen", + "markFiatSentButton": "Fiat als gesendet markieren", + "confirmReleaseSatsButton": "Bestätigen und Sats freigeben", + "viewDisputeButton": "Streitfall ansehen", + "waitingForBuyer": "Warte auf den Käufer…", + "waitingForSeller": "Warte auf den Verkäufer…", + "waitingForFiatPayment": "Warte auf die Fiat-Zahlung…", + "waitingForCounterpart": "Warte auf eine Gegenpartei…", + "yourTradeTimelineTitle": "DEIN TRADE", + "yourCounterpartFallback": "deine Gegenpartei", + "secureChatUnread": "Sicherer Chat · {count} neue", + "secureChatEncrypted": "Sicherer Chat · Ende-zu-Ende-verschlüsselt" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2333b652..83749709 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -890,5 +890,166 @@ "@timeAgoDays": { "description": "Compact relative time in days", "placeholders": {"count": {"type": "int"}} - } + }, + "tradeStatusLoading": "Loading", + "@tradeStatusLoading": {"description": "Trade status pill: loading"}, + "tradeStatusRate": "Rate", + "@tradeStatusRate": {"description": "Trade status pill: pending rating"}, + "tradeStatusRated": "Rated", + "@tradeStatusRated": {"description": "Trade status pill: rated"}, + "tradeInstructionActiveBuyer": "Once you have sent the money, mark it below. Only open a dispute if the seller stops responding.", + "@tradeInstructionActiveBuyer": {"description": "Instruction for buyer while trade is active"}, + "tradeInstructionFiatSentBuyer": "Fiat payment marked as sent. Waiting for the seller to confirm receipt and release your sats.", + "@tradeInstructionFiatSentBuyer": {"description": "Instruction for buyer after marking fiat sent"}, + "tradeInstructionActiveSeller": "Contact the buyer with payment instructions via the chat above.", + "@tradeInstructionActiveSeller": {"description": "Instruction for seller while trade is active"}, + "tradeInstructionFiatSentSeller": "The buyer has confirmed they sent the fiat payment. Once you verify receipt, release the sats.", + "@tradeInstructionFiatSentSeller": {"description": "Instruction for seller after buyer marks fiat sent"}, + "tradeInstructionDisputed": "A dispute resolver has been assigned. They will contact you through the app.", + "@tradeInstructionDisputed": {"description": "Instruction while a dispute is in progress"}, + "tradeInstructionPendingRating": "The trade completed successfully. Rate your counterpart to help build trust in the community.", + "@tradeInstructionPendingRating": {"description": "Instruction when the trade completed and awaits rating"}, + "tradeInstructionRated": "Thank you for your rating!", + "@tradeInstructionRated": {"description": "Instruction after the counterpart has been rated"}, + "tradeInstructionPending": "Your order is published and waiting for a counterpart to take it. You can cancel it at any time.", + "@tradeInstructionPending": {"description": "Instruction while the order is pending a taker"}, + "tradeInstructionCancelled": "This trade was cancelled. No funds were exchanged.", + "@tradeInstructionCancelled": {"description": "Instruction when the trade was cancelled"}, + "tradeInstructionInProgress": "Trade in progress.", + "@tradeInstructionInProgress": {"description": "Generic in-progress instruction fallback"}, + "theAgreedAmount": "the agreed amount", + "@theAgreedAmount": {"description": "Fallback for the trade amount when the order is unknown"}, + "tradeHeadlinePending": "Waiting for someone to take your order", + "@tradeHeadlinePending": {"description": "Headline while the order is pending"}, + "tradeHeadlineWaitingInvoiceBuyer": "Share a Lightning invoice to receive your sats", + "@tradeHeadlineWaitingInvoiceBuyer": {"description": "Headline for buyer while waiting for invoice"}, + "tradeHeadlineWaitingInvoiceSeller": "Waiting for the buyer to share an invoice", + "@tradeHeadlineWaitingInvoiceSeller": {"description": "Headline for seller while waiting for invoice"}, + "tradeHeadlineWaitingPaymentBuyer": "Waiting for the seller to lock the sats", + "@tradeHeadlineWaitingPaymentBuyer": {"description": "Headline for buyer while waiting for payment"}, + "tradeHeadlineWaitingPaymentSeller": "Pay the hold invoice to lock the sats", + "@tradeHeadlineWaitingPaymentSeller": {"description": "Headline for seller while waiting for payment"}, + "tradeHeadlineActiveBuyer": "Send {amount} to the seller", + "@tradeHeadlineActiveBuyer": { + "description": "Headline for buyer to send fiat", + "placeholders": {"amount": {"type": "String"}} + }, + "tradeHeadlineActiveSeller": "Waiting for the buyer to send {amount}", + "@tradeHeadlineActiveSeller": { + "description": "Headline for seller waiting for fiat", + "placeholders": {"amount": {"type": "String"}} + }, + "tradeHeadlineFiatSentBuyer": "Waiting for the seller to release your sats", + "@tradeHeadlineFiatSentBuyer": {"description": "Headline for buyer waiting for release"}, + "tradeHeadlineFiatSentSeller": "Confirm you received {amount}", + "@tradeHeadlineFiatSentSeller": { + "description": "Headline for seller to confirm receipt", + "placeholders": {"amount": {"type": "String"}} + }, + "tradeHeadlineDisputed": "Dispute in progress", + "@tradeHeadlineDisputed": {"description": "Headline while a dispute is in progress"}, + "tradeHeadlineComplete": "Trade complete!", + "@tradeHeadlineComplete": {"description": "Headline when the trade completed"}, + "tradeHeadlineCompleteRated": "Trade complete", + "@tradeHeadlineCompleteRated": {"description": "Headline when the trade completed and was rated"}, + "tradeHeadlineCancelled": "Order cancelled", + "@tradeHeadlineCancelled": {"description": "Headline when the order was cancelled"}, + "tradeHeadlineLoading": "Loading trade…", + "@tradeHeadlineLoading": {"description": "Headline while the trade is loading"}, + "tradeTimerPendingLabel": "Time for this order to stay in the book", + "@tradeTimerPendingLabel": {"description": "Timer label while the order is pending"}, + "tradeTimerPendingConsequence": "If it expires, the order is removed from the book. It won't affect your reputation.", + "@tradeTimerPendingConsequence": {"description": "Timer consequence while the order is pending"}, + "tradeTimerWaitingInvoiceLabelBuyer": "Time to share your invoice", + "@tradeTimerWaitingInvoiceLabelBuyer": {"description": "Timer label for buyer waiting for invoice"}, + "tradeTimerWaitingInvoiceLabelSeller": "Time for the buyer to share an invoice", + "@tradeTimerWaitingInvoiceLabelSeller": {"description": "Timer label for seller waiting for invoice"}, + "tradeTimerWaitingInvoiceConsequence": "If it expires, the trade is cancelled and the order returns to the book.", + "@tradeTimerWaitingInvoiceConsequence": {"description": "Timer consequence for the waiting-invoice/payment state"}, + "tradeTimerWaitingPaymentLabelBuyer": "Time for the seller to lock the sats", + "@tradeTimerWaitingPaymentLabelBuyer": {"description": "Timer label for buyer waiting for payment"}, + "tradeTimerWaitingPaymentLabelSeller": "Time to pay the hold invoice", + "@tradeTimerWaitingPaymentLabelSeller": {"description": "Timer label for seller waiting for payment"}, + "tradeTimerActiveLabelBuyer": "Time to send the fiat payment", + "@tradeTimerActiveLabelBuyer": {"description": "Timer label for buyer to send fiat"}, + "tradeTimerActiveLabelSeller": "Time for the buyer to send the fiat", + "@tradeTimerActiveLabelSeller": {"description": "Timer label for seller waiting for fiat"}, + "tradeTimerActiveConsequence": "If it expires, the trade can be cancelled. Coordinate in the chat if more time is needed.", + "@tradeTimerActiveConsequence": {"description": "Timer consequence while the trade is active"}, + "tradeTimerFiatSentLabelBuyer": "Time for the seller to confirm receipt", + "@tradeTimerFiatSentLabelBuyer": {"description": "Timer label for buyer waiting for confirmation"}, + "tradeTimerFiatSentLabelSeller": "Time to confirm receipt and release", + "@tradeTimerFiatSentLabelSeller": {"description": "Timer label for seller to confirm and release"}, + "tradeTimerFiatSentConsequence": "If something looks wrong, open a dispute from the ⋮ menu.", + "@tradeTimerFiatSentConsequence": {"description": "Timer consequence after fiat is marked sent"}, + "tradeStepOrderTaken": "Order taken", + "@tradeStepOrderTaken": {"description": "Timeline step: order taken"}, + "tradeStepInvoiceBuyer": "You share an invoice · seller locks the sats", + "@tradeStepInvoiceBuyer": {"description": "Timeline step for buyer: share invoice"}, + "tradeStepInvoiceSeller": "Buyer shares an invoice · you lock the sats", + "@tradeStepInvoiceSeller": {"description": "Timeline step for seller: lock sats"}, + "tradeStepFiatBuyer": "You send the fiat payment", + "@tradeStepFiatBuyer": {"description": "Timeline step for buyer: send fiat"}, + "tradeStepFiatSeller": "Buyer sends the fiat payment", + "@tradeStepFiatSeller": {"description": "Timeline step for seller: buyer sends fiat"}, + "tradeStepReleaseBuyer": "Seller confirms and releases your sats", + "@tradeStepReleaseBuyer": {"description": "Timeline step for buyer: seller releases sats"}, + "tradeStepReleaseSeller": "You confirm receipt and release the sats", + "@tradeStepReleaseSeller": {"description": "Timeline step for seller: confirm and release"}, + "tradeStepRate": "Rate your counterpart", + "@tradeStepRate": {"description": "Timeline step and CTA: rate your counterpart"}, + "activeTradeTitle": "ACTIVE TRADE", + "@activeTradeTitle": {"description": "App bar title while a trade is in flight"}, + "tradeIdShortLabel": "ID {id}", + "@tradeIdShortLabel": { + "description": "Short order ID label in the meta footer", + "placeholders": {"id": {"type": "String"}} + }, + "tradeCreatedAtLabel": "created {date}", + "@tradeCreatedAtLabel": { + "description": "Created-at label in the meta footer", + "placeholders": {"date": {"type": "String"}} + }, + "releaseSatsMenuItem": "Release sats", + "@releaseSatsMenuItem": {"description": "Overflow menu item: release sats"}, + "cancelOrderMenuItem": "Cancel order", + "@cancelOrderMenuItem": {"description": "Overflow menu item: cancel order"}, + "openDisputeMenuItem": "Open dispute", + "@openDisputeMenuItem": {"description": "Overflow menu item: open dispute"}, + "stepDoneLabel": "DONE", + "@stepDoneLabel": {"description": "Step pill when all steps are complete"}, + "stepIndicator": "STEP {current} OF {total}", + "@stepIndicator": { + "description": "Step pill showing progress", + "placeholders": {"current": {"type": "int"}, "total": {"type": "int"}} + }, + "addLightningInvoiceButton": "Add Lightning invoice", + "@addLightningInvoiceButton": {"description": "Primary CTA: add Lightning invoice"}, + "payHoldInvoiceButton": "Pay hold invoice", + "@payHoldInvoiceButton": {"description": "Primary CTA: pay hold invoice"}, + "markFiatSentButton": "Mark fiat sent", + "@markFiatSentButton": {"description": "Primary CTA: mark fiat sent"}, + "confirmReleaseSatsButton": "Confirm & release sats", + "@confirmReleaseSatsButton": {"description": "Primary CTA: confirm and release sats"}, + "viewDisputeButton": "View dispute", + "@viewDisputeButton": {"description": "Primary CTA: view dispute"}, + "waitingForBuyer": "Waiting for the buyer…", + "@waitingForBuyer": {"description": "Disabled CTA: waiting for the buyer"}, + "waitingForSeller": "Waiting for the seller…", + "@waitingForSeller": {"description": "Disabled CTA: waiting for the seller"}, + "waitingForFiatPayment": "Waiting for the fiat payment…", + "@waitingForFiatPayment": {"description": "Disabled CTA: waiting for the fiat payment"}, + "waitingForCounterpart": "Waiting for a counterpart…", + "@waitingForCounterpart": {"description": "Disabled CTA: waiting for a counterpart"}, + "yourTradeTimelineTitle": "YOUR TRADE", + "@yourTradeTimelineTitle": {"description": "Header of the trade step timeline"}, + "yourCounterpartFallback": "your counterpart", + "@yourCounterpartFallback": {"description": "Fallback name for the counterpart in the chat chip"}, + "secureChatUnread": "Secure chat · {count} new", + "@secureChatUnread": { + "description": "Chat chip subtitle with unread count", + "placeholders": {"count": {"type": "int"}} + }, + "secureChatEncrypted": "Secure chat · end-to-end encrypted", + "@secureChatEncrypted": {"description": "Chat chip subtitle when there are no unread messages"} } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 99b8690b..96a4f069 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -428,5 +428,75 @@ "timeAgoNow": "ahora", "timeAgoMinutes": "{count}m", "timeAgoHours": "{count}h", - "timeAgoDays": "{count}d" + "timeAgoDays": "{count}d", + "tradeStatusLoading": "Cargando", + "tradeStatusRate": "Calificar", + "tradeStatusRated": "Calificado", + "tradeInstructionActiveBuyer": "Una vez que hayas enviado el dinero, márcalo abajo. Abre una disputa solo si el vendedor deja de responder.", + "tradeInstructionFiatSentBuyer": "Pago fiat marcado como enviado. Esperando que el vendedor confirme la recepción y libere tus sats.", + "tradeInstructionActiveSeller": "Contacta al comprador con las instrucciones de pago a través del chat de arriba.", + "tradeInstructionFiatSentSeller": "El comprador confirmó que envió el pago fiat. Una vez que verifiques la recepción, libera los sats.", + "tradeInstructionDisputed": "Se asignó un resolutor de disputas. Se pondrá en contacto contigo a través de la app.", + "tradeInstructionPendingRating": "La operación se completó con éxito. Califica a tu contraparte para ayudar a construir confianza en la comunidad.", + "tradeInstructionRated": "¡Gracias por tu calificación!", + "tradeInstructionPending": "Tu orden está publicada y esperando a que una contraparte la tome. Puedes cancelarla en cualquier momento.", + "tradeInstructionCancelled": "Esta operación fue cancelada. No se intercambiaron fondos.", + "tradeInstructionInProgress": "Operación en curso.", + "theAgreedAmount": "el monto acordado", + "tradeHeadlinePending": "Esperando a que alguien tome tu orden", + "tradeHeadlineWaitingInvoiceBuyer": "Comparte una factura Lightning para recibir tus sats", + "tradeHeadlineWaitingInvoiceSeller": "Esperando a que el comprador comparta una factura", + "tradeHeadlineWaitingPaymentBuyer": "Esperando a que el vendedor bloquee los sats", + "tradeHeadlineWaitingPaymentSeller": "Paga la hold invoice para bloquear los sats", + "tradeHeadlineActiveBuyer": "Envía {amount} al vendedor", + "tradeHeadlineActiveSeller": "Esperando a que el comprador envíe {amount}", + "tradeHeadlineFiatSentBuyer": "Esperando a que el vendedor libere tus sats", + "tradeHeadlineFiatSentSeller": "Confirma que recibiste {amount}", + "tradeHeadlineDisputed": "Disputa en curso", + "tradeHeadlineComplete": "¡Operación completada!", + "tradeHeadlineCompleteRated": "Operación completada", + "tradeHeadlineCancelled": "Orden cancelada", + "tradeHeadlineLoading": "Cargando operación…", + "tradeTimerPendingLabel": "Tiempo para que esta orden permanezca en el libro", + "tradeTimerPendingConsequence": "Si expira, la orden se elimina del libro. No afectará tu reputación.", + "tradeTimerWaitingInvoiceLabelBuyer": "Tiempo para compartir tu factura", + "tradeTimerWaitingInvoiceLabelSeller": "Tiempo para que el comprador comparta una factura", + "tradeTimerWaitingInvoiceConsequence": "Si expira, la operación se cancela y la orden vuelve al libro.", + "tradeTimerWaitingPaymentLabelBuyer": "Tiempo para que el vendedor bloquee los sats", + "tradeTimerWaitingPaymentLabelSeller": "Tiempo para pagar la hold invoice", + "tradeTimerActiveLabelBuyer": "Tiempo para enviar el pago fiat", + "tradeTimerActiveLabelSeller": "Tiempo para que el comprador envíe el fiat", + "tradeTimerActiveConsequence": "Si expira, la operación puede cancelarse. Coordina en el chat si se necesita más tiempo.", + "tradeTimerFiatSentLabelBuyer": "Tiempo para que el vendedor confirme la recepción", + "tradeTimerFiatSentLabelSeller": "Tiempo para confirmar la recepción y liberar", + "tradeTimerFiatSentConsequence": "Si algo parece mal, abre una disputa desde el menú ⋮.", + "tradeStepOrderTaken": "Orden tomada", + "tradeStepInvoiceBuyer": "Compartes una factura · el vendedor bloquea los sats", + "tradeStepInvoiceSeller": "El comprador comparte una factura · tú bloqueas los sats", + "tradeStepFiatBuyer": "Envías el pago fiat", + "tradeStepFiatSeller": "El comprador envía el pago fiat", + "tradeStepReleaseBuyer": "El vendedor confirma y libera tus sats", + "tradeStepReleaseSeller": "Confirmas la recepción y liberas los sats", + "tradeStepRate": "Califica a tu contraparte", + "activeTradeTitle": "OPERACIÓN ACTIVA", + "tradeIdShortLabel": "ID {id}", + "tradeCreatedAtLabel": "creada {date}", + "releaseSatsMenuItem": "Liberar sats", + "cancelOrderMenuItem": "Cancelar orden", + "openDisputeMenuItem": "Abrir disputa", + "stepDoneLabel": "LISTO", + "stepIndicator": "PASO {current} DE {total}", + "addLightningInvoiceButton": "Agregar factura Lightning", + "payHoldInvoiceButton": "Pagar hold invoice", + "markFiatSentButton": "Marcar fiat enviado", + "confirmReleaseSatsButton": "Confirmar y liberar sats", + "viewDisputeButton": "Ver disputa", + "waitingForBuyer": "Esperando al comprador…", + "waitingForSeller": "Esperando al vendedor…", + "waitingForFiatPayment": "Esperando el pago fiat…", + "waitingForCounterpart": "Esperando una contraparte…", + "yourTradeTimelineTitle": "TU OPERACIÓN", + "yourCounterpartFallback": "tu contraparte", + "secureChatUnread": "Chat seguro · {count} nuevos", + "secureChatEncrypted": "Chat seguro · cifrado de extremo a extremo" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index b68f9fe8..25d969e8 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -428,5 +428,75 @@ "timeAgoNow": "maintenant", "timeAgoMinutes": "{count}min", "timeAgoHours": "{count}h", - "timeAgoDays": "{count}j" + "timeAgoDays": "{count}j", + "tradeStatusLoading": "Chargement", + "tradeStatusRate": "Évaluer", + "tradeStatusRated": "Évalué", + "tradeInstructionActiveBuyer": "Une fois l'argent envoyé, marquez-le ci-dessous. N'ouvrez un litige que si le vendeur cesse de répondre.", + "tradeInstructionFiatSentBuyer": "Paiement fiat marqué comme envoyé. En attente que le vendeur confirme la réception et libère vos sats.", + "tradeInstructionActiveSeller": "Contactez l'acheteur avec les instructions de paiement via le chat ci-dessus.", + "tradeInstructionFiatSentSeller": "L'acheteur a confirmé avoir envoyé le paiement fiat. Une fois la réception vérifiée, libérez les sats.", + "tradeInstructionDisputed": "Un médiateur de litige a été assigné. Il vous contactera via l'application.", + "tradeInstructionPendingRating": "La transaction s'est terminée avec succès. Évaluez votre contrepartie pour renforcer la confiance dans la communauté.", + "tradeInstructionRated": "Merci pour votre évaluation !", + "tradeInstructionPending": "Votre ordre est publié et attend qu'une contrepartie le prenne. Vous pouvez l'annuler à tout moment.", + "tradeInstructionCancelled": "Cette transaction a été annulée. Aucun fonds n'a été échangé.", + "tradeInstructionInProgress": "Transaction en cours.", + "theAgreedAmount": "le montant convenu", + "tradeHeadlinePending": "En attente que quelqu'un prenne votre ordre", + "tradeHeadlineWaitingInvoiceBuyer": "Partagez une facture Lightning pour recevoir vos sats", + "tradeHeadlineWaitingInvoiceSeller": "En attente que l'acheteur partage une facture", + "tradeHeadlineWaitingPaymentBuyer": "En attente que le vendeur verrouille les sats", + "tradeHeadlineWaitingPaymentSeller": "Payez la facture de retenue pour verrouiller les sats", + "tradeHeadlineActiveBuyer": "Envoyez {amount} au vendeur", + "tradeHeadlineActiveSeller": "En attente que l'acheteur envoie {amount}", + "tradeHeadlineFiatSentBuyer": "En attente que le vendeur libère vos sats", + "tradeHeadlineFiatSentSeller": "Confirmez que vous avez reçu {amount}", + "tradeHeadlineDisputed": "Litige en cours", + "tradeHeadlineComplete": "Transaction terminée !", + "tradeHeadlineCompleteRated": "Transaction terminée", + "tradeHeadlineCancelled": "Ordre annulé", + "tradeHeadlineLoading": "Chargement de la transaction…", + "tradeTimerPendingLabel": "Temps pour que cet ordre reste dans le carnet", + "tradeTimerPendingConsequence": "S'il expire, l'ordre est retiré du carnet. Cela n'affectera pas votre réputation.", + "tradeTimerWaitingInvoiceLabelBuyer": "Temps pour partager votre facture", + "tradeTimerWaitingInvoiceLabelSeller": "Temps pour que l'acheteur partage une facture", + "tradeTimerWaitingInvoiceConsequence": "S'il expire, la transaction est annulée et l'ordre retourne au carnet.", + "tradeTimerWaitingPaymentLabelBuyer": "Temps pour que le vendeur verrouille les sats", + "tradeTimerWaitingPaymentLabelSeller": "Temps pour payer la facture de retenue", + "tradeTimerActiveLabelBuyer": "Temps pour envoyer le paiement fiat", + "tradeTimerActiveLabelSeller": "Temps pour que l'acheteur envoie le fiat", + "tradeTimerActiveConsequence": "S'il expire, la transaction peut être annulée. Coordonnez-vous dans le chat si plus de temps est nécessaire.", + "tradeTimerFiatSentLabelBuyer": "Temps pour que le vendeur confirme la réception", + "tradeTimerFiatSentLabelSeller": "Temps pour confirmer la réception et libérer", + "tradeTimerFiatSentConsequence": "Si quelque chose semble anormal, ouvrez un litige depuis le menu ⋮.", + "tradeStepOrderTaken": "Ordre pris", + "tradeStepInvoiceBuyer": "Vous partagez une facture · le vendeur verrouille les sats", + "tradeStepInvoiceSeller": "L'acheteur partage une facture · vous verrouillez les sats", + "tradeStepFiatBuyer": "Vous envoyez le paiement fiat", + "tradeStepFiatSeller": "L'acheteur envoie le paiement fiat", + "tradeStepReleaseBuyer": "Le vendeur confirme et libère vos sats", + "tradeStepReleaseSeller": "Vous confirmez la réception et libérez les sats", + "tradeStepRate": "Évaluez votre contrepartie", + "activeTradeTitle": "TRANSACTION ACTIVE", + "tradeIdShortLabel": "ID {id}", + "tradeCreatedAtLabel": "créé {date}", + "releaseSatsMenuItem": "Libérer les sats", + "cancelOrderMenuItem": "Annuler l'ordre", + "openDisputeMenuItem": "Ouvrir un litige", + "stepDoneLabel": "TERMINÉ", + "stepIndicator": "ÉTAPE {current} SUR {total}", + "addLightningInvoiceButton": "Ajouter une facture Lightning", + "payHoldInvoiceButton": "Payer la facture de retenue", + "markFiatSentButton": "Marquer le fiat envoyé", + "confirmReleaseSatsButton": "Confirmer et libérer les sats", + "viewDisputeButton": "Voir le litige", + "waitingForBuyer": "En attente de l'acheteur…", + "waitingForSeller": "En attente du vendeur…", + "waitingForFiatPayment": "En attente du paiement fiat…", + "waitingForCounterpart": "En attente d'une contrepartie…", + "yourTradeTimelineTitle": "VOTRE TRANSACTION", + "yourCounterpartFallback": "votre contrepartie", + "secureChatUnread": "Chat sécurisé · {count} nouveaux", + "secureChatEncrypted": "Chat sécurisé · chiffré de bout en bout" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 120119a9..3dfc530a 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -428,5 +428,75 @@ "timeAgoNow": "ora", "timeAgoMinutes": "{count}min", "timeAgoHours": "{count}h", - "timeAgoDays": "{count}g" + "timeAgoDays": "{count}g", + "tradeStatusLoading": "Caricamento", + "tradeStatusRate": "Valuta", + "tradeStatusRated": "Valutato", + "tradeInstructionActiveBuyer": "Una volta inviato il denaro, contrassegnalo qui sotto. Apri una disputa solo se il venditore smette di rispondere.", + "tradeInstructionFiatSentBuyer": "Pagamento fiat contrassegnato come inviato. In attesa che il venditore confermi la ricezione e rilasci i tuoi sats.", + "tradeInstructionActiveSeller": "Contatta l'acquirente con le istruzioni di pagamento tramite la chat sopra.", + "tradeInstructionFiatSentSeller": "L'acquirente ha confermato di aver inviato il pagamento fiat. Una volta verificata la ricezione, rilascia i sats.", + "tradeInstructionDisputed": "È stato assegnato un risolutore di dispute. Ti contatterà tramite l'app.", + "tradeInstructionPendingRating": "L'operazione si è conclusa con successo. Valuta la tua controparte per contribuire a costruire fiducia nella comunità.", + "tradeInstructionRated": "Grazie per la tua valutazione!", + "tradeInstructionPending": "Il tuo ordine è pubblicato e in attesa che una controparte lo prenda. Puoi annullarlo in qualsiasi momento.", + "tradeInstructionCancelled": "Questa operazione è stata annullata. Non sono stati scambiati fondi.", + "tradeInstructionInProgress": "Operazione in corso.", + "theAgreedAmount": "l'importo concordato", + "tradeHeadlinePending": "In attesa che qualcuno prenda il tuo ordine", + "tradeHeadlineWaitingInvoiceBuyer": "Condividi una fattura Lightning per ricevere i tuoi sats", + "tradeHeadlineWaitingInvoiceSeller": "In attesa che l'acquirente condivida una fattura", + "tradeHeadlineWaitingPaymentBuyer": "In attesa che il venditore blocchi i sats", + "tradeHeadlineWaitingPaymentSeller": "Paga la hold invoice per bloccare i sats", + "tradeHeadlineActiveBuyer": "Invia {amount} al venditore", + "tradeHeadlineActiveSeller": "In attesa che l'acquirente invii {amount}", + "tradeHeadlineFiatSentBuyer": "In attesa che il venditore rilasci i tuoi sats", + "tradeHeadlineFiatSentSeller": "Conferma di aver ricevuto {amount}", + "tradeHeadlineDisputed": "Disputa in corso", + "tradeHeadlineComplete": "Operazione completata!", + "tradeHeadlineCompleteRated": "Operazione completata", + "tradeHeadlineCancelled": "Ordine annullato", + "tradeHeadlineLoading": "Caricamento operazione…", + "tradeTimerPendingLabel": "Tempo in cui questo ordine resta nel book", + "tradeTimerPendingConsequence": "Se scade, l'ordine viene rimosso dal book. Non influirà sulla tua reputazione.", + "tradeTimerWaitingInvoiceLabelBuyer": "Tempo per condividere la tua fattura", + "tradeTimerWaitingInvoiceLabelSeller": "Tempo perché l'acquirente condivida una fattura", + "tradeTimerWaitingInvoiceConsequence": "Se scade, l'operazione viene annullata e l'ordine torna nel book.", + "tradeTimerWaitingPaymentLabelBuyer": "Tempo perché il venditore blocchi i sats", + "tradeTimerWaitingPaymentLabelSeller": "Tempo per pagare la hold invoice", + "tradeTimerActiveLabelBuyer": "Tempo per inviare il pagamento fiat", + "tradeTimerActiveLabelSeller": "Tempo perché l'acquirente invii il fiat", + "tradeTimerActiveConsequence": "Se scade, l'operazione può essere annullata. Coordinatevi nella chat se serve più tempo.", + "tradeTimerFiatSentLabelBuyer": "Tempo perché il venditore confermi la ricezione", + "tradeTimerFiatSentLabelSeller": "Tempo per confermare la ricezione e rilasciare", + "tradeTimerFiatSentConsequence": "Se qualcosa non va, apri una disputa dal menu ⋮.", + "tradeStepOrderTaken": "Ordine preso", + "tradeStepInvoiceBuyer": "Condividi una fattura · il venditore blocca i sats", + "tradeStepInvoiceSeller": "L'acquirente condivide una fattura · tu blocchi i sats", + "tradeStepFiatBuyer": "Invii il pagamento fiat", + "tradeStepFiatSeller": "L'acquirente invia il pagamento fiat", + "tradeStepReleaseBuyer": "Il venditore conferma e rilascia i tuoi sats", + "tradeStepReleaseSeller": "Confermi la ricezione e rilasci i sats", + "tradeStepRate": "Valuta la tua controparte", + "activeTradeTitle": "OPERAZIONE ATTIVA", + "tradeIdShortLabel": "ID {id}", + "tradeCreatedAtLabel": "creata {date}", + "releaseSatsMenuItem": "Rilascia sats", + "cancelOrderMenuItem": "Annulla ordine", + "openDisputeMenuItem": "Apri disputa", + "stepDoneLabel": "FATTO", + "stepIndicator": "PASSO {current} DI {total}", + "addLightningInvoiceButton": "Aggiungi fattura Lightning", + "payHoldInvoiceButton": "Paga hold invoice", + "markFiatSentButton": "Segna fiat inviato", + "confirmReleaseSatsButton": "Conferma e rilascia sats", + "viewDisputeButton": "Vedi disputa", + "waitingForBuyer": "In attesa dell'acquirente…", + "waitingForSeller": "In attesa del venditore…", + "waitingForFiatPayment": "In attesa del pagamento fiat…", + "waitingForCounterpart": "In attesa di una controparte…", + "yourTradeTimelineTitle": "LA TUA OPERAZIONE", + "yourCounterpartFallback": "la tua controparte", + "secureChatUnread": "Chat sicura · {count} nuovi", + "secureChatEncrypted": "Chat sicura · crittografata end-to-end" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index cec4a217..a4944a49 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2527,6 +2527,426 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'{count}d'** String timeAgoDays(int count); + + /// Trade status pill: loading + /// + /// In en, this message translates to: + /// **'Loading'** + String get tradeStatusLoading; + + /// Trade status pill: pending rating + /// + /// In en, this message translates to: + /// **'Rate'** + String get tradeStatusRate; + + /// Trade status pill: rated + /// + /// In en, this message translates to: + /// **'Rated'** + String get tradeStatusRated; + + /// Instruction for buyer while trade is active + /// + /// In en, this message translates to: + /// **'Once you have sent the money, mark it below. Only open a dispute if the seller stops responding.'** + String get tradeInstructionActiveBuyer; + + /// Instruction for buyer after marking fiat sent + /// + /// In en, this message translates to: + /// **'Fiat payment marked as sent. Waiting for the seller to confirm receipt and release your sats.'** + String get tradeInstructionFiatSentBuyer; + + /// Instruction for seller while trade is active + /// + /// In en, this message translates to: + /// **'Contact the buyer with payment instructions via the chat above.'** + String get tradeInstructionActiveSeller; + + /// Instruction for seller after buyer marks fiat sent + /// + /// In en, this message translates to: + /// **'The buyer has confirmed they sent the fiat payment. Once you verify receipt, release the sats.'** + String get tradeInstructionFiatSentSeller; + + /// Instruction while a dispute is in progress + /// + /// In en, this message translates to: + /// **'A dispute resolver has been assigned. They will contact you through the app.'** + String get tradeInstructionDisputed; + + /// Instruction when the trade completed and awaits rating + /// + /// In en, this message translates to: + /// **'The trade completed successfully. Rate your counterpart to help build trust in the community.'** + String get tradeInstructionPendingRating; + + /// Instruction after the counterpart has been rated + /// + /// In en, this message translates to: + /// **'Thank you for your rating!'** + String get tradeInstructionRated; + + /// Instruction while the order is pending a taker + /// + /// In en, this message translates to: + /// **'Your order is published and waiting for a counterpart to take it. You can cancel it at any time.'** + String get tradeInstructionPending; + + /// Instruction when the trade was cancelled + /// + /// In en, this message translates to: + /// **'This trade was cancelled. No funds were exchanged.'** + String get tradeInstructionCancelled; + + /// Generic in-progress instruction fallback + /// + /// In en, this message translates to: + /// **'Trade in progress.'** + String get tradeInstructionInProgress; + + /// Fallback for the trade amount when the order is unknown + /// + /// In en, this message translates to: + /// **'the agreed amount'** + String get theAgreedAmount; + + /// Headline while the order is pending + /// + /// In en, this message translates to: + /// **'Waiting for someone to take your order'** + String get tradeHeadlinePending; + + /// Headline for buyer while waiting for invoice + /// + /// In en, this message translates to: + /// **'Share a Lightning invoice to receive your sats'** + String get tradeHeadlineWaitingInvoiceBuyer; + + /// Headline for seller while waiting for invoice + /// + /// In en, this message translates to: + /// **'Waiting for the buyer to share an invoice'** + String get tradeHeadlineWaitingInvoiceSeller; + + /// Headline for buyer while waiting for payment + /// + /// In en, this message translates to: + /// **'Waiting for the seller to lock the sats'** + String get tradeHeadlineWaitingPaymentBuyer; + + /// Headline for seller while waiting for payment + /// + /// In en, this message translates to: + /// **'Pay the hold invoice to lock the sats'** + String get tradeHeadlineWaitingPaymentSeller; + + /// Headline for buyer to send fiat + /// + /// In en, this message translates to: + /// **'Send {amount} to the seller'** + String tradeHeadlineActiveBuyer(String amount); + + /// Headline for seller waiting for fiat + /// + /// In en, this message translates to: + /// **'Waiting for the buyer to send {amount}'** + String tradeHeadlineActiveSeller(String amount); + + /// Headline for buyer waiting for release + /// + /// In en, this message translates to: + /// **'Waiting for the seller to release your sats'** + String get tradeHeadlineFiatSentBuyer; + + /// Headline for seller to confirm receipt + /// + /// In en, this message translates to: + /// **'Confirm you received {amount}'** + String tradeHeadlineFiatSentSeller(String amount); + + /// Headline while a dispute is in progress + /// + /// In en, this message translates to: + /// **'Dispute in progress'** + String get tradeHeadlineDisputed; + + /// Headline when the trade completed + /// + /// In en, this message translates to: + /// **'Trade complete!'** + String get tradeHeadlineComplete; + + /// Headline when the trade completed and was rated + /// + /// In en, this message translates to: + /// **'Trade complete'** + String get tradeHeadlineCompleteRated; + + /// Headline when the order was cancelled + /// + /// In en, this message translates to: + /// **'Order cancelled'** + String get tradeHeadlineCancelled; + + /// Headline while the trade is loading + /// + /// In en, this message translates to: + /// **'Loading trade…'** + String get tradeHeadlineLoading; + + /// Timer label while the order is pending + /// + /// In en, this message translates to: + /// **'Time for this order to stay in the book'** + String get tradeTimerPendingLabel; + + /// Timer consequence while the order is pending + /// + /// In en, this message translates to: + /// **'If it expires, the order is removed from the book. It won\'t affect your reputation.'** + String get tradeTimerPendingConsequence; + + /// Timer label for buyer waiting for invoice + /// + /// In en, this message translates to: + /// **'Time to share your invoice'** + String get tradeTimerWaitingInvoiceLabelBuyer; + + /// Timer label for seller waiting for invoice + /// + /// In en, this message translates to: + /// **'Time for the buyer to share an invoice'** + String get tradeTimerWaitingInvoiceLabelSeller; + + /// Timer consequence for the waiting-invoice/payment state + /// + /// In en, this message translates to: + /// **'If it expires, the trade is cancelled and the order returns to the book.'** + String get tradeTimerWaitingInvoiceConsequence; + + /// Timer label for buyer waiting for payment + /// + /// In en, this message translates to: + /// **'Time for the seller to lock the sats'** + String get tradeTimerWaitingPaymentLabelBuyer; + + /// Timer label for seller waiting for payment + /// + /// In en, this message translates to: + /// **'Time to pay the hold invoice'** + String get tradeTimerWaitingPaymentLabelSeller; + + /// Timer label for buyer to send fiat + /// + /// In en, this message translates to: + /// **'Time to send the fiat payment'** + String get tradeTimerActiveLabelBuyer; + + /// Timer label for seller waiting for fiat + /// + /// In en, this message translates to: + /// **'Time for the buyer to send the fiat'** + String get tradeTimerActiveLabelSeller; + + /// Timer consequence while the trade is active + /// + /// In en, this message translates to: + /// **'If it expires, the trade can be cancelled. Coordinate in the chat if more time is needed.'** + String get tradeTimerActiveConsequence; + + /// Timer label for buyer waiting for confirmation + /// + /// In en, this message translates to: + /// **'Time for the seller to confirm receipt'** + String get tradeTimerFiatSentLabelBuyer; + + /// Timer label for seller to confirm and release + /// + /// In en, this message translates to: + /// **'Time to confirm receipt and release'** + String get tradeTimerFiatSentLabelSeller; + + /// Timer consequence after fiat is marked sent + /// + /// In en, this message translates to: + /// **'If something looks wrong, open a dispute from the ⋮ menu.'** + String get tradeTimerFiatSentConsequence; + + /// Timeline step: order taken + /// + /// In en, this message translates to: + /// **'Order taken'** + String get tradeStepOrderTaken; + + /// Timeline step for buyer: share invoice + /// + /// In en, this message translates to: + /// **'You share an invoice · seller locks the sats'** + String get tradeStepInvoiceBuyer; + + /// Timeline step for seller: lock sats + /// + /// In en, this message translates to: + /// **'Buyer shares an invoice · you lock the sats'** + String get tradeStepInvoiceSeller; + + /// Timeline step for buyer: send fiat + /// + /// In en, this message translates to: + /// **'You send the fiat payment'** + String get tradeStepFiatBuyer; + + /// Timeline step for seller: buyer sends fiat + /// + /// In en, this message translates to: + /// **'Buyer sends the fiat payment'** + String get tradeStepFiatSeller; + + /// Timeline step for buyer: seller releases sats + /// + /// In en, this message translates to: + /// **'Seller confirms and releases your sats'** + String get tradeStepReleaseBuyer; + + /// Timeline step for seller: confirm and release + /// + /// In en, this message translates to: + /// **'You confirm receipt and release the sats'** + String get tradeStepReleaseSeller; + + /// Timeline step and CTA: rate your counterpart + /// + /// In en, this message translates to: + /// **'Rate your counterpart'** + String get tradeStepRate; + + /// App bar title while a trade is in flight + /// + /// In en, this message translates to: + /// **'ACTIVE TRADE'** + String get activeTradeTitle; + + /// Short order ID label in the meta footer + /// + /// In en, this message translates to: + /// **'ID {id}'** + String tradeIdShortLabel(String id); + + /// Created-at label in the meta footer + /// + /// In en, this message translates to: + /// **'created {date}'** + String tradeCreatedAtLabel(String date); + + /// Overflow menu item: release sats + /// + /// In en, this message translates to: + /// **'Release sats'** + String get releaseSatsMenuItem; + + /// Overflow menu item: cancel order + /// + /// In en, this message translates to: + /// **'Cancel order'** + String get cancelOrderMenuItem; + + /// Overflow menu item: open dispute + /// + /// In en, this message translates to: + /// **'Open dispute'** + String get openDisputeMenuItem; + + /// Step pill when all steps are complete + /// + /// In en, this message translates to: + /// **'DONE'** + String get stepDoneLabel; + + /// Step pill showing progress + /// + /// In en, this message translates to: + /// **'STEP {current} OF {total}'** + String stepIndicator(int current, int total); + + /// Primary CTA: add Lightning invoice + /// + /// In en, this message translates to: + /// **'Add Lightning invoice'** + String get addLightningInvoiceButton; + + /// Primary CTA: pay hold invoice + /// + /// In en, this message translates to: + /// **'Pay hold invoice'** + String get payHoldInvoiceButton; + + /// Primary CTA: mark fiat sent + /// + /// In en, this message translates to: + /// **'Mark fiat sent'** + String get markFiatSentButton; + + /// Primary CTA: confirm and release sats + /// + /// In en, this message translates to: + /// **'Confirm & release sats'** + String get confirmReleaseSatsButton; + + /// Primary CTA: view dispute + /// + /// In en, this message translates to: + /// **'View dispute'** + String get viewDisputeButton; + + /// Disabled CTA: waiting for the buyer + /// + /// In en, this message translates to: + /// **'Waiting for the buyer…'** + String get waitingForBuyer; + + /// Disabled CTA: waiting for the seller + /// + /// In en, this message translates to: + /// **'Waiting for the seller…'** + String get waitingForSeller; + + /// Disabled CTA: waiting for the fiat payment + /// + /// In en, this message translates to: + /// **'Waiting for the fiat payment…'** + String get waitingForFiatPayment; + + /// Disabled CTA: waiting for a counterpart + /// + /// In en, this message translates to: + /// **'Waiting for a counterpart…'** + String get waitingForCounterpart; + + /// Header of the trade step timeline + /// + /// In en, this message translates to: + /// **'YOUR TRADE'** + String get yourTradeTimelineTitle; + + /// Fallback name for the counterpart in the chat chip + /// + /// In en, this message translates to: + /// **'your counterpart'** + String get yourCounterpartFallback; + + /// Chat chip subtitle with unread count + /// + /// In en, this message translates to: + /// **'Secure chat · {count} new'** + String secureChatUnread(int count); + + /// Chat chip subtitle when there are no unread messages + /// + /// In en, this message translates to: + /// **'Secure chat · end-to-end encrypted'** + String get secureChatEncrypted; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 7ad6e56d..1b7d8f94 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1357,4 +1357,259 @@ class AppLocalizationsDe extends AppLocalizations { String timeAgoDays(int count) { return '${count}T'; } + + @override + String get tradeStatusLoading => 'Wird geladen'; + + @override + String get tradeStatusRate => 'Bewerten'; + + @override + String get tradeStatusRated => 'Bewertet'; + + @override + String get tradeInstructionActiveBuyer => + 'Sobald du das Geld gesendet hast, markiere es unten. Öffne nur einen Streitfall, wenn der Verkäufer nicht mehr antwortet.'; + + @override + String get tradeInstructionFiatSentBuyer => + 'Fiat-Zahlung als gesendet markiert. Warte darauf, dass der Verkäufer den Empfang bestätigt und deine Sats freigibt.'; + + @override + String get tradeInstructionActiveSeller => + 'Kontaktiere den Käufer mit den Zahlungsanweisungen über den Chat oben.'; + + @override + String get tradeInstructionFiatSentSeller => + 'Der Käufer hat bestätigt, die Fiat-Zahlung gesendet zu haben. Sobald du den Empfang prüfst, gib die Sats frei.'; + + @override + String get tradeInstructionDisputed => + 'Ein Streitschlichter wurde zugewiesen. Er wird dich über die App kontaktieren.'; + + @override + String get tradeInstructionPendingRating => + 'Der Trade wurde erfolgreich abgeschlossen. Bewerte deine Gegenpartei, um Vertrauen in der Community aufzubauen.'; + + @override + String get tradeInstructionRated => 'Danke für deine Bewertung!'; + + @override + String get tradeInstructionPending => + 'Deine Bestellung ist veröffentlicht und wartet auf eine Gegenpartei. Du kannst sie jederzeit stornieren.'; + + @override + String get tradeInstructionCancelled => + 'Dieser Trade wurde storniert. Es wurden keine Gelder ausgetauscht.'; + + @override + String get tradeInstructionInProgress => 'Trade läuft.'; + + @override + String get theAgreedAmount => 'den vereinbarten Betrag'; + + @override + String get tradeHeadlinePending => + 'Warte darauf, dass jemand deine Bestellung annimmt'; + + @override + String get tradeHeadlineWaitingInvoiceBuyer => + 'Teile eine Lightning-Rechnung, um deine Sats zu erhalten'; + + @override + String get tradeHeadlineWaitingInvoiceSeller => + 'Warte darauf, dass der Käufer eine Rechnung teilt'; + + @override + String get tradeHeadlineWaitingPaymentBuyer => + 'Warte darauf, dass der Verkäufer die Sats sperrt'; + + @override + String get tradeHeadlineWaitingPaymentSeller => + 'Bezahle die Hold-Invoice, um die Sats zu sperren'; + + @override + String tradeHeadlineActiveBuyer(String amount) { + return 'Sende $amount an den Verkäufer'; + } + + @override + String tradeHeadlineActiveSeller(String amount) { + return 'Warte darauf, dass der Käufer $amount sendet'; + } + + @override + String get tradeHeadlineFiatSentBuyer => + 'Warte darauf, dass der Verkäufer deine Sats freigibt'; + + @override + String tradeHeadlineFiatSentSeller(String amount) { + return 'Bestätige, dass du $amount erhalten hast'; + } + + @override + String get tradeHeadlineDisputed => 'Streitfall läuft'; + + @override + String get tradeHeadlineComplete => 'Trade abgeschlossen!'; + + @override + String get tradeHeadlineCompleteRated => 'Trade abgeschlossen'; + + @override + String get tradeHeadlineCancelled => 'Bestellung storniert'; + + @override + String get tradeHeadlineLoading => 'Trade wird geladen…'; + + @override + String get tradeTimerPendingLabel => + 'Zeit, in der diese Bestellung im Buch bleibt'; + + @override + String get tradeTimerPendingConsequence => + 'Bei Ablauf wird die Bestellung aus dem Buch entfernt. Es wirkt sich nicht auf deine Reputation aus.'; + + @override + String get tradeTimerWaitingInvoiceLabelBuyer => + 'Zeit, deine Rechnung zu teilen'; + + @override + String get tradeTimerWaitingInvoiceLabelSeller => + 'Zeit für den Käufer, eine Rechnung zu teilen'; + + @override + String get tradeTimerWaitingInvoiceConsequence => + 'Bei Ablauf wird der Trade storniert und die Bestellung kehrt ins Buch zurück.'; + + @override + String get tradeTimerWaitingPaymentLabelBuyer => + 'Zeit für den Verkäufer, die Sats zu sperren'; + + @override + String get tradeTimerWaitingPaymentLabelSeller => + 'Zeit, die Hold-Invoice zu bezahlen'; + + @override + String get tradeTimerActiveLabelBuyer => 'Zeit, die Fiat-Zahlung zu senden'; + + @override + String get tradeTimerActiveLabelSeller => + 'Zeit für den Käufer, das Fiat zu senden'; + + @override + String get tradeTimerActiveConsequence => + 'Bei Ablauf kann der Trade storniert werden. Stimmt euch im Chat ab, wenn mehr Zeit nötig ist.'; + + @override + String get tradeTimerFiatSentLabelBuyer => + 'Zeit für den Verkäufer, den Empfang zu bestätigen'; + + @override + String get tradeTimerFiatSentLabelSeller => + 'Zeit, den Empfang zu bestätigen und freizugeben'; + + @override + String get tradeTimerFiatSentConsequence => + 'Wenn etwas nicht stimmt, öffne einen Streitfall über das ⋮-Menü.'; + + @override + String get tradeStepOrderTaken => 'Bestellung angenommen'; + + @override + String get tradeStepInvoiceBuyer => + 'Du teilst eine Rechnung · der Verkäufer sperrt die Sats'; + + @override + String get tradeStepInvoiceSeller => + 'Der Käufer teilt eine Rechnung · du sperrst die Sats'; + + @override + String get tradeStepFiatBuyer => 'Du sendest die Fiat-Zahlung'; + + @override + String get tradeStepFiatSeller => 'Der Käufer sendet die Fiat-Zahlung'; + + @override + String get tradeStepReleaseBuyer => + 'Der Verkäufer bestätigt und gibt deine Sats frei'; + + @override + String get tradeStepReleaseSeller => + 'Du bestätigst den Empfang und gibst die Sats frei'; + + @override + String get tradeStepRate => 'Bewerte deine Gegenpartei'; + + @override + String get activeTradeTitle => 'AKTIVER TRADE'; + + @override + String tradeIdShortLabel(String id) { + return 'ID $id'; + } + + @override + String tradeCreatedAtLabel(String date) { + return 'erstellt $date'; + } + + @override + String get releaseSatsMenuItem => 'Sats freigeben'; + + @override + String get cancelOrderMenuItem => 'Bestellung stornieren'; + + @override + String get openDisputeMenuItem => 'Streitfall öffnen'; + + @override + String get stepDoneLabel => 'FERTIG'; + + @override + String stepIndicator(int current, int total) { + return 'SCHRITT $current VON $total'; + } + + @override + String get addLightningInvoiceButton => 'Lightning-Rechnung hinzufügen'; + + @override + String get payHoldInvoiceButton => 'Hold-Invoice bezahlen'; + + @override + String get markFiatSentButton => 'Fiat als gesendet markieren'; + + @override + String get confirmReleaseSatsButton => 'Bestätigen und Sats freigeben'; + + @override + String get viewDisputeButton => 'Streitfall ansehen'; + + @override + String get waitingForBuyer => 'Warte auf den Käufer…'; + + @override + String get waitingForSeller => 'Warte auf den Verkäufer…'; + + @override + String get waitingForFiatPayment => 'Warte auf die Fiat-Zahlung…'; + + @override + String get waitingForCounterpart => 'Warte auf eine Gegenpartei…'; + + @override + String get yourTradeTimelineTitle => 'DEIN TRADE'; + + @override + String get yourCounterpartFallback => 'deine Gegenpartei'; + + @override + String secureChatUnread(int count) { + return 'Sicherer Chat · $count neue'; + } + + @override + String get secureChatEncrypted => + 'Sicherer Chat · Ende-zu-Ende-verschlüsselt'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index b6ecd7c1..081d573d 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1335,4 +1335,255 @@ class AppLocalizationsEn extends AppLocalizations { String timeAgoDays(int count) { return '${count}d'; } + + @override + String get tradeStatusLoading => 'Loading'; + + @override + String get tradeStatusRate => 'Rate'; + + @override + String get tradeStatusRated => 'Rated'; + + @override + String get tradeInstructionActiveBuyer => + 'Once you have sent the money, mark it below. Only open a dispute if the seller stops responding.'; + + @override + String get tradeInstructionFiatSentBuyer => + 'Fiat payment marked as sent. Waiting for the seller to confirm receipt and release your sats.'; + + @override + String get tradeInstructionActiveSeller => + 'Contact the buyer with payment instructions via the chat above.'; + + @override + String get tradeInstructionFiatSentSeller => + 'The buyer has confirmed they sent the fiat payment. Once you verify receipt, release the sats.'; + + @override + String get tradeInstructionDisputed => + 'A dispute resolver has been assigned. They will contact you through the app.'; + + @override + String get tradeInstructionPendingRating => + 'The trade completed successfully. Rate your counterpart to help build trust in the community.'; + + @override + String get tradeInstructionRated => 'Thank you for your rating!'; + + @override + String get tradeInstructionPending => + 'Your order is published and waiting for a counterpart to take it. You can cancel it at any time.'; + + @override + String get tradeInstructionCancelled => + 'This trade was cancelled. No funds were exchanged.'; + + @override + String get tradeInstructionInProgress => 'Trade in progress.'; + + @override + String get theAgreedAmount => 'the agreed amount'; + + @override + String get tradeHeadlinePending => 'Waiting for someone to take your order'; + + @override + String get tradeHeadlineWaitingInvoiceBuyer => + 'Share a Lightning invoice to receive your sats'; + + @override + String get tradeHeadlineWaitingInvoiceSeller => + 'Waiting for the buyer to share an invoice'; + + @override + String get tradeHeadlineWaitingPaymentBuyer => + 'Waiting for the seller to lock the sats'; + + @override + String get tradeHeadlineWaitingPaymentSeller => + 'Pay the hold invoice to lock the sats'; + + @override + String tradeHeadlineActiveBuyer(String amount) { + return 'Send $amount to the seller'; + } + + @override + String tradeHeadlineActiveSeller(String amount) { + return 'Waiting for the buyer to send $amount'; + } + + @override + String get tradeHeadlineFiatSentBuyer => + 'Waiting for the seller to release your sats'; + + @override + String tradeHeadlineFiatSentSeller(String amount) { + return 'Confirm you received $amount'; + } + + @override + String get tradeHeadlineDisputed => 'Dispute in progress'; + + @override + String get tradeHeadlineComplete => 'Trade complete!'; + + @override + String get tradeHeadlineCompleteRated => 'Trade complete'; + + @override + String get tradeHeadlineCancelled => 'Order cancelled'; + + @override + String get tradeHeadlineLoading => 'Loading trade…'; + + @override + String get tradeTimerPendingLabel => + 'Time for this order to stay in the book'; + + @override + String get tradeTimerPendingConsequence => + 'If it expires, the order is removed from the book. It won\'t affect your reputation.'; + + @override + String get tradeTimerWaitingInvoiceLabelBuyer => 'Time to share your invoice'; + + @override + String get tradeTimerWaitingInvoiceLabelSeller => + 'Time for the buyer to share an invoice'; + + @override + String get tradeTimerWaitingInvoiceConsequence => + 'If it expires, the trade is cancelled and the order returns to the book.'; + + @override + String get tradeTimerWaitingPaymentLabelBuyer => + 'Time for the seller to lock the sats'; + + @override + String get tradeTimerWaitingPaymentLabelSeller => + 'Time to pay the hold invoice'; + + @override + String get tradeTimerActiveLabelBuyer => 'Time to send the fiat payment'; + + @override + String get tradeTimerActiveLabelSeller => + 'Time for the buyer to send the fiat'; + + @override + String get tradeTimerActiveConsequence => + 'If it expires, the trade can be cancelled. Coordinate in the chat if more time is needed.'; + + @override + String get tradeTimerFiatSentLabelBuyer => + 'Time for the seller to confirm receipt'; + + @override + String get tradeTimerFiatSentLabelSeller => + 'Time to confirm receipt and release'; + + @override + String get tradeTimerFiatSentConsequence => + 'If something looks wrong, open a dispute from the ⋮ menu.'; + + @override + String get tradeStepOrderTaken => 'Order taken'; + + @override + String get tradeStepInvoiceBuyer => + 'You share an invoice · seller locks the sats'; + + @override + String get tradeStepInvoiceSeller => + 'Buyer shares an invoice · you lock the sats'; + + @override + String get tradeStepFiatBuyer => 'You send the fiat payment'; + + @override + String get tradeStepFiatSeller => 'Buyer sends the fiat payment'; + + @override + String get tradeStepReleaseBuyer => 'Seller confirms and releases your sats'; + + @override + String get tradeStepReleaseSeller => + 'You confirm receipt and release the sats'; + + @override + String get tradeStepRate => 'Rate your counterpart'; + + @override + String get activeTradeTitle => 'ACTIVE TRADE'; + + @override + String tradeIdShortLabel(String id) { + return 'ID $id'; + } + + @override + String tradeCreatedAtLabel(String date) { + return 'created $date'; + } + + @override + String get releaseSatsMenuItem => 'Release sats'; + + @override + String get cancelOrderMenuItem => 'Cancel order'; + + @override + String get openDisputeMenuItem => 'Open dispute'; + + @override + String get stepDoneLabel => 'DONE'; + + @override + String stepIndicator(int current, int total) { + return 'STEP $current OF $total'; + } + + @override + String get addLightningInvoiceButton => 'Add Lightning invoice'; + + @override + String get payHoldInvoiceButton => 'Pay hold invoice'; + + @override + String get markFiatSentButton => 'Mark fiat sent'; + + @override + String get confirmReleaseSatsButton => 'Confirm & release sats'; + + @override + String get viewDisputeButton => 'View dispute'; + + @override + String get waitingForBuyer => 'Waiting for the buyer…'; + + @override + String get waitingForSeller => 'Waiting for the seller…'; + + @override + String get waitingForFiatPayment => 'Waiting for the fiat payment…'; + + @override + String get waitingForCounterpart => 'Waiting for a counterpart…'; + + @override + String get yourTradeTimelineTitle => 'YOUR TRADE'; + + @override + String get yourCounterpartFallback => 'your counterpart'; + + @override + String secureChatUnread(int count) { + return 'Secure chat · $count new'; + } + + @override + String get secureChatEncrypted => 'Secure chat · end-to-end encrypted'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 1772e256..6720bde1 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1352,4 +1352,257 @@ class AppLocalizationsEs extends AppLocalizations { String timeAgoDays(int count) { return '${count}d'; } + + @override + String get tradeStatusLoading => 'Cargando'; + + @override + String get tradeStatusRate => 'Calificar'; + + @override + String get tradeStatusRated => 'Calificado'; + + @override + String get tradeInstructionActiveBuyer => + 'Una vez que hayas enviado el dinero, márcalo abajo. Abre una disputa solo si el vendedor deja de responder.'; + + @override + String get tradeInstructionFiatSentBuyer => + 'Pago fiat marcado como enviado. Esperando que el vendedor confirme la recepción y libere tus sats.'; + + @override + String get tradeInstructionActiveSeller => + 'Contacta al comprador con las instrucciones de pago a través del chat de arriba.'; + + @override + String get tradeInstructionFiatSentSeller => + 'El comprador confirmó que envió el pago fiat. Una vez que verifiques la recepción, libera los sats.'; + + @override + String get tradeInstructionDisputed => + 'Se asignó un resolutor de disputas. Se pondrá en contacto contigo a través de la app.'; + + @override + String get tradeInstructionPendingRating => + 'La operación se completó con éxito. Califica a tu contraparte para ayudar a construir confianza en la comunidad.'; + + @override + String get tradeInstructionRated => '¡Gracias por tu calificación!'; + + @override + String get tradeInstructionPending => + 'Tu orden está publicada y esperando a que una contraparte la tome. Puedes cancelarla en cualquier momento.'; + + @override + String get tradeInstructionCancelled => + 'Esta operación fue cancelada. No se intercambiaron fondos.'; + + @override + String get tradeInstructionInProgress => 'Operación en curso.'; + + @override + String get theAgreedAmount => 'el monto acordado'; + + @override + String get tradeHeadlinePending => 'Esperando a que alguien tome tu orden'; + + @override + String get tradeHeadlineWaitingInvoiceBuyer => + 'Comparte una factura Lightning para recibir tus sats'; + + @override + String get tradeHeadlineWaitingInvoiceSeller => + 'Esperando a que el comprador comparta una factura'; + + @override + String get tradeHeadlineWaitingPaymentBuyer => + 'Esperando a que el vendedor bloquee los sats'; + + @override + String get tradeHeadlineWaitingPaymentSeller => + 'Paga la hold invoice para bloquear los sats'; + + @override + String tradeHeadlineActiveBuyer(String amount) { + return 'Envía $amount al vendedor'; + } + + @override + String tradeHeadlineActiveSeller(String amount) { + return 'Esperando a que el comprador envíe $amount'; + } + + @override + String get tradeHeadlineFiatSentBuyer => + 'Esperando a que el vendedor libere tus sats'; + + @override + String tradeHeadlineFiatSentSeller(String amount) { + return 'Confirma que recibiste $amount'; + } + + @override + String get tradeHeadlineDisputed => 'Disputa en curso'; + + @override + String get tradeHeadlineComplete => '¡Operación completada!'; + + @override + String get tradeHeadlineCompleteRated => 'Operación completada'; + + @override + String get tradeHeadlineCancelled => 'Orden cancelada'; + + @override + String get tradeHeadlineLoading => 'Cargando operación…'; + + @override + String get tradeTimerPendingLabel => + 'Tiempo para que esta orden permanezca en el libro'; + + @override + String get tradeTimerPendingConsequence => + 'Si expira, la orden se elimina del libro. No afectará tu reputación.'; + + @override + String get tradeTimerWaitingInvoiceLabelBuyer => + 'Tiempo para compartir tu factura'; + + @override + String get tradeTimerWaitingInvoiceLabelSeller => + 'Tiempo para que el comprador comparta una factura'; + + @override + String get tradeTimerWaitingInvoiceConsequence => + 'Si expira, la operación se cancela y la orden vuelve al libro.'; + + @override + String get tradeTimerWaitingPaymentLabelBuyer => + 'Tiempo para que el vendedor bloquee los sats'; + + @override + String get tradeTimerWaitingPaymentLabelSeller => + 'Tiempo para pagar la hold invoice'; + + @override + String get tradeTimerActiveLabelBuyer => 'Tiempo para enviar el pago fiat'; + + @override + String get tradeTimerActiveLabelSeller => + 'Tiempo para que el comprador envíe el fiat'; + + @override + String get tradeTimerActiveConsequence => + 'Si expira, la operación puede cancelarse. Coordina en el chat si se necesita más tiempo.'; + + @override + String get tradeTimerFiatSentLabelBuyer => + 'Tiempo para que el vendedor confirme la recepción'; + + @override + String get tradeTimerFiatSentLabelSeller => + 'Tiempo para confirmar la recepción y liberar'; + + @override + String get tradeTimerFiatSentConsequence => + 'Si algo parece mal, abre una disputa desde el menú ⋮.'; + + @override + String get tradeStepOrderTaken => 'Orden tomada'; + + @override + String get tradeStepInvoiceBuyer => + 'Compartes una factura · el vendedor bloquea los sats'; + + @override + String get tradeStepInvoiceSeller => + 'El comprador comparte una factura · tú bloqueas los sats'; + + @override + String get tradeStepFiatBuyer => 'Envías el pago fiat'; + + @override + String get tradeStepFiatSeller => 'El comprador envía el pago fiat'; + + @override + String get tradeStepReleaseBuyer => 'El vendedor confirma y libera tus sats'; + + @override + String get tradeStepReleaseSeller => + 'Confirmas la recepción y liberas los sats'; + + @override + String get tradeStepRate => 'Califica a tu contraparte'; + + @override + String get activeTradeTitle => 'OPERACIÓN ACTIVA'; + + @override + String tradeIdShortLabel(String id) { + return 'ID $id'; + } + + @override + String tradeCreatedAtLabel(String date) { + return 'creada $date'; + } + + @override + String get releaseSatsMenuItem => 'Liberar sats'; + + @override + String get cancelOrderMenuItem => 'Cancelar orden'; + + @override + String get openDisputeMenuItem => 'Abrir disputa'; + + @override + String get stepDoneLabel => 'LISTO'; + + @override + String stepIndicator(int current, int total) { + return 'PASO $current DE $total'; + } + + @override + String get addLightningInvoiceButton => 'Agregar factura Lightning'; + + @override + String get payHoldInvoiceButton => 'Pagar hold invoice'; + + @override + String get markFiatSentButton => 'Marcar fiat enviado'; + + @override + String get confirmReleaseSatsButton => 'Confirmar y liberar sats'; + + @override + String get viewDisputeButton => 'Ver disputa'; + + @override + String get waitingForBuyer => 'Esperando al comprador…'; + + @override + String get waitingForSeller => 'Esperando al vendedor…'; + + @override + String get waitingForFiatPayment => 'Esperando el pago fiat…'; + + @override + String get waitingForCounterpart => 'Esperando una contraparte…'; + + @override + String get yourTradeTimelineTitle => 'TU OPERACIÓN'; + + @override + String get yourCounterpartFallback => 'tu contraparte'; + + @override + String secureChatUnread(int count) { + return 'Chat seguro · $count nuevos'; + } + + @override + String get secureChatEncrypted => + 'Chat seguro · cifrado de extremo a extremo'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index c62ccb82..e1b5b7b4 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1358,4 +1358,258 @@ class AppLocalizationsFr extends AppLocalizations { String timeAgoDays(int count) { return '${count}j'; } + + @override + String get tradeStatusLoading => 'Chargement'; + + @override + String get tradeStatusRate => 'Évaluer'; + + @override + String get tradeStatusRated => 'Évalué'; + + @override + String get tradeInstructionActiveBuyer => + 'Une fois l\'argent envoyé, marquez-le ci-dessous. N\'ouvrez un litige que si le vendeur cesse de répondre.'; + + @override + String get tradeInstructionFiatSentBuyer => + 'Paiement fiat marqué comme envoyé. En attente que le vendeur confirme la réception et libère vos sats.'; + + @override + String get tradeInstructionActiveSeller => + 'Contactez l\'acheteur avec les instructions de paiement via le chat ci-dessus.'; + + @override + String get tradeInstructionFiatSentSeller => + 'L\'acheteur a confirmé avoir envoyé le paiement fiat. Une fois la réception vérifiée, libérez les sats.'; + + @override + String get tradeInstructionDisputed => + 'Un médiateur de litige a été assigné. Il vous contactera via l\'application.'; + + @override + String get tradeInstructionPendingRating => + 'La transaction s\'est terminée avec succès. Évaluez votre contrepartie pour renforcer la confiance dans la communauté.'; + + @override + String get tradeInstructionRated => 'Merci pour votre évaluation !'; + + @override + String get tradeInstructionPending => + 'Votre ordre est publié et attend qu\'une contrepartie le prenne. Vous pouvez l\'annuler à tout moment.'; + + @override + String get tradeInstructionCancelled => + 'Cette transaction a été annulée. Aucun fonds n\'a été échangé.'; + + @override + String get tradeInstructionInProgress => 'Transaction en cours.'; + + @override + String get theAgreedAmount => 'le montant convenu'; + + @override + String get tradeHeadlinePending => + 'En attente que quelqu\'un prenne votre ordre'; + + @override + String get tradeHeadlineWaitingInvoiceBuyer => + 'Partagez une facture Lightning pour recevoir vos sats'; + + @override + String get tradeHeadlineWaitingInvoiceSeller => + 'En attente que l\'acheteur partage une facture'; + + @override + String get tradeHeadlineWaitingPaymentBuyer => + 'En attente que le vendeur verrouille les sats'; + + @override + String get tradeHeadlineWaitingPaymentSeller => + 'Payez la facture de retenue pour verrouiller les sats'; + + @override + String tradeHeadlineActiveBuyer(String amount) { + return 'Envoyez $amount au vendeur'; + } + + @override + String tradeHeadlineActiveSeller(String amount) { + return 'En attente que l\'acheteur envoie $amount'; + } + + @override + String get tradeHeadlineFiatSentBuyer => + 'En attente que le vendeur libère vos sats'; + + @override + String tradeHeadlineFiatSentSeller(String amount) { + return 'Confirmez que vous avez reçu $amount'; + } + + @override + String get tradeHeadlineDisputed => 'Litige en cours'; + + @override + String get tradeHeadlineComplete => 'Transaction terminée !'; + + @override + String get tradeHeadlineCompleteRated => 'Transaction terminée'; + + @override + String get tradeHeadlineCancelled => 'Ordre annulé'; + + @override + String get tradeHeadlineLoading => 'Chargement de la transaction…'; + + @override + String get tradeTimerPendingLabel => + 'Temps pour que cet ordre reste dans le carnet'; + + @override + String get tradeTimerPendingConsequence => + 'S\'il expire, l\'ordre est retiré du carnet. Cela n\'affectera pas votre réputation.'; + + @override + String get tradeTimerWaitingInvoiceLabelBuyer => + 'Temps pour partager votre facture'; + + @override + String get tradeTimerWaitingInvoiceLabelSeller => + 'Temps pour que l\'acheteur partage une facture'; + + @override + String get tradeTimerWaitingInvoiceConsequence => + 'S\'il expire, la transaction est annulée et l\'ordre retourne au carnet.'; + + @override + String get tradeTimerWaitingPaymentLabelBuyer => + 'Temps pour que le vendeur verrouille les sats'; + + @override + String get tradeTimerWaitingPaymentLabelSeller => + 'Temps pour payer la facture de retenue'; + + @override + String get tradeTimerActiveLabelBuyer => + 'Temps pour envoyer le paiement fiat'; + + @override + String get tradeTimerActiveLabelSeller => + 'Temps pour que l\'acheteur envoie le fiat'; + + @override + String get tradeTimerActiveConsequence => + 'S\'il expire, la transaction peut être annulée. Coordonnez-vous dans le chat si plus de temps est nécessaire.'; + + @override + String get tradeTimerFiatSentLabelBuyer => + 'Temps pour que le vendeur confirme la réception'; + + @override + String get tradeTimerFiatSentLabelSeller => + 'Temps pour confirmer la réception et libérer'; + + @override + String get tradeTimerFiatSentConsequence => + 'Si quelque chose semble anormal, ouvrez un litige depuis le menu ⋮.'; + + @override + String get tradeStepOrderTaken => 'Ordre pris'; + + @override + String get tradeStepInvoiceBuyer => + 'Vous partagez une facture · le vendeur verrouille les sats'; + + @override + String get tradeStepInvoiceSeller => + 'L\'acheteur partage une facture · vous verrouillez les sats'; + + @override + String get tradeStepFiatBuyer => 'Vous envoyez le paiement fiat'; + + @override + String get tradeStepFiatSeller => 'L\'acheteur envoie le paiement fiat'; + + @override + String get tradeStepReleaseBuyer => 'Le vendeur confirme et libère vos sats'; + + @override + String get tradeStepReleaseSeller => + 'Vous confirmez la réception et libérez les sats'; + + @override + String get tradeStepRate => 'Évaluez votre contrepartie'; + + @override + String get activeTradeTitle => 'TRANSACTION ACTIVE'; + + @override + String tradeIdShortLabel(String id) { + return 'ID $id'; + } + + @override + String tradeCreatedAtLabel(String date) { + return 'créé $date'; + } + + @override + String get releaseSatsMenuItem => 'Libérer les sats'; + + @override + String get cancelOrderMenuItem => 'Annuler l\'ordre'; + + @override + String get openDisputeMenuItem => 'Ouvrir un litige'; + + @override + String get stepDoneLabel => 'TERMINÉ'; + + @override + String stepIndicator(int current, int total) { + return 'ÉTAPE $current SUR $total'; + } + + @override + String get addLightningInvoiceButton => 'Ajouter une facture Lightning'; + + @override + String get payHoldInvoiceButton => 'Payer la facture de retenue'; + + @override + String get markFiatSentButton => 'Marquer le fiat envoyé'; + + @override + String get confirmReleaseSatsButton => 'Confirmer et libérer les sats'; + + @override + String get viewDisputeButton => 'Voir le litige'; + + @override + String get waitingForBuyer => 'En attente de l\'acheteur…'; + + @override + String get waitingForSeller => 'En attente du vendeur…'; + + @override + String get waitingForFiatPayment => 'En attente du paiement fiat…'; + + @override + String get waitingForCounterpart => 'En attente d\'une contrepartie…'; + + @override + String get yourTradeTimelineTitle => 'VOTRE TRANSACTION'; + + @override + String get yourCounterpartFallback => 'votre contrepartie'; + + @override + String secureChatUnread(int count) { + return 'Chat sécurisé · $count nouveaux'; + } + + @override + String get secureChatEncrypted => 'Chat sécurisé · chiffré de bout en bout'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 08614ff5..6dd51c73 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1349,4 +1349,258 @@ class AppLocalizationsIt extends AppLocalizations { String timeAgoDays(int count) { return '${count}g'; } + + @override + String get tradeStatusLoading => 'Caricamento'; + + @override + String get tradeStatusRate => 'Valuta'; + + @override + String get tradeStatusRated => 'Valutato'; + + @override + String get tradeInstructionActiveBuyer => + 'Una volta inviato il denaro, contrassegnalo qui sotto. Apri una disputa solo se il venditore smette di rispondere.'; + + @override + String get tradeInstructionFiatSentBuyer => + 'Pagamento fiat contrassegnato come inviato. In attesa che il venditore confermi la ricezione e rilasci i tuoi sats.'; + + @override + String get tradeInstructionActiveSeller => + 'Contatta l\'acquirente con le istruzioni di pagamento tramite la chat sopra.'; + + @override + String get tradeInstructionFiatSentSeller => + 'L\'acquirente ha confermato di aver inviato il pagamento fiat. Una volta verificata la ricezione, rilascia i sats.'; + + @override + String get tradeInstructionDisputed => + 'È stato assegnato un risolutore di dispute. Ti contatterà tramite l\'app.'; + + @override + String get tradeInstructionPendingRating => + 'L\'operazione si è conclusa con successo. Valuta la tua controparte per contribuire a costruire fiducia nella comunità.'; + + @override + String get tradeInstructionRated => 'Grazie per la tua valutazione!'; + + @override + String get tradeInstructionPending => + 'Il tuo ordine è pubblicato e in attesa che una controparte lo prenda. Puoi annullarlo in qualsiasi momento.'; + + @override + String get tradeInstructionCancelled => + 'Questa operazione è stata annullata. Non sono stati scambiati fondi.'; + + @override + String get tradeInstructionInProgress => 'Operazione in corso.'; + + @override + String get theAgreedAmount => 'l\'importo concordato'; + + @override + String get tradeHeadlinePending => + 'In attesa che qualcuno prenda il tuo ordine'; + + @override + String get tradeHeadlineWaitingInvoiceBuyer => + 'Condividi una fattura Lightning per ricevere i tuoi sats'; + + @override + String get tradeHeadlineWaitingInvoiceSeller => + 'In attesa che l\'acquirente condivida una fattura'; + + @override + String get tradeHeadlineWaitingPaymentBuyer => + 'In attesa che il venditore blocchi i sats'; + + @override + String get tradeHeadlineWaitingPaymentSeller => + 'Paga la hold invoice per bloccare i sats'; + + @override + String tradeHeadlineActiveBuyer(String amount) { + return 'Invia $amount al venditore'; + } + + @override + String tradeHeadlineActiveSeller(String amount) { + return 'In attesa che l\'acquirente invii $amount'; + } + + @override + String get tradeHeadlineFiatSentBuyer => + 'In attesa che il venditore rilasci i tuoi sats'; + + @override + String tradeHeadlineFiatSentSeller(String amount) { + return 'Conferma di aver ricevuto $amount'; + } + + @override + String get tradeHeadlineDisputed => 'Disputa in corso'; + + @override + String get tradeHeadlineComplete => 'Operazione completata!'; + + @override + String get tradeHeadlineCompleteRated => 'Operazione completata'; + + @override + String get tradeHeadlineCancelled => 'Ordine annullato'; + + @override + String get tradeHeadlineLoading => 'Caricamento operazione…'; + + @override + String get tradeTimerPendingLabel => + 'Tempo in cui questo ordine resta nel book'; + + @override + String get tradeTimerPendingConsequence => + 'Se scade, l\'ordine viene rimosso dal book. Non influirà sulla tua reputazione.'; + + @override + String get tradeTimerWaitingInvoiceLabelBuyer => + 'Tempo per condividere la tua fattura'; + + @override + String get tradeTimerWaitingInvoiceLabelSeller => + 'Tempo perché l\'acquirente condivida una fattura'; + + @override + String get tradeTimerWaitingInvoiceConsequence => + 'Se scade, l\'operazione viene annullata e l\'ordine torna nel book.'; + + @override + String get tradeTimerWaitingPaymentLabelBuyer => + 'Tempo perché il venditore blocchi i sats'; + + @override + String get tradeTimerWaitingPaymentLabelSeller => + 'Tempo per pagare la hold invoice'; + + @override + String get tradeTimerActiveLabelBuyer => + 'Tempo per inviare il pagamento fiat'; + + @override + String get tradeTimerActiveLabelSeller => + 'Tempo perché l\'acquirente invii il fiat'; + + @override + String get tradeTimerActiveConsequence => + 'Se scade, l\'operazione può essere annullata. Coordinatevi nella chat se serve più tempo.'; + + @override + String get tradeTimerFiatSentLabelBuyer => + 'Tempo perché il venditore confermi la ricezione'; + + @override + String get tradeTimerFiatSentLabelSeller => + 'Tempo per confermare la ricezione e rilasciare'; + + @override + String get tradeTimerFiatSentConsequence => + 'Se qualcosa non va, apri una disputa dal menu ⋮.'; + + @override + String get tradeStepOrderTaken => 'Ordine preso'; + + @override + String get tradeStepInvoiceBuyer => + 'Condividi una fattura · il venditore blocca i sats'; + + @override + String get tradeStepInvoiceSeller => + 'L\'acquirente condivide una fattura · tu blocchi i sats'; + + @override + String get tradeStepFiatBuyer => 'Invii il pagamento fiat'; + + @override + String get tradeStepFiatSeller => 'L\'acquirente invia il pagamento fiat'; + + @override + String get tradeStepReleaseBuyer => + 'Il venditore conferma e rilascia i tuoi sats'; + + @override + String get tradeStepReleaseSeller => 'Confermi la ricezione e rilasci i sats'; + + @override + String get tradeStepRate => 'Valuta la tua controparte'; + + @override + String get activeTradeTitle => 'OPERAZIONE ATTIVA'; + + @override + String tradeIdShortLabel(String id) { + return 'ID $id'; + } + + @override + String tradeCreatedAtLabel(String date) { + return 'creata $date'; + } + + @override + String get releaseSatsMenuItem => 'Rilascia sats'; + + @override + String get cancelOrderMenuItem => 'Annulla ordine'; + + @override + String get openDisputeMenuItem => 'Apri disputa'; + + @override + String get stepDoneLabel => 'FATTO'; + + @override + String stepIndicator(int current, int total) { + return 'PASSO $current DI $total'; + } + + @override + String get addLightningInvoiceButton => 'Aggiungi fattura Lightning'; + + @override + String get payHoldInvoiceButton => 'Paga hold invoice'; + + @override + String get markFiatSentButton => 'Segna fiat inviato'; + + @override + String get confirmReleaseSatsButton => 'Conferma e rilascia sats'; + + @override + String get viewDisputeButton => 'Vedi disputa'; + + @override + String get waitingForBuyer => 'In attesa dell\'acquirente…'; + + @override + String get waitingForSeller => 'In attesa del venditore…'; + + @override + String get waitingForFiatPayment => 'In attesa del pagamento fiat…'; + + @override + String get waitingForCounterpart => 'In attesa di una controparte…'; + + @override + String get yourTradeTimelineTitle => 'LA TUA OPERAZIONE'; + + @override + String get yourCounterpartFallback => 'la tua controparte'; + + @override + String secureChatUnread(int count) { + return 'Chat sicura · $count nuovi'; + } + + @override + String get secureChatEncrypted => 'Chat sicura · crittografata end-to-end'; } From b095d8d122f789c2cb06498978ca547f9416a88a Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 01:55:03 -0300 Subject: [PATCH 10/30] feat: localize chat screens and info panel strings Replace hardcoded English text in chat_room_screen.dart, chat_rooms_screen.dart, and info_panels.dart with localized strings from AppLocalizations. Adds 27 new l10n keys across en/de/es/fr/it for error messages, tooltips, tab labels, empty states, and trade/user information panel content including field labels, placeholders, and safety notes. --- .../chat/screens/chat_room_screen.dart | 20 ++- .../chat/screens/chat_rooms_screen.dart | 15 +- lib/features/chat/widgets/info_panels.dart | 42 ++--- lib/l10n/app_de.arb | 28 +++- lib/l10n/app_en.arb | 60 ++++++- lib/l10n/app_es.arb | 28 +++- lib/l10n/app_fr.arb | 28 +++- lib/l10n/app_it.arb | 28 +++- lib/l10n/app_localizations.dart | 156 ++++++++++++++++++ lib/l10n/app_localizations_de.dart | 86 ++++++++++ lib/l10n/app_localizations_en.dart | 85 ++++++++++ lib/l10n/app_localizations_es.dart | 87 ++++++++++ lib/l10n/app_localizations_fr.dart | 87 ++++++++++ lib/l10n/app_localizations_it.dart | 86 ++++++++++ 14 files changed, 795 insertions(+), 41 deletions(-) diff --git a/lib/features/chat/screens/chat_room_screen.dart b/lib/features/chat/screens/chat_room_screen.dart index 063d2ed3..8f47c2ec 100644 --- a/lib/features/chat/screens/chat_room_screen.dart +++ b/lib/features/chat/screens/chat_room_screen.dart @@ -9,6 +9,7 @@ import 'package:mostro/features/chat/widgets/info_panels.dart'; import 'package:mostro/features/chat/widgets/message_bubble.dart'; import 'package:mostro/features/chat/widgets/message_input.dart'; import 'package:mostro/features/chat/widgets/trade_state_header.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/shared/widgets/bottom_nav_bar.dart'; import 'package:mostro/shared/widgets/nym_avatar.dart'; import 'package:mostro/src/rust/api/messages.dart' as messages_api; @@ -118,7 +119,7 @@ class _ChatRoomScreenState extends ConsumerState { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Failed to send: $e'), + content: Text(AppLocalizations.of(context).messageSendFailed), backgroundColor: Colors.red, ), ); @@ -177,7 +178,7 @@ class _ChatRoomScreenState extends ConsumerState { orElse: () => ChatRoomState( orderId: widget.orderId, peerPubkey: '', - peerHandle: 'Unknown', + peerHandle: AppLocalizations.of(context).unknownPeerHandle, peerIconIndex: 0, peerColorHue: 180, isSelling: false, @@ -211,8 +212,8 @@ class _ChatRoomScreenState extends ConsumerState { Widget build(BuildContext context) { if (widget.orderId.isEmpty) { return Scaffold( - appBar: AppBar(title: const Text('Chat')), - body: const Center(child: Text('Invalid trade ID')), + appBar: AppBar(title: Text(AppLocalizations.of(context).navChat)), + body: Center(child: Text(AppLocalizations.of(context).invalidTradeId)), bottomNavigationBar: const BottomNavBar(), ); } @@ -228,6 +229,7 @@ class _ChatRoomScreenState extends ConsumerState { if (colors == null) { throw StateError('AppColors theme extension must be registered'); } + final l10n = AppLocalizations.of(context); final screenWidth = MediaQuery.sizeOf(context).width; final showSidePanel = screenWidth >= AppBreakpoints.tablet; @@ -257,7 +259,7 @@ class _ChatRoomScreenState extends ConsumerState { color: colors.backgroundCard, child: Center( child: Text( - 'Select \u2139 or \u{1F464}\nfor details', + l10n.selectForDetailsHint, textAlign: TextAlign.center, style: TextStyle(color: colors.textSubtle), ), @@ -303,7 +305,7 @@ class _ChatRoomScreenState extends ConsumerState { child: Padding( padding: const EdgeInsets.all(AppSpacing.lg), child: Text( - 'No messages yet.\nSay hello to ${room.peerHandle}!', + l10n.noMessagesYet(room.peerHandle), textAlign: TextAlign.center, style: TextStyle(color: colors.textSubtle), ), @@ -362,7 +364,7 @@ class _ChatRoomScreenState extends ConsumerState { title: _AppBarTitle(room: room), actions: [ IconButton( - tooltip: 'Exchange Info', + tooltip: l10n.exchangeInfoTooltip, icon: Icon( Icons.info_outline, color: _showTradeInfo ? colors.mostroGreen : null, @@ -370,7 +372,7 @@ class _ChatRoomScreenState extends ConsumerState { onPressed: _toggleTradeInfo, ), IconButton( - tooltip: 'User Info', + tooltip: l10n.userInfoTooltip, icon: Icon( Icons.person_outline, color: _showUserInfo ? colors.mostroGreen : null, @@ -437,7 +439,7 @@ class _AppBarTitle extends StatelessWidget { ], ), Text( - 'You are chatting with ${room.peerHandle}', + AppLocalizations.of(context).chattingWith(room.peerHandle), style: textTheme.bodySmall?.copyWith(color: colors.textSubtle), ), ], diff --git a/lib/features/chat/screens/chat_rooms_screen.dart b/lib/features/chat/screens/chat_rooms_screen.dart index 909bd284..b8362954 100644 --- a/lib/features/chat/screens/chat_rooms_screen.dart +++ b/lib/features/chat/screens/chat_rooms_screen.dart @@ -8,6 +8,7 @@ import 'package:mostro/features/chat/providers/chat_providers.dart'; import 'package:mostro/features/chat/widgets/chat_list_item.dart'; import 'package:mostro/features/disputes/widgets/disputes_list.dart'; import 'package:mostro/features/drawer/screens/drawer_menu.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/shared/widgets/bottom_nav_bar.dart'; import 'package:mostro/shared/widgets/notification_bell.dart'; @@ -77,9 +78,9 @@ class _ChatRoomsScreenState extends ConsumerState { indicatorColor: colors.mostroGreen, labelColor: colors.mostroGreen, unselectedLabelColor: colors.textSubtle, - tabs: const [ - Tab(text: 'Messages'), - Tab(text: 'Disputes'), + tabs: [ + Tab(text: AppLocalizations.of(context).messagesTab), + Tab(text: AppLocalizations.of(context).disputesTab), ], ), Expanded( @@ -141,7 +142,7 @@ class _ChatAppBar extends StatelessWidget { IconButton( onPressed: onMenuTap, icon: const Icon(Icons.menu, size: 24), - tooltip: 'Menu', + tooltip: AppLocalizations.of(context).menuTooltip, ), const Spacer(), Icon(Icons.psychology, size: 28, color: green), @@ -179,7 +180,7 @@ class _MessagesTab extends ConsumerWidget { vertical: AppSpacing.sm, ), child: Text( - 'Your active trade conversations', + AppLocalizations.of(context).activeTradeConversations, style: textTheme.bodySmall?.copyWith(color: colors.textSubtle), ), ), @@ -231,7 +232,7 @@ class _EmptyMessages extends StatelessWidget { Icon(Icons.chat_bubble_outline, size: 64, color: colors.textSubtle), const SizedBox(height: AppSpacing.lg), Text( - 'No messages available', + AppLocalizations.of(context).noMessagesAvailable, style: textTheme.bodyMedium?.copyWith(color: colors.textSecondary), ), ], @@ -263,7 +264,7 @@ class _DisputesTab extends StatelessWidget { vertical: AppSpacing.sm, ), child: Text( - 'Disputes and admin chat', + AppLocalizations.of(context).disputesAndAdminChat, style: textTheme.bodySmall?.copyWith(color: colors.textSubtle), ), ), diff --git a/lib/features/chat/widgets/info_panels.dart b/lib/features/chat/widgets/info_panels.dart index f5f2b7c9..ba04ebb2 100644 --- a/lib/features/chat/widgets/info_panels.dart +++ b/lib/features/chat/widgets/info_panels.dart @@ -2,15 +2,16 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/shared/widgets/nym_avatar.dart'; // ── Shared helpers ──────────────────────────────────────────────────────────── -void _copyToClipboard(BuildContext context, String text, String label) { +void _copyToClipboard(BuildContext context, String text) { Clipboard.setData(ClipboardData(text: text)); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('$label copied'), + content: Text(AppLocalizations.of(context).aboutCopiedToClipboard), duration: const Duration(seconds: 2), ), ); @@ -45,46 +46,46 @@ class TradeInformationTab extends StatelessWidget { children: [ // Section header Text( - 'Trade Information', + AppLocalizations.of(context).tradeInformationTitle, style: textTheme.headlineSmall, ), const SizedBox(height: AppSpacing.md), // Order ID (copyable) _InfoRow( - label: 'Order ID', + label: AppLocalizations.of(context).orderIdLabel, value: orderId, colors: colors, textTheme: textTheme, copyable: true, ), _InfoRow( - label: 'Fiat Amount', + label: AppLocalizations.of(context).fiatAmountLabel, value: '—', colors: colors, textTheme: textTheme, ), _InfoRow( - label: 'Sats Amount', + label: AppLocalizations.of(context).satsAmountLabel, value: '—', colors: colors, textTheme: textTheme, ), _InfoRow( - label: 'Status', + label: AppLocalizations.of(context).statusLabel, value: null, // rendered as chip below colors: colors, textTheme: textTheme, customValue: _StatusChip(colors: colors), ), _InfoRow( - label: 'Payment Method', + label: AppLocalizations.of(context).paymentMethodLabel, value: '—', colors: colors, textTheme: textTheme, ), _InfoRow( - label: 'Created', + label: AppLocalizations.of(context).createdLabel, value: '—', colors: colors, textTheme: textTheme, @@ -100,7 +101,7 @@ class TradeInformationTab extends StatelessWidget { borderRadius: BorderRadius.circular(AppRadius.chip), ), child: Text( - 'Details wired when trade provider available (Phase 10+)', + AppLocalizations.of(context).tradeDetailsPlaceholder, style: textTheme.bodySmall?.copyWith( color: colors.textSubtle, fontStyle: FontStyle.italic, @@ -145,7 +146,8 @@ class UserInformationTab extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ // Section header - Text('User Information', style: textTheme.headlineSmall), + Text(AppLocalizations.of(context).userInformationTitle, + style: textTheme.headlineSmall), const SizedBox(height: AppSpacing.md), // Avatar + handle @@ -170,12 +172,12 @@ class UserInformationTab extends StatelessWidget { // Peer public key Text( - "Peer's Public Key", + AppLocalizations.of(context).peerPublicKeyLabel, style: textTheme.bodySmall?.copyWith(color: colors.textSubtle), ), const SizedBox(height: AppSpacing.xs), GestureDetector( - onTap: () => _copy(context, peerPubkey, "Peer's public key"), + onTap: () => _copy(context, peerPubkey), child: Text( peerPubkey, style: textTheme.bodySmall?.copyWith( @@ -189,12 +191,12 @@ class UserInformationTab extends StatelessWidget { // Shared key Text( - 'Your Shared Key', + AppLocalizations.of(context).yourSharedKeyLabel, style: textTheme.bodySmall?.copyWith(color: colors.textSubtle), ), const SizedBox(height: AppSpacing.xs), Text( - 'Available after bridge integration (Phase 10+)', + AppLocalizations.of(context).sharedKeyPlaceholder, style: textTheme.bodySmall?.copyWith( color: colors.textSubtle, fontStyle: FontStyle.italic, @@ -219,7 +221,7 @@ class UserInformationTab extends StatelessWidget { const SizedBox(width: AppSpacing.xs), Expanded( child: Text( - 'Keep your shared key safe — it is needed for dispute resolution', + AppLocalizations.of(context).sharedKeySafetyNote, style: textTheme.bodySmall ?.copyWith(color: colors.textSubtle), ), @@ -233,8 +235,8 @@ class UserInformationTab extends StatelessWidget { ); } - void _copy(BuildContext context, String text, String label) => - _copyToClipboard(context, text, label); + void _copy(BuildContext context, String text) => + _copyToClipboard(context, text); } // ── Private helpers ─────────────────────────────────────────────────────────── @@ -274,7 +276,7 @@ class _InfoRow extends StatelessWidget { child: customValue ?? GestureDetector( onTap: copyable && value != null - ? () => _copyToClipboard(context, value!, label) + ? () => _copyToClipboard(context, value!) : null, child: Text( value ?? '—', @@ -307,7 +309,7 @@ class _StatusChip extends StatelessWidget { borderRadius: BorderRadius.circular(AppRadius.chip), ), child: Text( - 'Active', + AppLocalizations.of(context).tradeStatusActive, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppColors.statusActive.$2, fontWeight: FontWeight.w500, diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 0d53e38a..2be88bba 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -498,5 +498,31 @@ "yourTradeTimelineTitle": "DEIN TRADE", "yourCounterpartFallback": "deine Gegenpartei", "secureChatUnread": "Sicherer Chat · {count} neue", - "secureChatEncrypted": "Sicherer Chat · Ende-zu-Ende-verschlüsselt" + "secureChatEncrypted": "Sicherer Chat · Ende-zu-Ende-verschlüsselt", + "messageSendFailed": "Nachricht konnte nicht gesendet werden. Bitte versuche es erneut.", + "invalidTradeId": "Ungültige Trade-ID", + "selectForDetailsHint": "Wähle ℹ oder 👤\nfür Details", + "noMessagesYet": "Noch keine Nachrichten.\nSag {handle} hallo!", + "exchangeInfoTooltip": "Tausch-Info", + "userInfoTooltip": "Benutzer-Info", + "chattingWith": "Du chattest mit {handle}", + "unknownPeerHandle": "Unbekannt", + "messagesTab": "Nachrichten", + "disputesTab": "Streitfälle", + "activeTradeConversations": "Deine aktiven Trade-Gespräche", + "noMessagesAvailable": "Keine Nachrichten verfügbar", + "disputesAndAdminChat": "Streitfälle und Admin-Chat", + "tradeInformationTitle": "Trade-Informationen", + "orderIdLabel": "Bestell-ID", + "fiatAmountLabel": "Fiat-Betrag", + "satsAmountLabel": "Sats-Betrag", + "statusLabel": "Status", + "paymentMethodLabel": "Zahlungsmethode", + "createdLabel": "Erstellt", + "tradeDetailsPlaceholder": "Details verfügbar, sobald der Trade-Provider bereit ist (Phase 10+)", + "userInformationTitle": "Benutzerinformationen", + "peerPublicKeyLabel": "Öffentlicher Schlüssel des Partners", + "yourSharedKeyLabel": "Dein gemeinsamer Schlüssel", + "sharedKeyPlaceholder": "Verfügbar nach der Bridge-Integration (Phase 10+)", + "sharedKeySafetyNote": "Bewahre deinen gemeinsamen Schlüssel sicher auf — er wird für die Streitbeilegung benötigt" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 83749709..3bfe2f28 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1051,5 +1051,63 @@ "placeholders": {"count": {"type": "int"}} }, "secureChatEncrypted": "Secure chat · end-to-end encrypted", - "@secureChatEncrypted": {"description": "Chat chip subtitle when there are no unread messages"} + "@secureChatEncrypted": {"description": "Chat chip subtitle when there are no unread messages"}, + "messageSendFailed": "Failed to send message. Please try again.", + "@messageSendFailed": {"description": "Snackbar shown when sending a chat message fails"}, + "invalidTradeId": "Invalid trade ID", + "@invalidTradeId": {"description": "Shown when the chat room has no valid trade ID"}, + "selectForDetailsHint": "Select ℹ or 👤\nfor details", + "@selectForDetailsHint": {"description": "Side panel hint on tablet/desktop chat when no panel is selected"}, + "noMessagesYet": "No messages yet.\nSay hello to {handle}!", + "@noMessagesYet": { + "description": "Empty state inside a chat room", + "placeholders": {"handle": {"type": "String"}} + }, + "exchangeInfoTooltip": "Exchange Info", + "@exchangeInfoTooltip": {"description": "Tooltip for the trade info toggle in the chat room"}, + "userInfoTooltip": "User Info", + "@userInfoTooltip": {"description": "Tooltip for the user info toggle in the chat room"}, + "chattingWith": "You are chatting with {handle}", + "@chattingWith": { + "description": "App bar subtitle in a chat room", + "placeholders": {"handle": {"type": "String"}} + }, + "unknownPeerHandle": "Unknown", + "@unknownPeerHandle": {"description": "Fallback handle when the peer is unknown"}, + "messagesTab": "Messages", + "@messagesTab": {"description": "Chat screen tab: messages"}, + "disputesTab": "Disputes", + "@disputesTab": {"description": "Chat screen tab: disputes"}, + "activeTradeConversations": "Your active trade conversations", + "@activeTradeConversations": {"description": "Description under the Messages tab"}, + "noMessagesAvailable": "No messages available", + "@noMessagesAvailable": {"description": "Empty state for the Messages tab"}, + "disputesAndAdminChat": "Disputes and admin chat", + "@disputesAndAdminChat": {"description": "Description under the Disputes tab"}, + "tradeInformationTitle": "Trade Information", + "@tradeInformationTitle": {"description": "Title of the trade information panel"}, + "orderIdLabel": "Order ID", + "@orderIdLabel": {"description": "Label for the order ID field"}, + "fiatAmountLabel": "Fiat Amount", + "@fiatAmountLabel": {"description": "Label for the fiat amount field"}, + "satsAmountLabel": "Sats Amount", + "@satsAmountLabel": {"description": "Label for the sats amount field"}, + "statusLabel": "Status", + "@statusLabel": {"description": "Label for the status field"}, + "paymentMethodLabel": "Payment Method", + "@paymentMethodLabel": {"description": "Label for the payment method field"}, + "createdLabel": "Created", + "@createdLabel": {"description": "Label for the created date field"}, + "tradeDetailsPlaceholder": "Details wired when trade provider available (Phase 10+)", + "@tradeDetailsPlaceholder": {"description": "Placeholder note in the trade information panel"}, + "userInformationTitle": "User Information", + "@userInformationTitle": {"description": "Title of the user information panel"}, + "peerPublicKeyLabel": "Peer's Public Key", + "@peerPublicKeyLabel": {"description": "Label for the peer public key"}, + "yourSharedKeyLabel": "Your Shared Key", + "@yourSharedKeyLabel": {"description": "Label for the shared key"}, + "sharedKeyPlaceholder": "Available after bridge integration (Phase 10+)", + "@sharedKeyPlaceholder": {"description": "Placeholder note for the shared key"}, + "sharedKeySafetyNote": "Keep your shared key safe — it is needed for dispute resolution", + "@sharedKeySafetyNote": {"description": "Safety note about the shared key"} } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 96a4f069..16ff59eb 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -498,5 +498,31 @@ "yourTradeTimelineTitle": "TU OPERACIÓN", "yourCounterpartFallback": "tu contraparte", "secureChatUnread": "Chat seguro · {count} nuevos", - "secureChatEncrypted": "Chat seguro · cifrado de extremo a extremo" + "secureChatEncrypted": "Chat seguro · cifrado de extremo a extremo", + "messageSendFailed": "No se pudo enviar el mensaje. Inténtalo de nuevo.", + "invalidTradeId": "ID de operación inválido", + "selectForDetailsHint": "Selecciona ℹ o 👤\npara ver detalles", + "noMessagesYet": "Aún no hay mensajes.\n¡Saluda a {handle}!", + "exchangeInfoTooltip": "Info del intercambio", + "userInfoTooltip": "Info del usuario", + "chattingWith": "Estás chateando con {handle}", + "unknownPeerHandle": "Desconocido", + "messagesTab": "Mensajes", + "disputesTab": "Disputas", + "activeTradeConversations": "Tus conversaciones de operaciones activas", + "noMessagesAvailable": "No hay mensajes disponibles", + "disputesAndAdminChat": "Disputas y chat con administradores", + "tradeInformationTitle": "Información de la operación", + "orderIdLabel": "ID de orden", + "fiatAmountLabel": "Monto fiat", + "satsAmountLabel": "Monto en sats", + "statusLabel": "Estado", + "paymentMethodLabel": "Método de pago", + "createdLabel": "Creada", + "tradeDetailsPlaceholder": "Detalles disponibles cuando el proveedor de operaciones esté listo (Fase 10+)", + "userInformationTitle": "Información del usuario", + "peerPublicKeyLabel": "Clave pública del par", + "yourSharedKeyLabel": "Tu clave compartida", + "sharedKeyPlaceholder": "Disponible tras la integración del puente (Fase 10+)", + "sharedKeySafetyNote": "Guarda tu clave compartida de forma segura — es necesaria para resolver disputas" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 25d969e8..72645c55 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -498,5 +498,31 @@ "yourTradeTimelineTitle": "VOTRE TRANSACTION", "yourCounterpartFallback": "votre contrepartie", "secureChatUnread": "Chat sécurisé · {count} nouveaux", - "secureChatEncrypted": "Chat sécurisé · chiffré de bout en bout" + "secureChatEncrypted": "Chat sécurisé · chiffré de bout en bout", + "messageSendFailed": "Échec de l'envoi du message. Veuillez réessayer.", + "invalidTradeId": "ID de transaction invalide", + "selectForDetailsHint": "Sélectionnez ℹ ou 👤\npour les détails", + "noMessagesYet": "Pas encore de messages.\nDites bonjour à {handle} !", + "exchangeInfoTooltip": "Infos sur l'échange", + "userInfoTooltip": "Infos utilisateur", + "chattingWith": "Vous discutez avec {handle}", + "unknownPeerHandle": "Inconnu", + "messagesTab": "Messages", + "disputesTab": "Litiges", + "activeTradeConversations": "Vos conversations de transactions actives", + "noMessagesAvailable": "Aucun message disponible", + "disputesAndAdminChat": "Litiges et chat administrateur", + "tradeInformationTitle": "Informations sur la transaction", + "orderIdLabel": "ID de l'ordre", + "fiatAmountLabel": "Montant fiat", + "satsAmountLabel": "Montant en sats", + "statusLabel": "Statut", + "paymentMethodLabel": "Moyen de paiement", + "createdLabel": "Créé", + "tradeDetailsPlaceholder": "Détails disponibles lorsque le fournisseur de transactions sera prêt (Phase 10+)", + "userInformationTitle": "Informations utilisateur", + "peerPublicKeyLabel": "Clé publique du pair", + "yourSharedKeyLabel": "Votre clé partagée", + "sharedKeyPlaceholder": "Disponible après l'intégration du pont (Phase 10+)", + "sharedKeySafetyNote": "Conservez votre clé partagée en sécurité — elle est nécessaire pour la résolution des litiges" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 3dfc530a..6fa3f111 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -498,5 +498,31 @@ "yourTradeTimelineTitle": "LA TUA OPERAZIONE", "yourCounterpartFallback": "la tua controparte", "secureChatUnread": "Chat sicura · {count} nuovi", - "secureChatEncrypted": "Chat sicura · crittografata end-to-end" + "secureChatEncrypted": "Chat sicura · crittografata end-to-end", + "messageSendFailed": "Impossibile inviare il messaggio. Riprova.", + "invalidTradeId": "ID operazione non valido", + "selectForDetailsHint": "Seleziona ℹ o 👤\nper i dettagli", + "noMessagesYet": "Ancora nessun messaggio.\nSaluta {handle}!", + "exchangeInfoTooltip": "Info sullo scambio", + "userInfoTooltip": "Info utente", + "chattingWith": "Stai chattando con {handle}", + "unknownPeerHandle": "Sconosciuto", + "messagesTab": "Messaggi", + "disputesTab": "Dispute", + "activeTradeConversations": "Le tue conversazioni di operazioni attive", + "noMessagesAvailable": "Nessun messaggio disponibile", + "disputesAndAdminChat": "Dispute e chat con amministratori", + "tradeInformationTitle": "Informazioni operazione", + "orderIdLabel": "ID ordine", + "fiatAmountLabel": "Importo fiat", + "satsAmountLabel": "Importo in sats", + "statusLabel": "Stato", + "paymentMethodLabel": "Metodo di pagamento", + "createdLabel": "Creata", + "tradeDetailsPlaceholder": "Dettagli disponibili quando il provider di operazioni sarà pronto (Fase 10+)", + "userInformationTitle": "Informazioni utente", + "peerPublicKeyLabel": "Chiave pubblica del peer", + "yourSharedKeyLabel": "La tua chiave condivisa", + "sharedKeyPlaceholder": "Disponibile dopo l'integrazione del bridge (Fase 10+)", + "sharedKeySafetyNote": "Conserva la tua chiave condivisa al sicuro — è necessaria per la risoluzione delle dispute" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index a4944a49..97b2e04b 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2947,6 +2947,162 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Secure chat · end-to-end encrypted'** String get secureChatEncrypted; + + /// Snackbar shown when sending a chat message fails + /// + /// In en, this message translates to: + /// **'Failed to send message. Please try again.'** + String get messageSendFailed; + + /// Shown when the chat room has no valid trade ID + /// + /// In en, this message translates to: + /// **'Invalid trade ID'** + String get invalidTradeId; + + /// Side panel hint on tablet/desktop chat when no panel is selected + /// + /// In en, this message translates to: + /// **'Select ℹ or 👤\nfor details'** + String get selectForDetailsHint; + + /// Empty state inside a chat room + /// + /// In en, this message translates to: + /// **'No messages yet.\nSay hello to {handle}!'** + String noMessagesYet(String handle); + + /// Tooltip for the trade info toggle in the chat room + /// + /// In en, this message translates to: + /// **'Exchange Info'** + String get exchangeInfoTooltip; + + /// Tooltip for the user info toggle in the chat room + /// + /// In en, this message translates to: + /// **'User Info'** + String get userInfoTooltip; + + /// App bar subtitle in a chat room + /// + /// In en, this message translates to: + /// **'You are chatting with {handle}'** + String chattingWith(String handle); + + /// Fallback handle when the peer is unknown + /// + /// In en, this message translates to: + /// **'Unknown'** + String get unknownPeerHandle; + + /// Chat screen tab: messages + /// + /// In en, this message translates to: + /// **'Messages'** + String get messagesTab; + + /// Chat screen tab: disputes + /// + /// In en, this message translates to: + /// **'Disputes'** + String get disputesTab; + + /// Description under the Messages tab + /// + /// In en, this message translates to: + /// **'Your active trade conversations'** + String get activeTradeConversations; + + /// Empty state for the Messages tab + /// + /// In en, this message translates to: + /// **'No messages available'** + String get noMessagesAvailable; + + /// Description under the Disputes tab + /// + /// In en, this message translates to: + /// **'Disputes and admin chat'** + String get disputesAndAdminChat; + + /// Title of the trade information panel + /// + /// In en, this message translates to: + /// **'Trade Information'** + String get tradeInformationTitle; + + /// Label for the order ID field + /// + /// In en, this message translates to: + /// **'Order ID'** + String get orderIdLabel; + + /// Label for the fiat amount field + /// + /// In en, this message translates to: + /// **'Fiat Amount'** + String get fiatAmountLabel; + + /// Label for the sats amount field + /// + /// In en, this message translates to: + /// **'Sats Amount'** + String get satsAmountLabel; + + /// Label for the status field + /// + /// In en, this message translates to: + /// **'Status'** + String get statusLabel; + + /// Label for the payment method field + /// + /// In en, this message translates to: + /// **'Payment Method'** + String get paymentMethodLabel; + + /// Label for the created date field + /// + /// In en, this message translates to: + /// **'Created'** + String get createdLabel; + + /// Placeholder note in the trade information panel + /// + /// In en, this message translates to: + /// **'Details wired when trade provider available (Phase 10+)'** + String get tradeDetailsPlaceholder; + + /// Title of the user information panel + /// + /// In en, this message translates to: + /// **'User Information'** + String get userInformationTitle; + + /// Label for the peer public key + /// + /// In en, this message translates to: + /// **'Peer\'s Public Key'** + String get peerPublicKeyLabel; + + /// Label for the shared key + /// + /// In en, this message translates to: + /// **'Your Shared Key'** + String get yourSharedKeyLabel; + + /// Placeholder note for the shared key + /// + /// In en, this message translates to: + /// **'Available after bridge integration (Phase 10+)'** + String get sharedKeyPlaceholder; + + /// Safety note about the shared key + /// + /// In en, this message translates to: + /// **'Keep your shared key safe — it is needed for dispute resolution'** + String get sharedKeySafetyNote; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 1b7d8f94..6eef840a 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1612,4 +1612,90 @@ class AppLocalizationsDe extends AppLocalizations { @override String get secureChatEncrypted => 'Sicherer Chat · Ende-zu-Ende-verschlüsselt'; + + @override + String get messageSendFailed => + 'Nachricht konnte nicht gesendet werden. Bitte versuche es erneut.'; + + @override + String get invalidTradeId => 'Ungültige Trade-ID'; + + @override + String get selectForDetailsHint => 'Wähle ℹ oder 👤\nfür Details'; + + @override + String noMessagesYet(String handle) { + return 'Noch keine Nachrichten.\nSag $handle hallo!'; + } + + @override + String get exchangeInfoTooltip => 'Tausch-Info'; + + @override + String get userInfoTooltip => 'Benutzer-Info'; + + @override + String chattingWith(String handle) { + return 'Du chattest mit $handle'; + } + + @override + String get unknownPeerHandle => 'Unbekannt'; + + @override + String get messagesTab => 'Nachrichten'; + + @override + String get disputesTab => 'Streitfälle'; + + @override + String get activeTradeConversations => 'Deine aktiven Trade-Gespräche'; + + @override + String get noMessagesAvailable => 'Keine Nachrichten verfügbar'; + + @override + String get disputesAndAdminChat => 'Streitfälle und Admin-Chat'; + + @override + String get tradeInformationTitle => 'Trade-Informationen'; + + @override + String get orderIdLabel => 'Bestell-ID'; + + @override + String get fiatAmountLabel => 'Fiat-Betrag'; + + @override + String get satsAmountLabel => 'Sats-Betrag'; + + @override + String get statusLabel => 'Status'; + + @override + String get paymentMethodLabel => 'Zahlungsmethode'; + + @override + String get createdLabel => 'Erstellt'; + + @override + String get tradeDetailsPlaceholder => + 'Details verfügbar, sobald der Trade-Provider bereit ist (Phase 10+)'; + + @override + String get userInformationTitle => 'Benutzerinformationen'; + + @override + String get peerPublicKeyLabel => 'Öffentlicher Schlüssel des Partners'; + + @override + String get yourSharedKeyLabel => 'Dein gemeinsamer Schlüssel'; + + @override + String get sharedKeyPlaceholder => + 'Verfügbar nach der Bridge-Integration (Phase 10+)'; + + @override + String get sharedKeySafetyNote => + 'Bewahre deinen gemeinsamen Schlüssel sicher auf — er wird für die Streitbeilegung benötigt'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 081d573d..f12d3a70 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1586,4 +1586,89 @@ class AppLocalizationsEn extends AppLocalizations { @override String get secureChatEncrypted => 'Secure chat · end-to-end encrypted'; + + @override + String get messageSendFailed => 'Failed to send message. Please try again.'; + + @override + String get invalidTradeId => 'Invalid trade ID'; + + @override + String get selectForDetailsHint => 'Select ℹ or 👤\nfor details'; + + @override + String noMessagesYet(String handle) { + return 'No messages yet.\nSay hello to $handle!'; + } + + @override + String get exchangeInfoTooltip => 'Exchange Info'; + + @override + String get userInfoTooltip => 'User Info'; + + @override + String chattingWith(String handle) { + return 'You are chatting with $handle'; + } + + @override + String get unknownPeerHandle => 'Unknown'; + + @override + String get messagesTab => 'Messages'; + + @override + String get disputesTab => 'Disputes'; + + @override + String get activeTradeConversations => 'Your active trade conversations'; + + @override + String get noMessagesAvailable => 'No messages available'; + + @override + String get disputesAndAdminChat => 'Disputes and admin chat'; + + @override + String get tradeInformationTitle => 'Trade Information'; + + @override + String get orderIdLabel => 'Order ID'; + + @override + String get fiatAmountLabel => 'Fiat Amount'; + + @override + String get satsAmountLabel => 'Sats Amount'; + + @override + String get statusLabel => 'Status'; + + @override + String get paymentMethodLabel => 'Payment Method'; + + @override + String get createdLabel => 'Created'; + + @override + String get tradeDetailsPlaceholder => + 'Details wired when trade provider available (Phase 10+)'; + + @override + String get userInformationTitle => 'User Information'; + + @override + String get peerPublicKeyLabel => 'Peer\'s Public Key'; + + @override + String get yourSharedKeyLabel => 'Your Shared Key'; + + @override + String get sharedKeyPlaceholder => + 'Available after bridge integration (Phase 10+)'; + + @override + String get sharedKeySafetyNote => + 'Keep your shared key safe — it is needed for dispute resolution'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 6720bde1..6d51d502 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1605,4 +1605,91 @@ class AppLocalizationsEs extends AppLocalizations { @override String get secureChatEncrypted => 'Chat seguro · cifrado de extremo a extremo'; + + @override + String get messageSendFailed => + 'No se pudo enviar el mensaje. Inténtalo de nuevo.'; + + @override + String get invalidTradeId => 'ID de operación inválido'; + + @override + String get selectForDetailsHint => 'Selecciona ℹ o 👤\npara ver detalles'; + + @override + String noMessagesYet(String handle) { + return 'Aún no hay mensajes.\n¡Saluda a $handle!'; + } + + @override + String get exchangeInfoTooltip => 'Info del intercambio'; + + @override + String get userInfoTooltip => 'Info del usuario'; + + @override + String chattingWith(String handle) { + return 'Estás chateando con $handle'; + } + + @override + String get unknownPeerHandle => 'Desconocido'; + + @override + String get messagesTab => 'Mensajes'; + + @override + String get disputesTab => 'Disputas'; + + @override + String get activeTradeConversations => + 'Tus conversaciones de operaciones activas'; + + @override + String get noMessagesAvailable => 'No hay mensajes disponibles'; + + @override + String get disputesAndAdminChat => 'Disputas y chat con administradores'; + + @override + String get tradeInformationTitle => 'Información de la operación'; + + @override + String get orderIdLabel => 'ID de orden'; + + @override + String get fiatAmountLabel => 'Monto fiat'; + + @override + String get satsAmountLabel => 'Monto en sats'; + + @override + String get statusLabel => 'Estado'; + + @override + String get paymentMethodLabel => 'Método de pago'; + + @override + String get createdLabel => 'Creada'; + + @override + String get tradeDetailsPlaceholder => + 'Detalles disponibles cuando el proveedor de operaciones esté listo (Fase 10+)'; + + @override + String get userInformationTitle => 'Información del usuario'; + + @override + String get peerPublicKeyLabel => 'Clave pública del par'; + + @override + String get yourSharedKeyLabel => 'Tu clave compartida'; + + @override + String get sharedKeyPlaceholder => + 'Disponible tras la integración del puente (Fase 10+)'; + + @override + String get sharedKeySafetyNote => + 'Guarda tu clave compartida de forma segura — es necesaria para resolver disputas'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index e1b5b7b4..38c8f2ed 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1612,4 +1612,91 @@ class AppLocalizationsFr extends AppLocalizations { @override String get secureChatEncrypted => 'Chat sécurisé · chiffré de bout en bout'; + + @override + String get messageSendFailed => + 'Échec de l\'envoi du message. Veuillez réessayer.'; + + @override + String get invalidTradeId => 'ID de transaction invalide'; + + @override + String get selectForDetailsHint => 'Sélectionnez ℹ ou 👤\npour les détails'; + + @override + String noMessagesYet(String handle) { + return 'Pas encore de messages.\nDites bonjour à $handle !'; + } + + @override + String get exchangeInfoTooltip => 'Infos sur l\'échange'; + + @override + String get userInfoTooltip => 'Infos utilisateur'; + + @override + String chattingWith(String handle) { + return 'Vous discutez avec $handle'; + } + + @override + String get unknownPeerHandle => 'Inconnu'; + + @override + String get messagesTab => 'Messages'; + + @override + String get disputesTab => 'Litiges'; + + @override + String get activeTradeConversations => + 'Vos conversations de transactions actives'; + + @override + String get noMessagesAvailable => 'Aucun message disponible'; + + @override + String get disputesAndAdminChat => 'Litiges et chat administrateur'; + + @override + String get tradeInformationTitle => 'Informations sur la transaction'; + + @override + String get orderIdLabel => 'ID de l\'ordre'; + + @override + String get fiatAmountLabel => 'Montant fiat'; + + @override + String get satsAmountLabel => 'Montant en sats'; + + @override + String get statusLabel => 'Statut'; + + @override + String get paymentMethodLabel => 'Moyen de paiement'; + + @override + String get createdLabel => 'Créé'; + + @override + String get tradeDetailsPlaceholder => + 'Détails disponibles lorsque le fournisseur de transactions sera prêt (Phase 10+)'; + + @override + String get userInformationTitle => 'Informations utilisateur'; + + @override + String get peerPublicKeyLabel => 'Clé publique du pair'; + + @override + String get yourSharedKeyLabel => 'Votre clé partagée'; + + @override + String get sharedKeyPlaceholder => + 'Disponible après l\'intégration du pont (Phase 10+)'; + + @override + String get sharedKeySafetyNote => + 'Conservez votre clé partagée en sécurité — elle est nécessaire pour la résolution des litiges'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 6dd51c73..19187aba 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1603,4 +1603,90 @@ class AppLocalizationsIt extends AppLocalizations { @override String get secureChatEncrypted => 'Chat sicura · crittografata end-to-end'; + + @override + String get messageSendFailed => 'Impossibile inviare il messaggio. Riprova.'; + + @override + String get invalidTradeId => 'ID operazione non valido'; + + @override + String get selectForDetailsHint => 'Seleziona ℹ o 👤\nper i dettagli'; + + @override + String noMessagesYet(String handle) { + return 'Ancora nessun messaggio.\nSaluta $handle!'; + } + + @override + String get exchangeInfoTooltip => 'Info sullo scambio'; + + @override + String get userInfoTooltip => 'Info utente'; + + @override + String chattingWith(String handle) { + return 'Stai chattando con $handle'; + } + + @override + String get unknownPeerHandle => 'Sconosciuto'; + + @override + String get messagesTab => 'Messaggi'; + + @override + String get disputesTab => 'Dispute'; + + @override + String get activeTradeConversations => + 'Le tue conversazioni di operazioni attive'; + + @override + String get noMessagesAvailable => 'Nessun messaggio disponibile'; + + @override + String get disputesAndAdminChat => 'Dispute e chat con amministratori'; + + @override + String get tradeInformationTitle => 'Informazioni operazione'; + + @override + String get orderIdLabel => 'ID ordine'; + + @override + String get fiatAmountLabel => 'Importo fiat'; + + @override + String get satsAmountLabel => 'Importo in sats'; + + @override + String get statusLabel => 'Stato'; + + @override + String get paymentMethodLabel => 'Metodo di pagamento'; + + @override + String get createdLabel => 'Creata'; + + @override + String get tradeDetailsPlaceholder => + 'Dettagli disponibili quando il provider di operazioni sarà pronto (Fase 10+)'; + + @override + String get userInformationTitle => 'Informazioni utente'; + + @override + String get peerPublicKeyLabel => 'Chiave pubblica del peer'; + + @override + String get yourSharedKeyLabel => 'La tua chiave condivisa'; + + @override + String get sharedKeyPlaceholder => + 'Disponibile dopo l\'integrazione del bridge (Fase 10+)'; + + @override + String get sharedKeySafetyNote => + 'Conserva la tua chiave condivisa al sicuro — è necessaria per la risoluzione delle dispute'; } From e4bf608e82d1d8a3dd3a72092c8a08e04ed907ec Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 02:05:34 -0300 Subject: [PATCH 11/30] feat: localize chat widget strings --- lib/features/chat/widgets/chat_list_item.dart | 8 +- .../chat/widgets/encrypted_file_message.dart | 21 ++-- .../chat/widgets/encrypted_image_message.dart | 9 +- lib/features/chat/widgets/message_bubble.dart | 9 +- lib/features/chat/widgets/message_input.dart | 8 +- .../chat/widgets/trade_state_header.dart | 15 +-- lib/l10n/app_de.arb | 18 +++- lib/l10n/app_en.arb | 52 +++++++++- lib/l10n/app_es.arb | 18 +++- lib/l10n/app_fr.arb | 18 +++- lib/l10n/app_it.arb | 18 +++- lib/l10n/app_localizations.dart | 96 +++++++++++++++++++ lib/l10n/app_localizations_de.dart | 60 ++++++++++++ lib/l10n/app_localizations_en.dart | 60 ++++++++++++ lib/l10n/app_localizations_es.dart | 62 ++++++++++++ lib/l10n/app_localizations_fr.dart | 62 ++++++++++++ lib/l10n/app_localizations_it.dart | 62 ++++++++++++ 17 files changed, 560 insertions(+), 36 deletions(-) diff --git a/lib/features/chat/widgets/chat_list_item.dart b/lib/features/chat/widgets/chat_list_item.dart index f3d64285..4824dc2c 100644 --- a/lib/features/chat/widgets/chat_list_item.dart +++ b/lib/features/chat/widgets/chat_list_item.dart @@ -25,18 +25,18 @@ class ChatListItem extends StatelessWidget { final colors = Theme.of(context).extension(); if (colors == null) throw StateError('AppColors theme extension must be registered'); final textTheme = Theme.of(context).textTheme; + final l10n = AppLocalizations.of(context); final contextLine = room.isSelling - ? 'You are selling sats to ${room.peerHandle}' - : 'You are buying sats from ${room.peerHandle}'; + ? l10n.sellingSatsTo(room.peerHandle) + : l10n.buyingSatsFrom(room.peerHandle); final previewText = room.lastMessage == null ? null : room.lastMessageIsOwn - ? 'You: ${room.lastMessage}' + ? l10n.youMessagePrefix(room.lastMessage!) : room.lastMessage; - final l10n = AppLocalizations.of(context); final timestampLabel = room.lastMessageAt > 0 ? _formatTimestamp(room.lastMessageAt, l10n) : ''; diff --git a/lib/features/chat/widgets/encrypted_file_message.dart b/lib/features/chat/widgets/encrypted_file_message.dart index d12ff429..28967fe3 100644 --- a/lib/features/chat/widgets/encrypted_file_message.dart +++ b/lib/features/chat/widgets/encrypted_file_message.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Placeholder widget for encrypted file attachments (non-image). /// @@ -36,7 +37,7 @@ class _EncryptedFileMessageState extends State { final icon = _iconForMime(widget.mimeType); final sizeLabel = _formatSize(widget.fileSizeBytes); - final typeLabel = _typeLabel(widget.mimeType); + final typeLabel = _typeLabel(widget.mimeType, AppLocalizations.of(context)); return Card( shape: RoundedRectangleBorder( @@ -90,7 +91,7 @@ class _EncryptedFileMessageState extends State { // Download button IconButton( icon: Icon(Icons.download, color: colors.textSecondary), - tooltip: 'Download', + tooltip: AppLocalizations.of(context).downloadTooltip, onPressed: _downloading ? null : () => _startDownload(context), ), ], @@ -118,9 +119,9 @@ class _EncryptedFileMessageState extends State { setState(() => _downloading = false); // Use this.context (State.context) which is guarded by the mounted check above. ScaffoldMessenger.of(this.context).showSnackBar( - const SnackBar( - content: Text('File download wired in Phase 10+'), - duration: Duration(seconds: 2), + SnackBar( + content: Text(AppLocalizations.of(this.context).fileDownloadPlaceholder), + duration: const Duration(seconds: 2), ), ); } @@ -142,12 +143,12 @@ class _EncryptedFileMessageState extends State { return '$kb KB'; } - String _typeLabel(String mime) { + String _typeLabel(String mime, AppLocalizations l10n) { if (mime.contains('pdf')) return 'PDF'; - if (mime.contains('video')) return 'Video'; - if (mime.contains('image')) return 'Image'; - if (mime.contains('zip') || mime.contains('tar')) return 'Archive'; - return 'File'; + if (mime.contains('video')) return l10n.fileTypeVideo; + if (mime.contains('image')) return l10n.fileTypeImage; + if (mime.contains('zip') || mime.contains('tar')) return l10n.fileTypeArchive; + return l10n.fileTypeFile; } } diff --git a/lib/features/chat/widgets/encrypted_image_message.dart b/lib/features/chat/widgets/encrypted_image_message.dart index bf7d9b73..ee27f8f4 100644 --- a/lib/features/chat/widgets/encrypted_image_message.dart +++ b/lib/features/chat/widgets/encrypted_image_message.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Placeholder widget for encrypted image attachments. /// @@ -49,7 +50,7 @@ class EncryptedImageMessage extends StatelessWidget { children: [ const SizedBox(height: 52), // below icon Text( - 'Tap to download', + AppLocalizations.of(context).tapToDownload, style: textTheme.bodySmall?.copyWith( color: colors.textSubtle, ), @@ -82,9 +83,9 @@ class EncryptedImageMessage extends StatelessWidget { void _onTap(BuildContext context) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Image download wired in Phase 10+'), - duration: Duration(seconds: 2), + SnackBar( + content: Text(AppLocalizations.of(context).imageDownloadPlaceholder), + duration: const Duration(seconds: 2), ), ); } diff --git a/lib/features/chat/widgets/message_bubble.dart b/lib/features/chat/widgets/message_bubble.dart index 4d084bd4..b1165a30 100644 --- a/lib/features/chat/widgets/message_bubble.dart +++ b/lib/features/chat/widgets/message_bubble.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; // ── ChatMessage model ───────────────────────────────────────────────────────── @@ -147,7 +148,7 @@ class MessageBubble extends StatelessWidget { if (message.hasAttachment) ...[ const SizedBox(height: AppSpacing.xs), Text( - '[Attachment]', + AppLocalizations.of(context).attachmentLabel, style: textTheme.bodySmall?.copyWith( color: Colors.white70, fontStyle: FontStyle.italic, @@ -178,9 +179,9 @@ class MessageBubble extends StatelessWidget { void _copyToClipboard(BuildContext context, String text) { Clipboard.setData(ClipboardData(text: text)); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Copied'), - duration: Duration(seconds: 2), + SnackBar( + content: Text(AppLocalizations.of(context).messageCopied), + duration: const Duration(seconds: 2), ), ); } diff --git a/lib/features/chat/widgets/message_input.dart b/lib/features/chat/widgets/message_input.dart index 07969586..ba01ce8f 100644 --- a/lib/features/chat/widgets/message_input.dart +++ b/lib/features/chat/widgets/message_input.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Bottom message composition bar. /// @@ -47,6 +48,7 @@ class _MessageInputState extends State { Widget build(BuildContext context) { final colors = Theme.of(context).extension(); if (colors == null) throw StateError('AppColors theme extension must be registered'); + final l10n = AppLocalizations.of(context); return Container( decoration: BoxDecoration( @@ -77,7 +79,7 @@ class _MessageInputState extends State { : IconButton( icon: Icon(Icons.attach_file, color: colors.textSubtle), onPressed: widget.onAttachFile, - tooltip: 'Attach file', + tooltip: l10n.disputeAttachFile, padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), @@ -94,7 +96,7 @@ class _MessageInputState extends State { color: colors.textPrimary, ), decoration: InputDecoration( - hintText: 'Write a message...', + hintText: l10n.disputeWriteMessageHint, hintStyle: TextStyle(color: colors.textSubtle), filled: true, fillColor: colors.backgroundInput, @@ -127,7 +129,7 @@ class _MessageInputState extends State { child: IconButton( icon: Icon(Icons.send, color: colors.mostroGreen), onPressed: _handleSend, - tooltip: 'Send', + tooltip: l10n.disputeSend, padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), diff --git a/lib/features/chat/widgets/trade_state_header.dart b/lib/features/chat/widgets/trade_state_header.dart index 903f8bae..e9021ab2 100644 --- a/lib/features/chat/widgets/trade_state_header.dart +++ b/lib/features/chat/widgets/trade_state_header.dart @@ -9,6 +9,7 @@ import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/home/providers/home_order_providers.dart'; import 'package:mostro/features/order/providers/trade_state_provider.dart'; import 'package:mostro/features/trades/providers/trades_providers.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/shared/widgets/status_chip.dart'; import 'package:mostro/src/rust/api/orders.dart' as orders_api; import 'package:mostro/src/rust/api/types.dart' as rust_types; @@ -83,6 +84,7 @@ class TradeStateHeader extends ConsumerWidget { final secondary = colors?.textSecondary ?? const Color(0xFFB0B3C6); final amber = colors?.warningAmber ?? const Color(0xFFE89C3C); final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); + final l10n = AppLocalizations.of(context); // Live status overrides the snapshot baked into the resolved order. final liveStatus = ref.watch(tradeStatusProvider(orderId)).valueOrNull; @@ -94,10 +96,11 @@ class TradeStateHeader extends ConsumerWidget { ref.watch(tradeRoleFromDbProvider(orderId)).valueOrNull ?? _deriveIsBuyer(order); - final roleWord = isBuyer ? 'Buying' : 'Selling'; final amountLabel = order.amountSats != null - ? '$roleWord ${_fmtSats(order.amountSats!)} sats' - : '$roleWord Bitcoin'; + ? (isBuyer + ? l10n.buyingSatsAmount(_fmtSats(order.amountSats!)) + : l10n.sellingSatsAmount(_fmtSats(order.amountSats!))) + : (isBuyer ? l10n.buyingBitcoin : l10n.sellingBitcoin); final dot = Text('·', style: textTheme.bodySmall?.copyWith(color: secondary)); @@ -129,7 +132,7 @@ class TradeStateHeader extends ConsumerWidget { crossAxisAlignment: WrapCrossAlignment.center, children: [ StatusChip( - label: statusFilter.label, + label: statusFilter.localizedLabel(l10n), background: pillBg, foreground: pillFg, ), @@ -178,7 +181,7 @@ class TradeStateHeader extends ConsumerWidget { const Spacer(), const SizedBox(width: AppSpacing.sm), Text( - 'View order', + l10n.viewOrderLink, style: textTheme.bodySmall?.copyWith( color: green, fontWeight: FontWeight.w600, @@ -299,7 +302,7 @@ class _CountdownChipState extends State<_CountdownChip> { Icon(Icons.schedule, size: 12, color: widget.color), const SizedBox(width: 3), Text( - '${_fmtRemaining(remaining)} left', + AppLocalizations.of(context).timeLeftLabel(_fmtRemaining(remaining)), style: textTheme.bodySmall?.copyWith( color: widget.color, fontWeight: FontWeight.w700, diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 2be88bba..0b78ceaf 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -524,5 +524,21 @@ "peerPublicKeyLabel": "Öffentlicher Schlüssel des Partners", "yourSharedKeyLabel": "Dein gemeinsamer Schlüssel", "sharedKeyPlaceholder": "Verfügbar nach der Bridge-Integration (Phase 10+)", - "sharedKeySafetyNote": "Bewahre deinen gemeinsamen Schlüssel sicher auf — er wird für die Streitbeilegung benötigt" + "sharedKeySafetyNote": "Bewahre deinen gemeinsamen Schlüssel sicher auf — er wird für die Streitbeilegung benötigt", + "attachmentLabel": "[Anhang]", + "sellingSatsTo": "Du verkaufst Sats an {handle}", + "buyingSatsFrom": "Du kaufst Sats von {handle}", + "youMessagePrefix": "Du: {message}", + "downloadTooltip": "Herunterladen", + "fileDownloadPlaceholder": "Datei-Download in Phase 10+ verfügbar", + "fileTypeVideo": "Video", + "fileTypeImage": "Bild", + "fileTypeArchive": "Archiv", + "fileTypeFile": "Datei", + "tapToDownload": "Zum Herunterladen tippen", + "imageDownloadPlaceholder": "Bild-Download in Phase 10+ verfügbar", + "buyingSatsAmount": "Kaufe {sats} Sats", + "sellingSatsAmount": "Verkaufe {sats} Sats", + "viewOrderLink": "Bestellung ansehen", + "timeLeftLabel": "{time} übrig" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3bfe2f28..7e216c42 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1109,5 +1109,55 @@ "sharedKeyPlaceholder": "Available after bridge integration (Phase 10+)", "@sharedKeyPlaceholder": {"description": "Placeholder note for the shared key"}, "sharedKeySafetyNote": "Keep your shared key safe — it is needed for dispute resolution", - "@sharedKeySafetyNote": {"description": "Safety note about the shared key"} + "@sharedKeySafetyNote": {"description": "Safety note about the shared key"}, + "attachmentLabel": "[Attachment]", + "@attachmentLabel": {"description": "Label shown on a chat bubble that has a file attachment"}, + "sellingSatsTo": "You are selling sats to {handle}", + "@sellingSatsTo": { + "description": "Chat list context line when the user is selling", + "placeholders": {"handle": {"type": "String"}} + }, + "buyingSatsFrom": "You are buying sats from {handle}", + "@buyingSatsFrom": { + "description": "Chat list context line when the user is buying", + "placeholders": {"handle": {"type": "String"}} + }, + "youMessagePrefix": "You: {message}", + "@youMessagePrefix": { + "description": "Chat list last-message preview prefix for own messages", + "placeholders": {"message": {"type": "String"}} + }, + "downloadTooltip": "Download", + "@downloadTooltip": {"description": "Tooltip for the file download button"}, + "fileDownloadPlaceholder": "File download wired in Phase 10+", + "@fileDownloadPlaceholder": {"description": "Placeholder snackbar for file download (not yet wired)"}, + "fileTypeVideo": "Video", + "@fileTypeVideo": {"description": "File type chip: video"}, + "fileTypeImage": "Image", + "@fileTypeImage": {"description": "File type chip: image"}, + "fileTypeArchive": "Archive", + "@fileTypeArchive": {"description": "File type chip: archive"}, + "fileTypeFile": "File", + "@fileTypeFile": {"description": "File type chip: generic file"}, + "tapToDownload": "Tap to download", + "@tapToDownload": {"description": "Hint on an encrypted image placeholder"}, + "imageDownloadPlaceholder": "Image download wired in Phase 10+", + "@imageDownloadPlaceholder": {"description": "Placeholder snackbar for image download (not yet wired)"}, + "buyingSatsAmount": "Buying {sats} sats", + "@buyingSatsAmount": { + "description": "Trade state header amount when buying", + "placeholders": {"sats": {"type": "String"}} + }, + "sellingSatsAmount": "Selling {sats} sats", + "@sellingSatsAmount": { + "description": "Trade state header amount when selling", + "placeholders": {"sats": {"type": "String"}} + }, + "viewOrderLink": "View order", + "@viewOrderLink": {"description": "Link in the trade state header to view the order"}, + "timeLeftLabel": "{time} left", + "@timeLeftLabel": { + "description": "Countdown label showing remaining time", + "placeholders": {"time": {"type": "String"}} + } } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 16ff59eb..3dbc0751 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -524,5 +524,21 @@ "peerPublicKeyLabel": "Clave pública del par", "yourSharedKeyLabel": "Tu clave compartida", "sharedKeyPlaceholder": "Disponible tras la integración del puente (Fase 10+)", - "sharedKeySafetyNote": "Guarda tu clave compartida de forma segura — es necesaria para resolver disputas" + "sharedKeySafetyNote": "Guarda tu clave compartida de forma segura — es necesaria para resolver disputas", + "attachmentLabel": "[Adjunto]", + "sellingSatsTo": "Le estás vendiendo sats a {handle}", + "buyingSatsFrom": "Le estás comprando sats a {handle}", + "youMessagePrefix": "Tú: {message}", + "downloadTooltip": "Descargar", + "fileDownloadPlaceholder": "Descarga de archivos disponible en la Fase 10+", + "fileTypeVideo": "Vídeo", + "fileTypeImage": "Imagen", + "fileTypeArchive": "Archivo comprimido", + "fileTypeFile": "Archivo", + "tapToDownload": "Toca para descargar", + "imageDownloadPlaceholder": "Descarga de imágenes disponible en la Fase 10+", + "buyingSatsAmount": "Comprando {sats} sats", + "sellingSatsAmount": "Vendiendo {sats} sats", + "viewOrderLink": "Ver orden", + "timeLeftLabel": "Quedan {time}" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 72645c55..3ff0f568 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -524,5 +524,21 @@ "peerPublicKeyLabel": "Clé publique du pair", "yourSharedKeyLabel": "Votre clé partagée", "sharedKeyPlaceholder": "Disponible après l'intégration du pont (Phase 10+)", - "sharedKeySafetyNote": "Conservez votre clé partagée en sécurité — elle est nécessaire pour la résolution des litiges" + "sharedKeySafetyNote": "Conservez votre clé partagée en sécurité — elle est nécessaire pour la résolution des litiges", + "attachmentLabel": "[Pièce jointe]", + "sellingSatsTo": "Vous vendez des sats à {handle}", + "buyingSatsFrom": "Vous achetez des sats à {handle}", + "youMessagePrefix": "Vous : {message}", + "downloadTooltip": "Télécharger", + "fileDownloadPlaceholder": "Téléchargement de fichiers disponible en Phase 10+", + "fileTypeVideo": "Vidéo", + "fileTypeImage": "Image", + "fileTypeArchive": "Archive", + "fileTypeFile": "Fichier", + "tapToDownload": "Appuyez pour télécharger", + "imageDownloadPlaceholder": "Téléchargement d'images disponible en Phase 10+", + "buyingSatsAmount": "Achat de {sats} sats", + "sellingSatsAmount": "Vente de {sats} sats", + "viewOrderLink": "Voir l'ordre", + "timeLeftLabel": "{time} restant" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 6fa3f111..20fb8d7a 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -524,5 +524,21 @@ "peerPublicKeyLabel": "Chiave pubblica del peer", "yourSharedKeyLabel": "La tua chiave condivisa", "sharedKeyPlaceholder": "Disponibile dopo l'integrazione del bridge (Fase 10+)", - "sharedKeySafetyNote": "Conserva la tua chiave condivisa al sicuro — è necessaria per la risoluzione delle dispute" + "sharedKeySafetyNote": "Conserva la tua chiave condivisa al sicuro — è necessaria per la risoluzione delle dispute", + "attachmentLabel": "[Allegato]", + "sellingSatsTo": "Stai vendendo sats a {handle}", + "buyingSatsFrom": "Stai comprando sats da {handle}", + "youMessagePrefix": "Tu: {message}", + "downloadTooltip": "Scarica", + "fileDownloadPlaceholder": "Download dei file disponibile nella Fase 10+", + "fileTypeVideo": "Video", + "fileTypeImage": "Immagine", + "fileTypeArchive": "Archivio", + "fileTypeFile": "File", + "tapToDownload": "Tocca per scaricare", + "imageDownloadPlaceholder": "Download delle immagini disponibile nella Fase 10+", + "buyingSatsAmount": "Acquisto di {sats} sats", + "sellingSatsAmount": "Vendita di {sats} sats", + "viewOrderLink": "Vedi ordine", + "timeLeftLabel": "{time} rimasti" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 97b2e04b..66b8c52c 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3103,6 +3103,102 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Keep your shared key safe — it is needed for dispute resolution'** String get sharedKeySafetyNote; + + /// Label shown on a chat bubble that has a file attachment + /// + /// In en, this message translates to: + /// **'[Attachment]'** + String get attachmentLabel; + + /// Chat list context line when the user is selling + /// + /// In en, this message translates to: + /// **'You are selling sats to {handle}'** + String sellingSatsTo(String handle); + + /// Chat list context line when the user is buying + /// + /// In en, this message translates to: + /// **'You are buying sats from {handle}'** + String buyingSatsFrom(String handle); + + /// Chat list last-message preview prefix for own messages + /// + /// In en, this message translates to: + /// **'You: {message}'** + String youMessagePrefix(String message); + + /// Tooltip for the file download button + /// + /// In en, this message translates to: + /// **'Download'** + String get downloadTooltip; + + /// Placeholder snackbar for file download (not yet wired) + /// + /// In en, this message translates to: + /// **'File download wired in Phase 10+'** + String get fileDownloadPlaceholder; + + /// File type chip: video + /// + /// In en, this message translates to: + /// **'Video'** + String get fileTypeVideo; + + /// File type chip: image + /// + /// In en, this message translates to: + /// **'Image'** + String get fileTypeImage; + + /// File type chip: archive + /// + /// In en, this message translates to: + /// **'Archive'** + String get fileTypeArchive; + + /// File type chip: generic file + /// + /// In en, this message translates to: + /// **'File'** + String get fileTypeFile; + + /// Hint on an encrypted image placeholder + /// + /// In en, this message translates to: + /// **'Tap to download'** + String get tapToDownload; + + /// Placeholder snackbar for image download (not yet wired) + /// + /// In en, this message translates to: + /// **'Image download wired in Phase 10+'** + String get imageDownloadPlaceholder; + + /// Trade state header amount when buying + /// + /// In en, this message translates to: + /// **'Buying {sats} sats'** + String buyingSatsAmount(String sats); + + /// Trade state header amount when selling + /// + /// In en, this message translates to: + /// **'Selling {sats} sats'** + String sellingSatsAmount(String sats); + + /// Link in the trade state header to view the order + /// + /// In en, this message translates to: + /// **'View order'** + String get viewOrderLink; + + /// Countdown label showing remaining time + /// + /// In en, this message translates to: + /// **'{time} left'** + String timeLeftLabel(String time); } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 6eef840a..64cc3d2e 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1698,4 +1698,64 @@ class AppLocalizationsDe extends AppLocalizations { @override String get sharedKeySafetyNote => 'Bewahre deinen gemeinsamen Schlüssel sicher auf — er wird für die Streitbeilegung benötigt'; + + @override + String get attachmentLabel => '[Anhang]'; + + @override + String sellingSatsTo(String handle) { + return 'Du verkaufst Sats an $handle'; + } + + @override + String buyingSatsFrom(String handle) { + return 'Du kaufst Sats von $handle'; + } + + @override + String youMessagePrefix(String message) { + return 'Du: $message'; + } + + @override + String get downloadTooltip => 'Herunterladen'; + + @override + String get fileDownloadPlaceholder => 'Datei-Download in Phase 10+ verfügbar'; + + @override + String get fileTypeVideo => 'Video'; + + @override + String get fileTypeImage => 'Bild'; + + @override + String get fileTypeArchive => 'Archiv'; + + @override + String get fileTypeFile => 'Datei'; + + @override + String get tapToDownload => 'Zum Herunterladen tippen'; + + @override + String get imageDownloadPlaceholder => 'Bild-Download in Phase 10+ verfügbar'; + + @override + String buyingSatsAmount(String sats) { + return 'Kaufe $sats Sats'; + } + + @override + String sellingSatsAmount(String sats) { + return 'Verkaufe $sats Sats'; + } + + @override + String get viewOrderLink => 'Bestellung ansehen'; + + @override + String timeLeftLabel(String time) { + return '$time übrig'; + } } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f12d3a70..41156b29 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1671,4 +1671,64 @@ class AppLocalizationsEn extends AppLocalizations { @override String get sharedKeySafetyNote => 'Keep your shared key safe — it is needed for dispute resolution'; + + @override + String get attachmentLabel => '[Attachment]'; + + @override + String sellingSatsTo(String handle) { + return 'You are selling sats to $handle'; + } + + @override + String buyingSatsFrom(String handle) { + return 'You are buying sats from $handle'; + } + + @override + String youMessagePrefix(String message) { + return 'You: $message'; + } + + @override + String get downloadTooltip => 'Download'; + + @override + String get fileDownloadPlaceholder => 'File download wired in Phase 10+'; + + @override + String get fileTypeVideo => 'Video'; + + @override + String get fileTypeImage => 'Image'; + + @override + String get fileTypeArchive => 'Archive'; + + @override + String get fileTypeFile => 'File'; + + @override + String get tapToDownload => 'Tap to download'; + + @override + String get imageDownloadPlaceholder => 'Image download wired in Phase 10+'; + + @override + String buyingSatsAmount(String sats) { + return 'Buying $sats sats'; + } + + @override + String sellingSatsAmount(String sats) { + return 'Selling $sats sats'; + } + + @override + String get viewOrderLink => 'View order'; + + @override + String timeLeftLabel(String time) { + return '$time left'; + } } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 6d51d502..8229585a 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1692,4 +1692,66 @@ class AppLocalizationsEs extends AppLocalizations { @override String get sharedKeySafetyNote => 'Guarda tu clave compartida de forma segura — es necesaria para resolver disputas'; + + @override + String get attachmentLabel => '[Adjunto]'; + + @override + String sellingSatsTo(String handle) { + return 'Le estás vendiendo sats a $handle'; + } + + @override + String buyingSatsFrom(String handle) { + return 'Le estás comprando sats a $handle'; + } + + @override + String youMessagePrefix(String message) { + return 'Tú: $message'; + } + + @override + String get downloadTooltip => 'Descargar'; + + @override + String get fileDownloadPlaceholder => + 'Descarga de archivos disponible en la Fase 10+'; + + @override + String get fileTypeVideo => 'Vídeo'; + + @override + String get fileTypeImage => 'Imagen'; + + @override + String get fileTypeArchive => 'Archivo comprimido'; + + @override + String get fileTypeFile => 'Archivo'; + + @override + String get tapToDownload => 'Toca para descargar'; + + @override + String get imageDownloadPlaceholder => + 'Descarga de imágenes disponible en la Fase 10+'; + + @override + String buyingSatsAmount(String sats) { + return 'Comprando $sats sats'; + } + + @override + String sellingSatsAmount(String sats) { + return 'Vendiendo $sats sats'; + } + + @override + String get viewOrderLink => 'Ver orden'; + + @override + String timeLeftLabel(String time) { + return 'Quedan $time'; + } } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 38c8f2ed..e403b0a8 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1699,4 +1699,66 @@ class AppLocalizationsFr extends AppLocalizations { @override String get sharedKeySafetyNote => 'Conservez votre clé partagée en sécurité — elle est nécessaire pour la résolution des litiges'; + + @override + String get attachmentLabel => '[Pièce jointe]'; + + @override + String sellingSatsTo(String handle) { + return 'Vous vendez des sats à $handle'; + } + + @override + String buyingSatsFrom(String handle) { + return 'Vous achetez des sats à $handle'; + } + + @override + String youMessagePrefix(String message) { + return 'Vous : $message'; + } + + @override + String get downloadTooltip => 'Télécharger'; + + @override + String get fileDownloadPlaceholder => + 'Téléchargement de fichiers disponible en Phase 10+'; + + @override + String get fileTypeVideo => 'Vidéo'; + + @override + String get fileTypeImage => 'Image'; + + @override + String get fileTypeArchive => 'Archive'; + + @override + String get fileTypeFile => 'Fichier'; + + @override + String get tapToDownload => 'Appuyez pour télécharger'; + + @override + String get imageDownloadPlaceholder => + 'Téléchargement d\'images disponible en Phase 10+'; + + @override + String buyingSatsAmount(String sats) { + return 'Achat de $sats sats'; + } + + @override + String sellingSatsAmount(String sats) { + return 'Vente de $sats sats'; + } + + @override + String get viewOrderLink => 'Voir l\'ordre'; + + @override + String timeLeftLabel(String time) { + return '$time restant'; + } } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 19187aba..2cf3ddd8 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1689,4 +1689,66 @@ class AppLocalizationsIt extends AppLocalizations { @override String get sharedKeySafetyNote => 'Conserva la tua chiave condivisa al sicuro — è necessaria per la risoluzione delle dispute'; + + @override + String get attachmentLabel => '[Allegato]'; + + @override + String sellingSatsTo(String handle) { + return 'Stai vendendo sats a $handle'; + } + + @override + String buyingSatsFrom(String handle) { + return 'Stai comprando sats da $handle'; + } + + @override + String youMessagePrefix(String message) { + return 'Tu: $message'; + } + + @override + String get downloadTooltip => 'Scarica'; + + @override + String get fileDownloadPlaceholder => + 'Download dei file disponibile nella Fase 10+'; + + @override + String get fileTypeVideo => 'Video'; + + @override + String get fileTypeImage => 'Immagine'; + + @override + String get fileTypeArchive => 'Archivio'; + + @override + String get fileTypeFile => 'File'; + + @override + String get tapToDownload => 'Tocca per scaricare'; + + @override + String get imageDownloadPlaceholder => + 'Download delle immagini disponibile nella Fase 10+'; + + @override + String buyingSatsAmount(String sats) { + return 'Acquisto di $sats sats'; + } + + @override + String sellingSatsAmount(String sats) { + return 'Vendita di $sats sats'; + } + + @override + String get viewOrderLink => 'Vedi ordine'; + + @override + String timeLeftLabel(String time) { + return '$time rimasti'; + } } From 43e795d6248f530361ed17564126fb214c689477 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 02:22:20 -0300 Subject: [PATCH 12/30] feat: localize order screens strings Replace hardcoded English text in add_lightning_invoice_screen.dart, add_order_screen.dart, and take_order_screen.dart with localized strings from AppLocalizations. --- .../screens/add_lightning_invoice_screen.dart | 21 +- .../order/screens/add_order_screen.dart | 67 +++--- .../order/screens/take_order_screen.dart | 45 ++-- lib/l10n/app_de.arb | 35 +++- lib/l10n/app_en.arb | 84 ++++++++ lib/l10n/app_es.arb | 35 +++- lib/l10n/app_fr.arb | 35 +++- lib/l10n/app_it.arb | 35 +++- lib/l10n/app_localizations.dart | 198 ++++++++++++++++++ lib/l10n/app_localizations_de.dart | 116 ++++++++++ lib/l10n/app_localizations_en.dart | 114 ++++++++++ lib/l10n/app_localizations_es.dart | 115 ++++++++++ lib/l10n/app_localizations_fr.dart | 117 +++++++++++ lib/l10n/app_localizations_it.dart | 116 ++++++++++ 14 files changed, 1056 insertions(+), 77 deletions(-) diff --git a/lib/features/order/screens/add_lightning_invoice_screen.dart b/lib/features/order/screens/add_lightning_invoice_screen.dart index 5663a971..c9ebd1ce 100644 --- a/lib/features/order/screens/add_lightning_invoice_screen.dart +++ b/lib/features/order/screens/add_lightning_invoice_screen.dart @@ -69,7 +69,7 @@ class _AddLightningInvoiceScreenState final resolvedSats = _resolvedSats(ref); if (_isLnAddress(input) && resolvedSats == null) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Waiting for trade amount — please try again shortly.')), + SnackBar(content: Text(AppLocalizations.of(context).waitingForTradeAmount)), ); return; } @@ -111,6 +111,7 @@ class _AddLightningInvoiceScreenState final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230); final inputBg = colors?.backgroundInput ?? const Color(0xFF252A3A); + final l10n = AppLocalizations.of(context); final isWalletConnected = ref.watch(isWalletConnectedProvider); @@ -121,7 +122,7 @@ class _AddLightningInvoiceScreenState // Show a loading indicator only in that case. Manual entry is always available. if (isWalletConnected && sats == null && !_manualMode) { return Scaffold( - appBar: AppBar(title: const Text('Add Invoice')), + appBar: AppBar(title: Text(l10n.addInvoiceTitle)), body: Center( child: Column( mainAxisSize: MainAxisSize.min, @@ -129,13 +130,13 @@ class _AddLightningInvoiceScreenState const CircularProgressIndicator(), const SizedBox(height: AppSpacing.md), Text( - 'Fetching trade amount…', + l10n.fetchingTradeAmount, style: TextStyle(color: Theme.of(context).extension()?.textSecondary), ), const SizedBox(height: AppSpacing.md), TextButton( onPressed: () => setState(() => _manualMode = true), - child: const Text('Enter invoice manually'), + child: Text(l10n.enterInvoiceManually), ), ], ), @@ -147,7 +148,7 @@ class _AddLightningInvoiceScreenState // to manual, show the auto-invoice widget instead of the manual form. if (isWalletConnected && !_manualMode && sats != null && sats > BigInt.zero) { return Scaffold( - appBar: AppBar(title: const Text('Add Invoice')), + appBar: AppBar(title: Text(l10n.addInvoiceTitle)), body: Padding( padding: const EdgeInsets.all(AppSpacing.lg), child: Center( @@ -165,7 +166,7 @@ class _AddLightningInvoiceScreenState } return Scaffold( - appBar: AppBar(title: const Text('Add Invoice')), + appBar: AppBar(title: Text(l10n.addInvoiceTitle)), body: Padding( padding: const EdgeInsets.all(AppSpacing.lg), child: Column( @@ -187,7 +188,7 @@ class _AddLightningInvoiceScreenState const SizedBox(width: AppSpacing.sm), Expanded( child: Text( - 'Enter a Lightning Invoice to receive your sats', + l10n.enterLightningInvoiceInstruction, style: theme.textTheme.bodyMedium, ), ), @@ -217,7 +218,7 @@ class _AddLightningInvoiceScreenState decoration: InputDecoration( hintText: 'lnbc...', floatingLabelBehavior: FloatingLabelBehavior.auto, - labelText: 'Lightning Invoice', + labelText: l10n.lightningInvoiceLabel, filled: true, fillColor: inputBg, border: OutlineInputBorder( @@ -242,7 +243,7 @@ class _AddLightningInvoiceScreenState child: TextButton( onPressed: () => context.pop(), child: Text( - 'Cancel', + l10n.cancel, style: TextStyle(color: colors?.textSecondary), ), ), @@ -266,7 +267,7 @@ class _AddLightningInvoiceScreenState height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) - : const Text('Submit'), + : Text(l10n.submitButton), ), ), ], diff --git a/lib/features/order/screens/add_order_screen.dart b/lib/features/order/screens/add_order_screen.dart index c6645aa0..20d573b6 100644 --- a/lib/features/order/screens/add_order_screen.dart +++ b/lib/features/order/screens/add_order_screen.dart @@ -227,9 +227,10 @@ class _AddOrderScreenState extends ConsumerState { final fiatCode = ref.watch(selectedFiatCodeProvider); final premium = ref.watch(premiumValueProvider); final isValid = _checkValid(selectedMethods, customMethod, isMarket, fixedSatsStr); + final l10n = AppLocalizations.of(context); return Scaffold( - appBar: AppBar(title: const Text('CREATING NEW ORDER')), + appBar: AppBar(title: Text(l10n.creatingNewOrderTitle)), body: ListView( padding: const EdgeInsets.all(AppSpacing.lg), children: [ @@ -244,7 +245,7 @@ class _AddOrderScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'You want to ${_isBuy ? 'buy' : 'sell'} Bitcoin', + _isBuy ? l10n.youWantToBuyBitcoin : l10n.youWantToSellBitcoin, style: theme.textTheme.headlineSmall, ), const SizedBox(height: AppSpacing.md), @@ -253,7 +254,7 @@ class _AddOrderScreenState extends ConsumerState { Row( children: [ Text( - 'Range order', + l10n.rangeOrderLabel, style: TextStyle( color: colors?.textSecondary, fontSize: 13, @@ -278,7 +279,7 @@ class _AddOrderScreenState extends ConsumerState { controller: _minController, keyboardType: TextInputType.number, decoration: InputDecoration( - hintText: 'Min', + hintText: l10n.minHint, filled: true, fillColor: inputBg, border: OutlineInputBorder( @@ -296,7 +297,7 @@ class _AddOrderScreenState extends ConsumerState { controller: _maxController, keyboardType: TextInputType.number, decoration: InputDecoration( - hintText: 'Max', + hintText: l10n.maxHint, filled: true, fillColor: inputBg, border: OutlineInputBorder( @@ -315,7 +316,7 @@ class _AddOrderScreenState extends ConsumerState { controller: _amountController, keyboardType: TextInputType.number, decoration: InputDecoration( - hintText: 'Fiat amount', + hintText: l10n.fiatAmountHint, filled: true, fillColor: inputBg, border: OutlineInputBorder( @@ -384,7 +385,7 @@ class _AddOrderScreenState extends ConsumerState { borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text('Cancel'), + child: Text(l10n.cancel), ), ), const SizedBox(width: AppSpacing.md), @@ -407,7 +408,7 @@ class _AddOrderScreenState extends ConsumerState { child: CircularProgressIndicator(strokeWidth: 2), ) - : const Text('Submit'), + : Text(l10n.submitButton), ), ), ], @@ -432,9 +433,9 @@ class _AddOrderScreenState extends ConsumerState { required String fiatCode, required double premium, }) { - final textPrimary = colors?.textPrimary ?? Colors.white; final secondary = colors?.textSecondary ?? Colors.grey; final subtle = colors?.textSubtle ?? Colors.grey; + final l10n = AppLocalizations.of(context); // Fiat side, mirroring _checkValid's rules. String? amountStr; @@ -461,44 +462,28 @@ class _AddOrderScreenState extends ConsumerState { Widget body; if (amountStr == null) { body = Text( - 'Enter an amount to see a live preview.', + l10n.enterAmountForPreview, style: TextStyle(fontSize: 13, color: subtle), ); } else { - final bold = TextStyle( - fontSize: 13, - fontWeight: FontWeight.w700, - color: textPrimary, - ); - final spans = []; + final String sentence; if (sats != null) { - spans - ..add(TextSpan(text: _isBuy ? 'You receive ' : 'You sell ')) - ..add(TextSpan( - text: '${_groupDigits(sats.toString())} sats', - style: bold, - )) - ..add(const TextSpan(text: ' for ')) - ..add(TextSpan(text: amountStr, style: bold)); + final satsStr = _groupDigits(sats.toString()); + sentence = _isBuy + ? l10n.previewReceiveFixed(satsStr, amountStr) + : l10n.previewSellFixed(satsStr, amountStr); } else { final priceLabel = premium == 0 - ? 'market price' - : 'market ${premium > 0 ? '+' : ''}${_formatNum(premium)}%'; - spans - ..add(TextSpan(text: _isBuy ? 'You buy BTC for ' : 'You sell BTC for ')) - ..add(TextSpan(text: amountStr, style: bold)) - ..add(const TextSpan(text: ' at ')) - ..add(TextSpan(text: priceLabel, style: bold)); + ? l10n.marketPriceLabel + : l10n.marketPricePremium( + '${premium > 0 ? '+' : ''}${_formatNum(premium)}'); + sentence = _isBuy + ? l10n.previewBuyMarket(amountStr, priceLabel) + : l10n.previewSellMarket(amountStr, priceLabel); } - spans - ..add(const TextSpan(text: ' · live for ')) - ..add(TextSpan(text: '24 h', style: bold)); - - body = Text.rich( - TextSpan( - style: TextStyle(fontSize: 13, height: 1.5, color: secondary), - children: spans, - ), + body = Text( + sentence, + style: TextStyle(fontSize: 13, height: 1.5, color: secondary), ); } @@ -514,7 +499,7 @@ class _AddOrderScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'PREVIEW', + l10n.previewLabel, style: TextStyle(fontSize: 11, letterSpacing: 1, color: subtle), ), const SizedBox(height: AppSpacing.xs), diff --git a/lib/features/order/screens/take_order_screen.dart b/lib/features/order/screens/take_order_screen.dart index 397a28fb..7e3f6225 100644 --- a/lib/features/order/screens/take_order_screen.dart +++ b/lib/features/order/screens/take_order_screen.dart @@ -186,17 +186,18 @@ class _TakeOrderScreenState extends ConsumerState { final textSec = colors?.textSecondary ?? const Color(0xFFB0B3C6); final flags = ref.watch(currencyFlagsProvider); final privacyMode = ref.watch(privacyModeProvider); + final l10n = AppLocalizations.of(context); if (order == null) { return Scaffold( - appBar: AppBar(title: const Text('Order Not Found')), - body: const Center(child: Text('This order is no longer available.')), + appBar: AppBar(title: Text(l10n.orderNotFoundTitle)), + body: Center(child: Text(l10n.orderNotFoundMessage)), ); } final flag = flags[order.fiatCode] ?? ''; - final title = widget.isBuying ? 'SELL ORDER DETAILS' : 'BUY ORDER DETAILS'; - final actionLabel = widget.isBuying ? 'BUY THESE SATS' : 'SELL SATS'; + final title = widget.isBuying ? l10n.sellOrderDetailsTitle : l10n.buyOrderDetailsTitle; + final actionLabel = widget.isBuying ? l10n.buyTheseSatsButton : l10n.sellSatsButton; final premiumPositive = order.premium >= 0; return Scaffold( @@ -212,14 +213,14 @@ class _TakeOrderScreenState extends ConsumerState { children: [ Text( widget.isBuying - ? 'Someone is selling sats' - : 'Someone is buying sats', + ? l10n.someoneSellingSats + : l10n.someoneBuyingSats, style: theme.textTheme.headlineMedium, ), const SizedBox(height: AppSpacing.xs), Text.rich( TextSpan( - text: 'for ', + text: l10n.takeOrderForPrefix, style: TextStyle(color: textSec, fontSize: 14), children: [ TextSpan( @@ -229,13 +230,14 @@ class _TakeOrderScreenState extends ConsumerState { fontWeight: FontWeight.w600, ), ), - const TextSpan(text: ' at market price'), + TextSpan(text: l10n.takeOrderAtMarketPrice), ], ), ), const SizedBox(height: AppSpacing.xs), Text( - 'Premium: ${premiumPositive ? '+' : ''}${order.premium.toStringAsFixed(1)}%', + l10n.premiumLabel( + '${premiumPositive ? '+' : ''}${order.premium.toStringAsFixed(1)}'), style: TextStyle( color: premiumPositive ? green : colors?.sellColor, fontSize: 13, @@ -299,14 +301,14 @@ class _TakeOrderScreenState extends ConsumerState { onPressed: () { Clipboard.setData(ClipboardData(text: order.id)); ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Order ID copied'), - duration: Duration(seconds: 1), + SnackBar( + content: Text(l10n.orderIdCopied), + duration: const Duration(seconds: 1), ), ); }, icon: const Icon(Icons.copy, size: 18), - tooltip: 'Copy order ID', + tooltip: l10n.copyOrderIdTooltip, padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), @@ -325,7 +327,7 @@ class _TakeOrderScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Creator reputation', + l10n.creatorReputation, style: TextStyle(color: textSec, fontSize: 12), ), const SizedBox(height: AppSpacing.md), @@ -334,7 +336,7 @@ class _TakeOrderScreenState extends ConsumerState { Expanded( child: _ReputationStat( value: order.rating.toStringAsFixed(1), - label: 'rating', + label: l10n.ratingStatLabel, icon: Icons.star, iconColor: Colors.amber, ), @@ -342,13 +344,13 @@ class _TakeOrderScreenState extends ConsumerState { Expanded( child: _ReputationStat( value: '${order.tradeCount}', - label: 'trades', + label: l10n.tradesStatLabel, ), ), Expanded( child: _ReputationStat( value: '${order.daysActive}', - label: 'days active', + label: l10n.daysActiveStatLabel, ), ), ], @@ -407,7 +409,7 @@ class _TakeOrderScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'TIME TO TAKE THIS ORDER', + l10n.timeToTakeOrder, style: TextStyle( color: green, fontSize: 11, @@ -418,8 +420,7 @@ class _TakeOrderScreenState extends ConsumerState { const SizedBox(height: AppSpacing.xs), Text.rich( TextSpan( - text: 'If it expires, the order is removed ' - 'from the book. ', + text: l10n.orderExpiryRemovedNote, style: TextStyle( color: textSec, fontSize: 12, @@ -427,7 +428,7 @@ class _TakeOrderScreenState extends ConsumerState { ), children: [ TextSpan( - text: "It won't affect your reputation.", + text: l10n.orderExpiryNoReputationNote, style: TextStyle( color: green, fontWeight: FontWeight.w600, @@ -467,7 +468,7 @@ class _TakeOrderScreenState extends ConsumerState { borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text('CLOSE'), + child: Text(l10n.closeRatingButton), ), ), const SizedBox(width: AppSpacing.md), diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 0b78ceaf..26fe9e21 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -540,5 +540,38 @@ "buyingSatsAmount": "Kaufe {sats} Sats", "sellingSatsAmount": "Verkaufe {sats} Sats", "viewOrderLink": "Bestellung ansehen", - "timeLeftLabel": "{time} übrig" + "timeLeftLabel": "{time} übrig", + "waitingForTradeAmount": "Warte auf den Trade-Betrag — bitte versuche es gleich noch einmal.", + "fetchingTradeAmount": "Trade-Betrag wird abgerufen…", + "enterInvoiceManually": "Rechnung manuell eingeben", + "enterLightningInvoiceInstruction": "Gib eine Lightning-Rechnung ein, um deine Sats zu erhalten", + "lightningInvoiceLabel": "Lightning-Rechnung", + "submitButton": "Absenden", + "sellOrderDetailsTitle": "DETAILS DER VERKAUFSORDER", + "buyOrderDetailsTitle": "DETAILS DER KAUFORDER", + "buyTheseSatsButton": "DIESE SATS KAUFEN", + "sellSatsButton": "SATS VERKAUFEN", + "someoneSellingSats": "Jemand verkauft Sats", + "someoneBuyingSats": "Jemand kauft Sats", + "takeOrderForPrefix": "für ", + "takeOrderAtMarketPrice": " zum Marktpreis", + "premiumLabel": "Aufschlag: {premium}%", + "creatorReputation": "Reputation des Erstellers", + "ratingStatLabel": "Bewertung", + "tradesStatLabel": "Trades", + "daysActiveStatLabel": "Tage aktiv", + "timeToTakeOrder": "ZEIT, DIESE ORDER ANZUNEHMEN", + "orderExpiryRemovedNote": "Bei Ablauf wird die Order aus dem Buch entfernt. ", + "orderExpiryNoReputationNote": "Es wirkt sich nicht auf deine Reputation aus.", + "minHint": "Min", + "maxHint": "Max", + "fiatAmountHint": "Fiat-Betrag", + "enterAmountForPreview": "Gib einen Betrag ein, um eine Live-Vorschau zu sehen.", + "previewLabel": "VORSCHAU", + "previewBuyMarket": "Du kaufst BTC für {amount} zu {price} · 24 h aktiv", + "previewSellMarket": "Du verkaufst BTC für {amount} zu {price} · 24 h aktiv", + "previewReceiveFixed": "Du erhältst {sats} Sats für {amount} · 24 h aktiv", + "previewSellFixed": "Du verkaufst {sats} Sats für {amount} · 24 h aktiv", + "marketPriceLabel": "Marktpreis", + "marketPricePremium": "Markt {premium}%" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7e216c42..9f0085fd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1159,5 +1159,89 @@ "@timeLeftLabel": { "description": "Countdown label showing remaining time", "placeholders": {"time": {"type": "String"}} + }, + "waitingForTradeAmount": "Waiting for trade amount — please try again shortly.", + "@waitingForTradeAmount": {"description": "Snackbar when submitting an LN address before the sats amount is known"}, + "fetchingTradeAmount": "Fetching trade amount…", + "@fetchingTradeAmount": {"description": "Loading text while resolving the trade amount"}, + "enterInvoiceManually": "Enter invoice manually", + "@enterInvoiceManually": {"description": "Button to switch to manual invoice entry"}, + "enterLightningInvoiceInstruction": "Enter a Lightning Invoice to receive your sats", + "@enterLightningInvoiceInstruction": {"description": "Instruction on the add invoice screen"}, + "lightningInvoiceLabel": "Lightning Invoice", + "@lightningInvoiceLabel": {"description": "Field label for the Lightning invoice input"}, + "submitButton": "Submit", + "@submitButton": {"description": "Generic submit button label"}, + "sellOrderDetailsTitle": "SELL ORDER DETAILS", + "@sellOrderDetailsTitle": {"description": "App bar title when taking a sell order"}, + "buyOrderDetailsTitle": "BUY ORDER DETAILS", + "@buyOrderDetailsTitle": {"description": "App bar title when taking a buy order"}, + "buyTheseSatsButton": "BUY THESE SATS", + "@buyTheseSatsButton": {"description": "Primary button to buy sats"}, + "sellSatsButton": "SELL SATS", + "@sellSatsButton": {"description": "Primary button to sell sats"}, + "someoneSellingSats": "Someone is selling sats", + "@someoneSellingSats": {"description": "Headline when a counterpart is selling sats"}, + "someoneBuyingSats": "Someone is buying sats", + "@someoneBuyingSats": {"description": "Headline when a counterpart is buying sats"}, + "takeOrderForPrefix": "for ", + "@takeOrderForPrefix": {"description": "Prefix before the order amount on the take order screen"}, + "takeOrderAtMarketPrice": " at market price", + "@takeOrderAtMarketPrice": {"description": "Suffix after the order amount indicating market price"}, + "premiumLabel": "Premium: {premium}%", + "@premiumLabel": { + "description": "Premium percentage label", + "placeholders": {"premium": {"type": "String"}} + }, + "creatorReputation": "Creator reputation", + "@creatorReputation": {"description": "Label for the creator reputation card"}, + "ratingStatLabel": "rating", + "@ratingStatLabel": {"description": "Reputation stat label: rating"}, + "tradesStatLabel": "trades", + "@tradesStatLabel": {"description": "Reputation stat label: trades"}, + "daysActiveStatLabel": "days active", + "@daysActiveStatLabel": {"description": "Reputation stat label: days active"}, + "timeToTakeOrder": "TIME TO TAKE THIS ORDER", + "@timeToTakeOrder": {"description": "Countdown label on the take order screen"}, + "orderExpiryRemovedNote": "If it expires, the order is removed from the book. ", + "@orderExpiryRemovedNote": {"description": "Consequence note prefix if the order expires"}, + "orderExpiryNoReputationNote": "It won't affect your reputation.", + "@orderExpiryNoReputationNote": {"description": "Highlighted consequence note about reputation"}, + "minHint": "Min", + "@minHint": {"description": "Hint for the minimum amount input"}, + "maxHint": "Max", + "@maxHint": {"description": "Hint for the maximum amount input"}, + "fiatAmountHint": "Fiat amount", + "@fiatAmountHint": {"description": "Hint for the fiat amount input"}, + "enterAmountForPreview": "Enter an amount to see a live preview.", + "@enterAmountForPreview": {"description": "Preview footer text before an amount is entered"}, + "previewLabel": "PREVIEW", + "@previewLabel": {"description": "Label of the order preview footer"}, + "previewBuyMarket": "You buy BTC for {amount} at {price} · live for 24 h", + "@previewBuyMarket": { + "description": "Preview sentence for a market buy order", + "placeholders": {"amount": {"type": "String"}, "price": {"type": "String"}} + }, + "previewSellMarket": "You sell BTC for {amount} at {price} · live for 24 h", + "@previewSellMarket": { + "description": "Preview sentence for a market sell order", + "placeholders": {"amount": {"type": "String"}, "price": {"type": "String"}} + }, + "previewReceiveFixed": "You receive {sats} sats for {amount} · live for 24 h", + "@previewReceiveFixed": { + "description": "Preview sentence for a fixed-price buy order", + "placeholders": {"sats": {"type": "String"}, "amount": {"type": "String"}} + }, + "previewSellFixed": "You sell {sats} sats for {amount} · live for 24 h", + "@previewSellFixed": { + "description": "Preview sentence for a fixed-price sell order", + "placeholders": {"sats": {"type": "String"}, "amount": {"type": "String"}} + }, + "marketPriceLabel": "market price", + "@marketPriceLabel": {"description": "Price label for market price with no premium"}, + "marketPricePremium": "market {premium}%", + "@marketPricePremium": { + "description": "Price label for market price with a premium", + "placeholders": {"premium": {"type": "String"}} } } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 3dbc0751..0961a27e 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -540,5 +540,38 @@ "buyingSatsAmount": "Comprando {sats} sats", "sellingSatsAmount": "Vendiendo {sats} sats", "viewOrderLink": "Ver orden", - "timeLeftLabel": "Quedan {time}" + "timeLeftLabel": "Quedan {time}", + "waitingForTradeAmount": "Esperando el monto de la operación — inténtalo de nuevo en un momento.", + "fetchingTradeAmount": "Obteniendo el monto de la operación…", + "enterInvoiceManually": "Ingresar factura manualmente", + "enterLightningInvoiceInstruction": "Ingresa una factura Lightning para recibir tus sats", + "lightningInvoiceLabel": "Factura Lightning", + "submitButton": "Enviar", + "sellOrderDetailsTitle": "DETALLES DE ORDEN DE VENTA", + "buyOrderDetailsTitle": "DETALLES DE ORDEN DE COMPRA", + "buyTheseSatsButton": "COMPRAR ESTOS SATS", + "sellSatsButton": "VENDER SATS", + "someoneSellingSats": "Alguien está vendiendo sats", + "someoneBuyingSats": "Alguien está comprando sats", + "takeOrderForPrefix": "por ", + "takeOrderAtMarketPrice": " a precio de mercado", + "premiumLabel": "Prima: {premium}%", + "creatorReputation": "Reputación del creador", + "ratingStatLabel": "calificación", + "tradesStatLabel": "operaciones", + "daysActiveStatLabel": "días activo", + "timeToTakeOrder": "TIEMPO PARA TOMAR ESTA ORDEN", + "orderExpiryRemovedNote": "Si expira, la orden se elimina del libro. ", + "orderExpiryNoReputationNote": "No afectará tu reputación.", + "minHint": "Mín", + "maxHint": "Máx", + "fiatAmountHint": "Monto fiat", + "enterAmountForPreview": "Ingresa un monto para ver una vista previa en vivo.", + "previewLabel": "VISTA PREVIA", + "previewBuyMarket": "Compras BTC por {amount} a {price} · activa por 24 h", + "previewSellMarket": "Vendes BTC por {amount} a {price} · activa por 24 h", + "previewReceiveFixed": "Recibes {sats} sats por {amount} · activa por 24 h", + "previewSellFixed": "Vendes {sats} sats por {amount} · activa por 24 h", + "marketPriceLabel": "precio de mercado", + "marketPricePremium": "mercado {premium}%" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 3ff0f568..026b68ef 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -540,5 +540,38 @@ "buyingSatsAmount": "Achat de {sats} sats", "sellingSatsAmount": "Vente de {sats} sats", "viewOrderLink": "Voir l'ordre", - "timeLeftLabel": "{time} restant" + "timeLeftLabel": "{time} restant", + "waitingForTradeAmount": "En attente du montant de la transaction — veuillez réessayer sous peu.", + "fetchingTradeAmount": "Récupération du montant de la transaction…", + "enterInvoiceManually": "Saisir la facture manuellement", + "enterLightningInvoiceInstruction": "Saisissez une facture Lightning pour recevoir vos sats", + "lightningInvoiceLabel": "Facture Lightning", + "submitButton": "Envoyer", + "sellOrderDetailsTitle": "DÉTAILS DE L'ORDRE DE VENTE", + "buyOrderDetailsTitle": "DÉTAILS DE L'ORDRE D'ACHAT", + "buyTheseSatsButton": "ACHETER CES SATS", + "sellSatsButton": "VENDRE DES SATS", + "someoneSellingSats": "Quelqu'un vend des sats", + "someoneBuyingSats": "Quelqu'un achète des sats", + "takeOrderForPrefix": "pour ", + "takeOrderAtMarketPrice": " au prix du marché", + "premiumLabel": "Prime : {premium}%", + "creatorReputation": "Réputation du créateur", + "ratingStatLabel": "note", + "tradesStatLabel": "transactions", + "daysActiveStatLabel": "jours actifs", + "timeToTakeOrder": "TEMPS POUR PRENDRE CET ORDRE", + "orderExpiryRemovedNote": "S'il expire, l'ordre est retiré du carnet. ", + "orderExpiryNoReputationNote": "Cela n'affectera pas votre réputation.", + "minHint": "Min", + "maxHint": "Max", + "fiatAmountHint": "Montant fiat", + "enterAmountForPreview": "Saisissez un montant pour voir un aperçu en direct.", + "previewLabel": "APERÇU", + "previewBuyMarket": "Vous achetez des BTC pour {amount} à {price} · actif pendant 24 h", + "previewSellMarket": "Vous vendez des BTC pour {amount} à {price} · actif pendant 24 h", + "previewReceiveFixed": "Vous recevez {sats} sats pour {amount} · actif pendant 24 h", + "previewSellFixed": "Vous vendez {sats} sats pour {amount} · actif pendant 24 h", + "marketPriceLabel": "prix du marché", + "marketPricePremium": "marché {premium}%" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 20fb8d7a..7bd657e8 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -540,5 +540,38 @@ "buyingSatsAmount": "Acquisto di {sats} sats", "sellingSatsAmount": "Vendita di {sats} sats", "viewOrderLink": "Vedi ordine", - "timeLeftLabel": "{time} rimasti" + "timeLeftLabel": "{time} rimasti", + "waitingForTradeAmount": "In attesa dell'importo dell'operazione — riprova tra poco.", + "fetchingTradeAmount": "Recupero dell'importo dell'operazione…", + "enterInvoiceManually": "Inserisci la fattura manualmente", + "enterLightningInvoiceInstruction": "Inserisci una fattura Lightning per ricevere i tuoi sats", + "lightningInvoiceLabel": "Fattura Lightning", + "submitButton": "Invia", + "sellOrderDetailsTitle": "DETTAGLI ORDINE DI VENDITA", + "buyOrderDetailsTitle": "DETTAGLI ORDINE DI ACQUISTO", + "buyTheseSatsButton": "COMPRA QUESTI SATS", + "sellSatsButton": "VENDI SATS", + "someoneSellingSats": "Qualcuno sta vendendo sats", + "someoneBuyingSats": "Qualcuno sta comprando sats", + "takeOrderForPrefix": "per ", + "takeOrderAtMarketPrice": " al prezzo di mercato", + "premiumLabel": "Premio: {premium}%", + "creatorReputation": "Reputazione del creatore", + "ratingStatLabel": "valutazione", + "tradesStatLabel": "operazioni", + "daysActiveStatLabel": "giorni attivo", + "timeToTakeOrder": "TEMPO PER PRENDERE QUEST'ORDINE", + "orderExpiryRemovedNote": "Se scade, l'ordine viene rimosso dal book. ", + "orderExpiryNoReputationNote": "Non influirà sulla tua reputazione.", + "minHint": "Min", + "maxHint": "Max", + "fiatAmountHint": "Importo fiat", + "enterAmountForPreview": "Inserisci un importo per vedere un'anteprima in tempo reale.", + "previewLabel": "ANTEPRIMA", + "previewBuyMarket": "Compri BTC per {amount} a {price} · attivo per 24 h", + "previewSellMarket": "Vendi BTC per {amount} a {price} · attivo per 24 h", + "previewReceiveFixed": "Ricevi {sats} sats per {amount} · attivo per 24 h", + "previewSellFixed": "Vendi {sats} sats per {amount} · attivo per 24 h", + "marketPriceLabel": "prezzo di mercato", + "marketPricePremium": "mercato {premium}%" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 66b8c52c..32cd3d6c 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3199,6 +3199,204 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'{time} left'** String timeLeftLabel(String time); + + /// Snackbar when submitting an LN address before the sats amount is known + /// + /// In en, this message translates to: + /// **'Waiting for trade amount — please try again shortly.'** + String get waitingForTradeAmount; + + /// Loading text while resolving the trade amount + /// + /// In en, this message translates to: + /// **'Fetching trade amount…'** + String get fetchingTradeAmount; + + /// Button to switch to manual invoice entry + /// + /// In en, this message translates to: + /// **'Enter invoice manually'** + String get enterInvoiceManually; + + /// Instruction on the add invoice screen + /// + /// In en, this message translates to: + /// **'Enter a Lightning Invoice to receive your sats'** + String get enterLightningInvoiceInstruction; + + /// Field label for the Lightning invoice input + /// + /// In en, this message translates to: + /// **'Lightning Invoice'** + String get lightningInvoiceLabel; + + /// Generic submit button label + /// + /// In en, this message translates to: + /// **'Submit'** + String get submitButton; + + /// App bar title when taking a sell order + /// + /// In en, this message translates to: + /// **'SELL ORDER DETAILS'** + String get sellOrderDetailsTitle; + + /// App bar title when taking a buy order + /// + /// In en, this message translates to: + /// **'BUY ORDER DETAILS'** + String get buyOrderDetailsTitle; + + /// Primary button to buy sats + /// + /// In en, this message translates to: + /// **'BUY THESE SATS'** + String get buyTheseSatsButton; + + /// Primary button to sell sats + /// + /// In en, this message translates to: + /// **'SELL SATS'** + String get sellSatsButton; + + /// Headline when a counterpart is selling sats + /// + /// In en, this message translates to: + /// **'Someone is selling sats'** + String get someoneSellingSats; + + /// Headline when a counterpart is buying sats + /// + /// In en, this message translates to: + /// **'Someone is buying sats'** + String get someoneBuyingSats; + + /// Prefix before the order amount on the take order screen + /// + /// In en, this message translates to: + /// **'for '** + String get takeOrderForPrefix; + + /// Suffix after the order amount indicating market price + /// + /// In en, this message translates to: + /// **' at market price'** + String get takeOrderAtMarketPrice; + + /// Premium percentage label + /// + /// In en, this message translates to: + /// **'Premium: {premium}%'** + String premiumLabel(String premium); + + /// Label for the creator reputation card + /// + /// In en, this message translates to: + /// **'Creator reputation'** + String get creatorReputation; + + /// Reputation stat label: rating + /// + /// In en, this message translates to: + /// **'rating'** + String get ratingStatLabel; + + /// Reputation stat label: trades + /// + /// In en, this message translates to: + /// **'trades'** + String get tradesStatLabel; + + /// Reputation stat label: days active + /// + /// In en, this message translates to: + /// **'days active'** + String get daysActiveStatLabel; + + /// Countdown label on the take order screen + /// + /// In en, this message translates to: + /// **'TIME TO TAKE THIS ORDER'** + String get timeToTakeOrder; + + /// Consequence note prefix if the order expires + /// + /// In en, this message translates to: + /// **'If it expires, the order is removed from the book. '** + String get orderExpiryRemovedNote; + + /// Highlighted consequence note about reputation + /// + /// In en, this message translates to: + /// **'It won\'t affect your reputation.'** + String get orderExpiryNoReputationNote; + + /// Hint for the minimum amount input + /// + /// In en, this message translates to: + /// **'Min'** + String get minHint; + + /// Hint for the maximum amount input + /// + /// In en, this message translates to: + /// **'Max'** + String get maxHint; + + /// Hint for the fiat amount input + /// + /// In en, this message translates to: + /// **'Fiat amount'** + String get fiatAmountHint; + + /// Preview footer text before an amount is entered + /// + /// In en, this message translates to: + /// **'Enter an amount to see a live preview.'** + String get enterAmountForPreview; + + /// Label of the order preview footer + /// + /// In en, this message translates to: + /// **'PREVIEW'** + String get previewLabel; + + /// Preview sentence for a market buy order + /// + /// In en, this message translates to: + /// **'You buy BTC for {amount} at {price} · live for 24 h'** + String previewBuyMarket(String amount, String price); + + /// Preview sentence for a market sell order + /// + /// In en, this message translates to: + /// **'You sell BTC for {amount} at {price} · live for 24 h'** + String previewSellMarket(String amount, String price); + + /// Preview sentence for a fixed-price buy order + /// + /// In en, this message translates to: + /// **'You receive {sats} sats for {amount} · live for 24 h'** + String previewReceiveFixed(String sats, String amount); + + /// Preview sentence for a fixed-price sell order + /// + /// In en, this message translates to: + /// **'You sell {sats} sats for {amount} · live for 24 h'** + String previewSellFixed(String sats, String amount); + + /// Price label for market price with no premium + /// + /// In en, this message translates to: + /// **'market price'** + String get marketPriceLabel; + + /// Price label for market price with a premium + /// + /// In en, this message translates to: + /// **'market {premium}%'** + String marketPricePremium(String premium); } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 64cc3d2e..0727bea3 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1758,4 +1758,120 @@ class AppLocalizationsDe extends AppLocalizations { String timeLeftLabel(String time) { return '$time übrig'; } + + @override + String get waitingForTradeAmount => + 'Warte auf den Trade-Betrag — bitte versuche es gleich noch einmal.'; + + @override + String get fetchingTradeAmount => 'Trade-Betrag wird abgerufen…'; + + @override + String get enterInvoiceManually => 'Rechnung manuell eingeben'; + + @override + String get enterLightningInvoiceInstruction => + 'Gib eine Lightning-Rechnung ein, um deine Sats zu erhalten'; + + @override + String get lightningInvoiceLabel => 'Lightning-Rechnung'; + + @override + String get submitButton => 'Absenden'; + + @override + String get sellOrderDetailsTitle => 'DETAILS DER VERKAUFSORDER'; + + @override + String get buyOrderDetailsTitle => 'DETAILS DER KAUFORDER'; + + @override + String get buyTheseSatsButton => 'DIESE SATS KAUFEN'; + + @override + String get sellSatsButton => 'SATS VERKAUFEN'; + + @override + String get someoneSellingSats => 'Jemand verkauft Sats'; + + @override + String get someoneBuyingSats => 'Jemand kauft Sats'; + + @override + String get takeOrderForPrefix => 'für '; + + @override + String get takeOrderAtMarketPrice => ' zum Marktpreis'; + + @override + String premiumLabel(String premium) { + return 'Aufschlag: $premium%'; + } + + @override + String get creatorReputation => 'Reputation des Erstellers'; + + @override + String get ratingStatLabel => 'Bewertung'; + + @override + String get tradesStatLabel => 'Trades'; + + @override + String get daysActiveStatLabel => 'Tage aktiv'; + + @override + String get timeToTakeOrder => 'ZEIT, DIESE ORDER ANZUNEHMEN'; + + @override + String get orderExpiryRemovedNote => + 'Bei Ablauf wird die Order aus dem Buch entfernt. '; + + @override + String get orderExpiryNoReputationNote => + 'Es wirkt sich nicht auf deine Reputation aus.'; + + @override + String get minHint => 'Min'; + + @override + String get maxHint => 'Max'; + + @override + String get fiatAmountHint => 'Fiat-Betrag'; + + @override + String get enterAmountForPreview => + 'Gib einen Betrag ein, um eine Live-Vorschau zu sehen.'; + + @override + String get previewLabel => 'VORSCHAU'; + + @override + String previewBuyMarket(String amount, String price) { + return 'Du kaufst BTC für $amount zu $price · 24 h aktiv'; + } + + @override + String previewSellMarket(String amount, String price) { + return 'Du verkaufst BTC für $amount zu $price · 24 h aktiv'; + } + + @override + String previewReceiveFixed(String sats, String amount) { + return 'Du erhältst $sats Sats für $amount · 24 h aktiv'; + } + + @override + String previewSellFixed(String sats, String amount) { + return 'Du verkaufst $sats Sats für $amount · 24 h aktiv'; + } + + @override + String get marketPriceLabel => 'Marktpreis'; + + @override + String marketPricePremium(String premium) { + return 'Markt $premium%'; + } } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 41156b29..859b243a 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1731,4 +1731,118 @@ class AppLocalizationsEn extends AppLocalizations { String timeLeftLabel(String time) { return '$time left'; } + + @override + String get waitingForTradeAmount => + 'Waiting for trade amount — please try again shortly.'; + + @override + String get fetchingTradeAmount => 'Fetching trade amount…'; + + @override + String get enterInvoiceManually => 'Enter invoice manually'; + + @override + String get enterLightningInvoiceInstruction => + 'Enter a Lightning Invoice to receive your sats'; + + @override + String get lightningInvoiceLabel => 'Lightning Invoice'; + + @override + String get submitButton => 'Submit'; + + @override + String get sellOrderDetailsTitle => 'SELL ORDER DETAILS'; + + @override + String get buyOrderDetailsTitle => 'BUY ORDER DETAILS'; + + @override + String get buyTheseSatsButton => 'BUY THESE SATS'; + + @override + String get sellSatsButton => 'SELL SATS'; + + @override + String get someoneSellingSats => 'Someone is selling sats'; + + @override + String get someoneBuyingSats => 'Someone is buying sats'; + + @override + String get takeOrderForPrefix => 'for '; + + @override + String get takeOrderAtMarketPrice => ' at market price'; + + @override + String premiumLabel(String premium) { + return 'Premium: $premium%'; + } + + @override + String get creatorReputation => 'Creator reputation'; + + @override + String get ratingStatLabel => 'rating'; + + @override + String get tradesStatLabel => 'trades'; + + @override + String get daysActiveStatLabel => 'days active'; + + @override + String get timeToTakeOrder => 'TIME TO TAKE THIS ORDER'; + + @override + String get orderExpiryRemovedNote => + 'If it expires, the order is removed from the book. '; + + @override + String get orderExpiryNoReputationNote => 'It won\'t affect your reputation.'; + + @override + String get minHint => 'Min'; + + @override + String get maxHint => 'Max'; + + @override + String get fiatAmountHint => 'Fiat amount'; + + @override + String get enterAmountForPreview => 'Enter an amount to see a live preview.'; + + @override + String get previewLabel => 'PREVIEW'; + + @override + String previewBuyMarket(String amount, String price) { + return 'You buy BTC for $amount at $price · live for 24 h'; + } + + @override + String previewSellMarket(String amount, String price) { + return 'You sell BTC for $amount at $price · live for 24 h'; + } + + @override + String previewReceiveFixed(String sats, String amount) { + return 'You receive $sats sats for $amount · live for 24 h'; + } + + @override + String previewSellFixed(String sats, String amount) { + return 'You sell $sats sats for $amount · live for 24 h'; + } + + @override + String get marketPriceLabel => 'market price'; + + @override + String marketPricePremium(String premium) { + return 'market $premium%'; + } } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 8229585a..e0b0fb3c 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1754,4 +1754,119 @@ class AppLocalizationsEs extends AppLocalizations { String timeLeftLabel(String time) { return 'Quedan $time'; } + + @override + String get waitingForTradeAmount => + 'Esperando el monto de la operación — inténtalo de nuevo en un momento.'; + + @override + String get fetchingTradeAmount => 'Obteniendo el monto de la operación…'; + + @override + String get enterInvoiceManually => 'Ingresar factura manualmente'; + + @override + String get enterLightningInvoiceInstruction => + 'Ingresa una factura Lightning para recibir tus sats'; + + @override + String get lightningInvoiceLabel => 'Factura Lightning'; + + @override + String get submitButton => 'Enviar'; + + @override + String get sellOrderDetailsTitle => 'DETALLES DE ORDEN DE VENTA'; + + @override + String get buyOrderDetailsTitle => 'DETALLES DE ORDEN DE COMPRA'; + + @override + String get buyTheseSatsButton => 'COMPRAR ESTOS SATS'; + + @override + String get sellSatsButton => 'VENDER SATS'; + + @override + String get someoneSellingSats => 'Alguien está vendiendo sats'; + + @override + String get someoneBuyingSats => 'Alguien está comprando sats'; + + @override + String get takeOrderForPrefix => 'por '; + + @override + String get takeOrderAtMarketPrice => ' a precio de mercado'; + + @override + String premiumLabel(String premium) { + return 'Prima: $premium%'; + } + + @override + String get creatorReputation => 'Reputación del creador'; + + @override + String get ratingStatLabel => 'calificación'; + + @override + String get tradesStatLabel => 'operaciones'; + + @override + String get daysActiveStatLabel => 'días activo'; + + @override + String get timeToTakeOrder => 'TIEMPO PARA TOMAR ESTA ORDEN'; + + @override + String get orderExpiryRemovedNote => + 'Si expira, la orden se elimina del libro. '; + + @override + String get orderExpiryNoReputationNote => 'No afectará tu reputación.'; + + @override + String get minHint => 'Mín'; + + @override + String get maxHint => 'Máx'; + + @override + String get fiatAmountHint => 'Monto fiat'; + + @override + String get enterAmountForPreview => + 'Ingresa un monto para ver una vista previa en vivo.'; + + @override + String get previewLabel => 'VISTA PREVIA'; + + @override + String previewBuyMarket(String amount, String price) { + return 'Compras BTC por $amount a $price · activa por 24 h'; + } + + @override + String previewSellMarket(String amount, String price) { + return 'Vendes BTC por $amount a $price · activa por 24 h'; + } + + @override + String previewReceiveFixed(String sats, String amount) { + return 'Recibes $sats sats por $amount · activa por 24 h'; + } + + @override + String previewSellFixed(String sats, String amount) { + return 'Vendes $sats sats por $amount · activa por 24 h'; + } + + @override + String get marketPriceLabel => 'precio de mercado'; + + @override + String marketPricePremium(String premium) { + return 'mercado $premium%'; + } } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index e403b0a8..39c15ea2 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1761,4 +1761,121 @@ class AppLocalizationsFr extends AppLocalizations { String timeLeftLabel(String time) { return '$time restant'; } + + @override + String get waitingForTradeAmount => + 'En attente du montant de la transaction — veuillez réessayer sous peu.'; + + @override + String get fetchingTradeAmount => + 'Récupération du montant de la transaction…'; + + @override + String get enterInvoiceManually => 'Saisir la facture manuellement'; + + @override + String get enterLightningInvoiceInstruction => + 'Saisissez une facture Lightning pour recevoir vos sats'; + + @override + String get lightningInvoiceLabel => 'Facture Lightning'; + + @override + String get submitButton => 'Envoyer'; + + @override + String get sellOrderDetailsTitle => 'DÉTAILS DE L\'ORDRE DE VENTE'; + + @override + String get buyOrderDetailsTitle => 'DÉTAILS DE L\'ORDRE D\'ACHAT'; + + @override + String get buyTheseSatsButton => 'ACHETER CES SATS'; + + @override + String get sellSatsButton => 'VENDRE DES SATS'; + + @override + String get someoneSellingSats => 'Quelqu\'un vend des sats'; + + @override + String get someoneBuyingSats => 'Quelqu\'un achète des sats'; + + @override + String get takeOrderForPrefix => 'pour '; + + @override + String get takeOrderAtMarketPrice => ' au prix du marché'; + + @override + String premiumLabel(String premium) { + return 'Prime : $premium%'; + } + + @override + String get creatorReputation => 'Réputation du créateur'; + + @override + String get ratingStatLabel => 'note'; + + @override + String get tradesStatLabel => 'transactions'; + + @override + String get daysActiveStatLabel => 'jours actifs'; + + @override + String get timeToTakeOrder => 'TEMPS POUR PRENDRE CET ORDRE'; + + @override + String get orderExpiryRemovedNote => + 'S\'il expire, l\'ordre est retiré du carnet. '; + + @override + String get orderExpiryNoReputationNote => + 'Cela n\'affectera pas votre réputation.'; + + @override + String get minHint => 'Min'; + + @override + String get maxHint => 'Max'; + + @override + String get fiatAmountHint => 'Montant fiat'; + + @override + String get enterAmountForPreview => + 'Saisissez un montant pour voir un aperçu en direct.'; + + @override + String get previewLabel => 'APERÇU'; + + @override + String previewBuyMarket(String amount, String price) { + return 'Vous achetez des BTC pour $amount à $price · actif pendant 24 h'; + } + + @override + String previewSellMarket(String amount, String price) { + return 'Vous vendez des BTC pour $amount à $price · actif pendant 24 h'; + } + + @override + String previewReceiveFixed(String sats, String amount) { + return 'Vous recevez $sats sats pour $amount · actif pendant 24 h'; + } + + @override + String previewSellFixed(String sats, String amount) { + return 'Vous vendez $sats sats pour $amount · actif pendant 24 h'; + } + + @override + String get marketPriceLabel => 'prix du marché'; + + @override + String marketPricePremium(String premium) { + return 'marché $premium%'; + } } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 2cf3ddd8..19d6b517 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1751,4 +1751,120 @@ class AppLocalizationsIt extends AppLocalizations { String timeLeftLabel(String time) { return '$time rimasti'; } + + @override + String get waitingForTradeAmount => + 'In attesa dell\'importo dell\'operazione — riprova tra poco.'; + + @override + String get fetchingTradeAmount => 'Recupero dell\'importo dell\'operazione…'; + + @override + String get enterInvoiceManually => 'Inserisci la fattura manualmente'; + + @override + String get enterLightningInvoiceInstruction => + 'Inserisci una fattura Lightning per ricevere i tuoi sats'; + + @override + String get lightningInvoiceLabel => 'Fattura Lightning'; + + @override + String get submitButton => 'Invia'; + + @override + String get sellOrderDetailsTitle => 'DETTAGLI ORDINE DI VENDITA'; + + @override + String get buyOrderDetailsTitle => 'DETTAGLI ORDINE DI ACQUISTO'; + + @override + String get buyTheseSatsButton => 'COMPRA QUESTI SATS'; + + @override + String get sellSatsButton => 'VENDI SATS'; + + @override + String get someoneSellingSats => 'Qualcuno sta vendendo sats'; + + @override + String get someoneBuyingSats => 'Qualcuno sta comprando sats'; + + @override + String get takeOrderForPrefix => 'per '; + + @override + String get takeOrderAtMarketPrice => ' al prezzo di mercato'; + + @override + String premiumLabel(String premium) { + return 'Premio: $premium%'; + } + + @override + String get creatorReputation => 'Reputazione del creatore'; + + @override + String get ratingStatLabel => 'valutazione'; + + @override + String get tradesStatLabel => 'operazioni'; + + @override + String get daysActiveStatLabel => 'giorni attivo'; + + @override + String get timeToTakeOrder => 'TEMPO PER PRENDERE QUEST\'ORDINE'; + + @override + String get orderExpiryRemovedNote => + 'Se scade, l\'ordine viene rimosso dal book. '; + + @override + String get orderExpiryNoReputationNote => + 'Non influirà sulla tua reputazione.'; + + @override + String get minHint => 'Min'; + + @override + String get maxHint => 'Max'; + + @override + String get fiatAmountHint => 'Importo fiat'; + + @override + String get enterAmountForPreview => + 'Inserisci un importo per vedere un\'anteprima in tempo reale.'; + + @override + String get previewLabel => 'ANTEPRIMA'; + + @override + String previewBuyMarket(String amount, String price) { + return 'Compri BTC per $amount a $price · attivo per 24 h'; + } + + @override + String previewSellMarket(String amount, String price) { + return 'Vendi BTC per $amount a $price · attivo per 24 h'; + } + + @override + String previewReceiveFixed(String sats, String amount) { + return 'Ricevi $sats sats per $amount · attivo per 24 h'; + } + + @override + String previewSellFixed(String sats, String amount) { + return 'Vendi $sats sats per $amount · attivo per 24 h'; + } + + @override + String get marketPriceLabel => 'prezzo di mercato'; + + @override + String marketPricePremium(String premium) { + return 'mercato $premium%'; + } } From 8dddb4929a195f78f00b81588596291228d00a0c Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 02:32:02 -0300 Subject: [PATCH 13/30] feat: localize order widget strings Replace hardcoded English text in currency_section.dart, order_preset_selector.dart, payment_method_section.dart, price_section.dart, and range_amount_modal.dart with localized strings from AppLocalizations. --- .../order/widgets/currency_section.dart | 7 +- .../order/widgets/order_preset_selector.dart | 25 +-- .../order/widgets/payment_method_section.dart | 14 +- lib/features/order/widgets/price_section.dart | 22 ++- .../order/widgets/range_amount_modal.dart | 15 +- lib/l10n/app_de.arb | 27 +++- lib/l10n/app_en.arb | 62 ++++++++ lib/l10n/app_es.arb | 27 +++- lib/l10n/app_fr.arb | 27 +++- lib/l10n/app_it.arb | 27 +++- lib/l10n/app_localizations.dart | 150 ++++++++++++++++++ lib/l10n/app_localizations_de.dart | 87 ++++++++++ lib/l10n/app_localizations_en.dart | 87 ++++++++++ lib/l10n/app_localizations_es.dart | 87 ++++++++++ lib/l10n/app_localizations_fr.dart | 87 ++++++++++ lib/l10n/app_localizations_it.dart | 87 ++++++++++ 16 files changed, 795 insertions(+), 43 deletions(-) diff --git a/lib/features/order/widgets/currency_section.dart b/lib/features/order/widgets/currency_section.dart index 77a14f73..8f550916 100644 --- a/lib/features/order/widgets/currency_section.dart +++ b/lib/features/order/widgets/currency_section.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/shared/utils/fiat_currencies.dart'; /// Provider for the currently selected fiat code in the create-order form. @@ -108,9 +109,9 @@ class _CurrencyPickerDialogState extends State<_CurrencyPickerDialog> { padding: const EdgeInsets.all(AppSpacing.md), child: TextField( autofocus: true, - decoration: const InputDecoration( - hintText: 'Search currency...', - prefixIcon: Icon(Icons.search), + decoration: InputDecoration( + hintText: AppLocalizations.of(context).searchCurrenciesHint, + prefixIcon: const Icon(Icons.search), ), onChanged: (v) => setState(() => _query = v), ), diff --git a/lib/features/order/widgets/order_preset_selector.dart b/lib/features/order/widgets/order_preset_selector.dart index a2adc05a..60ad9e69 100644 --- a/lib/features/order/widgets/order_preset_selector.dart +++ b/lib/features/order/widgets/order_preset_selector.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/src/rust/api/orders.dart' as orders_api; import 'package:mostro/src/rust/api/types.dart'; @@ -62,12 +63,13 @@ class OrderPresetSelector extends ConsumerWidget { final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230); final selected = ref.watch(selectedOrderPresetProvider); final lastOrder = ref.watch(lastSuccessfulOrderProvider).valueOrNull; + final l10n = AppLocalizations.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'START FROM A PRESET', + l10n.startFromPreset, style: TextStyle( fontSize: 11, letterSpacing: 1, @@ -80,9 +82,9 @@ class OrderPresetSelector extends ConsumerWidget { cardBg: cardBg, iconData: Icons.bolt, iconColor: green, - title: 'Express', - subtitle: _expressSubtitle(lastOrder), - tag: 'RECOMMENDED', + title: l10n.presetExpressTitle, + subtitle: _expressSubtitle(lastOrder, l10n), + tag: l10n.recommendedTag, tagColor: green, selected: selected == OrderPreset.express, highlightColor: green, @@ -94,8 +96,8 @@ class OrderPresetSelector extends ConsumerWidget { cardBg: cardBg, iconData: Icons.shield_outlined, iconColor: blue, - title: 'Conservative', - subtitle: 'Market price · 0% premium · you choose amount & methods', + title: l10n.presetConservativeTitle, + subtitle: l10n.presetConservativeSubtitle, selected: selected == OrderPreset.conservative, highlightColor: green, onTap: () => onSelect(OrderPreset.conservative, null), @@ -105,9 +107,8 @@ class OrderPresetSelector extends ConsumerWidget { cardBg: cardBg, iconData: Icons.settings, iconColor: purple, - title: 'Custom', - subtitle: - 'All fields — amount, range, methods, premium, fixed or market price', + title: l10n.presetCustomTitle, + subtitle: l10n.presetCustomSubtitle, selected: selected == OrderPreset.custom, highlightColor: green, onTap: () => onSelect(OrderPreset.custom, null), @@ -117,7 +118,7 @@ class OrderPresetSelector extends ConsumerWidget { } /// "Same as your last successful order — 50 ARS, MP, 0% premium". - static String _expressSubtitle(OrderInfo order) { + static String _expressSubtitle(OrderInfo order, AppLocalizations l10n) { final parts = []; if (order.fiatAmount != null) { parts.add('${_formatNum(order.fiatAmount!)} ${order.fiatCode}'); @@ -134,8 +135,8 @@ class OrderPresetSelector extends ConsumerWidget { .firstOrNull; if (firstMethod != null) parts.add(firstMethod); final p = order.premium; - parts.add('${p > 0 ? '+' : ''}${_formatNum(p)}% premium'); - return 'Same as your last successful order — ${parts.join(', ')}'; + parts.add(l10n.expressPremiumSuffix('${p > 0 ? '+' : ''}${_formatNum(p)}')); + return l10n.expressPresetSubtitle(parts.join(', ')); } static String _formatNum(double v) => diff --git a/lib/features/order/widgets/payment_method_section.dart b/lib/features/order/widgets/payment_method_section.dart index d6d94aba..7c8a1b48 100644 --- a/lib/features/order/widgets/payment_method_section.dart +++ b/lib/features/order/widgets/payment_method_section.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Common payment methods available for selection. const _commonMethods = [ @@ -58,11 +59,12 @@ class _PaymentMethodSectionState extends ConsumerState { final inputBg = colors?.backgroundInput ?? const Color(0xFF252A3A); final selected = ref.watch(selectedPaymentMethodsProvider); final custom = ref.watch(customPaymentMethodProvider); + final l10n = AppLocalizations.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Payment Methods', style: theme.textTheme.labelLarge), + Text(l10n.paymentMethodsLabel, style: theme.textTheme.labelLarge), const SizedBox(height: AppSpacing.sm), // Selected chips @@ -100,7 +102,7 @@ class _PaymentMethodSectionState extends ConsumerState { Icon(Icons.add, size: 16, color: green), const SizedBox(width: AppSpacing.sm), Text( - 'Add payment method', + l10n.addPaymentMethod, style: TextStyle(color: colors?.textSecondary), ), ], @@ -113,7 +115,7 @@ class _PaymentMethodSectionState extends ConsumerState { TextField( controller: _customController, decoration: InputDecoration( - hintText: 'Custom payment method...', + hintText: l10n.customPaymentMethodHint, filled: true, fillColor: inputBg, border: OutlineInputBorder( @@ -130,7 +132,7 @@ class _PaymentMethodSectionState extends ConsumerState { Padding( padding: const EdgeInsets.only(top: AppSpacing.xs), child: Text( - 'Custom method will be appended to selection', + l10n.customMethodAppendedNote, style: TextStyle( color: colors?.textSubtle, fontSize: 11, @@ -193,7 +195,7 @@ class _MethodPickerDialogState extends State<_MethodPickerDialog> { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Select Payment Methods', + AppLocalizations.of(context).selectPaymentMethodsTitle, style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: AppSpacing.md), @@ -228,7 +230,7 @@ class _MethodPickerDialogState extends State<_MethodPickerDialog> { backgroundColor: green, foregroundColor: Colors.black, ), - child: const Text('Done'), + child: Text(AppLocalizations.of(context).done), ), ), ], diff --git a/lib/features/order/widgets/price_section.dart b/lib/features/order/widgets/price_section.dart index 49006a90..772db60b 100644 --- a/lib/features/order/widgets/price_section.dart +++ b/lib/features/order/widgets/price_section.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Whether Market or Fixed price mode is selected. final isMarketPriceProvider = StateProvider((_) => true); @@ -55,6 +56,7 @@ class _PriceSectionState extends ConsumerState { final inputBg = colors?.backgroundInput ?? const Color(0xFF252A3A); final isMarket = ref.watch(isMarketPriceProvider); final premium = ref.watch(premiumValueProvider); + final l10n = AppLocalizations.of(context); // Sync controller from provider via listener (not in build body). ref.listen(premiumValueProvider, _syncControllerFromProvider); @@ -65,10 +67,10 @@ class _PriceSectionState extends ConsumerState { // Header with toggle Row( children: [ - Text('Price Type', style: theme.textTheme.labelLarge), + Text(l10n.priceTypeLabel, style: theme.textTheme.labelLarge), const Spacer(), Text( - isMarket ? 'Market' : 'Fixed', + isMarket ? l10n.priceTypeMarket : l10n.priceTypeFixed, style: TextStyle( color: colors?.textSecondary, fontSize: 12, @@ -86,7 +88,7 @@ class _PriceSectionState extends ConsumerState { icon: const Icon(Icons.info_outline, size: 18), padding: EdgeInsets.zero, constraints: const BoxConstraints(), - tooltip: 'Price type info', + tooltip: l10n.priceTypeInfoTooltip, ), ], ), @@ -106,7 +108,7 @@ class _PriceSectionState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Premium', + l10n.premiumSectionLabel, style: TextStyle( color: purple, fontWeight: FontWeight.w600, @@ -212,7 +214,7 @@ class _PriceSectionState extends ConsumerState { // Fixed sats input TextField( decoration: InputDecoration( - hintText: 'Amount in sats', + hintText: l10n.amountInSatsHint, filled: true, fillColor: inputBg, suffixText: 'sats', @@ -235,16 +237,12 @@ class _PriceSectionState extends ConsumerState { showDialog( context: context, builder: (dialogContext) => AlertDialog( - title: const Text('Price Types'), - content: const Text( - 'Market Price: Your order price follows the market rate with ' - 'a premium/discount percentage applied.\n\n' - 'Fixed Price: You set an exact price in satoshis.', - ), + title: Text(AppLocalizations.of(context).priceTypesDialogTitle), + content: Text(AppLocalizations.of(context).priceTypesDialogContent), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext), - child: const Text('OK'), + child: Text(AppLocalizations.of(context).okButtonLabel), ), ], ), diff --git a/lib/features/order/widgets/range_amount_modal.dart b/lib/features/order/widgets/range_amount_modal.dart index f5c043f2..8329374d 100644 --- a/lib/features/order/widgets/range_amount_modal.dart +++ b/lib/features/order/widgets/range_amount_modal.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Shows a modal dialog for entering an amount within a range. /// @@ -53,8 +54,8 @@ class _RangeAmountDialogState extends State<_RangeAmountDialog> { if (v == null) { _error = null; // don't show error while typing } else if (v < widget.min || v > widget.max) { - _error = 'Amount must be between ' - '${_fmt(widget.min)} and ${_fmt(widget.max)}'; + _error = AppLocalizations.of(context) + .amountRangeError(_fmt(widget.min), _fmt(widget.max)); } else { _error = null; } @@ -76,6 +77,7 @@ class _RangeAmountDialogState extends State<_RangeAmountDialog> { final colors = Theme.of(context).extension(); final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230); + final l10n = AppLocalizations.of(context); return Dialog( backgroundColor: cardBg, @@ -89,7 +91,7 @@ class _RangeAmountDialogState extends State<_RangeAmountDialog> { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Enter Amount', + l10n.enterAmountTitle, style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: AppSpacing.lg), @@ -113,7 +115,8 @@ class _RangeAmountDialogState extends State<_RangeAmountDialog> { const SizedBox(height: AppSpacing.sm), Text( - 'Min: ${_fmt(widget.min)} – Max: ${_fmt(widget.max)} ${widget.currencyCode}', + l10n.minMaxRangeLabel( + _fmt(widget.min), _fmt(widget.max), widget.currencyCode), style: TextStyle( color: colors?.textSubtle, fontSize: 12, @@ -135,7 +138,7 @@ class _RangeAmountDialogState extends State<_RangeAmountDialog> { borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text('Cancel'), + child: Text(l10n.cancel), ), ), const SizedBox(width: AppSpacing.md), @@ -152,7 +155,7 @@ class _RangeAmountDialogState extends State<_RangeAmountDialog> { borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text('Submit'), + child: Text(l10n.submitButton), ), ), ], diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 26fe9e21..0f3f9b60 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -573,5 +573,30 @@ "previewReceiveFixed": "Du erhältst {sats} Sats für {amount} · 24 h aktiv", "previewSellFixed": "Du verkaufst {sats} Sats für {amount} · 24 h aktiv", "marketPriceLabel": "Marktpreis", - "marketPricePremium": "Markt {premium}%" + "marketPricePremium": "Markt {premium}%", + "priceTypeLabel": "Preistyp", + "priceTypeMarket": "Markt", + "priceTypeFixed": "Fest", + "priceTypeInfoTooltip": "Info zum Preistyp", + "premiumSectionLabel": "Aufschlag", + "amountInSatsHint": "Betrag in Sats", + "priceTypesDialogTitle": "Preistypen", + "priceTypesDialogContent": "Marktpreis: Der Preis deiner Order folgt dem Marktkurs mit einem angewendeten Aufschlag/Abschlag-Prozentsatz.\n\nFestpreis: Du legst einen genauen Preis in Satoshis fest.", + "startFromPreset": "MIT EINER VORLAGE STARTEN", + "presetExpressTitle": "Express", + "recommendedTag": "EMPFOHLEN", + "presetConservativeTitle": "Konservativ", + "presetConservativeSubtitle": "Marktpreis · 0% Aufschlag · du wählst Betrag & Methoden", + "presetCustomTitle": "Benutzerdefiniert", + "presetCustomSubtitle": "Alle Felder — Betrag, Bereich, Methoden, Aufschlag, Fest- oder Marktpreis", + "expressPresetSubtitle": "Wie deine letzte erfolgreiche Order — {details}", + "expressPremiumSuffix": "{premium}% Aufschlag", + "paymentMethodsLabel": "Zahlungsmethoden", + "addPaymentMethod": "Zahlungsmethode hinzufügen", + "customPaymentMethodHint": "Eigene Zahlungsmethode...", + "customMethodAppendedNote": "Die eigene Methode wird zur Auswahl hinzugefügt", + "selectPaymentMethodsTitle": "Zahlungsmethoden auswählen", + "amountRangeError": "Der Betrag muss zwischen {min} und {max} liegen", + "enterAmountTitle": "Betrag eingeben", + "minMaxRangeLabel": "Min: {min} – Max: {max} {currency}" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9f0085fd..863c3b82 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1243,5 +1243,67 @@ "@marketPricePremium": { "description": "Price label for market price with a premium", "placeholders": {"premium": {"type": "String"}} + }, + "priceTypeLabel": "Price Type", + "@priceTypeLabel": {"description": "Label for the price type toggle"}, + "priceTypeMarket": "Market", + "@priceTypeMarket": {"description": "Price type value: market"}, + "priceTypeFixed": "Fixed", + "@priceTypeFixed": {"description": "Price type value: fixed"}, + "priceTypeInfoTooltip": "Price type info", + "@priceTypeInfoTooltip": {"description": "Tooltip for the price type info button"}, + "premiumSectionLabel": "Premium", + "@premiumSectionLabel": {"description": "Label of the premium slider section"}, + "amountInSatsHint": "Amount in sats", + "@amountInSatsHint": {"description": "Hint for the fixed sats amount input"}, + "priceTypesDialogTitle": "Price Types", + "@priceTypesDialogTitle": {"description": "Title of the price types info dialog"}, + "priceTypesDialogContent": "Market Price: Your order price follows the market rate with a premium/discount percentage applied.\n\nFixed Price: You set an exact price in satoshis.", + "@priceTypesDialogContent": {"description": "Body of the price types info dialog"}, + "startFromPreset": "START FROM A PRESET", + "@startFromPreset": {"description": "Header of the order preset selector"}, + "presetExpressTitle": "Express", + "@presetExpressTitle": {"description": "Express preset card title"}, + "recommendedTag": "RECOMMENDED", + "@recommendedTag": {"description": "Recommended tag on the express preset"}, + "presetConservativeTitle": "Conservative", + "@presetConservativeTitle": {"description": "Conservative preset card title"}, + "presetConservativeSubtitle": "Market price · 0% premium · you choose amount & methods", + "@presetConservativeSubtitle": {"description": "Conservative preset card subtitle"}, + "presetCustomTitle": "Custom", + "@presetCustomTitle": {"description": "Custom preset card title"}, + "presetCustomSubtitle": "All fields — amount, range, methods, premium, fixed or market price", + "@presetCustomSubtitle": {"description": "Custom preset card subtitle"}, + "expressPresetSubtitle": "Same as your last successful order — {details}", + "@expressPresetSubtitle": { + "description": "Express preset subtitle built from the last order", + "placeholders": {"details": {"type": "String"}} + }, + "expressPremiumSuffix": "{premium}% premium", + "@expressPremiumSuffix": { + "description": "Premium fragment of the express preset subtitle", + "placeholders": {"premium": {"type": "String"}} + }, + "paymentMethodsLabel": "Payment Methods", + "@paymentMethodsLabel": {"description": "Label of the payment methods section"}, + "addPaymentMethod": "Add payment method", + "@addPaymentMethod": {"description": "Button to open the payment method picker"}, + "customPaymentMethodHint": "Custom payment method...", + "@customPaymentMethodHint": {"description": "Hint for the custom payment method input"}, + "customMethodAppendedNote": "Custom method will be appended to selection", + "@customMethodAppendedNote": {"description": "Note explaining the custom method is appended"}, + "selectPaymentMethodsTitle": "Select Payment Methods", + "@selectPaymentMethodsTitle": {"description": "Title of the payment method picker dialog"}, + "amountRangeError": "Amount must be between {min} and {max}", + "@amountRangeError": { + "description": "Validation error when the amount is out of range", + "placeholders": {"min": {"type": "String"}, "max": {"type": "String"}} + }, + "enterAmountTitle": "Enter Amount", + "@enterAmountTitle": {"description": "Title of the range amount dialog"}, + "minMaxRangeLabel": "Min: {min} – Max: {max} {currency}", + "@minMaxRangeLabel": { + "description": "Range bounds label in the amount dialog", + "placeholders": {"min": {"type": "String"}, "max": {"type": "String"}, "currency": {"type": "String"}} } } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 0961a27e..5bdf7ed6 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -573,5 +573,30 @@ "previewReceiveFixed": "Recibes {sats} sats por {amount} · activa por 24 h", "previewSellFixed": "Vendes {sats} sats por {amount} · activa por 24 h", "marketPriceLabel": "precio de mercado", - "marketPricePremium": "mercado {premium}%" + "marketPricePremium": "mercado {premium}%", + "priceTypeLabel": "Tipo de precio", + "priceTypeMarket": "Mercado", + "priceTypeFixed": "Fijo", + "priceTypeInfoTooltip": "Info del tipo de precio", + "premiumSectionLabel": "Prima", + "amountInSatsHint": "Monto en sats", + "priceTypesDialogTitle": "Tipos de precio", + "priceTypesDialogContent": "Precio de mercado: el precio de tu orden sigue la tasa del mercado con un porcentaje de prima/descuento aplicado.\n\nPrecio fijo: estableces un precio exacto en satoshis.", + "startFromPreset": "EMPEZAR DESDE UN PRESET", + "presetExpressTitle": "Exprés", + "recommendedTag": "RECOMENDADO", + "presetConservativeTitle": "Conservador", + "presetConservativeSubtitle": "Precio de mercado · 0% de prima · eliges monto y métodos", + "presetCustomTitle": "Personalizado", + "presetCustomSubtitle": "Todos los campos — monto, rango, métodos, prima, precio fijo o de mercado", + "expressPresetSubtitle": "Igual que tu última orden exitosa — {details}", + "expressPremiumSuffix": "{premium}% de prima", + "paymentMethodsLabel": "Métodos de pago", + "addPaymentMethod": "Agregar método de pago", + "customPaymentMethodHint": "Método de pago personalizado...", + "customMethodAppendedNote": "El método personalizado se añadirá a la selección", + "selectPaymentMethodsTitle": "Selecciona métodos de pago", + "amountRangeError": "El monto debe estar entre {min} y {max}", + "enterAmountTitle": "Ingresa el monto", + "minMaxRangeLabel": "Mín: {min} – Máx: {max} {currency}" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 026b68ef..27d781b2 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -573,5 +573,30 @@ "previewReceiveFixed": "Vous recevez {sats} sats pour {amount} · actif pendant 24 h", "previewSellFixed": "Vous vendez {sats} sats pour {amount} · actif pendant 24 h", "marketPriceLabel": "prix du marché", - "marketPricePremium": "marché {premium}%" + "marketPricePremium": "marché {premium}%", + "priceTypeLabel": "Type de prix", + "priceTypeMarket": "Marché", + "priceTypeFixed": "Fixe", + "priceTypeInfoTooltip": "Infos sur le type de prix", + "premiumSectionLabel": "Prime", + "amountInSatsHint": "Montant en sats", + "priceTypesDialogTitle": "Types de prix", + "priceTypesDialogContent": "Prix du marché : le prix de votre ordre suit le taux du marché avec un pourcentage de prime/remise appliqué.\n\nPrix fixe : vous définissez un prix exact en satoshis.", + "startFromPreset": "PARTIR D'UN PRÉRÉGLAGE", + "presetExpressTitle": "Express", + "recommendedTag": "RECOMMANDÉ", + "presetConservativeTitle": "Conservateur", + "presetConservativeSubtitle": "Prix du marché · 0% de prime · vous choisissez le montant et les méthodes", + "presetCustomTitle": "Personnalisé", + "presetCustomSubtitle": "Tous les champs — montant, plage, méthodes, prime, prix fixe ou du marché", + "expressPresetSubtitle": "Comme votre dernier ordre réussi — {details}", + "expressPremiumSuffix": "{premium}% de prime", + "paymentMethodsLabel": "Moyens de paiement", + "addPaymentMethod": "Ajouter un moyen de paiement", + "customPaymentMethodHint": "Moyen de paiement personnalisé...", + "customMethodAppendedNote": "Le moyen personnalisé sera ajouté à la sélection", + "selectPaymentMethodsTitle": "Sélectionnez les moyens de paiement", + "amountRangeError": "Le montant doit être entre {min} et {max}", + "enterAmountTitle": "Saisir le montant", + "minMaxRangeLabel": "Min : {min} – Max : {max} {currency}" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 7bd657e8..53d69b6e 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -573,5 +573,30 @@ "previewReceiveFixed": "Ricevi {sats} sats per {amount} · attivo per 24 h", "previewSellFixed": "Vendi {sats} sats per {amount} · attivo per 24 h", "marketPriceLabel": "prezzo di mercato", - "marketPricePremium": "mercato {premium}%" + "marketPricePremium": "mercato {premium}%", + "priceTypeLabel": "Tipo di prezzo", + "priceTypeMarket": "Mercato", + "priceTypeFixed": "Fisso", + "priceTypeInfoTooltip": "Info sul tipo di prezzo", + "premiumSectionLabel": "Premio", + "amountInSatsHint": "Importo in sats", + "priceTypesDialogTitle": "Tipi di prezzo", + "priceTypesDialogContent": "Prezzo di mercato: il prezzo del tuo ordine segue il tasso di mercato con una percentuale di premio/sconto applicata.\n\nPrezzo fisso: imposti un prezzo esatto in satoshi.", + "startFromPreset": "INIZIA DA UN PRESET", + "presetExpressTitle": "Express", + "recommendedTag": "CONSIGLIATO", + "presetConservativeTitle": "Conservativo", + "presetConservativeSubtitle": "Prezzo di mercato · 0% di premio · scegli importo e metodi", + "presetCustomTitle": "Personalizzato", + "presetCustomSubtitle": "Tutti i campi — importo, intervallo, metodi, premio, prezzo fisso o di mercato", + "expressPresetSubtitle": "Come il tuo ultimo ordine riuscito — {details}", + "expressPremiumSuffix": "{premium}% di premio", + "paymentMethodsLabel": "Metodi di pagamento", + "addPaymentMethod": "Aggiungi metodo di pagamento", + "customPaymentMethodHint": "Metodo di pagamento personalizzato...", + "customMethodAppendedNote": "Il metodo personalizzato verrà aggiunto alla selezione", + "selectPaymentMethodsTitle": "Seleziona i metodi di pagamento", + "amountRangeError": "L'importo deve essere compreso tra {min} e {max}", + "enterAmountTitle": "Inserisci l'importo", + "minMaxRangeLabel": "Min: {min} – Max: {max} {currency}" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 32cd3d6c..c3bd6b20 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3397,6 +3397,156 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'market {premium}%'** String marketPricePremium(String premium); + + /// Label for the price type toggle + /// + /// In en, this message translates to: + /// **'Price Type'** + String get priceTypeLabel; + + /// Price type value: market + /// + /// In en, this message translates to: + /// **'Market'** + String get priceTypeMarket; + + /// Price type value: fixed + /// + /// In en, this message translates to: + /// **'Fixed'** + String get priceTypeFixed; + + /// Tooltip for the price type info button + /// + /// In en, this message translates to: + /// **'Price type info'** + String get priceTypeInfoTooltip; + + /// Label of the premium slider section + /// + /// In en, this message translates to: + /// **'Premium'** + String get premiumSectionLabel; + + /// Hint for the fixed sats amount input + /// + /// In en, this message translates to: + /// **'Amount in sats'** + String get amountInSatsHint; + + /// Title of the price types info dialog + /// + /// In en, this message translates to: + /// **'Price Types'** + String get priceTypesDialogTitle; + + /// Body of the price types info dialog + /// + /// In en, this message translates to: + /// **'Market Price: Your order price follows the market rate with a premium/discount percentage applied.\n\nFixed Price: You set an exact price in satoshis.'** + String get priceTypesDialogContent; + + /// Header of the order preset selector + /// + /// In en, this message translates to: + /// **'START FROM A PRESET'** + String get startFromPreset; + + /// Express preset card title + /// + /// In en, this message translates to: + /// **'Express'** + String get presetExpressTitle; + + /// Recommended tag on the express preset + /// + /// In en, this message translates to: + /// **'RECOMMENDED'** + String get recommendedTag; + + /// Conservative preset card title + /// + /// In en, this message translates to: + /// **'Conservative'** + String get presetConservativeTitle; + + /// Conservative preset card subtitle + /// + /// In en, this message translates to: + /// **'Market price · 0% premium · you choose amount & methods'** + String get presetConservativeSubtitle; + + /// Custom preset card title + /// + /// In en, this message translates to: + /// **'Custom'** + String get presetCustomTitle; + + /// Custom preset card subtitle + /// + /// In en, this message translates to: + /// **'All fields — amount, range, methods, premium, fixed or market price'** + String get presetCustomSubtitle; + + /// Express preset subtitle built from the last order + /// + /// In en, this message translates to: + /// **'Same as your last successful order — {details}'** + String expressPresetSubtitle(String details); + + /// Premium fragment of the express preset subtitle + /// + /// In en, this message translates to: + /// **'{premium}% premium'** + String expressPremiumSuffix(String premium); + + /// Label of the payment methods section + /// + /// In en, this message translates to: + /// **'Payment Methods'** + String get paymentMethodsLabel; + + /// Button to open the payment method picker + /// + /// In en, this message translates to: + /// **'Add payment method'** + String get addPaymentMethod; + + /// Hint for the custom payment method input + /// + /// In en, this message translates to: + /// **'Custom payment method...'** + String get customPaymentMethodHint; + + /// Note explaining the custom method is appended + /// + /// In en, this message translates to: + /// **'Custom method will be appended to selection'** + String get customMethodAppendedNote; + + /// Title of the payment method picker dialog + /// + /// In en, this message translates to: + /// **'Select Payment Methods'** + String get selectPaymentMethodsTitle; + + /// Validation error when the amount is out of range + /// + /// In en, this message translates to: + /// **'Amount must be between {min} and {max}'** + String amountRangeError(String min, String max); + + /// Title of the range amount dialog + /// + /// In en, this message translates to: + /// **'Enter Amount'** + String get enterAmountTitle; + + /// Range bounds label in the amount dialog + /// + /// In en, this message translates to: + /// **'Min: {min} – Max: {max} {currency}'** + String minMaxRangeLabel(String min, String max, String currency); } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 0727bea3..6731e5d7 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1874,4 +1874,91 @@ class AppLocalizationsDe extends AppLocalizations { String marketPricePremium(String premium) { return 'Markt $premium%'; } + + @override + String get priceTypeLabel => 'Preistyp'; + + @override + String get priceTypeMarket => 'Markt'; + + @override + String get priceTypeFixed => 'Fest'; + + @override + String get priceTypeInfoTooltip => 'Info zum Preistyp'; + + @override + String get premiumSectionLabel => 'Aufschlag'; + + @override + String get amountInSatsHint => 'Betrag in Sats'; + + @override + String get priceTypesDialogTitle => 'Preistypen'; + + @override + String get priceTypesDialogContent => + 'Marktpreis: Der Preis deiner Order folgt dem Marktkurs mit einem angewendeten Aufschlag/Abschlag-Prozentsatz.\n\nFestpreis: Du legst einen genauen Preis in Satoshis fest.'; + + @override + String get startFromPreset => 'MIT EINER VORLAGE STARTEN'; + + @override + String get presetExpressTitle => 'Express'; + + @override + String get recommendedTag => 'EMPFOHLEN'; + + @override + String get presetConservativeTitle => 'Konservativ'; + + @override + String get presetConservativeSubtitle => + 'Marktpreis · 0% Aufschlag · du wählst Betrag & Methoden'; + + @override + String get presetCustomTitle => 'Benutzerdefiniert'; + + @override + String get presetCustomSubtitle => + 'Alle Felder — Betrag, Bereich, Methoden, Aufschlag, Fest- oder Marktpreis'; + + @override + String expressPresetSubtitle(String details) { + return 'Wie deine letzte erfolgreiche Order — $details'; + } + + @override + String expressPremiumSuffix(String premium) { + return '$premium% Aufschlag'; + } + + @override + String get paymentMethodsLabel => 'Zahlungsmethoden'; + + @override + String get addPaymentMethod => 'Zahlungsmethode hinzufügen'; + + @override + String get customPaymentMethodHint => 'Eigene Zahlungsmethode...'; + + @override + String get customMethodAppendedNote => + 'Die eigene Methode wird zur Auswahl hinzugefügt'; + + @override + String get selectPaymentMethodsTitle => 'Zahlungsmethoden auswählen'; + + @override + String amountRangeError(String min, String max) { + return 'Der Betrag muss zwischen $min und $max liegen'; + } + + @override + String get enterAmountTitle => 'Betrag eingeben'; + + @override + String minMaxRangeLabel(String min, String max, String currency) { + return 'Min: $min – Max: $max $currency'; + } } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 859b243a..ad69e6cd 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1845,4 +1845,91 @@ class AppLocalizationsEn extends AppLocalizations { String marketPricePremium(String premium) { return 'market $premium%'; } + + @override + String get priceTypeLabel => 'Price Type'; + + @override + String get priceTypeMarket => 'Market'; + + @override + String get priceTypeFixed => 'Fixed'; + + @override + String get priceTypeInfoTooltip => 'Price type info'; + + @override + String get premiumSectionLabel => 'Premium'; + + @override + String get amountInSatsHint => 'Amount in sats'; + + @override + String get priceTypesDialogTitle => 'Price Types'; + + @override + String get priceTypesDialogContent => + 'Market Price: Your order price follows the market rate with a premium/discount percentage applied.\n\nFixed Price: You set an exact price in satoshis.'; + + @override + String get startFromPreset => 'START FROM A PRESET'; + + @override + String get presetExpressTitle => 'Express'; + + @override + String get recommendedTag => 'RECOMMENDED'; + + @override + String get presetConservativeTitle => 'Conservative'; + + @override + String get presetConservativeSubtitle => + 'Market price · 0% premium · you choose amount & methods'; + + @override + String get presetCustomTitle => 'Custom'; + + @override + String get presetCustomSubtitle => + 'All fields — amount, range, methods, premium, fixed or market price'; + + @override + String expressPresetSubtitle(String details) { + return 'Same as your last successful order — $details'; + } + + @override + String expressPremiumSuffix(String premium) { + return '$premium% premium'; + } + + @override + String get paymentMethodsLabel => 'Payment Methods'; + + @override + String get addPaymentMethod => 'Add payment method'; + + @override + String get customPaymentMethodHint => 'Custom payment method...'; + + @override + String get customMethodAppendedNote => + 'Custom method will be appended to selection'; + + @override + String get selectPaymentMethodsTitle => 'Select Payment Methods'; + + @override + String amountRangeError(String min, String max) { + return 'Amount must be between $min and $max'; + } + + @override + String get enterAmountTitle => 'Enter Amount'; + + @override + String minMaxRangeLabel(String min, String max, String currency) { + return 'Min: $min – Max: $max $currency'; + } } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index e0b0fb3c..2f3b7d09 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1869,4 +1869,91 @@ class AppLocalizationsEs extends AppLocalizations { String marketPricePremium(String premium) { return 'mercado $premium%'; } + + @override + String get priceTypeLabel => 'Tipo de precio'; + + @override + String get priceTypeMarket => 'Mercado'; + + @override + String get priceTypeFixed => 'Fijo'; + + @override + String get priceTypeInfoTooltip => 'Info del tipo de precio'; + + @override + String get premiumSectionLabel => 'Prima'; + + @override + String get amountInSatsHint => 'Monto en sats'; + + @override + String get priceTypesDialogTitle => 'Tipos de precio'; + + @override + String get priceTypesDialogContent => + 'Precio de mercado: el precio de tu orden sigue la tasa del mercado con un porcentaje de prima/descuento aplicado.\n\nPrecio fijo: estableces un precio exacto en satoshis.'; + + @override + String get startFromPreset => 'EMPEZAR DESDE UN PRESET'; + + @override + String get presetExpressTitle => 'Exprés'; + + @override + String get recommendedTag => 'RECOMENDADO'; + + @override + String get presetConservativeTitle => 'Conservador'; + + @override + String get presetConservativeSubtitle => + 'Precio de mercado · 0% de prima · eliges monto y métodos'; + + @override + String get presetCustomTitle => 'Personalizado'; + + @override + String get presetCustomSubtitle => + 'Todos los campos — monto, rango, métodos, prima, precio fijo o de mercado'; + + @override + String expressPresetSubtitle(String details) { + return 'Igual que tu última orden exitosa — $details'; + } + + @override + String expressPremiumSuffix(String premium) { + return '$premium% de prima'; + } + + @override + String get paymentMethodsLabel => 'Métodos de pago'; + + @override + String get addPaymentMethod => 'Agregar método de pago'; + + @override + String get customPaymentMethodHint => 'Método de pago personalizado...'; + + @override + String get customMethodAppendedNote => + 'El método personalizado se añadirá a la selección'; + + @override + String get selectPaymentMethodsTitle => 'Selecciona métodos de pago'; + + @override + String amountRangeError(String min, String max) { + return 'El monto debe estar entre $min y $max'; + } + + @override + String get enterAmountTitle => 'Ingresa el monto'; + + @override + String minMaxRangeLabel(String min, String max, String currency) { + return 'Mín: $min – Máx: $max $currency'; + } } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 39c15ea2..bdbe8788 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1878,4 +1878,91 @@ class AppLocalizationsFr extends AppLocalizations { String marketPricePremium(String premium) { return 'marché $premium%'; } + + @override + String get priceTypeLabel => 'Type de prix'; + + @override + String get priceTypeMarket => 'Marché'; + + @override + String get priceTypeFixed => 'Fixe'; + + @override + String get priceTypeInfoTooltip => 'Infos sur le type de prix'; + + @override + String get premiumSectionLabel => 'Prime'; + + @override + String get amountInSatsHint => 'Montant en sats'; + + @override + String get priceTypesDialogTitle => 'Types de prix'; + + @override + String get priceTypesDialogContent => + 'Prix du marché : le prix de votre ordre suit le taux du marché avec un pourcentage de prime/remise appliqué.\n\nPrix fixe : vous définissez un prix exact en satoshis.'; + + @override + String get startFromPreset => 'PARTIR D\'UN PRÉRÉGLAGE'; + + @override + String get presetExpressTitle => 'Express'; + + @override + String get recommendedTag => 'RECOMMANDÉ'; + + @override + String get presetConservativeTitle => 'Conservateur'; + + @override + String get presetConservativeSubtitle => + 'Prix du marché · 0% de prime · vous choisissez le montant et les méthodes'; + + @override + String get presetCustomTitle => 'Personnalisé'; + + @override + String get presetCustomSubtitle => + 'Tous les champs — montant, plage, méthodes, prime, prix fixe ou du marché'; + + @override + String expressPresetSubtitle(String details) { + return 'Comme votre dernier ordre réussi — $details'; + } + + @override + String expressPremiumSuffix(String premium) { + return '$premium% de prime'; + } + + @override + String get paymentMethodsLabel => 'Moyens de paiement'; + + @override + String get addPaymentMethod => 'Ajouter un moyen de paiement'; + + @override + String get customPaymentMethodHint => 'Moyen de paiement personnalisé...'; + + @override + String get customMethodAppendedNote => + 'Le moyen personnalisé sera ajouté à la sélection'; + + @override + String get selectPaymentMethodsTitle => 'Sélectionnez les moyens de paiement'; + + @override + String amountRangeError(String min, String max) { + return 'Le montant doit être entre $min et $max'; + } + + @override + String get enterAmountTitle => 'Saisir le montant'; + + @override + String minMaxRangeLabel(String min, String max, String currency) { + return 'Min : $min – Max : $max $currency'; + } } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 19d6b517..15544067 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1867,4 +1867,91 @@ class AppLocalizationsIt extends AppLocalizations { String marketPricePremium(String premium) { return 'mercato $premium%'; } + + @override + String get priceTypeLabel => 'Tipo di prezzo'; + + @override + String get priceTypeMarket => 'Mercato'; + + @override + String get priceTypeFixed => 'Fisso'; + + @override + String get priceTypeInfoTooltip => 'Info sul tipo di prezzo'; + + @override + String get premiumSectionLabel => 'Premio'; + + @override + String get amountInSatsHint => 'Importo in sats'; + + @override + String get priceTypesDialogTitle => 'Tipi di prezzo'; + + @override + String get priceTypesDialogContent => + 'Prezzo di mercato: il prezzo del tuo ordine segue il tasso di mercato con una percentuale di premio/sconto applicata.\n\nPrezzo fisso: imposti un prezzo esatto in satoshi.'; + + @override + String get startFromPreset => 'INIZIA DA UN PRESET'; + + @override + String get presetExpressTitle => 'Express'; + + @override + String get recommendedTag => 'CONSIGLIATO'; + + @override + String get presetConservativeTitle => 'Conservativo'; + + @override + String get presetConservativeSubtitle => + 'Prezzo di mercato · 0% di premio · scegli importo e metodi'; + + @override + String get presetCustomTitle => 'Personalizzato'; + + @override + String get presetCustomSubtitle => + 'Tutti i campi — importo, intervallo, metodi, premio, prezzo fisso o di mercato'; + + @override + String expressPresetSubtitle(String details) { + return 'Come il tuo ultimo ordine riuscito — $details'; + } + + @override + String expressPremiumSuffix(String premium) { + return '$premium% di premio'; + } + + @override + String get paymentMethodsLabel => 'Metodi di pagamento'; + + @override + String get addPaymentMethod => 'Aggiungi metodo di pagamento'; + + @override + String get customPaymentMethodHint => 'Metodo di pagamento personalizzato...'; + + @override + String get customMethodAppendedNote => + 'Il metodo personalizzato verrà aggiunto alla selezione'; + + @override + String get selectPaymentMethodsTitle => 'Seleziona i metodi di pagamento'; + + @override + String amountRangeError(String min, String max) { + return 'L\'importo deve essere compreso tra $min e $max'; + } + + @override + String get enterAmountTitle => 'Inserisci l\'importo'; + + @override + String minMaxRangeLabel(String min, String max, String currency) { + return 'Min: $min – Max: $max $currency'; + } } From 4601ee1514db294398bc54bc24abce014e8a0ec9 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 02:38:13 -0300 Subject: [PATCH 14/30] feat: localize dispute and rating screen strings Replace hardcoded English text in dispute_chat_screen.dart, dispute_messages_list.dart, disputes_list.dart, rate_counterpart_screen.dart, star_rating.dart, and home_screen.dart with localized strings from AppLocalizations. Adds 8 new l10n keys across en/de/es/fr/it for dispute details (title, IDs, reason, admin label), rating UI (submit/close buttons, star tooltips, error messages), and menu tooltip. --- .../disputes/screens/dispute_chat_screen.dart | 2 +- .../widgets/dispute_messages_list.dart | 12 ++--- .../disputes/widgets/disputes_list.dart | 2 +- lib/features/home/screens/home_screen.dart | 2 +- .../rate/screens/rate_counterpart_screen.dart | 20 ++++---- lib/features/rate/widgets/star_rating.dart | 3 +- lib/l10n/app_de.arb | 10 +++- lib/l10n/app_en.arb | 24 +++++++++- lib/l10n/app_es.arb | 10 +++- lib/l10n/app_fr.arb | 10 +++- lib/l10n/app_it.arb | 10 +++- lib/l10n/app_localizations.dart | 48 +++++++++++++++++++ lib/l10n/app_localizations_de.dart | 35 ++++++++++++++ lib/l10n/app_localizations_en.dart | 34 +++++++++++++ lib/l10n/app_localizations_es.dart | 34 +++++++++++++ lib/l10n/app_localizations_fr.dart | 34 +++++++++++++ lib/l10n/app_localizations_it.dart | 34 +++++++++++++ 17 files changed, 300 insertions(+), 24 deletions(-) diff --git a/lib/features/disputes/screens/dispute_chat_screen.dart b/lib/features/disputes/screens/dispute_chat_screen.dart index fb33e0fe..4819db23 100644 --- a/lib/features/disputes/screens/dispute_chat_screen.dart +++ b/lib/features/disputes/screens/dispute_chat_screen.dart @@ -77,7 +77,7 @@ class _DisputeChatScreenState extends ConsumerState { if (dispute == null) { final l10n = AppLocalizations.of(context); return Scaffold( - appBar: AppBar(title: const Text('Dispute')), + appBar: AppBar(title: Text(l10n.disputeScreenTitle)), body: Center(child: Text(l10n.disputeNotFound)), ); } diff --git a/lib/features/disputes/widgets/dispute_messages_list.dart b/lib/features/disputes/widgets/dispute_messages_list.dart index 75dac1c0..1c86938b 100644 --- a/lib/features/disputes/widgets/dispute_messages_list.dart +++ b/lib/features/disputes/widgets/dispute_messages_list.dart @@ -140,7 +140,7 @@ class DisputeInfoCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Dispute Details', + AppLocalizations.of(context).disputeDetailsTitle, style: textTheme.bodyLarge?.copyWith( color: colors.textPrimary, fontWeight: FontWeight.bold, @@ -148,13 +148,13 @@ class DisputeInfoCard extends StatelessWidget { ), const SizedBox(height: AppSpacing.sm), _IdRow( - label: 'Order ID', + label: AppLocalizations.of(context).orderIdLabel, value: dispute.tradeId, colors: colors, textTheme: textTheme, ), _IdRow( - label: 'Dispute ID', + label: AppLocalizations.of(context).disputeIdLabel, value: dispute.id, colors: colors, textTheme: textTheme, @@ -162,7 +162,7 @@ class DisputeInfoCard extends StatelessWidget { if (dispute.reason != null) ...[ const SizedBox(height: AppSpacing.xs), Text( - 'Reason: ${dispute.reason}', + AppLocalizations.of(context).disputeReasonLabel(dispute.reason!), style: textTheme.bodySmall?.copyWith(color: colors.textSecondary), ), ], @@ -193,7 +193,7 @@ class _IdRow extends StatelessWidget { Clipboard.setData(ClipboardData(text: value)); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('$label copied'), + content: Text(AppLocalizations.of(context).aboutCopiedToClipboard), duration: const Duration(seconds: 2), ), ); @@ -318,7 +318,7 @@ class DisputeMessageBubble extends StatelessWidget { children: [ if (message.isAdmin && !message.isMine) Text( - 'Admin', + AppLocalizations.of(context).adminLabel, style: textTheme.bodySmall?.copyWith( color: colors.tealAccent, fontWeight: FontWeight.bold, diff --git a/lib/features/disputes/widgets/disputes_list.dart b/lib/features/disputes/widgets/disputes_list.dart index 4d40acbc..642c97fe 100644 --- a/lib/features/disputes/widgets/disputes_list.dart +++ b/lib/features/disputes/widgets/disputes_list.dart @@ -109,7 +109,7 @@ class _ErrorState extends StatelessWidget { const SizedBox(height: AppSpacing.md), OutlinedButton( onPressed: onRetry, - child: const Text('Retry'), + child: Text(AppLocalizations.of(context).retry), ), ], ), diff --git a/lib/features/home/screens/home_screen.dart b/lib/features/home/screens/home_screen.dart index a704e5b3..2b23550f 100644 --- a/lib/features/home/screens/home_screen.dart +++ b/lib/features/home/screens/home_screen.dart @@ -305,7 +305,7 @@ class _MostroAppBar extends StatelessWidget { IconButton( onPressed: onMenuTap, icon: const Icon(Icons.menu, size: 24), - tooltip: 'Menu', + tooltip: AppLocalizations.of(context).menuTooltip, ), const Spacer(), GestureDetector( diff --git a/lib/features/rate/screens/rate_counterpart_screen.dart b/lib/features/rate/screens/rate_counterpart_screen.dart index 662eaa84..bb5eda87 100644 --- a/lib/features/rate/screens/rate_counterpart_screen.dart +++ b/lib/features/rate/screens/rate_counterpart_screen.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/rate/widgets/star_rating.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/src/rust/api/reputation.dart' as reputation_api; /// Rate counterpart screen — Route `/rate_user/:orderId`. @@ -46,7 +47,7 @@ class _RateCounterpartScreenState } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Rating failed: $e')), + SnackBar(content: Text(AppLocalizations.of(context).ratingFailed)), ); } finally { if (mounted) setState(() => _isSubmitting = false); @@ -60,6 +61,7 @@ class _RateCounterpartScreenState final textTheme = Theme.of(context).textTheme; final green = colors.mostroGreen; + final l10n = AppLocalizations.of(context); return Scaffold( backgroundColor: colors.backgroundDark, @@ -72,7 +74,7 @@ class _RateCounterpartScreenState // ── "RATE" header ───────────────────────────────────────── Text( - 'RATE', + l10n.rateScreenHeader, style: textTheme.labelMedium?.copyWith( color: colors.textSubtle, letterSpacing: 2, @@ -92,7 +94,7 @@ class _RateCounterpartScreenState ), const SizedBox(height: AppSpacing.sm), Text( - 'Successful order', + l10n.successfulOrder, style: textTheme.titleMedium?.copyWith( color: green, fontWeight: FontWeight.bold, @@ -142,9 +144,9 @@ class _RateCounterpartScreenState color: Colors.black54, ), ) - : const Text( - 'SUBMIT', - style: TextStyle(fontWeight: FontWeight.bold), + : Text( + l10n.submitUppercaseButton, + style: const TextStyle(fontWeight: FontWeight.bold), ), ), @@ -161,9 +163,9 @@ class _RateCounterpartScreenState borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text( - 'CLOSE', - style: TextStyle(fontWeight: FontWeight.bold), + child: Text( + l10n.closeRatingButton, + style: const TextStyle(fontWeight: FontWeight.bold), ), ), diff --git a/lib/features/rate/widgets/star_rating.dart b/lib/features/rate/widgets/star_rating.dart index 4b5ae216..896507fa 100644 --- a/lib/features/rate/widgets/star_rating.dart +++ b/lib/features/rate/widgets/star_rating.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Interactive 5-star rating selector. /// @@ -41,7 +42,7 @@ class StarRating extends StatelessWidget { final starNumber = index + 1; return IconButton( onPressed: onChanged == null ? null : () => onChanged!(starNumber), - tooltip: 'Select $starNumber star${starNumber == 1 ? '' : 's'}', + tooltip: AppLocalizations.of(context).selectStarTooltip(starNumber), padding: const EdgeInsets.symmetric(horizontal: 4), constraints: BoxConstraints(minWidth: starSize + 8, minHeight: starSize + 8), icon: Icon( diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 0f3f9b60..cc08131e 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -598,5 +598,13 @@ "selectPaymentMethodsTitle": "Zahlungsmethoden auswählen", "amountRangeError": "Der Betrag muss zwischen {min} und {max} liegen", "enterAmountTitle": "Betrag eingeben", - "minMaxRangeLabel": "Min: {min} – Max: {max} {currency}" + "minMaxRangeLabel": "Min: {min} – Max: {max} {currency}", + "ratingFailed": "Bewertung fehlgeschlagen. Bitte versuche es erneut.", + "submitUppercaseButton": "ABSENDEN", + "selectStarTooltip": "{count, plural, =1{1 Stern auswählen} other{{count} Sterne auswählen}}", + "disputeDetailsTitle": "Streitfall-Details", + "disputeIdLabel": "Streitfall-ID", + "disputeReasonLabel": "Grund: {reason}", + "adminLabel": "Administrator", + "disputeScreenTitle": "Streitfall" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 863c3b82..99b35dbb 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1305,5 +1305,27 @@ "@minMaxRangeLabel": { "description": "Range bounds label in the amount dialog", "placeholders": {"min": {"type": "String"}, "max": {"type": "String"}, "currency": {"type": "String"}} - } + }, + "ratingFailed": "Rating failed. Please try again.", + "@ratingFailed": {"description": "Snackbar shown when submitting a rating fails"}, + "submitUppercaseButton": "SUBMIT", + "@submitUppercaseButton": {"description": "Uppercase submit button on the rate screen"}, + "selectStarTooltip": "{count, plural, =1{Select 1 star} other{Select {count} stars}}", + "@selectStarTooltip": { + "description": "Tooltip for a star in the rating selector", + "placeholders": {"count": {"type": "int"}} + }, + "disputeDetailsTitle": "Dispute Details", + "@disputeDetailsTitle": {"description": "Title of the dispute info card"}, + "disputeIdLabel": "Dispute ID", + "@disputeIdLabel": {"description": "Label for the dispute ID field"}, + "disputeReasonLabel": "Reason: {reason}", + "@disputeReasonLabel": { + "description": "Label showing the dispute reason", + "placeholders": {"reason": {"type": "String"}} + }, + "adminLabel": "Admin", + "@adminLabel": {"description": "Sender label for administrator messages in a dispute"}, + "disputeScreenTitle": "Dispute", + "@disputeScreenTitle": {"description": "App bar title of the dispute chat screen"} } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 5bdf7ed6..cc34c5f0 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -598,5 +598,13 @@ "selectPaymentMethodsTitle": "Selecciona métodos de pago", "amountRangeError": "El monto debe estar entre {min} y {max}", "enterAmountTitle": "Ingresa el monto", - "minMaxRangeLabel": "Mín: {min} – Máx: {max} {currency}" + "minMaxRangeLabel": "Mín: {min} – Máx: {max} {currency}", + "ratingFailed": "La calificación falló. Inténtalo de nuevo.", + "submitUppercaseButton": "ENVIAR", + "selectStarTooltip": "{count, plural, =1{Seleccionar 1 estrella} other{Seleccionar {count} estrellas}}", + "disputeDetailsTitle": "Detalles de la disputa", + "disputeIdLabel": "ID de disputa", + "disputeReasonLabel": "Motivo: {reason}", + "adminLabel": "Administrador", + "disputeScreenTitle": "Disputa" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 27d781b2..1b8ebef9 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -598,5 +598,13 @@ "selectPaymentMethodsTitle": "Sélectionnez les moyens de paiement", "amountRangeError": "Le montant doit être entre {min} et {max}", "enterAmountTitle": "Saisir le montant", - "minMaxRangeLabel": "Min : {min} – Max : {max} {currency}" + "minMaxRangeLabel": "Min : {min} – Max : {max} {currency}", + "ratingFailed": "Échec de l'évaluation. Veuillez réessayer.", + "submitUppercaseButton": "ENVOYER", + "selectStarTooltip": "{count, plural, =1{Sélectionner 1 étoile} other{Sélectionner {count} étoiles}}", + "disputeDetailsTitle": "Détails du litige", + "disputeIdLabel": "ID du litige", + "disputeReasonLabel": "Raison : {reason}", + "adminLabel": "Administrateur", + "disputeScreenTitle": "Litige" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 53d69b6e..555b2470 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -598,5 +598,13 @@ "selectPaymentMethodsTitle": "Seleziona i metodi di pagamento", "amountRangeError": "L'importo deve essere compreso tra {min} e {max}", "enterAmountTitle": "Inserisci l'importo", - "minMaxRangeLabel": "Min: {min} – Max: {max} {currency}" + "minMaxRangeLabel": "Min: {min} – Max: {max} {currency}", + "ratingFailed": "Valutazione non riuscita. Riprova.", + "submitUppercaseButton": "INVIA", + "selectStarTooltip": "{count, plural, =1{Seleziona 1 stella} other{Seleziona {count} stelle}}", + "disputeDetailsTitle": "Dettagli della disputa", + "disputeIdLabel": "ID disputa", + "disputeReasonLabel": "Motivo: {reason}", + "adminLabel": "Amministratore", + "disputeScreenTitle": "Disputa" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index c3bd6b20..e542dbba 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3547,6 +3547,54 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Min: {min} – Max: {max} {currency}'** String minMaxRangeLabel(String min, String max, String currency); + + /// Snackbar shown when submitting a rating fails + /// + /// In en, this message translates to: + /// **'Rating failed. Please try again.'** + String get ratingFailed; + + /// Uppercase submit button on the rate screen + /// + /// In en, this message translates to: + /// **'SUBMIT'** + String get submitUppercaseButton; + + /// Tooltip for a star in the rating selector + /// + /// In en, this message translates to: + /// **'{count, plural, =1{Select 1 star} other{Select {count} stars}}'** + String selectStarTooltip(int count); + + /// Title of the dispute info card + /// + /// In en, this message translates to: + /// **'Dispute Details'** + String get disputeDetailsTitle; + + /// Label for the dispute ID field + /// + /// In en, this message translates to: + /// **'Dispute ID'** + String get disputeIdLabel; + + /// Label showing the dispute reason + /// + /// In en, this message translates to: + /// **'Reason: {reason}'** + String disputeReasonLabel(String reason); + + /// Sender label for administrator messages in a dispute + /// + /// In en, this message translates to: + /// **'Admin'** + String get adminLabel; + + /// App bar title of the dispute chat screen + /// + /// In en, this message translates to: + /// **'Dispute'** + String get disputeScreenTitle; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 6731e5d7..2d703b9e 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1961,4 +1961,39 @@ class AppLocalizationsDe extends AppLocalizations { String minMaxRangeLabel(String min, String max, String currency) { return 'Min: $min – Max: $max $currency'; } + + @override + String get ratingFailed => + 'Bewertung fehlgeschlagen. Bitte versuche es erneut.'; + + @override + String get submitUppercaseButton => 'ABSENDEN'; + + @override + String selectStarTooltip(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count Sterne auswählen', + one: '1 Stern auswählen', + ); + return '$_temp0'; + } + + @override + String get disputeDetailsTitle => 'Streitfall-Details'; + + @override + String get disputeIdLabel => 'Streitfall-ID'; + + @override + String disputeReasonLabel(String reason) { + return 'Grund: $reason'; + } + + @override + String get adminLabel => 'Administrator'; + + @override + String get disputeScreenTitle => 'Streitfall'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index ad69e6cd..987ec0ca 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1932,4 +1932,38 @@ class AppLocalizationsEn extends AppLocalizations { String minMaxRangeLabel(String min, String max, String currency) { return 'Min: $min – Max: $max $currency'; } + + @override + String get ratingFailed => 'Rating failed. Please try again.'; + + @override + String get submitUppercaseButton => 'SUBMIT'; + + @override + String selectStarTooltip(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Select $count stars', + one: 'Select 1 star', + ); + return '$_temp0'; + } + + @override + String get disputeDetailsTitle => 'Dispute Details'; + + @override + String get disputeIdLabel => 'Dispute ID'; + + @override + String disputeReasonLabel(String reason) { + return 'Reason: $reason'; + } + + @override + String get adminLabel => 'Admin'; + + @override + String get disputeScreenTitle => 'Dispute'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 2f3b7d09..064a4333 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1956,4 +1956,38 @@ class AppLocalizationsEs extends AppLocalizations { String minMaxRangeLabel(String min, String max, String currency) { return 'Mín: $min – Máx: $max $currency'; } + + @override + String get ratingFailed => 'La calificación falló. Inténtalo de nuevo.'; + + @override + String get submitUppercaseButton => 'ENVIAR'; + + @override + String selectStarTooltip(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Seleccionar $count estrellas', + one: 'Seleccionar 1 estrella', + ); + return '$_temp0'; + } + + @override + String get disputeDetailsTitle => 'Detalles de la disputa'; + + @override + String get disputeIdLabel => 'ID de disputa'; + + @override + String disputeReasonLabel(String reason) { + return 'Motivo: $reason'; + } + + @override + String get adminLabel => 'Administrador'; + + @override + String get disputeScreenTitle => 'Disputa'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index bdbe8788..e990e192 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1965,4 +1965,38 @@ class AppLocalizationsFr extends AppLocalizations { String minMaxRangeLabel(String min, String max, String currency) { return 'Min : $min – Max : $max $currency'; } + + @override + String get ratingFailed => 'Échec de l\'évaluation. Veuillez réessayer.'; + + @override + String get submitUppercaseButton => 'ENVOYER'; + + @override + String selectStarTooltip(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Sélectionner $count étoiles', + one: 'Sélectionner 1 étoile', + ); + return '$_temp0'; + } + + @override + String get disputeDetailsTitle => 'Détails du litige'; + + @override + String get disputeIdLabel => 'ID du litige'; + + @override + String disputeReasonLabel(String reason) { + return 'Raison : $reason'; + } + + @override + String get adminLabel => 'Administrateur'; + + @override + String get disputeScreenTitle => 'Litige'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 15544067..1e680783 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1954,4 +1954,38 @@ class AppLocalizationsIt extends AppLocalizations { String minMaxRangeLabel(String min, String max, String currency) { return 'Min: $min – Max: $max $currency'; } + + @override + String get ratingFailed => 'Valutazione non riuscita. Riprova.'; + + @override + String get submitUppercaseButton => 'INVIA'; + + @override + String selectStarTooltip(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Seleziona $count stelle', + one: 'Seleziona 1 stella', + ); + return '$_temp0'; + } + + @override + String get disputeDetailsTitle => 'Dettagli della disputa'; + + @override + String get disputeIdLabel => 'ID disputa'; + + @override + String disputeReasonLabel(String reason) { + return 'Motivo: $reason'; + } + + @override + String get adminLabel => 'Amministratore'; + + @override + String get disputeScreenTitle => 'Disputa'; } From 359f7f444d3e91826978adcac1e1cb842ae292d8 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 02:41:44 -0300 Subject: [PATCH 15/30] feat: use device language as default and validate stored language Replace hardcoded 'en' default with device language when supported by AppLocalizations. Add validation in localeProvider to fall back to English when stored language is unknown or corrupted. Extracts supported language codes from AppLocalizations.supportedLocales to ensure consistency between settings and available translations. --- .../settings/providers/settings_provider.dart | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/features/settings/providers/settings_provider.dart b/lib/features/settings/providers/settings_provider.dart index fb4c13ce..bb82148a 100644 --- a/lib/features/settings/providers/settings_provider.dart +++ b/lib/features/settings/providers/settings_provider.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:mostro/l10n/app_localizations.dart'; + // ── Preference keys ──────────────────────────────────────────────────────────── const _kLanguage = 'settings.language'; @@ -53,7 +55,7 @@ class AppSettingsState { factory AppSettingsState.fromPrefs(SharedPreferences prefs) { final themeModeStr = prefs.getString(_kThemeMode) ?? 'dark'; return AppSettingsState( - language: prefs.getString(_kLanguage) ?? 'en', + language: prefs.getString(_kLanguage) ?? _deviceDefaultLanguage(), defaultFiatCode: prefs.getString(_kFiatCode), defaultLightningAddress: prefs.getString(_kLightningAddress), loggingEnabled: prefs.getBool(_kLoggingEnabled) ?? false, @@ -122,11 +124,33 @@ final settingsProvider = (ref) => SettingsNotifier(), // no-persistence fallback; replaced in main() ); +/// Language codes the app ships translations for, derived from the generated +/// [AppLocalizations]. Used to validate stored and device languages. +final Set _supportedLanguageCodes = + AppLocalizations.supportedLocales.map((l) => l.languageCode).toSet(); + +/// The device's language when the app supports it, otherwise English. +/// +/// Used as the first-run default so a new user starts in their own language +/// instead of always defaulting to English. +String _deviceDefaultLanguage() { + final deviceCode = + WidgetsBinding.instance.platformDispatcher.locale.languageCode; + return _supportedLanguageCodes.contains(deviceCode) ? deviceCode : 'en'; +} + /// Current display locale, derived automatically from [settingsProvider]. /// /// Rebuilds whenever [AppSettingsState.language] changes so that /// [MaterialApp.router] locale stays in sync without manual updates. +/// +/// The stored language is validated against [AppLocalizations.supportedLocales] +/// and falls back to English when it is unknown, so a corrupted or unsupported +/// preference never leaves the UI unlocalized. final localeProvider = Provider((ref) { final language = ref.watch(settingsProvider.select((s) => s.language)); - return Locale(language); + final code = language.split(RegExp(r'[-_]')).first; + return _supportedLanguageCodes.contains(code) + ? Locale(code) + : const Locale('en'); }); From 666936e13cf0b7a081a84245b9f87ee80f99cf16 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 02:43:30 -0300 Subject: [PATCH 16/30] feat: configure untranslated l10n keys report and ignore generated file Add untranslated-messages-file to l10n.yaml to track missing translations across locales. Ignore generated lib/l10n/untranslated_messages.txt from version control as it's regenerated by flutter gen-l10n and intended for CI validation. --- .gitignore | 3 +++ l10n.yaml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index e1fbdbf7..94ebcbad 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ pubspec.lock # flutter_rust_bridge generated bindings (regenerated via: flutter_rust_bridge_codegen generate) lib/src/rust/ +# l10n untranslated-keys report (regenerated via: flutter gen-l10n; must be empty) +lib/l10n/untranslated_messages.txt + # Rust rust/target/ rust_builder/target/ diff --git a/l10n.yaml b/l10n.yaml index a4d323c6..f4f5238b 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -3,3 +3,7 @@ template-arb-file: app_en.arb output-localization-file: app_localizations.dart output-class: AppLocalizations nullable-getter: false +# Report keys present in the template (app_en.arb) but missing from any other +# locale. Regenerated by `flutter gen-l10n`; kept out of version control and +# checked in CI so a new key never lands untranslated. Currently empty. +untranslated-messages-file: lib/l10n/untranslated_messages.txt From 94f6eb043afce21bfdc2362bebb05a12208328c0 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 20:48:39 -0300 Subject: [PATCH 17/30] refactor: update l10n documentation and remove unused Mostro node strings Update AppLocalizations documentation for mostroNodeTitle and trustedBadgeLabel to reflect their actual usage context. Remove unused mostroNodeTitle and trustedBadgeLabel keys from de/es/fr/it translations. Change Italian trustedBadgeLabel from 'Attendibile' to 'Affidabile' for consistency. --- lib/l10n/app_de.arb | 2 -- lib/l10n/app_en.arb | 4 ---- lib/l10n/app_es.arb | 2 -- lib/l10n/app_fr.arb | 2 -- lib/l10n/app_it.arb | 2 -- lib/l10n/app_localizations.dart | 4 ++-- lib/l10n/app_localizations_it.dart | 2 +- 7 files changed, 3 insertions(+), 15 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index cc08131e..347c53ca 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -391,9 +391,7 @@ "noCurrenciesFoundMessage": "Keine Währungen gefunden", "failedToResetNodeMessage": "Knoten konnte nicht zurückgesetzt werden", "invalidPubkeyOrBridgeErrorMessage": "Ungültiger Public Key oder Bridge-Fehler", - "mostroNodeTitle": "Mostro-Knoten", "currentNodePublicKeyLabel": "Öffentlicher Schlüssel des aktuellen Knotens", - "trustedBadgeLabel": "Vertrauenswürdig", "useCustomNodePubkeyLabel": "Eigenen Knoten-Public-Key verwenden", "enterHexPubkeyHint": "Gib einen 64-stelligen Hex-Public-Key ein", "shareLogsTooltip": "Protokolle teilen", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 99b35dbb..d4aec31b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -806,12 +806,8 @@ "@failedToResetNodeMessage": {"description": "Error shown when resetting to the default Mostro node fails"}, "invalidPubkeyOrBridgeErrorMessage": "Invalid pubkey or bridge error", "@invalidPubkeyOrBridgeErrorMessage": {"description": "Error shown when setting a custom node pubkey fails"}, - "mostroNodeTitle": "Mostro Node", - "@mostroNodeTitle": {"description": "Title of the Mostro node selector bottom sheet"}, "currentNodePublicKeyLabel": "Current node public key", "@currentNodePublicKeyLabel": {"description": "Accessibility label for the current node public key"}, - "trustedBadgeLabel": "Trusted", - "@trustedBadgeLabel": {"description": "Badge shown when the active node is the default trusted node"}, "useCustomNodePubkeyLabel": "Use a custom node pubkey", "@useCustomNodePubkeyLabel": {"description": "Label above the custom node pubkey input"}, "enterHexPubkeyHint": "Enter 64-char hex pubkey", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index cc34c5f0..526e0f5b 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -391,9 +391,7 @@ "noCurrenciesFoundMessage": "No se encontraron monedas", "failedToResetNodeMessage": "No se pudo restablecer el nodo", "invalidPubkeyOrBridgeErrorMessage": "Clave pública inválida o error del puente", - "mostroNodeTitle": "Nodo Mostro", "currentNodePublicKeyLabel": "Clave pública del nodo actual", - "trustedBadgeLabel": "De confianza", "useCustomNodePubkeyLabel": "Usar una clave pública de nodo personalizada", "enterHexPubkeyHint": "Ingresa clave pública hex de 64 caracteres", "shareLogsTooltip": "Compartir registros", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 1b8ebef9..31d6ceab 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -391,9 +391,7 @@ "noCurrenciesFoundMessage": "Aucune devise trouvée", "failedToResetNodeMessage": "Échec de la réinitialisation du nœud", "invalidPubkeyOrBridgeErrorMessage": "Clé publique invalide ou erreur du pont", - "mostroNodeTitle": "Nœud Mostro", "currentNodePublicKeyLabel": "Clé publique du nœud actuel", - "trustedBadgeLabel": "De confiance", "useCustomNodePubkeyLabel": "Utiliser une clé publique de nœud personnalisée", "enterHexPubkeyHint": "Entrez une clé publique hex de 64 caractères", "shareLogsTooltip": "Partager les journaux", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 555b2470..7cdebc86 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -391,9 +391,7 @@ "noCurrenciesFoundMessage": "Nessuna valuta trovata", "failedToResetNodeMessage": "Impossibile reimpostare il nodo", "invalidPubkeyOrBridgeErrorMessage": "Chiave pubblica non valida o errore del bridge", - "mostroNodeTitle": "Nodo Mostro", "currentNodePublicKeyLabel": "Chiave pubblica del nodo attuale", - "trustedBadgeLabel": "Attendibile", "useCustomNodePubkeyLabel": "Usa una chiave pubblica del nodo personalizzata", "enterHexPubkeyHint": "Inserisci una chiave pubblica hex di 64 caratteri", "shareLogsTooltip": "Condividi i log", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e542dbba..08920384 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -962,7 +962,7 @@ abstract class AppLocalizations { /// **'Paste or scan a QR code'** String get pasteOrScanQrCode; - /// Title of the Mostro node selector bottom sheet + /// Section title on the Mostro node settings screen /// /// In en, this message translates to: /// **'Mostro Node'** @@ -974,7 +974,7 @@ abstract class AppLocalizations { /// **'Current Node'** String get currentNodeLabel; - /// Badge shown when the active node is the default trusted node + /// Badge shown on a verified/trusted Mostro node /// /// In en, this message translates to: /// **'Trusted'** diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 1e680783..c900065d 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -490,7 +490,7 @@ class AppLocalizationsIt extends AppLocalizations { String get currentNodeLabel => 'Nodo attuale'; @override - String get trustedBadgeLabel => 'Attendibile'; + String get trustedBadgeLabel => 'Affidabile'; @override String get useDefaultButtonLabel => 'Usa predefinito'; From adf73efa7a846d2a5b88e392e080d728535dbc2a Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 20:50:38 -0300 Subject: [PATCH 18/30] refactor: pass l10n and rooms as parameters to _resolveRoom and _buildRoomPreview Move AppLocalizations and chatRoomsNotifierProvider reads to call sites instead of accessing them inside _resolveRoom and _buildRoomPreview methods. This makes dependencies explicit and improves testability by removing context/ref access from helper methods. --- .../chat/screens/chat_room_screen.dart | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/features/chat/screens/chat_room_screen.dart b/lib/features/chat/screens/chat_room_screen.dart index 8f47c2ec..172fc2d9 100644 --- a/lib/features/chat/screens/chat_room_screen.dart +++ b/lib/features/chat/screens/chat_room_screen.dart @@ -103,6 +103,7 @@ class _ChatRoomScreenState extends ConsumerState { Future _onSend(String text) async { if (text.trim().isEmpty || _isSending) return; + final l10n = AppLocalizations.of(context); setState(() => _isSending = true); try { final sent = await messages_api.sendMessage( @@ -113,13 +114,17 @@ class _ChatRoomScreenState extends ConsumerState { setState(() => _messages.add(sent)); _scrollToBottom(); ref.read(chatRoomsNotifierProvider.notifier).upsertRoom( - _buildRoomPreview(lastMsg: sent), + _buildRoomPreview( + lastMsg: sent, + rooms: ref.read(chatRoomsNotifierProvider), + l10n: l10n, + ), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(AppLocalizations.of(context).messageSendFailed), + content: Text(l10n.messageSendFailed), backgroundColor: Colors.red, ), ); @@ -143,7 +148,11 @@ class _ChatRoomScreenState extends ConsumerState { _scrollToBottom(); _markRead(); ref.read(chatRoomsNotifierProvider.notifier).upsertRoom( - _buildRoomPreview(lastMsg: msg), + _buildRoomPreview( + lastMsg: msg, + rooms: ref.read(chatRoomsNotifierProvider), + l10n: AppLocalizations.of(context), + ), ); } @@ -171,14 +180,13 @@ class _ChatRoomScreenState extends ConsumerState { if (_showUserInfo) _showTradeInfo = false; }); - ChatRoomState _resolveRoom() { - final rooms = ref.watch(chatRoomsNotifierProvider); + ChatRoomState _resolveRoom(List rooms, AppLocalizations l10n) { return rooms.firstWhere( (r) => r.orderId == widget.orderId, orElse: () => ChatRoomState( orderId: widget.orderId, peerPubkey: '', - peerHandle: AppLocalizations.of(context).unknownPeerHandle, + peerHandle: l10n.unknownPeerHandle, peerIconIndex: 0, peerColorHue: 180, isSelling: false, @@ -186,8 +194,12 @@ class _ChatRoomScreenState extends ConsumerState { ); } - ChatRoomState _buildRoomPreview({required rust_types.ChatMessage lastMsg}) { - final room = _resolveRoom(); + ChatRoomState _buildRoomPreview({ + required rust_types.ChatMessage lastMsg, + required List rooms, + required AppLocalizations l10n, + }) { + final room = _resolveRoom(rooms, l10n); // Use the bridge-provided isRead / isMine flags rather than recomputing // from local _messages, which may not yet reflect the latest markAsRead // call (the bridge call is async and may still be in flight). @@ -224,12 +236,12 @@ class _ChatRoomScreenState extends ConsumerState { (_, next) => next.whenData(_onIncomingMessage), ); - final room = _resolveRoom(); + final l10n = AppLocalizations.of(context); + final room = _resolveRoom(ref.watch(chatRoomsNotifierProvider), l10n); final colors = Theme.of(context).extension(); if (colors == null) { throw StateError('AppColors theme extension must be registered'); } - final l10n = AppLocalizations.of(context); final screenWidth = MediaQuery.sizeOf(context).width; final showSidePanel = screenWidth >= AppBreakpoints.tablet; From e33976b05328087e781e17d4a243492319611c42 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 20:51:46 -0300 Subject: [PATCH 19/30] refactor: update German translations to informal 'du' and complete chat message string Change cancelOrderDialogContent from formal 'Sie' to informal 'du' for consistency with app tone. Complete secureChatUnread string by adding 'Nachrichten' (messages) after count to match other language implementations. --- lib/l10n/app_de.arb | 4 ++-- lib/l10n/app_localizations_de.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 347c53ca..e79763b6 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -221,7 +221,7 @@ "myOrderBuyTitle": "IHR KAUFANGEBOT", "cancelOrderButton": "Angebot stornieren", "cancelOrderDialogTitle": "Angebot stornieren", - "cancelOrderDialogContent": "Sind Sie sicher, dass Sie dieses Angebot stornieren möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "cancelOrderDialogContent": "Bist du sicher, dass du dieses Angebot stornieren möchtest? Diese Aktion kann nicht rückgängig gemacht werden.", "cancelOrderFailed": "Stornierung fehlgeschlagen. Bitte erneut versuchen.", "copyButtonLabel": "Kopieren", "closeButtonLabel": "Schließen", @@ -495,7 +495,7 @@ "waitingForCounterpart": "Warte auf eine Gegenpartei…", "yourTradeTimelineTitle": "DEIN TRADE", "yourCounterpartFallback": "deine Gegenpartei", - "secureChatUnread": "Sicherer Chat · {count} neue", + "secureChatUnread": "Sicherer Chat · {count} neue Nachrichten", "secureChatEncrypted": "Sicherer Chat · Ende-zu-Ende-verschlüsselt", "messageSendFailed": "Nachricht konnte nicht gesendet werden. Bitte versuche es erneut.", "invalidTradeId": "Ungültige Trade-ID", diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 2d703b9e..ac03e70e 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -678,7 +678,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get cancelOrderDialogContent => - 'Sind Sie sicher, dass Sie dieses Angebot stornieren möchten? Diese Aktion kann nicht rückgängig gemacht werden.'; + 'Bist du sicher, dass du dieses Angebot stornieren möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'; @override String get cancelOrderFailed => @@ -1606,7 +1606,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String secureChatUnread(int count) { - return 'Sicherer Chat · $count neue'; + return 'Sicherer Chat · $count neue Nachrichten'; } @override From 66f53bb59a122623b51f0be1439bfecaef57d5d8 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 20:54:05 -0300 Subject: [PATCH 20/30] feat: add CI check for untranslated localization keys Add workflow step to validate all l10n keys are translated across locales by running flutter gen-l10n and checking untranslated_messages.txt report. Fails build if any locale is missing keys that exist in app_en.arb template. --- .github/workflows/ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fc229a2..2ad7d87d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,6 +105,20 @@ jobs: - name: Generate Rust bridge bindings run: flutter_rust_bridge_codegen generate + # Regenerate localizations and fail if any locale is missing a key that + # exists in the English template (app_en.arb). The report is "{}" when + # everything is translated (see l10n.yaml: untranslated-messages-file). + - name: Check localizations are fully translated + run: | + flutter gen-l10n + report=lib/l10n/untranslated_messages.txt + if [ -f "$report" ] && [ "$(tr -d '[:space:]' < "$report")" != "{}" ]; then + echo "::error::Untranslated localization keys found (see below):" + cat "$report" + exit 1 + fi + echo "All localization keys are translated across every locale." + - name: Analyze run: flutter analyze From 68ef77247ef5eb67bee3b06858bf70db98eeb3d5 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 21:34:29 -0300 Subject: [PATCH 21/30] feat: add l10n strings for filters, Lightning invoice, and order actions Add 13 new localization keys across en/de/es/fr/it for filter dialog (title, reset/apply buttons, currency/rating labels), Lightning invoice UI (copy/share buttons, send prompt, change button, success label), and add-order button (buy/sell labels). Replace hardcoded English text in add_order_button.dart and bottom_nav_bar.dart with localized strings from AppLocalizations. --- lib/l10n/app_de.arb | 14 +++- lib/l10n/app_en.arb | 29 +++++++- lib/l10n/app_es.arb | 14 +++- lib/l10n/app_fr.arb | 14 +++- lib/l10n/app_it.arb | 14 +++- lib/l10n/app_localizations.dart | 72 +++++++++++++++++++ lib/l10n/app_localizations_de.dart | 38 ++++++++++ lib/l10n/app_localizations_en.dart | 38 ++++++++++ lib/l10n/app_localizations_es.dart | 38 ++++++++++ lib/l10n/app_localizations_fr.dart | 38 ++++++++++ lib/l10n/app_localizations_it.dart | 38 ++++++++++ lib/shared/widgets/add_order_button.dart | 5 +- lib/shared/widgets/bottom_nav_bar.dart | 14 ++-- .../ln_address_confirmation_widget.dart | 11 +-- .../widgets/mostro_reactive_button.dart | 7 +- lib/shared/widgets/order_filter.dart | 17 +++-- .../widgets/pay_lightning_invoice_widget.dart | 15 ++-- 17 files changed, 383 insertions(+), 33 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index e79763b6..753aac0e 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -604,5 +604,17 @@ "disputeIdLabel": "Streitfall-ID", "disputeReasonLabel": "Grund: {reason}", "adminLabel": "Administrator", - "disputeScreenTitle": "Streitfall" + "disputeScreenTitle": "Streitfall", + "filtersDialogTitle": "Filter", + "resetButton": "Zurücksetzen", + "currencyLabel": "Währung", + "ratingLabel": "Bewertung", + "applyButton": "Anwenden", + "successLabel": "Erfolg", + "copyButton": "Kopieren", + "shareButton": "Teilen", + "sendSatsToAddress": "Sende {sats} Sats an:", + "changeButton": "Ändern", + "buyLabel": "Kaufen", + "sellLabel": "Verkaufen" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d4aec31b..d28a1953 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1323,5 +1323,32 @@ "adminLabel": "Admin", "@adminLabel": {"description": "Sender label for administrator messages in a dispute"}, "disputeScreenTitle": "Dispute", - "@disputeScreenTitle": {"description": "App bar title of the dispute chat screen"} + "@disputeScreenTitle": {"description": "App bar title of the dispute chat screen"}, + "filtersDialogTitle": "Filters", + "@filtersDialogTitle": {"description": "Title of the order filter dialog"}, + "resetButton": "Reset", + "@resetButton": {"description": "Button that resets all filters"}, + "currencyLabel": "Currency", + "@currencyLabel": {"description": "Section label for the currency filter"}, + "ratingLabel": "Rating", + "@ratingLabel": {"description": "Section label for the rating filter"}, + "applyButton": "Apply", + "@applyButton": {"description": "Button that applies the filters and closes the dialog"}, + "successLabel": "Success", + "@successLabel": {"description": "Accessibility label for a reactive button in the success state"}, + "copyButton": "Copy", + "@copyButton": {"description": "Button that copies the Lightning invoice"}, + "shareButton": "Share", + "@shareButton": {"description": "Button that shares the Lightning invoice"}, + "sendSatsToAddress": "Send {sats} sats to:", + "@sendSatsToAddress": { + "description": "Prompt above a Lightning address confirmation", + "placeholders": {"sats": {"type": "String"}} + }, + "changeButton": "Change", + "@changeButton": {"description": "Button that returns to edit the Lightning address"}, + "buyLabel": "Buy", + "@buyLabel": {"description": "Buy action in the add-order button"}, + "sellLabel": "Sell", + "@sellLabel": {"description": "Sell action in the add-order button"} } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 526e0f5b..5dce41c1 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -604,5 +604,17 @@ "disputeIdLabel": "ID de disputa", "disputeReasonLabel": "Motivo: {reason}", "adminLabel": "Administrador", - "disputeScreenTitle": "Disputa" + "disputeScreenTitle": "Disputa", + "filtersDialogTitle": "Filtros", + "resetButton": "Restablecer", + "currencyLabel": "Moneda", + "ratingLabel": "Calificación", + "applyButton": "Aplicar", + "successLabel": "Éxito", + "copyButton": "Copiar", + "shareButton": "Compartir", + "sendSatsToAddress": "Envía {sats} sats a:", + "changeButton": "Cambiar", + "buyLabel": "Comprar", + "sellLabel": "Vender" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 31d6ceab..bd5d0545 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -604,5 +604,17 @@ "disputeIdLabel": "ID du litige", "disputeReasonLabel": "Raison : {reason}", "adminLabel": "Administrateur", - "disputeScreenTitle": "Litige" + "disputeScreenTitle": "Litige", + "filtersDialogTitle": "Filtres", + "resetButton": "Réinitialiser", + "currencyLabel": "Devise", + "ratingLabel": "Note", + "applyButton": "Appliquer", + "successLabel": "Succès", + "copyButton": "Copier", + "shareButton": "Partager", + "sendSatsToAddress": "Envoyez {sats} sats à :", + "changeButton": "Modifier", + "buyLabel": "Acheter", + "sellLabel": "Vendre" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 7cdebc86..374bed1a 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -604,5 +604,17 @@ "disputeIdLabel": "ID disputa", "disputeReasonLabel": "Motivo: {reason}", "adminLabel": "Amministratore", - "disputeScreenTitle": "Disputa" + "disputeScreenTitle": "Disputa", + "filtersDialogTitle": "Filtri", + "resetButton": "Reimposta", + "currencyLabel": "Valuta", + "ratingLabel": "Valutazione", + "applyButton": "Applica", + "successLabel": "Successo", + "copyButton": "Copia", + "shareButton": "Condividi", + "sendSatsToAddress": "Invia {sats} sats a:", + "changeButton": "Modifica", + "buyLabel": "Compra", + "sellLabel": "Vendi" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 08920384..60e30d0b 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3595,6 +3595,78 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Dispute'** String get disputeScreenTitle; + + /// Title of the order filter dialog + /// + /// In en, this message translates to: + /// **'Filters'** + String get filtersDialogTitle; + + /// Button that resets all filters + /// + /// In en, this message translates to: + /// **'Reset'** + String get resetButton; + + /// Section label for the currency filter + /// + /// In en, this message translates to: + /// **'Currency'** + String get currencyLabel; + + /// Section label for the rating filter + /// + /// In en, this message translates to: + /// **'Rating'** + String get ratingLabel; + + /// Button that applies the filters and closes the dialog + /// + /// In en, this message translates to: + /// **'Apply'** + String get applyButton; + + /// Accessibility label for a reactive button in the success state + /// + /// In en, this message translates to: + /// **'Success'** + String get successLabel; + + /// Button that copies the Lightning invoice + /// + /// In en, this message translates to: + /// **'Copy'** + String get copyButton; + + /// Button that shares the Lightning invoice + /// + /// In en, this message translates to: + /// **'Share'** + String get shareButton; + + /// Prompt above a Lightning address confirmation + /// + /// In en, this message translates to: + /// **'Send {sats} sats to:'** + String sendSatsToAddress(String sats); + + /// Button that returns to edit the Lightning address + /// + /// In en, this message translates to: + /// **'Change'** + String get changeButton; + + /// Buy action in the add-order button + /// + /// In en, this message translates to: + /// **'Buy'** + String get buyLabel; + + /// Sell action in the add-order button + /// + /// In en, this message translates to: + /// **'Sell'** + String get sellLabel; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index ac03e70e..77545cc0 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1996,4 +1996,42 @@ class AppLocalizationsDe extends AppLocalizations { @override String get disputeScreenTitle => 'Streitfall'; + + @override + String get filtersDialogTitle => 'Filter'; + + @override + String get resetButton => 'Zurücksetzen'; + + @override + String get currencyLabel => 'Währung'; + + @override + String get ratingLabel => 'Bewertung'; + + @override + String get applyButton => 'Anwenden'; + + @override + String get successLabel => 'Erfolg'; + + @override + String get copyButton => 'Kopieren'; + + @override + String get shareButton => 'Teilen'; + + @override + String sendSatsToAddress(String sats) { + return 'Sende $sats Sats an:'; + } + + @override + String get changeButton => 'Ändern'; + + @override + String get buyLabel => 'Kaufen'; + + @override + String get sellLabel => 'Verkaufen'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 987ec0ca..0b85c1ea 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1966,4 +1966,42 @@ class AppLocalizationsEn extends AppLocalizations { @override String get disputeScreenTitle => 'Dispute'; + + @override + String get filtersDialogTitle => 'Filters'; + + @override + String get resetButton => 'Reset'; + + @override + String get currencyLabel => 'Currency'; + + @override + String get ratingLabel => 'Rating'; + + @override + String get applyButton => 'Apply'; + + @override + String get successLabel => 'Success'; + + @override + String get copyButton => 'Copy'; + + @override + String get shareButton => 'Share'; + + @override + String sendSatsToAddress(String sats) { + return 'Send $sats sats to:'; + } + + @override + String get changeButton => 'Change'; + + @override + String get buyLabel => 'Buy'; + + @override + String get sellLabel => 'Sell'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 064a4333..77a60b91 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1990,4 +1990,42 @@ class AppLocalizationsEs extends AppLocalizations { @override String get disputeScreenTitle => 'Disputa'; + + @override + String get filtersDialogTitle => 'Filtros'; + + @override + String get resetButton => 'Restablecer'; + + @override + String get currencyLabel => 'Moneda'; + + @override + String get ratingLabel => 'Calificación'; + + @override + String get applyButton => 'Aplicar'; + + @override + String get successLabel => 'Éxito'; + + @override + String get copyButton => 'Copiar'; + + @override + String get shareButton => 'Compartir'; + + @override + String sendSatsToAddress(String sats) { + return 'Envía $sats sats a:'; + } + + @override + String get changeButton => 'Cambiar'; + + @override + String get buyLabel => 'Comprar'; + + @override + String get sellLabel => 'Vender'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index e990e192..94d754f4 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1999,4 +1999,42 @@ class AppLocalizationsFr extends AppLocalizations { @override String get disputeScreenTitle => 'Litige'; + + @override + String get filtersDialogTitle => 'Filtres'; + + @override + String get resetButton => 'Réinitialiser'; + + @override + String get currencyLabel => 'Devise'; + + @override + String get ratingLabel => 'Note'; + + @override + String get applyButton => 'Appliquer'; + + @override + String get successLabel => 'Succès'; + + @override + String get copyButton => 'Copier'; + + @override + String get shareButton => 'Partager'; + + @override + String sendSatsToAddress(String sats) { + return 'Envoyez $sats sats à :'; + } + + @override + String get changeButton => 'Modifier'; + + @override + String get buyLabel => 'Acheter'; + + @override + String get sellLabel => 'Vendre'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index c900065d..4cfb585e 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1988,4 +1988,42 @@ class AppLocalizationsIt extends AppLocalizations { @override String get disputeScreenTitle => 'Disputa'; + + @override + String get filtersDialogTitle => 'Filtri'; + + @override + String get resetButton => 'Reimposta'; + + @override + String get currencyLabel => 'Valuta'; + + @override + String get ratingLabel => 'Valutazione'; + + @override + String get applyButton => 'Applica'; + + @override + String get successLabel => 'Successo'; + + @override + String get copyButton => 'Copia'; + + @override + String get shareButton => 'Condividi'; + + @override + String sendSatsToAddress(String sats) { + return 'Invia $sats sats a:'; + } + + @override + String get changeButton => 'Modifica'; + + @override + String get buyLabel => 'Compra'; + + @override + String get sellLabel => 'Vendi'; } diff --git a/lib/shared/widgets/add_order_button.dart b/lib/shared/widgets/add_order_button.dart index 976e1b24..9e98de6b 100644 --- a/lib/shared/widgets/add_order_button.dart +++ b/lib/shared/widgets/add_order_button.dart @@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:mostro/core/app_routes.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Expandable FAB for creating orders. /// @@ -88,7 +89,7 @@ class _AddOrderButtonState extends State child: Padding( padding: const EdgeInsets.only(bottom: AppSpacing.sm), child: _SubButton( - label: 'Buy', + label: AppLocalizations.of(context).buyLabel, icon: Icons.arrow_downward, color: green, onTap: () { @@ -109,7 +110,7 @@ class _AddOrderButtonState extends State child: Padding( padding: const EdgeInsets.only(bottom: AppSpacing.sm), child: _SubButton( - label: 'Sell', + label: AppLocalizations.of(context).sellLabel, icon: Icons.arrow_upward, color: sellColor, onTap: () { diff --git a/lib/shared/widgets/bottom_nav_bar.dart b/lib/shared/widgets/bottom_nav_bar.dart index a8c477c9..8fbd9538 100644 --- a/lib/shared/widgets/bottom_nav_bar.dart +++ b/lib/shared/widgets/bottom_nav_bar.dart @@ -6,6 +6,7 @@ import 'package:mostro/core/app_routes.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/trades/providers/trades_providers.dart' show orderBookNotificationCountProvider; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/shared/providers/nav_providers.dart'; /// Badge count for the Chat tab. Will be wired to Rust bridge. @@ -28,6 +29,7 @@ class BottomNavBar extends ConsumerWidget { final disabledColor = colors?.textDisabled ?? const Color(0xFF6C757D); final tradesCount = ref.watch(orderBookNotificationCountProvider); final chatCount = ref.watch(chatNotificationCountProvider); + final l10n = AppLocalizations.of(context); return BottomNavigationBar( currentIndex: currentIndex, @@ -49,10 +51,10 @@ class BottomNavBar extends ConsumerWidget { } }, items: [ - const BottomNavigationBarItem( - icon: Icon(Icons.list_alt_outlined), - activeIcon: Icon(Icons.list_alt), - label: 'Order Book', + BottomNavigationBarItem( + icon: const Icon(Icons.list_alt_outlined), + activeIcon: const Icon(Icons.list_alt), + label: l10n.navOrderBook, ), BottomNavigationBarItem( icon: _BadgeIcon( @@ -65,7 +67,7 @@ class BottomNavBar extends ConsumerWidget { count: tradesCount, color: colors?.destructiveRed ?? const Color(0xFFD84D4D), ), - label: 'My Trades', + label: l10n.navMyTrades, ), BottomNavigationBarItem( icon: _BadgeIcon( @@ -78,7 +80,7 @@ class BottomNavBar extends ConsumerWidget { count: chatCount, color: colors?.destructiveRed ?? const Color(0xFFD84D4D), ), - label: 'Chat', + label: l10n.navChat, ), ], ); diff --git a/lib/shared/widgets/ln_address_confirmation_widget.dart b/lib/shared/widgets/ln_address_confirmation_widget.dart index f6df4dc6..284ea7e5 100644 --- a/lib/shared/widgets/ln_address_confirmation_widget.dart +++ b/lib/shared/widgets/ln_address_confirmation_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Confirmation widget for Lightning address payment. /// @@ -26,6 +27,7 @@ class LnAddressConfirmationWidget extends StatelessWidget { final colors = theme.extension(); final green = colors?.mostroGreen ?? const Color(0xFF8CC63F); final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230); + final l10n = AppLocalizations.of(context); return Container( padding: const EdgeInsets.all(AppSpacing.lg), @@ -40,12 +42,13 @@ class LnAddressConfirmationWidget extends StatelessWidget { children: [ Icon(Icons.bolt, color: green, size: 24), const SizedBox(width: AppSpacing.sm), - Text('Lightning Address', style: theme.textTheme.labelLarge), + Text(l10n.lightningAddressSettingTitle, + style: theme.textTheme.labelLarge), ], ), const SizedBox(height: AppSpacing.md), Text( - 'Send $amountSats sats to:', + l10n.sendSatsToAddress(amountSats.toString()), style: theme.textTheme.bodyMedium, ), const SizedBox(height: AppSpacing.sm), @@ -71,7 +74,7 @@ class LnAddressConfirmationWidget extends StatelessWidget { borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text('Change'), + child: Text(l10n.changeButton), ), ), const SizedBox(width: AppSpacing.md), @@ -85,7 +88,7 @@ class LnAddressConfirmationWidget extends StatelessWidget { borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text('Confirm'), + child: Text(l10n.confirmButtonLabel), ), ), ], diff --git a/lib/shared/widgets/mostro_reactive_button.dart b/lib/shared/widgets/mostro_reactive_button.dart index c7ef7573..2c119b25 100644 --- a/lib/shared/widgets/mostro_reactive_button.dart +++ b/lib/shared/widgets/mostro_reactive_button.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Button that shows a spinner while waiting for a Mostro response, /// a success check on completion, and an error state on failure. @@ -79,7 +80,7 @@ class _MostroReactiveButtonState extends State { switch (_state) { case _ButtonState.loading: return Semantics( - label: 'Loading', + label: AppLocalizations.of(context).loading, liveRegion: true, child: const SizedBox( width: 20, @@ -89,13 +90,13 @@ class _MostroReactiveButtonState extends State { ); case _ButtonState.success: return Semantics( - label: 'Success', + label: AppLocalizations.of(context).successLabel, liveRegion: true, child: const Icon(Icons.check, size: 20), ); case _ButtonState.error: return Semantics( - label: 'Error', + label: AppLocalizations.of(context).error, liveRegion: true, child: const Icon(Icons.error_outline, size: 20), ); diff --git a/lib/shared/widgets/order_filter.dart b/lib/shared/widgets/order_filter.dart index d2287980..cc04f6f6 100644 --- a/lib/shared/widgets/order_filter.dart +++ b/lib/shared/widgets/order_filter.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/home/providers/home_order_providers.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/shared/utils/fiat_currencies.dart'; /// Commonly traded currencies shown first in the filter. The full list @@ -45,6 +46,7 @@ class _OrderFilterDialog extends ConsumerWidget { final currencies = allCurrencies.isNotEmpty ? allCurrencies : _topCurrencies; final ratingRange = ref.watch(ratingFilterProvider); final premiumRange = ref.watch(premiumRangeFilterProvider); + final l10n = AppLocalizations.of(context); return Dialog( backgroundColor: cardBg, @@ -62,7 +64,8 @@ class _OrderFilterDialog extends ConsumerWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Filters', style: theme.textTheme.headlineSmall), + Text(l10n.filtersDialogTitle, + style: theme.textTheme.headlineSmall), TextButton( onPressed: () { ref.read(currencyFilterProvider.notifier).state = []; @@ -72,14 +75,14 @@ class _OrderFilterDialog extends ConsumerWidget { ref.read(premiumRangeFilterProvider.notifier).state = defaultPremiumRange; }, - child: Text('Reset', style: TextStyle(color: green)), + child: Text(l10n.resetButton, style: TextStyle(color: green)), ), ], ), const SizedBox(height: AppSpacing.lg), // Currency chips - Text('Currency', style: theme.textTheme.labelLarge), + Text(l10n.currencyLabel, style: theme.textTheme.labelLarge), const SizedBox(height: AppSpacing.sm), Wrap( spacing: AppSpacing.sm, @@ -104,7 +107,7 @@ class _OrderFilterDialog extends ConsumerWidget { const SizedBox(height: AppSpacing.lg), // Payment method chips - Text('Payment Method', style: theme.textTheme.labelLarge), + Text(l10n.paymentMethodLabel, style: theme.textTheme.labelLarge), const SizedBox(height: AppSpacing.sm), Wrap( spacing: AppSpacing.sm, @@ -129,7 +132,7 @@ class _OrderFilterDialog extends ConsumerWidget { const SizedBox(height: AppSpacing.lg), // Rating range slider - Text('Rating', style: theme.textTheme.labelLarge), + Text(l10n.ratingLabel, style: theme.textTheme.labelLarge), RangeSlider( values: RangeValues(ratingRange.min, ratingRange.max), min: 0, @@ -148,7 +151,7 @@ class _OrderFilterDialog extends ConsumerWidget { const SizedBox(height: AppSpacing.md), // Premium range slider - Text('Premium', style: theme.textTheme.labelLarge), + Text(l10n.premiumSectionLabel, style: theme.textTheme.labelLarge), RangeSlider( values: RangeValues(premiumRange.min, premiumRange.max), min: -10, @@ -178,7 +181,7 @@ class _OrderFilterDialog extends ConsumerWidget { borderRadius: BorderRadius.circular(AppRadius.button), ), ), - child: const Text('Apply'), + child: Text(l10n.applyButton), ), ), ], diff --git a/lib/shared/widgets/pay_lightning_invoice_widget.dart b/lib/shared/widgets/pay_lightning_invoice_widget.dart index 8f648448..b5f46c20 100644 --- a/lib/shared/widgets/pay_lightning_invoice_widget.dart +++ b/lib/shared/widgets/pay_lightning_invoice_widget.dart @@ -4,6 +4,7 @@ import 'package:qr_flutter/qr_flutter.dart'; import 'package:share_plus/share_plus.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// Manual pay invoice widget — QR code + copy + share. /// @@ -57,14 +58,14 @@ class PayLightningInvoiceWidget extends StatelessWidget { await Clipboard.setData(ClipboardData(text: bolt11)); if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Invoice copied'), - duration: Duration(seconds: 1), + SnackBar( + content: Text(AppLocalizations.of(context).invoiceCopied), + duration: const Duration(seconds: 1), ), ); }, icon: const Icon(Icons.copy, size: 16), - label: const Text('Copy'), + label: Text(AppLocalizations.of(context).copyButton), style: FilledButton.styleFrom( backgroundColor: green, foregroundColor: Colors.black, @@ -83,12 +84,14 @@ class PayLightningInvoiceWidget extends StatelessWidget { } catch (e) { if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Share failed: $e')), + SnackBar( + content: Text(AppLocalizations.of(context).shareFailed), + ), ); } }, icon: const Icon(Icons.share, size: 16), - label: const Text('Share'), + label: Text(AppLocalizations.of(context).shareButton), style: FilledButton.styleFrom( backgroundColor: green, foregroundColor: Colors.black, From c3699448df110d8a6b51fdeb1f81acdd7fd9a01e Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 21:42:02 -0300 Subject: [PATCH 22/30] refactor: normalize language codes at state boundary and add tests --- .../settings/providers/settings_provider.dart | 31 +++-- .../settings/settings_provider_test.dart | 119 ++++++++++++++++++ 2 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 test/features/settings/settings_provider_test.dart diff --git a/lib/features/settings/providers/settings_provider.dart b/lib/features/settings/providers/settings_provider.dart index bb82148a..a0cfc3f6 100644 --- a/lib/features/settings/providers/settings_provider.dart +++ b/lib/features/settings/providers/settings_provider.dart @@ -55,7 +55,7 @@ class AppSettingsState { factory AppSettingsState.fromPrefs(SharedPreferences prefs) { final themeModeStr = prefs.getString(_kThemeMode) ?? 'dark'; return AppSettingsState( - language: prefs.getString(_kLanguage) ?? _deviceDefaultLanguage(), + language: _normalizeLanguage(prefs.getString(_kLanguage)), defaultFiatCode: prefs.getString(_kFiatCode), defaultLightningAddress: prefs.getString(_kLightningAddress), loggingEnabled: prefs.getBool(_kLoggingEnabled) ?? false, @@ -80,8 +80,9 @@ class SettingsNotifier extends StateNotifier { final SharedPreferences? _prefs; void setLanguage(String code) { - state = state.copyWith(language: code); - _prefs?.setString(_kLanguage, code); + final normalized = _normalizeLanguage(code); + state = state.copyWith(language: normalized); + _prefs?.setString(_kLanguage, normalized); } void setDefaultFiatCode(String? code) { @@ -139,18 +140,28 @@ String _deviceDefaultLanguage() { return _supportedLanguageCodes.contains(deviceCode) ? deviceCode : 'en'; } +/// Normalizes a stored or selected language to a supported code. +/// +/// Strips any region qualifier (e.g. `es-MX` -> `es`) and falls back to the +/// device default when the value is empty or unsupported (e.g. `pt`). Keeping +/// [AppSettingsState.language] normalized ensures the effective locale, the +/// Settings display and the language picker all agree. +String _normalizeLanguage(String? stored) { + final code = (stored ?? '').split(RegExp(r'[-_]')).first; + return _supportedLanguageCodes.contains(code) + ? code + : _deviceDefaultLanguage(); +} + /// Current display locale, derived automatically from [settingsProvider]. /// /// Rebuilds whenever [AppSettingsState.language] changes so that /// [MaterialApp.router] locale stays in sync without manual updates. /// -/// The stored language is validated against [AppLocalizations.supportedLocales] -/// and falls back to English when it is unknown, so a corrupted or unsupported -/// preference never leaves the UI unlocalized. +/// [AppSettingsState.language] is normalized to a supported code at the state +/// boundary (see [_normalizeLanguage]), so the effective locale, the Settings +/// display and the language picker always agree. final localeProvider = Provider((ref) { final language = ref.watch(settingsProvider.select((s) => s.language)); - final code = language.split(RegExp(r'[-_]')).first; - return _supportedLanguageCodes.contains(code) - ? Locale(code) - : const Locale('en'); + return Locale(language); }); diff --git a/test/features/settings/settings_provider_test.dart b/test/features/settings/settings_provider_test.dart new file mode 100644 index 00000000..193a7437 --- /dev/null +++ b/test/features/settings/settings_provider_test.dart @@ -0,0 +1,119 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mostro/features/settings/providers/settings_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Tests for the saved-language normalization: the effective locale, the +/// persisted [AppSettingsState.language] and the Settings picker must always +/// agree, including for unsupported (`pt`) and region-qualified (`es-MX`) +/// stored values. +void main() { + final binding = TestWidgetsFlutterBinding.ensureInitialized(); + + // Force a deterministic, supported device locale so the device-default + // fallback is predictable across machines and CI. + setUp(() { + binding.platformDispatcher.localeTestValue = const Locale('en'); + }); + tearDown(() { + binding.platformDispatcher.clearLocaleTestValue(); + }); + + Future stateWith(String? language) async { + SharedPreferences.setMockInitialValues( + language == null ? {} : {'settings.language': language}, + ); + final prefs = await SharedPreferences.getInstance(); + return AppSettingsState.fromPrefs(prefs); + } + + group('AppSettingsState.fromPrefs language normalization', () { + test('supported code is kept as-is', () async { + expect((await stateWith('es')).language, 'es'); + expect((await stateWith('fr')).language, 'fr'); + expect((await stateWith('de')).language, 'de'); + }); + + test('region-qualified supported code is stripped to its base', () async { + expect((await stateWith('es-MX')).language, 'es'); + expect((await stateWith('es_MX')).language, 'es'); + expect((await stateWith('fr-CA')).language, 'fr'); + }); + + test('unsupported code falls back to the device default', () async { + expect((await stateWith('pt')).language, 'en'); + expect((await stateWith('pt-BR')).language, 'en'); + expect((await stateWith('zz')).language, 'en'); + }); + + test('missing or empty value falls back to the device default', () async { + expect((await stateWith(null)).language, 'en'); + expect((await stateWith('')).language, 'en'); + }); + + test('device default follows a supported device locale', () async { + binding.platformDispatcher.localeTestValue = const Locale('de'); + expect((await stateWith('pt')).language, 'de'); // unsupported -> device + expect((await stateWith(null)).language, 'de'); // first run -> device + expect((await stateWith('es')).language, 'es'); // explicit supported wins + }); + }); + + group('SettingsNotifier.setLanguage normalizes before persisting', () { + test('region-qualified and unsupported values are normalized', () async { + SharedPreferences.setMockInitialValues({}); + final prefs = await SharedPreferences.getInstance(); + final notifier = SettingsNotifier(prefs: prefs); + + notifier.setLanguage('es-MX'); + expect(notifier.state.language, 'es'); + expect(prefs.getString('settings.language'), 'es'); + + notifier.setLanguage('pt'); // unsupported -> device default (en) + expect(notifier.state.language, 'en'); + expect(prefs.getString('settings.language'), 'en'); + + notifier.setLanguage('it'); + expect(notifier.state.language, 'it'); + expect(prefs.getString('settings.language'), 'it'); + }); + }); + + group('localeProvider agrees with the persisted state', () { + Future expectAgreement(String? stored, String expected) async { + SharedPreferences.setMockInitialValues( + stored == null ? {} : {'settings.language': stored}, + ); + final prefs = await SharedPreferences.getInstance(); + final container = ProviderContainer( + overrides: [ + settingsProvider.overrideWith( + (ref) => SettingsNotifier( + prefs: prefs, + initial: AppSettingsState.fromPrefs(prefs), + ), + ), + ], + ); + addTearDown(container.dispose); + + final language = container.read(settingsProvider).language; + final locale = container.read(localeProvider); + expect(language, expected); + expect(locale.languageCode, expected); + // The effective locale and the persisted UI state must agree. + expect(locale.languageCode, language); + } + + test('supported es kept for both state and locale', () async { + await expectAgreement('es', 'es'); + }); + test('region-qualified es-MX -> es for both', () async { + await expectAgreement('es-MX', 'es'); + }); + test('unsupported pt -> device default for both', () async { + await expectAgreement('pt', 'en'); + }); + }); +} From 4e074cd432f0b2fdffa7973b49c674a2db8d69d2 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 21:57:52 -0300 Subject: [PATCH 23/30] refactor: use first supported locale from device preferences instead of only primary --- .github/workflows/ci.yml | 6 +++++- .../settings/providers/settings_provider.dart | 17 +++++++++++------ .../settings/settings_provider_test.dart | 16 +++++++++++++--- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ad7d87d..cc8d58cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,7 +112,11 @@ jobs: run: | flutter gen-l10n report=lib/l10n/untranslated_messages.txt - if [ -f "$report" ] && [ "$(tr -d '[:space:]' < "$report")" != "{}" ]; then + if [ ! -f "$report" ]; then + echo "::error::Expected untranslated-messages report was not generated." + exit 1 + fi + if [ "$(tr -d '[:space:]' < "$report")" != "{}" ]; then echo "::error::Untranslated localization keys found (see below):" cat "$report" exit 1 diff --git a/lib/features/settings/providers/settings_provider.dart b/lib/features/settings/providers/settings_provider.dart index a0cfc3f6..fc2dd052 100644 --- a/lib/features/settings/providers/settings_provider.dart +++ b/lib/features/settings/providers/settings_provider.dart @@ -130,14 +130,19 @@ final settingsProvider = final Set _supportedLanguageCodes = AppLocalizations.supportedLocales.map((l) => l.languageCode).toSet(); -/// The device's language when the app supports it, otherwise English. +/// The first supported language among the device's preferred locales, or +/// English when none is supported. /// -/// Used as the first-run default so a new user starts in their own language -/// instead of always defaulting to English. +/// Iterates the ordered [PlatformDispatcher.locales] rather than just the +/// primary locale, so a device preferring e.g. `pt-BR` then `es` starts in +/// Spanish instead of falling back to English. Used as the first-run default. String _deviceDefaultLanguage() { - final deviceCode = - WidgetsBinding.instance.platformDispatcher.locale.languageCode; - return _supportedLanguageCodes.contains(deviceCode) ? deviceCode : 'en'; + for (final locale in WidgetsBinding.instance.platformDispatcher.locales) { + if (_supportedLanguageCodes.contains(locale.languageCode)) { + return locale.languageCode; + } + } + return 'en'; } /// Normalizes a stored or selected language to a supported code. diff --git a/test/features/settings/settings_provider_test.dart b/test/features/settings/settings_provider_test.dart index 193a7437..ffb2ea1f 100644 --- a/test/features/settings/settings_provider_test.dart +++ b/test/features/settings/settings_provider_test.dart @@ -14,10 +14,10 @@ void main() { // Force a deterministic, supported device locale so the device-default // fallback is predictable across machines and CI. setUp(() { - binding.platformDispatcher.localeTestValue = const Locale('en'); + binding.platformDispatcher.localesTestValue = const [Locale('en')]; }); tearDown(() { - binding.platformDispatcher.clearLocaleTestValue(); + binding.platformDispatcher.clearLocalesTestValue(); }); Future stateWith(String? language) async { @@ -53,11 +53,21 @@ void main() { }); test('device default follows a supported device locale', () async { - binding.platformDispatcher.localeTestValue = const Locale('de'); + binding.platformDispatcher.localesTestValue = const [Locale('de')]; expect((await stateWith('pt')).language, 'de'); // unsupported -> device expect((await stateWith(null)).language, 'de'); // first run -> device expect((await stateWith('es')).language, 'es'); // explicit supported wins }); + + test('device default picks the first supported preferred locale', () async { + // Unsupported primary (pt-BR) but a supported secondary (es). + binding.platformDispatcher.localesTestValue = const [ + Locale('pt', 'BR'), + Locale('es'), + ]; + expect((await stateWith('pt')).language, 'es'); // unsupported stored -> secondary + expect((await stateWith(null)).language, 'es'); // first run -> secondary + }); }); group('SettingsNotifier.setLanguage normalizes before persisting', () { From ce91e35ea53c3c5119595f6ef31b0a6cbc3b3ece Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 22:08:54 -0300 Subject: [PATCH 24/30] refactor: resolve localized "Unknown" peer handle at render time instead of caching in state --- .../chat/providers/chat_providers.dart | 11 ++++++++ .../chat/screens/chat_room_screen.dart | 26 ++++++++++--------- lib/features/chat/widgets/chat_list_item.dart | 7 ++--- .../disputes/screens/dispute_chat_screen.dart | 2 +- .../trades/screens/trade_detail_screen.dart | 3 ++- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index c2115a2d..33977f3d 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro/features/trades/providers/trades_providers.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/src/rust/api/identity.dart' as identity_api; import 'package:mostro/src/rust/api/messages.dart' as messages_api; import 'package:mostro/src/rust/api/types.dart' as rust_types; @@ -94,6 +95,16 @@ class ChatRoomState { } } +/// Localized display name for the peer. +/// +/// [ChatRoomState.peerHandle] stays locale-independent (empty when the peer is +/// unknown); the localized "Unknown" fallback is resolved only here, at render +/// time, so a cached room never freezes a handle in a stale language. +extension ChatRoomStateDisplay on ChatRoomState { + String displayHandle(AppLocalizations l10n) => + peerHandle.isEmpty ? l10n.unknownPeerHandle : peerHandle; +} + // ── ChatRoomsNotifier ───────────────────────────────────────────────────────── /// Manages the list of active chat rooms. diff --git a/lib/features/chat/screens/chat_room_screen.dart b/lib/features/chat/screens/chat_room_screen.dart index 172fc2d9..4734dee2 100644 --- a/lib/features/chat/screens/chat_room_screen.dart +++ b/lib/features/chat/screens/chat_room_screen.dart @@ -117,7 +117,6 @@ class _ChatRoomScreenState extends ConsumerState { _buildRoomPreview( lastMsg: sent, rooms: ref.read(chatRoomsNotifierProvider), - l10n: l10n, ), ); } catch (e) { @@ -151,7 +150,6 @@ class _ChatRoomScreenState extends ConsumerState { _buildRoomPreview( lastMsg: msg, rooms: ref.read(chatRoomsNotifierProvider), - l10n: AppLocalizations.of(context), ), ); } @@ -180,13 +178,15 @@ class _ChatRoomScreenState extends ConsumerState { if (_showUserInfo) _showTradeInfo = false; }); - ChatRoomState _resolveRoom(List rooms, AppLocalizations l10n) { + ChatRoomState _resolveRoom(List rooms) { return rooms.firstWhere( (r) => r.orderId == widget.orderId, orElse: () => ChatRoomState( orderId: widget.orderId, peerPubkey: '', - peerHandle: l10n.unknownPeerHandle, + // Locale-independent: the localized "Unknown" is resolved at render + // time via ChatRoomState.displayHandle, never cached in the model. + peerHandle: '', peerIconIndex: 0, peerColorHue: 180, isSelling: false, @@ -197,9 +197,8 @@ class _ChatRoomScreenState extends ConsumerState { ChatRoomState _buildRoomPreview({ required rust_types.ChatMessage lastMsg, required List rooms, - required AppLocalizations l10n, }) { - final room = _resolveRoom(rooms, l10n); + final room = _resolveRoom(rooms); // Use the bridge-provided isRead / isMine flags rather than recomputing // from local _messages, which may not yet reflect the latest markAsRead // call (the bridge call is async and may still be in flight). @@ -237,7 +236,8 @@ class _ChatRoomScreenState extends ConsumerState { ); final l10n = AppLocalizations.of(context); - final room = _resolveRoom(ref.watch(chatRoomsNotifierProvider), l10n); + final room = _resolveRoom(ref.watch(chatRoomsNotifierProvider)); + final displayHandle = room.displayHandle(l10n); final colors = Theme.of(context).extension(); if (colors == null) { throw StateError('AppColors theme extension must be registered'); @@ -261,7 +261,7 @@ class _ChatRoomScreenState extends ConsumerState { : _showUserInfo ? UserInformationTab( key: const ValueKey('user'), - peerHandle: room.peerHandle, + peerHandle: displayHandle, peerPubkey: room.peerPubkey, peerIconIndex: room.peerIconIndex, peerColorHue: room.peerColorHue, @@ -300,7 +300,7 @@ class _ChatRoomScreenState extends ConsumerState { : _showUserInfo ? UserInformationTab( key: const ValueKey('user'), - peerHandle: room.peerHandle, + peerHandle: displayHandle, peerPubkey: room.peerPubkey, peerIconIndex: room.peerIconIndex, peerColorHue: room.peerColorHue, @@ -317,7 +317,7 @@ class _ChatRoomScreenState extends ConsumerState { child: Padding( padding: const EdgeInsets.all(AppSpacing.lg), child: Text( - l10n.noMessagesYet(room.peerHandle), + l10n.noMessagesYet(displayHandle), textAlign: TextAlign.center, style: TextStyle(color: colors.textSubtle), ), @@ -429,6 +429,8 @@ class _AppBarTitle extends StatelessWidget { throw StateError('AppColors theme extension must be registered'); } final textTheme = Theme.of(context).textTheme; + final l10n = AppLocalizations.of(context); + final handle = room.displayHandle(l10n); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -444,14 +446,14 @@ class _AppBarTitle extends StatelessWidget { ), const SizedBox(width: AppSpacing.sm), Text( - room.peerHandle, + handle, style: textTheme.headlineSmall, overflow: TextOverflow.ellipsis, ), ], ), Text( - AppLocalizations.of(context).chattingWith(room.peerHandle), + l10n.chattingWith(handle), style: textTheme.bodySmall?.copyWith(color: colors.textSubtle), ), ], diff --git a/lib/features/chat/widgets/chat_list_item.dart b/lib/features/chat/widgets/chat_list_item.dart index 4824dc2c..9629e52a 100644 --- a/lib/features/chat/widgets/chat_list_item.dart +++ b/lib/features/chat/widgets/chat_list_item.dart @@ -26,10 +26,11 @@ class ChatListItem extends StatelessWidget { if (colors == null) throw StateError('AppColors theme extension must be registered'); final textTheme = Theme.of(context).textTheme; final l10n = AppLocalizations.of(context); + final handle = room.displayHandle(l10n); final contextLine = room.isSelling - ? l10n.sellingSatsTo(room.peerHandle) - : l10n.buyingSatsFrom(room.peerHandle); + ? l10n.sellingSatsTo(handle) + : l10n.buyingSatsFrom(handle); final previewText = room.lastMessage == null ? null @@ -66,7 +67,7 @@ class ChatListItem extends StatelessWidget { children: [ // Peer handle Text( - room.peerHandle, + handle, style: textTheme.bodyLarge?.copyWith( color: colors.textPrimary, fontWeight: FontWeight.bold, diff --git a/lib/features/disputes/screens/dispute_chat_screen.dart b/lib/features/disputes/screens/dispute_chat_screen.dart index 4819db23..c7886cfa 100644 --- a/lib/features/disputes/screens/dispute_chat_screen.dart +++ b/lib/features/disputes/screens/dispute_chat_screen.dart @@ -140,7 +140,7 @@ class _HeaderTitle extends StatelessWidget { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final textTheme = Theme.of(context).textTheme; - final handle = dispute.peerHandle ?? 'Unknown'; + final handle = dispute.peerHandle ?? l10n.unknownPeerHandle; final title = dispute.isSelling ? l10n.disputeWithBuyer(handle) diff --git a/lib/features/trades/screens/trade_detail_screen.dart b/lib/features/trades/screens/trade_detail_screen.dart index eb2d117d..b1b00704 100644 --- a/lib/features/trades/screens/trade_detail_screen.dart +++ b/lib/features/trades/screens/trade_detail_screen.dart @@ -1083,7 +1083,8 @@ class _ChatChip extends ConsumerWidget { final rooms = ref.watch(chatRoomsNotifierProvider); final room = rooms.where((r) => r.orderId == orderId).firstOrNull; - final handle = room?.peerHandle ?? l10n.yourCounterpartFallback; + final handle = + room == null ? l10n.yourCounterpartFallback : room.displayHandle(l10n); final unread = room?.unreadCount ?? 0; return InkWell( From 6644a0dad4a8ed6f01bf56336514c92c547281fc Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 22:13:20 -0300 Subject: [PATCH 25/30] test: add regression guard for walkthrough slide highlights across locales MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add test to verify each onboarding slide produces at least one highlighted phrase in all supported languages. Extend highlight regex patterns to match verb conjugation variants (crear/crea, creare/crea, créer/créez, erstellen) and gender inflections (cifrato/cifrata) that appear in translated slide bodies. --- .../walkthrough/utils/highlight_config.dart | 6 +-- .../walkthrough/highlight_config_test.dart | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 test/features/walkthrough/highlight_config_test.dart diff --git a/lib/features/walkthrough/utils/highlight_config.dart b/lib/features/walkthrough/utils/highlight_config.dart index 603b78c1..42e2445f 100644 --- a/lib/features/walkthrough/utils/highlight_config.dart +++ b/lib/features/walkthrough/utils/highlight_config.dart @@ -24,15 +24,15 @@ class HighlightConfig { ], // Page 4: Encrypted Chat 3: [ - r'end.to.end encrypted|cifrado de extremo a extremo|crittografato end.to.end|chiffré de bout en bout|Ende.zu.Ende.verschlüsselt', + r'end.to.end encrypted|cifrado de extremo a extremo|crittografato end.to.end|cifrat[oa] end.to.end|chiffré de bout en bout|Ende.zu.Ende.verschlüsselt', ], // Page 5: Take an Offer 4: [ - "order book|libro de órdenes|libro degli ordini|carnet d'ordres|Orderbuch", + "order book|libro de órdenes|libro degli ordini|book degli ordini|carnet d'ordres|Orderbuch", ], // Page 6: Create Your Own Offer 5: [ - r'create your own offer|crea tu propia oferta|crea la tua offerta|créez votre propre offre|erstelle dein eigenes Angebot', + r'create your own offer|crear? tu propia oferta|crea(?:re)? la tua offerta|cré(?:er|ez) votre propre offre|dein eigenes Angebot erstellen', ], }; diff --git a/test/features/walkthrough/highlight_config_test.dart b/test/features/walkthrough/highlight_config_test.dart new file mode 100644 index 00000000..ea500200 --- /dev/null +++ b/test/features/walkthrough/highlight_config_test.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mostro/features/walkthrough/utils/highlight_config.dart'; +import 'package:mostro/l10n/app_localizations.dart'; + +/// Guards against the silent regression where a translated onboarding slide +/// body no longer contains a phrase that [HighlightConfig] highlights, dropping +/// the intended green emphasis. Every slide must produce at least one +/// highlighted span in every supported locale. +void main() { + List slideBodies(AppLocalizations l) => [ + l.walkthroughSlideOneBody, + l.walkthroughSlideTwoBody, + l.walkthroughSlideThreeBody, + l.walkthroughSlideFourBody, + l.walkthroughSlideFiveBody, + l.walkthroughSlideSixBody, + ]; + + for (final locale in AppLocalizations.supportedLocales) { + test('walkthrough slides highlight a phrase in ${locale.languageCode}', + () async { + final l10n = await AppLocalizations.delegate.load(locale); + final bodies = slideBodies(l10n); + + for (var i = 0; i < bodies.length; i++) { + final span = HighlightConfig.buildHighlighted( + bodies[i], + i, + const Color(0xFF8CC63F), + const TextStyle(), + ); + // The highlighted fragment is the only span with a bold weight. + final hasHighlight = (span.children ?? const []).any( + (c) => c is TextSpan && c.style?.fontWeight == FontWeight.w600, + ); + expect( + hasHighlight, + isTrue, + reason: 'slide ${i + 1} in ${locale.languageCode} has no highlight', + ); + } + }); + } +} From 3720080e9414ad57e019a960cc8c30b872151ffa Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 22:18:23 -0300 Subject: [PATCH 26/30] refactor: replace hardcoded notification strings with localized translations Replace hardcoded English text in notifications screen and widgets with AppLocalizations strings. Add unableToOpenNotification key across all locales (en/de/es/fr/it) for snackbar error message. Change PopupMenuButton itemBuilder parameters from `_` to `context` to access localization context. --- .../screens/notifications_screen.dart | 16 +++++++++------- .../notifications/widgets/notification_card.dart | 11 ++++++----- .../widgets/notification_group_card.dart | 11 ++++++----- .../widgets/system_notification_banner.dart | 11 ++++++----- lib/l10n/app_de.arb | 3 ++- lib/l10n/app_en.arb | 4 +++- lib/l10n/app_es.arb | 3 ++- lib/l10n/app_fr.arb | 3 ++- lib/l10n/app_it.arb | 3 ++- lib/l10n/app_localizations.dart | 6 ++++++ lib/l10n/app_localizations_de.dart | 4 ++++ lib/l10n/app_localizations_en.dart | 3 +++ lib/l10n/app_localizations_es.dart | 4 ++++ lib/l10n/app_localizations_fr.dart | 4 ++++ lib/l10n/app_localizations_it.dart | 4 ++++ 15 files changed, 63 insertions(+), 27 deletions(-) diff --git a/lib/features/notifications/screens/notifications_screen.dart b/lib/features/notifications/screens/notifications_screen.dart index dd4ec3ba..321ca739 100644 --- a/lib/features/notifications/screens/notifications_screen.dart +++ b/lib/features/notifications/screens/notifications_screen.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:mostro/core/app_routes.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/features/account/providers/backup_reminder_provider.dart'; import 'package:mostro/features/notifications/models/notification_model.dart'; import 'package:mostro/features/notifications/providers/notifications_provider.dart'; @@ -37,7 +38,7 @@ class _NotificationsScreenState extends ConsumerState { return Scaffold( appBar: AppBar( - title: const Text('Notifications'), + title: Text(AppLocalizations.of(context).notificationsScreenTitle), actions: [ PopupMenuButton<_MenuAction>( onSelected: (action) { @@ -49,14 +50,15 @@ class _NotificationsScreenState extends ConsumerState { } }, itemBuilder: - (_) => const [ + (context) => [ PopupMenuItem( value: _MenuAction.markAllRead, - child: Text('Mark all as read'), + child: + Text(AppLocalizations.of(context).markAllAsReadMenuItem), ), PopupMenuItem( value: _MenuAction.clearAll, - child: Text('Clear all'), + child: Text(AppLocalizations.of(context).clearAllMenuItem), ), ], ), @@ -190,9 +192,9 @@ class _NotificationsScreenState extends ConsumerState { void _handleTap(BuildContext context, NotificationModel n) { void noId() { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Unable to open notification details.'), - duration: Duration(seconds: 2), + SnackBar( + content: Text(AppLocalizations.of(context).unableToOpenNotification), + duration: const Duration(seconds: 2), ), ); } diff --git a/lib/features/notifications/widgets/notification_card.dart b/lib/features/notifications/widgets/notification_card.dart index a4600f99..855fee2f 100644 --- a/lib/features/notifications/widgets/notification_card.dart +++ b/lib/features/notifications/widgets/notification_card.dart @@ -2,6 +2,7 @@ import 'package:clock/clock.dart'; import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/features/notifications/models/notification_model.dart'; class NotificationCard extends StatelessWidget { @@ -219,15 +220,15 @@ class _OverflowMenu extends StatelessWidget { onDelete(); } }, - itemBuilder: (_) => [ + itemBuilder: (context) => [ if (!isRead) - const PopupMenuItem( + PopupMenuItem( value: _CardMenuAction.markRead, - child: Text('Mark as read'), + child: Text(AppLocalizations.of(context).markAsRead), ), - const PopupMenuItem( + PopupMenuItem( value: _CardMenuAction.delete, - child: Text('Delete'), + child: Text(AppLocalizations.of(context).deleteNotificationLabel), ), ], ); diff --git a/lib/features/notifications/widgets/notification_group_card.dart b/lib/features/notifications/widgets/notification_group_card.dart index af60e6b5..deb598ee 100644 --- a/lib/features/notifications/widgets/notification_group_card.dart +++ b/lib/features/notifications/widgets/notification_group_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/features/notifications/models/notification_model.dart'; /// Collapsible card grouping all notifications that reference the same @@ -361,15 +362,15 @@ class _EventOverflowMenu extends StatelessWidget { } }, itemBuilder: - (_) => [ + (context) => [ if (!isRead) - const PopupMenuItem( + PopupMenuItem( value: _EventMenuAction.markRead, - child: Text('Mark as read'), + child: Text(AppLocalizations.of(context).markAsRead), ), - const PopupMenuItem( + PopupMenuItem( value: _EventMenuAction.delete, - child: Text('Delete'), + child: Text(AppLocalizations.of(context).deleteNotificationLabel), ), ], ); diff --git a/lib/features/notifications/widgets/system_notification_banner.dart b/lib/features/notifications/widgets/system_notification_banner.dart index 47e38c3d..1c91bb01 100644 --- a/lib/features/notifications/widgets/system_notification_banner.dart +++ b/lib/features/notifications/widgets/system_notification_banner.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/features/notifications/models/notification_model.dart'; import 'package:mostro/features/notifications/widgets/notification_group_card.dart' show relativeTime; @@ -152,15 +153,15 @@ class _BannerOverflowMenu extends StatelessWidget { } }, itemBuilder: - (_) => [ + (context) => [ if (!isRead) - const PopupMenuItem( + PopupMenuItem( value: _BannerMenuAction.markRead, - child: Text('Mark as read'), + child: Text(AppLocalizations.of(context).markAsRead), ), - const PopupMenuItem( + PopupMenuItem( value: _BannerMenuAction.delete, - child: Text('Delete'), + child: Text(AppLocalizations.of(context).deleteNotificationLabel), ), ], ); diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 753aac0e..136ab43a 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -616,5 +616,6 @@ "sendSatsToAddress": "Sende {sats} Sats an:", "changeButton": "Ändern", "buyLabel": "Kaufen", - "sellLabel": "Verkaufen" + "sellLabel": "Verkaufen", + "unableToOpenNotification": "Benachrichtigungsdetails können nicht geöffnet werden." } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d28a1953..c20a72d0 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1350,5 +1350,7 @@ "buyLabel": "Buy", "@buyLabel": {"description": "Buy action in the add-order button"}, "sellLabel": "Sell", - "@sellLabel": {"description": "Sell action in the add-order button"} + "@sellLabel": {"description": "Sell action in the add-order button"}, + "unableToOpenNotification": "Unable to open notification details.", + "@unableToOpenNotification": {"description": "Snackbar shown when a notification has no linked detail to open"} } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 5dce41c1..4bca6bc9 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -616,5 +616,6 @@ "sendSatsToAddress": "Envía {sats} sats a:", "changeButton": "Cambiar", "buyLabel": "Comprar", - "sellLabel": "Vender" + "sellLabel": "Vender", + "unableToOpenNotification": "No se pueden abrir los detalles de la notificación." } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index bd5d0545..3d9fa529 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -616,5 +616,6 @@ "sendSatsToAddress": "Envoyez {sats} sats à :", "changeButton": "Modifier", "buyLabel": "Acheter", - "sellLabel": "Vendre" + "sellLabel": "Vendre", + "unableToOpenNotification": "Impossible d'ouvrir les détails de la notification." } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 374bed1a..fce6d268 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -616,5 +616,6 @@ "sendSatsToAddress": "Invia {sats} sats a:", "changeButton": "Modifica", "buyLabel": "Compra", - "sellLabel": "Vendi" + "sellLabel": "Vendi", + "unableToOpenNotification": "Impossibile aprire i dettagli della notifica." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 60e30d0b..50775b0d 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3667,6 +3667,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Sell'** String get sellLabel; + + /// Snackbar shown when a notification has no linked detail to open + /// + /// In en, this message translates to: + /// **'Unable to open notification details.'** + String get unableToOpenNotification; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 77545cc0..25a78972 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -2034,4 +2034,8 @@ class AppLocalizationsDe extends AppLocalizations { @override String get sellLabel => 'Verkaufen'; + + @override + String get unableToOpenNotification => + 'Benachrichtigungsdetails können nicht geöffnet werden.'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 0b85c1ea..6235041e 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2004,4 +2004,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get sellLabel => 'Sell'; + + @override + String get unableToOpenNotification => 'Unable to open notification details.'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 77a60b91..37146210 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -2028,4 +2028,8 @@ class AppLocalizationsEs extends AppLocalizations { @override String get sellLabel => 'Vender'; + + @override + String get unableToOpenNotification => + 'No se pueden abrir los detalles de la notificación.'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 94d754f4..12a883b6 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -2037,4 +2037,8 @@ class AppLocalizationsFr extends AppLocalizations { @override String get sellLabel => 'Vendre'; + + @override + String get unableToOpenNotification => + 'Impossible d\'ouvrir les détails de la notification.'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 4cfb585e..8d4b3ce1 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -2026,4 +2026,8 @@ class AppLocalizationsIt extends AppLocalizations { @override String get sellLabel => 'Vendi'; + + @override + String get unableToOpenNotification => + 'Impossibile aprire i dettagli della notifica.'; } From ffc69b291cfad1839303a4becd8a4c663732c08e Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 22:22:38 -0300 Subject: [PATCH 27/30] feat: add emphasis styling to order preview amounts and durations Wrap amounts, prices, and durations in order preview sentences with asterisks in ARB files across all locales (en/de/es/fr/it). Update add_order_screen.dart to split preview text on '*' delimiters and render odd-indexed segments with bold weight and primary text color while keeping even-indexed segments in base style. --- .../order/screens/add_order_screen.dart | 19 ++++++++++++++++--- lib/l10n/app_de.arb | 8 ++++---- lib/l10n/app_en.arb | 8 ++++---- lib/l10n/app_es.arb | 8 ++++---- lib/l10n/app_fr.arb | 8 ++++---- lib/l10n/app_it.arb | 8 ++++---- lib/l10n/app_localizations.dart | 8 ++++---- lib/l10n/app_localizations_de.dart | 8 ++++---- lib/l10n/app_localizations_en.dart | 8 ++++---- lib/l10n/app_localizations_es.dart | 8 ++++---- lib/l10n/app_localizations_fr.dart | 8 ++++---- lib/l10n/app_localizations_it.dart | 8 ++++---- 12 files changed, 60 insertions(+), 47 deletions(-) diff --git a/lib/features/order/screens/add_order_screen.dart b/lib/features/order/screens/add_order_screen.dart index 20d573b6..bbf27fa3 100644 --- a/lib/features/order/screens/add_order_screen.dart +++ b/lib/features/order/screens/add_order_screen.dart @@ -481,9 +481,22 @@ class _AddOrderScreenState extends ConsumerState { ? l10n.previewBuyMarket(amountStr, priceLabel) : l10n.previewSellMarket(amountStr, priceLabel); } - body = Text( - sentence, - style: TextStyle(fontSize: 13, height: 1.5, color: secondary), + // Emphasized fragments (amounts, price, duration) are wrapped in '*' in + // the ARB; split on it and bold the odd-indexed segments. + final base = TextStyle(fontSize: 13, height: 1.5, color: secondary); + final bold = base.copyWith( + fontWeight: FontWeight.w700, + color: colors?.textPrimary ?? Colors.white, + ); + final segments = sentence.split('*'); + body = Text.rich( + TextSpan( + style: base, + children: [ + for (var i = 0; i < segments.length; i++) + TextSpan(text: segments[i], style: i.isOdd ? bold : null), + ], + ), ); } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 136ab43a..def2ec53 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -566,10 +566,10 @@ "fiatAmountHint": "Fiat-Betrag", "enterAmountForPreview": "Gib einen Betrag ein, um eine Live-Vorschau zu sehen.", "previewLabel": "VORSCHAU", - "previewBuyMarket": "Du kaufst BTC für {amount} zu {price} · 24 h aktiv", - "previewSellMarket": "Du verkaufst BTC für {amount} zu {price} · 24 h aktiv", - "previewReceiveFixed": "Du erhältst {sats} Sats für {amount} · 24 h aktiv", - "previewSellFixed": "Du verkaufst {sats} Sats für {amount} · 24 h aktiv", + "previewBuyMarket": "Du kaufst BTC für *{amount}* zu *{price}* · *24 h* aktiv", + "previewSellMarket": "Du verkaufst BTC für *{amount}* zu *{price}* · *24 h* aktiv", + "previewReceiveFixed": "Du erhältst *{sats} Sats* für *{amount}* · *24 h* aktiv", + "previewSellFixed": "Du verkaufst *{sats} Sats* für *{amount}* · *24 h* aktiv", "marketPriceLabel": "Marktpreis", "marketPricePremium": "Markt {premium}%", "priceTypeLabel": "Preistyp", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c20a72d0..6c137892 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1213,22 +1213,22 @@ "@enterAmountForPreview": {"description": "Preview footer text before an amount is entered"}, "previewLabel": "PREVIEW", "@previewLabel": {"description": "Label of the order preview footer"}, - "previewBuyMarket": "You buy BTC for {amount} at {price} · live for 24 h", + "previewBuyMarket": "You buy BTC for *{amount}* at *{price}* · live for *24 h*", "@previewBuyMarket": { "description": "Preview sentence for a market buy order", "placeholders": {"amount": {"type": "String"}, "price": {"type": "String"}} }, - "previewSellMarket": "You sell BTC for {amount} at {price} · live for 24 h", + "previewSellMarket": "You sell BTC for *{amount}* at *{price}* · live for *24 h*", "@previewSellMarket": { "description": "Preview sentence for a market sell order", "placeholders": {"amount": {"type": "String"}, "price": {"type": "String"}} }, - "previewReceiveFixed": "You receive {sats} sats for {amount} · live for 24 h", + "previewReceiveFixed": "You receive *{sats} sats* for *{amount}* · live for *24 h*", "@previewReceiveFixed": { "description": "Preview sentence for a fixed-price buy order", "placeholders": {"sats": {"type": "String"}, "amount": {"type": "String"}} }, - "previewSellFixed": "You sell {sats} sats for {amount} · live for 24 h", + "previewSellFixed": "You sell *{sats} sats* for *{amount}* · live for *24 h*", "@previewSellFixed": { "description": "Preview sentence for a fixed-price sell order", "placeholders": {"sats": {"type": "String"}, "amount": {"type": "String"}} diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 4bca6bc9..939e4128 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -566,10 +566,10 @@ "fiatAmountHint": "Monto fiat", "enterAmountForPreview": "Ingresa un monto para ver una vista previa en vivo.", "previewLabel": "VISTA PREVIA", - "previewBuyMarket": "Compras BTC por {amount} a {price} · activa por 24 h", - "previewSellMarket": "Vendes BTC por {amount} a {price} · activa por 24 h", - "previewReceiveFixed": "Recibes {sats} sats por {amount} · activa por 24 h", - "previewSellFixed": "Vendes {sats} sats por {amount} · activa por 24 h", + "previewBuyMarket": "Compras BTC por *{amount}* a *{price}* · activa por *24 h*", + "previewSellMarket": "Vendes BTC por *{amount}* a *{price}* · activa por *24 h*", + "previewReceiveFixed": "Recibes *{sats} sats* por *{amount}* · activa por *24 h*", + "previewSellFixed": "Vendes *{sats} sats* por *{amount}* · activa por *24 h*", "marketPriceLabel": "precio de mercado", "marketPricePremium": "mercado {premium}%", "priceTypeLabel": "Tipo de precio", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 3d9fa529..83986f84 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -566,10 +566,10 @@ "fiatAmountHint": "Montant fiat", "enterAmountForPreview": "Saisissez un montant pour voir un aperçu en direct.", "previewLabel": "APERÇU", - "previewBuyMarket": "Vous achetez des BTC pour {amount} à {price} · actif pendant 24 h", - "previewSellMarket": "Vous vendez des BTC pour {amount} à {price} · actif pendant 24 h", - "previewReceiveFixed": "Vous recevez {sats} sats pour {amount} · actif pendant 24 h", - "previewSellFixed": "Vous vendez {sats} sats pour {amount} · actif pendant 24 h", + "previewBuyMarket": "Vous achetez des BTC pour *{amount}* à *{price}* · actif pendant *24 h*", + "previewSellMarket": "Vous vendez des BTC pour *{amount}* à *{price}* · actif pendant *24 h*", + "previewReceiveFixed": "Vous recevez *{sats} sats* pour *{amount}* · actif pendant *24 h*", + "previewSellFixed": "Vous vendez *{sats} sats* pour *{amount}* · actif pendant *24 h*", "marketPriceLabel": "prix du marché", "marketPricePremium": "marché {premium}%", "priceTypeLabel": "Type de prix", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index fce6d268..1b310c48 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -566,10 +566,10 @@ "fiatAmountHint": "Importo fiat", "enterAmountForPreview": "Inserisci un importo per vedere un'anteprima in tempo reale.", "previewLabel": "ANTEPRIMA", - "previewBuyMarket": "Compri BTC per {amount} a {price} · attivo per 24 h", - "previewSellMarket": "Vendi BTC per {amount} a {price} · attivo per 24 h", - "previewReceiveFixed": "Ricevi {sats} sats per {amount} · attivo per 24 h", - "previewSellFixed": "Vendi {sats} sats per {amount} · attivo per 24 h", + "previewBuyMarket": "Compri BTC per *{amount}* a *{price}* · attivo per *24 h*", + "previewSellMarket": "Vendi BTC per *{amount}* a *{price}* · attivo per *24 h*", + "previewReceiveFixed": "Ricevi *{sats} sats* per *{amount}* · attivo per *24 h*", + "previewSellFixed": "Vendi *{sats} sats* per *{amount}* · attivo per *24 h*", "marketPriceLabel": "prezzo di mercato", "marketPricePremium": "mercato {premium}%", "priceTypeLabel": "Tipo di prezzo", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 50775b0d..d9d58ae3 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3365,25 +3365,25 @@ abstract class AppLocalizations { /// Preview sentence for a market buy order /// /// In en, this message translates to: - /// **'You buy BTC for {amount} at {price} · live for 24 h'** + /// **'You buy BTC for *{amount}* at *{price}* · live for *24 h*'** String previewBuyMarket(String amount, String price); /// Preview sentence for a market sell order /// /// In en, this message translates to: - /// **'You sell BTC for {amount} at {price} · live for 24 h'** + /// **'You sell BTC for *{amount}* at *{price}* · live for *24 h*'** String previewSellMarket(String amount, String price); /// Preview sentence for a fixed-price buy order /// /// In en, this message translates to: - /// **'You receive {sats} sats for {amount} · live for 24 h'** + /// **'You receive *{sats} sats* for *{amount}* · live for *24 h*'** String previewReceiveFixed(String sats, String amount); /// Preview sentence for a fixed-price sell order /// /// In en, this message translates to: - /// **'You sell {sats} sats for {amount} · live for 24 h'** + /// **'You sell *{sats} sats* for *{amount}* · live for *24 h*'** String previewSellFixed(String sats, String amount); /// Price label for market price with no premium diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 25a78972..04d3779d 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1849,22 +1849,22 @@ class AppLocalizationsDe extends AppLocalizations { @override String previewBuyMarket(String amount, String price) { - return 'Du kaufst BTC für $amount zu $price · 24 h aktiv'; + return 'Du kaufst BTC für *$amount* zu *$price* · *24 h* aktiv'; } @override String previewSellMarket(String amount, String price) { - return 'Du verkaufst BTC für $amount zu $price · 24 h aktiv'; + return 'Du verkaufst BTC für *$amount* zu *$price* · *24 h* aktiv'; } @override String previewReceiveFixed(String sats, String amount) { - return 'Du erhältst $sats Sats für $amount · 24 h aktiv'; + return 'Du erhältst *$sats Sats* für *$amount* · *24 h* aktiv'; } @override String previewSellFixed(String sats, String amount) { - return 'Du verkaufst $sats Sats für $amount · 24 h aktiv'; + return 'Du verkaufst *$sats Sats* für *$amount* · *24 h* aktiv'; } @override diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 6235041e..ec036220 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1820,22 +1820,22 @@ class AppLocalizationsEn extends AppLocalizations { @override String previewBuyMarket(String amount, String price) { - return 'You buy BTC for $amount at $price · live for 24 h'; + return 'You buy BTC for *$amount* at *$price* · live for *24 h*'; } @override String previewSellMarket(String amount, String price) { - return 'You sell BTC for $amount at $price · live for 24 h'; + return 'You sell BTC for *$amount* at *$price* · live for *24 h*'; } @override String previewReceiveFixed(String sats, String amount) { - return 'You receive $sats sats for $amount · live for 24 h'; + return 'You receive *$sats sats* for *$amount* · live for *24 h*'; } @override String previewSellFixed(String sats, String amount) { - return 'You sell $sats sats for $amount · live for 24 h'; + return 'You sell *$sats sats* for *$amount* · live for *24 h*'; } @override diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 37146210..3c48f329 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1844,22 +1844,22 @@ class AppLocalizationsEs extends AppLocalizations { @override String previewBuyMarket(String amount, String price) { - return 'Compras BTC por $amount a $price · activa por 24 h'; + return 'Compras BTC por *$amount* a *$price* · activa por *24 h*'; } @override String previewSellMarket(String amount, String price) { - return 'Vendes BTC por $amount a $price · activa por 24 h'; + return 'Vendes BTC por *$amount* a *$price* · activa por *24 h*'; } @override String previewReceiveFixed(String sats, String amount) { - return 'Recibes $sats sats por $amount · activa por 24 h'; + return 'Recibes *$sats sats* por *$amount* · activa por *24 h*'; } @override String previewSellFixed(String sats, String amount) { - return 'Vendes $sats sats por $amount · activa por 24 h'; + return 'Vendes *$sats sats* por *$amount* · activa por *24 h*'; } @override diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 12a883b6..5ff98668 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1853,22 +1853,22 @@ class AppLocalizationsFr extends AppLocalizations { @override String previewBuyMarket(String amount, String price) { - return 'Vous achetez des BTC pour $amount à $price · actif pendant 24 h'; + return 'Vous achetez des BTC pour *$amount* à *$price* · actif pendant *24 h*'; } @override String previewSellMarket(String amount, String price) { - return 'Vous vendez des BTC pour $amount à $price · actif pendant 24 h'; + return 'Vous vendez des BTC pour *$amount* à *$price* · actif pendant *24 h*'; } @override String previewReceiveFixed(String sats, String amount) { - return 'Vous recevez $sats sats pour $amount · actif pendant 24 h'; + return 'Vous recevez *$sats sats* pour *$amount* · actif pendant *24 h*'; } @override String previewSellFixed(String sats, String amount) { - return 'Vous vendez $sats sats pour $amount · actif pendant 24 h'; + return 'Vous vendez *$sats sats* pour *$amount* · actif pendant *24 h*'; } @override diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 8d4b3ce1..19f6531c 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1842,22 +1842,22 @@ class AppLocalizationsIt extends AppLocalizations { @override String previewBuyMarket(String amount, String price) { - return 'Compri BTC per $amount a $price · attivo per 24 h'; + return 'Compri BTC per *$amount* a *$price* · attivo per *24 h*'; } @override String previewSellMarket(String amount, String price) { - return 'Vendi BTC per $amount a $price · attivo per 24 h'; + return 'Vendi BTC per *$amount* a *$price* · attivo per *24 h*'; } @override String previewReceiveFixed(String sats, String amount) { - return 'Ricevi $sats sats per $amount · attivo per 24 h'; + return 'Ricevi *$sats sats* per *$amount* · attivo per *24 h*'; } @override String previewSellFixed(String sats, String amount) { - return 'Vendi $sats sats per $amount · attivo per 24 h'; + return 'Vendi *$sats sats* per *$amount* · attivo per *24 h*'; } @override From 28be4fba826682504dda2a16a5bcef49484caa1e Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 22:54:17 -0300 Subject: [PATCH 28/30] refactor: replace hardcoded strings with localized translations in order list and notifications --- .../home/widgets/order_list_item.dart | 26 ++-- .../screens/notifications_screen.dart | 17 ++- .../widgets/notification_card.dart | 13 +- .../widgets/notification_group_card.dart | 31 +++-- .../widgets/system_notification_banner.dart | 2 +- .../order/screens/my_order_screen.dart | 3 +- lib/l10n/app_de.arb | 23 +++- lib/l10n/app_en.arb | 59 +++++++- lib/l10n/app_es.arb | 23 +++- lib/l10n/app_fr.arb | 23 +++- lib/l10n/app_it.arb | 23 +++- lib/l10n/app_localizations.dart | 126 ++++++++++++++++++ lib/l10n/app_localizations_de.dart | 80 +++++++++++ lib/l10n/app_localizations_en.dart | 80 +++++++++++ lib/l10n/app_localizations_es.dart | 80 +++++++++++ lib/l10n/app_localizations_fr.dart | 80 +++++++++++ lib/l10n/app_localizations_it.dart | 80 +++++++++++ lib/shared/widgets/nwc_invoice_widget.dart | 11 +- lib/shared/widgets/nwc_payment_widget.dart | 5 +- lib/shared/widgets/nym_avatar.dart | 4 +- 20 files changed, 736 insertions(+), 53 deletions(-) diff --git a/lib/features/home/widgets/order_list_item.dart b/lib/features/home/widgets/order_list_item.dart index 9a7b831f..9690d823 100644 --- a/lib/features/home/widgets/order_list_item.dart +++ b/lib/features/home/widgets/order_list_item.dart @@ -64,17 +64,17 @@ class OrderListItem extends StatelessWidget { // Reason pill styling (gold per design ≈ #FFC940; blue per info-blue). final (reasonLabel, reasonColor, reasonBg) = switch (reason) { OrderReason.bestPremium => ( - '⚡ Best premium', + l10n.reasonBestPremium, green, green.withValues(alpha: 0.15), ), OrderReason.mostReputable => ( - '⭐ Most reputable', + l10n.reasonMostReputable, Colors.amber, Colors.amber.withValues(alpha: 0.15), ), OrderReason.justPublished => ( - '🆕 Just published', + l10n.reasonJustPublished, const Color(0xFF7BB4F0), blueFill.withValues(alpha: 0.55), ), @@ -114,7 +114,7 @@ class OrderListItem extends StatelessWidget { ), const Spacer(), Text( - _relativeTime(order.createdAt), + _relativeTime(order.createdAt, l10n), style: theme.textTheme.bodySmall!.copyWith(color: textSec), ), ], @@ -151,7 +151,7 @@ class OrderListItem extends StatelessWidget { // Row 3: small secondary "Market price" caption Text( - 'Market price', + l10n.marketPriceCaption, style: TextStyle(color: textSec, fontSize: 11), ), const SizedBox(height: AppSpacing.sm), @@ -181,8 +181,8 @@ class OrderListItem extends StatelessWidget { ), Flexible( child: Text( - ' · ${order.tradeCount} trades' - ' · ${order.daysActive} days', + l10n.orderReputationStats( + order.tradeCount, order.daysActive), style: TextStyle(color: textSec, fontSize: 12), overflow: TextOverflow.ellipsis, ), @@ -223,12 +223,12 @@ class OrderListItem extends StatelessWidget { ); } - String _relativeTime(DateTime dt) { + String _relativeTime(DateTime dt, AppLocalizations l10n) { final diff = clock.now().difference(dt); - if (diff.isNegative || diff.inMinutes < 1) return 'Just now'; - if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; - if (diff.inHours < 24) return '${diff.inHours}h ago'; - return '${diff.inDays}d ago'; + if (diff.isNegative || diff.inMinutes < 1) return l10n.justNow; + if (diff.inMinutes < 60) return l10n.minutesAgo(diff.inMinutes); + if (diff.inHours < 24) return l10n.hoursAgo(diff.inHours); + return l10n.daysAgo(diff.inDays); } } @@ -340,7 +340,7 @@ class OrderListEmpty extends StatelessWidget { const Icon(Icons.inbox_outlined, size: 48, color: Colors.white38), const SizedBox(height: AppSpacing.md), Text( - 'No orders available', + AppLocalizations.of(context).noOrdersAvailable, style: Theme.of(context) .textTheme .bodyMedium! diff --git a/lib/features/notifications/screens/notifications_screen.dart b/lib/features/notifications/screens/notifications_screen.dart index 321ca739..76eeb96f 100644 --- a/lib/features/notifications/screens/notifications_screen.dart +++ b/lib/features/notifications/screens/notifications_screen.dart @@ -247,15 +247,20 @@ class _FilterChipsRow extends StatelessWidget { @override Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); final chips = <(_NotificationFilter, String)>[ - (_NotificationFilter.all, 'All'), + (_NotificationFilter.all, l10n.notifFilterAll), ( _NotificationFilter.disputes, - disputeCount > 0 ? 'Disputes · $disputeCount' : 'Disputes', + disputeCount > 0 + ? l10n.notifFilterDisputesCount(disputeCount) + : l10n.notifFilterDisputes, ), ( _NotificationFilter.system, - systemCount > 0 ? 'System · $systemCount' : 'System', + systemCount > 0 + ? l10n.notifFilterSystemCount(systemCount) + : l10n.notifFilterSystem, ), ]; @@ -371,7 +376,7 @@ class _BackupReminderBanner extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'You must back up your account', + AppLocalizations.of(context).youMustBackUpYourAccount, style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontWeight: FontWeight.w600, fontSize: 13, @@ -380,7 +385,7 @@ class _BackupReminderBanner extends StatelessWidget { ), const SizedBox(height: 2), Text( - 'Tap to view and save your secret words.', + AppLocalizations.of(context).tapToViewAndSaveSecretWords, style: Theme.of( context, ).textTheme.bodySmall!.copyWith(fontSize: 11), @@ -442,7 +447,7 @@ class _EmptyState extends StatelessWidget { ), const SizedBox(height: AppSpacing.md), Text( - 'No notifications', + AppLocalizations.of(context).noNotifications, style: Theme.of( context, ).textTheme.bodyMedium!.copyWith(color: Colors.white38), diff --git a/lib/features/notifications/widgets/notification_card.dart b/lib/features/notifications/widgets/notification_card.dart index 855fee2f..51cc8ba5 100644 --- a/lib/features/notifications/widgets/notification_card.dart +++ b/lib/features/notifications/widgets/notification_card.dart @@ -91,7 +91,8 @@ class NotificationCard extends StatelessWidget { ], const SizedBox(height: AppSpacing.sm), Text( - _relativeTime(notification.timestamp), + _relativeTime( + notification.timestamp, AppLocalizations.of(context)), style: Theme.of(context).textTheme.bodySmall!.copyWith( color: textSec, fontSize: 12, @@ -123,12 +124,12 @@ class NotificationCard extends StatelessWidget { ); } - String _relativeTime(DateTime dt) { + String _relativeTime(DateTime dt, AppLocalizations l10n) { final diff = clock.now().difference(dt); - if (diff.isNegative || diff.inMinutes < 1) return 'Just now'; - if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; - if (diff.inHours < 24) return '${diff.inHours}h ago'; - return '${diff.inDays}d ago'; + if (diff.isNegative || diff.inMinutes < 1) return l10n.justNow; + if (diff.inMinutes < 60) return l10n.minutesAgo(diff.inMinutes); + if (diff.inHours < 24) return l10n.hoursAgo(diff.inHours); + return l10n.daysAgo(diff.inDays); } } diff --git a/lib/features/notifications/widgets/notification_group_card.dart b/lib/features/notifications/widgets/notification_group_card.dart index deb598ee..2a1dfe5d 100644 --- a/lib/features/notifications/widgets/notification_group_card.dart +++ b/lib/features/notifications/widgets/notification_group_card.dart @@ -81,7 +81,7 @@ class _NotificationGroupCardState extends State { const SizedBox(width: AppSpacing.sm), Expanded( child: Text( - _groupTitle(), + _groupTitle(AppLocalizations.of(context)), style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontWeight: FontWeight.w600, color: Colors.white, @@ -163,9 +163,10 @@ class _NotificationGroupCardState extends State { Flexible( child: Text( _expanded - ? 'Hide earlier events' - : 'View ${_earlier.length} earlier ' - '${_earlier.length == 1 ? 'event' : 'events'}', + ? AppLocalizations.of(context) + .hideEarlierEvents + : AppLocalizations.of(context) + .viewEarlierEvents(_earlier.length), style: Theme.of(context) .textTheme .bodySmall! @@ -187,7 +188,9 @@ class _NotificationGroupCardState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - widget.isDisputeGroup ? 'View dispute' : 'Go to trade', + widget.isDisputeGroup + ? AppLocalizations.of(context).viewDisputeButton + : AppLocalizations.of(context).goToTrade, style: Theme.of(context).textTheme.bodySmall!.copyWith( color: green, fontSize: 12, @@ -209,15 +212,15 @@ class _NotificationGroupCardState extends State { /// "Trade · 5,400 sats" when an amount is derivable from any event's /// detail map, otherwise "Trade #a1b2c3d4" (short order id). - String _groupTitle() { + String _groupTitle(AppLocalizations l10n) { + final label = widget.isDisputeGroup ? l10n.disputeWord : l10n.tradeWord; final sats = _deriveSats(); if (sats != null) { - final label = widget.isDisputeGroup ? 'Dispute' : 'Trade'; return '$label · $sats sats'; } final id = _latest.orderId ?? _latest.disputeId ?? ''; final shortId = id.length > 8 ? id.substring(0, 8) : id; - return widget.isDisputeGroup ? 'Dispute #$shortId' : 'Trade #$shortId'; + return '$label #$shortId'; } /// Looks for an "N sats" amount in any event's detail values. @@ -315,7 +318,7 @@ class _EventRow extends StatelessWidget { ), const SizedBox(width: AppSpacing.sm), Text( - '· ${relativeTime(notification.timestamp)}', + '· ${relativeTime(notification.timestamp, AppLocalizations.of(context))}', style: Theme.of( context, ).textTheme.bodySmall!.copyWith(color: textSec, fontSize: 11), @@ -378,10 +381,10 @@ class _EventOverflowMenu extends StatelessWidget { } /// Shared relative-time formatter for notification widgets. -String relativeTime(DateTime dt) { +String relativeTime(DateTime dt, AppLocalizations l10n) { final diff = DateTime.now().difference(dt); - if (diff.isNegative || diff.inMinutes < 1) return 'Just now'; - if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; - if (diff.inHours < 24) return '${diff.inHours}h ago'; - return '${diff.inDays}d ago'; + if (diff.isNegative || diff.inMinutes < 1) return l10n.justNow; + if (diff.inMinutes < 60) return l10n.minutesAgo(diff.inMinutes); + if (diff.inHours < 24) return l10n.hoursAgo(diff.inHours); + return l10n.daysAgo(diff.inDays); } diff --git a/lib/features/notifications/widgets/system_notification_banner.dart b/lib/features/notifications/widgets/system_notification_banner.dart index 1c91bb01..91448039 100644 --- a/lib/features/notifications/widgets/system_notification_banner.dart +++ b/lib/features/notifications/widgets/system_notification_banner.dart @@ -99,7 +99,7 @@ class SystemNotificationBanner extends StatelessWidget { const SizedBox(height: 2), Text( '${notification.message} ' - '· ${relativeTime(notification.timestamp)}', + '· ${relativeTime(notification.timestamp, AppLocalizations.of(context))}', style: Theme.of(context).textTheme.bodySmall!.copyWith( color: textSec, fontSize: 11, diff --git a/lib/features/order/screens/my_order_screen.dart b/lib/features/order/screens/my_order_screen.dart index a995a016..5bb17c3a 100644 --- a/lib/features/order/screens/my_order_screen.dart +++ b/lib/features/order/screens/my_order_screen.dart @@ -204,7 +204,8 @@ class _MyOrderScreenState extends ConsumerState { ), const SizedBox(height: AppSpacing.xs), Text( - 'Market Price (${premiumPositive ? '+' : ''}${resolvedOrder.premium.toStringAsFixed(1)}%)', + AppLocalizations.of(context).marketPricePremiumLabel( + '${premiumPositive ? '+' : ''}${resolvedOrder.premium.toStringAsFixed(1)}'), style: TextStyle( color: premiumPositive ? green : sellColor, fontSize: 13, diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index def2ec53..112b22bd 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -617,5 +617,26 @@ "changeButton": "Ändern", "buyLabel": "Kaufen", "sellLabel": "Verkaufen", - "unableToOpenNotification": "Benachrichtigungsdetails können nicht geöffnet werden." + "unableToOpenNotification": "Benachrichtigungsdetails können nicht geöffnet werden.", + "reasonBestPremium": "⚡ Beste Prämie", + "reasonMostReputable": "⭐ Am angesehensten", + "reasonJustPublished": "🆕 Gerade veröffentlicht", + "marketPriceCaption": "Marktpreis", + "orderReputationStats": " · {trades} Trades · {days} Tage", + "hideEarlierEvents": "Frühere Ereignisse ausblenden", + "viewEarlierEvents": "{count, plural, =1{1 früheres Ereignis anzeigen} other{{count} frühere Ereignisse anzeigen}}", + "goToTrade": "Zum Trade", + "disputeWord": "Streitfall", + "tradeWord": "Trade", + "notifFilterAll": "Alle", + "notifFilterDisputes": "Streitfälle", + "notifFilterDisputesCount": "Streitfälle · {count}", + "notifFilterSystem": "System", + "notifFilterSystemCount": "System · {count}", + "payingStatus": "Zahlung läuft...", + "payWithWalletButton": "Mit Wallet bezahlen", + "generatingInvoiceNwc": "Rechnung wird über NWC erstellt...", + "unableToGenerateInvoice": "Rechnung konnte nicht automatisch erstellt werden", + "avatarIconLabel": "Avatar-Symbol", + "marketPricePremiumLabel": "Marktpreis ({premium}%)" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6c137892..5cc88c18 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1352,5 +1352,62 @@ "sellLabel": "Sell", "@sellLabel": {"description": "Sell action in the add-order button"}, "unableToOpenNotification": "Unable to open notification details.", - "@unableToOpenNotification": {"description": "Snackbar shown when a notification has no linked detail to open"} + "@unableToOpenNotification": {"description": "Snackbar shown when a notification has no linked detail to open"}, + "reasonBestPremium": "⚡ Best premium", + "@reasonBestPremium": {"description": "Order-book reason badge: best premium"}, + "reasonMostReputable": "⭐ Most reputable", + "@reasonMostReputable": {"description": "Order-book reason badge: most reputable"}, + "reasonJustPublished": "🆕 Just published", + "@reasonJustPublished": {"description": "Order-book reason badge: just published"}, + "marketPriceCaption": "Market price", + "@marketPriceCaption": {"description": "Caption under an order-book card amount"}, + "orderReputationStats": " · {trades} trades · {days} days", + "@orderReputationStats": { + "description": "Trades and active days in the order-book reputation row", + "placeholders": {"trades": {"type": "int"}, "days": {"type": "int"}} + }, + "hideEarlierEvents": "Hide earlier events", + "@hideEarlierEvents": {"description": "Notification group action to collapse earlier events"}, + "viewEarlierEvents": "{count, plural, =1{View 1 earlier event} other{View {count} earlier events}}", + "@viewEarlierEvents": { + "description": "Notification group action to expand earlier events", + "placeholders": {"count": {"type": "int"}} + }, + "goToTrade": "Go to trade", + "@goToTrade": {"description": "Notification group footer link to the trade"}, + "disputeWord": "Dispute", + "@disputeWord": {"description": "Word 'Dispute' used in a notification group title"}, + "tradeWord": "Trade", + "@tradeWord": {"description": "Word 'Trade' used in a notification group title"}, + "notifFilterAll": "All", + "@notifFilterAll": {"description": "Notifications filter chip: all"}, + "notifFilterDisputes": "Disputes", + "@notifFilterDisputes": {"description": "Notifications filter chip: disputes"}, + "notifFilterDisputesCount": "Disputes · {count}", + "@notifFilterDisputesCount": { + "description": "Notifications filter chip: disputes with count", + "placeholders": {"count": {"type": "int"}} + }, + "notifFilterSystem": "System", + "@notifFilterSystem": {"description": "Notifications filter chip: system"}, + "notifFilterSystemCount": "System · {count}", + "@notifFilterSystemCount": { + "description": "Notifications filter chip: system with count", + "placeholders": {"count": {"type": "int"}} + }, + "payingStatus": "Paying...", + "@payingStatus": {"description": "NWC pay button label while paying"}, + "payWithWalletButton": "Pay with Wallet", + "@payWithWalletButton": {"description": "NWC pay button label"}, + "generatingInvoiceNwc": "Generating invoice via NWC...", + "@generatingInvoiceNwc": {"description": "Status while auto-generating a Lightning invoice via NWC"}, + "unableToGenerateInvoice": "Unable to generate invoice automatically", + "@unableToGenerateInvoice": {"description": "Error when NWC invoice auto-generation fails"}, + "avatarIconLabel": "Avatar icon", + "@avatarIconLabel": {"description": "Accessibility label for the peer avatar icon"}, + "marketPricePremiumLabel": "Market Price ({premium}%)", + "@marketPricePremiumLabel": { + "description": "Market price with premium on the my-order screen", + "placeholders": {"premium": {"type": "String"}} + } } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 939e4128..113bb948 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -617,5 +617,26 @@ "changeButton": "Cambiar", "buyLabel": "Comprar", "sellLabel": "Vender", - "unableToOpenNotification": "No se pueden abrir los detalles de la notificación." + "unableToOpenNotification": "No se pueden abrir los detalles de la notificación.", + "reasonBestPremium": "⚡ Mejor prima", + "reasonMostReputable": "⭐ Más reputado", + "reasonJustPublished": "🆕 Recién publicada", + "marketPriceCaption": "Precio de mercado", + "orderReputationStats": " · {trades} operaciones · {days} días", + "hideEarlierEvents": "Ocultar eventos anteriores", + "viewEarlierEvents": "{count, plural, =1{Ver 1 evento anterior} other{Ver {count} eventos anteriores}}", + "goToTrade": "Ir a la operación", + "disputeWord": "Disputa", + "tradeWord": "Operación", + "notifFilterAll": "Todas", + "notifFilterDisputes": "Disputas", + "notifFilterDisputesCount": "Disputas · {count}", + "notifFilterSystem": "Sistema", + "notifFilterSystemCount": "Sistema · {count}", + "payingStatus": "Pagando...", + "payWithWalletButton": "Pagar con wallet", + "generatingInvoiceNwc": "Generando factura vía NWC...", + "unableToGenerateInvoice": "No se pudo generar la factura automáticamente", + "avatarIconLabel": "Icono de avatar", + "marketPricePremiumLabel": "Precio de mercado ({premium}%)" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 83986f84..4065ce69 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -617,5 +617,26 @@ "changeButton": "Modifier", "buyLabel": "Acheter", "sellLabel": "Vendre", - "unableToOpenNotification": "Impossible d'ouvrir les détails de la notification." + "unableToOpenNotification": "Impossible d'ouvrir les détails de la notification.", + "reasonBestPremium": "⚡ Meilleure prime", + "reasonMostReputable": "⭐ Plus réputé", + "reasonJustPublished": "🆕 Vient d'être publié", + "marketPriceCaption": "Prix du marché", + "orderReputationStats": " · {trades} transactions · {days} jours", + "hideEarlierEvents": "Masquer les événements précédents", + "viewEarlierEvents": "{count, plural, =1{Voir 1 événement précédent} other{Voir {count} événements précédents}}", + "goToTrade": "Aller à la transaction", + "disputeWord": "Litige", + "tradeWord": "Transaction", + "notifFilterAll": "Toutes", + "notifFilterDisputes": "Litiges", + "notifFilterDisputesCount": "Litiges · {count}", + "notifFilterSystem": "Système", + "notifFilterSystemCount": "Système · {count}", + "payingStatus": "Paiement...", + "payWithWalletButton": "Payer avec le portefeuille", + "generatingInvoiceNwc": "Génération de la facture via NWC...", + "unableToGenerateInvoice": "Impossible de générer la facture automatiquement", + "avatarIconLabel": "Icône d'avatar", + "marketPricePremiumLabel": "Prix du marché ({premium}%)" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 1b310c48..cad07336 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -617,5 +617,26 @@ "changeButton": "Modifica", "buyLabel": "Compra", "sellLabel": "Vendi", - "unableToOpenNotification": "Impossibile aprire i dettagli della notifica." + "unableToOpenNotification": "Impossibile aprire i dettagli della notifica.", + "reasonBestPremium": "⚡ Miglior premio", + "reasonMostReputable": "⭐ Più affidabile", + "reasonJustPublished": "🆕 Appena pubblicata", + "marketPriceCaption": "Prezzo di mercato", + "orderReputationStats": " · {trades} operazioni · {days} giorni", + "hideEarlierEvents": "Nascondi eventi precedenti", + "viewEarlierEvents": "{count, plural, =1{Vedi 1 evento precedente} other{Vedi {count} eventi precedenti}}", + "goToTrade": "Vai all'operazione", + "disputeWord": "Disputa", + "tradeWord": "Operazione", + "notifFilterAll": "Tutte", + "notifFilterDisputes": "Dispute", + "notifFilterDisputesCount": "Dispute · {count}", + "notifFilterSystem": "Sistema", + "notifFilterSystemCount": "Sistema · {count}", + "payingStatus": "Pagamento in corso...", + "payWithWalletButton": "Paga con il wallet", + "generatingInvoiceNwc": "Generazione fattura tramite NWC...", + "unableToGenerateInvoice": "Impossibile generare la fattura automaticamente", + "avatarIconLabel": "Icona avatar", + "marketPricePremiumLabel": "Prezzo di mercato ({premium}%)" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index d9d58ae3..a23150a6 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3673,6 +3673,132 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Unable to open notification details.'** String get unableToOpenNotification; + + /// Order-book reason badge: best premium + /// + /// In en, this message translates to: + /// **'⚡ Best premium'** + String get reasonBestPremium; + + /// Order-book reason badge: most reputable + /// + /// In en, this message translates to: + /// **'⭐ Most reputable'** + String get reasonMostReputable; + + /// Order-book reason badge: just published + /// + /// In en, this message translates to: + /// **'🆕 Just published'** + String get reasonJustPublished; + + /// Caption under an order-book card amount + /// + /// In en, this message translates to: + /// **'Market price'** + String get marketPriceCaption; + + /// Trades and active days in the order-book reputation row + /// + /// In en, this message translates to: + /// **' · {trades} trades · {days} days'** + String orderReputationStats(int trades, int days); + + /// Notification group action to collapse earlier events + /// + /// In en, this message translates to: + /// **'Hide earlier events'** + String get hideEarlierEvents; + + /// Notification group action to expand earlier events + /// + /// In en, this message translates to: + /// **'{count, plural, =1{View 1 earlier event} other{View {count} earlier events}}'** + String viewEarlierEvents(int count); + + /// Notification group footer link to the trade + /// + /// In en, this message translates to: + /// **'Go to trade'** + String get goToTrade; + + /// Word 'Dispute' used in a notification group title + /// + /// In en, this message translates to: + /// **'Dispute'** + String get disputeWord; + + /// Word 'Trade' used in a notification group title + /// + /// In en, this message translates to: + /// **'Trade'** + String get tradeWord; + + /// Notifications filter chip: all + /// + /// In en, this message translates to: + /// **'All'** + String get notifFilterAll; + + /// Notifications filter chip: disputes + /// + /// In en, this message translates to: + /// **'Disputes'** + String get notifFilterDisputes; + + /// Notifications filter chip: disputes with count + /// + /// In en, this message translates to: + /// **'Disputes · {count}'** + String notifFilterDisputesCount(int count); + + /// Notifications filter chip: system + /// + /// In en, this message translates to: + /// **'System'** + String get notifFilterSystem; + + /// Notifications filter chip: system with count + /// + /// In en, this message translates to: + /// **'System · {count}'** + String notifFilterSystemCount(int count); + + /// NWC pay button label while paying + /// + /// In en, this message translates to: + /// **'Paying...'** + String get payingStatus; + + /// NWC pay button label + /// + /// In en, this message translates to: + /// **'Pay with Wallet'** + String get payWithWalletButton; + + /// Status while auto-generating a Lightning invoice via NWC + /// + /// In en, this message translates to: + /// **'Generating invoice via NWC...'** + String get generatingInvoiceNwc; + + /// Error when NWC invoice auto-generation fails + /// + /// In en, this message translates to: + /// **'Unable to generate invoice automatically'** + String get unableToGenerateInvoice; + + /// Accessibility label for the peer avatar icon + /// + /// In en, this message translates to: + /// **'Avatar icon'** + String get avatarIconLabel; + + /// Market price with premium on the my-order screen + /// + /// In en, this message translates to: + /// **'Market Price ({premium}%)'** + String marketPricePremiumLabel(String premium); } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 04d3779d..4f8aeb6b 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -2038,4 +2038,84 @@ class AppLocalizationsDe extends AppLocalizations { @override String get unableToOpenNotification => 'Benachrichtigungsdetails können nicht geöffnet werden.'; + + @override + String get reasonBestPremium => '⚡ Beste Prämie'; + + @override + String get reasonMostReputable => '⭐ Am angesehensten'; + + @override + String get reasonJustPublished => '🆕 Gerade veröffentlicht'; + + @override + String get marketPriceCaption => 'Marktpreis'; + + @override + String orderReputationStats(int trades, int days) { + return ' · $trades Trades · $days Tage'; + } + + @override + String get hideEarlierEvents => 'Frühere Ereignisse ausblenden'; + + @override + String viewEarlierEvents(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count frühere Ereignisse anzeigen', + one: '1 früheres Ereignis anzeigen', + ); + return '$_temp0'; + } + + @override + String get goToTrade => 'Zum Trade'; + + @override + String get disputeWord => 'Streitfall'; + + @override + String get tradeWord => 'Trade'; + + @override + String get notifFilterAll => 'Alle'; + + @override + String get notifFilterDisputes => 'Streitfälle'; + + @override + String notifFilterDisputesCount(int count) { + return 'Streitfälle · $count'; + } + + @override + String get notifFilterSystem => 'System'; + + @override + String notifFilterSystemCount(int count) { + return 'System · $count'; + } + + @override + String get payingStatus => 'Zahlung läuft...'; + + @override + String get payWithWalletButton => 'Mit Wallet bezahlen'; + + @override + String get generatingInvoiceNwc => 'Rechnung wird über NWC erstellt...'; + + @override + String get unableToGenerateInvoice => + 'Rechnung konnte nicht automatisch erstellt werden'; + + @override + String get avatarIconLabel => 'Avatar-Symbol'; + + @override + String marketPricePremiumLabel(String premium) { + return 'Marktpreis ($premium%)'; + } } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index ec036220..97f2cdaa 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2007,4 +2007,84 @@ class AppLocalizationsEn extends AppLocalizations { @override String get unableToOpenNotification => 'Unable to open notification details.'; + + @override + String get reasonBestPremium => '⚡ Best premium'; + + @override + String get reasonMostReputable => '⭐ Most reputable'; + + @override + String get reasonJustPublished => '🆕 Just published'; + + @override + String get marketPriceCaption => 'Market price'; + + @override + String orderReputationStats(int trades, int days) { + return ' · $trades trades · $days days'; + } + + @override + String get hideEarlierEvents => 'Hide earlier events'; + + @override + String viewEarlierEvents(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'View $count earlier events', + one: 'View 1 earlier event', + ); + return '$_temp0'; + } + + @override + String get goToTrade => 'Go to trade'; + + @override + String get disputeWord => 'Dispute'; + + @override + String get tradeWord => 'Trade'; + + @override + String get notifFilterAll => 'All'; + + @override + String get notifFilterDisputes => 'Disputes'; + + @override + String notifFilterDisputesCount(int count) { + return 'Disputes · $count'; + } + + @override + String get notifFilterSystem => 'System'; + + @override + String notifFilterSystemCount(int count) { + return 'System · $count'; + } + + @override + String get payingStatus => 'Paying...'; + + @override + String get payWithWalletButton => 'Pay with Wallet'; + + @override + String get generatingInvoiceNwc => 'Generating invoice via NWC...'; + + @override + String get unableToGenerateInvoice => + 'Unable to generate invoice automatically'; + + @override + String get avatarIconLabel => 'Avatar icon'; + + @override + String marketPricePremiumLabel(String premium) { + return 'Market Price ($premium%)'; + } } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 3c48f329..2030fe1f 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -2032,4 +2032,84 @@ class AppLocalizationsEs extends AppLocalizations { @override String get unableToOpenNotification => 'No se pueden abrir los detalles de la notificación.'; + + @override + String get reasonBestPremium => '⚡ Mejor prima'; + + @override + String get reasonMostReputable => '⭐ Más reputado'; + + @override + String get reasonJustPublished => '🆕 Recién publicada'; + + @override + String get marketPriceCaption => 'Precio de mercado'; + + @override + String orderReputationStats(int trades, int days) { + return ' · $trades operaciones · $days días'; + } + + @override + String get hideEarlierEvents => 'Ocultar eventos anteriores'; + + @override + String viewEarlierEvents(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Ver $count eventos anteriores', + one: 'Ver 1 evento anterior', + ); + return '$_temp0'; + } + + @override + String get goToTrade => 'Ir a la operación'; + + @override + String get disputeWord => 'Disputa'; + + @override + String get tradeWord => 'Operación'; + + @override + String get notifFilterAll => 'Todas'; + + @override + String get notifFilterDisputes => 'Disputas'; + + @override + String notifFilterDisputesCount(int count) { + return 'Disputas · $count'; + } + + @override + String get notifFilterSystem => 'Sistema'; + + @override + String notifFilterSystemCount(int count) { + return 'Sistema · $count'; + } + + @override + String get payingStatus => 'Pagando...'; + + @override + String get payWithWalletButton => 'Pagar con wallet'; + + @override + String get generatingInvoiceNwc => 'Generando factura vía NWC...'; + + @override + String get unableToGenerateInvoice => + 'No se pudo generar la factura automáticamente'; + + @override + String get avatarIconLabel => 'Icono de avatar'; + + @override + String marketPricePremiumLabel(String premium) { + return 'Precio de mercado ($premium%)'; + } } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 5ff98668..4867246b 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -2041,4 +2041,84 @@ class AppLocalizationsFr extends AppLocalizations { @override String get unableToOpenNotification => 'Impossible d\'ouvrir les détails de la notification.'; + + @override + String get reasonBestPremium => '⚡ Meilleure prime'; + + @override + String get reasonMostReputable => '⭐ Plus réputé'; + + @override + String get reasonJustPublished => '🆕 Vient d\'être publié'; + + @override + String get marketPriceCaption => 'Prix du marché'; + + @override + String orderReputationStats(int trades, int days) { + return ' · $trades transactions · $days jours'; + } + + @override + String get hideEarlierEvents => 'Masquer les événements précédents'; + + @override + String viewEarlierEvents(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Voir $count événements précédents', + one: 'Voir 1 événement précédent', + ); + return '$_temp0'; + } + + @override + String get goToTrade => 'Aller à la transaction'; + + @override + String get disputeWord => 'Litige'; + + @override + String get tradeWord => 'Transaction'; + + @override + String get notifFilterAll => 'Toutes'; + + @override + String get notifFilterDisputes => 'Litiges'; + + @override + String notifFilterDisputesCount(int count) { + return 'Litiges · $count'; + } + + @override + String get notifFilterSystem => 'Système'; + + @override + String notifFilterSystemCount(int count) { + return 'Système · $count'; + } + + @override + String get payingStatus => 'Paiement...'; + + @override + String get payWithWalletButton => 'Payer avec le portefeuille'; + + @override + String get generatingInvoiceNwc => 'Génération de la facture via NWC...'; + + @override + String get unableToGenerateInvoice => + 'Impossible de générer la facture automatiquement'; + + @override + String get avatarIconLabel => 'Icône d\'avatar'; + + @override + String marketPricePremiumLabel(String premium) { + return 'Prix du marché ($premium%)'; + } } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 19f6531c..c7f6511c 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -2030,4 +2030,84 @@ class AppLocalizationsIt extends AppLocalizations { @override String get unableToOpenNotification => 'Impossibile aprire i dettagli della notifica.'; + + @override + String get reasonBestPremium => '⚡ Miglior premio'; + + @override + String get reasonMostReputable => '⭐ Più affidabile'; + + @override + String get reasonJustPublished => '🆕 Appena pubblicata'; + + @override + String get marketPriceCaption => 'Prezzo di mercato'; + + @override + String orderReputationStats(int trades, int days) { + return ' · $trades operazioni · $days giorni'; + } + + @override + String get hideEarlierEvents => 'Nascondi eventi precedenti'; + + @override + String viewEarlierEvents(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Vedi $count eventi precedenti', + one: 'Vedi 1 evento precedente', + ); + return '$_temp0'; + } + + @override + String get goToTrade => 'Vai all\'operazione'; + + @override + String get disputeWord => 'Disputa'; + + @override + String get tradeWord => 'Operazione'; + + @override + String get notifFilterAll => 'Tutte'; + + @override + String get notifFilterDisputes => 'Dispute'; + + @override + String notifFilterDisputesCount(int count) { + return 'Dispute · $count'; + } + + @override + String get notifFilterSystem => 'Sistema'; + + @override + String notifFilterSystemCount(int count) { + return 'Sistema · $count'; + } + + @override + String get payingStatus => 'Pagamento in corso...'; + + @override + String get payWithWalletButton => 'Paga con il wallet'; + + @override + String get generatingInvoiceNwc => 'Generazione fattura tramite NWC...'; + + @override + String get unableToGenerateInvoice => + 'Impossibile generare la fattura automaticamente'; + + @override + String get avatarIconLabel => 'Icona avatar'; + + @override + String marketPricePremiumLabel(String premium) { + return 'Prezzo di mercato ($premium%)'; + } } diff --git a/lib/shared/widgets/nwc_invoice_widget.dart b/lib/shared/widgets/nwc_invoice_widget.dart index b15d22e5..b8bb988f 100644 --- a/lib/shared/widgets/nwc_invoice_widget.dart +++ b/lib/shared/widgets/nwc_invoice_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/src/rust/api/nwc.dart' as nwc_api; /// Auto-generates a Lightning invoice via NWC when wallet is connected. @@ -25,7 +26,7 @@ class NwcInvoiceWidget extends StatefulWidget { class _NwcInvoiceWidgetState extends State { bool _loading = true; - String? _error; + bool _hasError = false; @override void initState() { @@ -47,7 +48,7 @@ class _NwcInvoiceWidgetState extends State { if (!mounted) return; setState(() { _loading = false; - _error = 'Unable to generate invoice automatically'; + _hasError = true; }); widget.onFallbackToManual(); } @@ -65,21 +66,21 @@ class _NwcInvoiceWidgetState extends State { CircularProgressIndicator(color: green), const SizedBox(height: AppSpacing.md), Text( - 'Generating invoice via NWC...', + AppLocalizations.of(context).generatingInvoiceNwc, style: TextStyle(color: colors?.textSecondary), ), ], ); } - if (_error != null) { + if (_hasError) { return Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.warning_amber, color: colors?.destructiveRed, size: 32), const SizedBox(height: AppSpacing.sm), Text( - _error!, + AppLocalizations.of(context).unableToGenerateInvoice, style: TextStyle(color: colors?.textSecondary, fontSize: 12), ), ], diff --git a/lib/shared/widgets/nwc_payment_widget.dart b/lib/shared/widgets/nwc_payment_widget.dart index 885d8f99..89e28c87 100644 --- a/lib/shared/widgets/nwc_payment_widget.dart +++ b/lib/shared/widgets/nwc_payment_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mostro/core/app_theme.dart'; +import 'package:mostro/l10n/app_localizations.dart'; import 'package:mostro/src/rust/api/nwc.dart' as nwc_api; /// NWC auto-pay widget — single "Pay with Wallet" button. @@ -67,7 +68,9 @@ class _NwcPaymentWidgetState extends State { child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.account_balance_wallet, size: 20), - label: Text(_paying ? 'Paying...' : 'Pay with Wallet'), + label: Text(_paying + ? AppLocalizations.of(context).payingStatus + : AppLocalizations.of(context).payWithWalletButton), style: FilledButton.styleFrom( backgroundColor: green, foregroundColor: Colors.black, diff --git a/lib/shared/widgets/nym_avatar.dart b/lib/shared/widgets/nym_avatar.dart index 1017704f..9a2b82b1 100644 --- a/lib/shared/widgets/nym_avatar.dart +++ b/lib/shared/widgets/nym_avatar.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:mostro/l10n/app_localizations.dart'; + /// Deterministic pseudonymous avatar derived from a Nostr public key. /// /// Renders a colored circle background (HSV hue from [colorHue]) with a @@ -44,7 +46,7 @@ class NymAvatar extends StatelessWidget { // Icon is ALWAYS white — FR-011c rendering contract. color: Colors.white, size: size * 0.55, - semanticLabel: 'Avatar icon', + semanticLabel: AppLocalizations.of(context).avatarIconLabel, ), ), ); From 4c8047065421be43951d2c688d6ae9a95561dee7 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 23:04:49 -0300 Subject: [PATCH 29/30] refactor: use localized number formatting for premium percentage display Replace toStringAsFixed(1) with NumberFormat.decimalPattern configured for current locale when displaying order premium. Ensures decimal separator and digit grouping follow user's language preferences while maintaining 1 decimal place precision. --- lib/features/order/screens/my_order_screen.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/features/order/screens/my_order_screen.dart b/lib/features/order/screens/my_order_screen.dart index 5bb17c3a..6d54c7f0 100644 --- a/lib/features/order/screens/my_order_screen.dart +++ b/lib/features/order/screens/my_order_screen.dart @@ -178,6 +178,11 @@ class _MyOrderScreenState extends ConsumerState { final flag = flags[resolvedOrder.fiatCode] ?? ''; final title = isSelling ? l10n.myOrderSellTitle : l10n.myOrderBuyTitle; final premiumPositive = resolvedOrder.premium >= 0; + final premiumValue = (NumberFormat.decimalPattern( + Localizations.localeOf(context).toLanguageTag()) + ..minimumFractionDigits = 1 + ..maximumFractionDigits = 1) + .format(resolvedOrder.premium); return Scaffold( appBar: AppBar( @@ -205,7 +210,7 @@ class _MyOrderScreenState extends ConsumerState { const SizedBox(height: AppSpacing.xs), Text( AppLocalizations.of(context).marketPricePremiumLabel( - '${premiumPositive ? '+' : ''}${resolvedOrder.premium.toStringAsFixed(1)}'), + '${premiumPositive ? '+' : ''}$premiumValue'), style: TextStyle( color: premiumPositive ? green : sellColor, fontSize: 13, From 2ef53b346c5a8b9698ee12755af267f6425921f9 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Tue, 21 Jul 2026 23:42:13 -0300 Subject: [PATCH 30/30] refactor: replace hardcoded dispute and accessibility strings with localized translations --- .../providers/disputes_providers.dart | 15 ---- .../disputes/widgets/dispute_list_item.dart | 42 +++++++++-- lib/features/drawer/screens/drawer_menu.dart | 3 +- .../screens/pay_lightning_invoice_screen.dart | 2 +- lib/l10n/app_de.arb | 14 +++- lib/l10n/app_en.arb | 32 ++++++++- lib/l10n/app_es.arb | 14 +++- lib/l10n/app_fr.arb | 14 +++- lib/l10n/app_it.arb | 14 +++- lib/l10n/app_localizations.dart | 72 +++++++++++++++++++ lib/l10n/app_localizations_de.dart | 58 +++++++++++++++ lib/l10n/app_localizations_en.dart | 56 +++++++++++++++ lib/l10n/app_localizations_es.dart | 57 +++++++++++++++ lib/l10n/app_localizations_fr.dart | 57 +++++++++++++++ lib/l10n/app_localizations_it.dart | 58 +++++++++++++++ lib/shared/widgets/notification_bell.dart | 8 ++- 16 files changed, 485 insertions(+), 31 deletions(-) diff --git a/lib/features/disputes/providers/disputes_providers.dart b/lib/features/disputes/providers/disputes_providers.dart index 2431d128..9f982c5a 100644 --- a/lib/features/disputes/providers/disputes_providers.dart +++ b/lib/features/disputes/providers/disputes_providers.dart @@ -114,21 +114,6 @@ class DisputeItem { isSelling: isSelling ?? this.isSelling, ); } - - /// Human-readable description shown in list items. - String get description { - if (status == DisputeStatus.resolved) { - return switch (resolution) { - DisputeResolution.fundsToBuyer => - isSelling ? "Dispute resolved in buyer's favour" : 'Dispute resolved in your favour', - DisputeResolution.fundsToSeller => - isSelling ? 'Dispute resolved in your favour' : "Dispute resolved in seller's favour", - DisputeResolution.cooperativeCancel => 'Order cancelled cooperatively', - null => 'Dispute resolved', - }; - } - return initiatedByMe ? 'You opened this dispute' : 'Counterpart opened this dispute'; - } } // ── DisputeNotifier ─────────────────────────────────────────────────────────── diff --git a/lib/features/disputes/widgets/dispute_list_item.dart b/lib/features/disputes/widgets/dispute_list_item.dart index 139514e9..c0ab9195 100644 --- a/lib/features/disputes/widgets/dispute_list_item.dart +++ b/lib/features/disputes/widgets/dispute_list_item.dart @@ -24,8 +24,9 @@ class DisputeListItem extends ConsumerWidget { final colors = Theme.of(context).extension(); if (colors == null) throw StateError('AppColors theme extension must be registered'); final textTheme = Theme.of(context).textTheme; + final l10n = AppLocalizations.of(context); - final (statusBg, statusFg, statusLabel) = _statusChip(dispute.status); + final (statusBg, statusFg, statusLabel) = _statusChip(dispute.status, l10n); final truncatedId = dispute.tradeId.length > 16 ? '${dispute.tradeId.substring(0, 16)}…' : dispute.tradeId; @@ -102,7 +103,7 @@ class DisputeListItem extends ConsumerWidget { // Description Text( - dispute.description, + dispute.localizedDescription(l10n), style: textTheme.bodySmall?.copyWith( color: colors.textSecondary, ), @@ -118,23 +119,52 @@ class DisputeListItem extends ConsumerWidget { ); } - static (Color, Color, String) _statusChip(DisputeStatus status) { + static (Color, Color, String) _statusChip( + DisputeStatus status, + AppLocalizations l10n, + ) { return switch (status) { DisputeStatus.open => ( AppColors.statusPending.$1, AppColors.statusPending.$2, - 'Initiated', + l10n.disputeInitiated, ), DisputeStatus.inReview => ( AppColors.statusActive.$1, AppColors.statusActive.$2, - 'In progress', + l10n.disputeInProgress, ), DisputeStatus.resolved => ( AppColors.statusInactive.$1, AppColors.statusInactive.$2, - 'Closed', + l10n.disputeStatusClosed, ), }; } } + +/// Localizes the summary line shown for a dispute in list items. +/// +/// Kept out of [DisputeItem] so the model stays locale-independent; the +/// wording is resolved at render time from the dispute's status, resolution +/// and the viewing user's role. +extension DisputeItemL10n on DisputeItem { + String localizedDescription(AppLocalizations l10n) { + if (status == DisputeStatus.resolved) { + return switch (resolution) { + DisputeResolution.fundsToBuyer => isSelling + ? l10n.disputeDescResolvedBuyerFavour + : l10n.disputeDescResolvedYourFavour, + DisputeResolution.fundsToSeller => isSelling + ? l10n.disputeDescResolvedYourFavour + : l10n.disputeDescResolvedSellerFavour, + DisputeResolution.cooperativeCancel => + l10n.disputeDescCooperativeCancel, + null => l10n.disputeDescResolved, + }; + } + return initiatedByMe + ? l10n.disputeDescYouOpened + : l10n.disputeDescCounterpartOpened; + } +} diff --git a/lib/features/drawer/screens/drawer_menu.dart b/lib/features/drawer/screens/drawer_menu.dart index 4ac6ed65..676ca78a 100644 --- a/lib/features/drawer/screens/drawer_menu.dart +++ b/lib/features/drawer/screens/drawer_menu.dart @@ -262,6 +262,7 @@ class _NavItem extends StatelessWidget { @override Widget build(BuildContext context) { final colors = Theme.of(context).extension(); + final l10n = AppLocalizations.of(context); final activeBg = green.withValues(alpha: 0.12); final iconColor = isActive ? green : Colors.white; final textColor = isActive ? green : Colors.white; @@ -272,7 +273,7 @@ class _NavItem extends StatelessWidget { button: true, label: label, selected: isActive, - hint: badgeCount > 0 ? '$badgeCount new' : null, + hint: badgeCount > 0 ? l10n.drawerBadgeNewCount(badgeCount) : null, child: Material( color: Colors.transparent, child: InkWell( diff --git a/lib/features/order/screens/pay_lightning_invoice_screen.dart b/lib/features/order/screens/pay_lightning_invoice_screen.dart index 6c30f882..2e579ae6 100644 --- a/lib/features/order/screens/pay_lightning_invoice_screen.dart +++ b/lib/features/order/screens/pay_lightning_invoice_screen.dart @@ -206,7 +206,7 @@ class _PayLightningInvoiceScreenState data: invoice, size: 200, backgroundColor: Colors.white, - semanticsLabel: 'Lightning invoice QR code', + semanticsLabel: l10n.lightningInvoiceQrLabel, ), ), ), diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 112b22bd..63aca7a8 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -638,5 +638,17 @@ "generatingInvoiceNwc": "Rechnung wird über NWC erstellt...", "unableToGenerateInvoice": "Rechnung konnte nicht automatisch erstellt werden", "avatarIconLabel": "Avatar-Symbol", - "marketPricePremiumLabel": "Marktpreis ({premium}%)" + "marketPricePremiumLabel": "Marktpreis ({premium}%)", + "disputeDescResolvedBuyerFavour": "Streitfall zugunsten des Käufers entschieden", + "disputeDescResolvedYourFavour": "Streitfall zu deinen Gunsten entschieden", + "disputeDescResolvedSellerFavour": "Streitfall zugunsten des Verkäufers entschieden", + "disputeDescCooperativeCancel": "Bestellung kooperativ storniert", + "disputeDescResolved": "Streitfall gelöst", + "disputeDescYouOpened": "Du hast diesen Streitfall eröffnet", + "disputeDescCounterpartOpened": "Die Gegenpartei hat diesen Streitfall eröffnet", + "notificationsBellNoUnread": "Benachrichtigungen, keine ungelesenen Benachrichtigungen", + "notificationsBellBackupActive": "Benachrichtigungen, Backup-Erinnerung aktiv", + "notificationsBellUnread": "{count, plural, =1{Benachrichtigungen, 1 ungelesen} other{Benachrichtigungen, {count} ungelesen}}", + "drawerBadgeNewCount": "{count, plural, =1{1 neu} other{{count} neu}}", + "lightningInvoiceQrLabel": "QR-Code der Lightning-Rechnung" } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5cc88c18..1949715f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1409,5 +1409,35 @@ "@marketPricePremiumLabel": { "description": "Market price with premium on the my-order screen", "placeholders": {"premium": {"type": "String"}} - } + }, + "disputeDescResolvedBuyerFavour": "Dispute resolved in buyer's favour", + "@disputeDescResolvedBuyerFavour": {"description": "Dispute list-item summary: resolved in the buyer's favour"}, + "disputeDescResolvedYourFavour": "Dispute resolved in your favour", + "@disputeDescResolvedYourFavour": {"description": "Dispute list-item summary: resolved in the viewing user's favour"}, + "disputeDescResolvedSellerFavour": "Dispute resolved in seller's favour", + "@disputeDescResolvedSellerFavour": {"description": "Dispute list-item summary: resolved in the seller's favour"}, + "disputeDescCooperativeCancel": "Order cancelled cooperatively", + "@disputeDescCooperativeCancel": {"description": "Dispute list-item summary: order cancelled cooperatively"}, + "disputeDescResolved": "Dispute resolved", + "@disputeDescResolved": {"description": "Dispute list-item summary: generic resolved outcome"}, + "disputeDescYouOpened": "You opened this dispute", + "@disputeDescYouOpened": {"description": "Dispute list-item summary: the viewing user opened the dispute"}, + "disputeDescCounterpartOpened": "Counterpart opened this dispute", + "@disputeDescCounterpartOpened": {"description": "Dispute list-item summary: the counterpart opened the dispute"}, + "notificationsBellNoUnread": "Notifications, no unread notifications", + "@notificationsBellNoUnread": {"description": "Notification bell accessibility label when there are no unread notifications"}, + "notificationsBellBackupActive": "Notifications, backup reminder active", + "@notificationsBellBackupActive": {"description": "Notification bell accessibility label when the backup reminder is active"}, + "notificationsBellUnread": "{count, plural, =1{Notifications, 1 unread} other{Notifications, {count} unread}}", + "@notificationsBellUnread": { + "description": "Notification bell accessibility label with the unread count", + "placeholders": {"count": {"type": "int"}} + }, + "drawerBadgeNewCount": "{count, plural, =1{1 new} other{{count} new}}", + "@drawerBadgeNewCount": { + "description": "Drawer menu item badge accessibility hint with the number of new items", + "placeholders": {"count": {"type": "int"}} + }, + "lightningInvoiceQrLabel": "Lightning invoice QR code", + "@lightningInvoiceQrLabel": {"description": "Accessibility label for the Lightning invoice QR code image"} } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 113bb948..88a06ef4 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -638,5 +638,17 @@ "generatingInvoiceNwc": "Generando factura vía NWC...", "unableToGenerateInvoice": "No se pudo generar la factura automáticamente", "avatarIconLabel": "Icono de avatar", - "marketPricePremiumLabel": "Precio de mercado ({premium}%)" + "marketPricePremiumLabel": "Precio de mercado ({premium}%)", + "disputeDescResolvedBuyerFavour": "Disputa resuelta a favor del comprador", + "disputeDescResolvedYourFavour": "Disputa resuelta a tu favor", + "disputeDescResolvedSellerFavour": "Disputa resuelta a favor del vendedor", + "disputeDescCooperativeCancel": "Orden cancelada cooperativamente", + "disputeDescResolved": "Disputa resuelta", + "disputeDescYouOpened": "Abriste esta disputa", + "disputeDescCounterpartOpened": "La contraparte abrió esta disputa", + "notificationsBellNoUnread": "Notificaciones, sin notificaciones sin leer", + "notificationsBellBackupActive": "Notificaciones, recordatorio de respaldo activo", + "notificationsBellUnread": "{count, plural, =1{Notificaciones, 1 sin leer} other{Notificaciones, {count} sin leer}}", + "drawerBadgeNewCount": "{count, plural, =1{1 nuevo} other{{count} nuevos}}", + "lightningInvoiceQrLabel": "Código QR de la factura Lightning" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 4065ce69..9806590a 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -638,5 +638,17 @@ "generatingInvoiceNwc": "Génération de la facture via NWC...", "unableToGenerateInvoice": "Impossible de générer la facture automatiquement", "avatarIconLabel": "Icône d'avatar", - "marketPricePremiumLabel": "Prix du marché ({premium}%)" + "marketPricePremiumLabel": "Prix du marché ({premium}%)", + "disputeDescResolvedBuyerFavour": "Litige résolu en faveur de l'acheteur", + "disputeDescResolvedYourFavour": "Litige résolu en votre faveur", + "disputeDescResolvedSellerFavour": "Litige résolu en faveur du vendeur", + "disputeDescCooperativeCancel": "Commande annulée coopérativement", + "disputeDescResolved": "Litige résolu", + "disputeDescYouOpened": "Vous avez ouvert ce litige", + "disputeDescCounterpartOpened": "La contrepartie a ouvert ce litige", + "notificationsBellNoUnread": "Notifications, aucune notification non lue", + "notificationsBellBackupActive": "Notifications, rappel de sauvegarde actif", + "notificationsBellUnread": "{count, plural, =1{Notifications, 1 non lue} other{Notifications, {count} non lues}}", + "drawerBadgeNewCount": "{count, plural, =1{1 nouveau} other{{count} nouveaux}}", + "lightningInvoiceQrLabel": "QR code de la facture Lightning" } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index cad07336..e9225501 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -638,5 +638,17 @@ "generatingInvoiceNwc": "Generazione fattura tramite NWC...", "unableToGenerateInvoice": "Impossibile generare la fattura automaticamente", "avatarIconLabel": "Icona avatar", - "marketPricePremiumLabel": "Prezzo di mercato ({premium}%)" + "marketPricePremiumLabel": "Prezzo di mercato ({premium}%)", + "disputeDescResolvedBuyerFavour": "Disputa risolta a favore dell'acquirente", + "disputeDescResolvedYourFavour": "Disputa risolta a tuo favore", + "disputeDescResolvedSellerFavour": "Disputa risolta a favore del venditore", + "disputeDescCooperativeCancel": "Ordine annullato cooperativamente", + "disputeDescResolved": "Disputa risolta", + "disputeDescYouOpened": "Hai aperto questa disputa", + "disputeDescCounterpartOpened": "La controparte ha aperto questa disputa", + "notificationsBellNoUnread": "Notifiche, nessuna notifica non letta", + "notificationsBellBackupActive": "Notifiche, promemoria di backup attivo", + "notificationsBellUnread": "{count, plural, =1{Notifiche, 1 non letta} other{Notifiche, {count} non lette}}", + "drawerBadgeNewCount": "{count, plural, =1{1 nuovo} other{{count} nuovi}}", + "lightningInvoiceQrLabel": "Codice QR della fattura Lightning" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index a23150a6..bbdf969c 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3799,6 +3799,78 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Market Price ({premium}%)'** String marketPricePremiumLabel(String premium); + + /// Dispute list-item summary: resolved in the buyer's favour + /// + /// In en, this message translates to: + /// **'Dispute resolved in buyer\'s favour'** + String get disputeDescResolvedBuyerFavour; + + /// Dispute list-item summary: resolved in the viewing user's favour + /// + /// In en, this message translates to: + /// **'Dispute resolved in your favour'** + String get disputeDescResolvedYourFavour; + + /// Dispute list-item summary: resolved in the seller's favour + /// + /// In en, this message translates to: + /// **'Dispute resolved in seller\'s favour'** + String get disputeDescResolvedSellerFavour; + + /// Dispute list-item summary: order cancelled cooperatively + /// + /// In en, this message translates to: + /// **'Order cancelled cooperatively'** + String get disputeDescCooperativeCancel; + + /// Dispute list-item summary: generic resolved outcome + /// + /// In en, this message translates to: + /// **'Dispute resolved'** + String get disputeDescResolved; + + /// Dispute list-item summary: the viewing user opened the dispute + /// + /// In en, this message translates to: + /// **'You opened this dispute'** + String get disputeDescYouOpened; + + /// Dispute list-item summary: the counterpart opened the dispute + /// + /// In en, this message translates to: + /// **'Counterpart opened this dispute'** + String get disputeDescCounterpartOpened; + + /// Notification bell accessibility label when there are no unread notifications + /// + /// In en, this message translates to: + /// **'Notifications, no unread notifications'** + String get notificationsBellNoUnread; + + /// Notification bell accessibility label when the backup reminder is active + /// + /// In en, this message translates to: + /// **'Notifications, backup reminder active'** + String get notificationsBellBackupActive; + + /// Notification bell accessibility label with the unread count + /// + /// In en, this message translates to: + /// **'{count, plural, =1{Notifications, 1 unread} other{Notifications, {count} unread}}'** + String notificationsBellUnread(int count); + + /// Drawer menu item badge accessibility hint with the number of new items + /// + /// In en, this message translates to: + /// **'{count, plural, =1{1 new} other{{count} new}}'** + String drawerBadgeNewCount(int count); + + /// Accessibility label for the Lightning invoice QR code image + /// + /// In en, this message translates to: + /// **'Lightning invoice QR code'** + String get lightningInvoiceQrLabel; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 4f8aeb6b..5c86161a 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -2118,4 +2118,62 @@ class AppLocalizationsDe extends AppLocalizations { String marketPricePremiumLabel(String premium) { return 'Marktpreis ($premium%)'; } + + @override + String get disputeDescResolvedBuyerFavour => + 'Streitfall zugunsten des Käufers entschieden'; + + @override + String get disputeDescResolvedYourFavour => + 'Streitfall zu deinen Gunsten entschieden'; + + @override + String get disputeDescResolvedSellerFavour => + 'Streitfall zugunsten des Verkäufers entschieden'; + + @override + String get disputeDescCooperativeCancel => 'Bestellung kooperativ storniert'; + + @override + String get disputeDescResolved => 'Streitfall gelöst'; + + @override + String get disputeDescYouOpened => 'Du hast diesen Streitfall eröffnet'; + + @override + String get disputeDescCounterpartOpened => + 'Die Gegenpartei hat diesen Streitfall eröffnet'; + + @override + String get notificationsBellNoUnread => + 'Benachrichtigungen, keine ungelesenen Benachrichtigungen'; + + @override + String get notificationsBellBackupActive => + 'Benachrichtigungen, Backup-Erinnerung aktiv'; + + @override + String notificationsBellUnread(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Benachrichtigungen, $count ungelesen', + one: 'Benachrichtigungen, 1 ungelesen', + ); + return '$_temp0'; + } + + @override + String drawerBadgeNewCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count neu', + one: '1 neu', + ); + return '$_temp0'; + } + + @override + String get lightningInvoiceQrLabel => 'QR-Code der Lightning-Rechnung'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 97f2cdaa..6db613d5 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2087,4 +2087,60 @@ class AppLocalizationsEn extends AppLocalizations { String marketPricePremiumLabel(String premium) { return 'Market Price ($premium%)'; } + + @override + String get disputeDescResolvedBuyerFavour => + 'Dispute resolved in buyer\'s favour'; + + @override + String get disputeDescResolvedYourFavour => 'Dispute resolved in your favour'; + + @override + String get disputeDescResolvedSellerFavour => + 'Dispute resolved in seller\'s favour'; + + @override + String get disputeDescCooperativeCancel => 'Order cancelled cooperatively'; + + @override + String get disputeDescResolved => 'Dispute resolved'; + + @override + String get disputeDescYouOpened => 'You opened this dispute'; + + @override + String get disputeDescCounterpartOpened => 'Counterpart opened this dispute'; + + @override + String get notificationsBellNoUnread => + 'Notifications, no unread notifications'; + + @override + String get notificationsBellBackupActive => + 'Notifications, backup reminder active'; + + @override + String notificationsBellUnread(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Notifications, $count unread', + one: 'Notifications, 1 unread', + ); + return '$_temp0'; + } + + @override + String drawerBadgeNewCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count new', + one: '1 new', + ); + return '$_temp0'; + } + + @override + String get lightningInvoiceQrLabel => 'Lightning invoice QR code'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 2030fe1f..4f11a64f 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -2112,4 +2112,61 @@ class AppLocalizationsEs extends AppLocalizations { String marketPricePremiumLabel(String premium) { return 'Precio de mercado ($premium%)'; } + + @override + String get disputeDescResolvedBuyerFavour => + 'Disputa resuelta a favor del comprador'; + + @override + String get disputeDescResolvedYourFavour => 'Disputa resuelta a tu favor'; + + @override + String get disputeDescResolvedSellerFavour => + 'Disputa resuelta a favor del vendedor'; + + @override + String get disputeDescCooperativeCancel => 'Orden cancelada cooperativamente'; + + @override + String get disputeDescResolved => 'Disputa resuelta'; + + @override + String get disputeDescYouOpened => 'Abriste esta disputa'; + + @override + String get disputeDescCounterpartOpened => + 'La contraparte abrió esta disputa'; + + @override + String get notificationsBellNoUnread => + 'Notificaciones, sin notificaciones sin leer'; + + @override + String get notificationsBellBackupActive => + 'Notificaciones, recordatorio de respaldo activo'; + + @override + String notificationsBellUnread(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Notificaciones, $count sin leer', + one: 'Notificaciones, 1 sin leer', + ); + return '$_temp0'; + } + + @override + String drawerBadgeNewCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count nuevos', + one: '1 nuevo', + ); + return '$_temp0'; + } + + @override + String get lightningInvoiceQrLabel => 'Código QR de la factura Lightning'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 4867246b..c9db948d 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -2121,4 +2121,61 @@ class AppLocalizationsFr extends AppLocalizations { String marketPricePremiumLabel(String premium) { return 'Prix du marché ($premium%)'; } + + @override + String get disputeDescResolvedBuyerFavour => + 'Litige résolu en faveur de l\'acheteur'; + + @override + String get disputeDescResolvedYourFavour => 'Litige résolu en votre faveur'; + + @override + String get disputeDescResolvedSellerFavour => + 'Litige résolu en faveur du vendeur'; + + @override + String get disputeDescCooperativeCancel => 'Commande annulée coopérativement'; + + @override + String get disputeDescResolved => 'Litige résolu'; + + @override + String get disputeDescYouOpened => 'Vous avez ouvert ce litige'; + + @override + String get disputeDescCounterpartOpened => + 'La contrepartie a ouvert ce litige'; + + @override + String get notificationsBellNoUnread => + 'Notifications, aucune notification non lue'; + + @override + String get notificationsBellBackupActive => + 'Notifications, rappel de sauvegarde actif'; + + @override + String notificationsBellUnread(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Notifications, $count non lues', + one: 'Notifications, 1 non lue', + ); + return '$_temp0'; + } + + @override + String drawerBadgeNewCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count nouveaux', + one: '1 nouveau', + ); + return '$_temp0'; + } + + @override + String get lightningInvoiceQrLabel => 'QR code de la facture Lightning'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index c7f6511c..855091a4 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -2110,4 +2110,62 @@ class AppLocalizationsIt extends AppLocalizations { String marketPricePremiumLabel(String premium) { return 'Prezzo di mercato ($premium%)'; } + + @override + String get disputeDescResolvedBuyerFavour => + 'Disputa risolta a favore dell\'acquirente'; + + @override + String get disputeDescResolvedYourFavour => 'Disputa risolta a tuo favore'; + + @override + String get disputeDescResolvedSellerFavour => + 'Disputa risolta a favore del venditore'; + + @override + String get disputeDescCooperativeCancel => + 'Ordine annullato cooperativamente'; + + @override + String get disputeDescResolved => 'Disputa risolta'; + + @override + String get disputeDescYouOpened => 'Hai aperto questa disputa'; + + @override + String get disputeDescCounterpartOpened => + 'La controparte ha aperto questa disputa'; + + @override + String get notificationsBellNoUnread => + 'Notifiche, nessuna notifica non letta'; + + @override + String get notificationsBellBackupActive => + 'Notifiche, promemoria di backup attivo'; + + @override + String notificationsBellUnread(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: 'Notifiche, $count non lette', + one: 'Notifiche, 1 non letta', + ); + return '$_temp0'; + } + + @override + String drawerBadgeNewCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count nuovi', + one: '1 nuovo', + ); + return '$_temp0'; + } + + @override + String get lightningInvoiceQrLabel => 'Codice QR della fattura Lightning'; } diff --git a/lib/shared/widgets/notification_bell.dart b/lib/shared/widgets/notification_bell.dart index 7b705eb6..64e66c70 100644 --- a/lib/shared/widgets/notification_bell.dart +++ b/lib/shared/widgets/notification_bell.dart @@ -6,6 +6,7 @@ import 'package:mostro/core/app_routes.dart'; import 'package:mostro/core/app_theme.dart'; import 'package:mostro/features/account/providers/backup_reminder_provider.dart'; import 'package:mostro/features/notifications/providers/notifications_provider.dart'; +import 'package:mostro/l10n/app_localizations.dart'; /// App-bar notification bell. /// @@ -71,11 +72,12 @@ class _NotificationBellState extends ConsumerState if (next > (prev ?? 0)) _triggerShake(); }); + final l10n = AppLocalizations.of(context); final semanticLabel = !isActive - ? 'Notifications, no unread notifications' + ? l10n.notificationsBellNoUnread : backupActive - ? 'Notifications, backup reminder active' - : 'Notifications, $unreadCount unread'; + ? l10n.notificationsBellBackupActive + : l10n.notificationsBellUnread(unreadCount); return Semantics( label: semanticLabel,