Skip to main content

es_fluent/
lib.rs

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 std::sync::{Arc, OnceLock};
30
31#[cfg(feature = "build")]
32pub mod build {
33    pub use es_fluent_toml::build::*;
34}
35
36mod traits;
37pub use traits::{EsFluentChoice, FluentDisplay, ThisFtl, ToFluentString};
38
39#[doc(hidden)]
40static CONTEXT: OnceLock<ArcSwap<FluentManager>> = OnceLock::new();
41
42#[doc(hidden)]
43static CUSTOM_LOCALIZER: OnceLock<
44    Box<
45        dyn Fn(&str, Option<&std::collections::HashMap<&str, FluentValue>>) -> Option<String>
46            + Send
47            + Sync,
48    >,
49> = OnceLock::new();
50
51/// Sets the global `FluentManager` context.
52///
53/// This function should be called once at the beginning of your application's
54/// lifecycle.
55///
56/// # Panics
57///
58/// This function will panic if the context has already been set.
59#[doc(hidden)]
60pub fn set_context(manager: FluentManager) {
61    CONTEXT
62        .set(ArcSwap::from_pointee(manager))
63        .map_err(|_| "Context already set")
64        .expect("Failed to set context");
65}
66
67/// Sets the global `FluentManager` context with a shared `ArcSwap<FluentManager>`.
68///
69/// This function is useful when you want to share the `FluentManager` between
70/// multiple threads.
71///
72/// # Panics
73///
74/// This function will panic if the context has already been set.
75#[doc(hidden)]
76pub fn set_shared_context(manager: Arc<FluentManager>) {
77    CONTEXT
78        .set(ArcSwap::new(manager))
79        .map_err(|_| "Context already set")
80        .expect("Failed to set shared context");
81}
82
83/// Sets a custom localizer function.
84///
85/// The custom localizer will be called before the global context's `localize`
86/// method. If the custom localizer returns `Some(message)`, the message will be
87/// returned. Otherwise, the global context will be used.
88///
89/// # Panics
90///
91/// This function will panic if the custom localizer has already been set.
92#[doc(hidden)]
93pub fn set_custom_localizer<F>(localizer: F)
94where
95    F: Fn(&str, Option<&std::collections::HashMap<&str, FluentValue>>) -> Option<String>
96        + Send
97        + Sync
98        + 'static,
99{
100    CUSTOM_LOCALIZER
101        .set(Box::new(localizer))
102        .map_err(|_| "Custom localizer already set")
103        .expect("Failed to set custom localizer");
104}
105
106/// Selects a language for all localizers in the global context.
107#[doc(hidden)]
108pub fn select_language(lang: &unic_langid::LanguageIdentifier) {
109    if let Some(context) = CONTEXT.get() {
110        context.load().select_language(lang);
111    }
112}
113
114/// Localizes a message by its ID.
115///
116/// This function will first try to use the custom localizer if it has been set.
117/// If the custom localizer returns `None`, it will then try to use the global
118/// context.
119///
120/// If the message is not found, a warning will be logged and the ID will be
121/// returned as the message.
122#[doc(hidden)]
123pub fn localize<'a>(
124    id: &str,
125    args: Option<&std::collections::HashMap<&str, FluentValue<'a>>>,
126) -> String {
127    if let Some(custom_localizer) = CUSTOM_LOCALIZER.get()
128        && let Some(message) = custom_localizer(id, args)
129    {
130        return message;
131    }
132
133    if let Some(context) = CONTEXT.get()
134        && let Some(message) = context.load().localize(id, args)
135    {
136        return message;
137    }
138
139    tracing::warn!("Translation for '{}' not found or context not set.", id);
140    id.to_string()
141}