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#[macro_export]
13macro_rules! msg {
14 ($x:ident) => {
15 $crate::Msg::$x.as_str()
16 };
17}
18
19impl 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 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 pub fn lang() -> Lang {
37 LANG.read().map(|guard| *guard).unwrap_or_default()
38 }
39
40 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 = &[u8]> {
103 s.as_bytes()
104 .split(|b| !b.is_ascii_alphabetic())
105 .filter(|w| !w.is_empty())
106 }
107
108 let mut buf = [0u8; 3];
109 str_to_ascii_words(s).find_map(|word| {
110 let len = word.len();
111 if !matches!(len, 2 | 3) {
112 return None;
113 }
114 for (dst, src) in buf[..len].iter_mut().zip(word) {
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}