diff --git a/Cargo.lock b/Cargo.lock index 0e028233a6..509a023643 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -654,6 +654,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "chunky-vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7bdea464ae038f09197b82430b921c53619fc8d2bcaf7b151013b3ca008017" + [[package]] name = "ciborium" version = "0.2.1" @@ -1108,7 +1114,7 @@ dependencies = [ "tikv-jemallocator", "tokio", "tokio-util", - "toml", + "toml 0.5.11", "whoami", ] @@ -1182,6 +1188,19 @@ dependencies = [ "syn 2.0.37", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.0", + "lock_api", + "once_cell", + "parking_lot_core 0.9.8", +] + [[package]] name = "data-encoding" version = "2.4.0" @@ -1555,6 +1574,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml 0.5.11", +] + [[package]] name = "flate2" version = "1.0.27" @@ -1565,6 +1593,65 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fluent" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f69378194459db76abd2ce3952b790db103ceb003008d3d50d97c41ff847a7" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-fallback" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08fdcccdeb6c01cb085f2bb3420506e6c67f025cee5db047529838c673a7d82b" +dependencies = [ + "async-trait", + "chunky-vec", + "fluent-bundle", + "futures", + "once_cell", + "rustc-hash", + "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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78" +dependencies = [ + "thiserror", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2586,6 +2673,76 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "i18n-config" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6691f16c6a35c1bb99a0f01aa39dd2b884d342b646689e9b8e4d51faf2cfdbd9" +dependencies = [ + "log", + "serde", + "serde_derive", + "thiserror", + "toml 0.7.8", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26261c73a1670a3f632a8765bb6b22c62fc391f3ddc805b87fd00cd6158e4456" +dependencies = [ + "arc-swap", + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "lazy_static", + "log", + "parking_lot 0.12.1", + "rust-embed", + "thiserror", + "unic-langid", + "walkdir", + "web-sys", +] + +[[package]] +name = "i18n-embed-fl" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc1f8715195dffc4caddcf1cf3128da15fe5d8a137606ea8856c9300047d5a2" +dependencies = [ + "dashmap", + "find-crate", + "fluent", + "fluent-syntax", + "i18n-config", + "i18n-embed", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.37", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a4d5bff745c9a6e1459c490059281b353a4ab0a4e1e58b3eeeaef71f97d07b" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -2731,6 +2888,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "intl-memoizer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f" +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.8.0" @@ -2798,7 +2974,7 @@ dependencies = [ "serde", "serde_json", "tokio", - "toml", + "toml 0.5.11", "tracing", "tracing-subscriber", "url", @@ -2822,7 +2998,7 @@ dependencies = [ "serde", "serde_json", "tokio", - "toml", + "toml 0.5.11", "tracing", "tracing-subscriber", "url", @@ -2836,7 +3012,7 @@ dependencies = [ "base64 0.21.4", "gix", "serde", - "toml", + "toml 0.5.11", ] [[package]] @@ -2850,7 +3026,7 @@ dependencies = [ "serde_json", "time", "tokio", - "toml", + "toml 0.5.11", "tracing", "url", "uuid", @@ -2965,7 +3141,7 @@ dependencies = [ "sketching", "tokio", "tokio-util", - "toml", + "toml 0.5.11", "tracing", "tss-esapi", "users", @@ -3016,7 +3192,7 @@ dependencies = [ "tokio", "tokio-openssl", "tokio-util", - "toml", + "toml 0.5.11", "tower", "tower-http", "tracing", @@ -3068,7 +3244,7 @@ dependencies = [ "time", "tokio", "tokio-util", - "toml", + "toml 0.5.11", "touch", "tracing", "url", @@ -3122,17 +3298,24 @@ dependencies = [ name = "kanidmd_web_ui" version = "1.1.0-rc.14-dev" dependencies = [ + "fluent", + "fluent-bundle", + "fluent-fallback", "gloo", "gloo-timers 0.3.0", + "i18n-embed", + "i18n-embed-fl", "js-sys", "kanidm_proto", "lazy_static", "qrcode", "regex", + "rust-embed", "serde", "serde-wasm-bindgen 0.5.0", "serde_json", "time", + "unic-langid", "url", "uuid", "wasm-bindgen", @@ -3794,7 +3977,7 @@ dependencies = [ "tokio", "tokio-openssl", "tokio-util", - "toml", + "toml 0.5.11", "tracing", "tracing-subscriber", "uuid", @@ -4439,6 +4622,40 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rust-embed" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3d8c6fd84090ae348e63a84336b112b5c3918b3bf0493a581f7bd8ee623c29" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.37", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873feff8cb7bf86fdf0a71bb21c95159f4e4a37dd7a4bd1855a940909b583ada" +dependencies = [ + "sha2 0.10.7", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -4570,6 +4787,12 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af" + [[package]] name = "selinux" version = "0.4.2" @@ -4706,6 +4929,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5105,6 +5337,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ac3f5b6856e931e15e07b478e98c8045239829a65f9156d4fa7e7788197a5ef" +dependencies = [ + "displaydoc", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -5216,19 +5457,36 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.0.0", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -5427,6 +5685,15 @@ dependencies = [ "target-lexicon", ] +[[package]] +name = "type-map" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46" +dependencies = [ + "rustc-hash", +] + [[package]] name = "typenum" version = "1.16.0" @@ -5439,6 +5706,25 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "unic-langid" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398f9ad7239db44fd0f80fe068d12ff22d78354080332a5077dc6f52f14dcf2f" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35bfd2f2b8796545b55d7d3fd3e89a0613f68a0d1c8bc28cb7ff96b411a35ff" +dependencies = [ + "serde", + "tinystr", +] + [[package]] name = "unicase" version = "2.7.0" diff --git a/server/web_ui/Cargo.toml b/server/web_ui/Cargo.toml index e1b7c93c4b..d278a7cb8d 100644 --- a/server/web_ui/Cargo.toml +++ b/server/web_ui/Cargo.toml @@ -34,6 +34,13 @@ yew-router = { workspace = true } time = { workspace = true } gloo-timers = "0.3.0" wasm-timer = "0.2.5" +i18n-embed = { version = "0.14.0", features = ["fluent-system", "web-sys-requester"]} +i18n-embed-fl = "0.7.0" +rust-embed = "8" +unic-langid = "0.9.1" +fluent = "0.16.0" +fluent-bundle = "0.15.2" +fluent-fallback = "0.7.0" regex.workspace = true lazy_static.workspace = true diff --git a/server/web_ui/i18n.toml b/server/web_ui/i18n.toml new file mode 100644 index 0000000000..9d3864a310 --- /dev/null +++ b/server/web_ui/i18n.toml @@ -0,0 +1,4 @@ +fallback_language = "en-US" + +[fluent] +assets_dir = "i18n" \ No newline at end of file diff --git a/server/web_ui/i18n/en-US/kanidmd_web_ui.ftl b/server/web_ui/i18n/en-US/kanidmd_web_ui.ftl new file mode 100644 index 0000000000..b82e40eb9f --- /dev/null +++ b/server/web_ui/i18n/en-US/kanidmd_web_ui.ftl @@ -0,0 +1,13 @@ +page-not-found = 404 — Page Not Found +goto-home = Home +breadcrumb-admin = Admin +breadcrumb-admin-accounts = Accounts +header-account-admin = Account Administration +accounts-list-load-waiting = Waiting on the accounts list to load... +th-accounts-display-name = Display Name +th-accounts-username = Username +th-accounts-description = Description +account-type-service = Service Account +account-type-person = Person +alert-failed-to-query-accounts = Failed to query accounts +alert-unauthorized = You're not authorized to see this page \ No newline at end of file diff --git a/server/web_ui/pkg/kanidmd_web_ui.js b/server/web_ui/pkg/kanidmd_web_ui.js index f3967e46d0..fea5e95b3a 100644 --- a/server/web_ui/pkg/kanidmd_web_ui.js +++ b/server/web_ui/pkg/kanidmd_web_ui.js @@ -225,7 +225,7 @@ function makeMutClosure(arg0, arg1, dtor, f) { return real; } function __wbg_adapter_48(arg0, arg1) { - wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hee64a599f575a1b3(arg0, arg1); + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hd5062f8742a3e5bf(arg0, arg1); } let stack_pointer = 128; @@ -237,19 +237,19 @@ function addBorrowedObject(obj) { } function __wbg_adapter_51(arg0, arg1, arg2) { try { - wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h6821a64a8b32b54e(arg0, arg1, addBorrowedObject(arg2)); + wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h3973ccc10169b3e2(arg0, arg1, addBorrowedObject(arg2)); } finally { heap[stack_pointer++] = undefined; } } function __wbg_adapter_54(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h578c1a08304759e7(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf0d692ef0f316208(arg0, arg1, addHeapObject(arg2)); } function __wbg_adapter_57(arg0, arg1, arg2) { try { - wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hb3016357f235b290(arg0, arg1, addBorrowedObject(arg2)); + wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__he09faeaf47590909(arg0, arg1, addBorrowedObject(arg2)); } finally { heap[stack_pointer++] = undefined; } @@ -724,6 +724,10 @@ function __wbg_get_imports() { const ret = getObject(arg0).credentials; return addHeapObject(ret); }; + imports.wbg.__wbg_languages_4ab80469955a57f7 = function(arg0) { + const ret = getObject(arg0).languages; + return addHeapObject(ret); + }; imports.wbg.__wbg_parentNode_9e53f8b17eb98c9d = function(arg0) { const ret = getObject(arg0).parentNode; return isLikeNone(ret) ? 0 : addHeapObject(ret); @@ -1146,20 +1150,20 @@ function __wbg_get_imports() { const ret = wasm.memory; return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper659 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 380, __wbg_adapter_48); + imports.wbg.__wbindgen_closure_wrapper673 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 387, __wbg_adapter_48); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper4175 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 1999, __wbg_adapter_51); + imports.wbg.__wbindgen_closure_wrapper4684 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 2258, __wbg_adapter_51); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper4941 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 2306, __wbg_adapter_54); + imports.wbg.__wbindgen_closure_wrapper5454 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 2565, __wbg_adapter_54); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper5000 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 2330, __wbg_adapter_57); + imports.wbg.__wbindgen_closure_wrapper5513 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 2589, __wbg_adapter_57); return addHeapObject(ret); }; diff --git a/server/web_ui/pkg/kanidmd_web_ui_bg.wasm b/server/web_ui/pkg/kanidmd_web_ui_bg.wasm index 434b8949a6..847b241d72 100644 Binary files a/server/web_ui/pkg/kanidmd_web_ui_bg.wasm and b/server/web_ui/pkg/kanidmd_web_ui_bg.wasm differ diff --git a/server/web_ui/pkg/kanidmd_web_ui_bg.wasm.br b/server/web_ui/pkg/kanidmd_web_ui_bg.wasm.br index 79ebdf4ca6..c2246cb5cc 100644 Binary files a/server/web_ui/pkg/kanidmd_web_ui_bg.wasm.br and b/server/web_ui/pkg/kanidmd_web_ui_bg.wasm.br differ diff --git a/server/web_ui/src/components/admin_accounts.rs b/server/web_ui/src/components/admin_accounts.rs index 209dd0a1dc..dc6c440527 100644 --- a/server/web_ui/src/components/admin_accounts.rs +++ b/server/web_ui/src/components/admin_accounts.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use gloo::console; +use i18n_embed_fl::fl; use yew::{html, Component, Context, Html, Properties}; use yew_router::prelude::Link; @@ -9,6 +10,7 @@ use crate::components::alpha_warning_banner; use crate::constants::{ CSS_BREADCRUMB_ITEM, CSS_BREADCRUMB_ITEM_ACTIVE, CSS_CELL, CSS_DT, CSS_TABLE, }; +use crate::manager::I18n; use crate::utils::{do_alert_error, do_page_header}; use crate::views::AdminRoute; use crate::{do_request, RequestMethod}; @@ -24,6 +26,8 @@ impl From for AdminListAccountsMsg { pub struct AdminListAccounts { state: ViewState, + i18n: Rc, + _context_listener: ContextHandle>, } // callback messaging for this confused pile of crab-bait @@ -36,6 +40,7 @@ pub enum AdminListAccountsMsg { emsg: String, kopid: Option, }, + I18n(I18n), } enum ViewState { @@ -143,6 +148,10 @@ impl Component for AdminListAccounts { fn create(ctx: &Context) -> Self { // TODO: work out the querystring thing so we can just show x number of elements // console::log!("query: {:?}", location().query); + let (i18n, context_listener) = ctx + .link() + .context(ctx.link().callback(Self::Message::I18n)) + .unwrap(); // start pulling the account data on startup ctx.link().send_future(async move { @@ -153,6 +162,8 @@ impl Component for AdminListAccounts { }); AdminListAccounts { state: ViewState::Loading, + i18n, + _context_listener: context_listener, } } @@ -178,6 +189,10 @@ impl Component for AdminListAccounts { console::log!("emsg: {:?}", emsg); console::log!("kopid: {:?}", kopid); } + AdminListAccountsMsg::I18n(i18n) => { + self.i18n = i18n; + return true; + } } false } @@ -187,15 +202,15 @@ impl Component for AdminListAccounts { <> - {do_page_header("Account Administration")} + {do_page_header(fl!(self.i18n.i18n, "header-account-admin"))} { alpha_warning_banner() }
{match &self.state { ViewState::Loading => { - html! {"Waiting on the accounts list to load..."} + html! {fl!(self.i18n.i18n, "accounts-list-load-waiting")} } ViewState::Responded { response } => { @@ -206,9 +221,9 @@ impl Component for AdminListAccounts { - {"Display Name"} - {"Username"} - {"Description"} + {fl!(self.i18n.i18n, "th-accounts-display-name")} + {fl!(self.i18n.i18n, "th-accounts-username")} + {fl!(self.i18n.i18n, "th-accounts-description")} @@ -227,8 +242,8 @@ impl Component for AdminListAccounts { None => String::from(""), }; let account_type: Html = match account.object_type { - EntityType::ServiceAccount => html!{{"Service}, - EntityType::Person => html!{{"Person"}}, + EntityType::ServiceAccount => html!{{fl!(self.i18n.i18n,}, + EntityType::Person => html!{{fl!(self.i18n.i18n,}, _ => html!("x"), }; @@ -269,12 +284,12 @@ impl Component for AdminListAccounts { console::error!("Failed to pull details", format!("{:?}", kopid)); html!( <> - {do_alert_error("Failed to Query Accounts", Some(emsg))} + {do_alert_error(fl!(self.i18n.i18n, "alert-failed-to-query-accounts"), Some(emsg))} ) } ViewState::NotAuthorized {} => { - do_alert_error("You're not authorized to see this page!", None) + do_alert_error(fl!(self.i18n.i18n, "alert-unauthorized"), None) } }}
diff --git a/server/web_ui/src/manager.rs b/server/web_ui/src/manager.rs index 4f9de80c06..783fabca03 100644 --- a/server/web_ui/src/manager.rs +++ b/server/web_ui/src/manager.rs @@ -4,7 +4,12 @@ //! not authenticated, this will determine that and send you to authentication first, then //! will allow you to proceed with the oauth flow. +use std::rc::Rc; + use gloo::console; +use i18n_embed::LanguageLoader; +use i18n_embed::unic_langid::LanguageIdentifier; +use i18n_embed_fl::fl; use serde::{Deserialize, Serialize}; use wasm_bindgen::UnwrapThrowExt; use yew::functional::*; @@ -16,6 +21,15 @@ use crate::login::{LoginApp, LoginWorkflow}; use crate::oauth2::Oauth2App; use crate::views::{ViewRoute, ViewsApp}; +use i18n_embed::{WebLanguageRequester, fluent::{ + FluentLanguageLoader, fluent_language_loader +}}; +use rust_embed::RustEmbed; + +#[derive(RustEmbed)] +#[folder = "i18n"] +struct Localizations; + // router to decide on state. #[derive(Routable, PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub enum Route { @@ -53,6 +67,28 @@ fn landing() -> Html { html! {
} } +#[function_component] +fn NotFound() -> Html { + let i18n = use_context::>().unwrap(); + + html! { + <> +
+ + // TODO: replace this with a call to domain info +

{ fl!(i18n.i18n, "page-not-found") }

+ +
+ to={ ViewRoute::Apps }> + { fl!(i18n.i18n, "goto-home") } + > +
+
+ { crate::utils::do_footer() } + + } +} + // Needed for yew to pass by value #[allow(clippy::needless_pass_by_value)] fn switch(route: Route) -> Html { @@ -74,27 +110,46 @@ fn switch(route: Route) -> Html { Route::NotFound => { add_body_form_classes!(); - html! { - <> -
- - // TODO: replace this with a call to domain info -

{ "404 - Page not found" }

- -
- to={ ViewRoute::Apps }> - { "Home" } - > -
-
- { crate::utils::do_footer() } - - } + html! { } } } } -pub struct ManagerApp {} +#[derive(Clone, Debug)] +pub struct I18n { + pub i18n: Rc +} + +impl I18n { + fn new() -> I18n { + let loader: FluentLanguageLoader = fluent_language_loader!(); + let requested_languages = { + let mut it = WebLanguageRequester::requested_languages(); + it.push(loader.fallback_language().clone()); + it + }; + + let languages_vec = requested_languages.iter().map(|it| it).collect::>(); + let languages = languages_vec.as_slice(); + let _ = loader + .load_languages(&Localizations, &languages) + .map_err(|err| { + console::warn!("issue loading i18n: {}", err.to_string()); + }); + + I18n { i18n: loader.into() } + } +} + +impl PartialEq for I18n { + fn eq(&self, _rhs: &I18n) -> bool { + true + } +} + +pub struct ManagerApp { + i18n: Rc +} impl Component for ManagerApp { type Message = (); @@ -103,7 +158,7 @@ impl Component for ManagerApp { fn create(_ctx: &Context) -> Self { #[cfg(debug_assertions)] console::debug!("manager::create"); - ManagerApp {} + ManagerApp { i18n: I18n::new().into() } } fn changed(&mut self, _ctx: &Context, _props: &Self::Properties) -> bool { @@ -127,9 +182,11 @@ impl Component for ManagerApp { fn view(&self, _ctx: &Context) -> Html { html! { - - render={ switch } /> - + > context={self.i18n.clone()}> + + render={ switch } /> + + >> } } }