Skip to main content

eska_presentation/localization/
locale.rs

1//! Supported locales and the CLI/environment/system selection policy.
2
3use std::{
4    env,
5    ffi::{OsStr, OsString},
6};
7
8/// A locale supported by eska's human-facing interface.
9#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
10pub enum Locale {
11    RuRu,
12    #[default]
13    EnUs,
14}
15
16impl Locale {
17    /// Normalizes one of the explicitly supported locale spellings.
18    #[must_use]
19    pub fn from_locale_name(value: &str) -> Option<Self> {
20        let base = value
21            .split(['.', '@'])
22            .next()
23            .unwrap_or(value)
24            .replace('_', "-")
25            .to_ascii_lowercase();
26
27        match base.as_str() {
28            "ru" | "ru-ru" => Some(Self::RuRu),
29            "en" | "en-us" => Some(Self::EnUs),
30            _ => None,
31        }
32    }
33
34    /// Returns the canonical locale identifier.
35    #[must_use]
36    pub const fn as_str(self) -> &'static str {
37        match self {
38            Self::RuRu => "ru-RU",
39            Self::EnUs => "en-US",
40        }
41    }
42}
43
44/// Resolves a locale from already obtained values in descending priority.
45///
46/// Supplying values directly keeps this policy independent from the test machine.
47#[must_use]
48pub fn resolve_locale(
49    cli_locale: Option<&str>,
50    environment_locale: Option<&str>,
51    system_locale: Option<&str>,
52) -> Locale {
53    cli_locale
54        .or(environment_locale)
55        .or(system_locale)
56        .and_then(Locale::from_locale_name)
57        .unwrap_or_default()
58}
59
60/// Resolves the locale using `ESKA_LANG`, the operating system, then `en-US`.
61#[must_use]
62pub fn resolve_locale_from_environment(cli_locale: Option<&str>) -> Locale {
63    let environment_locale: Option<OsString> = env::var_os("ESKA_LANG");
64    let system_locale = sys_locale::get_locale();
65    resolve_locale(
66        cli_locale,
67        environment_locale.as_deref().and_then(OsStr::to_str),
68        system_locale.as_deref(),
69    )
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn normalizes_supported_locale_names() {
78        for value in ["ru", "ru-RU", "ru_RU", "ru_RU.UTF-8"] {
79            assert_eq!(Locale::from_locale_name(value), Some(Locale::RuRu));
80        }
81        for value in ["en", "en-US", "en_US", "en_US.UTF-8"] {
82            assert_eq!(Locale::from_locale_name(value), Some(Locale::EnUs));
83        }
84    }
85
86    #[test]
87    fn resolves_locale_in_priority_order() {
88        assert_eq!(
89            resolve_locale(Some("ru"), Some("en"), Some("en")),
90            Locale::RuRu
91        );
92        assert_eq!(resolve_locale(None, Some("ru"), Some("en")), Locale::RuRu);
93        assert_eq!(resolve_locale(None, None, Some("ru")), Locale::RuRu);
94        assert_eq!(resolve_locale(None, None, Some("de-DE")), Locale::EnUs);
95        assert_eq!(resolve_locale(None, None, None), Locale::EnUs);
96    }
97}