ObsidianDragon - DragonX ImGui Wallet

Full-node GUI wallet for DragonX cryptocurrency.
Built with Dear ImGui, SDL3, and OpenGL3/DX11.

Features:
- Send/receive shielded and transparent transactions
- Autoshield with merged transaction display
- Built-in CPU mining (xmrig)
- Peer management and network monitoring
- Wallet encryption with PIN lock
- QR code generation for receive addresses
- Transaction history with pagination
- Console for direct RPC commands
- Cross-platform (Linux, Windows)
This commit is contained in:
2026-02-26 02:31:52 -06:00
commit 3aee55b49c
306 changed files with 177789 additions and 0 deletions

321
src/util/i18n.cpp Normal file
View File

@@ -0,0 +1,321 @@
// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "i18n.h"
#include <fstream>
#include <cstdio>
#include <cstring>
#include <nlohmann/json.hpp>
// Embedded language files
#include "embedded/lang_es.h"
#include "embedded/lang_zh.h"
#include "embedded/lang_ru.h"
#include "embedded/lang_de.h"
#include "embedded/lang_fr.h"
#include "embedded/lang_pt.h"
#include "embedded/lang_ja.h"
#include "embedded/lang_ko.h"
#include "../util/logger.h"
namespace dragonx {
namespace util {
using json = nlohmann::json;
I18n::I18n()
{
// Register built-in languages
registerLanguage("en", "English");
registerLanguage("es", "Español");
registerLanguage("zh", "中文");
registerLanguage("ru", "Русский");
registerLanguage("de", "Deutsch");
registerLanguage("fr", "Français");
registerLanguage("pt", "Português");
registerLanguage("ja", "日本語");
registerLanguage("ko", "한국어");
// Load default English strings (built-in fallback)
loadLanguage("en");
}
I18n& I18n::instance()
{
static I18n instance;
return instance;
}
bool I18n::loadLanguage(const std::string& locale)
{
// First, try to load from file (allows user overrides)
std::string lang_file = "res/lang/" + locale + ".json";
std::ifstream file(lang_file);
if (file.is_open()) {
try {
json j;
file >> j;
strings_.clear();
for (auto& [key, value] : j.items()) {
if (value.is_string()) {
strings_[key] = value.get<std::string>();
}
}
current_locale_ = locale;
DEBUG_LOGF("Loaded language file: %s (%zu strings)\n", lang_file.c_str(), strings_.size());
return true;
} catch (const std::exception& e) {
DEBUG_LOGF("Error parsing language file %s: %s\n", lang_file.c_str(), e.what());
}
}
// Try embedded language data
const unsigned char* embedded_data = nullptr;
unsigned int embedded_size = 0;
if (locale == "es") {
embedded_data = res_lang_es_json;
embedded_size = res_lang_es_json_len;
} else if (locale == "zh") {
embedded_data = res_lang_zh_json;
embedded_size = res_lang_zh_json_len;
} else if (locale == "ru") {
embedded_data = res_lang_ru_json;
embedded_size = res_lang_ru_json_len;
} else if (locale == "de") {
embedded_data = res_lang_de_json;
embedded_size = res_lang_de_json_len;
} else if (locale == "fr") {
embedded_data = res_lang_fr_json;
embedded_size = res_lang_fr_json_len;
} else if (locale == "pt") {
embedded_data = res_lang_pt_json;
embedded_size = res_lang_pt_json_len;
} else if (locale == "ja") {
embedded_data = res_lang_ja_json;
embedded_size = res_lang_ja_json_len;
} else if (locale == "ko") {
embedded_data = res_lang_ko_json;
embedded_size = res_lang_ko_json_len;
}
if (embedded_data != nullptr && embedded_size > 0) {
try {
std::string json_str(reinterpret_cast<const char*>(embedded_data), embedded_size);
json j = json::parse(json_str);
strings_.clear();
for (auto& [key, value] : j.items()) {
if (value.is_string()) {
strings_[key] = value.get<std::string>();
}
}
current_locale_ = locale;
DEBUG_LOGF("Loaded embedded language: %s (%zu strings)\n", locale.c_str(), strings_.size());
return true;
} catch (const std::exception& e) {
DEBUG_LOGF("Error parsing embedded language %s: %s\n", locale.c_str(), e.what());
}
}
// If English, use built-in strings
if (locale == "en") {
strings_.clear();
// Navigation & Tabs
strings_["balance"] = "Balance";
strings_["send"] = "Send";
strings_["receive"] = "Receive";
strings_["transactions"] = "Transactions";
strings_["mining"] = "Mining";
strings_["peers"] = "Peers";
strings_["market"] = "Market";
strings_["settings"] = "Settings";
// Balance Tab
strings_["summary"] = "Summary";
strings_["shielded"] = "Shielded";
strings_["transparent"] = "Transparent";
strings_["total"] = "Total";
strings_["unconfirmed"] = "Unconfirmed";
strings_["your_addresses"] = "Your Addresses";
strings_["z_addresses"] = "Z-Addresses";
strings_["t_addresses"] = "T-Addresses";
strings_["no_addresses"] = "No addresses found. Create one using the buttons above.";
strings_["new_z_address"] = "New Z-Address";
strings_["new_t_address"] = "New T-Address";
strings_["type"] = "Type";
strings_["address"] = "Address";
strings_["copy_address"] = "Copy Full Address";
strings_["send_from_this_address"] = "Send From This Address";
strings_["export_private_key"] = "Export Private Key";
strings_["export_viewing_key"] = "Export Viewing Key";
strings_["show_qr_code"] = "Show QR Code";
strings_["not_connected"] = "Not connected to daemon...";
// Send Tab
strings_["pay_from"] = "Pay From";
strings_["send_to"] = "Send To";
strings_["amount"] = "Amount";
strings_["memo"] = "Memo (optional, encrypted)";
strings_["miner_fee"] = "Miner Fee";
strings_["fee"] = "Fee";
strings_["send_transaction"] = "Send Transaction";
strings_["clear"] = "Clear";
strings_["select_address"] = "Select address...";
strings_["paste"] = "Paste";
strings_["max"] = "Max";
strings_["available"] = "Available";
strings_["invalid_address"] = "Invalid address format";
strings_["memo_z_only"] = "Note: Memos are only available when sending to shielded (z) addresses";
strings_["characters"] = "characters";
strings_["from"] = "From";
strings_["to"] = "To";
strings_["sending"] = "Sending transaction";
strings_["confirm_send"] = "Confirm Send";
strings_["confirm_transaction"] = "Confirm Transaction";
strings_["confirm_and_send"] = "Confirm & Send";
strings_["cancel"] = "Cancel";
// Receive Tab
strings_["receiving_addresses"] = "Your Receiving Addresses";
strings_["new_z_shielded"] = "New z-Address (Shielded)";
strings_["new_t_transparent"] = "New t-Address (Transparent)";
strings_["address_details"] = "Address Details";
strings_["view_on_explorer"] = "View on Explorer";
strings_["qr_code"] = "QR Code";
strings_["request_payment"] = "Request Payment";
// Transactions Tab
strings_["date"] = "Date";
strings_["status"] = "Status";
strings_["confirmations"] = "Confirmations";
strings_["confirmed"] = "Confirmed";
strings_["pending"] = "Pending";
strings_["sent"] = "sent";
strings_["received"] = "received";
strings_["mined"] = "mined";
// Mining Tab
strings_["mining_control"] = "Mining Control";
strings_["start_mining"] = "Start Mining";
strings_["stop_mining"] = "Stop Mining";
strings_["mining_threads"] = "Mining Threads";
strings_["mining_statistics"] = "Mining Statistics";
strings_["local_hashrate"] = "Local Hashrate";
strings_["network_hashrate"] = "Network Hashrate";
strings_["difficulty"] = "Difficulty";
strings_["est_time_to_block"] = "Est. Time to Block";
strings_["mining_off"] = "Mining is OFF";
strings_["mining_on"] = "Mining is ON";
// Peers Tab
strings_["connected_peers"] = "Connected Peers";
strings_["banned_peers"] = "Banned Peers";
strings_["ip_address"] = "IP Address";
strings_["version"] = "Version";
strings_["height"] = "Height";
strings_["ping"] = "Ping";
strings_["ban"] = "Ban";
strings_["unban"] = "Unban";
strings_["clear_all_bans"] = "Clear All Bans";
// Market Tab
strings_["price_chart"] = "Price Chart";
strings_["current_price"] = "Current Price";
strings_["24h_change"] = "24h Change";
strings_["24h_volume"] = "24h Volume";
strings_["market_cap"] = "Market Cap";
// Settings
strings_["general"] = "General";
strings_["display"] = "Display";
strings_["network"] = "Network";
strings_["theme"] = "Theme";
strings_["language"] = "Language";
strings_["dragonx_green"] = "DragonX (Green)";
strings_["dark"] = "Dark";
strings_["light"] = "Light";
strings_["allow_custom_fees"] = "Allow custom fees";
strings_["use_embedded_daemon"] = "Use embedded dragonxd";
strings_["save"] = "Save";
strings_["close"] = "Close";
// Menu
strings_["file"] = "File";
strings_["edit"] = "Edit";
strings_["view"] = "View";
strings_["help"] = "Help";
strings_["import_private_key"] = "Import Private Key...";
strings_["backup_wallet"] = "Backup Wallet...";
strings_["exit"] = "Exit";
strings_["about_dragonx"] = "About ObsidianDragon";
strings_["refresh_now"] = "Refresh Now";
// Dialogs
strings_["about"] = "About";
strings_["import"] = "Import";
strings_["export"] = "Export";
strings_["copy_to_clipboard"] = "Copy to Clipboard";
// Status
strings_["connected"] = "Connected";
strings_["disconnected"] = "Disconnected";
strings_["connecting"] = "Connecting...";
strings_["syncing"] = "Syncing...";
strings_["block"] = "Block";
strings_["no_addresses_available"] = "No addresses available";
// Errors & Messages
strings_["error"] = "Error";
strings_["success"] = "Success";
strings_["warning"] = "Warning";
strings_["amount_exceeds_balance"] = "Amount exceeds balance";
strings_["transaction_sent"] = "Transaction sent successfully";
current_locale_ = "en";
DEBUG_LOGF("Using built-in English strings (%zu strings)\n", strings_.size());
return true;
}
// Fallback: reload English so we never leave stale strings
DEBUG_LOGF("Language file not found: %s — falling back to English\n", lang_file.c_str());
if (locale != "en") {
loadLanguage("en");
}
return false;
}
const char* I18n::translate(const char* key) const
{
if (!key) return "";
auto it = strings_.find(key);
if (it != strings_.end()) {
return it->second.c_str();
}
// Return key if translation not found
return key;
}
void I18n::registerLanguage(const std::string& code, const std::string& name)
{
// Check if already registered
for (const auto& lang : available_languages_) {
if (lang.first == code) return;
}
available_languages_.emplace_back(code, name);
}
} // namespace util
} // namespace dragonx