hippox 0.1.0

The most reliable AI agent.🦛
Documentation
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::sync::RwLock;

#[derive(Debug, Deserialize)]
struct Translations {
    app: HashMap<String, String>,
    skill: HashMap<String, String>,
    error: HashMap<String, String>,
    prompt: HashMap<String, String>,
}

static CURRENT_LANG: Lazy<RwLock<String>> = Lazy::new(|| RwLock::new("en".to_string()));
static TRANSLATIONS: Lazy<RwLock<HashMap<String, Translations>>> = Lazy::new(|| RwLock::new(HashMap::new()));

pub fn init() {
    load_translations();
    let lang = std::env::var("HIPPO_LANG").unwrap_or_else(|_| "en".to_string());
    set_language(&lang);
}

fn load_translations() {
    let mut translations = HashMap::new();
    for lang in &["en", "zh"] {
        let path = format!("i18n/{}.toml", lang);
        if let Ok(content) = fs::read_to_string(&path) {
            if let Ok(trans) = toml::from_str(&content) {
                translations.insert(lang.to_string(), trans);
            }
        }
    }
    let mut store = TRANSLATIONS.write().unwrap();
    *store = translations;
}

pub fn set_language(lang: &str) {
    let mut current = CURRENT_LANG.write().unwrap();
    *current = lang.to_string();
}

pub fn t(key: &str) -> String {
    let lang = get_language();
    let translations = TRANSLATIONS.read().unwrap();
    if let Some(trans) = translations.get(&lang) {
        if let Some(value) = get_value(trans, key) {
            return value;
        }
    }
    if let Some(trans) = translations.get("en") {
        if let Some(value) = get_value(trans, key) {
            return value;
        }
    }
    key.to_string()
}

pub fn t_with_args(key: &str, args: &[String]) -> String {
    let mut result = t(key);
    for (i, arg) in args.iter().enumerate() {
        result = result.replace(&format!("{{{}}}", i), arg);
    }
    result
}

fn get_value(trans: &Translations, key: &str) -> Option<String> {
    let parts: Vec<&str> = key.split('.').collect();
    if parts.len() != 2 {
        return None;
    }
    match parts[0] {
        "app" => trans.app.get(parts[1]).cloned(),
        "skill" => trans.skill.get(parts[1]).cloned(),
        "error" => trans.error.get(parts[1]).cloned(),
        "prompt" => trans.prompt.get(parts[1]).cloned(),
        _ => None,
    }
}

fn get_language() -> String {
    CURRENT_LANG.read().unwrap().clone()
}

#[macro_export]
macro_rules! t {
    ($key:expr) => {
        $crate::i18n::t($key)
    };
    ($key:expr, $($arg:expr),*) => {{
        let args = vec![$($arg.to_string()),*];
        $crate::i18n::t_with_args($key, &args)
    }};
}