Add bootstrap download dialog and fix 100 missing translation keys
- New BootstrapDownloadDialog accessible from Settings page
- Stops daemon before download, prevents auto-restart during bootstrap
- Confirm/Downloading/Done/Failed states with progress display
- Mirror support (bootstrap2.dragonx.is)
- Add bootstrap_downloading_ flag to prevent tryConnect() auto-reconnect
- Right-align Download Bootstrap + Setup Wizard buttons in settings
- Add 100 missing i18n keys to all 8 language files (de/es/fr/ja/ko/pt/ru/zh)
- Includes bootstrap, explorer, mining benchmark, transfer, delete blockchain,
force quit, address label, and settings section translations
- Update add_missing_translations.py with new translation batch
This commit is contained in:
21
src/app.cpp
21
src/app.cpp
@@ -37,6 +37,7 @@
|
||||
#include "ui/windows/export_transactions_dialog.h"
|
||||
#include "ui/windows/address_label_dialog.h"
|
||||
#include "ui/windows/address_transfer_dialog.h"
|
||||
#include "ui/windows/bootstrap_download_dialog.h"
|
||||
#include "ui/windows/console_tab.h"
|
||||
#include "ui/pages/settings_page.h"
|
||||
#include "ui/theme.h"
|
||||
@@ -630,7 +631,8 @@ void App::update()
|
||||
} else if (core_timer_ >= active_core_interval_) {
|
||||
core_timer_ = 0.0f;
|
||||
if (!connection_in_progress_ &&
|
||||
wizard_phase_ == WizardPhase::None) {
|
||||
wizard_phase_ == WizardPhase::None &&
|
||||
!bootstrap_downloading_) {
|
||||
tryConnect();
|
||||
}
|
||||
}
|
||||
@@ -1356,6 +1358,9 @@ void App::render()
|
||||
// Address-to-address transfer confirmation
|
||||
ui::AddressTransferDialog::render();
|
||||
|
||||
// Bootstrap download from settings
|
||||
ui::BootstrapDownloadDialog::render();
|
||||
|
||||
// Windows Defender antivirus help dialog
|
||||
renderAntivirusHelpDialog();
|
||||
|
||||
@@ -2357,6 +2362,20 @@ void App::deleteBlockchainData()
|
||||
}).detach();
|
||||
}
|
||||
|
||||
bool App::stopDaemonForBootstrap()
|
||||
{
|
||||
bool wasRunning = isEmbeddedDaemonRunning();
|
||||
if (wasRunning) {
|
||||
DEBUG_LOGF("[App] Stopping embedded daemon for bootstrap download...\n");
|
||||
if (rpc_ && rpc_->isConnected()) {
|
||||
try { rpc_->call("stop"); } catch (...) {}
|
||||
rpc_->disconnect();
|
||||
}
|
||||
onDisconnected("Bootstrap");
|
||||
}
|
||||
return wasRunning;
|
||||
}
|
||||
|
||||
double App::getDaemonMemoryUsageMB() const
|
||||
{
|
||||
// If we have an embedded daemon with a tracked process handle, use it
|
||||
|
||||
@@ -273,6 +273,9 @@ public:
|
||||
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use; }
|
||||
void rescanBlockchain(); // restart daemon with -rescan flag
|
||||
void deleteBlockchainData(); // stop daemon, delete chain data, restart fresh
|
||||
bool stopDaemonForBootstrap(); // stop daemon + disconnect for bootstrap, returns true if was running
|
||||
bool isBootstrapDownloading() const { return bootstrap_downloading_; }
|
||||
void setBootstrapDownloading(bool v) { bootstrap_downloading_ = v; }
|
||||
|
||||
// Get daemon memory usage in MB (uses embedded daemon handle if available,
|
||||
// falls back to platform-level process scan for external daemons)
|
||||
@@ -542,6 +545,7 @@ private:
|
||||
// First-run wizard state
|
||||
WizardPhase wizard_phase_ = WizardPhase::None;
|
||||
std::unique_ptr<util::Bootstrap> bootstrap_;
|
||||
bool bootstrap_downloading_ = false; // true while settings bootstrap dialog is active
|
||||
std::string wizard_pending_passphrase_; // held until daemon connects
|
||||
std::string wizard_saved_passphrase_; // held until PinSetup completes/skipped
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,7 @@
|
||||
#include "../windows/block_info_dialog.h"
|
||||
#include "../windows/export_all_keys_dialog.h"
|
||||
#include "../windows/export_transactions_dialog.h"
|
||||
#include "../windows/bootstrap_download_dialog.h"
|
||||
#include "../../embedded/IconsMaterialDesign.h"
|
||||
#include "imgui.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
@@ -1236,6 +1237,7 @@ void RenderSettingsPage(App* app) {
|
||||
TR("tt_export_csv")
|
||||
};
|
||||
const char* wizLabel = TR("setup_wizard");
|
||||
const char* bsLabel = TR("download_bootstrap");
|
||||
float sp = Layout::spacingSm();
|
||||
ImFont* btnFont = S.resolveFont("button");
|
||||
|
||||
@@ -1244,7 +1246,8 @@ void RenderSettingsPage(App* app) {
|
||||
for (int i = 0; i < 5; i++)
|
||||
naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX;
|
||||
float wizW = ImGui::CalcTextSize(wizLabel).x + btnPadX;
|
||||
float totalW = naturalW + wizW + sp * 6;
|
||||
float bsW = ImGui::CalcTextSize(bsLabel).x + btnPadX;
|
||||
float totalW = naturalW + wizW + bsW + sp * 7;
|
||||
|
||||
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
|
||||
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
|
||||
@@ -1271,18 +1274,24 @@ void RenderSettingsPage(App* app) {
|
||||
ExportTransactionsDialog::show();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", t1[4]);
|
||||
|
||||
// Right-align Setup Wizard
|
||||
// Right-align Setup Wizard + Download Bootstrap
|
||||
float framePadX2 = ImGui::GetStyle().FramePadding.x * 2.0f;
|
||||
float curX = ImGui::GetCursorScreenPos().x;
|
||||
float wizBtnW = ImGui::CalcTextSize(wizLabel).x + btnPadX;
|
||||
if (scale < 1.0f) wizBtnW *= scale;
|
||||
float wizBtnW = ImGui::CalcTextSize(wizLabel).x + framePadX2;
|
||||
float bsBtnW = ImGui::CalcTextSize(bsLabel).x + framePadX2;
|
||||
float rightEdge = cardMin.x + availWidth - pad;
|
||||
float wizX = rightEdge - wizBtnW;
|
||||
if (wizX > curX) {
|
||||
float rightGroupW = bsBtnW + scaledSp + wizBtnW;
|
||||
float groupX = rightEdge - rightGroupW;
|
||||
if (groupX > curX) {
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::SetCursorScreenPos(ImVec2(wizX, ImGui::GetCursorScreenPos().y));
|
||||
ImGui::SetCursorScreenPos(ImVec2(groupX, ImGui::GetCursorScreenPos().y));
|
||||
} else {
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
}
|
||||
if (TactileButton(bsLabel, ImVec2(0, 0), btnFont))
|
||||
BootstrapDownloadDialog::show(app);
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_download_bootstrap"));
|
||||
ImGui::SameLine(0, scaledSp);
|
||||
if (TactileButton(wizLabel, ImVec2(0, 0), btnFont))
|
||||
app->restartWizard();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_wizard"));
|
||||
|
||||
309
src/ui/windows/bootstrap_download_dialog.h
Normal file
309
src/ui/windows/bootstrap_download_dialog.h
Normal file
@@ -0,0 +1,309 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "../../app.h"
|
||||
#include "../../util/bootstrap.h"
|
||||
#include "../../util/platform.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../material/type.h"
|
||||
#include "../material/colors.h"
|
||||
#include "../theme.h"
|
||||
#include "../../embedded/IconsMaterialDesign.h"
|
||||
#include "imgui.h"
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
|
||||
/**
|
||||
* @brief Modal dialog for downloading blockchain bootstrap from Settings.
|
||||
*
|
||||
* Reuses the existing Bootstrap class for download/verify/extract.
|
||||
* States: Confirm → Downloading → Done / Failed
|
||||
*/
|
||||
class BootstrapDownloadDialog {
|
||||
public:
|
||||
static void show(App* app) {
|
||||
s_open = true;
|
||||
s_app = app;
|
||||
s_state = State::Confirm;
|
||||
s_bootstrap.reset();
|
||||
s_errorMsg.clear();
|
||||
s_wasDaemonRunning = false;
|
||||
}
|
||||
|
||||
static bool isOpen() { return s_open; }
|
||||
|
||||
static void render() {
|
||||
if (!s_app) return;
|
||||
if (!s_open) {
|
||||
// Dialog was closed — ensure flag is cleared
|
||||
if (s_app->isBootstrapDownloading() && s_state != State::Downloading) {
|
||||
s_app->setBootstrapDownloading(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace material;
|
||||
const float dp = Layout::dpiScale();
|
||||
|
||||
if (BeginOverlayDialog(TR("download_bootstrap"), &s_open, 500.0f, 0.94f)) {
|
||||
|
||||
if (s_state == State::Confirm) {
|
||||
renderConfirm(dp);
|
||||
} else if (s_state == State::Downloading) {
|
||||
renderProgress(dp);
|
||||
} else if (s_state == State::Done) {
|
||||
renderDone(dp);
|
||||
} else if (s_state == State::Failed) {
|
||||
renderFailed(dp);
|
||||
}
|
||||
|
||||
EndOverlayDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
enum class State { Confirm, Downloading, Done, Failed };
|
||||
|
||||
// ---- Confirm screen ----
|
||||
static void renderConfirm(float dp) {
|
||||
using namespace material;
|
||||
ImGui::Spacing();
|
||||
|
||||
// Description
|
||||
ImGui::TextWrapped("%s", TR("bootstrap_desc"));
|
||||
ImGui::Spacing();
|
||||
|
||||
// Warning card
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.9f, 0.6f, 0.0f, 0.08f));
|
||||
ImGui::BeginChild("##bsWarn", ImVec2(0, 0),
|
||||
ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysUseWindowPadding,
|
||||
ImGuiWindowFlags_NoScrollbar);
|
||||
{
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
ImGui::PushFont(iconFont);
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(Warning()), "%s", ICON_MD_WARNING);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextWrapped("%s", TR("bootstrap_warning"));
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Trust warning
|
||||
{
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
ImGui::PushFont(iconFont);
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", ICON_MD_VERIFIED_USER);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()));
|
||||
ImGui::TextWrapped("%s", TR("bootstrap_trust_warning"));
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Buttons: Download | Mirror | Cancel
|
||||
float btnW = 140.0f * dp;
|
||||
float btnSm = 90.0f * dp;
|
||||
|
||||
if (TactileButton(TR("download"), ImVec2(btnW, 0))) {
|
||||
startDownload("");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton(TR("bootstrap_mirror"), ImVec2(btnW, 0))) {
|
||||
std::string mirrorUrl = std::string(util::Bootstrap::kMirrorUrl) + "/" + util::Bootstrap::kZipName;
|
||||
startDownload(mirrorUrl);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("%s", TR("bootstrap_mirror_tooltip"));
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton(TR("cancel"), ImVec2(btnSm, 0))) {
|
||||
s_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Progress screen ----
|
||||
static void renderProgress(float dp) {
|
||||
using namespace material;
|
||||
|
||||
if (!s_bootstrap) {
|
||||
s_state = State::Failed;
|
||||
s_errorMsg = "Bootstrap not initialized";
|
||||
return;
|
||||
}
|
||||
|
||||
auto prog = s_bootstrap->getProgress();
|
||||
|
||||
// Status title
|
||||
const char* statusTitle;
|
||||
if (prog.state == util::Bootstrap::State::Downloading)
|
||||
statusTitle = TR("bootstrap_downloading");
|
||||
else if (prog.state == util::Bootstrap::State::Verifying)
|
||||
statusTitle = TR("bootstrap_verifying");
|
||||
else
|
||||
statusTitle = TR("bootstrap_extracting");
|
||||
Type().text(TypeStyle::Subtitle2, statusTitle);
|
||||
ImGui::Spacing();
|
||||
|
||||
// Progress bar
|
||||
float barH = 8.0f * dp;
|
||||
float barW = ImGui::GetContentRegionAvail().x;
|
||||
ImVec2 barMin = ImGui::GetCursorScreenPos();
|
||||
ImVec2 barMax(barMin.x + barW, barMin.y + barH);
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
dl->AddRectFilled(barMin, barMax, IM_COL32(255, 255, 255, 30), 4.0f * dp);
|
||||
float fillW = barW * (prog.percent / 100.0f);
|
||||
if (fillW > 0) {
|
||||
dl->AddRectFilled(barMin, ImVec2(barMin.x + fillW, barMax.y),
|
||||
Primary(), 4.0f * dp);
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, barH));
|
||||
ImGui::Spacing();
|
||||
|
||||
// Percent + status text
|
||||
{
|
||||
char pctBuf[32];
|
||||
snprintf(pctBuf, sizeof(pctBuf), "%.1f%%", prog.percent);
|
||||
float pctW = ImGui::CalcTextSize(pctBuf).x;
|
||||
ImGui::Text("%s", prog.status_text.c_str());
|
||||
ImGui::SameLine(ImGui::GetContentRegionAvail().x - pctW + ImGui::GetCursorPosX());
|
||||
ImGui::Text("%s", pctBuf);
|
||||
}
|
||||
|
||||
// wallet.dat protection notice during extraction
|
||||
if (prog.state == util::Bootstrap::State::Extracting) {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("bootstrap_wallet_protected"));
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Daemon status indicator
|
||||
{
|
||||
bool daemonUp = s_app->isEmbeddedDaemonRunning();
|
||||
const std::string& dStatus = s_app->getDaemonStatus();
|
||||
ImU32 dotCol = daemonUp ? IM_COL32(76, 175, 80, 200)
|
||||
: IM_COL32(120, 120, 120, 160);
|
||||
if (dStatus.find("Stopping") != std::string::npos)
|
||||
dotCol = IM_COL32(255, 167, 38, 200);
|
||||
const char* label = daemonUp ? (dStatus.find("Stopping") != std::string::npos
|
||||
? TR("bootstrap_daemon_stopping")
|
||||
: TR("bootstrap_daemon_running"))
|
||||
: TR("bootstrap_daemon_stopped");
|
||||
|
||||
ImDrawList* ddl = ImGui::GetWindowDrawList();
|
||||
float dotR = 3.5f * dp;
|
||||
ImVec2 cp = ImGui::GetCursorScreenPos();
|
||||
ddl->AddCircleFilled(ImVec2(cp.x + dotR, cp.y + ImGui::GetTextLineHeight() * 0.5f),
|
||||
dotR, dotCol);
|
||||
ImGui::Indent(dotR * 2.0f + 6.0f * dp);
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), label);
|
||||
ImGui::Unindent(dotR * 2.0f + 6.0f * dp);
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Cancel button
|
||||
float btnW = 100.0f * dp;
|
||||
if (TactileButton(TR("cancel"), ImVec2(btnW, 0))) {
|
||||
s_bootstrap->cancel();
|
||||
}
|
||||
|
||||
// Check completion
|
||||
if (s_bootstrap->isDone()) {
|
||||
auto finalProg = s_bootstrap->getProgress();
|
||||
if (finalProg.state == util::Bootstrap::State::Completed) {
|
||||
s_state = State::Done;
|
||||
} else {
|
||||
s_errorMsg = finalProg.error;
|
||||
if (s_errorMsg.empty()) s_errorMsg = "Bootstrap failed";
|
||||
s_state = State::Failed;
|
||||
}
|
||||
s_bootstrap.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Done screen ----
|
||||
static void renderDone(float dp) {
|
||||
using namespace material;
|
||||
ImGui::Spacing();
|
||||
|
||||
Type().textColored(TypeStyle::H6, Success(), TR("bootstrap_success"));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", TR("bootstrap_success_desc"));
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = 140.0f * dp;
|
||||
if (s_wasDaemonRunning) {
|
||||
if (TactileButton(TR("bootstrap_restart_daemon"), ImVec2(btnW, 0))) {
|
||||
s_app->setBootstrapDownloading(false);
|
||||
s_app->startEmbeddedDaemon();
|
||||
s_open = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (TactileButton(TR("close"), ImVec2(90.0f * dp, 0))) {
|
||||
s_app->setBootstrapDownloading(false);
|
||||
s_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Failed screen ----
|
||||
static void renderFailed(float dp) {
|
||||
using namespace material;
|
||||
ImGui::Spacing();
|
||||
|
||||
Type().textColored(TypeStyle::H6, Error(), TR("bootstrap_failed"));
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("%s", s_errorMsg.c_str());
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
float btnW = 120.0f * dp;
|
||||
if (TactileButton(TR("retry"), ImVec2(btnW, 0))) {
|
||||
startDownload("");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton(TR("close"), ImVec2(90.0f * dp, 0))) {
|
||||
s_app->setBootstrapDownloading(false);
|
||||
s_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Shared: kick off download ----
|
||||
static void startDownload(const std::string& url) {
|
||||
s_wasDaemonRunning = s_app->stopDaemonForBootstrap();
|
||||
s_app->setBootstrapDownloading(true);
|
||||
s_bootstrap = std::make_unique<util::Bootstrap>();
|
||||
std::string dataDir = util::Platform::getDragonXDataDir();
|
||||
if (url.empty())
|
||||
s_bootstrap->start(dataDir);
|
||||
else
|
||||
s_bootstrap->start(dataDir, url);
|
||||
s_state = State::Downloading;
|
||||
s_errorMsg.clear();
|
||||
}
|
||||
|
||||
static inline bool s_open = false;
|
||||
static inline App* s_app = nullptr;
|
||||
static inline State s_state = State::Confirm;
|
||||
static inline std::unique_ptr<util::Bootstrap> s_bootstrap;
|
||||
static inline bool s_wasDaemonRunning = false;
|
||||
static inline std::string s_errorMsg;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
@@ -334,6 +334,26 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["tt_mine_idle"] = "Automatically start mining when the\nsystem is idle (no keyboard/mouse input)";
|
||||
strings_["tt_idle_delay"] = "How long to wait before starting mining";
|
||||
strings_["tt_wizard"] = "Re-run the initial setup wizard\nDaemon will be restarted";
|
||||
strings_["tt_download_bootstrap"] = "Download blockchain bootstrap to speed up sync\nExisting block data will be replaced";
|
||||
strings_["download_bootstrap"] = "Download Bootstrap";
|
||||
strings_["download"] = "Download";
|
||||
strings_["retry"] = "Retry";
|
||||
strings_["bootstrap_desc"] = "Download a blockchain bootstrap to dramatically speed up initial sync. This downloads a snapshot of the blockchain and extracts it into your data directory.";
|
||||
strings_["bootstrap_warning"] = "Existing block data (blocks, chainstate, notarizations) will be deleted and replaced. Your wallet.dat will NOT be modified or deleted.";
|
||||
strings_["bootstrap_trust_warning"] = "Only use bootstrap.dragonx.is or bootstrap2.dragonx.is. Using files from untrusted sources could compromise your node.";
|
||||
strings_["bootstrap_mirror"] = "Mirror";
|
||||
strings_["bootstrap_mirror_tooltip"] = "Download from mirror (bootstrap2.dragonx.is).\nUse this if the main download is slow or failing.";
|
||||
strings_["bootstrap_downloading"] = "Downloading bootstrap...";
|
||||
strings_["bootstrap_verifying"] = "Verifying checksums...";
|
||||
strings_["bootstrap_extracting"] = "Extracting blockchain data...";
|
||||
strings_["bootstrap_wallet_protected"] = "(wallet.dat is protected)";
|
||||
strings_["bootstrap_daemon_stopping"] = "Daemon stopping...";
|
||||
strings_["bootstrap_daemon_running"] = "Daemon running";
|
||||
strings_["bootstrap_daemon_stopped"] = "Daemon stopped";
|
||||
strings_["bootstrap_success"] = "Bootstrap Complete";
|
||||
strings_["bootstrap_success_desc"] = "Blockchain data has been extracted successfully. Start the daemon to begin syncing from the bootstrap point.";
|
||||
strings_["bootstrap_restart_daemon"] = "Restart Daemon";
|
||||
strings_["bootstrap_failed"] = "Bootstrap Failed";
|
||||
strings_["tt_open_dir"] = "Click to open in file explorer";
|
||||
strings_["tt_rpc_host"] = "Hostname of the DragonX daemon";
|
||||
strings_["tt_rpc_user"] = "RPC authentication username";
|
||||
|
||||
Reference in New Issue
Block a user