//! Process-global locale store + locale-aware task spawner. //! //! `fluent_i18n::set_locale` writes to a `thread_local!` `RefCell`, so //! `tokio::spawn` worker threads do NOT inherit the locale set on the main //! thread — every `t!` call inside a spawned task falls back to English. //! This module closes that gap: [`set_global_locale`] captures the locale //! once at startup (process-global `OnceLock`), and [`spawn_with_locale`] //! re-applies it at the start of each spawned task so `t!` renders in the //! user's locale, not the fallback. use std::future::Future; use std::sync::OnceLock; use tokio::task::JoinHandle; /// Process-global locale string (e.g. `"ru-RU"`), set once on the main /// thread at startup. Read by [`spawn_with_locale`] before each task. static GLOBAL_LOCALE: OnceLock> = OnceLock::new(); /// Capture the locale once at startup. Called from `main` after /// `set_locale`. Subsequent calls are no-ops (first wins). pub fn set_global_locale(locale: Option) { let _ = GLOBAL_LOCALE.set(locale); } /// Like `tokio::spawn`, but re-applies the process-global locale to the /// worker thread's thread-local before polling the future, so `t!` calls /// inside the task render in the user's locale. pub fn spawn_with_locale(future: F) -> JoinHandle where F: Future + Send + 'static, T: Send + 'static, { let locale = GLOBAL_LOCALE.get().and_then(|opt| opt.clone()); tokio::spawn(async move { if let Some(ref loc) = locale { let _ = fluent_i18n::set_locale(Some(loc.as_str())); } future.await }) } /// Re-apply the process-global locale to the CURRENT thread. Call at the /// top of a `tokio::task::spawn_blocking` closure (those threads are /// fresh and do not inherit the thread-local locale either). pub fn apply_global_locale() { if let Some(loc) = GLOBAL_LOCALE.get().and_then(|opt| opt.clone()) { let _ = fluent_i18n::set_locale(Some(loc.as_str())); } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn spawn_with_locale_inherits_locale() { set_global_locale(Some("ru-RU".to_string())); let result = spawn_with_locale(async { fluent_i18n::t!( "composed-dir-built", { "host" => "/tmp/x".to_string(), "node" => "n".to_string(), "sources" => 3u32, "files" => 1u32, "local" => 2u32, } ) }) .await .unwrap(); assert!( result.contains("источник"), "expected Russian output (источник), got: {result}" ); } }