From dfc3a3e69bd7c798fc7b63726cf877457223dff7 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Tue, 14 Nov 2023 01:26:38 +0200 Subject: [PATCH] wip - i18n & wasm --- .gitignore | 1 + Cargo.toml | 3 + core/Cargo.toml | 1 + core/src/app.rs | 209 ++++++++++++++++++++++++---------- core/src/core.rs | 138 ++++++++++++++++------ core/src/modules/metrics.rs | 78 ++----------- core/src/modules/mod.rs | 45 ++++---- core/src/runtime/kaspa/mod.rs | 28 +++-- core/src/utils.rs | 91 +++++++++++++++ extensions/chrome/Cargo.toml | 2 +- macros/src/register.rs | 19 +++- resources/i18n/i18n.json | 23 +++- 12 files changed, 438 insertions(+), 200 deletions(-) diff --git a/.gitignore b/.gitignore index 3f33699..faf8a55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/resources/i18n/* /target /macros/target /Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 5be211b..5010785 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,18 +90,21 @@ kaspad = { path = "../rusty-kaspa/kaspad" } # workflow-chrome = "0.8.1" # workflow-core = "0.8.1" # workflow-dom = "0.8.1" +# workflow-http = "0.8.1" # workflow-i18n = "0.8.1" # workflow-log = "0.8.1" # workflow-store = "0.8.1" workflow-chrome = { path = "../workflow-rs/chrome" } workflow-core = { path = "../workflow-rs/core", features = ["no-unsafe-eval"] } workflow-dom = { path = "../workflow-rs/dom" } +workflow-http = { path = "../workflow-rs/http" } workflow-i18n = { path = "../workflow-rs/i18n" } workflow-log = { path = "../workflow-rs/log" } workflow-store = { path = "../workflow-rs/store" } # workflow-chrome = { git = "https://github.com/workflow-rs/workflow-rs.git", branch = "master" } # workflow-core = { git = "https://github.com/workflow-rs/workflow-rs.git", branch = "master", features = ["no-unsafe-eval"] } # workflow-dom = { git = "https://github.com/workflow-rs/workflow-rs.git", branch = "master" } +# workflow-http = { git = "https://github.com/workflow-rs/workflow-rs.git", branch = "master" } # workflow-i18n = { git = "https://github.com/workflow-rs/workflow-rs.git", branch = "master" } # workflow-log = { git = "https://github.com/workflow-rs/workflow-rs.git", branch = "master" } # workflow-store = { git = "https://github.com/workflow-rs/workflow-rs.git", branch = "master" } diff --git a/core/Cargo.toml b/core/Cargo.toml index eac9902..4c23d33 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -25,6 +25,7 @@ kaspa-wrpc-client.workspace = true workflow-log.workspace = true workflow-core.workspace = true workflow-dom.workspace = true +workflow-http.workspace = true workflow-store.workspace = true workflow-i18n.workspace = true diff --git a/core/src/app.rs b/core/src/app.rs index b3baa24..36a579a 100644 --- a/core/src/app.rs +++ b/core/src/app.rs @@ -3,14 +3,13 @@ use cfg_if::cfg_if; use kaspa_ng_core::interop; use kaspa_ng_core::settings::Settings; use kaspa_wallet_core::api::WalletApi; -use std::path::PathBuf; use std::sync::Arc; use workflow_i18n::*; use workflow_log::*; #[allow(unused)] pub const KASPA_NG_ICON_256X256: &[u8] = include_bytes!("../../resources/icons/icon-256.png"); -pub const I18N_DATA: &str = include_str!("../../resources/i18n/i18n.json"); +pub const I18N_EMBEDDED: &str = include_str!("../../resources/i18n/i18n.json"); cfg_if! { if #[cfg(not(target_arch = "wasm32"))] { @@ -22,13 +21,23 @@ cfg_if! { use kaspa_ng_core::runtime; use clap::ArgAction; use eframe::IconData; + use crate::utils::*; + use std::fs; + // use log::info; + #[derive(Debug)] + enum I18n { + + Import, + Export, + } + enum Args { + I18n { op : I18n }, Kng { reset_settings : bool, disable : bool, - rebuild_i18n: bool, }, Kaspad { args : Box }, } @@ -41,6 +50,7 @@ cfg_if! { Args::Kaspad { args : Box::new(parse_kaspad_args()) } } else { let cmd = Command::new("kaspa-ng") + .about(format!("kaspa-ng v{} (rusty-kaspa v{})", env!("CARGO_PKG_VERSION"), kaspa_wallet_core::version())) // .version(env!("CARGO_PKG_VERSION")) .arg(arg!(--disable "Disable node services when starting.")) @@ -51,22 +61,73 @@ cfg_if! { .help("Reset KaspaNG settings.") ) .arg( - Arg::new("i18n") - .long("i18n") + Arg::new("i18n-export") + .long("i18n-export") .action(ArgAction::SetTrue) .hide(true) .help("Update i18n dictionary data.") ) - // .arg(arg!(--i18n "Process.")) + // .arg(arg!(--i18n "Process.")) + .subcommand( + Command::new("i18n").hide(true) + .subcommand( + Command::new("import")//.hide(true) + // .help("import i18n data") + .about("import JSON files suffixed with language codes (*_en.json, *_de.json, etc.)") + ) + .subcommand( + Command::new("export")//.hide(true) + .about("export default 'en' data as JSON") + ) + ) ; + + let matches = cmd.get_matches(); - let reset_settings = matches.get_one::("reset-settings").cloned().unwrap_or(false); - let disable = matches.get_one::("disable").cloned().unwrap_or(false); - let rebuild_i18n = matches.get_one::("i18n").cloned().unwrap_or(false); - Args::Kng { reset_settings, disable, rebuild_i18n } + // let v = matches.subcommand_matches("i18n") + // .and_then(|matches|{ + // matches.subcommand_matches("import").map(|_|I18n::Import).or_else(||{ + // matches.subcommand_matches("export").map(|_|I18n::Export) + // }) + + // }); + + // println!("v: {:#?}", v); + // std::process::exit(1); + + // .map(|_matches|I18n::Import) + // .or_else(|| matches); + + if let Some(matches) = matches.subcommand_matches("i18n") { + if let Some(_matches) = matches.subcommand_matches("import") { + Args::I18n { op : I18n::Import } + } else if let Some(_matches) = matches.subcommand_matches("export") { + Args::I18n { op : I18n::Export } + } else { + panic!("unknown i18n subcommand") + } + } else { + let reset_settings = matches.get_one::("reset-settings").cloned().unwrap_or(false); + let disable = matches.get_one::("disable").cloned().unwrap_or(false); + + Args::Kng { reset_settings, disable } + + } + // println!("Args: {:#?}", v); + // std::process::exit(1); + + // let i18n_import = matches.subcommand_matches("i18n").and_then(|matches|matches.subcommand_matches("import")).is_some(); + // let i18n_export = matches.subcommand_matches("i18n").and_then(|matches|matches.subcommand_matches("export")).is_some(); + + + // println!("matches: {:#?}", matches); + + // println!("i18n import: {:#?}", i18n_import); + // println!("i18n export: {:#?}", i18n_export); + // std::process::exit(1); } } @@ -84,7 +145,42 @@ cfg_if! { core.run(); } - Args::Kng { reset_settings, disable, rebuild_i18n } => { + Args::I18n { + op + } => { + let i18n_json_file = i18n_storage_file()?; + let i18n_json_file_store = i18n_storage_file()?; + i18n::Builder::new("en", "en") + .with_static_json_data(I18N_EMBEDDED) + .with_string_json_data(i18n_json_file.exists().then(move ||{ + fs::read_to_string(i18n_json_file) + }).transpose()?) + .with_store(move |json_data: &str| { + Ok(fs::write(&i18n_json_file_store, json_data)?) + }) + .try_init()?; + + match op { + I18n::Import => { + let source_folder = std::env::current_dir()?; + println!("importing translation files from: '{}'", source_folder.display()); + i18n::import_translation_files(source_folder,false)?; + } + I18n::Export => { + let target_folder = if let Some(cwd) = try_cwd_repo_root()? { + cwd.join("resources").join("i18n") + } else { + std::env::current_dir()? + }; + println!("exporting default language to: '{}'", target_folder.display()); + i18n::export_default_language(move |json_data: &str| { + Ok(fs::write(&target_folder, json_data)?) + })?; + } + } + } + + Args::Kng { reset_settings, disable } => { println!("kaspa-ng v{} (rusty-kaspa v{})", env!("CARGO_PKG_VERSION"), kaspa_wallet_core::version()); @@ -101,34 +197,25 @@ cfg_if! { }) }; - println!("SETTINGS: {:#?}", settings); + println!("settings: {:#?}", settings); + + let i18n_json_file = i18n_storage_file()?; + let i18n_json_file_load = i18n_json_file.clone(); + let i18n_json_file_store = i18n_json_file.clone(); + i18n::Builder::new(settings.language_code.as_str(), "en") + .with_static_json_data(I18N_EMBEDDED) + .with_string_json_data(i18n_json_file.exists().then(move ||{ + fs::read_to_string(i18n_json_file_load) + }).transpose()?) + .with_store(move |json_data: &str| { + Ok(fs::write(&i18n_json_file_store, json_data)?) + }) + .try_init()?; if disable { settings.node.node_kind = kaspa_ng_core::settings::KaspadNodeKind::Disable; } - if rebuild_i18n { - - let storage_path = i18n::storage_path()?; - let i18n_json = storage_path.join("i18n.json"); - if !i18n_json.exists() { - i18n::try_init(settings.language_code.as_str(), "en", None, Option::::None).expect("failed to init i18n"); - i18n::store(i18n::storage_path()?).expect("failed to store i18n data"); - } - // else { - - // } - // i18n::store("hello").expect("failed to store i18n data"); - - return Ok(()); - } else { - // i18n::try_init(settings.language_code.as_str(), "en", Some(I18N_DATA), Some(i18n::storage_path()?)).expect("failed to init i18n"); - i18n::try_init(settings.language_code.as_str(), "en", Some(I18N_DATA), Option::::None).expect("failed to init i18n"); - - } - - - let interop: Arc>> = Arc::new(Mutex::new(None)); let delegate = interop.clone(); // println!("spawn done"); @@ -188,32 +275,38 @@ cfg_if! { Settings::default() }); - init_i18n(settings.language_code.as_str()).expect("failed to init i18n"); - - // wasm_bindgen_futures::spawn_local(async { - use workflow_log::*; - log_info!("starting"); - eframe::WebRunner::new() - .start( - "kaspa-ng", - web_options, - Box::new(move |cc| { - let interop = interop::Interop::new(&cc.egui_ctx, &settings); - interop.start(); + // init_i18n(settings.language_code.as_str()).expect("failed to init i18n"); - let adaptor = kaspa_ng_core::adaptor::Adaptor::new(interop.clone()); - let window = web_sys::window().expect("no global `window` exists"); - js_sys::Reflect::set( - &window, - &JsValue::from_str("adaptor"), - &JsValue::from(adaptor), - ).expect("failed to set adaptor"); + i18n::Builder::new(settings.language_code.as_str(), "en") + .with_static_json_data(I18N_EMBEDDED) + // .with_store(move |json_data: &str| { + // }) + .try_init()?; - Box::new(kaspa_ng_core::Core::new(cc, interop, settings)) - }), - ) - .await - .expect("failed to start eframe"); + // wasm_bindgen_futures::spawn_local(async { + use workflow_log::*; + log_info!("starting"); + eframe::WebRunner::new() + .start( + "kaspa-ng", + web_options, + Box::new(move |cc| { + let interop = interop::Interop::new(&cc.egui_ctx, &settings); + interop.start(); + + let adaptor = kaspa_ng_core::adaptor::Adaptor::new(interop.clone()); + let window = web_sys::window().expect("no global `window` exists"); + js_sys::Reflect::set( + &window, + &JsValue::from_str("adaptor"), + &JsValue::from(adaptor), + ).expect("failed to set adaptor"); + + Box::new(kaspa_ng_core::Core::new(cc, interop, settings)) + }), + ) + .await + .expect("failed to start eframe"); // log_info!("shutting down..."); // }); diff --git a/core/src/core.rs b/core/src/core.rs index 7ce4cff..d632845 100644 --- a/core/src/core.rs +++ b/core/src/core.rs @@ -168,7 +168,17 @@ impl Core { // return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); // } - let modules = crate::modules::register_modules(&interop); + let modules: HashMap = { + cfg_if! { + if #[cfg(not(target_arch = "wasm32"))] { + crate::modules::register_generic_modules(&interop).into_iter().chain( + crate::modules::register_native_modules(&interop).into_iter() + ).collect() + } else { + crate::modules::register_generic_modules(&interop) + } + } + }; let mut module = if settings.developer_mode { modules @@ -507,20 +517,24 @@ impl eframe::App for Core { // } // ui.separator(); // if ui.button(RichText::new(format!("{} Settings",egui_phosphor::light::GEAR))).clicked() { - if ui.button("Node").clicked() { - self.select::(); - } - ui.separator(); - if ui.button("Metrics").clicked() { - self.select::(); - } - ui.separator(); if ui.button("Settings").clicked() { self.select::(); } - ui.separator(); - if ui.button("Logs").clicked() { - self.select::(); + cfg_if! { + if #[cfg(not(target_arch = "wasm32"))] { + ui.separator(); + if ui.button("Node").clicked() { + self.select::(); + } + ui.separator(); + if ui.button("Metrics").clicked() { + self.select::(); + } + ui.separator(); + if ui.button("Logs").clicked() { + self.select::(); + } + } } ui.separator(); if ui.button("About").clicked() { @@ -709,7 +723,8 @@ impl eframe::App for Core { impl Core { fn render_status(&mut self, ui: &mut egui::Ui) { - ui.horizontal(|ui| { + egui::menu::bar(ui, |ui| { + // ui.horizontal(|ui| { if !self.state().is_connected() { self.render_connected_state(ui, Status::Disconnected); } else { @@ -773,12 +788,82 @@ impl Core { } } + fn render_network_selector(&self, ui: &mut Ui) { + // let network_selector_id = ui.make_persistent_id("network-selector"); + // let mut network_selector = + // ui.button(self.settings.node.network.to_string());//.sense(Sense::clickable()); + // ui.label(self.settings.node.network.to_string());//.sense(Sense::clickable()); + // network_selector.sense.click = true; + // if network_selector.clicked() { + ui.menu_button(self.settings.node.network.to_string(), |ui| { + Network::iter().for_each(|network| { + if ui.button(network.to_string()).clicked() { + ui.close_menu(); + } + }); + }); + + // } + // if network_selector.clicked() { + // println!("network selector clicked..."); + // popup_above_or_below_widget(ui, network_selector_id, &network_selector, AboveOrBelow::Above, |ui| { + + // Network::iter().for_each(|network| { + // if ui.button(network.to_string()).clicked() { + // ui.close_menu(); + // } + // }); + // }); + // } + } + fn render_connected_state(&self, ui: &mut egui::Ui, state: Status) { //connected : bool, icon: &str, color : Color32) { let status_area_width = ui.available_width() - 24.; let status_icon_size = theme().status_icon_size; match state { + Status::Disconnected => { + ui.add_space(4.); + + match self.settings.node.node_kind { + KaspadNodeKind::Disable => { + ui.label( + RichText::new(egui_phosphor::light::PLUGS) + .size(status_icon_size) + .color(Color32::LIGHT_RED), + ); + ui.separator(); + ui.label("Not Connected"); + } + KaspadNodeKind::Remote => { + ui.label( + RichText::new(egui_phosphor::light::TREE_STRUCTURE) + .size(status_icon_size) + .color(Color32::LIGHT_RED), + ); + ui.separator(); + ui.label("Connecting..."); + } + #[cfg(not(target_arch = "wasm32"))] + _ => { + ui.label( + RichText::new(egui_phosphor::light::PLUGS) + .size(status_icon_size) + .color(Color32::LIGHT_RED), + ); + ui.separator(); + ui.label("Starting..."); + } + } + // if self.settings.node.node_kind != KaspadNodeKind::Disable { + // ui.label("Not Connected"); + + // } else { + // ui.label("Not Connected"); + // } + } + Status::Connected { current_daa_score, peers, @@ -797,18 +882,7 @@ impl Core { ui.label("CONNECTED"); // } ui.separator(); - // let network_selector = - ui.label(self.settings.node.network.to_string()); - // if network_selector.clicked() { - // popup_above_or_below_widget(ui, "network-selector".into(), &network_selector, AboveOrBelow::Above, |ui| { - - // Network::iter().for_each(|network| { - // if ui.button(network.to_string()).clicked() { - // ui.close_menu(); - // } - // }); - // }); - // } + self.render_network_selector(ui); // ui.menu_button(self.settings.node.network.to_string(), |ui| { // Network::iter().for_each(|network| { // if ui.button(network.to_string()).clicked() { @@ -824,16 +898,6 @@ impl Core { ui.label(format!("DAA {}", current_daa_score.separated_string())); } } - Status::Disconnected => { - ui.add_space(4.); - ui.label( - RichText::new(egui_phosphor::light::PLUGS) - .size(status_icon_size) - .color(Color32::LIGHT_RED), - ); - ui.separator(); - ui.label("Not Connected"); - } Status::Syncing { sync_status, peers } => { ui.vertical(|ui| { ui.horizontal(|ui| { @@ -846,7 +910,9 @@ impl Core { ui.separator(); ui.label("CONNECTED"); ui.separator(); - ui.label(self.settings.node.network.to_string()); + self.render_network_selector(ui); + + // ui.label(self.settings.node.network.to_string()); ui.separator(); self.render_peers(ui, peers); if let Some(status) = sync_status.as_ref() { diff --git a/core/src/modules/metrics.rs b/core/src/modules/metrics.rs index b942f9d..bdc70e4 100644 --- a/core/src/modules/metrics.rs +++ b/core/src/modules/metrics.rs @@ -51,43 +51,19 @@ impl ModuleT for Metrics { for metric in Metric::list().into_iter() { ui.vertical(|ui| { - // let value = metrics.get(&metric); - // let caption = metrics.format(&metric, true); - // ui.rig ui.with_layout(Layout::right_to_left(egui::Align::Min), |ui| { - ui.label(metrics.format(&metric, true)); }); // --- - - let metrics_data = self.interop.kaspa_service().metrics_data(); - let data = metrics_data.get(&metric).unwrap(); - let duration = 60 * 10; // 15 min (testing) - let samples = if data.len() < duration { data.len() } else { duration }; - - let graph_data = data[data.len()-samples..].to_vec(); - // let graph_data = &data[data.len()-samples..];//.to_vec(); - // let start_time = graph_data[0].x; - - // let graph_data = if graph_data.len() < samples { - // // vec![PlotPoint::new(0.,0.),samples-graph_data.len()]//.concat(graph_data) - // let len = samples-graph_data.len(); - // let mut vec = Vec::with_capacity(len); - // for _ in 0..len { - // vec.push(PlotPoint::new(0.,0.)); - // } - // vec - // } else { - // graph_data.to_vec() - // }; - - // let graph = CompositeGraph::new(metric.as_str(), &period_data); - // ui.add(graph); - - - + let graph_data = { + let metrics_data = self.interop.kaspa_service().metrics_data(); + let data = metrics_data.get(&metric).unwrap(); + let duration = 60 * 10; // 15 min (testing) + let samples = if data.len() < duration { data.len() } else { duration }; + data[data.len()-samples..].to_vec() + }; // let first_point = graph_data[0]; // let last_point = graph_data[graph_data.len() - 1]; @@ -103,20 +79,14 @@ impl ModuleT for Metrics { .allow_drag([false, false]) .allow_scroll(false) .y_axis_formatter(move |y,_size,_range|{ - // "".to_string() metric.format(y, true) }) .x_axis_formatter(move |x, _size, _range| { - // workflow_log::log_info!("x:{x}, size:{size}, range:{range:?}"); - // if x <= first_point.x || x >= last_point.x { - DateTime::::from_timestamp((x / 1000.0) as i64, 0) - .expect("could not parse timestamp") - .with_timezone(&chrono::Local) - .format("%H:%M:%S") - .to_string() - // } else { - // "".to_string() - // } + DateTime::::from_timestamp((x / 1000.0) as i64, 0) + .expect("could not parse timestamp") + .with_timezone(&chrono::Local) + .format("%H:%M:%S") + .to_string() }) .x_grid_spacer( uniform_grid_spacer(|_input| { @@ -148,30 +118,6 @@ impl ModuleT for Metrics { ui.add_space(12.); - - - - - - - - - - - - - - - - - - - - - - - - }); } } diff --git a/core/src/modules/mod.rs b/core/src/modules/mod.rs index a9e2768..073c078 100644 --- a/core/src/modules/mod.rs +++ b/core/src/modules/mod.rs @@ -2,27 +2,30 @@ use std::any::type_name; use crate::imports::*; -kaspa_ng_macros::register_modules!([ - about, - account_manager, - deposit, - metrics, - wallet_open, - request, - send, - settings, - transactions, - account_create, - wallet_create, - export, - import, - testing, - logs, - changelog, - welcome, - overview, - node, -]); +kaspa_ng_macros::register_modules!( + register_generic_modules, + [ + about, + account_create, + account_manager, + changelog, + deposit, + export, + import, + overview, + request, + send, + settings, + testing, + transactions, + wallet_create, + wallet_open, + welcome, + ] +); + +#[cfg(not(target_arch = "wasm32"))] +kaspa_ng_macros::register_modules!(register_native_modules, [logs, metrics, node,]); pub enum ModuleStyle { Large, diff --git a/core/src/runtime/kaspa/mod.rs b/core/src/runtime/kaspa/mod.rs index ad828a4..205587e 100644 --- a/core/src/runtime/kaspa/mod.rs +++ b/core/src/runtime/kaspa/mod.rs @@ -4,12 +4,20 @@ use crate::imports::*; use crate::runtime::Service; pub use futures::{future::FutureExt, select, Future}; use kaspa_metrics::{Metric, Metrics, MetricsSnapshot}; -#[cfg(not(target_arch = "wasm32"))] -use kaspa_rpc_service::service::RpcCoreService; #[allow(unused_imports)] use kaspa_wallet_core::rpc::{NotificationMode, Rpc, RpcCtl, WrpcEncoding}; use kaspa_wallet_core::{ConnectOptions, ConnectStrategy}; +cfg_if! { + if #[cfg(not(target_arch = "wasm32"))] { + #[cfg(not(target_arch = "wasm32"))] + use kaspa_rpc_service::service::RpcCoreService; + + const LOG_BUFFER_LINES: usize = 4096; + const LOG_BUFFER_MARGIN: usize = 128; + } +} + #[allow(clippy::identity_op)] const MAX_METRICS_SAMPLES: usize = 60 * 60 * 24 * 1; // 1 day @@ -77,7 +85,8 @@ pub struct KaspaService { #[cfg(not(target_arch = "wasm32"))] pub kaspad: Mutex>>, #[cfg(not(target_arch = "wasm32"))] - pub logs: Mutex>, + pub logs: Mutex>, + // pub logs: Mutex>, } impl KaspaService { @@ -129,7 +138,8 @@ impl KaspaService { #[cfg(not(target_arch = "wasm32"))] kaspad: Mutex::new(None), #[cfg(not(target_arch = "wasm32"))] - logs: Mutex::new(VecDeque::new()), + logs: Mutex::new(Vec::new()), + // logs: Mutex::new(VecDeque::new()), } } @@ -201,7 +211,8 @@ impl KaspaService { } #[cfg(not(target_arch = "wasm32"))] - pub fn logs(&self) -> MutexGuard<'_, VecDeque> { + // pub fn logs(&self) -> MutexGuard<'_, VecDeque> { + pub fn logs(&self) -> MutexGuard<'_, Vec> { self.logs.lock().unwrap() } @@ -209,10 +220,11 @@ impl KaspaService { pub async fn update_logs(&self, line: String) { { let mut logs = self.logs.lock().unwrap(); - while logs.len() > 4096 { - logs.pop_front(); + if logs.len() > LOG_BUFFER_LINES { + logs.drain(0..LOG_BUFFER_MARGIN); } - logs.push_back(line.as_str().into()); + // logs.push_back(line.as_str().into()); + logs.push(line.as_str().into()); } if update_logs_flag().load(Ordering::SeqCst) { diff --git a/core/src/utils.rs b/core/src/utils.rs index 5be30c9..c445fe6 100644 --- a/core/src/utils.rs +++ b/core/src/utils.rs @@ -126,3 +126,94 @@ impl From for Vec { arglist.args } } + +#[cfg(not(target_arch = "wasm32"))] +pub fn try_cwd_repo_root() -> Result> { + let cwd = std::env::current_dir()?; + let cargo_toml = cwd.join("Cargo.toml"); + let resources = cwd.join("resources").join("i18n"); + Ok((cargo_toml.exists() && resources.exists()).then_some(cwd)) +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn i18n_storage_folder() -> Result { + // check if we are in the repository, then use /resources/i18n/i18n.json + let mut path = std::env::current_exe()?; + path.pop(); + if path.ends_with("debug") || path.ends_with("release") { + path.pop(); + if path.ends_with("target") { + path.pop(); + } + path.push("resources"); + path.push("i18n"); + path.push("i18n.json"); + if !path.exists() { + panic!("Expecting i18n.json in the repository at '/resources/i18n/i18n.json'") + } else { + path.pop(); + } + Ok(path) + } else { + // check if we can find i18n.json in the same folder as the executable + path.push("i18n.json"); + if path.exists() { + path.pop(); + Ok(path) + } else { + // check for i18n.json in the current working directory + let mut local = std::env::current_dir()?.join("i18n.json"); + if local.exists() { + local.pop(); + Ok(local) + } else { + // fallback to the default storage folder, which is the + // same as kaspa-ng settings storage folder: `~/.kaspa-ng/` + let storage_folder = + Path::new(kaspa_wallet_core::storage::local::DEFAULT_STORAGE_FOLDER); + if !storage_folder.exists() { + std::fs::create_dir_all(storage_folder)?; + } + Ok(storage_folder.to_path_buf()) + } + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn i18n_storage_file() -> Result { + // check if we are in the repository, then use /resources/i18n/i18n.json + let mut path = std::env::current_exe()?; + path.pop(); + if path.ends_with("debug") || path.ends_with("release") { + path.pop(); + if path.ends_with("target") { + path.pop(); + } + path.push("resources"); + path.push("i18n"); + path.push("i18n.json"); + Ok(path) + } else { + // check if we can find i18n.json in the same folder as the executable + let in_same_folder = path.join("i18n.json"); + if in_same_folder.exists() { + Ok(in_same_folder) + } else { + // check for i18n.json in the current working directory + let local = std::env::current_dir()?.join("i18n.json"); + if local.exists() { + Ok(local) + } else { + // fallback to the default storage folder, which is the + // same as kaspa-ng settings storage folder: `~/.kaspa-ng/` + let storage_folder = + Path::new(kaspa_wallet_core::storage::local::DEFAULT_STORAGE_FOLDER); + if !storage_folder.exists() { + std::fs::create_dir_all(storage_folder)?; + } + Ok(storage_folder.join("kaspa-ng.i18n.json")) + } + } + } +} diff --git a/extensions/chrome/Cargo.toml b/extensions/chrome/Cargo.toml index 816fbb4..ad677a0 100644 --- a/extensions/chrome/Cargo.toml +++ b/extensions/chrome/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "chrome" +name = "kaspa-ng-chrome" version.workspace = true authors.workspace = true license.workspace = true diff --git a/macros/src/register.rs b/macros/src/register.rs index c2b4e4f..b44deeb 100644 --- a/macros/src/register.rs +++ b/macros/src/register.rs @@ -14,6 +14,7 @@ use syn::{ #[derive(Debug)] struct Modules { + function_name: Expr, modules: ExprArray, } @@ -26,18 +27,23 @@ impl Parse for Modules { "usage: declare_modules!()".to_string(), )); } + let parsed = parsed.unwrap(); - if parsed.len() != 1 { + if parsed.len() != 2 { return Err(Error::new_spanned( parsed, - "usage: declare_modules!()".to_string(), + "usage: declare_modules!(, )".to_string(), )); } let mut iter = parsed.iter(); + let function_name = iter.next().unwrap().clone(); let modules = get_modules(iter.next().unwrap().clone())?; - Ok(Modules { modules }) + Ok(Modules { + function_name, + modules, + }) } } @@ -73,7 +79,10 @@ pub fn register_modules(input: TokenStream) -> TokenStream { } fn render(modules: Modules) -> TokenStream { - let Modules { modules } = modules; + let Modules { + function_name, + modules, + } = modules; let mut pub_mod = HashSet::new(); let mut use_mod = Vec::new(); @@ -110,7 +119,7 @@ fn render(modules: Modules) -> TokenStream { #(#use_mod)* - pub fn register_modules(interop : &Interop) -> HashMap:: { + pub fn #function_name (interop : &Interop) -> HashMap:: { let mut modules = HashMap::::new(); #(#targets)* diff --git a/resources/i18n/i18n.json b/resources/i18n/i18n.json index c1b84a3..f066b59 100644 --- a/resources/i18n/i18n.json +++ b/resources/i18n/i18n.json @@ -11,16 +11,16 @@ "languages": { "pt": "Português", "hi": "Hindi", - "fi": "Finnish", + "nl": "Dutch", + "vi": "Vietnamese", "fa": "Farsi", "fil": "Filipino", "lt": "Lithuanian", - "nl": "Dutch", + "fi": "Finnish", "pa": "Panjabi", "es": "Español", "sv": "Swedish", "uk": "Ukrainian", - "vi": "Vietnamese", "af": "Afrikaans", "et": "Esti", "en": "English", @@ -67,11 +67,24 @@ "fi": {}, "pa": {}, "es": {}, - "sv": {}, "uk": {}, + "sv": {}, "af": {}, "et": {}, - "en": {}, + "en": { + "Address": "Address", + "Protocol": "Protocol", + "No peers": "No peers", + "Network Peers": "Network Peers", + "Connection duration": "Connection duration", + "Inbound": "Inbound", + "Ping": "Ping", + "User Agent": "User Agent", + "Node Status": "Node Status", + "Outbound": "Outbound", + "IBD": "IBD", + "Time Offset": "Time Offset" + }, "el": {}, "it": {}, "de": {},