use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "LocalizedWire", into = "LocalizedWire")]
pub enum Localized {
Plain(String),
Translated(BTreeMap<String, String>),
}
impl Localized {
pub fn languages(&self) -> impl Iterator<Item = &str> {
let map = match self {
Localized::Plain(_) => None,
Localized::Translated(map) => Some(map.keys().map(String::as_str)),
};
map.into_iter().flatten()
}
}
impl From<&str> for Localized {
fn from(text: &str) -> Self {
Localized::Plain(text.to_owned())
}
}
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub(crate) enum LocalizedWire {
Plain(String),
Translated(BTreeMap<String, String>),
}
impl TryFrom<LocalizedWire> for Localized {
type Error = String;
fn try_from(raw: LocalizedWire) -> Result<Self, Self::Error> {
match raw {
LocalizedWire::Plain(text) => Ok(Localized::Plain(text)),
LocalizedWire::Translated(map) => {
if map.is_empty() {
return Err("a translated text names no language".to_owned());
}
if let Some(bad) = map.keys().find(|tag| !is_language_tag(tag)) {
return Err(format!("`{bad}` is not a language tag"));
}
Ok(Localized::Translated(map))
}
}
}
}
impl From<Localized> for LocalizedWire {
fn from(text: Localized) -> Self {
match text {
Localized::Plain(text) => LocalizedWire::Plain(text),
Localized::Translated(map) => LocalizedWire::Translated(map),
}
}
}
pub fn is_language_tag(tag: &str) -> bool {
let mut parts = tag.split('-');
let language = parts.next().unwrap_or_default();
(2..=3).contains(&language.len())
&& language.bytes().all(|b| b.is_ascii_alphabetic())
&& parts.all(|part| {
(1..=8).contains(&part.len()) && part.bytes().all(|b| b.is_ascii_alphanumeric())
})
}