add basic i18n
This commit is contained in:
@@ -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.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user