DriftLedger/src/i18n/en.ts
fengmengqi 76a5853ab6 feat: 重构渠道模型与管道架构,全面升级 UI 主题和报表功能
核心重构 — 去除 channel 字段,引入 sourceAccount 模型:
- 从 ImportedEvent、Rule、EnhancedRule、OcrRule 等接口中彻底移除 channel 字段
- rules.ts 中 resolveChannelAccount → resolveSourceAccount,规则匹配与账户解析不再依赖渠道概念
- dedup.ts 重写去重逻辑:从基于渠道匹配改为基于交易对手(counterparty)匹配,支持相同金额/交易对手/时间窗口的多级置信度判断
- transferRecognizer.ts 增加资产负债表账户校验,确保转账双方均为 Assets/Liabilities 类账户
- 全局替换影响:types、rules、ocr、adapters、adapters-migrations、所有服务层和测试
新增基础设施:
- domain/constants.ts — 统一常量定义(支付包名、截图关键词、去重参数、方向检测函数 detectDirection()),消除 OCR/SMS/截图等模块的重复定义
- domain/channelConfig.ts — 渠道配置系统(支付宝/微信/银行),支持按包名和名称查找
- domain/pipelineSingleton.ts — 共享 BillPipeline 单例,解决 importStore/automationStore 的互斥锁共享问题
- domain/transactionBuilder.ts — 统一交易构建入口 buildAndSaveTransaction(),同时服务手动录入和无障碍监听
OCR 增强:
- 新增账单详情页解析(parseDetailPageBill),支持支付宝/微信详情页结构化提取
- checkIsDetailPage() 识别详情页特征词,防止误提取(如"消费1次"被误读为金额)
- 金额正则支持千分位逗号分隔,商户名正则改用 lookahead 边界匹配
- 时间解析支持中文格式(年月日)和跨年推断
- OcrProcessor 新增详情页路由,跳过 Layer 1 规则匹配
UI 全面升级:
- 主题重设计:accent 色从绿色改为靛蓝(#4F46E5),深色模式适配 OLED 纯黑,引入 Quicksand/Caveat 字体
- 新增 commonStyles.ts 统一 chip/input/modal 等通用样式
- 首页 Bento 网格布局:净资产英雄卡片 + 定期账单/月度统计并排展示
- 报表新增周报标签页,月报整合日历视图(支持点击查看当日交易明细)
- TrendLine 图表从 View 条形图重写为 SVG 贝塞尔曲线
- CategoryPicker 从水平滚动改为 4 列网格 + emoji 图标
- Button/Card 增加 press 缩放动画
管道与自动化改进:
- automationPipeline.ts 新增 handleIncomingBillEvent() 实时账单处理(悬浮账单卡片 + 前台 Alert 确认)
- 新增无障碍文本直解析 parseAndProcessAccessibilityTexts(),微信/支付宝详情页绕过 OCR
- rules.ts 新增智能还款检测(花呗/信用卡还款自动路由)和退款视为收入处理
- metadataStore 默认规则精简为 6 条通用规则,移除约 20 条个人化硬编码规则
存储与同步:
- storePersistence.ts 原子写入 + 崩溃恢复 + 重试机制
- 备份升级到 v2 格式,包含 settings 和 metadata
- 同步路径统一从 mobile.bean 改为 main.bean
- _layout.tsx 启动时自动迁移旧 mobile.bean 到 main.bean
其他:
- 删除独立日历页面,功能合并到报表月报标签
- i18n 清理:移除渠道相关翻译,新增 50+ 翻译键
- docs/android-build-guide.md 重写为 APK 体积优化指南
- 新增 design-system/beancount-mobile/MASTER.md 设计系统文档
- 测试全面更新覆盖以上所有变更
2026-07-18 18:02:45 +08:00

643 lines
24 KiB
TypeScript

/**
* English translations. Keys must stay in sync with zh.ts.
*/
export default {
app: {
name: 'Bean Mobile',
tagline: 'Simple, offline mobile bookkeeping. Your privacy is protected, and all data is securely stored locally.',
statusParsed: 'Ledger loaded. Your transactions are safely stored locally.',
},
common: {
cancel: 'Cancel',
delete: 'Delete',
confirm: 'OK',
save: 'Save',
edit: 'Edit',
add: 'Add',
back: 'Back',
notFound: 'Not Found',
untitled: 'Untitled',
clickEditLongDelete: 'Tap to edit · Long-press to delete',
noData: 'No data',
discard: 'Discard',
},
tab: {
home: 'Home',
transactions: 'Txns',
report: 'Report',
import: 'Import',
rules: 'Rules',
settings: 'Settings',
},
home: {
title: 'Ledger Status',
accounts: '%{count} open accounts · %{total} transactions',
readonlyHint: 'Ledger is ready. New transactions will be saved automatically.',
mobileBean: 'Local Records',
mobileBeanEmpty: 'No transactions yet',
newTransaction: 'New Transaction',
netWorthTitle: 'Net Worth',
assets: 'Assets',
liabilities: 'Liabilities',
netWorth: 'Net Worth',
income: 'Income',
expense: 'Expense',
balance: 'Balance',
monthlyTrend: 'Monthly Trend',
recentTransactions: 'Recent',
recentEmpty: 'No transactions yet. Tap + to add one.',
ocrScan: 'Scan Receipt',
dueRecurring: 'Due Recurring Bills',
dueAmount: 'Amount',
confirmRecurring: 'Confirm',
recurringSuccess: 'Success',
recurringSuccessDesc: 'Recurring bill "%{name}" has been recorded and next due date updated.',
recurringFail: 'Recurring recording failed',
accountTree: 'Account Balance Tree',
balanced: '✓ Balanced',
unbalanced: '✗ Unbalanced',
},
transaction: {
newTitle: 'Manual Entry',
amountPlaceholder: 'Amount, e.g. 24.50',
accountPlaceholder: 'Expense account',
submit: 'Save Transaction',
appended: 'Transaction saved successfully.',
detailTitle: 'Transaction Detail',
notFoundTitle: 'Transaction Not Found',
notFoundDesc: 'Transaction with ID %{id} could not be found. It may have been deleted or not yet loaded.',
summary: 'Summary',
noSummary: '(no summary)',
postings: 'Postings',
tags: 'Tags',
links: 'Links',
metadata: 'Additional Info',
rawBean: 'Underlying Text',
source: 'Source',
directionExpense: 'Expense',
directionIncome: 'Income',
directionTransfer: 'Transfer',
formDate: 'Date',
formNarration: 'Narration',
formPayee: 'Payee',
formPayeePlaceholder: 'Counterparty/merchant',
formSourceAccount: 'Source Account',
formAvailable: 'Available',
formCategoryExpense: 'Expense Category',
formCategoryIncome: 'Income Category',
formTags: 'Tags (optional)',
formTagsWillWrite: 'Will write',
amountRequired: 'Please enter an amount',
sameAccountError: 'Source and target accounts cannot be the same',
narrationDefaultExpense: 'Expense',
narrationDefaultIncome: 'Income',
narrationDefaultTransfer: 'Transfer',
advancedMode: 'Advanced Double-Entry Mode',
atLeast2Postings: 'Double-entry requires at least 2 valid posting lines',
unbalanced: 'Unbalanced! Sum of postings per currency must equal 0',
defaultAdvancedNarration: 'Double-entry transaction',
postingsList: 'Details',
addPosting: 'Add Detail',
deleteLastPosting: 'Delete Last',
targetAccount: 'Target Account',
unsavedTitle: 'Discard changes?',
unsavedMessage: 'You have unsaved changes. Are you sure you want to leave?',
hideDetails: 'Hide details',
addDetails: 'Add transaction details (date, payee, tags, etc.)',
extraDetails: 'Extra details',
invalidAmount: 'Please enter a valid amount value',
linkTransaction: 'Link Another Transaction',
linkSuccess: 'Linked',
linkSuccessDesc: 'Two transactions linked with tag ^%{tag}.',
linkFail: 'Link failed',
unlinkSuccess: 'Unlinked',
unlinkSuccessDesc: 'Removed link tag ^%{tag} from this transaction.',
unlinkFail: 'Unlink failed',
relatedTransactions: 'Related Transactions',
readOnlyLinks: 'Read-only transaction, links cannot be edited.',
linkMobileTx: 'Link Mobile Transaction',
searchTxPlaceholder: 'Search narration or account...',
noLinkableTx: 'No linkable mobile transactions',
edit: 'Edit',
delete: 'Delete',
deleteConfirm: 'Delete this transaction? This cannot be undone.',
deleteTitle: 'Delete Transaction',
editSuccess: 'Updated',
deleteSuccess: 'Deleted',
},
transactions: {
searchPlaceholder: 'Search narration / payee',
filterAll: 'All',
filterExpense: 'Expense',
filterIncome: 'Income',
filterTransfer: 'Transfer',
countTotal: '%{count} total',
countFiltered: '%{shown} / %{total}',
noMatchTitle: 'No Matching Transactions',
noMatchEmpty: 'No transactions yet.',
noMatchFiltered: 'No transactions match your search. Try adjusting the filters.',
advancedFilter: 'Advanced',
accountFilterPlaceholder: 'Account contains...',
dateFrom: 'From date',
dateTo: 'To date',
},
report: {
title: 'Report',
empty: 'No transaction data yet. Add transactions to see reports.',
monthly: 'Monthly Income/Expense',
categoryPie: 'Expense by Category',
heatmap: 'This Month Heatmap',
netWorthTrend: 'Net Worth Trend',
annual: '%{year} Annual Report',
shareTitle: 'Share Report',
exportSuccess: 'Exported',
exportFail: 'Export failed',
aiSummary: 'Report Summary',
trendTitle: 'Income/Expense Trend',
netWorthTitle: 'Net Worth Trend',
netWorthCurrent: 'Current Net Worth: %{amount}',
monthlyTitle: 'Monthly Report',
categoryTitle: 'Expense by Category',
annualTitle: '%{year} Annual Report',
annualIncome: 'Income',
annualExpense: 'Expense',
annualNet: 'Net',
annualStats: '%{count} transactions · Daily avg %{avg}',
topCategory: 'Top Categories',
tabWeekly: 'Weekly',
tabMonthly: 'Monthly',
tabAnnual: 'Annual',
weeklyTitle: 'Weekly Report',
weeklyIncome: 'Weekly Income',
weeklyExpense: 'Weekly Expense',
weeklyNet: 'Weekly Net',
weeklyStats: '%{count} transactions',
monthlyIncome: 'Monthly Income',
monthlyExpense: 'Monthly Expense',
monthlyNet: 'Monthly Net',
monthlyStats: '%{count} transactions',
},
calendar: {
title: 'Calendar',
noDayTx: 'No transactions on this day',
},
category: {
title: 'Categories',
expense: 'Expense Categories',
income: 'Income Categories',
addExpense: 'Add Expense Category',
addIncome: 'Add Income Category',
editTitle: 'Edit Category',
addTitle: 'Add %{type} Category',
fieldName: 'Category Name',
fieldLinkedAccount: 'Linked Beancount Account',
fieldKeywords: 'Keywords (comma-separated)',
keywordsLabel: 'Keywords',
deleteTitle: 'Delete Category',
deleteConfirm: 'Delete "%{name}"?',
namePlaceholder: 'e.g. Food',
accountPlaceholder: 'e.g. Expenses:Food',
keywordsPlaceholder: 'Match keywords, comma-separated, e.g. food, dinner, coffee',
},
tag: {
title: 'Tags',
count: 'Tags (%{count})',
add: 'Add Tag',
editTitle: 'Edit Tag',
invalidName: 'Invalid tag name',
invalidDesc: 'Only letters, digits, underscore, and hyphen are allowed (1-32 chars)',
selectColor: 'Select Color',
fieldName: 'Tag name (letters/digits/_/-)',
empty: 'No tags yet',
deleteTitle: 'Delete Tag',
deleteConfirm: 'Delete "#%{name}"?',
},
budget: {
title: 'Budgets',
add: 'Add Budget',
editTitle: 'Edit Budget',
fieldName: 'Budget Name',
fieldAmount: 'Amount',
fieldPeriod: 'Period (monthly/weekly/yearly)',
fieldCategoryAccount: 'Linked Account (blank = total)',
fieldStartDate: 'Start Date',
periodMonthly: 'mo',
periodWeekly: 'wk',
periodYearly: 'yr',
yuanPer: 'CNY / %{period}',
used: 'Used %{spent} (%{pct}%)',
unnamed: 'Untitled Budget',
empty: 'No budgets yet. Tap above to add one.',
deleteTitle: 'Delete Budget',
deleteConfirm: 'Delete "%{name}"?',
invalidAmountTitle: 'Invalid Amount',
invalidAmountDesc: 'Please enter a budget amount greater than 0',
fieldNamePlaceholder: 'e.g. Food Budget',
fieldAmountPlaceholder: 'e.g. 2000',
fieldCategoryPlaceholder: 'e.g. Expenses:Food',
},
creditCard: {
title: 'Credit Cards',
add: 'Add Credit Card',
editTitle: 'Edit Credit Card',
fieldName: 'Card Name',
fieldBank: 'Bank',
fieldLastFour: 'Last 4 Digits',
fieldBillingDay: 'Billing Day (1-28)',
fieldPaymentDay: 'Payment Day (1-28)',
fieldLimit: 'Credit Limit',
fieldCurrency: 'Currency',
fieldLinkedAccount: 'Linked Beancount Account',
labelBank: 'Bank',
labelBillingDay: 'Billing Day',
labelPaymentDay: 'Payment Day',
labelLimit: 'Limit',
daySuffix: '',
unnamed: 'Untitled Card',
empty: 'No credit cards yet. Tap above to add one.',
deleteTitle: 'Delete Credit Card',
deleteConfirm: 'Delete "%{name}"?',
},
rules: {
title: 'Auto-categorization Rules',
add: 'Add Rule',
editTitle: 'Edit Rule',
empty: 'No rules yet',
fieldPriority: 'Priority (higher = matched first)',
fieldCounterparty: 'Counterparty contains',
fieldMemo: 'Memo contains (optional)',
fieldSourceAccount: 'Source Account',
fieldCategoryAccount: 'Category Account',
fieldNarration: 'Narration (optional)',
fieldTags: 'Tags (comma-separated)',
condCounterparty: 'payee contains "%{val}"',
condMemo: 'memo contains "%{val}"',
condNone: 'no conditions',
hitsSuffix: '%{count} hits',
deleteTitle: 'Delete Rule',
deleteConfirm: 'Delete "%{name}"?',
},
importFlow: {
title: 'Bill Import',
hint: 'Flow: Import CSV → Process (transfer→dedup→categorize) → Confirm each.',
importButton: 'Import demo Alipay CSV',
processButton: 'Re-analyze (%{count} bills)',
result: 'Last run: %{drafts} drafts / %{duplicates} dups / %{transfers} transfers',
processed: 'Done: %{drafts} drafts, %{duplicates} duplicates, %{transfers} transfers.',
confirm: 'Confirm',
confirmed: 'Saved locally.',
transfer: 'Transfer',
trade: 'Trade',
fallbackWarn: '⚠ %{reason}',
selectFile: 'Select local CSV or Excel bill',
confirmAll: 'Commit all (%{count})',
duplicatesTitle: 'Suspected duplicates (%{count})',
collapseList: 'Collapse',
expandDuplicates: 'Expand suspected duplicates',
duplicateReason: 'Similar time and matching amount (%{date} · payee: %{payee})',
noDesc: 'No description',
duplicateAmount: 'Amount: %{amount} %{currency}',
unknownPayee: 'Unknown',
ignored: 'Ignored this duplicate.',
forcedImport: 'Bill forcibly imported!',
decodeFail: 'Bill decode failed: %{error}',
importSuccess: 'Imported %{format} bill: %{name}.',
formatExcel: 'Excel',
formatCsv: 'CSV',
pipelineRunning: 'Automatically categorizing and finding duplicates...',
pipelineDone: 'Parsed! Converted %{drafts} transactions, detected %{duplicates} suspected duplicates.',
pipelineFail: 'Analysis failed: %{error}',
pickFail: 'File selection failed: %{error}',
importFailTitle: 'Import failed',
commitFail: 'Commit failed: %{error}',
commitFailTitle: 'Recording failed',
batchPreparing: 'Validating and preparing batch commit...',
autoOpenFail: 'Auto-open failed: %{error}',
autoOpenFailTitle: 'Open failed',
batchAllSuccess: 'All committed! Successfully recorded %{count} transactions.',
batchPartial: 'Partial failure: %{success} succeeded, %{fail} failed.',
batchResultTitle: 'Batch result',
batchResultBody: 'Succeeded: %{success}\nFailed: %{fail}\n\nReasons:\n%{reasons}',
batchNoValid: 'No valid transactions. Reasons:\n%{reasons}',
batchFail: 'Batch commit failed: %{error}',
batchErrorTitle: 'Recording error',
suspectedDuplicate: 'Suspected duplicate',
skip: 'Skip',
forceImport: 'Import anyway',
},
onboarding: {
welcome: 'Welcome',
language: 'Choose Language',
theme: 'Choose Theme',
ledger: 'Ledger Import',
ledgerDesc: 'The app safely stores all new transactions in a separate local file without altering your existing ledger.',
permissions: 'Feature Setup',
permissionsDesc: 'OCR, notification listening, and SMS monitoring require manual permission grants in system settings (Accessibility/Notification access/SMS).',
security: 'Security',
securityDesc: 'Enable app lock (biometric) and blur private content when switching apps.',
prev: 'Previous',
next: 'Next',
start: 'Get Started',
enableLock: 'Enable App Lock',
lockEnabled: '✓ App Lock Enabled',
},
diagnostics: {
title: 'Read-only Compatibility & Diagnostics',
syntaxErrors: 'Syntax issues: %{count}',
unsupported: 'Preserved advanced directives: %{count}',
balanceAssertions: 'Balance assertions: %{count}',
options: 'Options: %{count}',
hint: 'Unrecognized directives are preserved in read-only mode.',
},
settings: {
title: 'Settings',
appearance: 'Appearance',
themeMode: 'Theme',
themeLight: 'Light',
themeDark: 'Dark',
themeSystem: 'System',
language: 'Language',
langZh: '中文',
langEn: 'English',
dataManagement: 'Data Management',
categories: 'Categories',
tags: 'Tags',
budgets: 'Budgets',
creditCards: 'Credit Cards',
calendar: 'Calendar View',
security: 'Security Settings',
appLock: 'Enable App Lock',
setPinDesc: 'Set a 4-digit PIN to unlock the app',
reminderTitle: 'Reminders',
reminderToggle: 'Daily bookkeeping reminder',
reminderTime: 'Reminder time',
syncTitle: 'Sync & Backup',
webdavConfig: 'WebDAV Sync Settings',
gitConfig: 'Git Sync Settings',
iCloudSync: 'Sync with iCloud Now',
syncNow: 'Sync Now',
backupNow: 'Database Backup & Maintenance',
backupBtn: 'Backup Data',
restoreBtn: 'Restore Data',
optimizeDb: 'Optimize & Maintain Database',
dedupLabel: 'Multi-channel De-duplication',
transferLabel: 'Smart Transfer Recognition',
syncSuccess: 'Sync successful',
syncFail: 'Sync failed: %{error}',
backupSuccess: 'Backup successful: %{path}',
restoreSuccess: 'Restore successful, ledger refreshed',
maintenanceSuccess: 'Database maintenance completed',
algorithmConfig: 'Auto-Classification Rules Settings',
selectLang: 'Select language',
preferencesTitle: 'Preferences & Security',
recurringTitle: 'Recurring Transactions',
systemFeaturesTitle: 'Features & System',
aiSettingsTitle: 'AI & Smart Entry',
syncSettingsTitle: 'Cloud Sync & Backup',
aboutTitle: 'About',
aboutVersion: 'v0.1.0 (Hermes)',
enableAiHint: 'Enable AI bill classification hints',
aiAssistantTitle: 'AI Bill Recognition Assistant',
aiProvider: 'AI Provider',
aiConfigBtn: 'Configure API connection',
aiCurrentModel: 'Current model: %{model} · URL: %{url}',
aiConfigTitle: 'Configure AI API',
aiFieldApiKey: 'API Key',
aiFieldBaseUrl: 'API Base URL',
aiFieldModel: 'Model name',
},
account: {
title: 'Accounts',
tabAssets: 'Assets',
tabLiabilities: 'Liabilities',
tabExpenses: 'Expense Categories',
tabIncome: 'Income Categories',
addNew: 'New account',
fullName: 'Full path',
currentBalance: 'Current balance',
adjustBalance: 'Calibrate balance',
closeAccount: 'Close account',
empty: 'No accounts in this category. They are auto-created when you record or import transactions.',
addModalTitle: 'New Account',
adjustModalTitle: 'Calibrate Account Balance',
fieldType: 'Account type',
fieldName: 'Account name (English/Chinese)',
fieldNamePlaceholder: 'Digits, hyphens, Chinese supported',
fieldCurrency: 'Currency',
fieldBalance: 'Target balance',
fieldBalancePlaceholder: 'e.g. 10000.00',
fieldDate: 'Effective date',
confirmClose: 'Confirm Close',
confirmCloseDesc: 'Close this account?\n\n%{name}\n\nThis account will be closed in your local ledger.',
btnConfirmClose: 'Close',
closeSuccess: 'Account closed',
closeSuccessDesc: 'Account has been closed in the local ledger.',
closeFail: 'Close failed',
addFail: 'Add failed',
addFailEmpty: 'Account type and name are required.',
invalidRootAccount: 'Account must start with one of the five root account types (Assets, Liabilities, Income, Expenses, Equity)',
openSuccess: 'Account opened',
openSuccessDesc: 'Account opened successfully:\n\n%{name}',
openFail: 'Open failed',
adjustFail: 'Calibrate failed',
adjustFailEmpty: 'Balance value cannot be empty.',
adjustSuccess: 'Balance adjusted',
adjustSuccessDesc: 'Account %{name} balance calibrated to %{balance} %{currency}.',
billingPeriod: 'Billing Period',
dueDate: 'Due Date',
statementAmount: 'Statement Amount',
availableCredit: 'Available Credit',
},
recurring: {
title: 'Recurring Transactions',
add: 'Add recurring bill',
frequency: 'Frequency',
freqMonthly: 'Monthly',
freqWeekly: 'Weekly',
freqYearly: 'Yearly',
freqDaily: 'Daily',
nextDue: 'Next due',
flow: 'Flow',
perAmount: 'Per period',
edit: 'Edit',
delete: 'Delete',
empty: 'No recurring bills yet. Tap above to add one.',
editTitle: 'Edit Recurring Bill',
addTitle: 'New Recurring Bill',
fieldName: 'Bill name',
fieldNamePlaceholder: 'e.g. iCloud subscription',
fieldFromAccount: 'Source account (Beancount path)',
fieldToAccount: 'Category account (Beancount path)',
fieldAmount: 'Amount',
fieldFrequency: 'Frequency (daily/weekly/monthly/yearly)',
fieldNextDue: 'Next due date',
deleteTitle: 'Delete Recurring Bill',
deleteConfirm: 'Delete "%{name}"?',
inputError: 'Input error',
inputErrorDesc: 'All fields are required.',
addSuccess: 'Added',
addSuccessDesc: 'Recurring bill "%{name}" created.',
editSuccess: 'Updated',
editSuccessDesc: 'Recurring bill "%{name}" updated.',
},
sync: {
title: 'Cloud Sync & Backup',
cloudTitle: 'Cloud Sync Configuration',
backupTitle: 'Backup & Maintenance',
configWebdav: 'Configure WebDAV',
configGit: 'Configure Git',
webdavSyncNow: 'WebDAV Sync Now',
gitSyncNow: 'Git Sync Now',
iCloudSync: 'iCloud Sync',
iCloudUnavailable: 'iCloud sync is iOS-only and unavailable on this platform',
backupNow: 'Backup Now',
restoreNow: 'Restore from Backup',
loadLocalLedger: 'Load local .bean ledger',
maintenance: 'Run maintenance & cleanup',
webdavUrl: 'WebDAV URL',
username: 'Username',
password: 'Password',
remotePath: 'Remote Path',
branch: 'Branch',
passwordToken: 'Password/Token',
webdavConfigTitle: 'WebDAV Configuration',
gitConfigTitle: 'Git Repository Configuration',
hint: 'Hint',
webdavNotConfigured: 'Please configure WebDAV first',
syncSuccess: 'Sync successful',
syncFail: 'Sync failed: %{error}',
webdvResult: 'Cloud sync complete (WebDAV: %{action})',
gitResult: 'Cloud sync complete (Git: %{action})',
gitConflict: 'Sync complete, %{count} conflicts detected — please review manually',
gitNotConfigured: 'Please configure Git remote URL first',
maintenanceTitle: 'Deep Maintenance',
maintenanceConfirm: 'Will scan and clean cache and orphan files. Continue?',
iCloudResult: 'iCloud sync complete: %{action}',
demoMode: 'Demo Mode',
gitDemoDesc: 'Git sync currently uses simulated data. Real implementation requires isomorphic-git (planned). Run demo?',
iCloudDemoDesc: 'iCloud sync currently uses simulated data. Run demo?',
backupSuccessFolder: 'Backup file saved successfully to selected folder!',
restoreSuccessParts: 'Restore successful! Restored: %{parts}',
restorePartsTx: 'Transactions',
restorePartsSettings: 'User settings',
restorePartsMeta: 'Categories/Rules/Budgets',
maintenanceDemoDesc: 'Maintenance cleanup currently uses simulated data. Run demo?',
runDemo: 'Run Demo',
demoSyncSuccess: 'Demo complete',
gitDemoContent: '2026-07-14 * "Git pull merge"\n Expenses:Food 15.00 CNY\n Assets:Alipay -15.00 CNY',
iCloudDemoContent: '2026-07-14 * "iCloud pull merge"\n Expenses:Shopping 88.00 CNY\n Assets:Alipay -88.00 CNY',
backupShareTitle: 'Share ledger backup',
backupSuccess: 'Backup successful',
backupSuccessDesc: 'Backup written to: %{path}',
backupFail: 'Backup failed',
restoreSuccess: 'Restore successful',
restoreDesc: 'Ledger restored from %{name}',
restoreFail: 'Restore failed',
loadSuccess: 'Loaded successfully',
loadDesc: 'Loaded local ledger: %{name}',
loadFail: 'Load failed',
maintenanceResult: 'Cleaned %{count} orphan files\nFreed %{size} bytes',
optimizeFail: 'Optimization failed',
exportTitle: 'Export & Share',
exportExcel: 'Export to Excel',
exportRules: 'Export rules',
importRules: 'Import rules',
exportSuccess: 'Exported',
exportFail: 'Export failed',
importRulesSuccess: 'Imported %{count} new rules',
},
errors: {
ledgerNotLoaded: 'Ledger not loaded, call loadLedger first',
},
ocr: {
scanning: 'Scanning receipt...',
notReady: 'OCR engine not ready, please try again later',
success: 'Recognized! Please verify the details',
failed: 'Could not recognize the receipt, please enter manually',
},
ai: {
chatTitle: 'AI Assistant',
welcome: 'Hi! I am your AI bookkeeping assistant. Try "spent 35 at Starbucks yesterday" to record a transaction, or just chat with me.',
inputPlaceholder: 'Type a message or expense description...',
thinking: 'AI thinking...',
notConfigured: 'AI is not configured. Go to Settings → AI Settings to enable and configure your API key.',
error: 'Sorry, something went wrong. Please try again.',
billDetected: 'Bill detected:',
tapToRecord: 'Tap to record',
},
remark: {
title: 'Remark Templates',
add: 'Add Template',
editTitle: 'Edit Template',
fieldName: 'Template name',
fieldTemplate: 'Template (${counterparty} ${time} placeholders)',
empty: 'No templates',
deleteTitle: 'Delete Template',
deleteConfirm: 'Delete "%{name}"?',
},
automation: {
title: 'Auto Bookkeeping',
detectedEvents: 'Detected Events (%{count})',
drafts: 'Pending Drafts (%{count})',
process: 'Process All',
noEvents: 'No detected events',
noDrafts: 'No pending drafts',
confirmDraft: 'Confirm',
rejectDraft: 'Reject',
sourceNotification: 'Notification',
sourceSms: 'SMS',
sourceScreenshot: 'Screenshot',
sourceOcr: 'OCR',
sourceManual: 'Manual',
channelStats: 'Source Stats',
confirmed: 'Saved successfully',
screenshotStart: 'Start Screenshot Monitor',
screenshotStop: 'Stop Screenshot Monitor',
screenshotUnavailable: 'Screenshot monitor module unavailable',
accessibilityTitle: 'Accessibility Bill Recognition',
serviceRunning: 'Service Enabled',
serviceStopped: 'Service Not Enabled',
openSettings: 'Open System Settings',
openSettingsFail: 'Cannot open system settings',
rememberPage: 'Remember Current Page',
rememberPageSuccess: 'Page Remembered',
rememberPageDesc: 'Will auto-capture and recognize %{pkg} (%{activity}) on content change',
rememberPageFail: 'Failed to remember page',
manualOcr: 'Manual OCR',
manualOcrTriggered: 'Recognition triggered, please wait',
manualOcrFail: 'Failed to trigger recognition',
whitelistTitle: 'Payment App Whitelist (auto-recognition only for these apps)',
whitelistEmpty: 'No whitelist',
rememberedPages: 'Remembered Pages (%{count})',
clearAll: 'Clear All',
clearPagesTitle: 'Clear Remembered Pages',
clearPagesConfirm: 'Clear all remembered pages? Auto-recognition will stop.',
bridgeUnavailable: 'Accessibility bridge unavailable (rebuild APK required)',
currentApp: 'Current App',
},
lockScreen: {
locked: 'App Locked',
bioPrompt: 'Please authenticate with biometrics to unlock',
authFail: 'Authentication failed, please enter passcode',
authenticating: 'Authenticating...',
clickUnlock: 'Unlock',
usePin: 'Use PIN Code',
enterPin: 'Enter Passcode',
pinFail: 'Incorrect passcode, try again',
useBio: 'Use Biometrics',
clear: 'Clear',
},
error: {
retry: 'Retry',
clearCache: 'Clear Cache & Restart',
exportLogs: 'Export Logs',
subTitle: 'If the issue persists, please export logs and submit to GitHub Issues',
noLogs: 'Notice',
noLogsMsg: 'No crash logs recorded',
exportFail: 'Export failed',
},
speedDial: {
expandHint: 'Expand quick actions',
},
};