use std::{collections::HashSet, sync::OnceLock};
use compact_str::ToCompactString;
use getset::Getters;
use lang_id::LangID;
use log::warn;
use smallvec::SmallVec;
use tap::Pipe;
use crate::{MiniStr, fallback::LocaleStrChain, sys::get_static_locale};
#[derive(Default, Debug, Getters, Clone)]
#[getset(get = "pub with_prefix")]
pub struct LocaleContext {
pub(crate) current_locale: OnceLock<LangID>,
#[getset(skip)]
pub(crate) chain: OnceLock<LocaleStrChain>,
pub(crate) all_locales: Option<Box<[LangID]>>,
}
impl LocaleContext {
pub fn with_all_locales<I: Into<Box<[LangID]>>>(mut self, locales: I) -> Self {
self.chain.take();
self.init_static_locale_if_uninitialized();
self.all_locales = Some(locales.into());
self
}
pub fn try_push_front_with_colon_separated_str(
&mut self,
language: Option<&str>,
) -> Result<(), LocaleStrChain> {
match language {
Some(value) if value.split(':').count() >= 2 => {
let custom = value
.split(':')
.filter(|x| !x.is_empty())
.map(|x| x.into())
.collect::<SmallVec<_, 4>>();
self.try_push_front_with_custom_chain(&custom)
}
_ => Ok(()),
}
}
pub fn collect_all_locales_to_vec(&self) -> Vec<MiniStr> {
match self.get_all_locales() {
Some(v) => v
.iter()
.map(|x| x.to_compact_string())
.collect(),
_ => Default::default(),
}
}
pub fn try_push_front_with_custom_chain(
&mut self,
custom: &[MiniStr],
) -> Result<(), LocaleStrChain> {
let new = {
let old = self
.get_or_try_init_chain()
.unwrap_or_default();
let mut seen = HashSet::new();
custom
.iter()
.chain(old.iter())
.filter(|&x| seen.insert(x))
.cloned()
.collect()
};
self.chain.take();
self.chain.set(new)
}
pub(crate) fn init_static_locale_if_uninitialized(&self) {
if self.is_current_locale_initialized() {
return;
}
self
.current_locale
.get_or_init(|| get_static_locale().clone());
}
pub(crate) fn is_current_locale_initialized(&self) -> bool {
self
.current_locale
.get()
.is_some()
}
pub fn is_chain_initialized(&self) -> bool {
self.chain.get().is_some()
}
pub fn with_current_locale(mut self, current: Option<LangID>) -> Self {
self.current_locale.take();
self
.current_locale
.get_or_init(|| match current {
Some(x) => x,
_ => get_static_locale().clone(),
});
self.chain.take();
self
}
pub fn get_or_try_init_chain(&self) -> Option<&[MiniStr]> {
let all_locales = match self.is_chain_initialized() {
true => Default::default(),
_ => match self
.get_all_locales()
.as_deref()
.filter(|x| !x.is_empty())
{
Some(x) => x,
_ => {
warn!("all_locales is empty");
None?
}
},
};
self
.chain
.get_or_init(|| {
self.init_static_locale_if_uninitialized();
let current = self
.current_locale
.get()
.expect("current_locale: Empty");
crate::fallback::init_str_chain(current, all_locales)
})
.as_ref()
.pipe(Some)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fallback::dbg_shared::init_logger;
#[ignore]
#[test]
fn test_push_front_chain() {
init_logger(false);
let mut ctx = LocaleContext::default()
.with_all_locales(glossa_l10n::error::locale_registry::all_locales())
.with_current_locale("gsw".parse().ok());
let old = ctx.get_or_try_init_chain();
assert_eq!(
old,
Some(
["de", "en"]
.map(Into::into)
.as_ref()
)
);
log::info!("old: {old:?}");
const LANGUAGE: &str = "es:fr:pt:en";
let custom = LANGUAGE
.split(':')
.map(Into::into)
.collect::<Vec<_>>();
let _ = ctx.try_push_front_with_custom_chain(&custom);
let new = ctx.get_or_try_init_chain();
log::info!("new: {new:?}");
assert_eq!(
new,
Some(
["es", "fr", "pt", "en", "de"]
.map(Into::into)
.as_ref()
)
)
}
}