fluent_templates/loader/
handlebars.rs1use 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 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}