Allow Exporting transaction history to a CSV file (#3230)

* Allow Exporting transaction history to a CSV file * fix: improve safety for share dialog bounds and navigation pop

Omar Hatem committed May 18, 2026 at 16:53 UTC 4125c5fe802d32863c7dac7f963ea8a51a3926db
38 files changed +538 -48
lib/core/csv_export_service.dart new
+317
@@ -0,0 +1,317 @@
1 +import 'dart:io';
2 +
3 +import 'package:cake_wallet/generated/i18n.dart';
4 +import 'package:cake_wallet/src/screens/backup/backup_page.dart';
5 +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
6 +import 'package:cake_wallet/utils/share_util.dart';
7 +import 'package:cake_wallet/utils/show_bar.dart';
8 +import 'package:cake_wallet/utils/show_pop_up.dart';
9 +import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
10 +import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
11 +import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
12 +import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
13 +import 'package:cake_wallet/view_model/dashboard/payjoin_transaction_list_item.dart';
14 +import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
15 +import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
16 +import 'package:cw_core/transaction_direction.dart';
17 +import 'package:file_picker/file_picker.dart';
18 +import 'package:flutter/material.dart';
19 +import 'package:intl/intl.dart';
20 +import 'package:path_provider/path_provider.dart';
21 +
22 +class CsvExportService {
23 + static const _columns = [
24 + 'record_type',
25 + 'date_time',
26 + 'type',
27 + 'amount',
28 + 'currency',
29 + 'fee',
30 + 'tx_id',
31 + 'address',
32 + 'status',
33 + 'note',
34 + 'from_amount',
35 + 'from_currency',
36 + 'to_amount',
37 + 'to_currency',
38 + 'trade_id',
39 + 'provider',
40 + 'confirmations',
41 + ];
42 +
43 + static const _utf8Bom = '';
44 +
45 + String buildCsvContent(List<ActionListItem> items) {
46 + final buf = StringBuffer();
47 + buf.write(_utf8Bom);
48 + buf.writeln(_columns.join(','));
49 +
50 + for (final item in items) {
51 + if (item is DateSectionItem) continue;
52 +
53 + final row = _buildRow(item);
54 + if (row != null) buf.writeln(row);
55 + }
56 +
57 + return buf.toString();
58 + }
59 +
60 + String? _buildRow(ActionListItem item) {
61 + if (item is TransactionListItem) return _transactionRow(item);
62 + if (item is TradeListItem) return _tradeRow(item);
63 + if (item is OrderListItem) return _orderRow(item);
64 + if (item is AnonpayTransactionListItem) return _anonpayRow(item);
65 + if (item is PayjoinTransactionListItem) return _payjoinRow(item);
66 + return null;
67 + }
68 +
69 + String _transactionRow(TransactionListItem item) {
70 + final tx = item.transaction;
71 + final type = tx.direction == TransactionDirection.incoming ? 'incoming' : 'outgoing';
72 + final status = tx.isPending ? 'pending' : 'confirmed';
73 +
74 + // Prefer tx.to/tx.from; fall back to address lists for chains that don't populate them.
75 + final address = tx.direction == TransactionDirection.incoming
76 + ? (tx.from?.isNotEmpty == true
77 + ? tx.from!
78 + : (tx.inputAddresses?.firstOrNull ?? ''))
79 + : (tx.to?.isNotEmpty == true
80 + ? tx.to!
81 + : (tx.outputAddresses?.firstOrNull ?? ''));
82 +
83 + // Split "0.2769 LTC" → amount="0.2769", currency="LTC"
84 + final (amount, currency) = _splitAmountCurrency(tx.amountFormatted());
85 +
86 + // fee is stored as (inputAmount - totalOutputAmount) which is negative for incoming
87 + // transactions — only include it when the raw value is a sensible positive fee.
88 + final fee = tx.fee != null && tx.fee! > 0 ? (tx.feeFormatted() ?? '') : '';
89 + final (feeAmount, _) = fee.isNotEmpty ? _splitAmountCurrency(fee) : ('', '');
90 +
91 + return _row([
92 + 'transaction',
93 + _isoDate(tx.date),
94 + type,
95 + amount,
96 + currency,
97 + feeAmount,
98 + tx.id,
99 + address,
100 + status,
101 + '',
102 + '',
103 + '',
104 + '',
105 + '',
106 + '',
107 + '',
108 + tx.confirmations.toString(),
109 + ]);
110 + }
111 +
112 + /// Splits a formatted amount like "0.2769 LTC" into ("0.2769", "LTC").
113 + /// If there is no trailing symbol the whole string is returned as the amount.
114 + (String, String) _splitAmountCurrency(String formatted) {
115 + final trimmed = formatted.trim();
116 + final idx = trimmed.lastIndexOf(' ');
117 + if (idx == -1) return (trimmed, '');
118 + return (trimmed.substring(0, idx), trimmed.substring(idx + 1));
119 + }
120 +
121 + String _tradeRow(TradeListItem item) {
122 + final trade = item.trade;
123 + return _row([
124 + 'trade',
125 + _isoDate(trade.createdAt ?? DateTime.fromMillisecondsSinceEpoch(0, isUtc: true)),
126 + 'swap',
127 + '',
128 + '',
129 + trade.fee?.toString() ?? '',
130 + trade.outputTransaction ?? '',
131 + trade.payoutAddress ?? '',
132 + trade.state.title,
133 + trade.memo ?? '',
134 + trade.amount,
135 + trade.from?.name ?? '',
136 + trade.receiveAmount ?? '',
137 + trade.to?.name ?? '',
138 + trade.id,
139 + trade.provider.title,
140 + '',
141 + ]);
142 + }
143 +
144 + String _orderRow(OrderListItem item) {
145 + final order = item.order;
146 + return _row([
147 + 'order',
148 + _isoDate(order.createdAt),
149 + order.source.title,
150 + order.amountFormatted(),
151 + order.from ?? '',
152 + '',
153 + order.transferId,
154 + '',
155 + order.state.title,
156 + '',
157 + order.amountFormatted(),
158 + order.from ?? '',
159 + order.receiveAmount ?? '',
160 + order.to ?? '',
161 + order.id,
162 + order.providerTitle,
163 + '',
164 + ]);
165 + }
166 +
167 + String _anonpayRow(AnonpayTransactionListItem item) {
168 + final tx = item.transaction;
169 + final amount = tx.fiatAmount?.toString() ?? tx.amountTo?.toString() ?? '';
170 + final currency = tx.fiatEquiv ?? tx.coinTo;
171 +
172 + return _row([
173 + 'anonpay',
174 + _isoDate(tx.createdAt),
175 + 'anonymous_payment',
176 + amount,
177 + currency,
178 + '',
179 + tx.invoiceId,
180 + tx.address,
181 + tx.status,
182 + '',
183 + '',
184 + '',
185 + '',
186 + '',
187 + '',
188 + tx.provider,
189 + '',
190 + ]);
191 + }
192 +
193 + String _payjoinRow(PayjoinTransactionListItem item) {
194 + final session = item.session;
195 + final type = session.isSenderSession ? 'send' : 'receive';
196 + final amount = session.rawAmount ?? '0';
197 +
198 + return _row([
199 + 'payjoin',
200 + _isoDate(session.inProgressSince ?? DateTime.fromMillisecondsSinceEpoch(0, isUtc: true)),
201 + type,
202 + amount,
203 + 'BTC',
204 + '',
205 + session.txId ?? '',
206 + '',
207 + item.status,
208 + '',
209 + '',
210 + '',
211 + '',
212 + '',
213 + '',
214 + '',
215 + '',
216 + ]);
217 + }
218 +
219 + String _row(List<String> fields) => fields.map(escapeField).join(',');
220 +
221 + String escapeField(String field) {
222 + if (field.contains(',') || field.contains('"') || field.contains('\n')) {
223 + return '"${field.replaceAll('"', '""')}"';
224 + }
225 + return field;
226 + }
227 +
228 + String _isoDate(DateTime dt) => dt.toUtc().toIso8601String();
229 +
230 + Future<void> exportToCsv(List<ActionListItem> items, BuildContext context) async {
231 + final dataItems = items.whereType<ActionListItem>().where((e) => e is! DateSectionItem).toList();
232 +
233 + if (dataItems.isEmpty) {
234 + await showBar<void>(context, S.current.csv_nothing_to_export);
235 + return;
236 + }
237 +
238 + final now = DateTime.now();
239 + final fileName = 'cake_wallet_export_${DateFormat('yyyyMMdd_HHmmss').format(now)}.csv';
240 +
241 + late File csvFile;
242 +
243 + final exportFuture = Future(() async {
244 + final content = buildCsvContent(items);
245 + csvFile = await _writeTempFile(fileName, content);
246 + });
247 +
248 + showPersistentActionOverlay(context, exportFuture, text: S.current.generating_csv);
249 + await exportFuture;
250 +
251 + if (!context.mounted) return;
252 +
253 + if (Platform.isAndroid) {
254 + _showAndroidExportDialog(context, csvFile, fileName);
255 + } else if (Platform.isIOS) {
256 + await _shareFile(csvFile, fileName, context);
257 + } else {
258 + await _saveFileDesktop(csvFile, fileName);
259 + }
260 + }
261 +
262 + void _showAndroidExportDialog(BuildContext context, File csvFile, String fileName) {
263 + showPopUp<void>(
264 + context: context,
265 + builder: (dialogContext) {
266 + return AlertWithTwoActions(
267 + alertTitle: S.current.export_csv,
268 + alertContent: S.current.select_destination,
269 + rightButtonText: S.current.save_to_downloads,
270 + leftButtonText: S.current.share,
271 + actionRightButton: () async {
272 + await _saveToDownloads(fileName, csvFile);
273 + Navigator.of(dialogContext).pop();
274 + await showBar<void>(context, S.current.file_saved);
275 + await csvFile.delete();
276 + },
277 + actionLeftButton: () async {
278 + Navigator.of(dialogContext).pop();
279 + await _shareFile(csvFile, fileName, context);
280 + },
281 + );
282 + },
283 + );
284 + }
285 +
286 + Future<void> _shareFile(File file, String fileName, BuildContext context) async {
287 + await ShareUtil.shareFile(filePath: file.path, fileName: fileName, context: context);
288 + if (await file.exists()) await file.delete();
289 + }
290 +
291 + Future<void> _saveFileDesktop(File csvFile, String fileName) async {
292 + final outputPath = await FilePicker.platform.saveFile(
293 + dialogTitle: 'Save CSV export',
294 + fileName: fileName,
295 + lockParentWindow: true,
296 + );
297 + if (outputPath == null) return;
298 + await csvFile.copy(outputPath);
299 + await csvFile.delete();
300 + }
301 +
302 + Future<File> _writeTempFile(String fileName, String content) async {
303 + final dir = await getApplicationDocumentsDirectory();
304 + final file = File('${dir.path}/$fileName');
305 + if (file.existsSync()) file.deleteSync();
306 + await file.writeAsString(content, flush: true);
307 + return file;
308 + }
309 +
310 + Future<void> _saveToDownloads(String fileName, File file) async {
311 + if (!Platform.isAndroid) return;
312 + const downloadsPath = '/storage/emulated/0/Download';
313 + final dest = File('$downloadsPath/$fileName');
314 + if (dest.existsSync()) dest.deleteSync();
315 + await file.copy(dest.path);
316 + }
317 +}
lib/new-ui/pages/send_page.dart
+1 -1
@@ -763,7 +763,7 @@ class _NewSendPageState extends State<NewSendPage> {
763 conditionToDetermineIfToUse2FA: check,
764 onAuthSuccess: (value) async {
765 if (value) {
766 - if (!widget.mode.popOnConfirmation) {
766 + if (!widget.mode.popOnConfirmation && Navigator.canPop(context)) {
767 Navigator.of(context, rootNavigator: true).pop();
768 }
769 showModalBottomSheet(
lib/new-ui/widgets/coins_page/assets_history/assets_top_bar.dart
+52 -22
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/core/csv_export_service.dart';
2 import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/new-ui/widgets/line_tab_switcher.dart';
4 import 'package:cake_wallet/routes.dart';
@@ -24,6 +25,9 @@ class AssetsTopBar extends StatelessWidget {
25 Widget build(BuildContext context) {
26 final settingsButtonText = _getSettingsButtonText();
27 final hasTokenSettingsButton = settingsButtonText != null;
28 + // Reuse the exact same detection logic as _getSettingsButtonText so the
29 + // export button always matches whether the Filters button is visible.
30 + final isHistoryTab = settingsButtonText == S.current.filters;
31
32 return SliverToBoxAdapter(
33 child: Padding(
@@ -39,8 +43,33 @@ class AssetsTopBar extends StatelessWidget {
43 selectedTab: selectedTab,
44 )
45 else SizedBox.shrink(),
42 - Opacity(
43 - opacity: hasTokenSettingsButton ? 1 : 0,
46 + Row(
47 + spacing: 8,
48 + children: [
49 + if (isHistoryTab)
50 + GestureDetector(
51 + onTap: () =>
52 + CsvExportService().exportToCsv(dashboardViewModel.items, context),
53 + child: Semantics(
54 + label: S.of(context).export_csv,
55 + button: true,
56 + child: Container(
57 + height: 36,
58 + width: 36,
59 + decoration: BoxDecoration(
60 + shape: BoxShape.circle,
61 + color: Theme.of(context).colorScheme.surfaceContainer,
62 + ),
63 + child: Icon(
64 + Icons.file_download_outlined,
65 + color: Theme.of(context).colorScheme.onSurface,
66 + size: 20,
67 + ),
68 + ),
69 + ),
70 + ),
71 + Opacity(
72 + opacity: hasTokenSettingsButton ? 1 : 0,
73 child: ElevatedButton(
74 onPressed: () {
75 if (tabs[selectedTab] == S.of(context).assets) {
@@ -48,40 +77,41 @@ class AssetsTopBar extends StatelessWidget {
77 Routes.homeSettings,
78 arguments: dashboardViewModel.balanceViewModel,
79 );
51 - } else if(tabs[selectedTab] == S.of(context).history) {
80 + } else if (tabs[selectedTab] == S.of(context).history) {
81 showPopUp<void>(
82 context: context,
54 - builder: (context) => FilterWidget(filterItems: dashboardViewModel.filterItems),
83 + builder: (context) =>
84 + FilterWidget(filterItems: dashboardViewModel.filterItems),
85 );
86 }
87 },
88 style: ElevatedButton.styleFrom(
89 padding: EdgeInsets.zero,
60 - shape: RoundedRectangleBorder(
61 - borderRadius: BorderRadius.circular(999999),
62 - ),
63 - backgroundColor: Theme.of(
64 - context,
65 - ).colorScheme.surfaceContainer,
66 - ),
67 - child: Padding(
68 - padding: const EdgeInsets.symmetric(horizontal: 12.0),
69 - child: Row(
70 - spacing: 6,
71 - children: [
90 + shape: RoundedRectangleBorder(
91 + borderRadius: BorderRadius.circular(999999),
92 + ),
93 + backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
94 + ),
95 + child: Padding(
96 + padding: const EdgeInsets.symmetric(horizontal: 12.0),
97 + child: Row(
98 + spacing: 6,
99 + children: [
100 CakeImageWidget(
101 imageUrl: "assets/new-ui/options_slider.svg",
102 colorFilter: ColorFilter.mode(
103 Theme.of(context).colorScheme.primary, BlendMode.srcIn)),
104 Text(
77 - settingsButtonText??"",
78 - style: TextStyle(color: Theme.of(context).colorScheme.primary),
79 - )
80 - ],
81 - ),
82 - ),
105 + settingsButtonText ?? "",
106 + style: TextStyle(color: Theme.of(context).colorScheme.primary),
107 + )
108 + ],
109 ),
110 + ),
111 ),
112 + ),
113 + ],
114 + ),
115 ],
116 ),
117 ),
lib/src/screens/dashboard/pages/transactions_page.dart
+3
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cake_wallet/core/csv_export_service.dart';
3 import 'package:cake_wallet/entities/balance_display_mode.dart';
4 import 'package:cake_wallet/order/order_source_description.dart';
5 import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
@@ -65,6 +66,8 @@ class TransactionsPage extends StatelessWidget {
66 }),
67 HeaderRow(
68 dashboardViewModel: dashboardViewModel,
69 + onExportCsv: () =>
70 + CsvExportService().exportToCsv(dashboardViewModel.items, context),
71 key: ValueKey('transactions_page_header_row_key'),
72 ),
73 Expanded(
lib/src/screens/dashboard/widgets/header_row.dart
+55 -23
@@ -5,9 +5,10 @@ import 'package:cake_wallet/generated/i18n.dart';
5 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
6
7 class HeaderRow extends StatelessWidget {
8 - HeaderRow({required this.dashboardViewModel, super.key});
8 + HeaderRow({required this.dashboardViewModel, this.onExportCsv, super.key});
9
10 final DashboardViewModel dashboardViewModel;
11 + final VoidCallback? onExportCsv;
12
13 @override
14 Widget build(BuildContext context) {
@@ -28,31 +29,62 @@ class HeaderRow extends StatelessWidget {
29 fontWeight: FontWeight.w500,
30 color: Theme.of(context).colorScheme.onSurface),
31 ),
31 - Semantics(
32 - container: true,
33 - child: GestureDetector(
34 - key: ValueKey('transactions_page_header_row_transaction_filter_button_key'),
35 - onTap: () {
36 - showPopUp<void>(
37 - context: context,
38 - builder: (context) => FilterWidget(filterItems: dashboardViewModel.filterItems),
39 - );
40 - },
41 - child: Semantics(
42 - label: 'Transaction Filter',
43 - button: true,
44 - enabled: true,
45 - child: Container(
46 - height: 36,
47 - width: 36,
48 - decoration: BoxDecoration(
49 - shape: BoxShape.circle,
50 - color: Theme.of(context).colorScheme.surfaceContainer,
32 + Row(
33 + children: [
34 + if (onExportCsv != null)
35 + Padding(
36 + padding: const EdgeInsets.only(right: 8),
37 + child: GestureDetector(
38 + key: ValueKey('transactions_page_header_row_export_csv_button_key'),
39 + onTap: onExportCsv,
40 + child: Semantics(
41 + label: S.of(context).export_csv,
42 + button: true,
43 + enabled: true,
44 + child: Container(
45 + height: 36,
46 + width: 36,
47 + decoration: BoxDecoration(
48 + shape: BoxShape.circle,
49 + color: Theme.of(context).colorScheme.surfaceContainer,
50 + ),
51 + child: Icon(
52 + Icons.file_download_outlined,
53 + color: Theme.of(context).colorScheme.onSurface,
54 + size: 20,
55 + ),
56 + ),
57 + ),
58 + ),
59 + ),
60 + Semantics(
61 + container: true,
62 + child: GestureDetector(
63 + key: ValueKey('transactions_page_header_row_transaction_filter_button_key'),
64 + onTap: () {
65 + showPopUp<void>(
66 + context: context,
67 + builder: (context) =>
68 + FilterWidget(filterItems: dashboardViewModel.filterItems),
69 + );
70 + },
71 + child: Semantics(
72 + label: 'Transaction Filter',
73 + button: true,
74 + enabled: true,
75 + child: Container(
76 + height: 36,
77 + width: 36,
78 + decoration: BoxDecoration(
79 + shape: BoxShape.circle,
80 + color: Theme.of(context).colorScheme.surfaceContainer,
81 + ),
82 + child: filterIcon,
83 + ),
84 ),
52 - child: filterIcon,
85 ),
86 ),
55 - ),
87 + ],
88 )
89 ],
90 ),
lib/utils/share_util.dart
+14 -2
@@ -33,8 +33,20 @@ class ShareUtil {
33 return Rect.zero;
34 }
35
36 - final box = context.findRenderObject() as RenderBox?;
36 + final renderObject = context.findRenderObject();
37 + if (renderObject is! RenderBox) {
38 + return Rect.zero;
39 + }
40
38 - return box!.localToGlobal(Offset.zero) & box.size;
41 + final rect = renderObject.localToGlobal(Offset.zero) & renderObject.size;
42 + // iOS requires sharePositionOrigin to lie fully within the source view's
43 + // bounds; clip to the screen so an offset/oversized page rect doesn't spill
44 + // past the edge and trigger a PlatformException on iPad.
45 + final screen = Offset.zero & MediaQuery.of(context).size;
46 + final clipped = rect.intersect(screen);
47 + if (clipped.isEmpty) {
48 + return Rect.zero;
49 + }
50 + return clipped;
51 }
52 }
res/values/strings_ar.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "ينتهي",
454 "expiresOn": "تنتهي في",
455 "expiry_and_validity": "تاريخ الانتهاء والصلاحية",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "تصدير النسخة الاحتياطية",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "تصدير سجلات Lightning",
460 "export_logs": "تصدير السجلات",
461 "export_outputs": "تصدير المخرجات",
462 "export_payjoin_logs": "تصدير سجلات Payjoin",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "تمديد الجلسة",
465 "external": "خارجي",
466 "external_wallet": "محفظة خارجية",
res/values/strings_bg.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Изтича",
454 "expiresOn": "Изтича на",
455 "expiry_and_validity": "Срок на валидност и валидност",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Експортиране на резервно копие",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Експортиране на Lightning логове",
460 "export_logs": "Експортиране на логове",
461 "export_outputs": "Експортиране на изходи",
462 "export_payjoin_logs": "Експортиране на Payjoin логове",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Удължи сесията",
465 "external": "Външен",
466 "external_wallet": "Външен портфейл",
res/values/strings_cs.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Vyprší",
454 "expiresOn": "Vyprší dne",
455 "expiry_and_validity": "Vypršení platnosti",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Exportovat zálohu",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Exportovat protokoly Lightning",
460 "export_logs": "Exportovat protokoly",
461 "export_outputs": "Exportovat výstupy",
462 "export_payjoin_logs": "Exportovat protokoly Payjoin",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Prodloužit relaci",
465 "external": "Externí",
466 "external_wallet": "Externí peněženka",
res/values/strings_de.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Läuft ab",
454 "expiresOn": "Läuft ab am",
455 "expiry_and_validity": "Ablauf und Gültigkeit",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Backup exportieren",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Lightning-Logs exportieren",
460 "export_logs": "Protokolle exportieren",
461 "export_outputs": "Outputs exportieren",
462 "export_payjoin_logs": "Payjoin-Logs exportieren",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Sitzung verlängern",
465 "external": "Extern",
466 "external_wallet": "Externe Wallet",
res/values/strings_en.arb
+3
@@ -455,11 +455,14 @@
455 "expires": "Expires",
456 "expiresOn": "Expires on",
457 "expiry_and_validity": "Expiry and Validity",
458 + "csv_nothing_to_export": "Nothing to export",
459 "export_backup": "Export backup",
460 + "export_csv": "Export CSV",
461 "export_lightning_logs": "Export Lightning logs",
462 "export_logs": "Export logs",
463 "export_outputs": "Export outputs",
464 "export_payjoin_logs": "Export Payjoin logs",
465 + "generating_csv": "Generating CSV...",
466 "extend_session": "Extend Session",
467 "external": "External",
468 "external_wallet": "External wallet",
res/values/strings_es.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Caduca",
454 "expiresOn": "Caduca el",
455 "expiry_and_validity": "Caducidad y validez",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Exportar copia de seguridad",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Exportar registros de Lightning",
460 "export_logs": "Exportar registros",
461 "export_outputs": "Exportar salidas",
462 "export_payjoin_logs": "Exportar registros de Payjoin",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Extender sesión",
465 "external": "Externo",
466 "external_wallet": "Billetera externa",
res/values/strings_fa.arb
+3
@@ -451,11 +451,14 @@
451 "expires": "منقضی می‌شود",
452 "expiresOn": "انقضا در",
453 "expiry_and_validity": "انقضا و اعتبار",
454 + "csv_nothing_to_export": "Nothing to export",
455 "export_backup": "صدور نسخه پشتیبان",
456 + "export_csv": "Export CSV",
457 "export_lightning_logs": "خروجی گرفتن از گزارش‌های لایتنینگ",
458 "export_logs": "خروجی گرفتن از گزارش‌ها",
459 "export_outputs": "برون‌برد خروجی‌ها",
460 "export_payjoin_logs": "گزارش‌های Payjoin را صادر کنید",
461 + "generating_csv": "Generating CSV...",
462 "extend_session": "تمدید جلسه",
463 "external": "خارجی",
464 "external_wallet": "کیف پول خارجی",
res/values/strings_fr.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Expire",
454 "expiresOn": "Expire le",
455 "expiry_and_validity": "Expiration et validité",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Exporter la sauvegarde",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Exporter les logs Lightning",
460 "export_logs": "Exporter les journaux",
461 "export_outputs": "Exporter les sorties",
462 "export_payjoin_logs": "Exporter les journaux de Payjoin",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Prolonger la session",
465 "external": "Externe",
466 "external_wallet": "Portefeuille externe",
res/values/strings_gn.arb
+3
@@ -355,9 +355,12 @@
355 "expires": "Opa hag̃ua",
356 "expiresOn": "Opa hag̃ua",
357 "expiry_and_validity": "Opaha ára ha hekorã",
358 + "csv_nothing_to_export": "Nothing to export",
359 "export_backup": "Emondo hag̃ua backup",
360 + "export_csv": "Export CSV",
361 "export_lightning_logs": "Emondo Lightning rembiapokue",
362 "export_payjoin_logs": "Emondo okápe Payjoin registro-kuéra",
363 + "generating_csv": "Generating CSV...",
364 "external": "Okápegua",
365 "external_wallet": "Billetera externa",
366 "extra_id": "ID extra:",
res/values/strings_ha.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Yana ƙarewa",
454 "expiresOn": "Yana ƙarewa a",
455 "expiry_and_validity": "Ranar ƙarewa da inganci",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Fitar da ajiyar baya",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Fitar da log ɗin Lightning",
460 "export_logs": "Fitar da log",
461 "export_outputs": "Fitar da abubuwan fitarwa",
462 "export_payjoin_logs": "Fitar da log ɗin Payjoin",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Tsawaita Zama",
465 "external": "Na waje",
466 "external_wallet": "Wallet na waje",
res/values/strings_hi.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "समाप्त होता है",
454 "expiresOn": "इस दिन समाप्त होता है",
455 "expiry_and_validity": "समाप्ति और वैधता",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "बैकअप निर्यात करें",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "लाइटनिंग लॉग्स निर्यात करें",
460 "export_logs": "लॉग निर्यात करें",
461 "export_outputs": "आउटपुट निर्यात करें",
462 "export_payjoin_logs": "Payjoin लॉग एक्सपोर्ट करें",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "सत्र बढ़ाएँ",
465 "external": "बाहरी",
466 "external_wallet": "बाहरी वॉलेट",
res/values/strings_hr.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Ističe",
454 "expiresOn": "Istječe",
455 "expiry_and_validity": "Istek i valjanost",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Izvezi sigurnosnu kopiju",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Izvezi Lightning zapise",
460 "export_logs": "Izvezi zapise",
461 "export_outputs": "Izvoz izlaza",
462 "export_payjoin_logs": "Izvezi Payjoin zapise",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Produži sesiju",
465 "external": "Vanjski",
466 "external_wallet": "Vanjski novčanik",
res/values/strings_hy.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Լրանում է",
454 "expiresOn": "Լրանում է",
455 "expiry_and_validity": "Ժամկետ և վավերականություն",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Արտահանել պահուստային պատճենը",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Արտահանել Lightning-ի տեղեկամատյանները",
460 "export_logs": "Արտահանել տեղեկամատյանները",
461 "export_outputs": "Արտահանել ելքերը",
462 "export_payjoin_logs": "Արտահանել Payjoin-ի մատյանները",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Երկարացնել սեսիան",
465 "external": "Արտաքին",
466 "external_wallet": "Արտաքին դրամապանակ",
res/values/strings_id.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Kedaluwarsa",
454 "expiresOn": "Kedaluwarsa pada",
455 "expiry_and_validity": "Kedaluwarsa dan Validitas",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Ekspor cadangan",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Ekspor log Lightning",
460 "export_logs": "Ekspor log",
461 "export_outputs": "Ekspor keluaran",
462 "export_payjoin_logs": "Ekspor log Payjoin",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Perpanjang Sesi",
465 "external": "Eksternal",
466 "external_wallet": "Dompet eksternal",
res/values/strings_it.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Scade",
454 "expiresOn": "Scade il",
455 "expiry_and_validity": "Scadenza e validità",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Esporta backup",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Esporta i log di Lightning",
460 "export_logs": "Esporta registri",
461 "export_outputs": "Esporta output",
462 "export_payjoin_logs": "Esporta i log Payjoin",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Estendi la sessione",
465 "external": "Esterno",
466 "external_wallet": "Portafoglio esterno",
res/values/strings_ja.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "有効期限",
454 "expiresOn": "有効期限",
455 "expiry_and_validity": "有効期限と有効性",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "バックアップをエクスポート",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Lightningログをエクスポート",
460 "export_logs": "ログをエクスポート",
461 "export_outputs": "アウトプットをエクスポート",
462 "export_payjoin_logs": "Payjoinログをエクスポート",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "セッションを延長",
465 "external": "外部",
466 "external_wallet": "外部ウォレット",
res/values/strings_ko.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "만료됨",
454 "expiresOn": "만료일",
455 "expiry_and_validity": "만료 및 유효 기간",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "백업 내보내기",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Lightning 로그 내보내기",
460 "export_logs": "로그 내보내기",
461 "export_outputs": "출력 내보내기",
462 "export_payjoin_logs": "Payjoin 로그 내보내기",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "세션 연장",
465 "external": "외부",
466 "external_wallet": "외부 지갑",
res/values/strings_my.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "သက်တမ်းကုန်ဆုံးသည်",
454 "expiresOn": "သက်တမ်းကုန်ဆုံးမည့်ရက်",
455 "expiry_and_validity": "သက်တမ်းကုန်ဆုံးရက်နှင့် အကျုံးဝင်မှု",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "အရန်ကူးကို ထုတ်ယူရန်",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Lightning မှတ်တမ်းများကို ထုတ်ယူပါ",
460 "export_logs": "မှတ်တမ်းများကို ထုတ်ယူရန်",
461 "export_outputs": "Outputs များကို ထုတ်ယူရန်",
462 "export_payjoin_logs": "Payjoin မှတ်တမ်းများကို တင်ပို့ပါ",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "စက်ရှင် သက်တမ်းတိုးရန်",
465 "external": "ပြင်ပ",
466 "external_wallet": "ပြင်ပ Wallet",
res/values/strings_nl.arb
+3
@@ -451,11 +451,14 @@
451 "expires": "Verloopt",
452 "expiresOn": "Verloopt op",
453 "expiry_and_validity": "Vervaldatum en geldigheid",
454 + "csv_nothing_to_export": "Nothing to export",
455 "export_backup": "Back-up exporteren",
456 + "export_csv": "Export CSV",
457 "export_lightning_logs": "Lightning-logboeken exporteren",
458 "export_logs": "Exporteer logboeken",
459 "export_outputs": "Outputs exporteren",
460 "export_payjoin_logs": "Payjoin-logboeken exporteren",
461 + "generating_csv": "Generating CSV...",
462 "extend_session": "Sessie verlengen",
463 "external": "Extern",
464 "external_wallet": "Externe wallet",
res/values/strings_pl.arb
+3
@@ -452,11 +452,14 @@
452 "expires": "Wygasa",
453 "expiresOn": "Wygasa",
454 "expiry_and_validity": "Termin ważności i ważność",
455 + "csv_nothing_to_export": "Nothing to export",
456 "export_backup": "Eksportuj kopię zapasową",
457 + "export_csv": "Export CSV",
458 "export_lightning_logs": "Eksportuj logi Lightning",
459 "export_logs": "Eksportuj logi",
460 "export_outputs": "Eksportuj wyjścia",
461 "export_payjoin_logs": "Eksportuj logi Payjoin",
462 + "generating_csv": "Generating CSV...",
463 "extend_session": "Przedłuż sesję",
464 "external": "Zewnętrzny",
465 "external_wallet": "Zewnętrzny portfel",
res/values/strings_pt.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Expira",
454 "expiresOn": "Expira em",
455 "expiry_and_validity": "Expiração e validade",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Exportar backup",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Exportar logs do Lightning",
460 "export_logs": "Exportar logs",
461 "export_outputs": "Exportar saídas",
462 "export_payjoin_logs": "Exportar logs do Payjoin",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Estender sessão",
465 "external": "Externo",
466 "external_wallet": "Carteira externa",
res/values/strings_pt_BR.arb
+3
@@ -451,11 +451,14 @@
451 "expires": "Expira",
452 "expiresOn": "Expira em",
453 "expiry_and_validity": "Expiração e Validade",
454 + "csv_nothing_to_export": "Nothing to export",
455 "export_backup": "Exportar backup",
456 + "export_csv": "Export CSV",
457 "export_lightning_logs": "Exportar logs do Lightning",
458 "export_logs": "Exportar logs",
459 "export_outputs": "Exportar saídas",
460 "export_payjoin_logs": "Exportar logs do Payjoin",
461 + "generating_csv": "Generating CSV...",
462 "extend_session": "Estender sessão",
463 "external": "Externo",
464 "external_wallet": "Carteira externa",
res/values/strings_ru.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Истекает",
454 "expiresOn": "Истекает",
455 "expiry_and_validity": "Срок действия и действительность",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Экспорт резервной копии",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Экспорт логов Lightning",
460 "export_logs": "Экспорт журналов",
461 "export_outputs": "Экспорт выходов",
462 "export_payjoin_logs": "Экспорт журналов Payjoin",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Продлить сессию",
465 "external": "Внешний",
466 "external_wallet": "Внешний кошелёк",
res/values/strings_th.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "หมดอายุ",
454 "expiresOn": "หมดอายุวันที่",
455 "expiry_and_validity": "วันหมดอายุและระยะเวลาที่มีผล",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "ส่งออกข้อมูลสำรอง",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "ส่งออกล็อก Lightning",
460 "export_logs": "ส่งออกบันทึก",
461 "export_outputs": "ส่งออกเอาต์พุต",
462 "export_payjoin_logs": "ส่งออกล็อก Payjoin",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "ขยายเซสชัน",
465 "external": "ภายนอก",
466 "external_wallet": "กระเป๋าเงินภายนอก",
res/values/strings_tl.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Mag-e-expire",
454 "expiresOn": "Mag-e-expire sa",
455 "expiry_and_validity": "Pag-expire at Pagiging Balido",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "I-export ang backup",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "I-export ang mga log ng Lightning",
460 "export_logs": "I-export ang mga log",
461 "export_outputs": "I-export ang mga output",
462 "export_payjoin_logs": "I-export ang mga Payjoin log",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Palawigin ang session",
465 "external": "Panlabas",
466 "external_wallet": "Panlabas na wallet",
res/values/strings_tr.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Sona erme tarihi",
454 "expiresOn": "Şu tarihte sona erer",
455 "expiry_and_validity": "Son Kullanma ve Geçerlilik",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Yedeklemeyi dışa aktar",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Lightning günlüklerini dışa aktar",
460 "export_logs": "Günlükleri dışa aktar",
461 "export_outputs": "Çıktıları dışa aktar",
462 "export_payjoin_logs": "Payjoin günlüklerini dışa aktar",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Oturumu Uzat",
465 "external": "Harici",
466 "external_wallet": "Harici cüzdan",
res/values/strings_uk.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "Термін дії закінчується",
454 "expiresOn": "Дійсний до",
455 "expiry_and_validity": "Термін дії та чинність",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "Експортувати резервну копію",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "Експортувати логи Lightning",
460 "export_logs": "Експорт журналів",
461 "export_outputs": "Експорт виходів",
462 "export_payjoin_logs": "Експорт журналів Payjoin",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "Подовжити сеанс",
465 "external": "Зовнішній",
466 "external_wallet": "Зовнішній гаманець",
res/values/strings_ur.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "میعاد ختم ہوتا ہے",
454 "expiresOn": "میعاد ختم ہونے کی تاریخ",
455 "expiry_and_validity": "میعادِ ختم اور درستگی",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "بیک اپ ایکسپورٹ کریں",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "لائٹننگ لاگز برآمد کریں",
460 "export_logs": "لاگز برآمد کریں",
461 "export_outputs": "آؤٹ پٹس ایکسپورٹ کریں",
462 "export_payjoin_logs": "Payjoin لاگز ایکسپورٹ کریں",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "سیشن میں توسیع کریں",
465 "external": "بیرونی",
466 "external_wallet": "بیرونی والیٹ",
res/values/strings_vi.arb
+3
@@ -452,11 +452,14 @@
452 "expires": "Hết hạn",
453 "expiresOn": "Hết hạn vào",
454 "expiry_and_validity": "Thời hạn và hiệu lực",
455 + "csv_nothing_to_export": "Nothing to export",
456 "export_backup": "Xuất bản sao lưu",
457 + "export_csv": "Export CSV",
458 "export_lightning_logs": "Xuất nhật ký Lightning",
459 "export_logs": "Xuất nhật ký",
460 "export_outputs": "Xuất đầu ra",
461 "export_payjoin_logs": "Xuất nhật ký Payjoin",
462 + "generating_csv": "Generating CSV...",
463 "extend_session": "Gia hạn phiên",
464 "external": "Bên ngoài",
465 "external_wallet": "Ví ngoài",
res/values/strings_yo.arb
+3
@@ -454,11 +454,14 @@
454 "expires": "Yóò parí",
455 "expiresOn": "Yóò parí ní",
456 "expiry_and_validity": "Ọjọ́ ìparí àti ìmúlò",
457 + "csv_nothing_to_export": "Nothing to export",
458 "export_backup": "Gbé ẹ̀dà àfipamọ́ jáde",
459 + "export_csv": "Export CSV",
460 "export_lightning_logs": "Gbe àwọn àkọọlẹ Lightning jáde",
461 "export_logs": "Gbe àwọn ìwé-àkọsílẹ̀ jáde",
462 "export_outputs": "Gbe àwọn output jáde",
463 "export_payjoin_logs": "Gbejade àwọn ìkọ̀ọ́lẹ̀ Payjoin",
464 + "generating_csv": "Generating CSV...",
465 "extend_session": "Fa igba iṣẹ́ pọ̀ síi",
466 "external": "Ita",
467 "external_wallet": "Apamọwọ ita",
res/values/strings_zh.arb
+3
@@ -453,11 +453,14 @@
453 "expires": "到期",
454 "expiresOn": "到期于",
455 "expiry_and_validity": "有效期和有效性",
456 + "csv_nothing_to_export": "Nothing to export",
457 "export_backup": "导出备份",
458 + "export_csv": "Export CSV",
459 "export_lightning_logs": "导出 Lightning 日志",
460 "export_logs": "导出日志",
461 "export_outputs": "导出输出",
462 "export_payjoin_logs": "导出 Payjoin 日志",
463 + "generating_csv": "Generating CSV...",
464 "extend_session": "延长会话",
465 "external": "外部",
466 "external_wallet": "外部钱包",
res/values/strings_zh_tw.arb
+3
@@ -392,11 +392,14 @@
392 "expires": "到期",
393 "expiresOn": "到期日",
394 "expiry_and_validity": "到期與有效性",
395 + "csv_nothing_to_export": "Nothing to export",
396 "export_backup": "匯出備份",
397 + "export_csv": "Export CSV",
398 "export_lightning_logs": "匯出 Lightning 記錄",
399 "export_logs": "匯出記錄",
400 "export_outputs": "匯出輸出項",
401 "export_payjoin_logs": "匯出 Payjoin 日誌",
402 + "generating_csv": "Generating CSV...",
403 "extend_session": "延長登入階段",
404 "extra_id": "額外 ID:",
405 "extracted_address_content": "您將發送資金至\n${recipient_name}",