From dc6ece50b1fa993da61f68617cbd9deca979cbe6 Mon Sep 17 00:00:00 2001 From: technic93 Date: Sat, 18 Jul 2020 11:15:27 +0200 Subject: [PATCH] Default lang argument in Tera templates (#14) * Default lang argument in Tera templates this makes lang argument optional by providing global default value See https://github.com/XAMPPRocky/fluent-templates/issues/13 * generate tests only for enabled features before this commit tests generated for disabled features were empty * add tests for defautlt lang feature * documentation for with_default_lang --- templates/src/lib.rs | 5 ++++- templates/src/loader.rs | 16 +++++++++++++++- templates/src/loader/tera.rs | 24 ++++++++++++++++-------- templates/tests/template.rs | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/templates/src/lib.rs b/templates/src/lib.rs index bc5fd1e..4408764 100644 --- a/templates/src/lib.rs +++ b/templates/src/lib.rs @@ -168,7 +168,9 @@ //! what language to get that key for. Optionally you can pass extra arguments //! to the function as arguments to the resource. `fluent-templates` will //! automatically convert argument keys from Tera's `snake_case` to the fluent's -//! preferred `kebab-case` arguments. +//! preferred `kebab-case` arguments. +//! The `lang` parameter is optional when the default language of the corresponding +//! `FluentLoader` is set (see [`FluentLoader::with_default_lang`]). //! //! ```toml //!fluent-templates = { version = "*", features = ["tera"] } @@ -276,6 +278,7 @@ //! [`static_loader!`]: ./macro.static_loader.html //! [`StaticLoader`]: ./struct.StaticLoader.html //! [`ArcLoader`]: ./struct.ArcLoader.html +//! [`FluentLoader::with_default_lang`]: ./struct.FluentLoader.html#method.with_default_lang //! [`handlebars::Context`]: https://docs.rs/handlebars/3.1.0/handlebars/struct.Context.html #![warn(missing_docs)] diff --git a/templates/src/loader.rs b/templates/src/loader.rs index 0727234..8b169f2 100644 --- a/templates/src/loader.rs +++ b/templates/src/loader.rs @@ -80,12 +80,26 @@ where /// for integrating with different libraries. pub struct FluentLoader { loader: L, + default_lang: Option, } impl FluentLoader { /// Create a new `FluentLoader`. pub fn new(loader: L) -> Self { - Self { loader } + Self { + loader, + default_lang: None, + } + } + + /// Set default language for this `FluentLoader`. + /// Template engines can use this value when rendering translations. + /// Sofar this feature is only implemented for Tera. + pub fn with_default_lang(self, lang: LanguageIdentifier) -> Self { + Self { + loader: self.loader, + default_lang: Some(lang), + } } } diff --git a/templates/src/loader/tera.rs b/templates/src/loader/tera.rs index 0d4702b..2efebc7 100644 --- a/templates/src/loader/tera.rs +++ b/templates/src/loader/tera.rs @@ -2,6 +2,7 @@ use fluent_bundle::FluentValue; use serde_json::Value as Json; use snafu::OptionExt; use std::collections::HashMap; +use unic_langid::LanguageIdentifier; use crate::Loader; @@ -35,15 +36,22 @@ fn json_to_fluent(json: Json) -> crate::Result, Error> { } } +fn parse_language(arg: &Json) -> crate::Result { + arg.as_str() + .context(self::LangArgumentInvalid)? + .parse::() + .ok() + .context(self::LangArgumentInvalid) +} + impl tera::Function for crate::FluentLoader { fn call(&self, args: &HashMap) -> Result { - let lang = args - .get(LANG_KEY) - .and_then(Json::as_str) - .context(self::NoLangArgument)? - .parse::() - .ok() - .context(self::LangArgumentInvalid)?; + let lang_arg = args.get(LANG_KEY).map(parse_language).transpose()?; + let lang = lang_arg + .as_ref() + .or(self.default_lang.as_ref()) + .context(self::NoLangArgument)?; + let id = args .get(FLUENT_KEY) .and_then(Json::as_str) @@ -64,7 +72,7 @@ impl tera::Function for crate::FluentLoader { ); } - let response = self.loader.lookup_with_args(&lang, &id, &fluent_args); + let response = self.loader.lookup_with_args(lang, &id, &fluent_args); Ok(Json::String(response)) } diff --git a/templates/tests/template.rs b/templates/tests/template.rs index 1165cc0..977b133 100644 --- a/templates/tests/template.rs +++ b/templates/tests/template.rs @@ -96,6 +96,7 @@ macro_rules! generate_tests { }; } +#[cfg(feature = "handlebars")] mod handlebars { generate_tests! { fn english(handlebars, "en-US") { @@ -127,6 +128,7 @@ mod handlebars { } } +#[cfg(feature = "tera")] mod tera { generate_tests! { fn english(tera, "en-US") { @@ -152,4 +154,35 @@ mod tera { assert_eq!(r#"{{ fluent(key="fallback", lang="{lang}") }}"#, "this should fall back"); } } + + /// Default lang argument works + #[test] + fn use_default_lang() { + let loader = FluentLoader::new(&*super::LOCALES).with_default_lang("de".parse().unwrap()); + let mut tera = tera::Tera::default(); + tera.register_function("fluent", loader); + let context = tera::Context::new(); + assert_eq!( + tera.render_str(r#"{{ fluent(key="hello-world") }}"#, &context) + .unwrap(), + "Hallo Welt!" + ); + assert_eq!( + tera.render_str(r#"{{ fluent(key="hello-world", lang="fr") }}"#, &context) + .unwrap(), + "Bonjour le monde!" + ); + } + + /// Rendering fails when no default and no explicit lang argument is provided + #[test] + fn no_default_and_no_argument_error() { + let loader = FluentLoader::new(&*super::LOCALES); + let mut tera = tera::Tera::default(); + tera.register_function("fluent", loader); + let context = tera::Context::new(); + assert!(tera + .render_str(r#"{{ fluent(key="hellow-world") }}"#, &context) + .is_err()); + } }