add basic i18n

This commit is contained in:
milan
2026-04-12 00:12:45 +02:00
parent 7958275438
commit 01e7da136c
8 changed files with 461 additions and 30 deletions
+87
View File
@@ -0,0 +1,87 @@
use rmcp::serde_json;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::{LazyLock, OnceLock, RwLock};
#[derive(Debug, Deserialize)]
struct Language {
translations: HashMap<String, String>,
}
static LANGUAGES: OnceLock<HashMap<Locale, Language>> = OnceLock::new();
static LOCALE_SELECTION: LazyLock<RwLock<Locale>> = 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::<Language>(include_str!($file)).unwrap()
};
}
fn load_languages() -> &'static HashMap<Locale, Language> {
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<impl Into<String>>) -> 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<impl Into<String>>) -> String {
let locale: Locale = LOCALE_SELECTION.read().unwrap().clone();
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::<String>::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.");
}
}