Skip to main content

fb2_clean/i18n/
pubs.rs

1use super::{Lang, Msg};
2use std::{
3    env, fmt,
4    str::FromStr,
5    sync::{LazyLock, RwLock},
6};
7
8static LANG: LazyLock<RwLock<Lang>> =
9    LazyLock::new(|| RwLock::new(get_lang_from_system_locale().unwrap_or_default()));
10
11/// Returns a localized string of [`Msg`].
12#[macro_export]
13macro_rules! msg {
14    ($x:ident) => {
15        $crate::Msg::$x.as_str()
16    };
17}
18
19/// Displays a localized string.
20impl fmt::Display for Msg {
21    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22        write!(f, "{}", self.as_str())
23    }
24}
25
26impl Msg {
27    /// Returns a localized string.
28    pub fn as_str(self) -> &'static str {
29        match Self::lang() {
30            Lang::Rus => self.as_str_rus(),
31            _ => self.as_str_eng(),
32        }
33    }
34
35    /// Returns the current language.
36    pub fn lang() -> Lang {
37        LANG.read().map(|guard| *guard).unwrap_or_default()
38    }
39
40    /// Tries set language.
41    ///
42    /// # Errors
43    /// Returns an error if internal RwLock::write failed.
44    pub fn set_lang(lang: Lang) -> Result<(), String> {
45        if lang == Self::lang() {
46            Ok(())
47        } else {
48            LANG.write()
49                .map(|mut l| *l = lang)
50                .map_err(|e| e.to_string())
51        }
52    }
53}
54
55impl FromStr for Lang {
56    type Err = String;
57
58    fn from_str(s: &str) -> Result<Lang, Self::Err> {
59        get_lang_from_str(s).ok_or_else(|| Msg::NotFoundAValidLangCode.to_string())
60    }
61}
62
63fn get_lang_from_system_locale() -> Option<Lang> {
64    let locale = env::var("LC_ALL")
65        .ok()
66        .or_else(|| env::var("LANG").ok())
67        .or_else(|| env::var("LC_MESSAGES").ok())
68        .or_else(|| get_system_locale_fallback())?;
69
70    return get_lang_from_str(&locale);
71
72    fn get_system_locale_fallback() -> Option<String> {
73        #[cfg(windows)]
74        {
75            use std::ffi::OsString;
76            use std::os::windows::ffi::OsStringExt;
77            use winapi::um::winnls::GetUserDefaultLocaleName;
78
79            const LOCALE_NAME_MAX_LENGTH: usize = 85;
80            let mut buffer = [0u16; LOCALE_NAME_MAX_LENGTH];
81
82            let len = unsafe {
83                GetUserDefaultLocaleName(buffer.as_mut_ptr(), LOCALE_NAME_MAX_LENGTH as i32)
84            };
85
86            if len > 0 {
87                let os_str = OsString::from_wide(&buffer[..(len as usize - 1)]);
88                os_str.into_string().ok()
89            } else {
90                None
91            }
92        }
93
94        #[cfg(unix)]
95        {
96            None
97        }
98    }
99}
100
101fn get_lang_from_str(s: &str) -> Option<Lang> {
102    fn str_to_ascii_words(s: &str) -> impl Iterator<Item = &str> {
103        use lazy_regex::{Lazy, Regex, regex};
104        static REGEX_ASCII_WORD: &Lazy<Regex> = regex!(r"[a-zA-Z]+");
105        REGEX_ASCII_WORD.find_iter(s).map(|mat| mat.as_str())
106    }
107
108    let mut buf = [0u8; 3];
109    str_to_ascii_words(s).find_map(|s| {
110        let len = s.len();
111        if !matches!(len, 2 | 3) {
112            return None;
113        }
114        for (dst, src) in buf[..len].iter_mut().zip(s.bytes()) {
115            *dst = src.to_ascii_lowercase();
116        }
117
118        let lang = match &buf[..len] {
119            b"en" | b"eng" => Lang::Eng,
120            b"ru" | b"rus" => Lang::Rus,
121            _ => return None,
122        };
123        Some(lang)
124    })
125}