1#![doc = include_str!("../README.md")]
2
3#[doc(hidden)]
4pub mod meta;
5
6#[doc(hidden)]
7pub mod registry;
8
9#[cfg(feature = "derive")]
10pub use es_fluent_derive::{EsFluent, EsFluentChoice, EsFluentThis, EsFluentVariants};
11
12#[doc(hidden)]
13pub use fluent_bundle::FluentValue;
14
15#[doc(hidden)]
16pub use inventory as __inventory;
17
18#[doc(hidden)]
19pub use rust_embed as __rust_embed;
20
21#[doc(hidden)]
22pub use es_fluent_manager_core as __manager_core;
23
24#[doc(hidden)]
25pub use unic_langid;
26
27use arc_swap::ArcSwap;
28use es_fluent_manager_core::FluentManager;
29use es_fluent_shared::EsFluentError;
30use parking_lot::RwLock;
31use std::sync::{Arc, OnceLock};
32
33#[cfg(feature = "build")]
34pub mod build {
35 pub use es_fluent_toml::build::*;
36}
37
38mod traits;
39pub use traits::{EsFluentChoice, FluentDisplay, ThisFtl, ToFluentString};
40
41#[doc(hidden)]
42static CONTEXT: OnceLock<ArcSwap<FluentManager>> = OnceLock::new();
43
44#[doc(hidden)]
45type DomainAwareCustomLocalizer = dyn for<'a> Fn(
46 Option<&str>,
47 &str,
48 Option<&std::collections::HashMap<&str, FluentValue<'a>>>,
49 ) -> Option<String>
50 + Send
51 + Sync;
52
53#[doc(hidden)]
54static CUSTOM_LOCALIZER: OnceLock<RwLock<Option<Arc<DomainAwareCustomLocalizer>>>> =
55 OnceLock::new();
56
57fn custom_localizer_slot() -> &'static RwLock<Option<Arc<DomainAwareCustomLocalizer>>> {
58 CUSTOM_LOCALIZER.get_or_init(|| RwLock::new(None))
59}
60
61fn try_custom_localizer<'a>(
62 id: &str,
63 args: Option<&std::collections::HashMap<&str, FluentValue<'a>>>,
64) -> Option<String> {
65 CUSTOM_LOCALIZER
66 .get()
67 .and_then(|slot| slot.read().clone())
68 .and_then(|custom_localizer| custom_localizer(None, id, args))
69}
70
71fn try_custom_localizer_in_domain<'a>(
72 domain: &str,
73 id: &str,
74 args: Option<&std::collections::HashMap<&str, FluentValue<'a>>>,
75) -> Option<String> {
76 CUSTOM_LOCALIZER
77 .get()
78 .and_then(|slot| slot.read().clone())
79 .and_then(|custom_localizer| custom_localizer(Some(domain), id, args))
80}
81
82#[derive(Debug)]
83pub enum GlobalLocalizationError {
84 ContextAlreadyInitialized,
85 ContextNotInitialized,
86 CustomLocalizerAlreadyInitialized,
87 LanguageSelectionFailed(EsFluentError),
88}
89
90impl std::fmt::Display for GlobalLocalizationError {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 match self {
93 Self::ContextAlreadyInitialized => {
94 f.write_str("the global FluentManager context is already initialized")
95 },
96 Self::ContextNotInitialized => {
97 f.write_str("the global FluentManager context is not initialized")
98 },
99 Self::CustomLocalizerAlreadyInitialized => {
100 f.write_str("the global custom localizer is already initialized")
101 },
102 Self::LanguageSelectionFailed(source) => {
103 write!(f, "failed to select the requested language: {source}")
104 },
105 }
106 }
107}
108
109impl std::error::Error for GlobalLocalizationError {
110 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
111 match self {
112 Self::LanguageSelectionFailed(source) => Some(source),
113 _ => None,
114 }
115 }
116}
117
118impl From<EsFluentError> for GlobalLocalizationError {
119 fn from(value: EsFluentError) -> Self {
120 Self::LanguageSelectionFailed(value)
121 }
122}
123
124#[doc(hidden)]
133pub fn set_context(manager: FluentManager) {
134 try_set_context(manager).expect("Failed to set context");
135}
136
137#[doc(hidden)]
146pub fn set_shared_context(manager: Arc<FluentManager>) {
147 try_set_shared_context(manager).expect("Failed to set shared context");
148}
149
150#[doc(hidden)]
151pub fn try_set_context(manager: FluentManager) -> Result<(), GlobalLocalizationError> {
152 CONTEXT
153 .set(ArcSwap::from_pointee(manager))
154 .map_err(|_| GlobalLocalizationError::ContextAlreadyInitialized)
155}
156
157#[doc(hidden)]
158pub fn try_set_shared_context(manager: Arc<FluentManager>) -> Result<(), GlobalLocalizationError> {
159 CONTEXT
160 .set(ArcSwap::new(manager))
161 .map_err(|_| GlobalLocalizationError::ContextAlreadyInitialized)
162}
163
164#[doc(hidden)]
169pub fn set_custom_localizer_with_domain<F>(localizer: F)
170where
171 F: for<'a> Fn(
172 Option<&str>,
173 &str,
174 Option<&std::collections::HashMap<&str, FluentValue<'a>>>,
175 ) -> Option<String>
176 + Send
177 + Sync
178 + 'static,
179{
180 try_set_custom_localizer_with_domain(localizer)
181 .expect("Failed to set domain-aware custom localizer");
182}
183
184#[doc(hidden)]
185pub fn try_set_custom_localizer_with_domain<F>(localizer: F) -> Result<(), GlobalLocalizationError>
186where
187 F: for<'a> Fn(
188 Option<&str>,
189 &str,
190 Option<&std::collections::HashMap<&str, FluentValue<'a>>>,
191 ) -> Option<String>
192 + Send
193 + Sync
194 + 'static,
195{
196 let mut slot = custom_localizer_slot().write();
197 if slot.is_some() {
198 return Err(GlobalLocalizationError::CustomLocalizerAlreadyInitialized);
199 }
200 *slot = Some(Arc::new(localizer));
201 Ok(())
202}
203
204#[doc(hidden)]
206pub fn replace_custom_localizer_with_domain<F>(localizer: F)
207where
208 F: for<'a> Fn(
209 Option<&str>,
210 &str,
211 Option<&std::collections::HashMap<&str, FluentValue<'a>>>,
212 ) -> Option<String>
213 + Send
214 + Sync
215 + 'static,
216{
217 *custom_localizer_slot().write() = Some(Arc::new(localizer));
218}
219
220#[doc(hidden)]
222pub fn select_language(
223 lang: &unic_langid::LanguageIdentifier,
224) -> Result<(), GlobalLocalizationError> {
225 let context = CONTEXT
226 .get()
227 .ok_or(GlobalLocalizationError::ContextNotInitialized)?;
228 context.load().select_language(lang).map_err(Into::into)
229}
230
231#[doc(hidden)]
240pub fn localize<'a>(
241 id: &str,
242 args: Option<&std::collections::HashMap<&str, FluentValue<'a>>>,
243) -> String {
244 if let Some(message) = try_custom_localizer(id, args) {
245 return message;
246 }
247
248 if let Some(context) = CONTEXT.get()
249 && let Some(message) = context.load().localize(id, args)
250 {
251 return message;
252 }
253
254 tracing::warn!("Translation for '{}' not found or context not set.", id);
255 id.to_string()
256}
257
258#[doc(hidden)]
264pub fn localize_in_domain<'a>(
265 domain: &str,
266 id: &str,
267 args: Option<&std::collections::HashMap<&str, FluentValue<'a>>>,
268) -> String {
269 if let Some(message) = try_custom_localizer_in_domain(domain, id, args) {
270 return message;
271 }
272
273 if let Some(context) = CONTEXT.get()
274 && let Some(message) = context.load().localize_in_domain(domain, id, args)
275 {
276 return message;
277 }
278
279 tracing::warn!(
280 "Translation for '{}' in domain '{}' not found or context not set.",
281 id,
282 domain
283 );
284 id.to_string()
285}