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}