diff --git a/Cargo.toml b/Cargo.toml index 73a0d0f..f8c8dd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ grin_wallet_controller = { git = "https://github.com/yeastplume/grin-wallet", br ## ui egui = { version = "0.27.2", default-features = false } egui_extras = { version = "0.27.2", features = ["image"] } -rust-i18n = "2.1.0" +rust-i18n = "2.3.1" ## other thiserror = "1.0.58" diff --git a/locales/en.yml b/locales/en.yml index ffe2c67..bf63107 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -1,3 +1,4 @@ +lang_name: English copy: Copy paste: Paste continue: Continue @@ -12,6 +13,8 @@ clear: Clear create: Create id: Identifier kernel: Kernel +settings: Settings +language: Language wallets: await_conf_amount: Awaiting confirmation await_fin_amount: Awaiting finalization diff --git a/locales/ru.yml b/locales/ru.yml index e0a5142..f2e2406 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -1,3 +1,4 @@ +lang_name: Русский copy: Копировать paste: Вставить continue: Продолжить @@ -12,6 +13,8 @@ clear: Очистить create: Создать id: Идентификатор kernel: Ядро +settings: Настройки +language: Язык wallets: await_conf_amount: Ожидает подтверждения await_fin_amount: Ожидает завершения diff --git a/src/config.rs b/src/config.rs index 93b72b0..d44a2f5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -37,9 +37,14 @@ pub struct AppConfig { height: f32, /// Position of the desktop window. - x: Option, y: Option + x: Option, y: Option, + + /// Locale code for i18n. + lang: Option } +pub const DEFAULT_LOCALE: &str = "en"; + pub const DEFAULT_WIDTH: f32 = 1200.0; pub const DEFAULT_HEIGHT: f32 = 720.0; @@ -54,6 +59,7 @@ impl Default for AppConfig { height: DEFAULT_HEIGHT, x: None, y: None, + lang: None, } } } @@ -170,4 +176,20 @@ impl AppConfig { } None } + + /// Save locale code. + pub fn save_locale(lang: &str) { + let mut w_app_config = Settings::app_config_to_update(); + w_app_config.lang = Some(lang.to_string()); + w_app_config.save(); + } + + /// Get current saved locale code. + pub fn locale() -> Option { + let r_config = Settings::app_config_to_read(); + if r_config.lang.is_some() { + return Some(r_config.lang.clone().unwrap()) + } + None + } } \ No newline at end of file diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index 8b602eb..f2324ab 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -17,9 +17,9 @@ use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea, Widget}; use crate::AppConfig; use crate::gui::Colors; -use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_LOCK, FOLDER_OPEN, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SPINNER, SUITCASE, WARNING_CIRCLE}; +use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, CHECK, CHECK_FAT, COMPUTER_TOWER, FLAG, FOLDER_LOCK, FOLDER_OPEN, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SPINNER, SUITCASE, WARNING_CIRCLE}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, Root, TitlePanel, View}; +use crate::gui::views::{Modal, NodeSetup, Root, TitlePanel, View}; use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions, TitleContentType, TitleType}; use crate::gui::views::wallets::creation::WalletCreation; use crate::gui::views::wallets::types::WalletTabType; @@ -51,6 +51,9 @@ pub struct WalletsContent { /// Identifier for wallet opening [`Modal`]. const OPEN_WALLET_MODAL: &'static str = "open_wallet_modal"; +/// Identifier for wallet opening [`Modal`]. +const SETTINGS_MODAL: &'static str = "settings_modal"; + impl Default for WalletsContent { fn default() -> Self { Self { @@ -62,6 +65,7 @@ impl Default for WalletsContent { show_wallets_at_dual_panel: AppConfig::show_wallets_at_dual_panel(), modal_ids: vec![ OPEN_WALLET_MODAL, + SETTINGS_MODAL, WalletCreation::NAME_PASS_MODAL ] } @@ -83,6 +87,7 @@ impl ModalContainer for WalletsContent { WalletCreation::NAME_PASS_MODAL => { self.creation_content.name_pass_modal_ui(ui, modal, cb) }, + SETTINGS_MODAL => self.settings_modal_ui(ui, modal), _ => {} } } @@ -298,7 +303,11 @@ impl WalletsContent { }; }, |ui, frame| { View::title_button(ui, GEAR, || { - //TODO: show settings. + // Show settings modal. + Modal::new(SETTINGS_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("settings")) + .show(); }); }, ui, frame); } @@ -586,6 +595,89 @@ impl WalletsContent { }); } + /// Draw creating wallet name/password input [`Modal`] content. + pub fn settings_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal) { + ui.add_space(6.0); + + // Draw chain type selection. + NodeSetup::chain_type_ui(ui); + + ui.add_space(8.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + + ui.vertical_centered(|ui| { + ui.label(RichText::new(format!("{} {}", GLOBE_SIMPLE, t!("language"))) + .size(16.0) + .color(Colors::GRAY) + ); + }); + ui.add_space(6.0); + + // Draw available list of languages to select. + let locales = rust_i18n::available_locales!(); + for (index, locale) in locales.iter().enumerate() { + Self::language_item_ui(locale, ui, index, locales.len(), modal); + } + + ui.add_space(8.0); + + // Show button to close modal. + ui.vertical_centered_justified(|ui| { + View::button(ui, t!("close"), Colors::WHITE, || { + modal.close(); + }); + }); + ui.add_space(6.0); + } + + /// Draw language selection item content. + fn language_item_ui(locale: &str, ui: &mut egui::Ui, index: usize, len: usize, modal: &Modal) { + // Setup layout size. + let mut rect = ui.available_rect_before_wrap(); + rect.set_height(50.0); + + // Draw round background. + let bg_rect = rect.clone(); + let item_rounding = View::item_rounding(index, len, false); + ui.painter().rect(bg_rect, item_rounding, Colors::FILL, View::ITEM_STROKE); + + ui.vertical(|ui| { + ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { + // Draw button to select language. + let is_current = if let Some(lang) = AppConfig::locale() { + lang == locale + } else { + rust_i18n::locale() == locale + }; + if !is_current { + View::item_button(ui, View::item_rounding(index, len, true), CHECK, None, || { + rust_i18n::set_locale(locale); + AppConfig::save_locale(locale); + modal.close(); + }); + } else { + ui.add_space(14.0); + ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::GREEN)); + ui.add_space(14.0); + } + + let layout_size = ui.available_size(); + ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| { + ui.add_space(12.0); + ui.vertical(|ui| { + // Draw language name. + ui.add_space(12.0); + ui.label(RichText::new(t!("lang_name", locale = locale)) + .size(17.0) + .color(Colors::TEXT)); + ui.add_space(3.0); + }); + }); + }); + }); + } + /// Handle Back key event. /// Return `false` when event was handled. pub fn on_back(&mut self) -> bool { diff --git a/src/lib.rs b/src/lib.rs index dad94d5..1340d40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,14 +15,14 @@ #[macro_use] extern crate rust_i18n; -use eframe::wgpu; -use egui::{Context, Stroke, vec2}; +use egui::{Context, Stroke}; #[cfg(target_os = "android")] use winit::platform::android::activity::AndroidApp; pub use config::AppConfig; pub use settings::Settings; +use crate::config::DEFAULT_LOCALE; use crate::gui::{Colors, PlatformApp}; use crate::gui::platform::PlatformCallbacks; @@ -206,14 +206,24 @@ pub fn setup_fonts(ctx: &Context) { /// Setup translations. fn setup_i18n() { - const DEFAULT_LOCALE: &str = "en"; - let locale = sys_locale::get_locale().unwrap_or(String::from(DEFAULT_LOCALE)); - let locale_str = if locale.contains("-") { - locale.split("-").next().unwrap_or(DEFAULT_LOCALE) + // Set saved locale or get from system. + if let Some(lang) = AppConfig::locale() { + if rust_i18n::available_locales!().contains(&lang.as_str()) { + rust_i18n::set_locale(lang.as_str()); + } } else { - locale.as_str() - }; - if _rust_i18n_available_locales().contains(&locale_str) { - rust_i18n::set_locale(locale_str); + let locale = sys_locale::get_locale().unwrap_or(String::from(DEFAULT_LOCALE)); + let locale_str = if locale.contains("-") { + locale.split("-").next().unwrap_or(DEFAULT_LOCALE) + } else { + locale.as_str() + }; + + // Set best possible locale. + if rust_i18n::available_locales!().contains(&locale_str) { + rust_i18n::set_locale(locale_str); + } else { + rust_i18n::set_locale(DEFAULT_LOCALE); + } } } \ No newline at end of file