use rmcp::serde_json; use serde::Deserialize; use std::collections::HashMap; use std::sync::{LazyLock, OnceLock, RwLock}; #[derive(Debug, Deserialize)] struct Language { translations: HashMap, } static LANGUAGES: OnceLock> = OnceLock::new(); static LOCALE_SELECTION: LazyLock> = LazyLock::new(|| RwLock::new(Locale::DE)); #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Locale { DE, } #[macro_export] macro_rules! include_language { ($file:expr) => { serde_json::from_str::(include_str!($file)).unwrap() }; } fn load_languages() -> &'static HashMap { LANGUAGES .get_or_init(|| HashMap::from([(Locale::DE, include_language!("translations/de.json"))])) } /// /// # Examples /// /// ``` /// let formatted = format_dynamically("{...} is cool!".to_string(), /// vec!["Rust".to_string()]); /// assert_eq!(formatted, "Rust is cool!"); /// ``` fn format_dynamically(mut template: String, arguments: Vec>) -> String { let replaced_str = "{...}"; for arg in arguments { template = template.replacen(replaced_str, arg.into().as_str(), 1); } template } pub fn translate(template_name: String, arguments: Vec>) -> String { let locale: Locale = *LOCALE_SELECTION.read().unwrap(); let languages = load_languages(); let translation = languages .get(&locale) .unwrap() .translations .get(&template_name) .unwrap(); format_dynamically(translation.clone(), arguments) } #[macro_export] macro_rules! tlt { ($template_name:tt) => { translate($template_name.to_string(), Vec::::new()) }; ($template_name:tt, $($args:tt)*) => { translate($template_name.to_string(), vec![$($args)*]) }; } #[cfg(test)] mod tests { use super::*; #[test] fn test_dynamic_formatting() { let formatted = format_dynamically("{...} is cool!".to_string(), vec!["Rust".to_string()]); assert_eq!(formatted, "Rust is cool!"); let formatted = format_dynamically( "{...}, {...} and {...} are three consecutive numbers.".to_string(), vec!["1".to_string(), "2".to_string(), "3".to_string()], ); assert_eq!(formatted, "1, 2 and 3 are three consecutive numbers."); } }