tairitsu-web 0.5.1

Tairitsu Web Framework - Modular WebAssembly-first framework with SSR, CSR, SSG support
use std::{
    hash::{Hash, Hasher},
    sync::LazyLock,
};

use iso639_enum::{IsoCompat, Language as IsoLang};

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum TextDirection {
    #[default]
    Ltr,
    Rtl,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ScriptVariant {
    Default,
    Hans,
    Hant,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Language {
    iso: IsoLang,
    script: ScriptVariant,
}

impl Hash for Language {
    fn hash<H: Hasher>(&self, state: &mut H) {
        (self.iso as i32).hash(state);
        self.script.hash(state);
    }
}

impl Language {
    pub const ENGLISH: Self = Self {
        iso: IsoLang::Eng,
        script: ScriptVariant::Default,
    };
    pub const CHINESE_SIMPLIFIED: Self = Self {
        iso: IsoLang::Cmn,
        script: ScriptVariant::Hans,
    };
    pub const CHINESE_TRADITIONAL: Self = Self {
        iso: IsoLang::Cmn,
        script: ScriptVariant::Hant,
    };
    pub const FRENCH: Self = Self {
        iso: IsoLang::Fra,
        script: ScriptVariant::Default,
    };
    pub const RUSSIAN: Self = Self {
        iso: IsoLang::Rus,
        script: ScriptVariant::Default,
    };
    pub const SPANISH: Self = Self {
        iso: IsoLang::Spa,
        script: ScriptVariant::Default,
    };
    pub const ARABIC: Self = Self {
        iso: IsoLang::Ara,
        script: ScriptVariant::Default,
    };
    pub const JAPANESE: Self = Self {
        iso: IsoLang::Jpn,
        script: ScriptVariant::Default,
    };
    pub const KOREAN: Self = Self {
        iso: IsoLang::Kor,
        script: ScriptVariant::Default,
    };

    pub fn new(iso: IsoLang) -> Self {
        Self {
            iso,
            script: ScriptVariant::Default,
        }
    }

    pub fn with_script(iso: IsoLang, script: ScriptVariant) -> Self {
        Self { iso, script }
    }

    pub fn iso(&self) -> IsoLang {
        self.iso
    }

    pub fn script(&self) -> ScriptVariant {
        self.script
    }

    pub fn code(&self) -> String {
        match self.script {
            ScriptVariant::Hans => "zhs".to_string(),
            ScriptVariant::Hant => "zht".to_string(),
            _ => self
                .iso
                .iso639_1()
                .unwrap_or(self.iso.iso639_3())
                .to_lowercase(),
        }
    }

    pub fn url_prefix(&self) -> String {
        self.code()
    }

    pub fn native_name(&self) -> &str {
        match self.script {
            ScriptVariant::Hans => "简体中文",
            ScriptVariant::Hant => "繁體中文",
            _ => self.iso.autonym().unwrap_or(self.iso.name()),
        }
    }

    pub fn short_name(&self) -> &'static str {
        if let Some(a2) = self.iso.iso639_1() {
            match (a2, self.script) {
                ("en", _) => "EN",
                ("zh", ScriptVariant::Hans) => "",
                ("zh", ScriptVariant::Hant) => "",
                ("zh", _) => "",
                ("ja", _) => "",
                ("ko", _) => "",
                ("de", _) => "DE",
                ("fr", _) => "FR",
                ("es", _) => "ES",
                ("ar", _) => "ع",
                ("ru", _) => "РУ",
                (other, _) => match other {
                    "pt" => "PT",
                    "it" => "IT",
                    "nl" => "NL",
                    "pl" => "PL",
                    "tr" => "TR",
                    "vi" => "VI",
                    "th" => "TH",
                    "id" => "ID",
                    "hi" => "HI",
                    _ => "??",
                },
            }
        } else {
            "??"
        }
    }

    pub fn is_rtl(&self) -> bool {
        static RTL_ALPHA2: &[&str] = &[
            "ar", "he", "fa", "ur", "yi", "syr", "diq", "ckb", "ps", "ug", "ku", "dv", "sd",
        ];
        static RTL_ALPHA3: &[&str] = &[
            "adp", "afb", "ajp", "apc", "arb", "arz", "haz", "mhr", "phr", "shi", "sux", "tmh",
            "uzn", "ydd", "yud",
        ];
        if let Some(a2) = self.iso.iso639_1() {
            return RTL_ALPHA2.contains(&a2);
        }
        RTL_ALPHA3.contains(&self.iso.iso639_3())
    }

    pub fn direction(&self) -> TextDirection {
        if self.is_rtl() {
            TextDirection::Rtl
        } else {
            TextDirection::Ltr
        }
    }

    pub fn from_code(code: &str) -> Option<Self> {
        let code = code.trim();
        match code {
            "en" | "en-US" | "en-us" => return Some(Self::ENGLISH),
            "zhs" | "zh-CHS" | "zh-chs" | "zh-Hans" | "zh-CN" | "zh-cn" => {
                return Some(Self::CHINESE_SIMPLIFIED);
            }
            "zht" | "zh-CHT" | "zh-cht" | "zh-Hant" | "zh-TW" | "zh-tw" => {
                return Some(Self::CHINESE_TRADITIONAL);
            }
            "fr" | "fr-FR" | "fr-fr" => return Some(Self::FRENCH),
            "ru" | "ru-RU" | "ru-ru" => return Some(Self::RUSSIAN),
            "es" | "es-ES" | "es-es" => return Some(Self::SPANISH),
            "ar" | "ar-SA" | "ar-sa" => return Some(Self::ARABIC),
            "ja" | "ja-JP" | "ja-jp" => return Some(Self::JAPANESE),
            "ko" | "ko-KR" | "ko-kr" => return Some(Self::KOREAN),
            _ => {}
        }

        if let Some((lang, _region)) = code.split_once('-') {
            if let Ok(iso) = IsoLang::from_iso639_1(lang) {
                return Some(Self::new(iso));
            }
            if let Ok(iso) = IsoLang::from_iso639_3(lang) {
                return Some(Self::new(iso));
            }
        }

        if let Ok(iso) = IsoLang::from_iso639_1(code) {
            return Some(Self::new(iso));
        }
        if let Ok(iso) = IsoLang::from_iso639_3(code) {
            return Some(Self::new(iso));
        }

        None
    }

    pub fn from_url_prefix(prefix: &str) -> Option<Self> {
        Self::from_code(prefix)
    }

    pub fn from_iso(iso: IsoLang) -> Self {
        Self::new(iso)
    }

    pub fn all() -> &'static [Language] {
        &[
            Self::ENGLISH,
            Self::CHINESE_SIMPLIFIED,
            Self::CHINESE_TRADITIONAL,
            Self::FRENCH,
            Self::RUSSIAN,
            Self::SPANISH,
            Self::ARABIC,
            Self::JAPANESE,
            Self::KOREAN,
        ]
    }

    pub fn un_official_languages() -> &'static [Language] {
        &[
            Self::ARABIC,
            Self::CHINESE_SIMPLIFIED,
            Self::ENGLISH,
            Self::FRENCH,
            Self::RUSSIAN,
            Self::SPANISH,
        ]
    }

    pub fn east_asian_languages() -> &'static [Language] {
        &[
            Self::CHINESE_SIMPLIFIED,
            Self::CHINESE_TRADITIONAL,
            Self::JAPANESE,
            Self::KOREAN,
        ]
    }

    pub fn rtl_languages() -> &'static [Language] {
        &[Self::ARABIC]
    }

    pub fn default_lang() -> Self {
        Self::CHINESE_SIMPLIFIED
    }
}

static COMMON_ALPHA3: &[&str] = &[
    "eng", "zho", "cmn", "spa", "fra", "deu", "jpn", "kor", "ara", "por", "rus", "ita", "nld",
    "pol", "tur", "vie", "tha", "ind", "msa", "fil", "hin", "ben", "tam", "tel", "mar", "guj",
    "kan", "mal", "ori", "pan", "bur", "khm", "lao", "sin", "nep", "fas", "urd", "heb", "swa",
    "afr", "ron", "ukr", "ces", "slk", "hun", "bul", "ell", "dan", "nor", "swe", "fin", "est",
    "lav", "lit", "srp", "hrv", "slv", "cat", "eus", "glg",
];

static COMMON_LANGUAGES: LazyLock<Vec<Language>> = LazyLock::new(|| {
    COMMON_ALPHA3
        .iter()
        .filter_map(|&code| IsoLang::from_iso639_3(code).ok().map(Language::new))
        .collect()
});

pub fn common_languages() -> &'static [Language] {
    COMMON_LANGUAGES.as_slice()
}