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