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:
321
src/util/i18n.cpp
Normal file
321
src/util/i18n.cpp
Normal 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
|
||||
Reference in New Issue
Block a user