Skip to main content

cloudiful_bevy_localization/
locale.rs

1use crate::definition_registry::active_definition;
2use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub struct Locale(&'static str);
6
7impl Default for Locale {
8    fn default() -> Self {
9        active_definition().fallback_locale()
10    }
11}
12
13impl Locale {
14    pub const fn new(id: &'static str) -> Self {
15        Self(id)
16    }
17
18    pub const fn id(self) -> &'static str {
19        self.0
20    }
21
22    pub fn available() -> Vec<Self> {
23        active_definition().locales()
24    }
25
26    pub fn from_serialized(raw: &str) -> Option<Self> {
27        let normalized = canonical_locale_id(raw);
28
29        Self::available()
30            .into_iter()
31            .find(|locale| canonical_locale_id(locale.id()) == normalized)
32    }
33}
34
35impl Serialize for Locale {
36    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
37    where
38        S: Serializer,
39    {
40        serializer.serialize_str(self.id())
41    }
42}
43
44impl<'de> Deserialize<'de> for Locale {
45    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
46    where
47        D: Deserializer<'de>,
48    {
49        let raw = String::deserialize(deserializer)?;
50        Locale::from_serialized(&raw)
51            .ok_or_else(|| D::Error::custom(format!("unknown locale: {raw}")))
52    }
53}
54
55fn canonical_locale_id(raw: &str) -> String {
56    raw.chars()
57        .filter(|ch| ch.is_ascii_alphanumeric())
58        .map(|ch| ch.to_ascii_lowercase())
59        .collect()
60}