fluent_template_helper/loader/
arc_loader.rs1use std::collections::HashMap;
2use std::fs::read_dir;
3use std::path::{Path, PathBuf};
4use std::sync::Arc;
5
6use fluent_bundle::concurrent::FluentBundle;
7use fluent_bundle::{FluentResource, FluentValue};
8
9pub use unic_langid::{langid, langids, LanguageIdentifier};
10
11pub struct ArcLoaderBuilder<'a, 'b> {
12 location: &'a Path,
13 fallback: LanguageIdentifier,
14 shared: Option<&'b [PathBuf]>,
15 customize: Option<fn(&mut FluentBundle<Arc<FluentResource>>)>,
16}
17
18impl<'a, 'b> ArcLoaderBuilder<'a, 'b> {
19
20 pub fn shared_resources(mut self, shared: Option<&'b [PathBuf]>) -> Self {
22 self.shared = shared;
23 self
24 }
25
26 pub fn customize(mut self, customize: fn(&mut FluentBundle<Arc<FluentResource>>)) -> Self {
28 self.customize = Some(customize);
29 self
30 }
31
32 pub fn build(self) -> Result<ArcLoader, Box<dyn std::error::Error>> {
34 let mut resources = HashMap::new();
35
36 for entry in read_dir(self.location)? {
37 let entry = entry?;
38 if entry.file_type()?.is_dir() {
39 if let Ok(lang) = entry.file_name().into_string() {
40 let lang_resources = super::read_from_dir(entry.path())?
41 .into_iter()
42 .map(Arc::new)
43 .collect::<Vec<_>>();
44 resources.insert(lang.parse::<LanguageIdentifier>()?, lang_resources);
45 }
46 }
47 }
48
49 let mut bundles = HashMap::new();
50 for (lang, v) in resources.iter() {
51 let mut bundle = FluentBundle::new(&[lang.clone()][..]);
52
53 for shared_resource in self.shared.as_deref().unwrap_or(&[]) {
54 bundle.add_resource(Arc::new(super::read_from_file(shared_resource)?))
55 .expect("Failed to add core FTL resources to the bundle.");
56 }
57
58 for res in v {
59 bundle
60 .add_resource(res.clone())
61 .expect("Failed to add FTL resources to the bundle.");
62 }
63
64 if let Some(customize) = self.customize {
65 (customize)(&mut bundle);
66 }
67
68 bundles.insert(lang.clone(), bundle);
69 }
70
71 let fallbacks = super::build_fallbacks(&*resources.keys().cloned().collect::<Vec<_>>());
72
73 Ok(ArcLoader {
74 resources,
75 bundles,
76 fallbacks,
77 fallback: self.fallback,
78 })
79 }
80}
81
82pub struct ArcLoader {
97 bundles: HashMap<LanguageIdentifier, FluentBundle<Arc<FluentResource>>>,
98 fallback: LanguageIdentifier,
99 fallbacks: HashMap<LanguageIdentifier, Vec<LanguageIdentifier>>,
100 resources: HashMap<LanguageIdentifier, Vec<Arc<FluentResource>>>,
101}
102
103impl super::Loader for ArcLoader {
104 fn lookup(
106 &self,
107 lang: &LanguageIdentifier,
108 text_id: &str,
109 args: Option<&HashMap<&str, FluentValue>>,
110 ) -> String {
111 for l in self.fallbacks.get(lang).expect("language not found") {
112 if let Some(val) = self.lookup_single_language(l, text_id, args) {
113 return val;
114 }
115 }
116 if *lang != self.fallback {
117 if let Some(val) = self.lookup_single_language(&self.fallback, text_id, args) {
118 return val;
119 }
120 }
121 format!("Unknown localization {}", text_id)
122 }
123}
124
125impl ArcLoader {
126 pub fn new<P: AsRef<Path> + ?Sized>(location: &P, fallback: LanguageIdentifier) -> ArcLoaderBuilder {
128 ArcLoaderBuilder { location: location.as_ref(), fallback, shared: None, customize: None }
129 }
130
131 pub fn locales(&self) -> Vec<LanguageIdentifier> {
133 self.resources.keys().cloned().collect()
134 }
135
136 pub fn lookup_single_language(
138 &self,
139 lang: &LanguageIdentifier,
140 text_id: &str,
141 args: Option<&HashMap<&str, FluentValue>>,
142 ) -> Option<String> {
143 if let Some(bundle) = self.bundles.get(lang) {
144 if let Some(message) = bundle.get_message(text_id).and_then(|m| m.value) {
145 let mut errors = Vec::new();
146
147 let value = bundle.format_pattern(&message, args, &mut errors);
148
149 if errors.is_empty() {
150 Some(value.into())
151 } else {
152 panic!(
153 "Failed to format a message for locale {} and id {}.\nErrors\n{:?}",
154 lang, text_id, errors
155 )
156 }
157 } else {
158 None
159 }
160 } else {
161 panic!("Unknown language {}", lang)
162 }
163 }
164}
165
166