Skip to main content

es_fluent_lang/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub use unic_langid::{LanguageIdentifier, langid};
4
5#[cfg(feature = "macros")]
6pub use es_fluent_lang_macro::es_fluent_language;
7
8use es_fluent_manager_core::{I18nModule, LocalizationError, Localizer};
9use fluent_bundle::{FluentArgs, FluentBundle, FluentResource, FluentValue};
10use std::collections::HashMap;
11use std::sync::{Arc, OnceLock, RwLock};
12
13const ES_FLUENT_LANG_FTL: &str = include_str!("../es-fluent-lang.ftl");
14
15fn embedded_resource() -> Arc<FluentResource> {
16    static RESOURCE: OnceLock<Arc<FluentResource>> = OnceLock::new();
17    RESOURCE
18        .get_or_init(|| {
19            Arc::new(
20                FluentResource::try_new(ES_FLUENT_LANG_FTL.to_owned()).expect(
21                    "Invalid Fluent resource embedded in es-fluent-lang/es-fluent-lang.ftl",
22                ),
23            )
24        })
25        .clone()
26}
27
28struct EsFluentLanguageModule;
29
30impl I18nModule for EsFluentLanguageModule {
31    fn name(&self) -> &'static str {
32        "es-fluent-lang"
33    }
34
35    fn create_localizer(&self) -> Box<dyn Localizer> {
36        Box::new(EsFluentLanguageLocalizer::new(
37            embedded_resource(),
38            langid!("en-US"),
39        ))
40    }
41}
42
43struct EsFluentLanguageLocalizer {
44    resource: Arc<FluentResource>,
45    current_lang: RwLock<LanguageIdentifier>,
46}
47
48impl EsFluentLanguageLocalizer {
49    fn new(resource: Arc<FluentResource>, default_lang: LanguageIdentifier) -> Self {
50        Self {
51            resource,
52            current_lang: RwLock::new(default_lang),
53        }
54    }
55}
56
57impl Localizer for EsFluentLanguageLocalizer {
58    fn select_language(&self, lang: &LanguageIdentifier) -> Result<(), LocalizationError> {
59        *self.current_lang.write().expect("lock poisoned") = lang.clone();
60        Ok(())
61    }
62
63    fn localize<'a>(
64        &self,
65        id: &str,
66        args: Option<&HashMap<&str, FluentValue<'a>>>,
67    ) -> Option<String> {
68        let lang = self.current_lang.read().expect("lock poisoned").clone();
69        let mut bundle = FluentBundle::new(vec![lang]);
70        if let Err(err) = bundle.add_resource(self.resource.clone()) {
71            tracing::error!("Failed to add es-fluent-lang resource: {:?}", err);
72            return None;
73        }
74
75        let message = bundle.get_message(id)?;
76        let pattern = message.value()?;
77        let mut errors = Vec::new();
78
79        let fluent_args = args.map(|args| {
80            let mut fluent_args = FluentArgs::new();
81            for (key, value) in args {
82                fluent_args.set(*key, value.clone());
83            }
84            fluent_args
85        });
86
87        let formatted = bundle.format_pattern(pattern, fluent_args.as_ref(), &mut errors);
88
89        if errors.is_empty() {
90            Some(formatted.into_owned())
91        } else {
92            tracing::error!(
93                "Formatting errors while localizing '{}' from es-fluent-lang: {:?}",
94                id,
95                errors
96            );
97            None
98        }
99    }
100}
101
102inventory::submit! {
103    &EsFluentLanguageModule as &dyn I18nModule
104}
105
106#[cfg(feature = "bevy")]
107mod bevy_support {
108    use super::*;
109    use es_fluent_manager_core::StaticI18nResource;
110    use std::sync::Arc;
111
112    struct EsFluentLangStaticResource;
113
114    static STATIC_RESOURCE: EsFluentLangStaticResource = EsFluentLangStaticResource;
115
116    impl StaticI18nResource for EsFluentLangStaticResource {
117        fn domain(&self) -> &'static str {
118            "es-fluent-lang"
119        }
120
121        fn resource(&self) -> Arc<FluentResource> {
122            embedded_resource()
123        }
124    }
125
126    inventory::submit! {
127        &STATIC_RESOURCE as &dyn StaticI18nResource
128    }
129}