diff --git a/Cargo.lock b/Cargo.lock index bcced0f..d35ff67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -308,6 +308,7 @@ dependencies = [ "bcrypt", "diesel", "dotenv", + "fluent-bundle", "r2d2", "regex", "rocket", @@ -316,6 +317,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "unic-langid", "validator", ] @@ -331,6 +333,36 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "fluent-bundle" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a79916560098f0a57215a489e37b7fc83088949eab7f7977dcc329b254d50c17" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rental", + "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.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9389354f858e38f37d9a249133611a1fcaec469f44773b04ddbd82f4f08d49eb" + [[package]] name = "fsevent" version = "0.4.0" @@ -366,6 +398,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.12.3" @@ -555,6 +596,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "intl-memoizer" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0ed58ba6089d49f8a9a7d5e16fc9b9e2019cdf40ef270f3d465fa244d9630b" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c271cdb1f12a9feb3a017619c3ee681f971f270f6757341d6abe1f9f7a98bc3" +dependencies = [ + "tinystr", + "unic-langid", +] + [[package]] name = "iovec" version = "0.1.4" @@ -1022,6 +1083,27 @@ version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" +[[package]] +name = "rental" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8545debe98b2b139fb04cad8618b530e9b07c152d99a5de83c860b877d67847f" +dependencies = [ + "rental-impl", + "stable_deref_trait", +] + +[[package]] +name = "rental-impl" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "475e68978dc5b743f2f40d8e0a8fdc83f1c5e78cbf4b8fa5e74e73beebc340de" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.7", + "syn 1.0.44", +] + [[package]] name = "rocket" version = "0.4.5" @@ -1214,6 +1296,12 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "state" version = "0.4.1" @@ -1274,6 +1362,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tinystr" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1" + [[package]] name = "tinyvec" version = "0.3.4" @@ -1295,6 +1389,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" +[[package]] +name = "type-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2741b1474c327d95c1f1e3b0a2c3977c8e128409c572a33af2914e7d636717" +dependencies = [ + "fxhash", +] + [[package]] name = "typeable" version = "0.1.2" @@ -1313,6 +1416,24 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "unic-langid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d" +dependencies = [ + "tinystr", +] + [[package]] name = "unicase" version = "1.4.2" diff --git a/Cargo.toml b/Cargo.toml index 6a0731b..6d7cc44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,12 +15,14 @@ publish = true [dependencies] bcrypt = "0.8.2" dotenv = "0.15.0" +fluent-bundle = "0.13.1" r2d2 = "0.8.9" regex = "1.4.1" rocket_csrf = { path = "/home/kotovalexarian/repos/github/kotovalexarian/rocket_csrf" } serde = "1.0" serde_derive = "1.0" serde_json = "1.0" +unic-langid = "0.9.0" [dependencies.diesel] version = "1.4.5" diff --git a/src/config.rs b/src/config.rs index e4b4891..47c5236 100644 --- a/src/config.rs +++ b/src/config.rs @@ -128,6 +128,19 @@ impl Config { Ok(result_str.to_string()) } + pub fn locales_path(&self) -> Result { + let mut result_path_buf = std::path::PathBuf::new(); + result_path_buf.push(self.root.to_string()); + result_path_buf.push("locales"); + + let result_str = match result_path_buf.to_str() { + None => return Err(()), + Some(value) => value, + }; + + Ok(result_str.to_string()) + } + pub fn use_env_for_root(&mut self) { self.root = match std::env::var("ROOT") { Err(_) => return, diff --git a/src/i18n.rs b/src/i18n.rs new file mode 100644 index 0000000..8181a15 --- /dev/null +++ b/src/i18n.rs @@ -0,0 +1,53 @@ +use std::collections::HashMap; + +use fluent_bundle::FluentResource; +use fluent_bundle::concurrent::FluentBundle; +use unic_langid::LanguageIdentifier; + +pub struct I18n(HashMap>); + +impl I18n { + pub fn new(path: &str, locales: &[&str]) -> Result { + let lang_ids: Vec> = + locales.iter().map(|locale| { + locale.parse::() + }).collect(); + + if let Some(_) = lang_ids.iter().find(|lang_id| lang_id.is_err()) { + return Err(()); + } + + let lang_ids: Vec<&LanguageIdentifier> = + lang_ids.iter().map(|lang_id| lang_id.as_ref().unwrap()).collect(); + + let mut hash_map = HashMap::new(); + + for lang_id in lang_ids { + let locale = lang_id.to_string(); + + let mut path_buf = std::path::PathBuf::from(path); + path_buf.push(&locale); + path_buf.set_extension("ftl"); + + let data = match std::fs::read_to_string(path_buf) { + Ok(data) => data, + Err(_) => return Err(()), + }; + + let resource = match FluentResource::try_new(data) { + Ok(resource) => resource, + Err(_) => return Err(()), + }; + + let mut bundle = FluentBundle::default(); + + if let Err(_) = bundle.add_resource(resource) { + return Err(()); + }; + + hash_map.insert(locale, bundle); + } + + Ok(Self(hash_map)) + } +} diff --git a/src/main.rs b/src/main.rs index de8f2bf..4d0bfca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod config; mod database; mod forms; +mod i18n; mod models; mod responses; mod routes; diff --git a/src/routes/home.rs b/src/routes/home.rs index 80e2b92..b15ea60 100644 --- a/src/routes/home.rs +++ b/src/routes/home.rs @@ -1,13 +1,16 @@ use crate::states; use crate::views; +use crate::i18n::I18n; use crate::responses::CommonResponse; +use rocket::State; use rocket_contrib::templates::Template; use rocket_csrf::CsrfToken; #[get("/")] pub fn index( + _i18n: State, csrf_token: CsrfToken, current_user: states::MaybeCurrentUser, ) -> Result { diff --git a/src/routes/sessions.rs b/src/routes/sessions.rs index 7769051..2090734 100644 --- a/src/routes/sessions.rs +++ b/src/routes/sessions.rs @@ -4,8 +4,10 @@ use crate::views; use crate::models; use crate::forms; +use crate::i18n::I18n; use crate::responses::CommonResponse; +use rocket::State; use rocket::http::{Cookie, Cookies}; use rocket::response::Redirect; use rocket::request::Form; @@ -14,6 +16,7 @@ use rocket_csrf::CsrfToken; #[get("/sign_in")] pub fn new( + _i18n: State, csrf_token: CsrfToken, current_user: states::MaybeCurrentUser, ) -> Result { @@ -40,6 +43,7 @@ pub fn new( #[post("/sign_in", data = "
")] pub fn create( + _i18n: State, csrf_token: CsrfToken, db_conn: database::DbConn, current_user: states::MaybeCurrentUser, @@ -76,6 +80,7 @@ pub fn create( #[delete("/sign_out", data = "")] pub fn delete( + _i18n: State, csrf_token: CsrfToken, current_user: states::MaybeCurrentUser, form: Form, diff --git a/src/routes/users.rs b/src/routes/users.rs index 072bb68..46b995d 100644 --- a/src/routes/users.rs +++ b/src/routes/users.rs @@ -4,8 +4,10 @@ use crate::views; use crate::models; use crate::forms; +use crate::i18n::I18n; use crate::responses::CommonResponse; +use rocket::State; use rocket::http::{Cookie, Cookies}; use rocket::response::Redirect; use rocket::request::Form; @@ -14,6 +16,7 @@ use rocket_csrf::CsrfToken; #[get("/sign_up")] pub fn new( + _i18n: State, csrf_token: CsrfToken, current_user: states::MaybeCurrentUser, ) -> Result { @@ -40,6 +43,7 @@ pub fn new( #[post("/sign_up", data = "")] pub fn create( + _i18n: State, csrf_token: CsrfToken, db_conn: database::DbConn, current_user: states::MaybeCurrentUser, diff --git a/src/web.rs b/src/web.rs index fd8693b..553be65 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,5 +1,6 @@ use crate::config; use crate::database; +use crate::i18n::I18n; use crate::routes; use rocket_contrib::serve::{Options as ServeOptions, StaticFiles}; @@ -8,9 +9,13 @@ use rocket_contrib::templates::Template; pub fn rocket(config: &config::Config) -> Result { let rocket_config = config.to_rocket_config()?; - let public_path = config.public_path()?; + let public_path = config.public_path()?; + let locales_path = config.locales_path()?; + + let i18n = I18n::new(&locales_path, &["en", "ru"])?; let result = rocket::custom(rocket_config) + .manage(i18n) .manage(database::create_db_pool(config)) .attach(rocket_csrf::Fairing::new()) .attach(Template::fairing())