fluent_templates/loader/
arc_loader.rs1use 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
17pub 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 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 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 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
98pub 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 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 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 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 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 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 pub fn fallback(&self) -> &LanguageIdentifier {
205 &self.fallback
206 }
207}