use std::sync::{OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::{fmt::Display, fmt::Formatter};
use super::Utf16String;
static DEFAULT_LOCALE: OnceLock<RwLock<Locale>> = OnceLock::new();
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Locale {
language_tag: Utf16String,
country: Utf16String,
}
impl Locale {
#[must_use]
pub const fn new(language_tag: Utf16String, country: Utf16String) -> Self {
Self {
language_tag,
country,
}
}
#[must_use]
pub const fn to_language_tag(&self) -> &Utf16String {
&self.language_tag
}
#[must_use]
pub const fn get_country(&self) -> &Utf16String {
&self.country
}
#[must_use]
pub fn get_language(&self) -> Utf16String {
Utf16String::from_rust_str(
self.language_tag
.to_string_lossy()
.split(['-', '_'])
.next()
.unwrap_or(""),
)
}
#[must_use]
pub fn get_variant(&self) -> Utf16String {
let tag = self.language_tag.to_string_lossy();
let mut parts = tag.split(['-', '_']);
let _language = parts.next();
let country = self.country.to_string_lossy();
let mut remaining = parts.collect::<Vec<_>>();
if !country.is_empty()
&& remaining
.first()
.is_some_and(|part| part.eq_ignore_ascii_case(&country))
{
remaining.remove(0);
}
Utf16String::from_rust_str(&remaining.join("_"))
}
#[must_use]
pub fn get_default() -> Self {
read_recovering_poison(default_locale_lock()).clone()
}
pub fn set_default(locale: Self) {
*write_recovering_poison(default_locale_lock()) = locale;
}
}
impl Display for Locale {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
let language = self.get_language().to_string_lossy();
let country = self.country.to_string_lossy();
let variant = self.get_variant().to_string_lossy();
formatter.write_str(&language)?;
if !country.is_empty() || !variant.is_empty() {
write!(formatter, "_{country}")?;
}
if !variant.is_empty() {
write!(formatter, "_{variant}")?;
}
Ok(())
}
}
fn default_locale_lock() -> &'static RwLock<Locale> {
DEFAULT_LOCALE.get_or_init(|| RwLock::new(locale_from_environment()))
}
fn locale_from_environment() -> Locale {
let raw = ["LC_ALL", "LC_MESSAGES", "LANG"]
.into_iter()
.find_map(|name| std::env::var(name).ok().filter(|value| !value.is_empty()))
.unwrap_or_else(|| "en-US".to_owned());
let without_encoding = raw.split('.').next().unwrap_or("en-US");
let normalized = without_encoding.replace('_', "-");
let country = normalized
.split('-')
.nth(1)
.filter(|part| part.len() == 2 || part.len() == 3)
.unwrap_or("")
.to_ascii_uppercase();
Locale::new(
Utf16String::from_rust_str(&normalized),
Utf16String::from_rust_str(&country),
)
}
fn read_recovering_poison<T>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
lock.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write_recovering_poison<T>(lock: &RwLock<T>) -> RwLockWriteGuard<'_, T> {
lock.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}