fluent_templates/loader/
handlebars.rs

1use handlebars::{
2    Context, Handlebars, Helper, HelperDef, HelperResult, Output, RenderContext, RenderErrorReason,
3    Renderable,
4};
5
6use fluent_bundle::FluentValue;
7use handlebars::template::{Parameter, TemplateElement};
8use serde_json::Value as Json;
9use std::borrow::Cow;
10use std::collections::HashMap;
11
12use crate::{FluentLoader, Loader};
13
14#[derive(Default)]
15struct StringOutput {
16    pub s: String,
17}
18
19impl Output for StringOutput {
20    fn write(&mut self, seg: &str) -> Result<(), std::io::Error> {
21        self.s.push_str(seg);
22        Ok(())
23    }
24}
25
26impl<L: Loader + Send + Sync> HelperDef for FluentLoader<L> {
27    fn call<'reg: 'rc, 'rc>(
28        &self,
29        h: &Helper<'rc>,
30        reg: &'reg Handlebars,
31        context: &'rc Context,
32        rcx: &mut RenderContext<'reg, 'rc>,
33        out: &mut dyn Output,
34    ) -> HelperResult {
35        let id = if let Some(id) = h.param(0) {
36            id
37        } else {
38            return Err(RenderErrorReason::ParamNotFoundForIndex("fluent", 0).into());
39        };
40
41        if id.relative_path().is_some() {
42            return Err(RenderErrorReason::ParamTypeMismatchForName(
43                "fluent",
44                "0".to_string(),
45                "takes a string parameter with no path".to_string(),
46            )
47            .into());
48        }
49
50        let id = if let Json::String(ref s) = *id.value() {
51            s
52        } else {
53            return Err(RenderErrorReason::ParamTypeMismatchForName(
54                "fluent",
55                "0".to_string(),
56                "string".to_string(),
57            )
58            .into());
59        };
60
61        let mut args: Option<HashMap<Cow<'static, str>, FluentValue>> = if h.hash().is_empty() {
62            None
63        } else {
64            let map = h
65                .hash()
66                .iter()
67                .filter_map(|(k, v)| {
68                    let json = v.value();
69                    let val = match json {
70                        // `Number::as_f64` can't fail here because we haven't
71                        // enabled `arbitrary_precision` feature
72                        // in `serde_json`.
73                        Json::Number(n) => n.as_f64().unwrap().into(),
74                        Json::String(s) => s.to_owned().into(),
75                        _ => return None,
76                    };
77                    Some((Cow::from(k.to_string()), val))
78                })
79                .collect();
80            Some(map)
81        };
82
83        if let Some(tpl) = h.template() {
84            if args.is_none() {
85                args = Some(HashMap::new());
86            }
87            let args = args.as_mut().unwrap();
88            for element in &tpl.elements {
89                if let TemplateElement::HelperBlock(ref block) = element {
90                    if block.name != Parameter::Name("fluentparam".into()) {
91                        return Err(RenderErrorReason::Other(format!(
92                            "{{{{fluent}}}} can only contain {{{{fluentparam}}}} elements, not {}",
93                            block.name.expand_as_name(reg, context, rcx).unwrap()
94                        ))
95                        .into());
96                    }
97                    let id = if let Some(el) = block.params.first() {
98                        if let Parameter::Literal(Json::String(ref s)) = *el {
99                            s
100                        } else {
101                            return Err(RenderErrorReason::ParamTypeMismatchForName(
102                                "fluentparam",
103                                "0".into(),
104                                "string".into(),
105                            )
106                            .into());
107                        }
108                    } else {
109                        return Err(
110                            RenderErrorReason::ParamNotFoundForIndex("fluentparam", 0).into()
111                        );
112                    };
113                    if let Some(ref tpl) = block.template {
114                        let mut s = StringOutput::default();
115                        tpl.render(reg, context, rcx, &mut s)?;
116                        args.insert(
117                            Cow::Owned(String::from(id)),
118                            FluentValue::String(s.s.into()),
119                        );
120                    }
121                }
122            }
123        }
124        let lang = context
125            .data()
126            .get("lang")
127            .expect("Language not set in context")
128            .as_str()
129            .expect("Language must be string")
130            .parse()
131            .expect("Language not valid identifier");
132
133        let response = self.loader.lookup_complete(&lang, id, args.as_ref());
134        out.write(&response)
135            .map_err(|error| RenderErrorReason::NestedError(Box::new(error)).into())
136    }
137}