Skip to main content

anycms_i18n/
lib.rs

1//! # anycms-i18n
2//!
3//! Internationalization support for the anycms-rs ecosystem.
4//!
5//! ## Quick Start
6//!
7//! ```rust,ignore
8//! use anycms_i18n::{I18nBuilder, t};
9//!
10//! let i18n = I18nBuilder::new()
11//!     .default_locale("en")
12//!     .fallback_locale("en")
13//!     .embedded_translations(&[
14//!         ("en", include_str!("../../locales/en.toml")),
15//!         ("zh-CN", include_str!("../../locales/zh-CN.toml")),
16//!     ])
17//!     .build()
18//!     .unwrap();
19//!
20//! // Simple translation
21//! let msg = t!("welcome");
22//!
23//! // With locale override
24//! let msg = t!("welcome", locale = "zh-CN");
25//!
26//! // With interpolation
27//! let msg = t!("greeting", name = "world");
28//!
29//! // With plural
30//! let msg = t!("items", count = 5);
31//! ```
32
33mod backend;
34mod builder;
35mod core;
36mod error;
37mod flat_backend;
38mod interpolate;
39mod locale;
40mod macros;
41mod plural;
42
43#[cfg(feature = "json-backend")]
44mod json_backend;
45
46#[cfg(feature = "yaml-backend")]
47mod yaml_backend;
48
49// ---- public API ----
50
51pub use backend::{ChainedBackend, TomlBackend};
52pub use builder::I18nBuilder;
53pub use core::{Backend, I18n, Reloadable};
54pub use error::I18nError;
55pub use flat_backend::FlatBackend;
56pub use interpolate::interpolate;
57pub use locale::{Locale, negotiate_locale};
58pub use plural::{PluralCategory, plural_category};
59
60#[cfg(feature = "json-backend")]
61pub use json_backend::JsonBackend;
62
63#[cfg(feature = "yaml-backend")]
64pub use yaml_backend::YamlBackend;
65
66// Note: `t!` and `__t_inner!` are #[macro_export] and automatically at crate root.
67// No explicit re-export needed.
68
69// i18n!() init macro (feature-gated)
70#[cfg(feature = "init")]
71pub use anycms_i18n_macro::i18n;
72
73// embed_locales!() helper macro (feature-gated)
74#[cfg(feature = "init")]
75pub use anycms_i18n_macro::embed_locales;
76
77// Hot-reload support (feature-gated)
78#[cfg(feature = "hot-reload")]
79mod hot_reload;
80#[cfg(feature = "hot-reload")]
81pub use hot_reload::HotReloader;
82
83// ---- Global I18n instance ----
84
85use std::sync::OnceLock;
86
87static GLOBAL_I18N: OnceLock<I18n> = OnceLock::new();
88
89/// Set the global [`I18n`] instance used by the `t!` macro.
90///
91/// Can only be called once; returns `Err` if already set.
92pub fn set_global(i18n: I18n) -> Result<(), I18n> {
93    GLOBAL_I18N.set(i18n)
94}
95
96/// Get a reference to the global [`I18n`] instance.
97///
98/// Returns `None` if [`set_global`] has not been called.
99pub fn global() -> Option<&'static I18n> {
100    GLOBAL_I18N.get()
101}
102
103// ---- Global HotReloader (kept alive so watcher doesn't stop) ----
104
105#[cfg(feature = "hot-reload")]
106static GLOBAL_RELOADER: OnceLock<HotReloader> = OnceLock::new();
107
108/// Store the [`HotReloader`] globally so it stays alive.
109///
110/// Called automatically by `i18n!("...", hot_reload)`.
111/// Can only be called once; returns `Err` if already set.
112#[cfg(feature = "hot-reload")]
113pub fn set_global_reloader(reloader: HotReloader) -> Result<(), HotReloader> {
114    GLOBAL_RELOADER.set(reloader)
115}
116
117// ---- Task-local locale (for async web frameworks) ----
118
119#[cfg(feature = "task-local")]
120tokio::task_local! {
121    /// Task-local locale, set by web framework middleware.
122    ///
123    /// The `t!()` macro checks this before falling back to the global default.
124    /// Use [`set_task_locale`] to wrap a future with a locale scope.
125    pub static CURRENT_LOCALE: String;
126}
127
128/// Get the current task-local locale, if set.
129///
130/// Returns `None` if not inside a [`CURRENT_LOCALE`] scope
131/// (i.e., not in a web request context).
132#[cfg(feature = "task-local")]
133pub fn task_locale() -> Option<String> {
134    CURRENT_LOCALE.try_with(|l| l.clone()).ok()
135}
136
137#[cfg(not(feature = "task-local"))]
138pub fn task_locale() -> Option<String> {
139    None
140}