cosmic_files/
localize.rs

1// SPDX-License-Identifier: GPL-3.0-only
2
3use 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            // Try language-only fallback (e.g., "en" from "en-US")
62            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
84// Get the `Localizer` to be used for localizing this library.
85pub 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}