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(mut self, shared: Option<&'b [PathBuf]>) -> Self {
28 self.shared = shared;
29 self
30 }
31
32 pub fn customize(
34 mut self,
35 customize: impl FnMut(&mut FluentBundle<Arc<FluentResource>>) + 'static,
36 ) -> Self {
37 self.customize = Some(Box::new(customize));
38 self
39 }
40
41 pub fn build(mut self) -> Result<ArcLoader, Box<dyn std::error::Error>> {
43 let mut resources = HashMap::new();
44
45 for entry in read_dir(self.location)? {
46 let entry = entry?;
47 if entry.file_type()?.is_dir() {
48 if let Ok(lang) = entry.file_name().into_string() {
49 let lang_resources = crate::fs::read_from_dir(entry.path())?
50 .into_iter()
51 .map(Arc::new)
52 .collect::<Vec<_>>();
53 resources.insert(lang.parse::<LanguageIdentifier>()?, lang_resources);
54 }
55 }
56 }
57
58 let mut bundles = HashMap::new();
59 for (lang, v) in resources.iter() {
60 let mut bundle = FluentBundle::new_concurrent(vec![lang.clone()]);
61
62 for shared_resource in self.shared.unwrap_or(&[]) {
63 bundle
64 .add_resource(Arc::new(crate::fs::read_from_file(shared_resource)?))
65 .map_err(|errors| LoaderError::FluentBundle { errors })?;
66 }
67
68 for res in v {
69 bundle
70 .add_resource(res.clone())
71 .map_err(|errors| LoaderError::FluentBundle { errors })?;
72 }
73
74 if let Some(customize) = self.customize.as_mut() {
75 (customize)(&mut bundle);
76 }
77
78 bundles.insert(lang.clone(), bundle);
79 }
80
81 let fallbacks = super::build_fallbacks(&resources.keys().cloned().collect::<Vec<_>>());
82
83 Ok(ArcLoader {
84 bundles,
85 fallbacks,
86 fallback: self.fallback,
87 })
88 }
89}
90
91pub struct ArcLoader {
104 bundles: HashMap<LanguageIdentifier, FluentBundle<Arc<FluentResource>>>,
105 fallback: LanguageIdentifier,
106 fallbacks: HashMap<LanguageIdentifier, Vec<LanguageIdentifier>>,
107}
108
109impl super::Loader for ArcLoader {
110 fn lookup_complete(
112 &self,
113 lang: &LanguageIdentifier,
114 text_id: &str,
115 args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
116 ) -> String {
117 for lang in negotiate_languages(&[lang], &self.bundles.keys().collect::<Vec<_>>(), None) {
118 if let Ok(val) = self.lookup_single_language(lang, text_id, args) {
119 return val;
120 }
121 }
122 if *lang != self.fallback {
123 if let Ok(val) = self.lookup_single_language(&self.fallback, text_id, args) {
124 return val;
125 }
126 }
127 format!("Unknown localization {text_id}")
128 }
129
130 fn try_lookup_complete(
132 &self,
133 lang: &LanguageIdentifier,
134 text_id: &str,
135 args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
136 ) -> Option<String> {
137 for lang in negotiate_languages(&[lang], &self.bundles.keys().collect::<Vec<_>>(), None) {
138 if let Ok(val) = self.lookup_single_language(lang, text_id, args) {
139 return Some(val);
140 }
141 }
142 if *lang != self.fallback {
143 if let Ok(val) = self.lookup_single_language(&self.fallback, text_id, args) {
144 return Some(val);
145 }
146 }
147 None
148 }
149
150 fn locales(&self) -> Box<dyn Iterator<Item = &LanguageIdentifier> + '_> {
151 Box::new(self.fallbacks.keys())
152 }
153}
154
155impl ArcLoader {
156 pub fn builder<P: AsRef<Path> + ?Sized>(
158 location: &P,
159 fallback: LanguageIdentifier,
160 ) -> ArcLoaderBuilder {
161 ArcLoaderBuilder {
162 location: location.as_ref(),
163 fallback,
164 shared: None,
165 customize: None,
166 }
167 }
168
169 pub fn lookup_single_language<T: AsRef<str>>(
171 &self,
172 lang: &LanguageIdentifier,
173 text_id: &str,
174 args: Option<&HashMap<T, FluentValue>>,
175 ) -> Result<String, LookupError> {
176 super::shared::lookup_single_language(&self.bundles, lang, text_id, args)
177 }
178
179 pub fn lookup_no_default_fallback<S: AsRef<str>>(
182 &self,
183 lang: &LanguageIdentifier,
184 text_id: &str,
185 args: Option<&HashMap<S, FluentValue>>,
186 ) -> Option<String> {
187 super::shared::lookup_no_default_fallback(
188 &self.bundles,
189 &self.fallbacks,
190 lang,
191 text_id,
192 args,
193 )
194 }
195
196 pub fn fallback(&self) -> &LanguageIdentifier {
198 &self.fallback
199 }
200}