fluent_templates/loader/
tera.rs1use fluent_bundle::FluentValue;
2use std::borrow::Cow;
3use std::collections::HashMap;
4use unic_langid::LanguageIdentifier;
5
6use crate::Loader;
7
8const LANG_KEY: &str = "lang";
9const FLUENT_KEY: &str = "key";
10
11#[derive(Debug, thiserror::Error)]
12enum Error {
13 #[error("No `lang` argument provided.")]
14 NoLangArgument,
15 #[error("`lang` must be a valid unicode language identifier.")]
16 LangArgumentInvalid,
17 #[error("No `id` argument provided.")]
18 NoFluentArgument,
19 #[error("Couldn't convert JSON to Fluent value.")]
20 JsonToFluentFail,
21}
22
23impl From<Error> for tera::Error {
24 fn from(error: Error) -> Self {
25 tera::Error::message(error)
26 }
27}
28
29fn tera_to_fluent(json: tera::Value) -> crate::Result<FluentValue<'static>, Error> {
30 if let Some(n) = json.as_f64() {
31 return Ok(FluentValue::from(n));
32 }
33 if let Some(n) = json.as_u64() {
34 return Ok(FluentValue::from(n));
35 }
36 if let Some(s) = json.as_str() {
37 return Ok(FluentValue::from(s.to_string()));
38 }
39 return Err(Error::JsonToFluentFail);
40}
41
42fn parse_language(arg: &str) -> crate::Result<LanguageIdentifier, Error> {
43 arg.parse::<LanguageIdentifier>()
44 .ok()
45 .ok_or(Error::LangArgumentInvalid)
46}
47
48impl<L: Loader + Send + Sync + 'static> tera::Function<tera::TeraResult<tera::Value>>
49 for crate::FluentLoader<L>
50{
51 fn call(&self, kwargs: tera::Kwargs, _: &tera::State) -> tera::TeraResult<tera::Value> {
52 let lang_arg: Option<LanguageIdentifier> = kwargs
53 .get::<&str>(LANG_KEY)
54 .map_err(|_| Error::LangArgumentInvalid)?
55 .map(parse_language)
56 .transpose()?;
57 let lang = lang_arg
58 .as_ref()
59 .or(self.default_lang.as_ref())
60 .ok_or(Error::NoLangArgument)?;
61
62 let id = kwargs
63 .must_get::<&str>(FLUENT_KEY)
64 .map_err(|_| Error::NoFluentArgument)?;
65
66 fn is_not_tera_key((k, _): &(&tera::value::Key<'_>, &tera::Value)) -> bool {
68 match k.as_str() {
69 Some(LANG_KEY) | Some(FLUENT_KEY) | Some("__tera_one_off") => false,
70 _ => true,
71 }
72 }
73
74 let mut fluent_args = HashMap::new();
75
76 for (key, value) in kwargs.iter().filter(is_not_tera_key) {
77 if let Some(key) = key.as_str() {
78 fluent_args.insert(
79 Cow::from(heck::ToKebabCase::to_kebab_case(key)),
80 tera_to_fluent(value.clone())?,
81 );
82 }
83 }
84
85 let response = self.loader.lookup_with_args(lang, id, &fluent_args);
86 Ok(tera::Value::from(response))
87 }
88}