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}",