fluent_templates/loader/
multi_loader.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use crate::Loader;
use fluent_bundle::FluentValue;
use std::borrow::Cow;
use std::collections::{HashMap, VecDeque};

pub use unic_langid::LanguageIdentifier;

/// A loader comprised of other loaders.
///
/// This loader allows for loaders with multiple sources to be used
/// from a single one, instead of a multiple of them.
///
/// The idea behind this loader is to allow for the scenario where you depend
/// on crates that have their own loader (think of protocol crates which
/// are dependencies of a frontend -> the frontend needs to know each of
/// the protocol's messages and be able to display them). Using a multiloader
/// allows you to query multiple localization sources from one single source.
///
/// Note that a [`M̀ultiloader`] is most useful where each of your fluent modules
/// is specially namespaced to avoid name collisions.
///
/// # Usage
/// ```rust
/// use fluent_templates::{ArcLoader, StaticLoader, MultiLoader, Loader};
/// use unic_langid::{LanguageIdentifier, langid};
///
/// const US_ENGLISH: LanguageIdentifier = langid!("en-US");
/// const CHINESE: LanguageIdentifier = langid!("zh-CN");
///
/// fluent_templates::static_loader! {
///     static LOCALES = {
///         locales: "./tests/locales",
///         fallback_language: "en-US",
///         // Removes unicode isolating marks around arguments, you typically
///         // should only set to false when testing.
///         customise: |bundle| bundle.set_use_isolating(false),
///     };
/// }
///
/// fn main() {
///     let cn_loader = ArcLoader::builder("./tests/locales", CHINESE)
///         .customize(|bundle| bundle.set_use_isolating(false))
///         .build()
///         .unwrap();
///
///     let mut multiloader = MultiLoader::from_iter([
///         Box::new(&*LOCALES) as Box<dyn Loader>,
///     ]);
///     multiloader.push_back(Box::new(cn_loader) as Box<dyn Loader>);
///     assert_eq!("Hello World!", multiloader.lookup(&US_ENGLISH, "hello-world"));
///     assert_eq!("儿", multiloader.lookup(&CHINESE, "exists"));
/// }
/// ```
///
/// # Order of search
/// The one that is inserted first is also the one searched first.
pub struct MultiLoader {
    loaders: VecDeque<Box<dyn Loader>>,
}

impl MultiLoader {
    /// Creates a [`MultiLoader`] without any loaders.
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates a [`MultiLoader`] from an iterator of loaders.
    pub fn from_iter(iter: impl IntoIterator<Item = Box<dyn Loader>>) -> Self {
        Self {
            loaders: iter.into_iter().collect(),
        }
    }

    /// Pushes a loader in front of all the others in terms of precedence.
    pub fn push_front(&mut self, loader: Box<dyn Loader>) {
        self.loaders.push_front(loader);
    }

    /// Pushes a loader at the back in terms of precedence.
    pub fn push_back(&mut self, loader: Box<dyn Loader>) {
        self.loaders.push_back(loader);
    }

    /// Pushes a loader at the back in terms of precedence.
    pub fn remove(&mut self, idx: usize) -> Option<Box<dyn Loader>> {
        self.loaders.remove(idx)
    }
}

impl Default for MultiLoader {
    fn default() -> Self {
        Self {
            loaders: VecDeque::default(),
        }
    }
}

impl crate::Loader for MultiLoader {
    fn lookup_complete(
        &self,
        lang: &unic_langid::LanguageIdentifier,
        text_id: &str,
        args: Option<&std::collections::HashMap<Cow<'static, str>, fluent_bundle::FluentValue>>,
    ) -> String {
        for loader in self.loaders.iter() {
            if let Some(text) = loader.try_lookup_complete(lang, text_id, args) {
                return text;
            }
        }
        format!("Unknown localization {text_id}")
    }

    fn try_lookup_complete(
        &self,
        lang: &LanguageIdentifier,
        text_id: &str,
        args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
    ) -> Option<String> {
        for loader in self.loaders.iter() {
            if let Some(text) = loader.try_lookup_complete(lang, text_id, args) {
                return Some(text);
            }
        }
        None
    }

    fn locales(&self) -> Box<dyn Iterator<Item = &LanguageIdentifier> + '_> {
        Box::new(self.loaders.iter().map(|loader| loader.locales()).flatten())
    }
}