diff --git a/Cargo.lock b/Cargo.lock index 5629134..974ee1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -804,16 +804,15 @@ dependencies = [ "dotenv", "glob", "handlebars", - "lazy_static", "log", "pretty_env_logger", - "regex", "rust-embed", "serde", "serde_json", "serde_qs", "serde_yaml", "tide", + "y10n", ] [[package]] @@ -2034,6 +2033,18 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "y10n" +version = "0.1.0" +dependencies = [ + "glob", + "lazy_static", + "log", + "regex", + "serde", + "serde_yaml", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 77a4722..efadb68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,8 @@ dotenv = "*" glob = "0" # For rendering simple HTTP views handlebars = { version = "4", features = ["dir_source"] } -lazy_static = "1" log = "*" pretty_env_logger = "0.3" -regex = "1" # Used for embedding templates rust-embed = "5" @@ -28,3 +26,6 @@ serde_yaml = "0.8" # Web framework tide = "*" + +# Localization support +y10n = { "path" = "../y10n" } diff --git a/src/dao.rs b/src/dao.rs index aed3baa..378cdb6 100644 --- a/src/dao.rs +++ b/src/dao.rs @@ -34,9 +34,9 @@ pub mod v1 { Ok(path) => { projects.push( serde_yaml::from_reader(File::open(path).expect("Failed to open path")) - .expect("Failed to load") + .expect("Failed to load"), ); - }, + } Err(e) => println!("{:?}", e), } } @@ -46,17 +46,13 @@ pub mod v1 { #[derive(Clone, Debug, Deserialize, Serialize)] pub enum ScmType { - Git { - url: String, - }, + Git { url: String }, } #[derive(Clone, Debug, Deserialize, Serialize)] pub enum TriggerType { Manual, - Cron { - schedule: String, - }, + Cron { schedule: String }, } #[cfg(test)] diff --git a/src/i18n.rs b/src/i18n.rs deleted file mode 100644 index a28d080..0000000 --- a/src/i18n.rs +++ /dev/null @@ -1,81 +0,0 @@ -/** - * The i18n module provides some simple internationalization support - * in a way that is compatible with handlebars rendering - */ -use log::*; - -pub fn parse_languages(header: &str) -> Vec { - trace!("Parsing languages from: {}", header); - let mut results = vec![]; - - for part in header.split(",") { - if let Ok(language) = Language::from(part) { - results.push(language); - } - } - results -} - -#[derive(Clone, Debug)] -pub struct Language { - pub code: String, - region: Option, - quality: f64, -} - -lazy_static! { - static ref LANG_REGEX: regex::Regex = regex::Regex::new(r"(?P\w+)-?(?P\w+)?(;q=(?P([0-9]*[.])?[0-9]+)?)?").unwrap(); -} - -impl Language { - fn from(segment: &str) -> Result { - if let Some(captures) = LANG_REGEX.captures(segment) { - println!("caps: {:?}", captures); - Ok(Language { - code: captures.name("code").map_or("unknown".to_string(), |c| c.as_str().to_string()), - region: captures.name("region").map_or(None, |c| Some(c.as_str().to_string())), - quality: captures.name("quality").map_or(1.0, |c| c.as_str().parse().unwrap_or(0.0)), - }) - } - else { - Err(Error::Generic) - } - } -} - -#[derive(Clone, Debug)] -enum Error { - Generic, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn language_from_segment() { - let lang = Language::from("en-US"); - assert!(lang.is_ok()); - let lang = lang.unwrap(); - assert_eq!("en", lang.code); - assert_eq!(Some("US".to_string()), lang.region); - assert_eq!(1.0, lang.quality); - } - - #[test] - fn parse_langs_simple() { - let header = "en-US,en;q=0.5"; - let langs = parse_languages(&header); - assert_eq!(langs.len(), 2); - } - - #[test] - fn parse_langs_multi() { - let header = "en-US,en;q=0.7,de-DE;q=0.3"; - let langs = parse_languages(&header); - assert_eq!(langs.len(), 3); - let de = langs.get(2).unwrap(); - assert_eq!("de", de.code); - assert_eq!(0.3, de.quality); - } -} diff --git a/src/main.rs b/src/main.rs index 865edb8..b64053d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,10 @@ */ #[macro_use] extern crate serde_json; -#[macro_use] -extern crate lazy_static; use log::*; mod dao; -mod i18n; mod state; #[async_std::main] @@ -36,7 +33,7 @@ async fn main() -> Result<(), std::io::Error> { Some(l) => l.as_str(), None => "en", }; - let langs = crate::i18n::parse_languages(lang); + let langs = y10n::parse_accept_language(lang); info!("Lang: {:?}", langs); req.state().render("index", &langs, Some(data)).await diff --git a/src/state.rs b/src/state.rs index b810b9c..2dce7b6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,14 +1,12 @@ use async_std::sync::{Arc, RwLock}; use handlebars::Handlebars; use log::*; -use std::fs::File; -use std::path::Path; - -use crate::i18n::Language; +use y10n::*; #[derive(Clone, Debug)] pub struct AppState<'a> { hb: Arc>>, + y10n: Arc>, } impl AppState<'_> { @@ -17,8 +15,11 @@ impl AppState<'_> { // This ensures that we get errors rather than empty strings for bad values hb.set_strict_mode(true); + let y10n = Y10n::from_glob("i18n/**/*.yml"); + Self { hb: Arc::new(RwLock::new(hb)), + y10n: Arc::new(RwLock::new(y10n)), } } @@ -44,24 +45,17 @@ impl AppState<'_> { } if let Some(data) = &data { - if ! data.is_object() { - warn!("The render function was called without a map, this can lead to funny results"); + if !data.is_object() { + warn!( + "The render function was called without a map, this can lead to funny results" + ); } } - let mut i18n = serde_yaml::Value::Null; - // merge in the langages from lowest priority - for lang in langs.iter().rev() { - let filename = format!("i18n/{}.yml", lang.code); - if ! Path::exists(Path::new(&filename)) { - continue; - } + let y10n = self.y10n.read().await; + let localize = y10n.localize(&langs); + let mut mustache = json!({ "t": localize }); - let t: serde_yaml::Value = serde_yaml::from_reader(File::open(filename)?)?; - merge_yaml(&mut i18n, t); - } - - let mut mustache = json!({"t" : i18n}); merge(&mut mustache, data.unwrap_or(serde_json::Value::Null)); let hb = self.hb.read().await; @@ -70,11 +64,11 @@ impl AppState<'_> { let mut body = tide::Body::from_string(view); body.set_mime("text/html"); Ok(body) - }, + } Err(e) => { error!("Failed to render {}: {:?}", name, e); Err(e.into()) - }, + } } } } @@ -85,11 +79,10 @@ fn merge(a: &mut serde_json::Value, b: serde_json::Value) { let a = a.as_object_mut().unwrap(); for (k, v) in b { if v.is_array() && a.contains_key(&k) && a.get(&k).as_ref().unwrap().is_array() { - let mut _a = a.get(&k).unwrap().as_array().unwrap().to_owned(); - _a.append(&mut v.as_array().unwrap().to_owned()); - a[&k] = serde_json::Value::from(_a); - } - else{ + let mut _a = a.get(&k).unwrap().as_array().unwrap().to_owned(); + _a.append(&mut v.as_array().unwrap().to_owned()); + a[&k] = serde_json::Value::from(_a); + } else { merge(a.entry(k).or_insert(serde_json::Value::Null), v); } } @@ -97,24 +90,3 @@ fn merge(a: &mut serde_json::Value, b: serde_json::Value) { (a, b) => *a = b, } } - -fn merge_yaml(a: &mut serde_yaml::Value, b: serde_yaml::Value) { - match (a, b) { - (a @ &mut serde_yaml::Value::Mapping(_), serde_yaml::Value::Mapping(b)) => { - let a = a.as_mapping_mut().unwrap(); - for (k, v) in b { - if v.is_sequence() && a.contains_key(&k) && a[&k].is_sequence() { - let mut _b = a.get(&k).unwrap().as_sequence().unwrap().to_owned(); - _b.append(&mut v.as_sequence().unwrap().to_owned()); - a[&k] = serde_yaml::Value::from(_b); - continue; - } - if !a.contains_key(&k) {a.insert(k.to_owned(), v.to_owned());} - else { merge_yaml(&mut a[&k], v); } - - } - - } - (a, b) => *a = b, - } -}