es_fluent/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#[cfg(feature = "derive")]
4pub use es_fluent_derive::{EsFluent, EsFluentChoice, EsFluentKv, EsFluentThis};
5
6#[doc(hidden)]
7pub use es_fluent_manager_core::{FluentManager, I18nModule, LocalizationError, Localizer};
8
9#[doc(hidden)]
10pub use fluent_bundle::FluentValue;
11
12#[doc(hidden)]
13pub use inventory as __inventory;
14
15#[doc(hidden)]
16pub use rust_embed as __rust_embed;
17
18#[doc(hidden)]
19pub use es_fluent_manager_core as __manager_core;
20
21#[doc(hidden)]
22pub use es_fluent_core as __core;
23
24#[doc(hidden)]
25pub use unic_langid;
26
27mod traits;
28pub use traits::{EsFluentChoice, FluentDisplay, ThisFtl, ToFluentString};
29
30use std::sync::{Arc, OnceLock, RwLock};
31
32#[doc(hidden)]
33static CONTEXT: OnceLock<Arc<RwLock<FluentManager>>> = OnceLock::new();
34
35#[doc(hidden)]
36static CUSTOM_LOCALIZER: OnceLock<
37    Box<
38        dyn Fn(&str, Option<&std::collections::HashMap<&str, FluentValue>>) -> Option<String>
39            + Send
40            + Sync,
41    >,
42> = OnceLock::new();
43
44/// Sets the global `FluentManager` context.
45///
46/// This function should be called once at the beginning of your application's
47/// lifecycle.
48///
49/// # Panics
50///
51/// This function will panic if the context has already been set.
52#[doc(hidden)]
53pub fn set_context(manager: FluentManager) {
54    CONTEXT
55        .set(Arc::new(RwLock::new(manager)))
56        .map_err(|_| "Context already set")
57        .expect("Failed to set context");
58}
59
60/// Sets the global `FluentManager` context with a shared `Arc<RwLock<FluentManager>>`.
61///
62/// This function is useful when you want to share the `FluentManager` between
63/// multiple threads.
64///
65/// # Panics
66///
67/// This function will panic if the context has already been set.
68#[doc(hidden)]
69pub fn set_shared_context(manager: Arc<RwLock<FluentManager>>) {
70    CONTEXT
71        .set(manager)
72        .map_err(|_| "Context already set")
73        .expect("Failed to set shared context");
74}
75
76/// Sets a custom localizer function.
77///
78/// The custom localizer will be called before the global context's `localize`
79/// method. If the custom localizer returns `Some(message)`, the message will be
80/// returned. Otherwise, the global context will be used.
81///
82/// # Panics
83///
84/// This function will panic if the custom localizer has already been set.
85#[doc(hidden)]
86pub fn set_custom_localizer<F>(localizer: F)
87where
88    F: Fn(&str, Option<&std::collections::HashMap<&str, FluentValue>>) -> Option<String>
89        + Send
90        + Sync
91        + 'static,
92{
93    CUSTOM_LOCALIZER
94        .set(Box::new(localizer))
95        .map_err(|_| "Custom localizer already set")
96        .expect("Failed to set custom localizer");
97}
98
99/// Updates the global `FluentManager` context.
100#[doc(hidden)]
101pub fn update_context<F>(f: F)
102where
103    F: FnOnce(&mut FluentManager),
104{
105    if let Some(context_arc) = CONTEXT.get() {
106        let mut context = context_arc
107            .write()
108            .expect("Failed to acquire write lock on context");
109        f(&mut context);
110    }
111}
112
113/// Localizes a message by its ID.
114///
115/// This function will first try to use the custom localizer if it has been set.
116/// If the custom localizer returns `None`, it will then try to use the global
117/// context.
118///
119/// If the message is not found, a warning will be logged and the ID will be
120/// returned as the message.
121#[doc(hidden)]
122pub fn localize<'a>(
123    id: &str,
124    args: Option<&std::collections::HashMap<&str, FluentValue<'a>>>,
125) -> String {
126    if let Some(custom_localizer) = CUSTOM_LOCALIZER.get()
127        && let Some(message) = custom_localizer(id, args)
128    {
129        return message;
130    }
131
132    if let Some(context_arc) = CONTEXT.get() {
133        let context = context_arc
134            .read()
135            .expect("Failed to acquire read lock on context");
136
137        if let Some(message) = context.localize(id, args) {
138            return message;
139        }
140    }
141
142    tracing::warn!("Translation for '{}' not found or context not set.", id);
143    id.to_string()
144}