fluent_fallback/
bundles.rs

1use crate::{
2    cache::{AsyncCache, Cache},
3    env::LocalesProvider,
4    errors::LocalizationError,
5    generator::{BundleGenerator, BundleIterator, BundleStream},
6    types::{L10nAttribute, L10nKey, L10nMessage, ResourceId},
7};
8use fluent_bundle::{FluentArgs, FluentBundle, FluentError};
9use rustc_hash::FxHashSet;
10use std::borrow::Cow;
11
12pub enum BundlesInner<G>
13where
14    G: BundleGenerator,
15{
16    Iter(Cache<G::Iter, G::Resource>),
17    Stream(AsyncCache<G::Stream, G::Resource>),
18}
19
20pub struct Bundles<G>(BundlesInner<G>)
21where
22    G: BundleGenerator;
23
24impl<G> Bundles<G>
25where
26    G: BundleGenerator,
27    G::Iter: BundleIterator,
28{
29    pub fn prefetch_sync(&self) {
30        match &self.0 {
31            BundlesInner::Iter(iter) => iter.prefetch(),
32            BundlesInner::Stream(_) => panic!("Can't prefetch a sync bundle set asynchronously"),
33        }
34    }
35}
36
37impl<G> Bundles<G>
38where
39    G: BundleGenerator,
40    G::Stream: BundleStream,
41{
42    pub async fn prefetch_async(&self) {
43        match &self.0 {
44            BundlesInner::Iter(_) => panic!("Can't prefetch a async bundle set synchronously"),
45            BundlesInner::Stream(stream) => stream.prefetch().await,
46        }
47    }
48}
49
50impl<G> Bundles<G>
51where
52    G: BundleGenerator,
53{
54    pub fn new<P>(sync: bool, res_ids: FxHashSet<ResourceId>, generator: &G, provider: &P) -> Self
55    where
56        G: BundleGenerator<LocalesIter = P::Iter>,
57        P: LocalesProvider,
58    {
59        Self(if sync {
60            BundlesInner::Iter(Cache::new(
61                generator.bundles_iter(provider.locales(), res_ids),
62            ))
63        } else {
64            BundlesInner::Stream(AsyncCache::new(
65                generator.bundles_stream(provider.locales(), res_ids),
66            ))
67        })
68    }
69
70    pub async fn format_value<'l>(
71        &'l self,
72        id: &'l str,
73        args: Option<&'l FluentArgs<'_>>,
74        errors: &mut Vec<LocalizationError>,
75    ) -> Option<Cow<'l, str>> {
76        match &self.0 {
77            BundlesInner::Iter(cache) => Self::format_value_from_iter(cache, id, args, errors),
78            BundlesInner::Stream(stream) => {
79                Self::format_value_from_stream(stream, id, args, errors).await
80            }
81        }
82    }
83
84    pub async fn format_values<'l>(
85        &'l self,
86        keys: &'l [L10nKey<'l>],
87        errors: &mut Vec<LocalizationError>,
88    ) -> Vec<Option<Cow<'l, str>>> {
89        match &self.0 {
90            BundlesInner::Iter(cache) => Self::format_values_from_iter(cache, keys, errors),
91            BundlesInner::Stream(stream) => {
92                Self::format_values_from_stream(stream, keys, errors).await
93            }
94        }
95    }
96
97    pub async fn format_messages<'l>(
98        &'l self,
99        keys: &'l [L10nKey<'l>],
100        errors: &mut Vec<LocalizationError>,
101    ) -> Vec<Option<L10nMessage<'l>>> {
102        match &self.0 {
103            BundlesInner::Iter(cache) => Self::format_messages_from_iter(cache, keys, errors),
104            BundlesInner::Stream(stream) => {
105                Self::format_messages_from_stream(stream, keys, errors).await
106            }
107        }
108    }
109
110    pub fn format_value_sync<'l>(
111        &'l self,
112        id: &'l str,
113        args: Option<&'l FluentArgs>,
114        errors: &mut Vec<LocalizationError>,
115    ) -> Result<Option<Cow<'l, str>>, LocalizationError> {
116        match &self.0 {
117            BundlesInner::Iter(cache) => Ok(Self::format_value_from_iter(cache, id, args, errors)),
118            BundlesInner::Stream(_) => Err(LocalizationError::SyncRequestInAsyncMode),
119        }
120    }
121
122    pub fn format_values_sync<'l>(
123        &'l self,
124        keys: &'l [L10nKey<'l>],
125        errors: &mut Vec<LocalizationError>,
126    ) -> Result<Vec<Option<Cow<'l, str>>>, LocalizationError> {
127        match &self.0 {
128            BundlesInner::Iter(cache) => Ok(Self::format_values_from_iter(cache, keys, errors)),
129            BundlesInner::Stream(_) => Err(LocalizationError::SyncRequestInAsyncMode),
130        }
131    }
132
133    pub fn format_messages_sync<'l>(
134        &'l self,
135        keys: &'l [L10nKey<'l>],
136        errors: &mut Vec<LocalizationError>,
137    ) -> Result<Vec<Option<L10nMessage<'l>>>, LocalizationError> {
138        match &self.0 {
139            BundlesInner::Iter(cache) => Ok(Self::format_messages_from_iter(cache, keys, errors)),
140            BundlesInner::Stream(_) => Err(LocalizationError::SyncRequestInAsyncMode),
141        }
142    }
143}
144
145macro_rules! format_value_from_inner {
146    ($step:expr, $id:expr, $args:expr, $errors:expr) => {
147        let mut found_message = false;
148
149        while let Some(bundle) = $step {
150            let bundle = bundle.as_ref().unwrap_or_else(|(bundle, err)| {
151                $errors.extend(err.iter().cloned().map(Into::into));
152                bundle
153            });
154
155            if let Some(msg) = bundle.get_message($id) {
156                found_message = true;
157                if let Some(value) = msg.value() {
158                    let mut format_errors = vec![];
159                    let result = bundle.format_pattern(value, $args, &mut format_errors);
160                    if !format_errors.is_empty() {
161                        $errors.push(LocalizationError::Resolver {
162                            id: $id.to_string(),
163                            locale: bundle.locales[0].clone(),
164                            errors: format_errors,
165                        });
166                    }
167                    return Some(result);
168                } else {
169                    $errors.push(LocalizationError::MissingValue {
170                        id: $id.to_string(),
171                        locale: Some(bundle.locales[0].clone()),
172                    });
173                }
174            } else {
175                $errors.push(LocalizationError::MissingMessage {
176                    id: $id.to_string(),
177                    locale: Some(bundle.locales[0].clone()),
178                });
179            }
180        }
181        if found_message {
182            $errors.push(LocalizationError::MissingValue {
183                id: $id.to_string(),
184                locale: None,
185            });
186        } else {
187            $errors.push(LocalizationError::MissingMessage {
188                id: $id.to_string(),
189                locale: None,
190            });
191        }
192        return None;
193    };
194}
195
196#[derive(Clone)]
197enum Value<'l> {
198    Present(Cow<'l, str>),
199    Missing,
200    None,
201}
202
203macro_rules! format_values_from_inner {
204    ($step:expr, $keys:expr, $errors:expr) => {
205        let mut cells = vec![Value::None; $keys.len()];
206
207        while let Some(bundle) = $step {
208            let bundle = bundle.as_ref().unwrap_or_else(|(bundle, err)| {
209                $errors.extend(err.iter().cloned().map(Into::into));
210                bundle
211            });
212
213            let mut has_missing = false;
214
215            for (key, cell) in $keys
216                .iter()
217                .zip(&mut cells)
218                .filter(|(_, cell)| !matches!(cell, Value::Present(_)))
219            {
220                if let Some(msg) = bundle.get_message(&key.id) {
221                    if let Some(value) = msg.value() {
222                        let mut format_errors = vec![];
223                        *cell = Value::Present(bundle.format_pattern(
224                            value,
225                            key.args.as_ref(),
226                            &mut format_errors,
227                        ));
228                        if !format_errors.is_empty() {
229                            $errors.push(LocalizationError::Resolver {
230                                id: key.id.to_string(),
231                                locale: bundle.locales[0].clone(),
232                                errors: format_errors,
233                            });
234                        }
235                    } else {
236                        *cell = Value::Missing;
237                        has_missing = true;
238                        $errors.push(LocalizationError::MissingValue {
239                            id: key.id.to_string(),
240                            locale: Some(bundle.locales[0].clone()),
241                        });
242                    }
243                } else {
244                    has_missing = true;
245                    $errors.push(LocalizationError::MissingMessage {
246                        id: key.id.to_string(),
247                        locale: Some(bundle.locales[0].clone()),
248                    });
249                }
250            }
251            if !has_missing {
252                break;
253            }
254        }
255
256        return $keys
257            .iter()
258            .zip(cells)
259            .map(|(key, value)| match value {
260                Value::Present(value) => Some(value),
261                Value::Missing => {
262                    $errors.push(LocalizationError::MissingValue {
263                        id: key.id.to_string(),
264                        locale: None,
265                    });
266                    None
267                }
268                Value::None => {
269                    $errors.push(LocalizationError::MissingMessage {
270                        id: key.id.to_string(),
271                        locale: None,
272                    });
273                    None
274                }
275            })
276            .collect();
277    };
278}
279
280macro_rules! format_messages_from_inner {
281    ($step:expr, $keys:expr, $errors:expr) => {
282        let mut result = vec![None; $keys.len()];
283
284        let mut is_complete = false;
285
286        while let Some(bundle) = $step {
287            let bundle = bundle.as_ref().unwrap_or_else(|(bundle, err)| {
288                $errors.extend(err.iter().cloned().map(Into::into));
289                bundle
290            });
291
292            let mut has_missing = false;
293            for (key, cell) in $keys
294                .iter()
295                .zip(&mut result)
296                .filter(|(_, cell)| cell.is_none())
297            {
298                let mut format_errors = vec![];
299                let msg = Self::format_message_from_bundle(bundle, key, &mut format_errors);
300
301                if msg.is_none() {
302                    has_missing = true;
303                    $errors.push(LocalizationError::MissingMessage {
304                        id: key.id.to_string(),
305                        locale: Some(bundle.locales[0].clone()),
306                    });
307                } else if !format_errors.is_empty() {
308                    $errors.push(LocalizationError::Resolver {
309                        id: key.id.to_string(),
310                        locale: bundle.locales.get(0).cloned().unwrap(),
311                        errors: format_errors,
312                    });
313                }
314
315                *cell = msg;
316            }
317            if !has_missing {
318                is_complete = true;
319                break;
320            }
321        }
322
323        if !is_complete {
324            for (key, _) in $keys
325                .iter()
326                .zip(&mut result)
327                .filter(|(_, cell)| cell.is_none())
328            {
329                $errors.push(LocalizationError::MissingMessage {
330                    id: key.id.to_string(),
331                    locale: None,
332                });
333            }
334        }
335
336        return result;
337    };
338}
339
340impl<G> Bundles<G>
341where
342    G: BundleGenerator,
343{
344    fn format_value_from_iter<'l>(
345        cache: &'l Cache<G::Iter, G::Resource>,
346        id: &'l str,
347        args: Option<&'l FluentArgs>,
348        errors: &mut Vec<LocalizationError>,
349    ) -> Option<Cow<'l, str>> {
350        let mut bundle_iter = cache.into_iter();
351        format_value_from_inner!(bundle_iter.next(), id, args, errors);
352    }
353
354    async fn format_value_from_stream<'l>(
355        stream: &'l AsyncCache<G::Stream, G::Resource>,
356        id: &'l str,
357        args: Option<&'l FluentArgs<'_>>,
358        errors: &mut Vec<LocalizationError>,
359    ) -> Option<Cow<'l, str>> {
360        use futures::StreamExt;
361
362        let mut bundle_stream = stream.stream();
363        format_value_from_inner!(bundle_stream.next().await, id, args, errors);
364    }
365
366    async fn format_messages_from_stream<'l>(
367        stream: &'l AsyncCache<G::Stream, G::Resource>,
368        keys: &'l [L10nKey<'l>],
369        errors: &mut Vec<LocalizationError>,
370    ) -> Vec<Option<L10nMessage<'l>>> {
371        use futures::StreamExt;
372        let mut bundle_stream = stream.stream();
373        format_messages_from_inner!(bundle_stream.next().await, keys, errors);
374    }
375
376    async fn format_values_from_stream<'l>(
377        stream: &'l AsyncCache<G::Stream, G::Resource>,
378        keys: &'l [L10nKey<'l>],
379        errors: &mut Vec<LocalizationError>,
380    ) -> Vec<Option<Cow<'l, str>>> {
381        use futures::StreamExt;
382        let mut bundle_stream = stream.stream();
383
384        format_values_from_inner!(bundle_stream.next().await, keys, errors);
385    }
386
387    fn format_message_from_bundle<'l>(
388        bundle: &'l FluentBundle<G::Resource>,
389        key: &'l L10nKey,
390        format_errors: &mut Vec<FluentError>,
391    ) -> Option<L10nMessage<'l>> {
392        let msg = bundle.get_message(&key.id)?;
393        let value = msg
394            .value()
395            .map(|pattern| bundle.format_pattern(pattern, key.args.as_ref(), format_errors));
396        let attributes = msg
397            .attributes()
398            .map(|attr| {
399                let value = bundle.format_pattern(attr.value(), key.args.as_ref(), format_errors);
400                L10nAttribute {
401                    name: attr.id().into(),
402                    value,
403                }
404            })
405            .collect();
406        Some(L10nMessage { value, attributes })
407    }
408
409    fn format_messages_from_iter<'l>(
410        cache: &'l Cache<G::Iter, G::Resource>,
411        keys: &'l [L10nKey<'l>],
412        errors: &mut Vec<LocalizationError>,
413    ) -> Vec<Option<L10nMessage<'l>>> {
414        let mut bundle_iter = cache.into_iter();
415        format_messages_from_inner!(bundle_iter.next(), keys, errors);
416    }
417
418    fn format_values_from_iter<'l>(
419        cache: &'l Cache<G::Iter, G::Resource>,
420        keys: &'l [L10nKey<'l>],
421        errors: &mut Vec<LocalizationError>,
422    ) -> Vec<Option<Cow<'l, str>>> {
423        let mut bundle_iter = cache.into_iter();
424        format_values_from_inner!(bundle_iter.next(), keys, errors);
425    }
426}