embystream 0.0.26

Another Emby streaming application (frontend/backend separation) written in Rust.
Documentation
//! JSON-backed UI strings (`locales/en.json`, `locales/zh.json`).
//! Missing keys in `zh` fall back to `en`; if still missing, the key string is returned.

use std::cell::Cell;
use std::collections::HashMap;

use once_cell::sync::Lazy;
use serde_json::Value;

use crate::cli::UiLang;

static EN_MAP: Lazy<HashMap<String, String>> = Lazy::new(load_en);
static ZH_MAP: Lazy<HashMap<String, String>> = Lazy::new(load_zh);

fn load_en() -> HashMap<String, String> {
    let v: Value = serde_json::from_str(include_str!("../locales/en.json"))
        .expect("locales/en.json must be valid JSON");
    json_object_to_map(v)
}

fn load_zh() -> HashMap<String, String> {
    let v: Value = serde_json::from_str(include_str!("../locales/zh.json"))
        .expect("locales/zh.json must be valid JSON");
    json_object_to_map(v)
}

fn json_object_to_map(v: Value) -> HashMap<String, String> {
    let o = v.as_object().expect("locale root must be a JSON object");
    o.iter()
        .map(|(k, v)| {
            let s = match v {
                Value::String(s) => s.clone(),
                _ => v.to_string(),
            };
            (k.clone(), s)
        })
        .collect()
}

fn get_map(lang: UiLang) -> &'static HashMap<String, String> {
    match lang {
        UiLang::En => &EN_MAP,
        UiLang::Zh => &ZH_MAP,
    }
}

/// Resolve `key` for an explicit language (e.g. `--help` localization in `cli_lang`).
pub fn lookup(lang: UiLang, key: &str) -> String {
    if let Some(s) = get_map(lang).get(key) {
        return s.clone();
    }
    if lang == UiLang::Zh {
        if let Some(s) = EN_MAP.get(key) {
            return s.clone();
        }
    }
    key.to_string()
}

/// Replace `{name}` placeholders in a looked-up string.
pub fn lookup_fmt(lang: UiLang, key: &str, pairs: &[(&str, &str)]) -> String {
    let mut s = lookup(lang, key);
    for (k, v) in pairs {
        s = s.replace(&format!("{{{k}}}"), v);
    }
    s
}

thread_local! {
    static UI_LANG: Cell<UiLang> = const { Cell::new(UiLang::En) };
}

/// Thread-local UI language for the config wizard (`cli_wizard::run` sets this).
pub fn set_ui_lang(lang: UiLang) {
    UI_LANG.with(|c| c.set(lang));
}

pub fn ui_lang() -> UiLang {
    UI_LANG.with(|c| c.get())
}

/// Wizard strings using the thread-local language from [`set_ui_lang`].
pub fn tr(key: &str) -> String {
    lookup(ui_lang(), key)
}

pub fn tr_fmt(key: &str, pairs: &[(&str, &str)]) -> String {
    lookup_fmt(ui_lang(), key, pairs)
}