1#![deny(missing_docs)]
2
3use std::collections::HashMap;
47use std::sync::{Arc, RwLock};
48
49use bracket::{
50 error::HelperError,
51 helper::{Helper, HelperValue, LocalHelper},
52 parser::ast::Node,
53 render::{Context, Render, Type},
54};
55
56use serde_json::Value;
57
58use fluent_templates::fluent_bundle::FluentValue;
59use fluent_templates::LanguageIdentifier;
60use fluent_templates::Loader;
61
62static FLUENT_PARAM: &str = "fluentparam";
63
64#[derive(Clone)]
66pub struct FluentParam {
67 parameters: Arc<RwLock<HashMap<String, String>>>,
68}
69
70impl Helper for FluentParam {
71 fn call<'render, 'call>(
72 &self,
73 rc: &mut Render<'render>,
74 ctx: &Context<'call>,
75 template: Option<&'render Node<'render>>,
76 ) -> HelperValue {
77 ctx.arity(1..1)?;
78 ctx.assert_block(template)?;
79
80 let param_name = ctx.try_get(0, &[Type::String])?.as_str().unwrap();
81
82 let node = template.unwrap();
83 let content = rc.buffer(node)?;
84 let mut writer = self.parameters.write().unwrap();
85 writer.insert(param_name.to_string(), content);
86
87 Ok(None)
88 }
89}
90
91impl LocalHelper for FluentParam {}
92
93pub struct FluentHelper {
95 loader: Box<dyn Loader + Send + Sync>,
96 pub escape: bool,
98}
99
100impl FluentHelper {
101 pub fn new(loader: Box<dyn Loader + Send + Sync>) -> Self {
105 Self {
106 loader,
107 escape: true,
108 }
109 }
110}
111
112impl Helper for FluentHelper {
113 fn call<'render, 'call>(
114 &self,
115 rc: &mut Render<'render>,
116 ctx: &Context<'call>,
117 template: Option<&'render Node<'render>>,
118 ) -> HelperValue {
119 ctx.arity(1..1)?;
120
121 let msg_id = ctx.try_get(0, &[Type::String])?.as_str().unwrap();
122
123 let lang = rc
124 .evaluate("@root.lang")?
125 .ok_or_else(|| {
126 HelperError::new(format!(
127 "Helper '{}' requires a 'lang' variable in the template data",
128 ctx.name()
129 ))
130 })?
131 .as_str()
132 .ok_or_else(|| {
133 HelperError::new(format!(
134 "Type error in helper '{}' the 'lang' variable must be a string",
135 ctx.name()
136 ))
137 })?;
138
139 let lang_id = lang
140 .parse::<LanguageIdentifier>()
141 .map_err(|e| HelperError::new(e.to_string()))?;
142
143 let mut args: Option<HashMap<String, FluentValue>> =
145 if ctx.parameters().is_empty() {
146 None
147 } else {
148 let map = ctx
149 .parameters()
150 .iter()
151 .filter_map(|(k, v)| {
152 let val = match v {
153 Value::Number(n) => n.as_f64().unwrap().into(),
157 Value::String(s) => s.to_owned().into(),
158 _ => return None,
159 };
160 Some((k.to_string(), val))
161 })
162 .collect();
163 Some(map)
164 };
165
166 if let Some(node) = template {
167 let parameters: Arc<RwLock<HashMap<String, String>>> =
168 Arc::new(RwLock::new(HashMap::new()));
169 let local_helper = FluentParam {
170 parameters: Arc::clone(¶meters),
171 };
172 rc.register_local_helper(FLUENT_PARAM, Box::new(local_helper));
173 rc.template(node)?;
174 rc.unregister_local_helper(FLUENT_PARAM);
175
176 let lock = Arc::try_unwrap(parameters).expect(
177 "Fluent helper parameters lock still has multiple owners!",
178 );
179 let map = lock
180 .into_inner()
181 .expect("Fluent helper failed to get inner value from lock!");
182
183 let params = args.get_or_insert(HashMap::new());
184 for (k, v) in map {
185 params.insert(k, FluentValue::String(v.into()));
186 }
187 }
188
189 let message =
190 self.loader
191 .lookup_complete(&lang_id, &msg_id, args.as_ref());
192 if self.escape {
193 rc.write_escaped(&message)?;
194 } else {
195 rc.write(&message)?;
196 }
197
198 Ok(None)
199 }
200}