use core::fmt;
use ::koine01::{Country, Language, Locale};
use crate::Lang;
pub fn language(lang: Lang) -> Option<Language> {
Language::from_iso(lang.language()).filter(|language| !language.is_default())
}
pub fn country(lang: Lang) -> Option<Country> {
Country::from_iso(lang.region()?).filter(|country| !country.is_default())
}
pub fn locale(lang: Lang) -> Option<Locale> {
let country = match lang.region() {
Some(_) => country(lang)?,
None => Country::DEFAULT,
};
Some(Locale::new(language(lang)?, country))
}
pub fn lang(locale: Locale) -> Option<Lang> {
let entry = locale.language().to_iso()?;
let language = entry.part1.unwrap_or(entry.id);
let country = locale.country();
let region = (!country.is_default()).then(|| country.as_str());
Lang::from_parts(language, region)
}
pub fn is_known(lang: Lang) -> bool {
match locale(lang) {
Some(locale) if lang.region().is_some() => locale.is_listed(),
Some(_) => true,
None => false,
}
}
pub fn name(lang: Lang) -> Option<&'static str> {
Some(language(lang)?.to_iso()?.name)
}
impl TryFrom<Lang> for Locale {
type Error = Error;
fn try_from(lang: Lang) -> Result<Self, Self::Error> {
let Some(language) = language(lang) else {
return Err(Error(Kind::Language));
};
let country = match lang.region() {
Some(_) => country(lang).ok_or(Error(Kind::Country))?,
None => Country::DEFAULT,
};
Ok(Locale::new(language, country))
}
}
impl TryFrom<Locale> for Lang {
type Error = Error;
fn try_from(locale: Locale) -> Result<Self, Self::Error> {
lang(locale).ok_or(Error(Kind::Default))
}
}
impl TryFrom<Language> for Lang {
type Error = Error;
fn try_from(language: Language) -> Result<Self, Self::Error> {
Lang::try_from(Locale::new(language, Country::DEFAULT))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Error(Kind);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Kind {
Language,
Country,
Default,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Kind::Language => f.write_str("koine does not know the language"),
Kind::Country => f.write_str("koine does not know the region"),
Kind::Default => f.write_str("koine's default sentinel names no language"),
}
}
}
impl core::error::Error for Error {}
#[cfg(test)]
mod tests {
use ::koine01::{Country, Language, Locale};
use super::*;
const SV_SE: Lang = Lang::new("sv-SE");
#[test]
fn lang_to_locale() {
assert_eq!(Locale::try_from(Lang::EN_US), Ok(Locale::EN_US));
assert_eq!(
Locale::try_from(Lang::new("sv")),
Ok(Locale::new(Language::new(b"swe"), Country::DEFAULT))
);
assert_eq!(
Locale::try_from(Lang::new("swe-SE")),
Locale::try_from(SV_SE)
);
assert_eq!(
Locale::try_from(Lang::new("xx-SE")),
Err(Error(Kind::Language))
);
assert_eq!(
Locale::try_from(Lang::new("sv-ZZ")),
Err(Error(Kind::Country))
);
assert_eq!(locale(SV_SE), Locale::try_from(SV_SE).ok());
assert_eq!(locale(Lang::new("sv-ZZ")), None);
}
#[test]
fn locale_to_lang() {
assert_eq!(Lang::try_from(Locale::EN_US), Ok(Lang::EN_US));
assert_eq!(Lang::try_from(Locale::UNKNOWN), Ok(Lang::new("und")));
assert_eq!(
Lang::try_from(Locale::new(Language::new(b"swe"), Country::new(b"FI"))),
Ok(Lang::new("sv-FI"))
);
assert_eq!(
Lang::try_from(Locale::new(Language::new(b"fil"), Country::new(b"PH"))),
Ok(Lang::new("fil-PH"))
);
assert_eq!(Lang::try_from(Locale::DEFAULT), Err(Error(Kind::Default)));
assert_eq!(
Lang::try_from(Locale::new(Language::DEFAULT, Country::US)),
Err(Error(Kind::Default))
);
assert_eq!(lang(Locale::EN_US), Some(Lang::EN_US));
}
#[test]
fn language_to_lang() {
assert_eq!(Lang::try_from(Language::new(b"swe")), Ok(Lang::new("sv")));
assert_eq!(Lang::try_from(Language::UND), Ok(Lang::new("und")));
assert_eq!(Lang::try_from(Language::DEFAULT), Err(Error(Kind::Default)));
}
#[test]
fn round_trips() {
for tag in ["en-US", "sv-SE", "sv", "fil-PH", "pt-BR", "zh-TW", "und"] {
let lang = Lang::new(tag);
let locale = Locale::try_from(lang).unwrap();
assert_eq!(Lang::try_from(locale), Ok(lang), "{tag}");
assert_eq!(locale.to_string(), tag, "{tag}");
}
for locale in ::koine01::locale::iter().step_by(13) {
let locale = Locale::new(
Language::new(locale.language.as_bytes()),
Country::new(locale.country.as_bytes()),
);
let lang = Lang::try_from(locale).unwrap();
assert_eq!(Locale::try_from(lang), Ok(locale), "{locale}");
assert_eq!(lang.to_string(), locale.to_string(), "{locale}");
assert!(is_known(lang), "{lang}");
}
}
#[test]
fn parts() {
assert_eq!(language(SV_SE), Some(Language::new(b"swe")));
assert_eq!(language(Lang::new("xx")), None);
assert_eq!(country(SV_SE), Some(Country::new(b"SE")));
assert_eq!(country(Lang::new("sv")), None);
assert_eq!(country(Lang::new("sv-ZZ")), None);
assert_eq!(name(SV_SE), Some("Swedish"));
assert_eq!(name(Lang::new("en-GB")), Some("English"));
assert_eq!(name(Lang::new("xx")), None);
}
#[test]
fn known() {
assert!(is_known(Lang::EN_US));
assert!(is_known(Lang::new("en")));
assert!(is_known(Lang::new("fil")));
assert!(!is_known(Lang::new("xx")));
assert!(!is_known(Lang::new("en-ZZ")));
assert!(!is_known(Lang::new("sv-JP")));
}
#[test]
fn error_messages() {
assert_eq!(
Error(Kind::Language).to_string(),
"koine does not know the language"
);
assert_eq!(
Error(Kind::Country).to_string(),
"koine does not know the region"
);
assert_eq!(
Error(Kind::Default).to_string(),
"koine's default sentinel names no language"
);
}
}