diff --git a/src/mail.rs b/src/mail.rs index cdfcda7..f058176 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use failure; use handlebars::Handlebars; @@ -10,10 +10,9 @@ use uuid::Uuid; use crate::counters; use rocket_i18n::I18n; -use gettext_macros::include_i18n; use gettext_macros::i18n; -use crate::i18n::I18NHelper; +use crate::template_helpers; use crate::database::types::Email; use crate::Result; @@ -73,7 +72,7 @@ impl Service { fn new(from: String, base_uri: String, template_dir: PathBuf, transport: Transport) -> Result { - let templates = load_handlebars(template_dir)?; + let templates = template_helpers::load_handlebars(template_dir)?; let domain = url::Url::parse(&base_uri) ?.host_str().ok_or_else(|| failure::err_msg("No host in base-URI")) @@ -167,7 +166,7 @@ impl Service { &self, template: &str, locale: &str, - ctx: impl Serialize + Clone + ctx: impl Serialize ) -> Result<(String, String)> { let html = self.templates.render(&format!("{}/{}.htm", locale, template), &ctx) .or_else(|_| self.templates.render(&format!("{}.htm", template), &ctx)) @@ -185,7 +184,7 @@ impl Service { subject: &str, template: &str, locale: &str, - ctx: impl Serialize + Clone + ctx: impl Serialize ) -> Result<()> { let (html, txt) = self.render_template(template, locale, ctx)?; @@ -221,35 +220,3 @@ impl Service { } } -fn load_handlebars(template_dir: PathBuf) -> Result { - let mut handlebars = Handlebars::new(); - - let i18ns = include_i18n!(); - let i18n_helper = I18NHelper::new(i18ns); - handlebars.register_helper("text", Box::new(i18n_helper)); - - let mut glob_path = template_dir.join("**").join("*"); - glob_path.set_extension("hbs"); - let glob_path = glob_path.to_str().expect("valid glob path string"); - - for path in glob::glob(glob_path).unwrap().flatten() { - let template_name = remove_extension(path.strip_prefix(&template_dir)?); - handlebars.register_template_file(&template_name.to_string_lossy(), &path)?; - } - - Ok(handlebars) -} - -fn remove_extension>(path: P) -> PathBuf { - let path = path.as_ref(); - let stem = match path.file_stem() { - Some(stem) => stem, - None => return path.to_path_buf() - }; - - match path.parent() { - Some(parent) => parent.join(stem), - None => PathBuf::from(stem) - } -} - diff --git a/src/main.rs b/src/main.rs index 962ce23..84c09ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,6 +52,7 @@ mod rate_limiter; mod dump; mod counters; mod i18n; +mod template_helpers; mod gettext_strings; mod web; diff --git a/src/template_helpers.rs b/src/template_helpers.rs new file mode 100644 index 0000000..b2b1f81 --- /dev/null +++ b/src/template_helpers.rs @@ -0,0 +1,82 @@ +use std::path::{Path, PathBuf}; +use std::collections::HashSet; + +use handlebars::Handlebars; + +use gettext_macros::include_i18n; + +use crate::Result; +use crate::i18n::I18NHelper; + +#[derive(Debug)] +pub struct TemplateOverrides(String, HashSet<(String)>); + +impl TemplateOverrides { + pub fn load(template_path: &Path, localized_dir: &str) -> Result { + load_localized_template_names(template_path, localized_dir) + .map(|vec| Self(localized_dir.to_owned(), vec)) + } + + pub fn get_template_override(&self, lang: &str, tmpl: &str) -> Option { + let template_name = format!("{}/{}/{}", self.0, lang, tmpl); + if self.1.contains(&template_name) { + println!("{}", &template_name); + Some(template_name) + } else { + None + } + } +} + +fn load_localized_template_names(template_path: &Path, localized_dir: &str) -> Result> { + let language_glob = template_path.join(localized_dir).join("*"); + glob::glob(language_glob.to_str().expect("valid glob path string")) + .unwrap() + .flatten() + .flat_map(|language_path| { + let mut template_glob = language_path.join("**").join("*"); + template_glob.set_extension("hbs"); + glob::glob(template_glob.to_str().expect("valid glob path string")) + .unwrap() + .flatten() + .map(move |path| { + // TODO this is a hack + let template_name = remove_extension(remove_extension(path.strip_prefix(&template_path)?)); + Ok(template_name.to_string_lossy().into_owned()) + }) + }) + .collect() +} + +pub fn load_handlebars(template_dir: PathBuf) -> Result { + let mut handlebars = Handlebars::new(); + + let i18ns = include_i18n!(); + let i18n_helper = I18NHelper::new(i18ns); + handlebars.register_helper("text", Box::new(i18n_helper)); + + let mut glob_path = template_dir.join("**").join("*"); + glob_path.set_extension("hbs"); + let glob_path = glob_path.to_str().expect("valid glob path string"); + + for path in glob::glob(glob_path).unwrap().flatten() { + let template_name = remove_extension(path.strip_prefix(&template_dir)?); + handlebars.register_template_file(&template_name.to_string_lossy(), &path)?; + } + + Ok(handlebars) +} + +fn remove_extension>(path: P) -> PathBuf { + let path = path.as_ref(); + let stem = match path.file_stem() { + Some(stem) => stem, + None => return path.to_path_buf() + }; + + match path.parent() { + Some(parent) => parent.join(stem), + None => PathBuf::from(stem) + } +} + diff --git a/src/web/mod.rs b/src/web/mod.rs index 8214930..777bd7c 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -21,6 +21,7 @@ use std::path::PathBuf; use crate::mail; use crate::tokens; use crate::counters; +use crate::template_helpers::TemplateOverrides; use crate::i18n::I18NHelper; use crate::rate_limiter::RateLimiter; @@ -48,9 +49,16 @@ impl Responder<'static> for HagridTemplate { fn respond_to(self, req: &rocket::Request) -> std::result::Result, Status> { let HagridTemplate(tmpl, ctx) = self; let i18n: I18n = req.guard().expect("Error parsing language"); + let template_overrides: rocket::State = req.guard().expect("TemplateOverrides must be in managed state"); + let template_override = template_overrides.get_template_override(i18n.lang, tmpl); let origin: RequestOrigin = req.guard().expect("Error determining request origin"); let layout_context = templates::HagridLayout::new(ctx, i18n, origin); - Template::render(tmpl, layout_context).respond_to(req) + + if let Some(template_override) = template_override { + Template::render(template_override, layout_context) + } else { + Template::render(tmpl, layout_context) + }.respond_to(req) } } @@ -410,6 +418,8 @@ fn rocket_factory(mut rocket: rocket::Rocket) -> Result { let mail_service = configure_mail_service(rocket.config())?; let rate_limiter = configure_rate_limiter(rocket.config())?; let maintenance_mode = configure_maintenance_mode(rocket.config())?; + let localized_template_list = configure_localized_template_list(rocket.config())?; + println!("{:?}", localized_template_list); let prometheus = configure_prometheus(rocket.config()); @@ -427,6 +437,7 @@ fn rocket_factory(mut rocket: rocket::Rocket) -> Result { .manage(mail_service) .manage(db_service) .manage(rate_limiter) + .manage(localized_template_list) .mount("/", routes); if let Some(prometheus) = prometheus { @@ -514,6 +525,11 @@ fn configure_rate_limiter(config: &Config) -> Result { Ok(RateLimiter::new(timeout_secs)) } +fn configure_localized_template_list(config: &Config) -> Result { + let template_dir: PathBuf = config.get_str("template_dir")?.into(); + TemplateOverrides::load(&template_dir, "localized") +} + fn configure_maintenance_mode(config: &Config) -> Result { let maintenance_file: PathBuf = config.get_str("maintenance_file") .unwrap_or("maintenance").into();