use crate::compat::{Mutex, OnceLock, String};
#[cfg_attr(alloc_frugal, allow(dead_code))]
fn cache() -> &'static Mutex<Option<&'static str>> {
static CACHE: OnceLock<Mutex<Option<&'static str>>> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(None))
}
#[cfg_attr(alloc_frugal, allow(dead_code))]
pub(crate) fn intern(value: Option<String>) -> Option<&'static str> {
let value = value?;
if value.is_empty() {
return None;
}
let mut guard = crate::compat::lock(cache());
if let Some(current) = *guard {
if current == value.as_str() {
return Some(current);
}
}
let leaked: &'static str = crate::compat::Box::leak(value.into_boxed_str());
*guard = Some(leaked);
Some(leaked)
}
#[cfg(not(alloc_frugal))]
#[cfg_attr(not(any(unix_desktop, target_vendor = "apple")), allow(dead_code))]
pub(crate) fn from_environment() -> Option<String> {
["LC_ALL", "LC_MESSAGES", "LANG", "LANGUAGE"]
.iter()
.filter_map(|name| std::env::var(name).ok())
.find_map(|value| normalize(&value))
}
#[cfg(alloc_frugal)]
#[cfg_attr(not(any(unix_desktop, target_vendor = "apple")), allow(dead_code))]
pub(crate) fn from_environment() -> Option<String> {
None
}
#[cfg(not(alloc_frugal))]
#[cfg_attr(not(any(unix_desktop, target_vendor = "apple")), allow(dead_code))]
pub(crate) fn normalize(value: &str) -> Option<String> {
let value = value.trim();
if value.is_empty() || value == "C" || value == "POSIX" {
return None;
}
let without_modifier = value.split('@').next().unwrap_or(value);
let without_encoding = without_modifier.split('.').next().unwrap_or(without_modifier);
let tag = without_encoding.replace('_', "-");
if tag.is_empty() {
None
} else {
Some(tag)
}
}
pub(crate) fn generation_hash(
color_scheme: Option<bool>,
high_contrast: Option<bool>,
reduce_motion: Option<bool>,
locale: Option<&str>,
) -> u64 {
if color_scheme.is_none()
&& high_contrast.is_none()
&& reduce_motion.is_none()
&& locale.is_none()
{
return 0;
}
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
let mut hash = FNV_OFFSET;
let mut mix = |part: u64| {
hash ^= part;
hash = hash.wrapping_mul(FNV_PRIME);
};
for fact in [color_scheme, high_contrast, reduce_motion] {
match fact {
None => mix(0),
Some(false) => mix(1),
Some(true) => mix(2),
}
}
match locale {
None => mix(0),
Some(tag) => {
mix(1);
for byte in tag.bytes() {
mix(u64::from(byte));
}
}
}
hash
}
#[cfg(all(test, not(alloc_frugal)))]
mod tests {
use super::*;
#[test]
fn normalization_strips_encoding_and_modifier() {
assert_eq!(normalize("de_DE.UTF-8@euro").as_deref(), Some("de-DE"));
assert_eq!(normalize("pt_BR.utf8").as_deref(), Some("pt-BR"));
assert_eq!(normalize("ja").as_deref(), Some("ja"));
assert_eq!(normalize("C"), None);
assert_eq!(normalize("POSIX"), None);
assert_eq!(normalize(""), None);
}
#[test]
fn interning_is_stable_and_replaces_on_change() {
let first = intern(Some(String::from("en-US"))).expect("a tag");
let again = intern(Some(String::from("en-US"))).expect("a tag");
assert_eq!(first.as_ptr(), again.as_ptr(), "the same tag must be interned once");
let second = intern(Some(String::from("fr-FR"))).expect("a tag");
assert_eq!(second, "fr-FR");
let third = intern(Some(String::from("fr-FR"))).expect("a tag");
assert_eq!(second.as_ptr(), third.as_ptr());
let _ = intern(Some(String::from("en-US")));
}
#[test]
fn empty_and_absent_values_answer_none() {
assert_eq!(intern(None), None);
assert_eq!(intern(Some(String::new())), None);
}
#[test]
fn generation_is_zero_only_when_nothing_was_read() {
assert_eq!(generation_hash(None, None, None, None), 0);
let baseline = generation_hash(Some(false), Some(false), Some(false), Some("en-US"));
assert_ne!(baseline, 0);
assert_ne!(baseline, generation_hash(Some(true), Some(false), Some(false), Some("en-US")));
assert_ne!(baseline, generation_hash(Some(false), Some(true), Some(false), Some("en-US")));
assert_ne!(baseline, generation_hash(Some(false), Some(false), Some(true), Some("en-US")));
assert_ne!(baseline, generation_hash(Some(false), Some(false), Some(false), Some("de-DE")));
assert_eq!(baseline, generation_hash(Some(false), Some(false), Some(false), Some("en-US")));
}
}