1use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader};
4use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer};
5use icu::collator::options::CollatorOptions;
6use icu::collator::preferences::CollationNumericOrdering;
7use icu::collator::{Collator, CollatorBorrowed, CollatorPreferences};
8use icu::locale::Locale;
9use rust_embed::RustEmbed;
10use std::sync::LazyLock;
11
12#[derive(RustEmbed)]
13#[folder = "i18n/"]
14struct Localizations;
15
16pub static LANGUAGE_LOADER: LazyLock<FluentLanguageLoader> = LazyLock::new(|| {
17 let loader: FluentLanguageLoader = fluent_language_loader!();
18
19 loader
20 .load_fallback_language(&Localizations)
21 .expect("Error while loading fallback language");
22
23 loader
24});
25
26pub static LANGUAGE_SORTER: LazyLock<CollatorBorrowed> = LazyLock::new(|| {
27 let create_collator = |locale: Locale| {
28 let mut prefs = CollatorPreferences::from(locale);
29 prefs.numeric_ordering = Some(CollationNumericOrdering::True);
30 Collator::try_new(prefs, CollatorOptions::default()).ok()
31 };
32
33 Locale::try_from_str(&LANGUAGE_LOADER.current_language().to_string())
34 .ok()
35 .and_then(create_collator)
36 .or_else(|| {
37 Locale::try_from_str(&LANGUAGE_LOADER.fallback_language().to_string())
38 .ok()
39 .and_then(create_collator)
40 })
41 .unwrap_or_else(|| {
42 let locale = Locale::try_from_str("en-US").expect("en-US is a valid BCP-47 tag");
43 create_collator(locale)
44 .expect("Creating a collator from the system's current language, the fallback language, or American English should succeed")
45 })
46});
47
48pub static LOCALE: LazyLock<Locale> = LazyLock::new(|| {
49 for var in ["LC_TIME", "LC_ALL", "LANG"] {
50 if let Ok(locale_str) = std::env::var(var) {
51 let cleaned_locale = locale_str
52 .split('.')
53 .next()
54 .unwrap_or(&locale_str)
55 .replace('_', "-");
56
57 if let Ok(locale) = Locale::try_from_str(&cleaned_locale) {
58 return locale;
59 }
60
61 if let Some(lang) = cleaned_locale.split('-').next()
63 && let Ok(locale) = Locale::try_from_str(lang)
64 {
65 return locale;
66 }
67 }
68 }
69 log::warn!("No valid locale found in environment, using fallback");
70 Locale::try_from_str("en-US").expect("Failed to parse fallback locale 'en-US'")
71});
72
73#[macro_export]
74macro_rules! fl {
75 ($message_id:literal) => {{
76 i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id)
77 }};
78
79 ($message_id:literal, $($args:tt)*) => {{
80 i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args)*)
81 }};
82}
83
84pub fn localizer() -> Box<dyn Localizer> {
86 Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
87}
88
89pub fn localize() {
90 let localizer = localizer();
91 let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages();
92
93 if let Err(error) = localizer.select(&requested_languages) {
94 eprintln!("Error while loading language for COSMIC Files {error}");
95 }
96}