feat: Full UI internationalization, pool hashrate stats, and layout caching
- Replace all hardcoded English strings with TR() translation keys across every tab, dialog, and component (~20 UI files) - Expand all 8 language files (de, es, fr, ja, ko, pt, ru, zh) with complete translations (~37k lines added) - Improve i18n loader with exe-relative path fallback and English base fallback for missing keys - Add pool-side hashrate polling via pool stats API in xmrig_manager - Introduce Layout::beginFrame() per-frame caching and refresh balance layout config only on schema generation change - Offload daemon output parsing to worker thread - Add CJK subset fallback font for Chinese/Japanese/Korean glyphs
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include "../material/color_theme.h"
|
||||
#include "../theme.h"
|
||||
#include "../../embedded/IconsMaterialDesign.h"
|
||||
#include "../../util/i18n.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <cstring>
|
||||
@@ -75,8 +76,8 @@ ConsoleTab::ConsoleTab()
|
||||
refreshColors();
|
||||
|
||||
// Add welcome message
|
||||
addLine("DragonX Wallet Console", COLOR_INFO);
|
||||
addLine("Type 'help' for available commands", COLOR_INFO);
|
||||
addLine(TR("console_welcome"), COLOR_INFO);
|
||||
addLine(TR("console_type_help"), COLOR_INFO);
|
||||
addLine("", COLOR_RESULT);
|
||||
}
|
||||
|
||||
@@ -117,27 +118,38 @@ void ConsoleTab::render(daemon::EmbeddedDaemon* daemon, rpc::RPCClient* rpc, rpc
|
||||
if (current_state == daemon::EmbeddedDaemon::State::Starting &&
|
||||
last_daemon_state_ == daemon::EmbeddedDaemon::State::Stopped) {
|
||||
addLine("", COLOR_RESULT);
|
||||
addLine("=== Starting DragonX Full Node ===", COLOR_INFO);
|
||||
addLine("Capturing daemon output...", COLOR_INFO);
|
||||
addLine(TR("console_starting_node"), COLOR_INFO);
|
||||
addLine(TR("console_capturing_output"), COLOR_INFO);
|
||||
addLine("", COLOR_RESULT);
|
||||
shown_startup_message_ = true;
|
||||
}
|
||||
else if (current_state == daemon::EmbeddedDaemon::State::Running &&
|
||||
last_daemon_state_ != daemon::EmbeddedDaemon::State::Running) {
|
||||
addLine("=== Daemon is running ===", COLOR_INFO);
|
||||
addLine(TR("console_daemon_started"), COLOR_INFO);
|
||||
}
|
||||
else if (current_state == daemon::EmbeddedDaemon::State::Stopped &&
|
||||
last_daemon_state_ == daemon::EmbeddedDaemon::State::Running) {
|
||||
addLine("", COLOR_RESULT);
|
||||
addLine("=== Daemon stopped ===", COLOR_INFO);
|
||||
addLine(TR("console_daemon_stopped"), COLOR_INFO);
|
||||
}
|
||||
else if (current_state == daemon::EmbeddedDaemon::State::Error) {
|
||||
addLine("=== Daemon error: " + daemon->getLastError() + " ===", COLOR_ERROR);
|
||||
addLine(std::string(TR("console_daemon_error")) + daemon->getLastError() + " ===", COLOR_ERROR);
|
||||
}
|
||||
|
||||
last_daemon_state_ = current_state;
|
||||
}
|
||||
|
||||
|
||||
// Track RPC connection state and show a message when connected
|
||||
if (rpc) {
|
||||
bool connected_now = rpc->isConnected();
|
||||
if (connected_now && !last_rpc_connected_) {
|
||||
addLine(TR("console_connected"), COLOR_INFO);
|
||||
} else if (!connected_now && last_rpc_connected_) {
|
||||
addLine(TR("console_disconnected"), COLOR_ERROR);
|
||||
}
|
||||
last_rpc_connected_ = connected_now;
|
||||
}
|
||||
|
||||
// Check for new daemon output — always capture so toggle works as a live filter
|
||||
if (daemon) {
|
||||
std::string new_output = daemon->getOutputSince(last_daemon_output_size_);
|
||||
@@ -372,31 +384,31 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
// Daemon status with colored dot
|
||||
if (daemon) {
|
||||
auto state = daemon->getState();
|
||||
const char* status_text = "Unknown";
|
||||
const char* status_text = TR("console_status_unknown");
|
||||
ImU32 dotCol = IM_COL32(150, 150, 150, 255);
|
||||
bool pulse = false;
|
||||
|
||||
switch (state) {
|
||||
case daemon::EmbeddedDaemon::State::Stopped:
|
||||
status_text = "Stopped";
|
||||
status_text = TR("console_status_stopped");
|
||||
dotCol = IM_COL32(150, 150, 150, 255);
|
||||
break;
|
||||
case daemon::EmbeddedDaemon::State::Starting:
|
||||
status_text = "Starting";
|
||||
status_text = TR("console_status_starting");
|
||||
dotCol = Warning();
|
||||
pulse = true;
|
||||
break;
|
||||
case daemon::EmbeddedDaemon::State::Running:
|
||||
status_text = "Running";
|
||||
status_text = TR("console_status_running");
|
||||
dotCol = Success();
|
||||
break;
|
||||
case daemon::EmbeddedDaemon::State::Stopping:
|
||||
status_text = "Stopping";
|
||||
status_text = TR("console_status_stopping");
|
||||
dotCol = Warning();
|
||||
pulse = true;
|
||||
break;
|
||||
case daemon::EmbeddedDaemon::State::Error:
|
||||
status_text = "Error";
|
||||
status_text = TR("console_status_error");
|
||||
dotCol = Error();
|
||||
break;
|
||||
}
|
||||
@@ -418,7 +430,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
ImGui::SameLine();
|
||||
Type().textColored(TypeStyle::Caption, dotCol, status_text);
|
||||
} else {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), "No daemon");
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("console_no_daemon"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -426,7 +438,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
ImGui::SameLine();
|
||||
|
||||
// Auto-scroll toggle
|
||||
if (ImGui::Checkbox("Auto-scroll", &auto_scroll_)) {
|
||||
if (ImGui::Checkbox(TR("console_auto_scroll"), &auto_scroll_)) {
|
||||
if (auto_scroll_) {
|
||||
scroll_to_bottom_ = true;
|
||||
new_lines_since_scroll_ = 0;
|
||||
@@ -440,7 +452,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
// Daemon messages toggle
|
||||
{
|
||||
static bool s_prev_daemon_enabled = true;
|
||||
ImGui::Checkbox("Daemon", &s_daemon_messages_enabled);
|
||||
ImGui::Checkbox(TR("console_daemon"), &s_daemon_messages_enabled);
|
||||
// When toggling daemon filter while auto-scroll is active, scroll to bottom
|
||||
if (s_prev_daemon_enabled != s_daemon_messages_enabled && auto_scroll_) {
|
||||
scroll_to_bottom_ = true;
|
||||
@@ -448,7 +460,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
s_prev_daemon_enabled = s_daemon_messages_enabled;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Show daemon output messages");
|
||||
ImGui::SetTooltip("%s", TR("console_show_daemon_output"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -458,7 +470,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
// Errors-only toggle
|
||||
{
|
||||
static bool s_prev_errors_only = false;
|
||||
ImGui::Checkbox("Errors", &s_errors_only_enabled);
|
||||
ImGui::Checkbox(TR("console_errors"), &s_errors_only_enabled);
|
||||
// When toggling errors filter while auto-scroll is active, scroll to bottom
|
||||
if (s_prev_errors_only != s_errors_only_enabled && auto_scroll_) {
|
||||
scroll_to_bottom_ = true;
|
||||
@@ -466,7 +478,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
s_prev_errors_only = s_errors_only_enabled;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Show only error messages");
|
||||
ImGui::SetTooltip("%s", TR("console_show_errors_only"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -474,7 +486,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
ImGui::SameLine();
|
||||
|
||||
// Clear button
|
||||
if (TactileButton("Clear", ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
if (TactileButton(TR("console_clear"), ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
clear();
|
||||
clearSelection();
|
||||
}
|
||||
@@ -482,7 +494,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
ImGui::SameLine();
|
||||
|
||||
// Copy button — material styled
|
||||
if (TactileButton("Copy", ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
if (TactileButton(TR("copy"), ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
std::lock_guard<std::mutex> lock(lines_mutex_);
|
||||
if (has_selection_) {
|
||||
std::string selected = getSelectedText();
|
||||
@@ -501,17 +513,17 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(has_selection_ ? "Copy selected text" : "Copy all output");
|
||||
ImGui::SetTooltip("%s", has_selection_ ? TR("console_copy_selected") : TR("console_copy_all"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Commands reference button
|
||||
if (TactileButton("Commands", ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
if (TactileButton(TR("console_commands"), ImVec2(0, 0), schema::UI().resolveFont("button"))) {
|
||||
show_commands_popup_ = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Show RPC command reference");
|
||||
ImGui::SetTooltip("%s", TR("console_show_rpc_ref"));
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -519,7 +531,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
// Line count
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(lines_mutex_);
|
||||
ImGui::TextDisabled("(%zu lines)", lines_.size());
|
||||
ImGui::TextDisabled(TR("console_line_count"), lines_.size());
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -531,7 +543,7 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
float filterAvail = ImGui::GetContentRegionAvail().x - zoomBtnSpace;
|
||||
float filterW = std::min(schema::UI().drawElement("tabs.console", "filter-max-width").size, filterAvail * schema::UI().drawElement("tabs.console", "filter-width-ratio").size);
|
||||
ImGui::SetNextItemWidth(filterW);
|
||||
ImGui::InputTextWithHint("##ConsoleFilter", "Filter output...", filter_text_, sizeof(filter_text_));
|
||||
ImGui::InputTextWithHint("##ConsoleFilter", TR("console_filter_hint"), filter_text_, sizeof(filter_text_));
|
||||
|
||||
// Zoom +/- buttons (right side of toolbar)
|
||||
ImGui::SameLine();
|
||||
@@ -548,14 +560,14 @@ void ConsoleTab::renderToolbar(daemon::EmbeddedDaemon* daemon)
|
||||
s_console_zoom = std::max(zoomMin, s_console_zoom - zoomStep);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Zoom out (%.0f%%)", s_console_zoom * 100.0f);
|
||||
ImGui::SetTooltip(TR("console_zoom_out"), s_console_zoom * 100.0f);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (TactileButton(ICON_MD_ADD, ImVec2(btnSz, btnSz), Type().iconMed())) {
|
||||
s_console_zoom = std::min(zoomMax, s_console_zoom + zoomStep);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Zoom in (%.0f%%)", s_console_zoom * 100.0f);
|
||||
ImGui::SetTooltip(TR("console_zoom_in"), s_console_zoom * 100.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -889,7 +901,7 @@ void ConsoleTab::renderOutput()
|
||||
// Filter indicator (text filter only — daemon toggle is already visible in toolbar)
|
||||
if (has_text_filter) {
|
||||
char filterBuf[128];
|
||||
snprintf(filterBuf, sizeof(filterBuf), "Showing %d of %zu lines",
|
||||
snprintf(filterBuf, sizeof(filterBuf), TR("console_showing_lines"),
|
||||
visible_count, lines_.size());
|
||||
ImVec2 indicatorPos = ImGui::GetCursorScreenPos();
|
||||
ImGui::GetWindowDrawList()->AddText(indicatorPos,
|
||||
@@ -899,13 +911,13 @@ void ConsoleTab::renderOutput()
|
||||
|
||||
// Right-click context menu
|
||||
if (ImGui::BeginPopupContextWindow("ConsoleContextMenu")) {
|
||||
if (ImGui::MenuItem("Copy", "Ctrl+C", false, has_selection_)) {
|
||||
if (ImGui::MenuItem(TR("copy"), "Ctrl+C", false, has_selection_)) {
|
||||
std::string selected = getSelectedText();
|
||||
if (!selected.empty()) {
|
||||
ImGui::SetClipboardText(selected.c_str());
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem("Select All", "Ctrl+A")) {
|
||||
if (ImGui::MenuItem(TR("console_select_all"), "Ctrl+A")) {
|
||||
if (!lines_.empty()) {
|
||||
sel_anchor_ = {0, 0};
|
||||
sel_end_ = {static_cast<int>(lines_.size()) - 1,
|
||||
@@ -914,7 +926,7 @@ void ConsoleTab::renderOutput()
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Clear Console")) {
|
||||
if (ImGui::MenuItem(TR("console_clear_console"))) {
|
||||
// Can't call clear() here (already holding lock), just mark for clearing
|
||||
lines_.clear();
|
||||
has_selection_ = false;
|
||||
@@ -938,7 +950,7 @@ void ConsoleTab::renderOutput()
|
||||
dlInd->AddRect(iMin, iMax, IM_COL32(255, 218, 0, 120), 12.0f);
|
||||
|
||||
char buf[48];
|
||||
snprintf(buf, sizeof(buf), " %d new line%s",
|
||||
snprintf(buf, sizeof(buf), TR("console_new_lines"),
|
||||
new_lines_since_scroll_, new_lines_since_scroll_ != 1 ? "s" : "");
|
||||
ImFont* capFont = Type().caption();
|
||||
if (!capFont) capFont = ImGui::GetFont();
|
||||
@@ -1229,7 +1241,7 @@ void ConsoleTab::renderInput(rpc::RPCClient* rpc, rpc::RPCWorker* worker)
|
||||
data->InsertChars(0, matches[0]);
|
||||
} else if (matches.size() > 1) {
|
||||
// Multiple matches — show list in console and complete common prefix
|
||||
console->addLine("Completions:", ConsoleTab::COLOR_INFO);
|
||||
console->addLine(TR("console_completions"), ConsoleTab::COLOR_INFO);
|
||||
std::string line = " ";
|
||||
for (size_t m = 0; m < matches.size(); m++) {
|
||||
if (m > 0) line += " ";
|
||||
@@ -1284,17 +1296,17 @@ void ConsoleTab::renderCommandsPopup()
|
||||
using namespace material;
|
||||
|
||||
float popW = std::min(schema::UI().drawElement("tabs.console", "popup-max-width").size, ImGui::GetMainViewport()->Size.x * schema::UI().drawElement("tabs.console", "popup-width-ratio").size);
|
||||
if (!material::BeginOverlayDialog("RPC Command Reference", &show_commands_popup_, popW, 0.94f)) {
|
||||
if (!material::BeginOverlayDialog(TR("console_rpc_reference"), &show_commands_popup_, popW, 0.94f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Type().text(TypeStyle::H6, "RPC Command Reference");
|
||||
Type().text(TypeStyle::H6, TR("console_rpc_reference"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// Search filter
|
||||
static char cmdFilter[128] = {0};
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputTextWithHint("##CmdSearch", "Search commands...", cmdFilter, sizeof(cmdFilter));
|
||||
ImGui::InputTextWithHint("##CmdSearch", TR("console_search_commands"), cmdFilter, sizeof(cmdFilter));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
// Command entries
|
||||
@@ -1507,9 +1519,9 @@ void ConsoleTab::renderCommandsPopup()
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (cmd.params[0] != '\0')
|
||||
ImGui::SetTooltip("Click to insert '%s %s' into input", cmd.name, cmd.params);
|
||||
ImGui::SetTooltip(TR("console_click_insert_params"), cmd.name, cmd.params);
|
||||
else
|
||||
ImGui::SetTooltip("Click to insert '%s' into input", cmd.name);
|
||||
ImGui::SetTooltip(TR("console_click_insert"), cmd.name);
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
if (showParams) {
|
||||
@@ -1575,7 +1587,7 @@ void ConsoleTab::renderCommandsPopup()
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
// Close button
|
||||
if (ImGui::Button("Close", ImVec2(-1, 0))) {
|
||||
if (ImGui::Button(TR("console_close"), ImVec2(-1, 0))) {
|
||||
cmdFilter[0] = '\0';
|
||||
show_commands_popup_ = false;
|
||||
}
|
||||
@@ -1605,28 +1617,28 @@ void ConsoleTab::executeCommand(const std::string& cmd, rpc::RPCClient* rpc, rpc
|
||||
}
|
||||
|
||||
if (cmd == "help") {
|
||||
addLine("Available commands:", COLOR_INFO);
|
||||
addLine(" clear - Clear console output", COLOR_RESULT);
|
||||
addLine(" help - Show this help", COLOR_RESULT);
|
||||
addLine(TR("console_available_commands"), COLOR_INFO);
|
||||
addLine(TR("console_help_clear"), COLOR_RESULT);
|
||||
addLine(TR("console_help_help"), COLOR_RESULT);
|
||||
addLine("", COLOR_RESULT);
|
||||
addLine("Common RPC commands (when connected):", COLOR_INFO);
|
||||
addLine(" getinfo - Get daemon info", COLOR_RESULT);
|
||||
addLine(" getbalance - Get transparent balance", COLOR_RESULT);
|
||||
addLine(" z_gettotalbalance - Get total balance", COLOR_RESULT);
|
||||
addLine(" getblockcount - Get current block height", COLOR_RESULT);
|
||||
addLine(" getpeerinfo - Get connected peers", COLOR_RESULT);
|
||||
addLine(" setgenerate true/false [threads] - Mining on/off", COLOR_RESULT);
|
||||
addLine(" getmininginfo - Get mining status", COLOR_RESULT);
|
||||
addLine(" stop - Stop the daemon", COLOR_RESULT);
|
||||
addLine(TR("console_common_rpc"), COLOR_INFO);
|
||||
addLine(TR("console_help_getinfo"), COLOR_RESULT);
|
||||
addLine(TR("console_help_getbalance"), COLOR_RESULT);
|
||||
addLine(TR("console_help_gettotalbalance"), COLOR_RESULT);
|
||||
addLine(TR("console_help_getblockcount"), COLOR_RESULT);
|
||||
addLine(TR("console_help_getpeerinfo"), COLOR_RESULT);
|
||||
addLine(TR("console_help_setgenerate"), COLOR_RESULT);
|
||||
addLine(TR("console_help_getmininginfo"), COLOR_RESULT);
|
||||
addLine(TR("console_help_stop"), COLOR_RESULT);
|
||||
addLine("", COLOR_RESULT);
|
||||
addLine("Click 'Commands' in the toolbar for full RPC reference", COLOR_INFO);
|
||||
addLine("Use Tab for command completion, Up/Down for history", COLOR_INFO);
|
||||
addLine(TR("console_click_commands"), COLOR_INFO);
|
||||
addLine(TR("console_tab_completion"), COLOR_INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute RPC command
|
||||
if (!rpc || !rpc->isConnected()) {
|
||||
addLine("Error: Not connected to daemon", COLOR_ERROR);
|
||||
addLine(TR("console_not_connected"), COLOR_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1839,7 +1851,7 @@ void ConsoleTab::clear()
|
||||
last_xmrig_output_size_ = 0;
|
||||
}
|
||||
// addLine() takes the lock itself, so call it outside the locked scope
|
||||
addLine("Console cleared", COLOR_INFO);
|
||||
addLine(TR("console_cleared"), COLOR_INFO);
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
Reference in New Issue
Block a user