//! magic for custom translations and strings use std::collections::HashMap; #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct TranslationTable { #[serde(flatten)] entries: HashMap, } impl TranslationTable { #[allow(unused)] pub fn new() -> Self { Self::default() } /// Iterate all entries pub fn entries(&self) -> impl Iterator { self.entries.iter() } pub fn get_translation_raw(&self, key: &str) -> Option<&str> { self.entries.get(key).map(|s| s.as_str()) } /// Add or update a translation pub fn add_translation(&mut self, key: impl ToString, subs: impl ToString) { self.entries.insert(key.to_string(), subs.to_string()); } pub fn translation_exists(&self, key: &str) -> bool { self.entries.contains_key(key) } pub fn subs(&self, key: &str, substitutions: &[&str]) -> String { match self.entries.get(key) { Some(s) => { // TODO optimize let mut s = s.clone(); for pair in substitutions.chunks(2) { if pair.len() != 2 { continue; } s = s.replace(&format!("{{{}}}", pair[0]), pair[1]); } s } None => key.to_owned(), } } } #[cfg(test)] mod tests { use crate::tr::TranslationTable; #[test] fn deser_tr_table() { let tr: TranslationTable = serde_json::from_str(r#"{"foo":"bar"}"#).unwrap(); assert_eq!("bar", tr.subs("foo", &[])); assert_eq!("xxx", tr.subs("xxx", &[])); } #[test] fn subs() { let mut tr = TranslationTable::new(); tr.add_translation("hello_user", "Hello, {user}!"); assert_eq!("Hello, James!", tr.subs("hello_user", &["user", "James"])); } } #[macro_export] macro_rules! tr { ($tr_haver:expr, $key:literal) => { $tr_haver.tr().subs($key, &[]) }; ($tr_haver:expr, $key:literal, $($k:tt=$value:expr),*) => { $tr_haver.tr().subs($key, &[ $(stringify!($k), $value),* ]) }; }