fluent_templates/loader/
arc_loader.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::fs::read_dir;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use crate::languages::negotiate_languages;
8use crate::FluentBundle;
9use fluent_bundle::{FluentResource, FluentValue};
10
11use crate::error::{LoaderError, LookupError};
12
13pub use unic_langid::LanguageIdentifier;
14
15type Customize = Option<Box<dyn FnMut(&mut FluentBundle<Arc<FluentResource>>)>>;
16
17/// A builder pattern struct for constructing `ArcLoader`s.
18pub struct ArcLoaderBuilder<'a, 'b> {
19    location: &'a Path,
20    fallback: LanguageIdentifier,
21    shared: Option<&'b [PathBuf]>,
22    customize: Customize,
23}
24
25impl<'a, 'b> ArcLoaderBuilder<'a, 'b> {
26    /// Adds Fluent resources that are shared across all localizations.
27    pub fn shared_resources<'b2>(
28        self,
29        shared: Option<&'b2 [PathBuf]>,
30    ) -> ArcLoaderBuilder<'a, 'b2> {
31        ArcLoaderBuilder {
32            location: self.location,
33            fallback: self.fallback,
34            shared,
35            customize: self.customize,
36        }
37    }
38
39    /// Allows you to customise each `FluentBundle`.
40    pub fn customize(
41        mut self,
42        customize: impl FnMut(&mut FluentBundle<Arc<FluentResource>>) + 'static,
43    ) -> Self {
44        self.customize = Some(Box::new(customize));
45        self
46    }
47
48    /// Constructs an `ArcLoader` from the settings provided.
49    pub fn build(mut self) -> Result<ArcLoader, Box<dyn std::error::Error>> {
50        let mut resources = HashMap::new();
51
52        for entry in read_dir(self.location)? {
53            let entry = entry?;
54            if entry.file_type()?.is_dir() {
55                if let Ok(lang) = entry.file_name().into_string() {
56                    let lang_resources = crate::fs::read_from_dir(entry.path())?
57                        .into_iter()
58                        .map(Arc::new)
59                        .collect::<Vec<_>>();
60                    resources.insert(lang.parse::<LanguageIdentifier>()?, lang_resources);
61                }
62            }
63        }
64
65        let mut bundles = HashMap::new();
66        for (lang, v) in resources.iter() {
67            let mut bundle = FluentBundle::new_concurrent(vec![lang.clone()]);
68
69            for shared_resource in self.shared.unwrap_or(&[]) {
70                bundle
71                    .add_resource(Arc::new(crate::fs::read_from_file(shared_resource)?))
72                    .map_err(|errors| LoaderError::FluentBundle { errors })?;
73            }
74
75            for res in v {
76                bundle
77                    .add_resource(res.clone())
78                    .map_err(|errors| LoaderError::FluentBundle { errors })?;
79            }
80
81            if let Some(customize) = self.customize.as_mut() {
82                (customize)(&mut bundle);
83            }
84
85            bundles.insert(lang.clone(), bundle);
86        }
87
88        let fallbacks = super::build_fallbacks(&resources.keys().cloned().collect::<Vec<_>>());
89
90        Ok(ArcLoader {
91            bundles,
92            fallbacks,
93            fallback: self.fallback,
94        })
95    }
96}
97
98/// A loader that uses `Arc<FluentResource>` as its backing storage. This is
99/// mainly useful for when you need to load fluent at run time. You can
100/// configure the initialisation with `ArcLoaderBuilder`.
101/// ```no_run
102/// use fluent_templates::ArcLoader;
103///
104/// let loader = ArcLoader::builder("locales/", unic_langid::langid!("en-US"))
105///     .shared_resources(Some(&["locales/core.ftl".into()]))
106///     .customize(|bundle| bundle.set_use_isolating(false))
107///     .build()
108///     .unwrap();
109/// ```
110pub struct ArcLoader {
111    bundles: HashMap<LanguageIdentifier, FluentBundle<Arc<FluentResource>>>,
112    fallback: LanguageIdentifier,
113    fallbacks: HashMap<LanguageIdentifier, Vec<LanguageIdentifier>>,
114}
115
116impl super::Loader for ArcLoader {
117    // Traverse the fallback chain,
118    fn lookup_complete(
119        &self,
120        lang: &LanguageIdentifier,
121        text_id: &str,
122        args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
123    ) -> String {
124        for lang in negotiate_languages(&[lang], &self.bundles.keys().collect::<Vec<_>>(), None) {
125            if let Ok(val) = self.lookup_single_language(lang, text_id, args) {
126                return val;
127            }
128        }
129        if *lang != self.fallback {
130            if let Ok(val) = self.lookup_single_language(&self.fallback, text_id, args) {
131                return val;
132            }
133        }
134        format!("Unknown localization key: {text_id:?}")
135    }
136
137    // Traverse the fallback chain,
138    fn try_lookup_complete(
139        &self,
140        lang: &LanguageIdentifier,
141        text_id: &str,
142        args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
143    ) -> Option<String> {
144        for lang in negotiate_languages(&[lang], &self.bundles.keys().collect::<Vec<_>>(), None) {
145            if let Ok(val) = self.lookup_single_language(lang, text_id, args) {
146                return Some(val);
147            }
148        }
149        if *lang != self.fallback {
150            if let Ok(val) = self.lookup_single_language(&self.fallback, text_id, args) {
151                return Some(val);
152            }
153        }
154        None
155    }
156
157    fn locales(&self) -> Box<dyn Iterator<Item = &LanguageIdentifier> + '_> {
158        Box::new(self.fallbacks.keys())
159    }
160}
161
162impl ArcLoader {
163    /// Creates a new `ArcLoaderBuilder`
164    pub fn builder<'a, P: AsRef<Path> + ?Sized>(
165        location: &'a P,
166        fallback: LanguageIdentifier,
167    ) -> ArcLoaderBuilder<'a, 'static> {
168        ArcLoaderBuilder {
169            location: location.as_ref(),
170            fallback,
171            shared: None,
172            customize: None,
173        }
174    }
175
176    /// Convenience function to look up a string for a single language
177    pub fn lookup_single_language<T: AsRef<str>>(
178        &self,
179        lang: &LanguageIdentifier,
180        text_id: &str,
181        args: Option<&HashMap<T, FluentValue>>,
182    ) -> Result<String, LookupError> {
183        super::shared::lookup_single_language(&self.bundles, lang, text_id, args)
184    }
185
186    /// Convenience function to look up a string without falling back to the
187    /// default fallback language
188    pub fn lookup_no_default_fallback<S: AsRef<str>>(
189        &self,
190        lang: &LanguageIdentifier,
191        text_id: &str,
192        args: Option<&HashMap<S, FluentValue>>,
193    ) -> Option<String> {
194        super::shared::lookup_no_default_fallback(
195            &self.bundles,
196            &self.fallbacks,
197            lang,
198            text_id,
199            args,
200        )
201    }
202
203    /// Return the fallback language
204    pub fn fallback(&self) -> &LanguageIdentifier {
205        &self.fallback
206    }
207}