sync status eta (#1701)

* sync status eta * other necessary changes * feat: Enhance Sync ETA display mode - Enhance block rate calculation with weighted averages(accounts for network flunctuations and sync speed of more recent blocks while calculating eta, basically giving us more accuracy) - Add option to switch between ETA and blocks remaining display in display settings - Add placeholder ETA while gathering block data * chore: Listen to smaller changes in eta --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> Co-authored-by: Blazebrain <davidadegoke16@gmail.com>

Matthew Fosse committed Aug 19, 2025 at 08:09 UTC c8dca29f68b690de3c579e606cc7863c76254e0d
36 files changed +361 -8
cw_core/lib/sync_status.dart
+160 -3
@@ -1,6 +1,10 @@
1 abstract class SyncStatus {
2 const SyncStatus();
3 double progress();
4 +
5 + String formattedProgress() {
6 + return "${(progress() * 100).toStringAsFixed(2)}%";
7 + }
8 }
9
10 class StartingScanSyncStatus extends SyncStatus {
@@ -15,10 +19,12 @@ class StartingScanSyncStatus extends SyncStatus {
19 }
20
21 class SyncingSyncStatus extends SyncStatus {
18 - SyncingSyncStatus(this.blocksLeft, this.ptc);
22 + SyncingSyncStatus(this.blocksLeft, this.ptc) {
23 + updateEtaHistory(blocksLeft);
24 + }
25
20 - final double ptc;
21 - final int blocksLeft;
26 + double ptc;
27 + int blocksLeft;
28
29 @override
30 double progress() => ptc;
@@ -31,10 +37,161 @@ class SyncingSyncStatus extends SyncStatus {
37 final diff = track - (chainTip - syncHeight);
38 final ptc = diff <= 0 ? 0.0 : diff / track;
39 final left = chainTip - syncHeight;
40 + updateEtaHistory(left + 1);
41
42 // sum 1 because if at the chain tip, will say "0 blocks left"
43 return SyncingSyncStatus(left + 1, ptc);
44 }
45 +
46 + static void updateEtaHistory(int blocksLeft) {
47 + blockHistory[DateTime.now()] = blocksLeft;
48 +
49 + // keep only the last 30 entries (gives us better statistical accuracy)
50 + while (blockHistory.length > 30) {
51 + blockHistory.remove(blockHistory.keys.first);
52 + }
53 + }
54 +
55 + static Map<DateTime, int> blockHistory = {};
56 + static Duration? lastEtaDuration;
57 + static const int _minDataPoints = 3;
58 + static const int _maxDataAgeMinutes = 2;
59 +
60 + String? getFormattedEtaWithPlaceholder() {
61 + _cleanOldEntries();
62 +
63 + // If we have enough data, show actual ETA
64 + if (blockHistory.length >= _minDataPoints) {
65 + final eta = getFormattedEta();
66 + if (eta != null) return eta;
67 + }
68 +
69 + // Show the placeholder ETA while gathering data
70 + return '--:--';
71 + }
72 +
73 + void _cleanOldEntries() {
74 + final cutoffTime = DateTime.now().subtract(Duration(minutes: _maxDataAgeMinutes));
75 + blockHistory.removeWhere((key, value) => key.isBefore(cutoffTime));
76 + }
77 +
78 + String? getFormattedEta() {
79 + Duration? duration = getEtaDuration();
80 +
81 + // Don't show ETA for very long durations or very few blocks
82 + if (duration.inDays > 0 || blocksLeft < 100) return null;
83 +
84 + // Apply smoothing to prevent ETA jumping
85 + duration = _applySmoothing(duration);
86 + lastEtaDuration = duration;
87 +
88 + return _formatDuration(duration);
89 + }
90 +
91 + Duration getEtaDuration() {
92 + DateTime now = DateTime.now();
93 + DateTime completionTime = calculateEta();
94 + return completionTime.difference(now);
95 + }
96 +
97 + Duration _applySmoothing(Duration newDuration) {
98 + if (lastEtaDuration == null) {
99 + return newDuration;
100 + }
101 +
102 + final currentSeconds = lastEtaDuration!.inSeconds;
103 + final newSeconds = newDuration.inSeconds;
104 + final diff = (newSeconds - currentSeconds).abs();
105 +
106 + // Apply different smoothing based on the magnitude of change
107 + if (diff > 3600) {
108 + // If it's more than 1 hour difference, it's a large change so we move by max 30 minutes
109 + final direction = newSeconds > currentSeconds ? 1 : -1;
110 + final maxChange = 30 * 60;
111 + final adjustedSeconds = currentSeconds + (direction * maxChange);
112 + return Duration(seconds: adjustedSeconds);
113 + } else if (diff > 300) {
114 + // If it's more than 5 minutes difference, it's a medium change so we move by max 2 minutes
115 + final direction = newSeconds > currentSeconds ? 1 : -1;
116 + final maxChange = 2 * 60;
117 + final adjustedSeconds = currentSeconds + (direction * maxChange);
118 + return Duration(seconds: adjustedSeconds);
119 + } else if (diff > 60) {
120 + // If it's more than 1 minute difference, it's a small change so we move by max 30 seconds
121 + final direction = newSeconds > currentSeconds ? 1 : -1;
122 + final maxChange = 30;
123 + final adjustedSeconds = currentSeconds + (direction * maxChange);
124 + return Duration(seconds: adjustedSeconds);
125 + }
126 +
127 + return newDuration;
128 + }
129 +
130 + String _formatDuration(Duration duration) {
131 + String twoDigits(int n) => n.toString().padLeft(2, '0');
132 +
133 + final hours = twoDigits(duration.inHours);
134 + final minutes = twoDigits(duration.inMinutes.remainder(60));
135 + final seconds = twoDigits(duration.inSeconds.remainder(60));
136 +
137 + if (hours == '00') {
138 + return '${minutes}m${seconds}s';
139 + }
140 + return '${hours}h${minutes}m${seconds}s';
141 + }
142 +
143 + DateTime calculateEta() {
144 + double rate = _calculateBlockRate();
145 + if (rate == 0) {
146 + return DateTime.now().add(const Duration(days: 2));
147 + }
148 + int remainingBlocks = this.blocksLeft;
149 + double timeRemainingSeconds = remainingBlocks / rate;
150 + return DateTime.now().add(Duration(seconds: timeRemainingSeconds.round()));
151 + }
152 +
153 + // Enhanced block rate calculation with weighted averages
154 + double _calculateBlockRate() {
155 + List<DateTime> timestamps = blockHistory.keys.toList();
156 + List<int> blockCounts = blockHistory.values.toList();
157 +
158 + if (timestamps.length < 2) return 0;
159 +
160 + // Sort by timestamp to ensure chronological order
161 + final sortedData =
162 + List.generate(timestamps.length, (i) => MapEntry(timestamps[i], blockCounts[i]))
163 + ..sort((a, b) => a.key.compareTo(b.key));
164 +
165 + double totalWeightedTime = 0;
166 + double totalWeightedBlocks = 0;
167 + double totalWeight = 0;
168 +
169 + for (int i = 0; i < sortedData.length - 1; i++) {
170 + final current = sortedData[i];
171 + final next = sortedData[i + 1];
172 +
173 + final blocksProcessed = current.value - next.value;
174 +
175 + if (blocksProcessed <= 0) continue; // Skip invalid data
176 +
177 + final timeDifference = next.key.difference(current.key);
178 +
179 + if (timeDifference.inSeconds <= 0) continue; // Skip invalid time
180 +
181 + // Weight recent data more heavily (exponential decay)
182 + final weight = 1.0 / (1.0 + (sortedData.length - 1 - i) * 0.1);
183 +
184 + totalWeightedTime += timeDifference.inSeconds * weight;
185 + totalWeightedBlocks += blocksProcessed * weight;
186 + totalWeight += weight;
187 + }
188 +
189 + if (totalWeight == 0 || totalWeightedTime == 0) return 0;
190 +
191 + final weightedRate = totalWeightedBlocks / totalWeightedTime;
192 +
193 + return weightedRate;
194 + }
195 }
196
197 class ProcessingSyncStatus extends SyncStatus {
lib/core/sync_status_title.dart
+19 -4
@@ -1,11 +1,26 @@
1 +import 'package:cake_wallet/entities/sync_status_display_mode.dart';
2 import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cw_core/sync_status.dart';
4
4 -String syncStatusTitle(SyncStatus syncStatus) {
5 +String syncStatusTitle(SyncStatus syncStatus, SyncStatusDisplayMode syncStatusDisplayMode) {
6 if (syncStatus is SyncingSyncStatus) {
6 - return syncStatus.blocksLeft == 1
7 - ? S.current.block_remaining
8 - : S.current.Blocks_remaining('${syncStatus.blocksLeft}');
7 + if (syncStatus.blocksLeft == 1) {
8 + return S.current.block_remaining;
9 + }
10 +
11 + // Check user preference for sync status display
12 + if (syncStatusDisplayMode == SyncStatusDisplayMode.eta) {
13 + // Get ETA with placeholder while gathering data
14 + String eta = syncStatus.getFormattedEtaWithPlaceholder() ?? '';
15 +
16 + if (eta.isEmpty) {
17 + return S.current.Blocks_remaining('${syncStatus.blocksLeft}');
18 + } else {
19 + return "${syncStatus.formattedProgress()} - ${S.current.eta} $eta";
20 + }
21 + } else {
22 + return S.current.Blocks_remaining('${syncStatus.blocksLeft}');
23 + }
24 }
25
26 if (syncStatus is SyncedTipSyncStatus) {
lib/entities/preferences_key.dart
+1
@@ -114,5 +114,6 @@ class PreferencesKey {
114 static String backgroundSyncLastTrigger(String walletId) => 'background_sync_last_trigger_${walletId}';
115 static const backgroundSyncNotificationsEnabled = 'background_sync_notifications_enabled';
116 static const enableAutomaticNodeSwitching = 'enable_automatic_node_switching';
117 + static const syncStatusDisplayMode = 'sync_status_display_mode';
118 static const backgroundImage = 'background_image';
119 }
lib/entities/sync_status_display_mode.dart new
+34
@@ -0,0 +1,34 @@
1 +enum SyncStatusDisplayMode { eta, blocksRemaining }
2 +
3 +extension SyncStatusDisplayModeExtension on SyncStatusDisplayMode {
4 + String get title {
5 + switch (this) {
6 + case SyncStatusDisplayMode.eta:
7 + return 'ETA';
8 + case SyncStatusDisplayMode.blocksRemaining:
9 + return 'Blocks Remaining';
10 + }
11 + }
12 +
13 + String get description {
14 + switch (this) {
15 + case SyncStatusDisplayMode.eta:
16 + return 'Show estimated time remaining for sync completion';
17 + case SyncStatusDisplayMode.blocksRemaining:
18 + return 'Show number of blocks remaining to sync';
19 + }
20 + }
21 +
22 + static SyncStatusDisplayMode fromString(String value) {
23 + switch (value) {
24 + case 'eta':
25 + return SyncStatusDisplayMode.eta;
26 + case 'blocksRemaining':
27 + return SyncStatusDisplayMode.blocksRemaining;
28 + default:
29 + return SyncStatusDisplayMode.eta;
30 + }
31 + }
32 +
33 + String toJson() => name;
34 +}
lib/src/screens/dashboard/widgets/sync_indicator.dart
+1 -1
@@ -20,7 +20,7 @@ class SyncIndicator extends StatelessWidget {
20 return Observer(builder: (_) {
21 final syncIndicatorWidth = 237.0;
22 final status = dashboardViewModel.status;
23 - final statusText = syncStatusTitle(status);
23 + final statusText = syncStatusTitle(status, dashboardViewModel.settingsStore.syncStatusDisplayMode);
24 final progress = status.progress();
25 final indicatorOffset = progress * syncIndicatorWidth;
26 final indicatorWidth = progress < 1
lib/src/screens/settings/display_settings_page.dart
+10
@@ -1,5 +1,6 @@
1 import 'package:cake_wallet/entities/fiat_currency.dart';
2 import 'package:cake_wallet/entities/language_service.dart';
3 +import 'package:cake_wallet/entities/sync_status_display_mode.dart';
4 import 'package:cake_wallet/generated/i18n.dart';
5 import 'package:cake_wallet/src/screens/base_page.dart';
6 import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
@@ -46,6 +47,15 @@ class DisplaySettingsPage extends BasePage {
47 _displaySettingsViewModel.setShowAddressBookPopup(value);
48 },
49 ),
50 + SettingsPickerCell<SyncStatusDisplayMode>(
51 + title: S.current.sync_status_display_mode,
52 + items: SyncStatusDisplayMode.values.toList(),
53 + selectedItem: _displaySettingsViewModel.syncStatusDisplayMode,
54 + onItemSelected: (SyncStatusDisplayMode mode) =>
55 + _displaySettingsViewModel.setSyncStatusDisplayMode(mode),
56 + displayItem: (SyncStatusDisplayMode mode) => mode.title,
57 + isGridView: false,
58 + ),
59 //if (!isHaven) it does not work correctly
60 if (!_displaySettingsViewModel.disabledFiatApiMode)
61 SettingsPickerCell<FiatCurrency>(
lib/store/settings_store.dart
+16
@@ -24,6 +24,7 @@ import 'package:cake_wallet/entities/secret_store_key.dart';
24 import 'package:cake_wallet/entities/seed_phrase_length.dart';
25 import 'package:cake_wallet/entities/seed_type.dart';
26 import 'package:cake_wallet/entities/sort_balance_types.dart';
27 +import 'package:cake_wallet/entities/sync_status_display_mode.dart';
28 import 'package:cake_wallet/entities/wallet_list_order_types.dart';
29 import 'package:cake_wallet/ethereum/ethereum.dart';
30 import 'package:cake_wallet/wownero/wownero.dart';
@@ -55,6 +56,7 @@ abstract class SettingsStoreBase with Store {
56 required SharedPreferences sharedPreferences,
57 required bool initialShouldShowMarketPlaceInDashboard,
58 required bool initialShowAddressBookPopupEnabled,
59 + required SyncStatusDisplayMode initialSyncStatusDisplayMode,
60 required FiatCurrency initialFiatCurrency,
61 required BalanceDisplayMode initialBalanceDisplayMode,
62 required bool initialSaveRecipientAddress,
@@ -168,6 +170,7 @@ abstract class SettingsStoreBase with Store {
170 contactListAscending = initialContactListAscending,
171 shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard,
172 showAddressBookPopupEnabled = initialShowAddressBookPopupEnabled,
173 + syncStatusDisplayMode = initialSyncStatusDisplayMode,
174 exchangeStatus = initialExchangeStatus,
175 pinCodeLength = initialPinLength,
176 languageCode = initialLanguageCode,
@@ -388,6 +391,11 @@ abstract class SettingsStoreBase with Store {
391 (bool value) =>
392 sharedPreferences.setBool(PreferencesKey.showAddressBookPopupEnabled, value));
393
394 + reaction(
395 + (_) => syncStatusDisplayMode,
396 + (SyncStatusDisplayMode value) =>
397 + sharedPreferences.setString(PreferencesKey.syncStatusDisplayMode, value.toJson()));
398 +
399 reaction((_) => pinCodeLength,
400 (int pinLength) => sharedPreferences.setInt(PreferencesKey.currentPinLength, pinLength));
401
@@ -674,6 +682,9 @@ abstract class SettingsStoreBase with Store {
682 @observable
683 bool showAddressBookPopupEnabled;
684
685 + @observable
686 + SyncStatusDisplayMode syncStatusDisplayMode;
687 +
688 @observable
689 ObservableList<ActionListDisplayMode> actionlistDisplayMode;
690
@@ -1012,6 +1023,8 @@ abstract class SettingsStoreBase with Store {
1023 sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true;
1024 final showAddressBookPopupEnabled =
1025 sharedPreferences.getBool(PreferencesKey.showAddressBookPopupEnabled) ?? true;
1026 + final syncStatusDisplayMode = SyncStatusDisplayModeExtension.fromString(
1027 + sharedPreferences.getString(PreferencesKey.syncStatusDisplayMode) ?? SyncStatusDisplayMode.eta.name);
1028 final exchangeStatus = ExchangeApiMode.deserialize(
1029 raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
1030 ExchangeApiMode.enabled.raw);
@@ -1307,6 +1320,7 @@ abstract class SettingsStoreBase with Store {
1320 sharedPreferences: sharedPreferences,
1321 initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
1322 initialShowAddressBookPopupEnabled: showAddressBookPopupEnabled,
1323 + initialSyncStatusDisplayMode: syncStatusDisplayMode,
1324 nodes: nodes,
1325 powNodes: powNodes,
1326 appVersion: packageInfo.version,
@@ -1504,6 +1518,8 @@ abstract class SettingsStoreBase with Store {
1518 showAddressBookPopupEnabled =
1519 sharedPreferences.getBool(PreferencesKey.showAddressBookPopupEnabled) ??
1520 showAddressBookPopupEnabled;
1521 + syncStatusDisplayMode = SyncStatusDisplayModeExtension.fromString(
1522 + sharedPreferences.getString(PreferencesKey.syncStatusDisplayMode) ?? SyncStatusDisplayMode.eta.name);
1523 exchangeStatus = ExchangeApiMode.deserialize(
1524 raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
1525 ExchangeApiMode.enabled.raw);
lib/view_model/settings/display_settings_view_model.dart
+8
@@ -1,5 +1,6 @@
1 import 'package:cake_wallet/entities/balance_display_mode.dart';
2 import 'package:cake_wallet/entities/fiat_currency.dart';
3 +import 'package:cake_wallet/entities/sync_status_display_mode.dart';
4 import 'package:cake_wallet/store/settings_store.dart';
5 import 'package:cake_wallet/themes/core/material_base_theme.dart';
6 import 'package:mobx/mobx.dart';
@@ -44,6 +45,9 @@ abstract class DisplaySettingsViewModelBase with Store {
45 @computed
46 bool get showAddressBookPopup => _settingsStore.showAddressBookPopupEnabled;
47
48 + @computed
49 + SyncStatusDisplayMode get syncStatusDisplayMode => _settingsStore.syncStatusDisplayMode;
50 +
51 @computed
52 String get backgroundImage => _settingsStore.backgroundImage;
53
@@ -91,6 +95,10 @@ abstract class DisplaySettingsViewModelBase with Store {
95 @action
96 void setShowAddressBookPopup(bool value) => _settingsStore.showAddressBookPopupEnabled = value;
97
98 + @action
99 + void setSyncStatusDisplayMode(SyncStatusDisplayMode value) =>
100 + _settingsStore.syncStatusDisplayMode = value;
101 +
102 @action
103 void setBackgroundImage(String path) => _settingsStore.backgroundImage = path;
104 }
res/values/strings_ar.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "مُقدَّر",
355 "estimated_new_fee": "رسوم جديدة مقدرة",
356 "estimated_receive_amount": "مقدرة المبلغ الاستقبال",
357 + "eta": "إيتا",
358 "etherscan_history": "Etherscan تاريخ",
359 "event": "ﺙﺪﺣ",
360 "events": "ﺙﺍﺪﺣﻷﺍ",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "جاري محاولة المزامنة",
912 "sync_status_connected": "متصل",
913 "sync_status_connecting": "يتم التوصيل",
914 + "sync_status_display_mode": "مزامنة عرض حالة",
915 + "sync_status_display_mode_blocks": "إظهار الكتل المتبقية",
916 + "sync_status_display_mode_eta": "تظهر ETA",
917 "sync_status_failed_connect": "انقطع الاتصال",
918 "sync_status_not_connected": "غير متصل",
919 "sync_status_starting_scan": "بدء المسح الضوئي (من ${height})",
res/values/strings_bg.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Изчислено",
355 "estimated_new_fee": "Прогнозна нова такса",
356 "estimated_receive_amount": "Прогнозна сума за получаване",
357 + "eta": "Ета",
358 "etherscan_history": "История на Etherscan",
359 "event": "Събитие",
360 "events": "събития",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "ОПИТ ЗА СИНХРОНИЗАЦИЯ",
912 "sync_status_connected": "СВЪРЗВАНЕ",
913 "sync_status_connecting": "СВЪРЗВАНЕ",
914 + "sync_status_display_mode": "Дисплей на състоянието на синхронизиране",
915 + "sync_status_display_mode_blocks": "Покажете останали блокове",
916 + "sync_status_display_mode_eta": "Покажи ETA",
917 "sync_status_failed_connect": "НЕУСПЕШНО СВЪРЗВАНЕ",
918 "sync_status_not_connected": "НЯМА ВРЪЗКА",
919 "sync_status_starting_scan": "Стартиране на сканиране (от ${height})",
res/values/strings_cs.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Odhadováno",
355 "estimated_new_fee": "Odhadovaný nový poplatek",
356 "estimated_receive_amount": "Odhadovaná částka přijímání",
357 + "eta": "ETA",
358 "etherscan_history": "Historie Etherscanu",
359 "event": "událost",
360 "events": "Události",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "ZAHAJUJI SYNCHR.",
912 "sync_status_connected": "PŘIPOJENO",
913 "sync_status_connecting": "PŘIPOJOVÁNÍ",
914 + "sync_status_display_mode": "Synchronizace stavu stavu",
915 + "sync_status_display_mode_blocks": "Zobrazit bloky zbývající",
916 + "sync_status_display_mode_eta": "Ukažte ETA",
917 "sync_status_failed_connect": "ODPOJENO",
918 "sync_status_not_connected": "NEPŘIPOJENO",
919 "sync_status_starting_scan": "Počáteční skenování (z ${height})",
res/values/strings_de.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Geschätzt",
355 "estimated_new_fee": "Geschätzte neue Gebühr",
356 "estimated_receive_amount": "Geschätzter Empfangsbetrag",
357 + "eta": "ETA",
358 "etherscan_history": "Etherscan-Historie",
359 "event": "Ereignis",
360 "events": "Veranstaltungen",
@@ -911,6 +912,9 @@
912 "sync_status_attempting_sync": "SYNC VERSUCHEN",
913 "sync_status_connected": "VERBUNDEN",
914 "sync_status_connecting": "VERBINDEN",
915 + "sync_status_display_mode": "Synchronisationsstatusanzeige",
916 + "sync_status_display_mode_blocks": "Blöcke verbleibend zeigen",
917 + "sync_status_display_mode_eta": "Zeigen ETA",
918 "sync_status_failed_connect": "GETRENNT",
919 "sync_status_not_connected": "NICHT VERBUNDEN",
920 "sync_status_starting_scan": "SCAN starten (von ${height})",
res/values/strings_en.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Estimated",
355 "estimated_new_fee": "Estimated new fee",
356 "estimated_receive_amount": "Estimated receive amount",
357 + "eta": "ETA",
358 "etherscan_history": "Etherscan history",
359 "event": "Event",
360 "events": "Events",
@@ -911,6 +912,9 @@
912 "sync_status_attempting_sync": "ATTEMPTING SYNC",
913 "sync_status_connected": "CONNECTED",
914 "sync_status_connecting": "CONNECTING",
915 + "sync_status_display_mode": "Sync Status Display",
916 + "sync_status_display_mode_blocks": "Show Blocks Remaining",
917 + "sync_status_display_mode_eta": "Show ETA",
918 "sync_status_failed_connect": "DISCONNECTED",
919 "sync_status_not_connected": "NOT CONNECTED",
920 "sync_status_starting_scan": "STARTING SCAN (from ${height})",
res/values/strings_es.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Estimado",
355 "estimated_new_fee": "Nueva tarifa estimada",
356 "estimated_receive_amount": "Cantidad de recepción estimada",
357 + "eta": "ETA",
358 "etherscan_history": "historia de etherscan",
359 "event": "Evento",
360 "events": "Eventos",
@@ -911,6 +912,9 @@
912 "sync_status_attempting_sync": "INTENTAR SINCRONIZAR",
913 "sync_status_connected": "CONECTADO",
914 "sync_status_connecting": "CONECTANDO",
915 + "sync_status_display_mode": "Pantalla de estado de sincronización",
916 + "sync_status_display_mode_blocks": "Mostrar bloques restantes",
917 + "sync_status_display_mode_eta": "Mostrar ETA",
918 "sync_status_failed_connect": "DESCONECTADO",
919 "sync_status_not_connected": "NO CONECTADO",
920 "sync_status_starting_scan": "Iniciar escaneo (de ${height})",
res/values/strings_fr.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Estimé",
355 "estimated_new_fee": "De nouveaux frais estimés",
356 "estimated_receive_amount": "Recevoir estimé le montant",
357 + "eta": "ETA",
358 "etherscan_history": "Historique Etherscan",
359 "event": "Événement",
360 "events": "Événements",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "TENTATIVE DE SYNCHRONISATION",
912 "sync_status_connected": "CONNECTÉ",
913 "sync_status_connecting": "CONNEXION EN COURS",
914 + "sync_status_display_mode": "Affichage de l'état de synchronisation",
915 + "sync_status_display_mode_blocks": "Afficher les blocs restants",
916 + "sync_status_display_mode_eta": "Show Eta",
917 "sync_status_failed_connect": "DÉCONNECTÉ",
918 "sync_status_not_connected": "NON CONNECTÉ",
919 "sync_status_starting_scan": "Démarrer la numérisation (à partir de ${height})",
res/values/strings_ha.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Kiyasta",
355 "estimated_new_fee": "An kiyasta sabon kudin",
356 "estimated_receive_amount": "Kiyasta samun adadin",
357 + "eta": "Tsire-tsire",
358 "etherscan_history": "Etherscan tarihin kowane zamani",
359 "event": "Lamarin",
360 "events": "Abubuwan da suka faru",
@@ -912,6 +913,9 @@
913 "sync_status_attempting_sync": "KWAFI",
914 "sync_status_connected": "HANNU",
915 "sync_status_connecting": "HADA",
916 + "sync_status_display_mode": "Nunin halin Setc",
917 + "sync_status_display_mode_blocks": "Nuna shinge da sauran",
918 + "sync_status_display_mode_eta": "Nuna ETA",
919 "sync_status_failed_connect": "BABU INTERNET",
920 "sync_status_not_connected": "BABU INTERNET",
921 "sync_status_starting_scan": "Farawa Scan (daga ${height})",
res/values/strings_hi.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "अनुमानित",
355 "estimated_new_fee": "अनुमानित नया शुल्क",
356 "estimated_receive_amount": "अनुमानित राशि",
357 + "eta": "ईटा",
358 "etherscan_history": "इथरस्कैन इतिहास",
359 "event": "आयोजन",
360 "events": "आयोजन",
@@ -912,6 +913,9 @@
913 "sync_status_attempting_sync": "सिंक करने का प्रयास",
914 "sync_status_connected": "जुड़े हुए",
915 "sync_status_connecting": "कनेक्ट",
916 + "sync_status_display_mode": "सिंक स्थिति प्रदर्शन",
917 + "sync_status_display_mode_blocks": "शेष ब्लॉक दिखाएं",
918 + "sync_status_display_mode_eta": "शो एटा",
919 "sync_status_failed_connect": "डिस्कनेक्ट किया गया",
920 "sync_status_not_connected": "जुड़े नहीं हैं",
921 "sync_status_starting_scan": "स्कैन शुरू करना (${height} से)",
res/values/strings_hr.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "procijenjen",
355 "estimated_new_fee": "Procijenjena nova naknada",
356 "estimated_receive_amount": "Procijenjeni iznos primanja",
357 + "eta": "Eta",
358 "etherscan_history": "Etherscan povijest",
359 "event": "Događaj",
360 "events": "Događaji",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "POKUŠAJ SINKRONIZACIJE",
912 "sync_status_connected": "SPOJENO",
913 "sync_status_connecting": "SPAJANJE",
914 + "sync_status_display_mode": "Sinkronizirani prikaz statusa",
915 + "sync_status_display_mode_blocks": "Prikažite preostale blokove",
916 + "sync_status_display_mode_eta": "Pokazati eta",
917 "sync_status_failed_connect": "ISKLJUČENO",
918 "sync_status_not_connected": "NIJE POVEZANO",
919 "sync_status_starting_scan": "Početno skeniranje (od ${height})",
res/values/strings_hy.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Գնահատված",
355 "estimated_new_fee": "Գնահատված նոր միջնորդավճար",
356 "estimated_receive_amount": "Գնահատված ստացված գումար",
357 + "eta": "Eta",
358 "etherscan_history": "Etherscan պատմություն",
359 "event": "Իրադարձություն",
360 "events": "Իրադարձություններ",
@@ -908,6 +909,9 @@
909 "sync_status_attempting_sync": "ՀԱՄԱԺԱՄԵՑՄԱՆ ՓՈՐՁ",
910 "sync_status_connected": "ՄԻԱՑՎԱԾԷ",
911 "sync_status_connecting": "ՄԻԱՑՎՈՒՄ Է",
912 + "sync_status_display_mode": "Sync կարգավիճակի ցուցադրում",
913 + "sync_status_display_mode_blocks": "Show ուցադրել բլոկները",
914 + "sync_status_display_mode_eta": "Show ուցադրել ETA",
915 "sync_status_failed_connect": "ՉՄԻԱՑԱՎ",
916 "sync_status_not_connected": "ՄԻԱՑՎԱԾ ՉԷ",
917 "sync_status_starting_scan": "Սկսած սկան (${height})",
res/values/strings_id.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Diperkirakan",
355 "estimated_new_fee": "Perkiraan biaya baru",
356 "estimated_receive_amount": "Diperkirakan jumlah menerima",
357 + "eta": "Eta",
358 "etherscan_history": "Sejarah Etherscan",
359 "event": "Peristiwa",
360 "events": "Acara",
@@ -913,6 +914,9 @@
914 "sync_status_attempting_sync": "MENCOBA SINKRONISASI",
915 "sync_status_connected": "TERHUBUNG",
916 "sync_status_connecting": "MENGHUBUNGKAN",
917 + "sync_status_display_mode": "Tampilan Status Sinkronisasi",
918 + "sync_status_display_mode_blocks": "Tampilkan blok tersisa",
919 + "sync_status_display_mode_eta": "Tunjukkan eta",
920 "sync_status_failed_connect": "GAGAL TERHUBUNG",
921 "sync_status_not_connected": "TIDAK TERHUBUNG",
922 "sync_status_starting_scan": "Mulai pemindaian (dari ${height})",
res/values/strings_it.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Stimato",
355 "estimated_new_fee": "Nuova commissione stimata",
356 "estimated_receive_amount": "Importo di ricezione stimato",
357 + "eta": "Eta",
358 "etherscan_history": "Cronologia Etherscan",
359 "event": "Evento",
360 "events": "Eventi",
@@ -911,6 +912,9 @@
912 "sync_status_attempting_sync": "TENTATIVO DI SINCRONIZZAZIONE",
913 "sync_status_connected": "CONNESSO",
914 "sync_status_connecting": "CONNESSIONE",
915 + "sync_status_display_mode": "Visualizzazione dello stato di sincronizzazione",
916 + "sync_status_display_mode_blocks": "Mostra blocchi rimanenti",
917 + "sync_status_display_mode_eta": "Mostra eta",
918 "sync_status_failed_connect": "DISCONNESSO",
919 "sync_status_not_connected": "NON CONNESSO",
920 "sync_status_starting_scan": "AVVIO SCANSIONE (da ${height})",
res/values/strings_ja.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "推定",
355 "estimated_new_fee": "推定新しい料金",
356 "estimated_receive_amount": "推定受信金額",
357 + "eta": "ETA",
358 "etherscan_history": "イーサスキャンの歴史",
359 "event": "イベント",
360 "events": "イベント",
@@ -911,6 +912,9 @@
912 "sync_status_attempting_sync": "同期を試みています",
913 "sync_status_connected": "接続済み",
914 "sync_status_connecting": "接続中",
915 + "sync_status_display_mode": "ステータス表示を同期します",
916 + "sync_status_display_mode_blocks": "残りのブロックを表示します",
917 + "sync_status_display_mode_eta": "ETAを表示します",
918 "sync_status_failed_connect": "切断されました",
919 "sync_status_not_connected": "接続されていません",
920 "sync_status_starting_scan": "スキャンを開始する(${height} から)",
res/values/strings_ko.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "예상",
355 "estimated_new_fee": "예상 새 수수료",
356 "estimated_receive_amount": "예상 수령 금액",
357 + "eta": "에타",
358 "etherscan_history": "Etherscan 내역",
359 "event": "이벤트",
360 "events": "이벤트",
@@ -911,6 +912,9 @@
912 "sync_status_attempting_sync": "동기화 시도 중",
913 "sync_status_connected": "연결됨",
914 "sync_status_connecting": "연결 중",
915 + "sync_status_display_mode": "동기화 상태 표시",
916 + "sync_status_display_mode_blocks": "남은 블록을 보여줍니다",
917 + "sync_status_display_mode_eta": "ETA를 보여주십시오",
918 "sync_status_failed_connect": "연결 끊김",
919 "sync_status_not_connected": "연결되지 않음",
920 "sync_status_starting_scan": "스캔 시작 중 (${height}부터)",
res/values/strings_my.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "ခန့်မှန်း",
355 "estimated_new_fee": "ခန့်မှန်းသစ်ခန့်မှန်း",
356 "estimated_receive_amount": "ခန့်မှန်းရရှိသောပမာဏ",
357 + "eta": "eta",
358 "etherscan_history": "Etherscan သမိုင်း",
359 "event": "ပွဲ",
360 "events": "အဲ့ဒါနဲ့",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "ချိန်ကိုက်ခြင်းကို ကြိုးစားနေသည်။",
912 "sync_status_connected": "ချိတ်ဆက်ထားသည်။",
913 "sync_status_connecting": "ချိတ်ဆက်ခြင်း။",
914 + "sync_status_display_mode": "Sync status display ကို",
915 + "sync_status_display_mode_blocks": "ကျန်ရှိနေသေးသောလုပ်ကွက်များကိုပြပါ",
916 + "sync_status_display_mode_eta": "Eta ကိုပြပါ",
917 "sync_status_failed_connect": "အဆက်အသွယ်ဖြတ်ထားသည်။",
918 "sync_status_not_connected": "မချိတ်ဆက်ပါ။",
919 "sync_status_starting_scan": "စကင်ဖတ်စစ်ဆေးမှုစတင်ခြင်း (${height})",
res/values/strings_nl.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Geschatte",
355 "estimated_new_fee": "Geschatte nieuwe vergoeding",
356 "estimated_receive_amount": "Geschat ontvangen bedrag",
357 + "eta": "ETA",
358 "etherscan_history": "Etherscan-geschiedenis",
359 "event": "Evenement",
360 "events": "Evenementen",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "SYNCHRONISATIE PROBEREN",
912 "sync_status_connected": "VERBONDEN",
913 "sync_status_connecting": "AANSLUITING",
914 + "sync_status_display_mode": "Synchronisatiestatusweergave",
915 + "sync_status_display_mode_blocks": "Toon blokken overgebleven",
916 + "sync_status_display_mode_eta": "Toon ETA",
917 "sync_status_failed_connect": "LOSGEKOPPELD",
918 "sync_status_not_connected": "NIET VERBONDEN",
919 "sync_status_starting_scan": "SCAN starten (van ${height})",
res/values/strings_pl.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Oszacowano",
355 "estimated_new_fee": "Szacowana nowa opłata",
356 "estimated_receive_amount": "Szacowana kwota otrzymania",
357 + "eta": "Eta",
358 "etherscan_history": "Historia Etherscanu",
359 "event": "Wydarzenie",
360 "events": "Wydarzenia",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "PRÓBA SYNCHRONIZACJI",
912 "sync_status_connected": "POŁĄCZONY",
913 "sync_status_connecting": "ŁĄCZENIE",
914 + "sync_status_display_mode": "Wyświetlanie statusu synchronizacji",
915 + "sync_status_display_mode_blocks": "Pokaż pozostałe bloki",
916 + "sync_status_display_mode_eta": "Pokaż eta",
917 "sync_status_failed_connect": "POŁĄCZENIE NIEUDANE",
918 "sync_status_not_connected": "NIE POŁĄCZONY",
919 "sync_status_starting_scan": "Rozpoczęcie skanowania (od ${height})",
res/values/strings_pt.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Estimado",
355 "estimated_new_fee": "Nova taxa estimada",
356 "estimated_receive_amount": "Valor estimado de recebimento",
357 + "eta": "ETA",
358 "etherscan_history": "história Etherscan",
359 "event": "Evento",
360 "events": "Eventos",
@@ -912,6 +913,9 @@
913 "sync_status_attempting_sync": "TENTANDO SINCRONIZAR",
914 "sync_status_connected": "CONECTADO",
915 "sync_status_connecting": "CONECTANDO",
916 + "sync_status_display_mode": "Exibição de status de sincronização",
917 + "sync_status_display_mode_blocks": "Mostre blocos restantes",
918 + "sync_status_display_mode_eta": "Mostre ETA",
919 "sync_status_failed_connect": "DESCONECTADO",
920 "sync_status_not_connected": "DESCONECTADO",
921 "sync_status_starting_scan": "Começando scan (de ${height})",
res/values/strings_ru.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Примерно",
355 "estimated_new_fee": "Расчетная новая плата",
356 "estimated_receive_amount": "Расчетная сумма получения",
357 + "eta": "ETA",
358 "etherscan_history": "История Эфириума",
359 "event": "Событие",
360 "events": "События",
@@ -911,6 +912,9 @@
912 "sync_status_attempting_sync": "ПОПЫТКА СИНХРОНИЗАЦИИ",
913 "sync_status_connected": "ПОДКЛЮЧЕНО",
914 "sync_status_connecting": "ПОДКЛЮЧЕНИЕ",
915 + "sync_status_display_mode": "Отображение статуса синхронизации",
916 + "sync_status_display_mode_blocks": "Показать оставшиеся блоки",
917 + "sync_status_display_mode_eta": "Показать ETA",
918 "sync_status_failed_connect": "ОТКЛЮЧЕНО",
919 "sync_status_not_connected": "НЕ ПОДКЛЮЧЁН",
920 "sync_status_starting_scan": "Начальное сканирование (от ${height})",
res/values/strings_th.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "ประมาณการ",
355 "estimated_new_fee": "ค่าธรรมเนียมใหม่โดยประมาณ",
356 "estimated_receive_amount": "โดยประมาณว่าจำนวนเงินที่ได้รับ",
357 + "eta": "กทพ.",
358 "etherscan_history": "ประวัติอีเธอร์สแกน",
359 "event": "เหตุการณ์",
360 "events": "กิจกรรม",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "พยายามซิงโครไนซ์",
912 "sync_status_connected": "เชื่อมต่อแล้ว",
913 "sync_status_connecting": "กำลังเชื่อมต่อ",
914 + "sync_status_display_mode": "การแสดงสถานะซิงค์",
915 + "sync_status_display_mode_blocks": "โชว์บล็อกที่เหลืออยู่",
916 + "sync_status_display_mode_eta": "แสดง ETA",
917 "sync_status_failed_connect": "การเชื่อมต่อล้มเหลว",
918 "sync_status_not_connected": "ไม่ได้เชื่อมต่อ",
919 "sync_status_starting_scan": "การสแกนเริ่มต้น (จาก ${height})",
res/values/strings_tl.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Tinatayang",
355 "estimated_new_fee": "Tinatayang bagong fee",
356 "estimated_receive_amount": "Tinatayang natanggap na halaga",
357 + "eta": "Eta",
358 "etherscan_history": "Kasaysayan ng Etherscan",
359 "event": "Kaganapan",
360 "events": "Mga kaganapan",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "SINUSUBUKANG I-SYNC",
912 "sync_status_connected": "KONEKTADO",
913 "sync_status_connecting": "KUMOKENEKTA",
914 + "sync_status_display_mode": "Pagpapakita ng katayuan ng pag -sync",
915 + "sync_status_display_mode_blocks": "Ipakita ang natitirang mga bloke",
916 + "sync_status_display_mode_eta": "Ipakita ang Eta",
917 "sync_status_failed_connect": "NADISKONEKTA",
918 "sync_status_not_connected": "HINDI KONEKTADO",
919 "sync_status_starting_scan": "Simula sa pag -scan (mula sa ${height})",
res/values/strings_tr.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Tahmini",
355 "estimated_new_fee": "Tahmini yeni ücret",
356 "estimated_receive_amount": "Tahmini alma miktarı",
357 + "eta": "ETA",
358 "etherscan_history": "Etherscan geçmişi",
359 "event": "Etkinlik",
360 "events": "Olaylar",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "SENKRONİZE EDİLMEYE ÇALIŞILIYOR",
912 "sync_status_connected": "BAĞLANILDI",
913 "sync_status_connecting": "BAĞLANILIYOR",
914 + "sync_status_display_mode": "Senkronizasyon Durum Ekranı",
915 + "sync_status_display_mode_blocks": "Kalan blokları göster",
916 + "sync_status_display_mode_eta": "ETA'yı Göster",
917 "sync_status_failed_connect": "BAĞLANTI KESİLDİ",
918 "sync_status_not_connected": "BAĞLI DEĞİL",
919 "sync_status_starting_scan": "Başlangıç ​​taraması (${height})",
res/values/strings_uk.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "Приблизно ",
355 "estimated_new_fee": "Орієнтовна нова комісія",
356 "estimated_receive_amount": "Орієнтовна сума отримує",
357 + "eta": "ETA",
358 "etherscan_history": "Історія Etherscan",
359 "event": "Подія",
360 "events": "Події",
@@ -911,6 +912,9 @@
912 "sync_status_attempting_sync": "СПРОБА СИНХРОНІЗАЦІЇ",
913 "sync_status_connected": "ПІДКЛЮЧЕНО",
914 "sync_status_connecting": "ПІДКЛЮЧЕННЯ",
915 + "sync_status_display_mode": "Дисплей стану синхронізації",
916 + "sync_status_display_mode_blocks": "Показати блоки, що залишилися",
917 + "sync_status_display_mode_eta": "Шоу ETA",
918 "sync_status_failed_connect": "ВІДКЛЮЧЕНО",
919 "sync_status_not_connected": "НЕ ПІДКЛЮЧЕННИЙ",
920 "sync_status_starting_scan": "Початок сканування (від ${height})",
res/values/strings_ur.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "تخمینہ لگایا",
355 "estimated_new_fee": "تخمینہ شدہ نئی فیس",
356 "estimated_receive_amount": "تخمینہ وصول کی رقم",
357 + "eta": "ETA",
358 "etherscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﺮﮭﺘﯾﺍ",
359 "event": "ﺐﯾﺮﻘﺗ",
360 "events": "ﺕﺎﺒﯾﺮﻘﺗ",
@@ -912,6 +913,9 @@
913 "sync_status_attempting_sync": "ہم آہنگی کی کوشش کر رہا ہے۔",
914 "sync_status_connected": "منسلک",
915 "sync_status_connecting": "جڑ رہا ہے۔",
916 + "sync_status_display_mode": "مطابقت پذیری کی حیثیت ڈسپلے",
917 + "sync_status_display_mode_blocks": "باقی بلاکس دکھائیں",
918 + "sync_status_display_mode_eta": "ETA دکھائیں",
919 "sync_status_failed_connect": "منقطع",
920 "sync_status_not_connected": "منسلک نہیں",
921 "sync_status_starting_scan": "اسکین شروع کرنا (${height})",
res/values/strings_vi.arb
+4
@@ -353,6 +353,7 @@
353 "estimated": "Ước tính",
354 "estimated_new_fee": "Phí mới ước tính",
355 "estimated_receive_amount": "Số tiền nhận ước tính",
356 + "eta": "Eta",
357 "etherscan_history": "Lịch sử Etherscan",
358 "event": "Sự kiện",
359 "events": "Các sự kiện",
@@ -907,6 +908,9 @@
908 "sync_status_attempting_sync": "ĐANG THỬ ĐỒNG BỘ",
909 "sync_status_connected": "ĐÃ KẾT NỐI",
910 "sync_status_connecting": "ĐANG KẾT NỐI",
911 + "sync_status_display_mode": "Hiển thị trạng thái đồng bộ hóa",
912 + "sync_status_display_mode_blocks": "Hiển thị các khối còn lại",
913 + "sync_status_display_mode_eta": "Hiển thị ETA",
914 "sync_status_failed_connect": "ĐÃ NGẮT KẾT NỐI",
915 "sync_status_not_connected": "CHƯA KẾT NỐI",
916 "sync_status_starting_scan": "ĐANG BẮT ĐẦU QUÉT (${height})",
res/values/strings_yo.arb
+4
@@ -355,6 +355,7 @@
355 "estimated": "Ó tó a fojú díwọ̀n",
356 "estimated_new_fee": "Ifoju tuntun owo tuntun",
357 "estimated_receive_amount": "Ifoju gba iye",
358 + "eta": "Eta",
359 "etherscan_history": "Etherscan itan",
360 "event": "Iṣẹlẹ",
361 "events": "Awọn iṣẹlẹ",
@@ -911,6 +912,9 @@
912 "sync_status_attempting_sync": "Ń GBÌYÀNJÚ MÚDỌ́GBA",
913 "sync_status_connected": "TI DÁRAPỌ̀ MỌ́",
914 "sync_status_connecting": "Ń DÁRAPỌ̀ MỌ́",
915 + "sync_status_display_mode": "Sync Ifihan Ipo",
916 + "sync_status_display_mode_blocks": "Ṣafihan awọn bulọọki ti o ku",
917 + "sync_status_display_mode_eta": "Fihan efa",
918 "sync_status_failed_connect": "ÌKÀNPỌ̀ TI KÚ",
919 "sync_status_not_connected": "KÒ TI DÁRAPỌ̀ MỌ́ Ọ",
920 "sync_status_starting_scan": "Ibẹrẹ ọlọjẹ (lati ${height})",
res/values/strings_zh.arb
+4
@@ -354,6 +354,7 @@
354 "estimated": "估计值",
355 "estimated_new_fee": "估计新费用",
356 "estimated_receive_amount": "估计接收金额",
357 + "eta": "ETA",
358 "etherscan_history": "以太扫描历史",
359 "event": "事件",
360 "events": "活动",
@@ -910,6 +911,9 @@
911 "sync_status_attempting_sync": "嘗試同步",
912 "sync_status_connected": "已连接",
913 "sync_status_connecting": "连接中",
914 + "sync_status_display_mode": "同步状态显示",
915 + "sync_status_display_mode_blocks": "剩下的显示块",
916 + "sync_status_display_mode_eta": "显示ETA",
917 "sync_status_failed_connect": "断线",
918 "sync_status_not_connected": "未连接",
919 "sync_status_starting_scan": "启动扫描(来自 ${height})",