localization-macros 0.1.4

t! macro, the easiest way.
Documentation
use serde_json::{Map, Value};
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs::read_dir as std_read_dir;
use std::io::Result as IoResult;
use std::path::PathBuf;
use std::sync::LazyLock;

fn read_dir_dirs(path: &str) -> IoResult<Vec<OsString>> {
    let mut dirs = vec![];
    for entry in std_read_dir(path)? {
        let entry = entry?;
        if entry.file_type()?.is_dir() {
            dirs.push(entry.file_name());
        }
    }
    Ok(dirs)
}

fn read_dir_get_all_files(path: PathBuf) -> IoResult<Vec<String>> {
    let mut files = vec![];
    for entry in std_read_dir(path)? {
        let entry = entry?;
        let mime = entry.file_type()?;
        if mime.is_file() {
            files.push(entry.file_name().to_string_lossy().to_string());
        }
        if mime.is_dir() {
            let sub_files = read_dir_get_all_files(entry.path())?;
            files.append(
                &mut sub_files
                    .iter()
                    .map(|s| format!("{}/{}", entry.file_name().to_string_lossy(), s))
                    .collect(),
            );
        }
    }
    Ok(files)
}

fn json_kv_obj(m: &Map<String, Value>) -> Vec<(String, String)> {
    let mut vec = vec![];
    for (key, value) in m {
        match value {
            Value::String(value) => vec.push((key.clone(), value.clone())),
            Value::Object(m) => {
                let sub_vec = json_kv_obj(m);
                for (sub_key, sub_value) in sub_vec {
                    vec.push((format!("{}.{}", key, sub_key), sub_value));
                }
            }
            _ => {}
        }
    }
    vec
}

fn json_kv(s: String) -> Vec<(String, String)> {
    let json: Value = serde_json::from_str(&s).unwrap();
    json.as_object().map(json_kv_obj).unwrap_or(vec![])
}

type H = HashMap<(String, String), String>;

fn dir_to_json(path: PathBuf) -> H {
    let mut hash = H::new();
    let all_files = read_dir_get_all_files(path.clone()).unwrap();
    let path = path.to_string_lossy().to_string();
    for file_name in all_files {
        let file_content = std::fs::read_to_string(format!("{}/{}", path, file_name)).unwrap();
        let file_name = file_name
            .chars()
            .rev()
            .skip_while(|c| *c != '.')
            .skip(1)
            .collect::<String>()
            .chars()
            .rev()
            .collect::<String>();
        let file = json_kv(file_content.clone());
        for (key, value) in file {
            hash.insert((file_name.clone(), key.clone()), value);
        }
    }
    hash
}

type LocaleKV = HashMap<String, HashMap<String, String>>;
static LOCALE: LazyLock<LocaleKV> = LazyLock::new(init_locale);

fn root() -> String {
    std::env::var("LOCALIZATION_ROOT").unwrap_or("./translations".to_string())
}

pub fn default_locale() -> String {
    std::env::var("LOCALIZATION_DEFAULT").unwrap_or("en-US".to_string())
}

fn backhash(h: LocaleKV) -> LocaleKV {
    let mut new = LocaleKV::new();
    for (locale, map) in h {
        for (key, value) in map {
            let file_map = new.entry(key).or_default();
            file_map.insert(locale.clone(), value);
        }
    }
    new
}

fn init_locale() -> LocaleKV {
    let mut hash = LocaleKV::new();
    let locales = read_dir_dirs(&root()).unwrap();
    for locale in locales {
        let locale_path = locale.clone();
        let locale = locale.to_string_lossy().to_string();
        let ds = dir_to_json(PathBuf::from(root()).join(locale_path));
        let mut map = HashMap::new();
        for ((file_name, key), value) in ds {
            map.insert(format!("{}:{}", file_name, key), value);
        }
        hash.insert(locale, map);
    }
    backhash(hash)
}

pub fn get_locale() -> &'static LocaleKV {
    &LOCALE
}

pub fn get_locale_list() -> Vec<String> {
    let mut locs = vec![];
    for entry in get_locale().values() {
        for loc in entry.keys() {
            if !locs.contains(loc) {
                locs.push(loc.clone());
            }
        }
    }
    locs
}