embedded_lang/
language_set.rs

1use serde::{Deserialize, Serialize};
2use std::collections::{HashMap, HashSet};
3use std::ops::Index;
4
5use crate::Language;
6
7/// A searchable set of language string instances
8#[derive(Serialize, Deserialize, Clone)]
9pub struct LanguageSet {
10    current: String,
11    fallback: String,
12    languages: HashMap<String, Language>,
13}
14
15impl LanguageSet {
16    /// Create a new language instance
17    ///
18    /// # Arguments
19    /// * `fallback_language` - Language code for the fallback language
20    /// * `languages` - Array of language instances to use
21    pub fn new(fallback_language: &str, languages: &[Language]) -> Self {
22        Self {
23            current: fallback_language.to_string(),
24            fallback: fallback_language.to_string(),
25            languages: languages
26                .iter()
27                .map(|l| (l.short_name().to_string(), l.clone()))
28                .collect(),
29        }
30    }
31
32    /// List all supported languages
33    pub fn all_languages(&self) -> Vec<Language> {
34        self.languages.values().cloned().collect()
35    }
36
37    /// Return the set's fallback language
38    pub fn fallback_language(&self) -> Option<&Language> {
39        self.languages.get(&self.fallback)
40    }
41
42    /// Return the set's current language
43    pub fn current_language(&self) -> Option<&Language> {
44        self.languages.get(&self.current)
45    }
46
47    /// Add a language to the set
48    ///
49    /// # Arguments
50    /// * `language` - New language
51    pub fn add_language(&mut self, language: Language) {
52        self.languages
53            .insert(language.short_name().to_string(), language);
54    }
55
56    /// Add a language from a JSON file to the set
57    ///
58    /// # Arguments
59    /// * `language` - New language
60    pub fn load_language(
61        &mut self,
62        filename: &str,
63        resources: HashMap<String, Vec<u8>>,
64    ) -> Result<(), String> {
65        match Language::new_from_file(filename, resources) {
66            Ok(lang) => {
67                self.add_language(lang);
68                Ok(())
69            }
70            Err(e) => Err(e),
71        }
72    }
73
74    /// Check the completeness of all language packs against the fallback
75    /// Returns the list of missing strings for each language
76    pub fn verify(&self) -> HashMap<String, Vec<String>> {
77        if let Some(fallback) = self
78            .fallback_language()
79            .and_then(|l| Some(l.strings().keys().cloned().collect::<HashSet<String>>()))
80        {
81            self.languages
82                .iter()
83                .map(|l| {
84                    (
85                        l.0.clone(),
86                        l.1.strings()
87                            .keys()
88                            .cloned()
89                            .collect::<HashSet<String>>()
90                            .difference(&fallback)
91                            .cloned()
92                            .collect(),
93                    )
94                })
95                .collect::<HashMap<String, Vec<String>>>()
96        } else {
97            HashMap::default()
98        }
99    }
100
101    /// Set the fallback language for lookups
102    ///
103    /// # Arguments
104    /// * `language` - New language
105    pub fn set_fallback_language(&mut self, language: &str) -> bool {
106        if self.languages.contains_key(language) {
107            self.fallback = language.to_string();
108            true
109        } else {
110            false
111        }
112    }
113
114    /// Set the current language for lookups
115    ///
116    /// # Arguments
117    /// * `language` - New language
118    ///
119    /// returns false if the language code is not recognized
120    pub fn set_language(&mut self, language: &str) -> bool {
121        if self.languages.contains_key(language) {
122            self.current = language.to_string();
123            true
124        } else {
125            false
126        }
127    }
128
129    /// Look up a string in a specific language
130    ///
131    /// # Arguments
132    /// * `language` - Language to search
133    /// * `name` - String to find
134    pub fn get_from_lang(&self, language: &str, name: &str) -> Option<&str> {
135        if let Some(lang) = self.languages.get(language) {
136            lang.get(name)
137        } else {
138            None
139        }
140    }
141
142    /// Look up a string
143    ///
144    /// # Arguments
145    /// * `name` - String to find
146    pub fn get(&self, name: &str) -> Option<&str> {
147        self.current_language()
148            .and_then(|l| l.get(name))
149            .or(self.fallback_language().and_then(|l| l.get(name)))
150    }
151
152    /// Return an embedded resource as a utf8 string
153    pub fn utf8_resource(&self, name: &str) -> Option<&str> {
154        self.current_language()
155            .and_then(|l| l.utf8_resource(name))
156            .or(self.fallback_language().and_then(|l| l.utf8_resource(name)))
157    }
158
159    /// Return an embedded resource as a slice of bytes
160    pub fn binary_resource(&self, name: &str) -> Option<&[u8]> {
161        self.current_language()
162            .and_then(|l| l.binary_resource(name))
163            .or(self
164                .fallback_language()
165                .and_then(|l| l.binary_resource(name)))
166    }
167}
168
169impl Index<&str> for LanguageSet {
170    type Output = str;
171
172    fn index(&self, name: &str) -> &Self::Output {
173        self.get(name).unwrap_or_default()
174    }
175}
176
177#[cfg(test)]
178mod test_token {
179    use super::*;
180    use crate as embedded_lang;
181    use crate::embedded_language;
182
183    #[test]
184    fn test_current_language() {
185        let mut set = LanguageSet::new(
186            "fr",
187            &[
188                embedded_language!("../examples/en.lang.json"),
189                embedded_language!("../examples/fr.lang.json"),
190            ],
191        );
192
193        assert_eq!(set.current_language().unwrap().short_name(), "fr");
194        set.set_language("en");
195        assert_eq!(set.current_language().unwrap().short_name(), "en");
196    }
197
198    #[test]
199    fn test_fallback_language() {
200        let mut set = LanguageSet::new(
201            "fr",
202            &[
203                embedded_language!("../examples/en.lang.json"),
204                embedded_language!("../examples/fr.lang.json"),
205            ],
206        );
207
208        assert_eq!(set.fallback_language().unwrap().short_name(), "fr");
209        set.set_fallback_language("en");
210        assert_eq!(set.fallback_language().unwrap().short_name(), "en");
211    }
212
213    #[test]
214    fn test_add_language() {
215        let mut set = LanguageSet::new("fr", &[embedded_language!("../examples/fr.lang.json")]);
216
217        set.add_language(embedded_language!("../examples/en.lang.json"));
218
219        assert_eq!(set.set_language("en"), true);
220    }
221
222    #[test]
223    fn test_load_language() {
224        let mut set = LanguageSet::new("fr", &[embedded_language!("../examples/fr.lang.json")]);
225
226        assert_eq!(
227            set.load_language("examples/en.lang.json", HashMap::default())
228                .is_ok(),
229            true
230        );
231        assert_eq!(set.set_language("en"), true);
232    }
233
234    #[test]
235    fn test_set_fallback_language() {
236        let mut set = LanguageSet::new(
237            "fr",
238            &[
239                embedded_language!("../examples/en.lang.json"),
240                embedded_language!("../examples/fr.lang.json"),
241            ],
242        );
243
244        assert_eq!(set.set_fallback_language("en"), true);
245        assert_eq!(set.fallback_language().unwrap().short_name(), "en");
246
247        assert_eq!(set.set_fallback_language("foo"), false);
248        assert_eq!(set.fallback_language().unwrap().short_name(), "en");
249    }
250
251    #[test]
252    fn test_set_language() {
253        let mut set = LanguageSet::new(
254            "fr",
255            &[
256                embedded_language!("../examples/en.lang.json"),
257                embedded_language!("../examples/fr.lang.json"),
258            ],
259        );
260
261        assert_eq!(set.set_language("en"), true);
262        assert_eq!(set.current_language().unwrap().short_name(), "en");
263
264        assert_eq!(set.set_language("foo"), false);
265        assert_eq!(set.current_language().unwrap().short_name(), "en");
266    }
267
268    #[test]
269    fn test_get_from_lang() {
270        let mut set = LanguageSet::new(
271            "fr",
272            &[
273                embedded_language!("../examples/en.lang.json"),
274                embedded_language!("../examples/fr.lang.json"),
275            ],
276        );
277        set.set_fallback_language("en");
278
279        assert_eq!(set.get_from_lang("fr", "tree"), Some("arbre"));
280        assert_eq!(set.get_from_lang("fr", "mustard"), None);
281        assert_eq!(set.get_from_lang("en", "nope"), None);
282    }
283
284    #[test]
285    fn test_get() {
286        let mut set = LanguageSet::new(
287            "fr",
288            &[
289                embedded_language!("../examples/en.lang.json"),
290                embedded_language!("../examples/fr.lang.json"),
291            ],
292        );
293        set.set_fallback_language("en");
294
295        assert_eq!(set.get("tree"), Some("arbre"));
296        assert_eq!(set.get("mustard"), Some("mustard"));
297        assert_eq!(set.get("nope"), None);
298    }
299
300    #[test]
301    fn test_index() {
302        let mut set = LanguageSet::new(
303            "fr",
304            &[
305                embedded_language!("../examples/en.lang.json"),
306                embedded_language!("../examples/fr.lang.json"),
307            ],
308        );
309        set.set_fallback_language("en");
310
311        assert_eq!(set["tree"], "arbre".to_string());
312        assert_eq!(set["mustard"], "mustard".to_string());
313        assert_eq!(set["nope"], "".to_string());
314    }
315}