diff --git a/noita-proxy/Cargo.lock b/noita-proxy/Cargo.lock index 10daf8e8..94a58eb4 100644 --- a/noita-proxy/Cargo.lock +++ b/noita-proxy/Cargo.lock @@ -128,6 +128,12 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arrayref" version = "0.3.7" @@ -273,6 +279,16 @@ dependencies = [ "objc2 0.5.1", ] +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -1052,6 +1068,97 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +[[package]] +name = "fluent" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell 0.10.3", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" +dependencies = [ + "thiserror", +] + +[[package]] +name = "fluent-template-macros" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d2bcae1f3ec390c50161fcf130d3228750e9ecf965618584e046d884199b83" +dependencies = [ + "flume", + "ignore", + "once_cell", + "proc-macro2", + "quote", + "syn", + "unic-langid", +] + +[[package]] +name = "fluent-templates" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197feb1e37209c6b3d29f0754b11fc070890efb2b1d761caac4e5287a200e9db" +dependencies = [ + "arc-swap", + "fluent", + "fluent-bundle", + "fluent-langneg", + "fluent-syntax", + "fluent-template-macros", + "flume", + "heck", + "ignore", + "intl-memoizer", + "log", + "once_cell", + "serde_json", + "thiserror", + "unic-langid", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1207,6 +1314,19 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", +] + [[package]] name = "glow" version = "0.13.1" @@ -1309,6 +1429,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1450,6 +1576,22 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.6", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "image" version = "0.24.9" @@ -1509,6 +1651,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "intl-memoizer" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe22e020fce238ae18a6d5d8c502ee76a52a6e880d99477657e6acc30ec57bda" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -1815,6 +1976,7 @@ dependencies = [ "eframe", "egui-file-dialog", "egui_extras", + "fluent-templates", "image 0.25.1", "lz4_flex", "poll-promise", @@ -1831,6 +1993,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tungstenite", + "unic-langid", "zip", ] @@ -2230,6 +2393,12 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.81" @@ -2622,6 +2791,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "self_cell" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" +dependencies = [ + "self_cell 1.0.4", +] + +[[package]] +name = "self_cell" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" + [[package]] name = "serde" version = "1.0.199" @@ -2790,6 +2974,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "static_assertions" @@ -3013,6 +3200,15 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -3205,12 +3401,65 @@ dependencies = [ "utf-8", ] +[[package]] +name = "type-map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash", +] + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unic-langid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" +dependencies = [ + "unic-langid-impl", + "unic-langid-macros", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" +dependencies = [ + "serde", + "tinystr", +] + +[[package]] +name = "unic-langid-macros" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da1cd2c042d3c7569a1008806b02039e7a4a2bdf8f8e96bd3c792434a0e275e" +dependencies = [ + "proc-macro-hack", + "tinystr", + "unic-langid-impl", + "unic-langid-macros-impl", +] + +[[package]] +name = "unic-langid-macros-impl" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed7f4237ba393424195053097c1516bd4590dc82b84f2f97c5c69e12704555b" +dependencies = [ + "proc-macro-hack", + "quote", + "syn", + "unic-langid-impl", +] + [[package]] name = "unicase" version = "2.7.0" diff --git a/noita-proxy/Cargo.toml b/noita-proxy/Cargo.toml index 723306f9..903e69d7 100644 --- a/noita-proxy/Cargo.toml +++ b/noita-proxy/Cargo.toml @@ -34,6 +34,8 @@ egui_extras = { version = "*", features = ["all_loaders"] } bytemuck = { version = "1.16.0", features = ["derive"] } bincode = "1.3.3" rustc-hash = "1.1.0" +fluent-templates = "0.9.4" +unic-langid = { version = "0.9.5", features = ["serde"] } [profile.dev] opt-level = 1 diff --git a/noita-proxy/assets/lang/en-US/main.ftl b/noita-proxy/assets/lang/en-US/main.ftl new file mode 100644 index 00000000..a8e43f58 --- /dev/null +++ b/noita-proxy/assets/lang/en-US/main.ftl @@ -0,0 +1,15 @@ +connect_steam = Connect using steam +connect_steam_create = Create lobby +connect_steam_connect = Connect to lobby in clipboard + +connect_ip = Connect using ip + +connect_settings = Game settings +connect_settings_debug = Debug settings +connect_settings_debug_en = Debug/cheat mode +connect_settings_debug_fixed_seed = Use fixed seed +connect_settings_wsv = World sync version to use: + +lang_picker = Choose a language + +button_confirm = Confirm diff --git a/noita-proxy/assets/lang/ru-RU/main.ftl b/noita-proxy/assets/lang/ru-RU/main.ftl new file mode 100644 index 00000000..7a46e3b7 --- /dev/null +++ b/noita-proxy/assets/lang/ru-RU/main.ftl @@ -0,0 +1,15 @@ +connect_steam = Подключение по Steam +connect_steam_create = Создать лобби +connect_steam_connect = Подключиться к лобби в буфере обмена + +connect_ip = Подключение по IP + +connect_settings = Настройки игры +connect_settings_debug = Настройки разработчика +connect_settings_debug_en = Подрубить читы +connect_settings_debug_fixed_seed = Фиксированный сид мира +connect_settings_wsv = Версия синхронизатора мира: + +lang_picker = Выберите язык + +button_confirm = Подтвердить diff --git a/noita-proxy/src/lang.rs b/noita-proxy/src/lang.rs new file mode 100644 index 00000000..798dd466 --- /dev/null +++ b/noita-proxy/src/lang.rs @@ -0,0 +1,51 @@ +use std::sync::RwLock; + +use fluent_templates::{LanguageIdentifier, Loader}; +use unic_langid::langid; + +fluent_templates::static_loader! { + // Declare our `StaticLoader` named `LOCALES`. + static LOCALES = { + // The directory of localisations and fluent resources. + locales: "./assets/lang", + // The language to falback on if something is not present. + fallback_language: "en-US", + }; +} + +// static LANG: LanguageIdentifier = langid!("en-US"); +static LANG: RwLock = RwLock::new(langid!("ru-RU")); + +pub struct LangDesc { + name: &'static str, + id: LanguageIdentifier, +} + +impl LangDesc { + const fn new(name: &'static str, id: LanguageIdentifier) -> Self { + Self { name, id } + } + + pub fn name(&self) -> &'static str { + self.name + } + + pub fn id(&self) -> LanguageIdentifier { + self.id.clone() + } +} + +pub static LANGS: [LangDesc; 2] = [ + LangDesc::new("English", langid!("en-US")), + LangDesc::new("Русский", langid!("ru-RU")), +]; + +pub fn set_current_locale(lang_id: LanguageIdentifier) { + *LANG.write().unwrap() = lang_id; +} + +pub fn tr(text_id: &str) -> String { + LOCALES + .try_lookup(&LANG.read().unwrap(), text_id) + .unwrap_or_else(|| text_id.to_string()) +} diff --git a/noita-proxy/src/lib.rs b/noita-proxy/src/lib.rs index 81fdf756..6540dfe7 100644 --- a/noita-proxy/src/lib.rs +++ b/noita-proxy/src/lib.rs @@ -8,6 +8,7 @@ use std::{ use bitcode::{Decode, Encode}; use clipboard::{ClipboardContext, ClipboardProvider}; use eframe::egui::{self, Align2, Color32, InnerResponse, Margin, RichText, TextureOptions, Ui}; +use lang::{set_current_locale, tr, LANGS}; use mod_manager::{Modmanager, ModmanagerSettings}; use net::{omni::PeerVariant, NetManagerInit}; use self_update::SelfUpdateManager; @@ -15,7 +16,9 @@ use serde::{Deserialize, Serialize}; use steamworks::{LobbyId, SteamAPIInitError}; use tangled::Peer; use tracing::info; +use unic_langid::LanguageIdentifier; +pub mod lang; pub mod messages; mod mod_manager; pub mod net; @@ -36,6 +39,7 @@ enum AppState { Netman { netman: Arc }, Error { message: String }, SelfUpdate, + LangPick, } #[derive(Debug, Serialize, Deserialize)] @@ -46,6 +50,7 @@ struct AppSavedState { nickname: Option, times_started: u32, world_sync_version: u32, + lang_id: Option, } impl Default for AppSavedState { @@ -57,6 +62,7 @@ impl Default for AppSavedState { nickname: None, times_started: 0, world_sync_version: 1, + lang_id: None, } } } @@ -102,10 +108,18 @@ impl App { .and_then(|storage| eframe::get_value(storage, MODMANAGER)) .unwrap_or_default(); saved_state.times_started += 1; + + let state = if let Some(lang_id) = &saved_state.lang_id { + set_current_locale(lang_id.clone()); + AppState::ModManager + } else { + AppState::LangPick + }; + egui_extras::install_image_loaders(&cc.egui_ctx); info!("Creating the app..."); Self { - state: AppState::ModManager, + state, modmanager: Modmanager::default(), steam_state: steam_helper::SteamState::new(), saved_state, @@ -196,20 +210,39 @@ impl App { let item_spacing = ui.spacing().item_spacing.x; let rect = ui.max_rect(); + let (rect, right_b_panel) = + rect.split_left_right_at_x(rect.right() - (50.0 + item_spacing)); let (settings_rect, right) = rect.split_left_right_at_fraction(0.5); let (steam_connect_rect, ip_connect_rect) = right.split_top_bottom_at_fraction(0.5); + + ui.allocate_ui_at_rect(right_b_panel.shrink(item_spacing), |ui| { + filled_group(ui, |ui| { + ui.set_min_size(ui.available_size()); + + if ui.button("EN").clicked() { + self.state = AppState::LangPick; + } + }) + }); + ui.allocate_ui_at_rect(settings_rect.shrink(item_spacing), |ui| { filled_group(ui, |ui| { ui.set_min_size(ui.available_size()); - heading_with_underline(ui, "Game settings"); + heading_with_underline(ui, tr("connect_settings")); - ui.label("Debug settings"); - ui.checkbox(&mut self.saved_state.debug_mode, "Debug/cheat mode"); - ui.checkbox(&mut self.saved_state.use_constant_seed, "Use fixed seed"); + ui.label(tr("connect_settings_debug")); + ui.checkbox( + &mut self.saved_state.debug_mode, + tr("connect_settings_debug_en"), + ); + ui.checkbox( + &mut self.saved_state.use_constant_seed, + tr("connect_settings_debug_fixed_seed"), + ); ui.add_space(20.0); - ui.label("World sync version to use:"); + ui.label(tr("connect_settings_wsv")); ui.horizontal(|ui| { ui.radio_value(&mut self.saved_state.world_sync_version, 1, "v1"); ui.radio_value( @@ -224,14 +257,14 @@ impl App { filled_group(ui, |ui| { ui.set_min_size(ui.available_size()); - heading_with_underline(ui, "Connect using steam"); + heading_with_underline(ui, tr("connect_steam")); match &self.steam_state { Ok(_) => { - if ui.button("Create lobby").clicked() { + if ui.button(tr("connect_steam_create")).clicked() { self.start_steam_host(); } - if ui.button("Connect to lobby in clipboard").clicked() { + if ui.button(tr("connect_steam_connect")).clicked() { let id = ClipboardProvider::new() .and_then(|mut ctx: ClipboardContext| ctx.get_contents()); match id { @@ -256,7 +289,7 @@ impl App { filled_group(ui, |ui| { ui.set_min_size(ui.available_size()); - heading_with_underline(ui, "Connect by ip"); + heading_with_underline(ui, tr("connect_ip")); ui.label("Note: steam networking is more reliable. Use it, if possible."); if ui.button("Host").clicked() { @@ -387,6 +420,25 @@ impl eframe::App for App { self.self_update.self_update(ui); }); } + AppState::LangPick => { + egui::Window::new(tr("lang_picker")) + .auto_sized() + .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) + .show(ctx, |ui| { + for lang in &LANGS { + ui.set_max_width(200.0); + ui.vertical_centered_justified(|ui| { + if ui.button(lang.name()).clicked() { + self.saved_state.lang_id = Some(lang.id()); + set_current_locale(lang.id()) + } + }); + } + if ui.button(tr("button_confirm")).clicked() { + self.state = AppState::ModManager; + } + }); + } }; } diff --git a/noita-proxy/src/main.rs b/noita-proxy/src/main.rs index d58ed742..be55dca9 100644 --- a/noita-proxy/src/main.rs +++ b/noita-proxy/src/main.rs @@ -15,6 +15,7 @@ fn main() -> Result<(), eframe::Error> { ) .finish(); tracing::subscriber::set_global_default(my_subscriber).expect("setting tracing default failed"); + let icon = image::load_from_memory(include_bytes!("../assets/icon.png")) .unwrap() .to_rgba8();