mod default_rulebook;
#[cfg(feature = "getlang")]
pub mod getlang;
pub mod macros;
#[cfg(feature = "per_lang_default_rules")]
pub mod per_lang_default_rules;
use std::{rc::Rc, sync::Arc};
#[cfg(feature = "getlang")]
pub use getlang::system_want_langids;
use itertools::Itertools;
pub use unic_langid::{self, LanguageIdentifier};
#[derive(Clone, Copy, Debug, Default)]
pub struct LocaleFallbackSolver<R: for<'a> PolyL10nRulebook<'a> = ARulebook> {
pub rulebook: R,
}
impl<R: for<'a> PolyL10nRulebook<'a>> LocaleFallbackSolver<R> {
pub fn solve_locale<L: AsRef<LanguageIdentifier>>(&self, locale: L) -> Vec<LanguageIdentifier> {
use std::hash::{Hash, Hasher};
let locale = locale.as_ref();
let mut locales = self.rulebook.find_fallback_locale(locale).collect_vec();
let h = |l: &LanguageIdentifier| {
let mut hasher = std::hash::DefaultHasher::default();
l.hash(&mut hasher);
hasher.finish()
};
let mut locale_hashes = locales.iter().map(h).collect_vec();
let mut old_len = 0;
while old_len != locales.len() {
#[allow(clippy::indexing_slicing)]
let new_locales = locales[old_len..]
.iter()
.flat_map(|locale| {
self.rulebook.find_fallback_locale(locale).chain(
self.rulebook
.find_fallback_locale_ref(locale)
.map(Clone::clone),
)
})
.filter(|l| !locale_hashes.contains(&h(l)))
.unique()
.collect_vec();
old_len = locales.len();
locales.extend_from_slice(&new_locales);
locale_hashes.extend(new_locales.iter().map(h));
}
locales.into_iter().unique().collect_vec()
}
}
pub trait PolyL10nRulebook<'s> {
fn find_fallback_locale(
&self,
_: &LanguageIdentifier,
) -> impl Iterator<Item = LanguageIdentifier> {
std::iter::empty()
}
fn find_fallback_locale_ref(
&'s self,
_: &LanguageIdentifier,
) -> impl Iterator<Item = &'s LanguageIdentifier> {
std::iter::empty()
}
}
impl<'s, M, LS: 's> PolyL10nRulebook<'s> for M
where
M: for<'a> std::ops::Index<&'a LanguageIdentifier, Output = LS>,
&'s LS: IntoIterator<Item = &'s LanguageIdentifier>,
{
fn find_fallback_locale_ref(
&'s self,
locale: &LanguageIdentifier,
) -> impl Iterator<Item = &'s LanguageIdentifier> {
(&self[locale]).into_iter()
}
}
pub type FnRules = Vec<Box<dyn Fn(&LanguageIdentifier) -> Vec<LanguageIdentifier>>>;
pub struct Rulebook<A = ()> {
pub rules: FnRules,
pub owned_values: A,
}
impl<A: std::fmt::Debug> std::fmt::Debug for Rulebook<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Rulebook")
.field("owned_values", &self.owned_values)
.field("rules", &PseudoFnRules::from(&self.rules))
.finish_non_exhaustive()
}
}
struct PseudoFnRules {
len: usize,
}
impl From<&FnRules> for PseudoFnRules {
fn from(value: &FnRules) -> Self {
Self { len: value.len() }
}
}
impl std::fmt::Debug for PseudoFnRules {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FnRules")
.field("len", &self.len)
.finish_non_exhaustive()
}
}
impl<A> PolyL10nRulebook<'_> for Rulebook<A> {
fn find_fallback_locale(
&self,
locale: &LanguageIdentifier,
) -> impl Iterator<Item = LanguageIdentifier> {
self.rules.iter().flat_map(|f| f(locale))
}
}
impl Rulebook<Rc<Vec<Rulebook>>> {
pub fn from_rulebooks<I: Iterator<Item = Rulebook>>(rulebooks: I) -> Self {
let mut new = Self {
owned_values: Rc::new(rulebooks.collect_vec()),
rules: vec![],
};
let owned_values = Rc::clone(&new.owned_values);
new.rules = vec![Box::new(move |l: &LanguageIdentifier| {
owned_values
.iter()
.flat_map(|rulebook| rulebook.find_fallback_locale(l).collect_vec())
.collect()
})];
new
}
}
impl<RR, R> Rulebook<(Rc<Vec<RR>>, std::marker::PhantomData<R>)>
where
RR: AsRef<Rulebook<R>> + 'static,
{
pub fn from_ref_rulebooks<I: Iterator<Item = RR>>(rulebooks: I) -> Self {
let mut new = Self {
owned_values: (Rc::new(rulebooks.collect_vec()), std::marker::PhantomData),
rules: vec![],
};
let owned_values = Rc::clone(&new.owned_values.0);
new.rules = vec![Box::new(move |l: &LanguageIdentifier| {
(owned_values.iter())
.flat_map(|rulebook| rulebook.as_ref().find_fallback_locale(l).collect_vec())
.collect()
})];
new
}
}
impl Rulebook {
#[must_use]
pub fn from_fn<F: Fn(&LanguageIdentifier) -> Vec<LanguageIdentifier> + 'static>(f: F) -> Self {
Self {
rules: vec![Box::new(f)],
owned_values: (),
}
}
#[must_use]
pub const fn from_fns(rules: FnRules) -> Self {
Self {
rules,
owned_values: (),
}
}
pub fn from_map<M, LS>(map: M) -> Self
where
M: for<'a> std::ops::Index<&'a LanguageIdentifier, Output = LS> + 'static,
for<'b> &'b LS: IntoIterator<Item = &'b LanguageIdentifier>,
{
Self::from_fn(move |l| map[l].into_iter().cloned().collect())
}
}
impl Default for Rulebook {
fn default() -> Self {
Self::from_fn(default_rulebook::default_rulebook)
}
}
pub type AFnRules = Vec<Box<dyn Fn(&LanguageIdentifier) -> Vec<LanguageIdentifier> + Send + Sync>>;
pub struct ARulebook<A = ()> {
pub rules: AFnRules,
pub owned_values: A,
}
impl<A: std::fmt::Debug> std::fmt::Debug for ARulebook<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ARulebook")
.field("owned_values", &self.owned_values)
.field("rules", &APseudoFnRules::from(&self.rules))
.finish_non_exhaustive()
}
}
struct APseudoFnRules {
len: usize,
}
impl From<&AFnRules> for APseudoFnRules {
fn from(value: &AFnRules) -> Self {
Self { len: value.len() }
}
}
impl std::fmt::Debug for APseudoFnRules {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AFnRules")
.field("len", &self.len)
.finish_non_exhaustive()
}
}
impl<A> PolyL10nRulebook<'_> for ARulebook<A> {
fn find_fallback_locale(
&self,
locale: &LanguageIdentifier,
) -> impl Iterator<Item = LanguageIdentifier> {
self.rules.iter().flat_map(|f| f(locale))
}
}
impl ARulebook<Arc<Vec<ARulebook>>> {
pub fn from_rulebooks<I: Iterator<Item = ARulebook>>(rulebooks: I) -> Self {
let mut new = Self {
owned_values: Arc::new(rulebooks.collect_vec()),
rules: vec![],
};
let owned_values = Arc::clone(&new.owned_values);
new.rules = vec![Box::new(move |l: &LanguageIdentifier| {
owned_values
.iter()
.flat_map(|rulebook| rulebook.find_fallback_locale(l).collect_vec())
.collect()
})];
new
}
}
impl<RR, R> ARulebook<(Arc<Vec<RR>>, std::marker::PhantomData<R>)>
where
RR: AsRef<ARulebook<R>> + 'static + Send + Sync,
{
pub fn from_ref_rulebooks<I: Iterator<Item = RR>>(rulebooks: I) -> Self {
let mut new = Self {
owned_values: (Arc::new(rulebooks.collect_vec()), std::marker::PhantomData),
rules: vec![],
};
let owned_values = Arc::clone(&new.owned_values.0);
new.rules = vec![Box::new(move |l: &LanguageIdentifier| {
(owned_values.iter())
.flat_map(|rulebook| rulebook.as_ref().find_fallback_locale(l).collect_vec())
.collect()
})];
new
}
}
impl ARulebook {
#[must_use]
pub fn from_fn<
F: Fn(&LanguageIdentifier) -> Vec<LanguageIdentifier> + 'static + Send + Sync,
>(
f: F,
) -> Self {
Self {
rules: vec![Box::new(f)],
owned_values: (),
}
}
#[must_use]
pub const fn from_fns(rules: AFnRules) -> Self {
Self {
rules,
owned_values: (),
}
}
pub fn from_map<M, LS>(map: M) -> Self
where
M: for<'a> std::ops::Index<&'a LanguageIdentifier, Output = LS> + 'static + Send + Sync,
for<'b> &'b LS: IntoIterator<Item = &'b LanguageIdentifier>,
{
Self::from_fn(move |l| map[l].into_iter().cloned().collect())
}
}
impl Default for ARulebook {
fn default() -> Self {
Self::from_fn(default_rulebook::default_rulebook)
}
}