Skip to main content

pacenotes_installer\i18n/
language.rs

1const LANG_ENGLISH: u16 = 0x09;
2const SUBLANG_ENGLISH_US: u16 = 0x01;
3const LANG_POLISH: u16 = 0x15;
4const SUBLANG_DEFAULT: u16 = 0x01;
5const PRIMARY_LANGUAGE_MASK: u16 = 0x3FF;
6
7/// `MAKELANGID`.
8const fn make_langid(primary: u16, sublanguage: u16) -> u16 {
9    (sublanguage << 10) | primary
10}
11
12#[derive(Clone, Copy, PartialEq, Eq, Debug)]
13pub enum Language {
14    En,
15    Pl,
16}
17
18impl Language {
19    /// In picker order, so an index is also the combo item index.
20    pub const ALL: [Language; 2] = [Language::En, Language::Pl];
21
22    /// The endonym, never translated, so a user in the wrong language still recognises theirs.
23    pub fn label(self) -> &'static str {
24        match self {
25            Language::En => "English",
26            Language::Pl => "Polski",
27        }
28    }
29
30    pub fn index(self) -> usize {
31        Language::ALL
32            .iter()
33            .position(|&language| language == self)
34            .unwrap_or(0)
35    }
36
37    pub fn from_index(index: usize) -> Language {
38        Language::ALL.get(index).copied().unwrap_or(Language::En)
39    }
40
41    /// The `lang` of `GET /motd`, which names the server's `<lang>.txt`.
42    pub fn tag(self) -> &'static str {
43        match self {
44            Language::En => "en",
45            Language::Pl => "pl",
46        }
47    }
48
49    /// The inverse of `tag`, for a restarted instance told which language to keep.
50    pub fn from_tag(tag: &str) -> Option<Language> {
51        Language::ALL
52            .iter()
53            .copied()
54            .find(|language| language.tag() == tag)
55    }
56
57    fn langid(self) -> u16 {
58        match self {
59            Language::En => make_langid(LANG_ENGLISH, SUBLANG_ENGLISH_US),
60            Language::Pl => make_langid(LANG_POLISH, SUBLANG_DEFAULT),
61        }
62    }
63
64    fn primary(self) -> u16 {
65        self.langid() & PRIMARY_LANGUAGE_MASK
66    }
67}
68
69/// The OS UI language when the installer ships it, matched on the primary
70/// language so every Polish locale lands on Polish.
71pub fn os_language() -> Option<Language> {
72    let ui_language = unsafe { windows_sys::Win32::Globalization::GetUserDefaultUILanguage() };
73    let primary = ui_language & PRIMARY_LANGUAGE_MASK;
74    Language::ALL
75        .iter()
76        .copied()
77        .find(|language| language.primary() == primary)
78}
79
80/// The language scene 1 chose, or 0 before it has.
81static CHOSEN: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new(0);
82
83/// Per thread, so workers get already-loaded strings from the UI thread; it is
84/// also remembered for the panic hook, which can run on any thread.
85pub fn set_language(language: Language) {
86    CHOSEN.store(language.langid(), std::sync::atomic::Ordering::Relaxed);
87    unsafe { windows_sys::Win32::Globalization::SetThreadUILanguage(language.langid()) };
88}
89
90/// Applies the chosen language to this thread; before scene 1 answers the OS default stands.
91pub fn use_chosen_language() {
92    let langid = CHOSEN.load(std::sync::atomic::Ordering::Relaxed);
93    if langid != 0 {
94        unsafe { windows_sys::Win32::Globalization::SetThreadUILanguage(langid) };
95    }
96}