fluent_templates/
loader.rs

1//! This modules contains both the `static_loader` and `ArcLoader`
2//! implementations, as well as the `Loader` trait. Which provides a loader
3//! agnostic interface.
4
5#[cfg(feature = "handlebars")]
6mod handlebars;
7
8#[cfg(feature = "tera")]
9mod tera;
10
11mod multi_loader;
12mod shared;
13
14use std::borrow::Cow;
15use std::collections::HashMap;
16
17use crate::FluentBundle;
18use fluent_bundle::{FluentArgs, FluentResource, FluentValue};
19use fluent_langneg::negotiate_languages;
20
21pub use unic_langid::{langid, langids, LanguageIdentifier};
22
23mod arc_loader;
24mod static_loader;
25
26pub use arc_loader::{ArcLoader, ArcLoaderBuilder};
27pub use multi_loader::MultiLoader;
28pub use static_loader::StaticLoader;
29
30/// A loader capable of looking up Fluent keys given a language.
31pub trait Loader {
32    /// Look up `text_id` for `lang` in Fluent.
33    fn lookup(&self, lang: &LanguageIdentifier, text_id: &str) -> String {
34        self.lookup_complete(lang, text_id, None)
35    }
36
37    /// Look up `text_id` for `lang` with `args` in Fluent.
38    fn lookup_with_args(
39        &self,
40        lang: &LanguageIdentifier,
41        text_id: &str,
42        args: &HashMap<Cow<'static, str>, FluentValue>,
43    ) -> String {
44        self.lookup_complete(lang, text_id, Some(args))
45    }
46
47    /// Look up `text_id` for `lang` in Fluent, using any `args` if provided.
48    fn lookup_complete(
49        &self,
50        lang: &LanguageIdentifier,
51        text_id: &str,
52        args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
53    ) -> String;
54
55    /// Look up `text_id` for `lang` in Fluent.
56    fn try_lookup(&self, lang: &LanguageIdentifier, text_id: &str) -> Option<String> {
57        self.try_lookup_complete(lang, text_id, None)
58    }
59
60    /// Look up `text_id` for `lang` with `args` in Fluent.
61    fn try_lookup_with_args(
62        &self,
63        lang: &LanguageIdentifier,
64        text_id: &str,
65        args: &HashMap<Cow<'static, str>, FluentValue>,
66    ) -> Option<String> {
67        self.try_lookup_complete(lang, text_id, Some(args))
68    }
69
70    /// Look up `text_id` for `lang` in Fluent, using any `args` if provided.
71    fn try_lookup_complete(
72        &self,
73        lang: &LanguageIdentifier,
74        text_id: &str,
75        args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
76    ) -> Option<String>;
77
78    /// Returns an Iterator over the locales that are present.
79    fn locales(&self) -> Box<dyn Iterator<Item = &LanguageIdentifier> + '_>;
80}
81
82impl<L> Loader for std::sync::Arc<L>
83where
84    L: Loader,
85{
86    fn lookup_complete(
87        &self,
88        lang: &LanguageIdentifier,
89        text_id: &str,
90        args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
91    ) -> String {
92        L::lookup_complete(self, lang, text_id, args)
93    }
94
95    fn try_lookup_complete(
96        &self,
97        lang: &LanguageIdentifier,
98        text_id: &str,
99        args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
100    ) -> Option<String> {
101        L::try_lookup_complete(self, lang, text_id, args)
102    }
103
104    fn locales(&self) -> Box<dyn Iterator<Item = &LanguageIdentifier> + '_> {
105        L::locales(self)
106    }
107}
108
109impl<L> Loader for &L
110where
111    L: Loader + ?Sized,
112{
113    fn lookup_complete(
114        &self,
115        lang: &LanguageIdentifier,
116        text_id: &str,
117        args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
118    ) -> String {
119        L::lookup_complete(self, lang, text_id, args)
120    }
121
122    fn try_lookup_complete(
123        &self,
124        lang: &LanguageIdentifier,
125        text_id: &str,
126        args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
127    ) -> Option<String> {
128        L::try_lookup_complete(self, lang, text_id, args)
129    }
130
131    fn locales(&self) -> Box<dyn Iterator<Item = &LanguageIdentifier> + '_> {
132        L::locales(self)
133    }
134}
135
136impl<T: Loader + ?Sized> Loader for Box<T> {
137    fn lookup_complete(
138        &self,
139        lang: &LanguageIdentifier,
140        text_id: &str,
141        args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
142    ) -> String {
143        self.as_ref().lookup_complete(lang, text_id, args)
144    }
145
146    fn try_lookup_complete(
147        &self,
148        lang: &LanguageIdentifier,
149        text_id: &str,
150        args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
151    ) -> Option<String> {
152        self.as_ref().try_lookup_complete(lang, text_id, args)
153    }
154
155    fn locales(&self) -> Box<dyn Iterator<Item = &LanguageIdentifier> + '_> {
156        self.as_ref().locales()
157    }
158}
159
160/// A `Loader` agnostic container type with optional trait implementations
161/// for integrating with different libraries.
162pub struct FluentLoader<L> {
163    loader: L,
164    #[allow(unused)]
165    default_lang: Option<LanguageIdentifier>,
166}
167
168impl<L> FluentLoader<L> {
169    /// Create a new `FluentLoader`.
170    pub fn new(loader: L) -> Self {
171        Self {
172            loader,
173            default_lang: None,
174        }
175    }
176
177    /// Set default language for this `FluentLoader`.
178    /// Template engines can use this value when rendering translations.
179    /// So far this feature is only implemented for Tera.
180    pub fn with_default_lang(self, lang: LanguageIdentifier) -> Self {
181        Self {
182            loader: self.loader,
183            default_lang: Some(lang),
184        }
185    }
186}
187
188/// Constructs a map of languages with a list of potential fallback languages.
189pub fn build_fallbacks(
190    locales: &[LanguageIdentifier],
191) -> HashMap<LanguageIdentifier, Vec<LanguageIdentifier>> {
192    let mut map = HashMap::new();
193
194    for locale in locales.iter() {
195        map.insert(
196            locale.to_owned(),
197            negotiate_languages(
198                &[locale],
199                locales,
200                None,
201                fluent_langneg::NegotiationStrategy::Filtering,
202            )
203            .into_iter()
204            .cloned()
205            .collect::<Vec<_>>(),
206        );
207    }
208
209    map
210}
211
212/// Creates a new static `FluentBundle` for `lang` using `resources`. Optionally
213/// shared resources can be specified with `core_resource` and the bundle can
214/// be customized with `customizer`.
215fn create_bundle(
216    lang: LanguageIdentifier,
217    resources: &'static [FluentResource],
218    core_resource: Option<&'static FluentResource>,
219    customizer: &impl Fn(&mut FluentBundle<&'static FluentResource>),
220) -> FluentBundle<&'static FluentResource> {
221    let mut bundle: FluentBundle<&'static FluentResource> =
222        FluentBundle::new_concurrent(vec![lang]);
223    if let Some(core) = core_resource {
224        bundle
225            .add_resource(core)
226            .expect("Failed to add core resource to bundle");
227    }
228    for res in resources {
229        bundle
230            .add_resource(res)
231            .expect("Failed to add FTL resources to the bundle.");
232    }
233
234    customizer(&mut bundle);
235    bundle
236}
237
238/// Maps from map of languages containing a list of resources to a map of
239/// languages containing a `FluentBundle` of those resources.
240pub fn build_bundles(
241    resources: &'static HashMap<LanguageIdentifier, Vec<FluentResource>>,
242    core_resource: Option<&'static FluentResource>,
243    customizer: impl Fn(&mut FluentBundle<&'static FluentResource>),
244) -> HashMap<LanguageIdentifier, FluentBundle<&'static FluentResource>> {
245    let mut bundles = HashMap::new();
246    for (k, v) in resources.iter() {
247        bundles.insert(
248            k.clone(),
249            create_bundle(k.clone(), v, core_resource, &customizer),
250        );
251    }
252    bundles
253}
254
255fn map_to_fluent_args<'map, T: AsRef<str>>(map: &'map HashMap<T, FluentValue>) -> FluentArgs<'map> {
256    map.iter()
257        .map(|(key, value)| (key.as_ref(), value.clone()))
258        .collect()
259}