cloudiful-bevy-localization 0.1.2

Reusable Bevy localization runtime built around app-provided static registries.
Documentation
use crate::definition_registry::active_definition;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Locale(&'static str);

impl Default for Locale {
    fn default() -> Self {
        active_definition().fallback_locale()
    }
}

impl Locale {
    pub const fn new(id: &'static str) -> Self {
        Self(id)
    }

    pub const fn id(self) -> &'static str {
        self.0
    }

    pub fn available() -> Vec<Self> {
        active_definition().locales()
    }

    pub fn from_serialized(raw: &str) -> Option<Self> {
        let normalized = canonical_locale_id(raw);

        Self::available()
            .into_iter()
            .find(|locale| canonical_locale_id(locale.id()) == normalized)
    }
}

impl Serialize for Locale {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(self.id())
    }
}

impl<'de> Deserialize<'de> for Locale {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let raw = String::deserialize(deserializer)?;
        Locale::from_serialized(&raw)
            .ok_or_else(|| D::Error::custom(format!("unknown locale: {raw}")))
    }
}

fn canonical_locale_id(raw: &str) -> String {
    raw.chars()
        .filter(|ch| ch.is_ascii_alphanumeric())
        .map(|ch| ch.to_ascii_lowercase())
        .collect()
}