use serde::Deserialize;
use serde::de::{self, Deserializer, MapAccess, Visitor};
use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LocalizedField {
Plain(String),
Multilingual(BTreeMap<String, String>),
}
impl LocalizedField {
pub fn resolve(&self, preferred_language: &str) -> String {
match self {
LocalizedField::Plain(s) => s.clone(),
LocalizedField::Multilingual(map) => map
.get(preferred_language)
.filter(|s| !s.is_empty())
.or_else(|| {
if preferred_language != "en" {
map.get("en").filter(|s| !s.is_empty())
} else {
None
}
})
.or_else(|| map.values().find(|s| !s.is_empty()))
.cloned()
.unwrap_or_default(),
}
}
}
impl<'de> Deserialize<'de> for LocalizedField {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct LocalizedFieldVisitor;
impl<'de> Visitor<'de> for LocalizedFieldVisitor {
type Value = LocalizedField;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a string or a map of language codes to strings")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<LocalizedField, E> {
Ok(LocalizedField::Plain(value.to_string()))
}
fn visit_string<E: de::Error>(self, value: String) -> Result<LocalizedField, E> {
Ok(LocalizedField::Plain(value))
}
fn visit_map<M>(self, map: M) -> Result<LocalizedField, M::Error>
where
M: MapAccess<'de>,
{
let translations: BTreeMap<String, String> =
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))?;
Ok(LocalizedField::Multilingual(translations))
}
}
deserializer.deserialize_any(LocalizedFieldVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resolve_plain_ignores_language() {
let field = LocalizedField::Plain("Hello".to_string());
assert_eq!(field.resolve("en"), "Hello");
assert_eq!(field.resolve("de"), "Hello");
assert_eq!(field.resolve("fr"), "Hello");
}
#[test]
fn test_resolve_multilingual_preferred_language() {
let mut map = BTreeMap::new();
map.insert("en".to_string(), "English".to_string());
map.insert("de".to_string(), "Deutsch".to_string());
map.insert("fr".to_string(), "Francais".to_string());
let field = LocalizedField::Multilingual(map);
assert_eq!(field.resolve("de"), "Deutsch");
assert_eq!(field.resolve("fr"), "Francais");
assert_eq!(field.resolve("en"), "English");
}
#[test]
fn test_resolve_multilingual_falls_back_to_en() {
let mut map = BTreeMap::new();
map.insert("en".to_string(), "English".to_string());
map.insert("de".to_string(), "Deutsch".to_string());
let field = LocalizedField::Multilingual(map);
assert_eq!(field.resolve("it"), "English");
}
#[test]
fn test_resolve_multilingual_falls_back_to_first_available() {
let mut map = BTreeMap::new();
map.insert("de".to_string(), "Deutsch".to_string());
let field = LocalizedField::Multilingual(map);
assert_eq!(field.resolve("fr"), "Deutsch");
}
#[test]
fn test_resolve_multilingual_empty_map() {
let field = LocalizedField::Multilingual(BTreeMap::new());
assert_eq!(field.resolve("en"), "");
}
#[test]
fn test_resolve_multilingual_skips_empty_preferred() {
let mut map = BTreeMap::new();
map.insert("en".to_string(), "".to_string());
map.insert("de".to_string(), "Deutsch".to_string());
map.insert("fr".to_string(), "".to_string());
let field = LocalizedField::Multilingual(map);
assert_eq!(field.resolve("en"), "Deutsch");
assert_eq!(field.resolve("fr"), "Deutsch");
}
#[test]
fn test_deserialize_plain_string() {
let field: LocalizedField = serde_json::from_str(r#""My Dataset""#).unwrap();
assert_eq!(field, LocalizedField::Plain("My Dataset".to_string()));
}
#[test]
fn test_deserialize_multilingual_object() {
let field: LocalizedField =
serde_json::from_str(r#"{"en": "English Title", "de": "Deutscher Titel"}"#).unwrap();
match &field {
LocalizedField::Multilingual(map) => {
assert_eq!(map.get("en").unwrap(), "English Title");
assert_eq!(map.get("de").unwrap(), "Deutscher Titel");
}
_ => panic!("Expected Multilingual variant"),
}
}
#[test]
fn test_deserialize_option_null() {
let field: Option<LocalizedField> = serde_json::from_str("null").unwrap();
assert!(field.is_none());
}
#[test]
fn test_deserialize_option_plain() {
let field: Option<LocalizedField> = serde_json::from_str(r#""description""#).unwrap();
assert_eq!(
field,
Some(LocalizedField::Plain("description".to_string()))
);
}
#[test]
fn test_deserialize_option_multilingual() {
let field: Option<LocalizedField> =
serde_json::from_str(r#"{"en": "English", "fr": "Francais"}"#).unwrap();
assert!(matches!(field, Some(LocalizedField::Multilingual(_))));
}
}