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