1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
pub use current::*;
use fluent::{FluentBundle, FluentResource};
pub use load::*;
use once_cell::sync::Lazy;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::sync::RwLock;
use unic_langid::{langid, LanguageIdentifier};

mod current;
mod load;

pub static CURRENT_BVE_LOCALE: Lazy<RwLock<BVELocaleBundle>> =
    Lazy::new(|| RwLock::new(load_locale_bundle(get_current_locale())));
pub static ENGLISH_LOCALE: Lazy<BVELocaleBundle> =
    Lazy::new(|| load_locale_bundle(BVELocale::from_language(BVELanguage::EN)));

pub struct BVELocaleBundle {
    pub language: BVELocale,
    pub bundle: FluentBundle<FluentResource>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct BVELocale {
    pub langid: LanguageIdentifier,
    pub lang: BVELanguage,
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum BVELanguage {
    EN,
    DE,
}

impl BVELocale {
    #[must_use]
    pub fn from_ident(langid: LanguageIdentifier) -> Self {
        let lang = match langid.language() {
            "de" => BVELanguage::DE,
            _ => BVELanguage::EN,
        };
        Self { langid, lang }
    }

    #[must_use]
    pub fn from_language(lang: BVELanguage) -> Self {
        let langid = match lang {
            BVELanguage::EN => langid!("en-US"),
            BVELanguage::DE => langid!("de-DE"),
        };
        Self { langid, lang }
    }

    #[must_use]
    pub fn to_ident(&self) -> &'static str {
        self.lang.to_ident()
    }
}

impl BVELanguage {
    #[must_use]
    pub fn to_ident(self) -> &'static str {
        match self {
            Self::EN => "en",
            Self::DE => "de",
        }
    }
}

impl Display for BVELanguage {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.to_ident())
    }
}

#[macro_export]
macro_rules! localize {
    // Localize in english for logging
    (@en $name:literal, $($key:literal -> $value:literal),+ $(,)*) => {
        $crate::localize!($crate::l10n::ENGLISH_LOCALE, $name, $($key -> $value),+)
    };
    (@en $name:literal) => {
        $crate::localize!($crate::l10n::ENGLISH_LOCALE, $name)
    };
    // Localize in the current locale
    ($name:literal, $($key:literal -> $value:literal),+ $(,)*) => {
        $crate::localize!($crate::l10n::CURRENT_BVE_LOCALE.read().expect("Unable to lock LocaleMutex"), $name, $($key -> $value),+)
    };
    ($name:literal) => {
        $crate::localize!($crate::l10n::CURRENT_BVE_LOCALE.read().expect("Unable to lock LocaleMutex"), $name)
    };
    // Localize over the locale provided as first argument, taken by reference.
    ($locale:expr, $name:literal, $($key:literal -> $value:literal),+ $(,)*) => {{
        let mut errors = std::vec::Vec::new();
        let mut args = std::collections::HashMap::new();
        $(
            args.insert($key, fluent::FluentValue::from($value));
        )*
        let guard = &$locale;
        let msg = guard.bundle.get_message($name).expect("Missing translation name");
        let pattern = msg.value.expect("Message has no pattern");
        let formatted: std::string::String = guard.bundle.format_pattern(&pattern, Some(&args), &mut errors).to_string();
        assert_eq!(errors, std::vec::Vec::new());
        formatted
    }};
    ($locale:expr, $name:literal) => {{
        let mut errors = std::vec::Vec::new();
        let guard = &$locale;
        let msg = guard.bundle.get_message($name).expect("Missing translation name");
        let pattern = msg.value.expect("Message has no pattern");
        let formatted: std::string::String = guard.bundle.format_pattern(&pattern, None, &mut errors).to_string();
        assert_eq!(errors, std::vec::Vec::new());
        formatted
    }};
}

#[cfg(test)]
mod test {
    use crate::l10n::{load_locale_bundle, BVELanguage, BVELocale};

    macro_rules! loc_test {
        ($($tokens:tt)*) => {{
            let result = localize!($($tokens)*);
            assert!(!result.is_empty());
        }};
    }

    macro_rules! language_test {
        ($name:ident, $lang:ident) => {
            #[test]
            fn $name() {
                let language = load_locale_bundle(BVELocale::from_language(BVELanguage::$lang));
                loc_test!(language, "name");
                loc_test!(language, "language-code");
                loc_test!(language, "welcome", "name" -> "MyUsername");
            }
        };
    }

    language_test!(en, EN);
    language_test!(de, DE);
}