locale.rs 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. //! Process-global locale store + locale-aware task spawner.
  2. //!
  3. //! `fluent_i18n::set_locale` writes to a `thread_local!` `RefCell`, so
  4. //! `tokio::spawn` worker threads do NOT inherit the locale set on the main
  5. //! thread — every `t!` call inside a spawned task falls back to English.
  6. //! This module closes that gap: [`set_global_locale`] captures the locale
  7. //! once at startup (process-global `OnceLock`), and [`spawn_with_locale`]
  8. //! re-applies it at the start of each spawned task so `t!` renders in the
  9. //! user's locale, not the fallback.
  10. use std::future::Future;
  11. use std::sync::OnceLock;
  12. use tokio::task::JoinHandle;
  13. /// Process-global locale string (e.g. `"ru-RU"`), set once on the main
  14. /// thread at startup. Read by [`spawn_with_locale`] before each task.
  15. static GLOBAL_LOCALE: OnceLock<Option<String>> = OnceLock::new();
  16. /// Capture the locale once at startup. Called from `main` after
  17. /// `set_locale`. Subsequent calls are no-ops (first wins).
  18. pub fn set_global_locale(locale: Option<String>) {
  19. let _ = GLOBAL_LOCALE.set(locale);
  20. }
  21. /// Like `tokio::spawn`, but re-applies the process-global locale to the
  22. /// worker thread's thread-local before polling the future, so `t!` calls
  23. /// inside the task render in the user's locale.
  24. pub fn spawn_with_locale<F, T>(future: F) -> JoinHandle<T>
  25. where
  26. F: Future<Output = T> + Send + 'static,
  27. T: Send + 'static,
  28. {
  29. let locale = GLOBAL_LOCALE.get().and_then(|opt| opt.clone());
  30. tokio::spawn(async move {
  31. if let Some(ref loc) = locale {
  32. let _ = fluent_i18n::set_locale(Some(loc.as_str()));
  33. }
  34. future.await
  35. })
  36. }
  37. #[cfg(test)]
  38. mod tests {
  39. use super::*;
  40. #[tokio::test]
  41. async fn spawn_with_locale_inherits_locale() {
  42. set_global_locale(Some("ru-RU".to_string()));
  43. let result = spawn_with_locale(async {
  44. fluent_i18n::t!(
  45. "composed-dir-built",
  46. {
  47. "host" => "/tmp/x".to_string(),
  48. "node" => "n".to_string(),
  49. "sources" => 3u32,
  50. "files" => 1u32,
  51. }
  52. )
  53. })
  54. .await
  55. .unwrap();
  56. assert!(
  57. result.contains("источник"),
  58. "expected Russian output (источник), got: {result}"
  59. );
  60. }
  61. }