|
|
@@ -0,0 +1,67 @@
|
|
|
+//! 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<Option<String>> = 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<String>) {
|
|
|
+ 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<F, T>(future: F) -> JoinHandle<T>
|
|
|
+where
|
|
|
+ F: Future<Output = T> + 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
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+#[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,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ assert!(
|
|
|
+ result.contains("источник"),
|
|
|
+ "expected Russian output (источник), got: {result}"
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|