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<'a, L> Loader for &'a L
110where
111    L: Loader,
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
136/// A `Loader` agnostic container type with optional trait implementations
137/// for integrating with different libraries.
138pub struct FluentLoader<L> {
139    loader: L,
140    #[allow(unused)]
141    default_lang: Option<LanguageIdentifier>,
142}
143
144impl<L> FluentLoader<L> {
145    /// Create a new `FluentLoader`.
146    pub fn new(loader: L) -> Self {
147        Self {
148            loader,
149            default_lang: None,
150        }
151    }
152
153    /// Set default language for this `FluentLoader`.
154    /// Template engines can use this value when rendering translations.
155    /// So far this feature is only implemented for Tera.
156    pub fn with_default_lang(self, lang: LanguageIdentifier) -> Self {
157        Self {
158            loader: self.loader,
159            default_lang: Some(lang),
160        }
161    }
162}
163
164/// Constructs a map of languages with a list of potential fallback languages.
165pub fn build_fallbacks(
166    locales: &[LanguageIdentifier],
167) -> HashMap<LanguageIdentifier, Vec<LanguageIdentifier>> {
168    let mut map = HashMap::new();
169
170    for locale in locales.iter() {
171        map.insert(
172            locale.to_owned(),
173            negotiate_languages(
174                &[locale],
175                locales,
176                None,
177                fluent_langneg::NegotiationStrategy::Filtering,
178            )
179            .into_iter()
180            .cloned()
181            .collect::<Vec<_>>(),
182        );
183    }
184
185    map
186}
187
188/// Creates a new static `FluentBundle` for `lang` using `resources`. Optionally
189/// shared resources can be specified with `core_resource` and the bundle can
190/// be customized with `customizer`.
191fn create_bundle(
192    lang: LanguageIdentifier,
193    resources: &'static [FluentResource],
194    core_resource: Option<&'static FluentResource>,
195    customizer: &impl Fn(&mut FluentBundle<&'static FluentResource>),
196) -> FluentBundle<&'static FluentResource> {
197    let mut bundle: FluentBundle<&'static FluentResource> =
198        FluentBundle::new_concurrent(vec![lang]);
199    if let Some(core) = core_resource {
200        bundle
201            .add_resource(core)
202            .expect("Failed to add core resource to bundle");
203    }
204    for res in resources {
205        bundle
206            .add_resource(res)
207            .expect("Failed to add FTL resources to the bundle.");
208    }
209
210    customizer(&mut bundle);
211    bundle
212}
213
214/// Maps from map of languages containing a list of resources to a map of
215/// languages containing a `FluentBundle` of those resources.
216pub fn build_bundles(
217    resources: &'static HashMap<LanguageIdentifier, Vec<FluentResource>>,
218    core_resource: Option<&'static FluentResource>,
219    customizer: impl Fn(&mut FluentBundle<&'static FluentResource>),
220) -> HashMap<LanguageIdentifier, FluentBundle<&'static FluentResource>> {
221    let mut bundles = HashMap::new();
222    for (k, v) in resources.iter() {
223        bundles.insert(
224            k.clone(),
225            create_bundle(k.clone(), v, core_resource, &customizer),
226        );
227    }
228    bundles
229}
230
231fn map_to_fluent_args<'map, T: AsRef<str>>(map: &'map HashMap<T, FluentValue>) -> FluentArgs<'map> {
232    map.iter()
233        .map(|(key, value)| (key.as_ref(), value.clone()))
234        .collect()
235}