i18n: add template override mechanism

This commit is contained in:
Vincent Breitmoser 2019-09-30 12:52:00 +02:00
parent 069d332123
commit 6236d3bf40
No known key found for this signature in database
GPG Key ID: 7BD18320DEADFA11
4 changed files with 105 additions and 39 deletions

View File

@ -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<Self> {
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<Handlebars> {
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<P: AsRef<Path>>(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)
}
}

View File

@ -52,6 +52,7 @@ mod rate_limiter;
mod dump;
mod counters;
mod i18n;
mod template_helpers;
mod gettext_strings;
mod web;

82
src/template_helpers.rs Normal file
View File

@ -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<Self> {
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<String> {
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<HashSet<(String)>> {
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<Handlebars> {
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<P: AsRef<Path>>(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)
}
}

View File

@ -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<Response<'static>, Status> {
let HagridTemplate(tmpl, ctx) = self;
let i18n: I18n = req.guard().expect("Error parsing language");
let template_overrides: rocket::State<TemplateOverrides> = 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<rocket::Rocket> {
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<rocket::Rocket> {
.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<RateLimiter> {
Ok(RateLimiter::new(timeout_secs))
}
fn configure_localized_template_list(config: &Config) -> Result<TemplateOverrides> {
let template_dir: PathBuf = config.get_str("template_dir")?.into();
TemplateOverrides::load(&template_dir, "localized")
}
fn configure_maintenance_mode(config: &Config) -> Result<MaintenanceMode> {
let maintenance_file: PathBuf = config.get_str("maintenance_file")
.unwrap_or("maintenance").into();