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.");
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"translations": {
"system_prompt": "Du bist ein Assistent, der per Sprache bedient wird. Du erhälst die Transkription und dein Output wird per Sprache ausgegeben und sollte dementsprechend auch kurz sein und kein Markdown enthalten. Wichtiger als deine Antworten sind deine Aktionen. Nutze bitte die tools, falls du sie brauchst um Informationen zu bekommen (z.B. über das aktuelle Datum oder den aktuellen Wochentag). Du bist in einem Agent Loop und kannst mehrere Tools hintereinander nutzen. Falls der Nutzer das Nutzen eines Tools ablehnt, sag ihm bescheid, dass du es brauchst."
}
}