Files
sure/mobile/lib/l10n/app_en.arb
ghost 401cd6ab08 feat(mobile): privacy mode to mask money values (#2386)
* feat(mobile): add privacy mode to mask money values

Adds an app-wide "privacy mode" so users can hide monetary amounts from
over-the-shoulder view.

- PrivacyProvider (ChangeNotifier) backed by PreferencesService, so the
  choice persists across launches and every money widget rebuilds on
  toggle.
- MoneyMasker.mask() collapses an amount's numeric portion into a short
  fixed run of bullets while keeping the currency symbol and sign
  (e.g. CA$1,234.56 -> CA$••••). A fixed run avoids leaking the value's
  magnitude and reads cleanly without stray separators. Currency- and
  locale-agnostic — it operates on already-formatted strings.
- Masking applied at every money render site: net worth + per-currency
  totals + breakdown sheet (NetWorthCard), account balances (AccountCard,
  AccountDetailHeader, transaction form account selector), and transaction
  amounts (transactions list, recent transactions, calendar).
- Two entry points: a "Hide amounts" switch in Settings -> Security, and a
  quick eye toggle in the top bar (visible on every tab).

Tests: MoneyMasker unit tests (fixed-run mask, magnitude hidden, symbol/
sign kept, passthrough, idempotent) + a widget test asserting the net
worth masks/unmasks as the provider flips; account_card_test updated to
provide the new provider. flutter analyze: no new issues; full suite
(123) green.

* fix(mobile): address privacy-mode review feedback

- Startup masking (Codex P1): read the privacy preference in main() before
  runApp and seed PrivacyProvider with it, so the first frame already has
  the correct value — money is never briefly rendered unmasked for a user
  who enabled "Hide amounts". Provider stays fail-closed otherwise: starts
  masked, SureApp's no-arg default is masked, and a failed read keeps it
  masked. A late-completing initial load no longer clobbers an explicit
  user toggle.
- setHidden() reverts the in-memory state (and logs) if persistence fails,
  keeping the UI consistent with what's actually stored.
- Mask the cash-balance detail chip in AccountDetailHeader (was leaking
  the cash position in privacy mode).
- Privacy top-bar toggle gets a "Toggle privacy" tooltip + icon semantic
  label for accessibility (kept as an InkWell to match the adjacent
  settings control).
- Tests: assert fail-closed initial state; assert the exact masked count;
  test the persistence round-trip (set -> reload); add
  PreferencesService.resetForTest() and reset between tests so the cached
  singleton can't leak state.

125 tests pass; flutter analyze: no new issues.

* refactor(mobile): thread hideAmounts through calendar tiles

Per review: the calendar tile builders read PrivacyProvider via
context.read, relying implicitly on the parent build()'s context.watch to
rebuild them — fragile if a tile is later extracted or wrapped in a
RepaintBoundary. Pass hideAmounts down explicitly instead, matching the
recent_transactions_screen pattern:

- build() (context.watch) -> _buildCalendar -> _buildDayCell
- _showTransactionsDialog reads once when the modal opens ->
  _buildTransactionTile

No more context.read inside tile methods. 125 tests pass; analyze clean.

* fix(mobile): watch PrivacyProvider inside calendar dialog builder

Moving the hideAmounts read inside the showDialog builder and switching
from context.read to context.watch ensures the dialog re-masks transaction
amounts if the user toggles privacy mode while the dialog is open.
2026-06-30 06:49:05 +02:00

979 lines
50 KiB
Plaintext

{
"@@locale": "en",
"appTitle": "Sure Finances",
"@appTitle": { "description": "Application title shown in the OS task switcher." },
"commonCancel": "Cancel",
"@commonCancel": { "description": "Generic cancel action label." },
"commonSave": "Save",
"@commonSave": { "description": "Generic save/confirm action label." },
"commonTryAgain": "Try Again",
"@commonTryAgain": { "description": "Generic retry action label." },
"commonDelete": "Delete",
"@commonDelete": { "description": "Generic delete action label." },
"commonAll": "All",
"@commonAll": { "description": "Label for the 'All' filter chip (currency filter, category filter, etc.)." },
"commonRefresh": "Refresh",
"@commonRefresh": { "description": "Generic refresh action label." },
"commonClose": "Close",
"@commonClose": { "description": "Generic close/dismiss action label." },
"commonUndo": "Undo",
"@commonUndo": { "description": "Generic undo action label." },
"chatSuggestionNetWorth": "What is my current net worth?",
"@chatSuggestionNetWorth": { "description": "Suggested chat prompt on the empty assistant screen." },
"chatSuggestionSpending": "How has my spending changed this month?",
"@chatSuggestionSpending": { "description": "Suggested chat prompt on the empty assistant screen." },
"chatSuggestionSavings": "How can I improve my savings rate?",
"@chatSuggestionSavings": { "description": "Suggested chat prompt on the empty assistant screen." },
"chatSuggestionExpenses": "What are my biggest expenses lately?",
"@chatSuggestionExpenses": { "description": "Suggested chat prompt on the empty assistant screen." },
"loginEmailLabel": "Email",
"@loginEmailLabel": { "description": "Label for the email text field on the login screen." },
"loginEmailRequired": "Email is required",
"@loginEmailRequired": { "description": "Validation error when email field is empty." },
"loginEmailInvalid": "Please enter a valid email",
"@loginEmailInvalid": { "description": "Validation error when email format is invalid." },
"loginPasswordLabel": "Password",
"@loginPasswordLabel": { "description": "Label for the password field on the login screen." },
"loginPasswordRequired": "Password is required",
"@loginPasswordRequired": { "description": "Validation error when password field is empty." },
"loginSignIn": "Sign in",
"@loginSignIn": { "description": "Primary sign-in button label." },
"loginSignInWithGoogle": "Sign in with Google",
"@loginSignInWithGoogle": { "description": "Google SSO button label." },
"loginMfaLabel": "Authentication Code",
"@loginMfaLabel": { "description": "Label for the MFA code input field." },
"loginApiKeyLabel": "API Key",
"@loginApiKeyLabel": { "description": "Label for the API key input field." },
"navHome": "Home",
"@navHome": { "description": "Bottom navigation label for the Home (dashboard) tab." },
"navIntro": "Intro",
"@navIntro": { "description": "Bottom navigation label for the Intro tab." },
"navAssistant": "Assistant",
"@navAssistant": { "description": "Bottom navigation label for the AI assistant tab." },
"navMore": "More",
"@navMore": { "description": "Bottom navigation label for the More tab." },
"dashboardSyncError": "Sync failed",
"@dashboardSyncError": { "description": "Snackbar message when a sync attempt fails." },
"dashboardSyncFailed": "Sync failed. Please try again.",
"@dashboardSyncFailed": { "description": "Snackbar message when a sync attempt fails with retry instruction." },
"dashboardRefreshing": "Refreshing accounts…",
"@dashboardRefreshing": { "description": "Snackbar message while accounts are being refreshed after a transaction." },
"dashboardAccountsUpdated": "Accounts updated",
"@dashboardAccountsUpdated": { "description": "Snackbar message shown after accounts refresh completes." },
"dashboardSyncing": "Syncing data from server…",
"@dashboardSyncing": { "description": "Snackbar message while sync is in progress." },
"dashboardSynced": "Synced",
"@dashboardSynced": { "description": "Label shown in the sync status indicator after a successful sync." },
"dashboardErrorLoadingAccounts": "Failed to load accounts",
"@dashboardErrorLoadingAccounts": { "description": "Error state message when accounts fail to load." },
"dashboardNoAccounts": "No accounts yet",
"@dashboardNoAccounts": { "description": "Empty state message when no accounts exist." },
"dashboardNoAccountsSubtitle": "Add accounts in the web app to see them here.",
"@dashboardNoAccountsSubtitle": { "description": "Empty state subtitle when no accounts exist." },
"dashboardFilterEmpty": "No accounts match the current filter",
"@dashboardFilterEmpty": { "description": "Empty state when the active account filter returns no results." },
"chatListTitle": "Chats",
"@chatListTitle": { "description": "Title for the chat list screen / app bar." },
"chatListNewChat": "New chat",
"@chatListNewChat": { "description": "FAB tooltip for creating a new chat." },
"chatListEmpty": "No chats yet",
"@chatListEmpty": { "description": "Empty-state heading on the chat list screen." },
"chatListEmptySubtitle": "Start a conversation with your AI assistant",
"@chatListEmptySubtitle": { "description": "Empty-state subtitle on the chat list screen." },
"chatListDeleteTitle": "Delete Chat",
"@chatListDeleteTitle": { "description": "Title for the delete-chat confirmation dialog." },
"chatConversationNewTitle": "New Conversation",
"@chatConversationNewTitle": { "description": "Placeholder title for a new conversation before it is saved." },
"chatConversationMessageHint": "Ask anything about your finances…",
"@chatConversationMessageHint": { "description": "Hint text inside the chat message input field." },
"chatConversationGreetingWithName": "Hi {firstName}, how can I help?",
"@chatConversationGreetingWithName": {
"description": "Greeting shown in a new conversation when the user's first name is known.",
"placeholders": { "firstName": { "type": "String" } }
},
"chatConversationGreetingNoName": "Hi there, how can I help?",
"@chatConversationGreetingNoName": { "description": "Greeting shown in a new conversation when the user's first name is not available." },
"transactionFormNewTitle": "New Transaction",
"@transactionFormNewTitle": { "description": "App bar title for the new-transaction form." },
"transactionFormTypeLabel": "Type",
"@transactionFormTypeLabel": { "description": "Label for the transaction type segmented control." },
"transactionFormTypeExpense": "Expense",
"@transactionFormTypeExpense": { "description": "Segment label for an expense transaction." },
"transactionFormTypeIncome": "Income",
"@transactionFormTypeIncome": { "description": "Segment label for an income transaction." },
"transactionFormAmountLabel": "Amount",
"@transactionFormAmountLabel": { "description": "Label for the amount input field." },
"transactionFormAmountRequired": "Amount is required",
"@transactionFormAmountRequired": { "description": "Validation error when amount is empty." },
"transactionFormAmountInvalid": "Please enter a valid amount",
"@transactionFormAmountInvalid": { "description": "Validation error when amount is not a number." },
"transactionFormDateLabel": "Date",
"@transactionFormDateLabel": { "description": "Label for the date picker field." },
"transactionFormNameLabel": "Name",
"@transactionFormNameLabel": { "description": "Label for the transaction name/payee field." },
"transactionFormCategoryLabel": "Category",
"@transactionFormCategoryLabel": { "description": "Label for the category picker field." },
"transactionEditTitle": "Edit Transaction",
"@transactionEditTitle": { "description": "App bar title for the edit-transaction screen." },
"transactionEditNameLabel": "Name",
"@transactionEditNameLabel": { "description": "Label for the name field in the edit form." },
"transactionEditNameRequired": "Name is required",
"@transactionEditNameRequired": { "description": "Validation error when name is empty in edit form." },
"transactionEditNotesLabel": "Notes",
"@transactionEditNotesLabel": { "description": "Label for the notes field in the edit form." },
"transactionEditCategoryLabel": "Category",
"@transactionEditCategoryLabel": { "description": "Label for the category field in the edit form." },
"transactionEditMerchantLabel": "Merchant",
"@transactionEditMerchantLabel": { "description": "Label for the merchant field in the edit form." },
"transactionEditTagsLabel": "Tags",
"@transactionEditTagsLabel": { "description": "Label for the tags field in the edit form." },
"transactionEditSaving": "Saving…",
"@transactionEditSaving": { "description": "In-progress label shown on the save button while saving." },
"transactionsListDeleteTitle": "Delete Transaction",
"@transactionsListDeleteTitle": { "description": "Title for the single-transaction delete confirmation dialog." },
"transactionsListDeleteSingleContent": "Are you sure you want to delete \"{name}\"?",
"@transactionsListDeleteSingleContent": {
"description": "Body of the delete confirmation dialog for a named transaction.",
"placeholders": { "name": { "type": "String" } }
},
"transactionsListDeleteMultiTitle": "Delete Transactions",
"@transactionsListDeleteMultiTitle": { "description": "Title for the multi-transaction delete confirmation dialog." },
"transactionsListDeleteMultiContent": "Are you sure you want to delete the selected transactions?",
"@transactionsListDeleteMultiContent": { "description": "Body of the multi-transaction delete confirmation dialog." },
"transactionsListEmpty": "No transactions",
"@transactionsListEmpty": { "description": "Empty-state message when no transactions exist." },
"transactionsListAuthFailed": "Authentication failed: Please log in again",
"@transactionsListAuthFailed": { "description": "Snackbar message when the API returns an auth failure." },
"transactionsListNoTransactionsYet": "No transactions yet",
"@transactionsListNoTransactionsYet": { "description": "Empty-state heading when an account has no transactions." },
"transactionsListEmptyAddFirst": "Tap + to add your first transaction",
"@transactionsListEmptyAddFirst": { "description": "Empty-state subtitle prompting the user to add their first transaction." },
"transactionsListNoCategoryMatch": "No transactions match this category",
"@transactionsListNoCategoryMatch": { "description": "Empty state shown when category filter returns no transactions." },
"transactionsListRetry": "Retry",
"@transactionsListRetry": { "description": "Button label on the transactions error state." },
"transactionsListDeletedSuccess": "Transaction deleted",
"@transactionsListDeletedSuccess": { "description": "Snackbar after a single transaction is deleted." },
"transactionsListSingleDeleteFailed": "Failed to delete transaction",
"@transactionsListSingleDeleteFailed": { "description": "Snackbar when deleting a single transaction fails." },
"transactionsListDeletedMulti": "{count, plural, one{Deleted {count} transaction} other{Deleted {count} transactions}}",
"@transactionsListDeletedMulti": {
"description": "Snackbar message after multiple transactions are deleted.",
"placeholders": { "count": { "type": "int" } }
},
"transactionsListDeleteFailed": "Failed to delete transactions",
"@transactionsListDeleteFailed": { "description": "Snackbar when multi-delete fails." },
"transactionsListDeleteNoToken": "Failed to delete: No access token",
"@transactionsListDeleteNoToken": { "description": "Snackbar when delete fails due to missing token." },
"transactionsListUndoTitle": "Undo Transaction",
"@transactionsListUndoTitle": { "description": "Title for the undo-transaction dialog." },
"transactionsListUndoRemovePending": "Remove this pending transaction?",
"@transactionsListUndoRemovePending": { "description": "Confirmation question for removing a pending transaction." },
"transactionsListUndoRestoreConfirm": "Restore this transaction?",
"@transactionsListUndoRestoreConfirm": { "description": "Confirmation question for restoring a transaction." },
"transactionsListUndoPendingRemoved": "Pending transaction removed",
"@transactionsListUndoPendingRemoved": { "description": "Snackbar after a pending transaction is removed." },
"transactionsListUndoRestored": "Transaction restored",
"@transactionsListUndoRestored": { "description": "Snackbar after a transaction is restored." },
"transactionsListUndoFailed": "Failed to undo transaction",
"@transactionsListUndoFailed": { "description": "Snackbar when undoing a transaction fails." },
"settingsSectionDisplay": "Display",
"@settingsSectionDisplay": { "description": "Settings section header for display/appearance options." },
"settingsSectionConnection": "Connection",
"@settingsSectionConnection": { "description": "Settings section header for server/connection options." },
"settingsSectionDataManagement": "Data Management",
"@settingsSectionDataManagement": { "description": "Settings section header for data import/export options." },
"settingsSectionSecurity": "Security",
"@settingsSectionSecurity": { "description": "Settings section header for security options." },
"settingsSectionDangerZone": "Danger Zone",
"@settingsSectionDangerZone": { "description": "Settings section header for destructive actions." },
"settingsThemeLabel": "Theme",
"@settingsThemeLabel": { "description": "Label for the theme picker setting." },
"settingsThemeSystem": "System",
"@settingsThemeSystem": { "description": "Theme option: follow system setting." },
"settingsThemeLight": "Light",
"@settingsThemeLight": { "description": "Theme option: light mode." },
"settingsThemeDark": "Dark",
"@settingsThemeDark": { "description": "Theme option: dark mode." },
"settingsProxyHeadersLabel": "Custom Proxy Headers",
"@settingsProxyHeadersLabel": { "description": "Label for the custom proxy headers setting." },
"settingsPrivacyHideAmountsLabel": "Hide amounts",
"@settingsPrivacyHideAmountsLabel": { "description": "Label for the toggle that masks monetary amounts across the app." },
"settingsPrivacyHideAmountsContent": "Mask money values across the app",
"@settingsPrivacyHideAmountsContent": { "description": "Subtitle describing the hide-amounts toggle." },
"settingsBiometricLabel": "Biometric Lock",
"@settingsBiometricLabel": { "description": "Label for the biometric lock toggle." },
"settingsBiometricEnable": "Enable biometric lock?",
"@settingsBiometricEnable": { "description": "Title of the dialog to enable biometric lock." },
"settingsBiometricEnableContent": "Require biometric authentication when resuming the app.",
"@settingsBiometricEnableContent": { "description": "Body text of the enable-biometric dialog." },
"settingsCheckForUpdates": "Check for Updates",
"@settingsCheckForUpdates": { "description": "Label for the check-for-updates action." },
"settingsUpdateAvailableTitle": "Update Available",
"@settingsUpdateAvailableTitle": { "description": "Title for the update-available dialog." },
"settingsUpdateAvailableContent": "Version {version} is available. Update now?",
"@settingsUpdateAvailableContent": {
"description": "Body text for the update-available dialog.",
"placeholders": { "version": { "type": "String" } }
},
"settingsUpdateNewerVersionFallback": "a newer version",
"@settingsUpdateNewerVersionFallback": { "description": "Fallback used in the update-available dialog when the store version number is unknown (e.g. 'Version a newer version is available')." },
"settingsUpdateNow": "Update Now",
"@settingsUpdateNow": { "description": "Confirm button in the update-available dialog." },
"settingsNoUpdateAvailable": "You're on the latest version.",
"@settingsNoUpdateAvailable": { "description": "Snackbar when no update is available." },
"settingsUpdateError": "Could not check for updates.",
"@settingsUpdateError": { "description": "Snackbar when the update check fails." },
"settingsClearDataTitle": "Clear All Data",
"@settingsClearDataTitle": { "description": "Title for the clear-all-data confirmation dialog." },
"settingsClearDataContent": "This will remove all locally cached data. Your data on the server will not be affected.",
"@settingsClearDataContent": { "description": "Body text for the clear-all-data dialog." },
"settingsClearData": "Clear Data",
"@settingsClearData": { "description": "Confirm button in the clear-data dialog." },
"settingsClearDataSuccess": "Local data cleared",
"@settingsClearDataSuccess": { "description": "Snackbar after local data is cleared." },
"settingsDeleteAccountTitle": "Delete Account",
"@settingsDeleteAccountTitle": { "description": "Title for the delete-account confirmation dialog." },
"settingsDeleteAccount": "Delete Account",
"@settingsDeleteAccount": { "description": "Confirm button in the delete-account dialog." },
"settingsSignOutTitle": "Sign Out",
"@settingsSignOutTitle": { "description": "Title for the sign-out confirmation dialog." },
"settingsSignOutContent": "Are you sure you want to sign out?",
"@settingsSignOutContent": { "description": "Body text for the sign-out dialog." },
"settingsSignOut": "Sign Out",
"@settingsSignOut": { "description": "Confirm button in the sign-out dialog and settings list tile label." },
"settingsDebugLogs": "Debug Logs",
"@settingsDebugLogs": { "description": "Label for the debug logs navigation tile." },
"ssoOnboardingTitle": "Link Your Account",
"@ssoOnboardingTitle": { "description": "App bar title for the SSO onboarding screen." },
"ssoOnboardingTabLink": "Link existing",
"@ssoOnboardingTabLink": { "description": "Tab label for linking an existing Sure account via SSO." },
"ssoOnboardingTabCreate": "Create new",
"@ssoOnboardingTabCreate": { "description": "Tab label for creating a new Sure account via SSO." },
"ssoOnboardingFirstNameLabel": "First Name",
"@ssoOnboardingFirstNameLabel": { "description": "Label for the first-name field on the SSO onboarding screen." },
"ssoOnboardingLastNameLabel": "Last Name",
"@ssoOnboardingLastNameLabel": { "description": "Label for the last-name field on the SSO onboarding screen." },
"ssoOnboardingLinkButton": "Link Account",
"@ssoOnboardingLinkButton": { "description": "Button label to link an existing account." },
"ssoOnboardingCreateButton": "Create Account",
"@ssoOnboardingCreateButton": { "description": "Button label to create a new account." },
"ssoOnboardingAcceptInvitation": "Accept Invitation",
"@ssoOnboardingAcceptInvitation": { "description": "Tab and submit button label when the user has a pending household invitation to accept." },
"calendarTitle": "Account Calendar",
"@calendarTitle": { "description": "App bar title for the account calendar screen." },
"calendarAccountTypeSection": "Account Type",
"@calendarAccountTypeSection": { "description": "Section header for the account-type selector on the calendar screen." },
"calendarSegmentAssets": "Assets",
"@calendarSegmentAssets": { "description": "Segment label for asset-type accounts on the calendar." },
"calendarSegmentLiabilities": "Liabilities",
"@calendarSegmentLiabilities": { "description": "Segment label for liability-type accounts on the calendar." },
"calendarSelectAccount": "Select Account",
"@calendarSelectAccount": { "description": "Placeholder shown in the account dropdown when none is selected." },
"calendarMonthlyChange": "Monthly Change",
"@calendarMonthlyChange": { "description": "Label for the monthly-change metric on the calendar screen." },
"calendarNoTransactions": "No transactions on this day",
"@calendarNoTransactions": { "description": "Empty-state message when no transactions exist for the selected day." },
"moreCalendar": "Account Calendar",
"@moreCalendar": { "description": "Menu item title for Account Calendar in the More screen." },
"moreCalendarSubtitle": "View monthly balance changes by account",
"@moreCalendarSubtitle": { "description": "Subtitle for the Account Calendar menu item in the More screen." },
"moreRecentTransactions": "Recent Transactions",
"@moreRecentTransactions": { "description": "Menu item title for Recent Transactions in the More screen." },
"moreRecentTransactionsSubtitle": "View recent transactions across all accounts",
"@moreRecentTransactionsSubtitle": { "description": "Subtitle for the Recent Transactions menu item." },
"biometricTitle": "App Locked",
"@biometricTitle": { "description": "Heading shown on the biometric lock screen." },
"biometricSubtitle": "Authenticate to continue",
"@biometricSubtitle": { "description": "Subtitle shown on the biometric lock screen." },
"biometricUnlock": "Unlock",
"@biometricUnlock": { "description": "Button label when biometric prompt is ready." },
"biometricAuthenticating": "Authenticating…",
"@biometricAuthenticating": { "description": "Button label while the biometric prompt is in progress." },
"biometricLogOut": "Log out",
"@biometricLogOut": { "description": "Button to log out from the biometric lock screen." },
"backendConfigTitle": "Configuration",
"@backendConfigTitle": { "description": "Headline on the backend configuration screen." },
"backendConfigSubtitle": "Update your Sure server URL",
"@backendConfigSubtitle": { "description": "Subtitle below the headline on the backend configuration screen." },
"backendConfigExampleUrlsLabel": "Example URLs",
"@backendConfigExampleUrlsLabel": { "description": "Label for the example URLs info box on the config screen." },
"backendConfigUrlLabel": "Sure server URL",
"@backendConfigUrlLabel": { "description": "Label for the server URL field on the config screen." },
"backendConfigUrlHint": "https://app.sure.am",
"@backendConfigUrlHint": { "description": "Hint text for the server URL field." },
"backendConfigProxyHeadersLabel": "Custom proxy headers",
"@backendConfigProxyHeadersLabel": { "description": "Label for the custom proxy headers section on the config screen." },
"backendConfigProxyHeadersSubtitle": "Optional headers for a reverse proxy or auth gateway",
"@backendConfigProxyHeadersSubtitle": { "description": "Subtitle for the proxy headers expansion tile when no headers are configured." },
"backendConfigProxyHeadersCount": "{count} configured",
"@backendConfigProxyHeadersCount": {
"description": "Subtitle for the proxy headers expansion tile when headers are configured.",
"placeholders": { "count": { "type": "int" } }
},
"backendConfigTesting": "Testing…",
"@backendConfigTesting": { "description": "Label on the test-connection button while the test is in progress." },
"backendConfigTestButton": "Test Connection",
"@backendConfigTestButton": { "description": "Button label to test the server connection." },
"backendConfigContinueButton": "Continue",
"@backendConfigContinueButton": { "description": "Button label to proceed after saving configuration." },
"backendConfigChangeHint": "You can change this later in the settings.",
"@backendConfigChangeHint": { "description": "Helper text below the config form." },
"recentTransactionsTitle": "Recent Transactions",
"@recentTransactionsTitle": { "description": "App bar title for the recent transactions screen." },
"recentTransactionsEmpty": "No Transactions",
"@recentTransactionsEmpty": { "description": "Empty-state message on the recent transactions screen." },
"recentTransactionsDisplayLimit": "Display Limit",
"@recentTransactionsDisplayLimit": { "description": "Tooltip for the display-limit popup menu on the recent transactions screen." },
"recentTransactionsShowN": "Show {count}",
"@recentTransactionsShowN": {
"description": "Menu item label to show N recent transactions.",
"placeholders": { "count": { "type": "int" } }
},
"recentTransactionsPullToRefresh": "Pull to refresh",
"@recentTransactionsPullToRefresh": { "description": "Empty-state subtitle prompting the user to pull to refresh." },
"logViewerTitle": "Debug Logs",
"@logViewerTitle": { "description": "App bar title for the log viewer screen." },
"logViewerFilterAll": "All",
"@logViewerFilterAll": { "description": "Log level filter chip: show all log entries." },
"logViewerFilterInfo": "Info",
"@logViewerFilterInfo": { "description": "Log level filter chip: show info entries only." },
"logViewerFilterWarning": "Warning",
"@logViewerFilterWarning": { "description": "Log level filter chip: show warning entries only." },
"logViewerFilterError": "Error",
"@logViewerFilterError": { "description": "Log level filter chip: show error entries only." },
"logViewerFilterDebug": "Debug",
"@logViewerFilterDebug": { "description": "Log level filter chip: show debug entries only." },
"logViewerAutoScrollEnable": "Enable auto-scroll",
"@logViewerAutoScrollEnable": { "description": "Tooltip for the auto-scroll toggle button when auto-scroll is off." },
"logViewerAutoScrollDisable": "Disable auto-scroll",
"@logViewerAutoScrollDisable": { "description": "Tooltip for the auto-scroll toggle button when auto-scroll is on." },
"logViewerCopyLogs": "Copy logs",
"@logViewerCopyLogs": { "description": "Tooltip for the copy-logs action button." },
"logViewerClearLogs": "Clear logs",
"@logViewerClearLogs": { "description": "Tooltip for the clear-logs action button." },
"logViewerLogsCopied": "Logs copied to clipboard",
"@logViewerLogsCopied": { "description": "Snackbar message after logs are copied to the clipboard." },
"logViewerEmpty": "No logs yet",
"@logViewerEmpty": { "description": "Empty-state message shown when there are no log entries to display." },
"connectivityOffline": "You are offline",
"@connectivityOffline": { "description": "Message shown in the connectivity banner when the device has no network." },
"connectivityPendingSync": "{count, plural, one{{count} transaction pending sync} other{{count} transactions pending sync}}",
"@connectivityPendingSync": {
"description": "Connectivity banner message showing how many transactions are queued for sync.",
"placeholders": { "count": { "type": "int" } }
},
"connectivitySyncNow": "Sync Now",
"@connectivitySyncNow": { "description": "Button label in the connectivity banner to trigger an immediate sync." },
"proxyHeadersAddHeader": "Add header",
"@proxyHeadersAddHeader": { "description": "Button label to add a new custom proxy header." },
"proxyHeadersNameLabel": "Header name",
"@proxyHeadersNameLabel": { "description": "Label for the header-name input field." },
"proxyHeadersNameHint": "X-Auth-Token",
"@proxyHeadersNameHint": { "description": "Hint text for the header-name input (example header name)." },
"proxyHeadersValueLabel": "Header value",
"@proxyHeadersValueLabel": { "description": "Label for the header-value input field." },
"proxyHeadersRemove": "Remove header",
"@proxyHeadersRemove": { "description": "Tooltip for the remove-header icon button." },
"accountDetailRefreshTooltip": "Refresh account details",
"@accountDetailRefreshTooltip": { "description": "Tooltip for the refresh button on the account detail header." },
"accountDetailRecentBalanceHistory": "Recent balance history",
"@accountDetailRecentBalanceHistory": { "description": "Section heading for the balance history chart in the account detail header." },
"accountDetailTopHoldings": "Top holdings",
"@accountDetailTopHoldings": { "description": "Section heading for the top holdings list in the account detail header." },
"accountDetailHoldingFallback": "Holding",
"@accountDetailHoldingFallback": { "description": "Fallback label when a holding has no name." },
"accountDetailCashChip": "Cash {amount}",
"@accountDetailCashChip": {
"description": "Label for the cash position chip in the account detail header.",
"placeholders": { "amount": { "type": "String" } }
},
"biometricLockFailedRetry": "Authentication failed. Tap Unlock to try again.",
"@biometricLockFailedRetry": { "description": "Snackbar shown on the biometric lock screen when authentication fails." },
"settingsBiometricVerifyReason": "Verify biometric to enable app lock",
"@settingsBiometricVerifyReason": { "description": "Reason shown in the system biometric prompt when enabling app lock from settings." },
"settingsBiometricFailed": "Biometric authentication failed.",
"@settingsBiometricFailed": { "description": "Snackbar shown when biometric verification fails while enabling app lock." },
"settingsUpdateOpenStoreError": "Unable to open store link",
"@settingsUpdateOpenStoreError": { "description": "Snackbar shown when the app store update link cannot be opened." },
"settingsClearDataFailed": "Failed to clear local data.",
"@settingsClearDataFailed": { "description": "Snackbar shown when clearing local data fails." },
"settingsClearDataSuccessDetailed": "Local data cleared successfully. Pull to refresh to sync from server.",
"@settingsClearDataSuccessDetailed": { "description": "Snackbar shown after local data is cleared, prompting the user to pull to refresh." },
"settingsContactOpenLinkError": "Unable to open link",
"@settingsContactOpenLinkError": { "description": "Snackbar shown when the contact/Discord link cannot be opened." },
"settingsResetAccountContent": "Resetting your account will delete all your accounts, categories, merchants, tags, and other data, but keep your user account intact.\n\nThis action cannot be undone. Are you sure?",
"@settingsResetAccountContent": { "description": "Body text for the reset-account confirmation dialog." },
"settingsResetAccount": "Reset Account",
"@settingsResetAccount": { "description": "Title and confirm button label for the reset-account action." },
"settingsResetAccountInitiated": "Account reset has been initiated. This may take a moment.",
"@settingsResetAccountInitiated": { "description": "Snackbar shown after an account reset is initiated." },
"settingsResetAccountFailed": "Failed to reset account",
"@settingsResetAccountFailed": { "description": "Snackbar shown when resetting the account fails." },
"settingsDeleteAccountConfirmContent": "Deleting your account will permanently remove all your data and cannot be undone.\n\nAre you sure you want to delete your account?",
"@settingsDeleteAccountConfirmContent": { "description": "Body text for the delete-account confirmation dialog on the settings screen." },
"settingsDeleteAccountFailed": "Failed to delete account",
"@settingsDeleteAccountFailed": { "description": "Snackbar shown when deleting the account fails." },
"settingsProxyHeadersNote": "Headers are sent by the app with API requests. External browser SSO pages may not receive them.",
"@settingsProxyHeadersNote": { "description": "Explanatory note shown in the custom proxy headers dialog." },
"settingsProxyHeadersSaved": "Custom proxy headers saved",
"@settingsProxyHeadersSaved": { "description": "Snackbar shown after custom proxy headers are saved." },
"settingsProxyHeadersSaveFailed": "Failed to save custom proxy headers.",
"@settingsProxyHeadersSaveFailed": { "description": "Snackbar shown when saving custom proxy headers fails." },
"settingsAppVersion": "App Version: {version}",
"@settingsAppVersion": {
"description": "App version list tile title on the settings screen.",
"placeholders": { "version": { "type": "String" } }
},
"settingsCheckForUpdatesSubtitle": "See if a newer version is available",
"@settingsCheckForUpdatesSubtitle": { "description": "Subtitle for the check-for-updates list tile." },
"settingsContactUs": "Contact us",
"@settingsContactUs": { "description": "Title for the contact-us list tile on the settings screen." },
"settingsDebugLogsSemantics": "Open debug logs",
"@settingsDebugLogsSemantics": { "description": "Accessibility label for the debug logs list tile." },
"settingsDebugLogsSubtitle": "View app diagnostic logs",
"@settingsDebugLogsSubtitle": { "description": "Subtitle for the debug logs list tile." },
"settingsGroupByAccountType": "Group by Account Type",
"@settingsGroupByAccountType": { "description": "Title for the group-by-account-type toggle on the settings screen." },
"settingsGroupByAccountTypeSubtitle": "Group accounts by type (Crypto, Bank, etc.)",
"@settingsGroupByAccountTypeSubtitle": { "description": "Subtitle for the group-by-account-type toggle." },
"settingsProxyHeadersTileTitle": "Custom proxy headers",
"@settingsProxyHeadersTileTitle": { "description": "Title for the custom proxy headers list tile on the settings screen." },
"settingsProxyHeadersTileSubtitleEmpty": "Optional headers for a reverse proxy or auth gateway",
"@settingsProxyHeadersTileSubtitleEmpty": { "description": "Subtitle for the custom proxy headers list tile when no headers are configured." },
"settingsProxyHeadersTileSubtitleCount": "{count} configured",
"@settingsProxyHeadersTileSubtitleCount": {
"description": "Subtitle for the custom proxy headers list tile when headers are configured.",
"placeholders": { "count": { "type": "int" } }
},
"settingsClearDataTileSubtitle": "Remove all cached transactions and accounts",
"@settingsClearDataTileSubtitle": { "description": "Subtitle for the clear-local-data list tile." },
"settingsResetAccountTileSubtitle": "Delete all accounts, categories, merchants, and tags but keep your user account",
"@settingsResetAccountTileSubtitle": { "description": "Subtitle for the reset-account list tile in the danger zone." },
"settingsDeleteAccountTileSubtitle": "Permanently remove all your data. This cannot be undone.",
"@settingsDeleteAccountTileSubtitle": { "description": "Subtitle for the delete-account list tile in the danger zone." },
"settingsUserFallback": "User",
"@settingsUserFallback": { "description": "Fallback display name when the user has no name set." },
"chatListDeleteMultiContent": "{count, plural, one{Delete {count} chat? This cannot be undone.} other{Delete {count} chats? This cannot be undone.}}",
"@chatListDeleteMultiContent": {
"description": "Body text for the multi-chat delete confirmation dialog.",
"placeholders": { "count": { "type": "int" } }
},
"chatListDeletedSuccess": "Chats deleted",
"@chatListDeletedSuccess": { "description": "Snackbar shown after multiple chats are deleted." },
"chatListDeleteFailed": "Failed to delete chats",
"@chatListDeleteFailed": { "description": "Snackbar shown when deleting multiple chats fails." },
"chatListError": "Failed to load chats",
"@chatListError": { "description": "Error-state heading on the chat list screen." },
"chatListJustNow": "Just now",
"@chatListJustNow": { "description": "Relative time label for very recent chats." },
"chatListDeleteSingleContent": "Are you sure you want to delete \"{title}\"?",
"@chatListDeleteSingleContent": {
"description": "Body text for the single-chat delete confirmation dialog.",
"placeholders": { "title": { "type": "String" } }
},
"loginSignUpOpenError": "Unable to open sign up page",
"@loginSignUpOpenError": { "description": "Snackbar shown when the sign-up page cannot be opened." },
"loginApiKeyDialogTitle": "API Key Login",
"@loginApiKeyDialogTitle": { "description": "Title for the API key login dialog on the login screen." },
"loginApiKeyDialogBody": "Enter your API key to sign in.",
"@loginApiKeyDialogBody": { "description": "Body text for the API key login dialog." },
"loginApiKeyInvalid": "Invalid API key",
"@loginApiKeyInvalid": { "description": "Fallback error shown when an API key login fails." },
"loginApiKeySignIn": "Sign In",
"@loginApiKeySignIn": { "description": "Sign-in button label in the API key login dialog." },
"loginDemoOrSignUpPrefix": "Demo account or ",
"@loginDemoOrSignUpPrefix": { "description": "Leading text before the Sign Up link on the login screen." },
"loginSignUpLink": "Sign Up",
"@loginSignUpLink": { "description": "Tappable Sign Up link text on the login screen." },
"loginSignUpSuffix": "!",
"@loginSignUpSuffix": { "description": "Trailing punctuation after the Sign Up link on the login screen." },
"loginMfaInfo": "Two-factor authentication is enabled. Enter your code.",
"@loginMfaInfo": { "description": "Info banner shown when MFA is required during login." },
"loginMfaCodeRequired": "Please enter your authentication code",
"@loginMfaCodeRequired": { "description": "Validation error when the MFA code field is empty." },
"loginOrDivider": "or",
"@loginOrDivider": { "description": "Divider label between primary and alternate sign-in options." },
"loginServerUrlHeading": "Sure server URL:",
"@loginServerUrlHeading": { "description": "Heading above the displayed server URL on the login screen." },
"loginApiKeyLoginButton": "API-Key Login",
"@loginApiKeyLoginButton": { "description": "Button label to open the API key login dialog." },
"loginBackendSettingsTooltip": "Backend Settings",
"@loginBackendSettingsTooltip": { "description": "Tooltip for the backend settings button on the login screen." },
"transactionEditSessionExpired": "Session expired. Please login again.",
"@transactionEditSessionExpired": { "description": "Snackbar shown when the session expires while editing a transaction." },
"transactionEditUpdated": "Transaction updated",
"@transactionEditUpdated": { "description": "Snackbar shown after a transaction is updated successfully." },
"transactionEditUpdateFailed": "Failed to update transaction",
"@transactionEditUpdateFailed": { "description": "Fallback snackbar shown when updating a transaction fails." },
"transactionEditNameMaxLength": "Name must be {max} characters or fewer",
"@transactionEditNameMaxLength": {
"description": "Validation error when the transaction name exceeds the maximum length.",
"placeholders": { "max": { "type": "int" } }
},
"transactionEditNameInvalidChars": "Name contains unsupported characters",
"@transactionEditNameInvalidChars": { "description": "Validation error when the transaction name contains control characters." },
"transactionEditNotesMaxLength": "Notes must be {max} characters or fewer",
"@transactionEditNotesMaxLength": {
"description": "Validation error when the notes exceed the maximum length.",
"placeholders": { "max": { "type": "int" } }
},
"transactionEditNotesInvalidChars": "Notes contain unsupported characters",
"@transactionEditNotesInvalidChars": { "description": "Validation error when the notes contain control characters." },
"transactionEditNoCategory": "No category",
"@transactionEditNoCategory": { "description": "Dropdown option representing no selected category in the edit form." },
"transactionEditCurrentCategory": "Current category",
"@transactionEditCurrentCategory": { "description": "Fallback dropdown label for the currently selected category when its name is unknown." },
"transactionEditNoMerchant": "No merchant",
"@transactionEditNoMerchant": { "description": "Dropdown option representing no selected merchant in the edit form." },
"transactionEditCurrentMerchant": "Current merchant",
"@transactionEditCurrentMerchant": { "description": "Fallback dropdown label for the currently selected merchant when its name is unknown." },
"transactionEditNoTags": "No tags available",
"@transactionEditNoTags": { "description": "Message shown when no tags are available to select in the edit form." },
"transactionEditUnknownTag": "Unknown tag",
"@transactionEditUnknownTag": { "description": "Fallback label for a selected tag whose name is unknown." },
"transactionEditSyncedOnly": "Only synced transactions can be edited from mobile.",
"@transactionEditSyncedOnly": { "description": "Notice shown when a transaction cannot be edited because it is not yet synced." },
"transactionEditCategoryHelper": "Choose a replacement category",
"@transactionEditCategoryHelper": { "description": "Helper text under the category dropdown in the edit form." },
"transactionEditMerchantHelper": "Choose a replacement merchant",
"@transactionEditMerchantHelper": { "description": "Helper text under the merchant dropdown in the edit form." },
"transactionFormSessionExpired": "Session expired. Please login again.",
"@transactionFormSessionExpired": { "description": "Snackbar shown when the session expires while creating a transaction." },
"transactionFormAmountRequiredPrompt": "Please enter an amount",
"@transactionFormAmountRequiredPrompt": { "description": "Validation error when the amount field is empty in the create form." },
"transactionFormAmountInvalidNumber": "Please enter a valid number",
"@transactionFormAmountInvalidNumber": { "description": "Validation error when the amount is not a valid number." },
"transactionFormAmountTooSmall": "Amount must be greater than 0",
"@transactionFormAmountTooSmall": { "description": "Validation error when the amount is zero or negative." },
"transactionFormCreateSuccessOnline": "Transaction created successfully",
"@transactionFormCreateSuccessOnline": { "description": "Snackbar shown after a transaction is created while online." },
"transactionFormCreateSuccessOffline": "Transaction saved (will sync when online)",
"@transactionFormCreateSuccessOffline": { "description": "Snackbar shown after a transaction is saved while offline." },
"transactionFormCreateFailed": "Failed to create transaction",
"@transactionFormCreateFailed": { "description": "Snackbar shown when creating a transaction fails." },
"transactionFormGenericError": "Error: {error}",
"@transactionFormGenericError": {
"description": "Snackbar shown when an unexpected error occurs while creating a transaction.",
"placeholders": { "error": { "type": "String" } }
},
"transactionFormLess": "Less",
"@transactionFormLess": { "description": "Toggle label to hide the optional transaction fields." },
"transactionFormMore": "More",
"@transactionFormMore": { "description": "Toggle label to show the optional transaction fields." },
"transactionFormDateHelper": "Optional (default: today)",
"@transactionFormDateHelper": { "description": "Helper text for the date field in the create form." },
"transactionFormNameHelper": "Optional (default: SureApp)",
"@transactionFormNameHelper": { "description": "Helper text for the name field in the create form." },
"transactionFormCategoryLoading": "Loading categories…",
"@transactionFormCategoryLoading": { "description": "Placeholder shown while categories are loading in the create form." },
"transactionFormCategoryHelper": "Optional",
"@transactionFormCategoryHelper": { "description": "Helper text for the category dropdown in the create form." },
"transactionFormNoCategory": "No category",
"@transactionFormNoCategory": { "description": "Dropdown option representing no selected category in the create form." },
"transactionFormCreateButton": "Create Transaction",
"@transactionFormCreateButton": { "description": "Submit button label on the create-transaction form." },
"transactionFormAmountHelper": "Required",
"@transactionFormAmountHelper": { "description": "Helper text under the amount field indicating it is required." },
"logViewerClearConfirm": "Are you sure you want to clear all logs?",
"@logViewerClearConfirm": { "description": "Body text for the clear-logs confirmation dialog." },
"logViewerClear": "Clear",
"@logViewerClear": { "description": "Confirm button label in the clear-logs dialog." },
"chatConversationEditTitle": "Edit Title",
"@chatConversationEditTitle": { "description": "Title for the edit-chat-title dialog." },
"chatConversationTitleLabel": "Chat Title",
"@chatConversationTitleLabel": { "description": "Label for the chat title input field in the edit dialog." },
"chatConversationRefreshTooltip": "Refresh",
"@chatConversationRefreshTooltip": { "description": "Tooltip for the refresh button in the chat conversation screen." },
"chatConversationLoadError": "Failed to load chat",
"@chatConversationLoadError": { "description": "Error-state heading when a chat fails to load." },
"navEnableAiChatTitle": "Turn on AI Chat?",
"@navEnableAiChatTitle": { "description": "Title for the dialog prompting the user to enable AI chat." },
"navEnableAiChatContent": "AI Chat is currently disabled in your account settings. Would you like to turn it on now?",
"@navEnableAiChatContent": { "description": "Body text for the enable-AI-chat dialog." },
"navEnableAiChatNotNow": "Not now",
"@navEnableAiChatNotNow": { "description": "Dismiss button in the enable-AI-chat dialog." },
"navEnableAiChatConfirm": "Turn on AI",
"@navEnableAiChatConfirm": { "description": "Confirm button in the enable-AI-chat dialog." },
"navEnableAiChatFailed": "Unable to enable AI right now.",
"@navEnableAiChatFailed": { "description": "Snackbar shown when enabling AI chat fails." },
"transactionsListEditTooltip": "Edit transaction",
"@transactionsListEditTooltip": { "description": "Tooltip for the edit button on a transaction row." },
"connectivitySignInToSync": "Please sign in to sync transactions",
"@connectivitySignInToSync": { "description": "Snackbar shown when trying to sync without being signed in." },
"connectivitySyncSuccess": "Transactions synced successfully",
"@connectivitySyncSuccess": { "description": "Snackbar shown after transactions sync successfully." },
"connectivitySyncFailed": "Failed to sync transactions. Please try again.",
"@connectivitySyncFailed": { "description": "Snackbar shown when a manual sync fails." },
"connectivityAuthFailed": "Unable to authenticate. Please try again.",
"@connectivityAuthFailed": { "description": "Snackbar shown when authentication fails during a manual sync." },
"ssoOnboardingSignedInAs": "Signed in as {email}",
"@ssoOnboardingSignedInAs": {
"description": "Header text on the SSO onboarding screen showing the signed-in Google email.",
"placeholders": { "email": { "type": "String" } }
},
"ssoOnboardingGoogleVerified": "Google account verified",
"@ssoOnboardingGoogleVerified": { "description": "Header text on the SSO onboarding screen when the Google email is unknown." },
"ssoOnboardingLinkCredentialsNote": "Enter your existing account credentials to link with Google Sign-In.",
"@ssoOnboardingLinkCredentialsNote": { "description": "Explanatory note on the link-existing form prompting the user to enter their credentials." },
"ssoOnboardingPendingInvitationNote": "You have a pending invitation. Accept it to join an existing household.",
"@ssoOnboardingPendingInvitationNote": { "description": "Explanatory note on the create form when the user has a pending household invitation." },
"ssoOnboardingCreateIdentityNote": "Create a new account using your Google identity.",
"@ssoOnboardingCreateIdentityNote": { "description": "Explanatory note on the create form when creating a brand-new account via Google." },
"ssoOnboardingFirstNameRequired": "First name is required",
"@ssoOnboardingFirstNameRequired": { "description": "Validation error when the first-name field is empty on the SSO onboarding screen." },
"ssoOnboardingLastNameRequired": "Last name is required",
"@ssoOnboardingLastNameRequired": { "description": "Validation error when the last-name field is empty on the SSO onboarding screen." },
"backendConfigTimeout": "Connection timeout. Please check the URL and try again.",
"@backendConfigTimeout": { "description": "Error shown when the connection test times out." },
"backendConfigSuccess": "Connection successful!",
"@backendConfigSuccess": { "description": "Message shown when the connection test succeeds." },
"backendConfigServerError": "Server responded with status {code}. Please check if this is a Sure backend server.",
"@backendConfigServerError": {
"description": "Error shown when the server returns a non-success status during the connection test.",
"placeholders": { "code": { "type": "int" } }
},
"backendConfigConnectionFailed": "Connection failed: {error}",
"@backendConfigConnectionFailed": {
"description": "Error shown when the connection test fails with an exception.",
"placeholders": { "error": { "type": "String" } }
},
"backendConfigSaveFailed": "Failed to save URL: {error}",
"@backendConfigSaveFailed": {
"description": "Error shown when saving the backend URL fails.",
"placeholders": { "error": { "type": "String" } }
},
"backendConfigUrlRequired": "Please enter a backend URL",
"@backendConfigUrlRequired": { "description": "Validation error when the backend URL field is empty." },
"backendConfigUrlScheme": "URL must start with http:// or https://",
"@backendConfigUrlScheme": { "description": "Validation error when the backend URL is missing an http(s) scheme." },
"backendConfigUrlInvalid": "Please enter a valid URL",
"@backendConfigUrlInvalid": { "description": "Validation error when the backend URL is malformed." },
"backendConfigHeadersHelp": "Headers are sent by the app with API requests. External browser SSO pages may not receive them.",
"@backendConfigHeadersHelp": { "description": "Helper text below the custom proxy headers section on the backend config screen." },
"recentTransactionsUnknownAccount": "Unknown Account",
"@recentTransactionsUnknownAccount": { "description": "Fallback label when a transaction's account cannot be resolved." },
"accountDetailUnavailable": "Account details are temporarily unavailable",
"@accountDetailUnavailable": { "description": "Error message shown when account detail and balances both fail to load." },
"chatListMinutesAgo": "{minutes}m ago",
"@chatListMinutesAgo": {
"description": "Relative time label for a chat updated some minutes ago.",
"placeholders": { "minutes": { "type": "int" } }
},
"chatListHoursAgo": "{hours}h ago",
"@chatListHoursAgo": {
"description": "Relative time label for a chat updated some hours ago.",
"placeholders": { "hours": { "type": "int" } }
},
"chatListDaysAgo": "{days}d ago",
"@chatListDaysAgo": {
"description": "Relative time label for a chat updated some days ago.",
"placeholders": { "days": { "type": "int" } }
},
"chatConversationStartFailed": "Failed to start conversation. Please try again.",
"@chatConversationStartFailed": { "description": "Fallback snackbar shown when creating a new conversation from the first message fails." }
}