i18n_embed/
requester.rs

1use crate::{I18nEmbedError, Localizer};
2use std::{collections::HashMap, sync::Weak};
3
4/// A trait used by [I18nAssets](crate::I18nAssets) to ascertain which
5/// languages are being requested.
6pub trait LanguageRequester<'a> {
7    /// Add a listener to this `LanguageRequester`. When the system
8    /// reports that the currently requested languages has changed,
9    /// each listener will have its
10    /// [Localizer#select()](Localizer#select()) method called. [Weak]
11    /// is used so that when the [Arc](std::sync::Arc) that it references
12    /// is dropped, the listener will also be removed next time this
13    /// requester is polled/updates.
14    ///
15    /// If you haven't already selected a language for the localizer
16    /// you are adding here, you may want to manually call
17    /// [#poll()](#poll()) after adding the listener/s.
18    fn add_listener(&mut self, listener: Weak<dyn Localizer>);
19    /// Add a listener to this `LanguageRequester`. When the system
20    /// reports that the currently requested languages has changed,
21    /// each listener will have its
22    /// [Localizer#select()](Localizer#select()) method called. As
23    /// opposed to [LanguageRequester::add_listener()], this listener
24    /// will not be removed.
25    ///
26    /// If you haven't already selected a language for the localizer
27    /// you are adding here, you may want to manually call
28    /// [#poll()](#poll()) after adding the listener/s.
29    fn add_listener_ref(&mut self, listener: &'a dyn Localizer);
30    /// Poll the system's currently selected language, and call
31    /// [Localizer#select()](Localizer#select()) on each of the
32    /// listeners.
33    ///
34    /// **NOTE:** Support for this across systems currently
35    /// varies, it may not change when the system requested language
36    /// changes during runtime without restarting your application. In
37    /// the future some platforms may also gain support for automatic
38    /// triggering when the requested display language changes.
39    fn poll(&mut self) -> Result<(), I18nEmbedError>;
40    /// Override the languages fed to the [Localizer](Localizer) listeners during
41    /// a [#poll()](#poll()). Set this as `None` to disable the override.
42    fn set_language_override(
43        &mut self,
44        language_override: Option<unic_langid::LanguageIdentifier>,
45    ) -> Result<(), I18nEmbedError>;
46    /// The currently requested languages.
47    fn requested_languages(&self) -> Vec<unic_langid::LanguageIdentifier>;
48    /// The languages reported to be available in the
49    /// listener [Localizer](Localizer)s.
50    fn available_languages(&self) -> Result<Vec<unic_langid::LanguageIdentifier>, I18nEmbedError>;
51    /// The languages currently loaded, keyed by the
52    /// [LanguageLoader::domain()](crate::LanguageLoader::domain()).
53    fn current_languages(&self) -> HashMap<String, unic_langid::LanguageIdentifier>;
54}
55
56/// Provide the functionality for overrides and listeners for a
57/// [LanguageRequester](LanguageRequester) implementation.
58pub struct LanguageRequesterImpl<'a> {
59    arc_listeners: Vec<Weak<dyn Localizer>>,
60    ref_listeners: Vec<&'a dyn Localizer>,
61    language_override: Option<unic_langid::LanguageIdentifier>,
62}
63
64impl<'a> LanguageRequesterImpl<'a> {
65    /// Create a new [LanguageRequesterImpl](LanguageRequesterImpl).
66    pub fn new() -> LanguageRequesterImpl<'a> {
67        LanguageRequesterImpl {
68            arc_listeners: Vec::new(),
69            ref_listeners: Vec::new(),
70            language_override: None,
71        }
72    }
73
74    /// Set an override for the requested language which is used when the
75    /// [LanguageRequesterImpl#poll()](LanguageRequester#poll()) method
76    /// is called. If `None`, then no override is used.
77    pub fn set_language_override(
78        &mut self,
79        language_override: Option<unic_langid::LanguageIdentifier>,
80    ) -> Result<(), I18nEmbedError> {
81        self.language_override = language_override;
82        Ok(())
83    }
84
85    /// Add a weak reference to a [Localizer], which listens to
86    /// changes to the current language.
87    pub fn add_listener(&mut self, listener: Weak<dyn Localizer>) {
88        self.arc_listeners.push(listener);
89    }
90
91    /// Add a reference to [Localizer], which listens to changes to
92    /// the current language.
93    pub fn add_listener_ref(&mut self, listener: &'a dyn Localizer) {
94        self.ref_listeners.push(listener);
95    }
96
97    /// With the provided `requested_languages` call
98    /// [Localizer#select()](Localizer#select()) on each of the
99    /// listeners.
100    pub fn poll_without_override(
101        &mut self,
102        requested_languages: Vec<unic_langid::LanguageIdentifier>,
103    ) -> Result<(), I18nEmbedError> {
104        let mut errors: Vec<I18nEmbedError> = Vec::new();
105
106        self.arc_listeners
107            .retain(|listener| match listener.upgrade() {
108                Some(arc_listener) => {
109                    if let Err(error) = arc_listener.select(&requested_languages) {
110                        errors.push(error);
111                    }
112
113                    true
114                }
115                None => false,
116            });
117
118        for boxed_listener in &self.ref_listeners {
119            if let Err(error) = boxed_listener.select(&requested_languages) {
120                errors.push(error);
121            }
122        }
123
124        if errors.is_empty() {
125            Ok(())
126        } else if errors.len() == 1 {
127            Err(errors.into_iter().next().unwrap())
128        } else {
129            Err(I18nEmbedError::Multiple(errors))
130        }
131    }
132
133    /// With the provided `requested_languages` call
134    /// [Localizer#select()](Localizer#select()) on each of the
135    /// listeners. The `requested_languages` may be ignored if
136    /// [#set_language_override()](#set_language_override()) has been
137    /// set.
138    pub fn poll(
139        &mut self,
140        requested_languages: Vec<unic_langid::LanguageIdentifier>,
141    ) -> Result<(), I18nEmbedError> {
142        let languages = match &self.language_override {
143            Some(language) => {
144                log::debug!("Using language override: {}", language);
145                vec![language.clone()]
146            }
147            None => requested_languages,
148        };
149
150        self.poll_without_override(languages)
151    }
152
153    /// The languages reported to be available in the
154    /// listener [Localizer](Localizer)s.
155    pub fn available_languages(
156        &self,
157    ) -> Result<Vec<unic_langid::LanguageIdentifier>, I18nEmbedError> {
158        let mut available_languages = std::collections::HashSet::new();
159
160        for weak_arc_listener in &self.arc_listeners {
161            if let Some(arc_listener) = weak_arc_listener.upgrade() {
162                arc_listener
163                    .available_languages()?
164                    .iter()
165                    .for_each(|language| {
166                        available_languages.insert(language.clone());
167                    })
168            }
169        }
170
171        for boxed_listener in &self.ref_listeners {
172            boxed_listener
173                .available_languages()?
174                .iter()
175                .for_each(|language| {
176                    available_languages.insert(language.clone());
177                })
178        }
179
180        Ok(available_languages.into_iter().collect())
181    }
182
183    /// Gets a `HashMap` with what each language is currently set
184    /// (value) per domain (key).
185    pub fn current_languages(&self) -> HashMap<String, unic_langid::LanguageIdentifier> {
186        let mut current_languages = HashMap::new();
187        for weak_listener in &self.arc_listeners {
188            if let Some(localizer) = weak_listener.upgrade() {
189                let loader = localizer.language_loader();
190                current_languages.insert(loader.domain().to_string(), loader.current_language());
191            }
192        }
193
194        current_languages
195    }
196}
197
198impl Default for LanguageRequesterImpl<'_> {
199    fn default() -> Self {
200        LanguageRequesterImpl::new()
201    }
202}
203
204impl std::fmt::Debug for LanguageRequesterImpl<'_> {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        let listeners_debug: String = self
207            .arc_listeners
208            .iter()
209            .map(|l| match l.upgrade() {
210                Some(l) => format!("{l:p}"),
211                None => "None".to_string(),
212            })
213            .collect::<Vec<String>>()
214            .join(", ");
215        write!(
216            f,
217            "LanguageRequesterImpl(listeners: {}, language_override: {:?})",
218            listeners_debug, self.language_override,
219        )
220    }
221}
222
223/// A [LanguageRequester](LanguageRequester) for the desktop platform,
224/// supporting windows, linux and mac. It uses
225/// [sys-locale](sys-locale) to select the language based on the
226/// system selected language.
227///
228/// ⚠️ *This API requires the following crate features to be activated: `desktop-requester`.*
229#[cfg(feature = "desktop-requester")]
230#[derive(Debug)]
231pub struct DesktopLanguageRequester<'a> {
232    implementation: LanguageRequesterImpl<'a>,
233}
234
235#[cfg(feature = "desktop-requester")]
236impl<'a> LanguageRequester<'a> for DesktopLanguageRequester<'a> {
237    fn requested_languages(&self) -> Vec<unic_langid::LanguageIdentifier> {
238        DesktopLanguageRequester::requested_languages()
239    }
240
241    fn add_listener(&mut self, listener: Weak<dyn Localizer>) {
242        self.implementation.add_listener(listener)
243    }
244
245    fn add_listener_ref(&mut self, listener: &'a dyn Localizer) {
246        self.implementation.add_listener_ref(listener)
247    }
248
249    fn set_language_override(
250        &mut self,
251        language_override: Option<unic_langid::LanguageIdentifier>,
252    ) -> Result<(), I18nEmbedError> {
253        self.implementation.set_language_override(language_override)
254    }
255
256    fn poll(&mut self) -> Result<(), I18nEmbedError> {
257        self.implementation.poll(self.requested_languages())
258    }
259
260    fn available_languages(&self) -> Result<Vec<unic_langid::LanguageIdentifier>, I18nEmbedError> {
261        self.implementation.available_languages()
262    }
263
264    fn current_languages(&self) -> HashMap<String, unic_langid::LanguageIdentifier> {
265        self.implementation.current_languages()
266    }
267}
268
269#[cfg(feature = "desktop-requester")]
270impl Default for DesktopLanguageRequester<'_> {
271    fn default() -> Self {
272        DesktopLanguageRequester::new()
273    }
274}
275
276#[cfg(feature = "desktop-requester")]
277impl DesktopLanguageRequester<'_> {
278    /// Create a new `DesktopLanguageRequester`.
279    pub fn new() -> Self {
280        DesktopLanguageRequester {
281            implementation: LanguageRequesterImpl::new(),
282        }
283    }
284
285    /// The languages being requested by the operating
286    /// system/environment according to the [sys-locale] crate's
287    /// implementation.
288    pub fn requested_languages() -> Vec<unic_langid::LanguageIdentifier> {
289        let ids: Vec<unic_langid::LanguageIdentifier> = sys_locale::get_locales()
290            .filter_map(|tag| match tag.parse() {
291                Ok(tag) => Some(tag),
292                Err(err) => {
293                    log::error!("Unable to parse your locale: {:?}", err);
294                    None
295                }
296            })
297            .collect();
298
299        log::info!("Current Locale: {:?}", ids);
300
301        ids
302    }
303}
304
305/// A [LanguageRequester](LanguageRequester) for the `web-sys` web platform.
306///
307/// ⚠️ *This API requires the following crate features to be activated: `web-sys-requester`.*
308#[cfg(feature = "web-sys-requester")]
309#[derive(Debug)]
310pub struct WebLanguageRequester<'a> {
311    implementation: LanguageRequesterImpl<'a>,
312}
313
314#[cfg(feature = "web-sys-requester")]
315impl WebLanguageRequester<'_> {
316    /// Create a new `WebLanguageRequester`.
317    pub fn new() -> Self {
318        WebLanguageRequester {
319            implementation: LanguageRequesterImpl::new(),
320        }
321    }
322
323    /// The languages currently being requested by the browser context.
324    pub fn requested_languages() -> Vec<unic_langid::LanguageIdentifier> {
325        use fluent_langneg::convert_vec_str_to_langids_lossy;
326        let window = web_sys::window().expect("no global `window` exists");
327        let navigator = window.navigator();
328        let languages = navigator.languages();
329
330        let requested_languages =
331            convert_vec_str_to_langids_lossy(languages.iter().map(|js_value| {
332                js_value
333                    .as_string()
334                    .expect("language value should be a string.")
335            }));
336
337        requested_languages
338    }
339}
340
341#[cfg(feature = "web-sys-requester")]
342impl Default for WebLanguageRequester<'_> {
343    fn default() -> Self {
344        WebLanguageRequester::new()
345    }
346}
347
348#[cfg(feature = "web-sys-requester")]
349impl<'a> LanguageRequester<'a> for WebLanguageRequester<'a> {
350    fn requested_languages(&self) -> Vec<unic_langid::LanguageIdentifier> {
351        Self::requested_languages()
352    }
353
354    fn add_listener(&mut self, listener: Weak<dyn Localizer>) {
355        self.implementation.add_listener(listener)
356    }
357
358    fn add_listener_ref(&mut self, listener: &'a dyn Localizer) {
359        self.implementation.add_listener_ref(listener)
360    }
361
362    fn poll(&mut self) -> Result<(), I18nEmbedError> {
363        self.implementation.poll(self.requested_languages())
364    }
365
366    fn set_language_override(
367        &mut self,
368        language_override: Option<unic_langid::LanguageIdentifier>,
369    ) -> Result<(), I18nEmbedError> {
370        self.implementation.set_language_override(language_override)
371    }
372
373    fn available_languages(&self) -> Result<Vec<unic_langid::LanguageIdentifier>, I18nEmbedError> {
374        self.implementation.available_languages()
375    }
376
377    fn current_languages(&self) -> HashMap<String, unic_langid::LanguageIdentifier> {
378        self.implementation.current_languages()
379    }
380}