roblox-slang 3.0.1

Type-safe internationalization for Roblox experiences
Documentation
use super::KeyTransform;
use crate::utils::flatten::unflatten_to_json;
use anyhow::{Context, Result};
use serde_json::Value;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
pub fn migrate_custom_json(
    input_path: &Path,
    output_path: &Path,
    transform: KeyTransform,
) -> Result<()> {
    let content = fs::read_to_string(input_path)
        .context(format!("Failed to read {}", input_path.display()))?;

    let json: Value = serde_json::from_str(&content).context("Failed to parse JSON")?;
    let flat_translations = extract_translations(&json)?;
    let transformed: HashMap<String, String> = flat_translations
        .into_iter()
        .map(|(k, v)| {
            let new_key = super::key_transform::transform_key(&k, transform);
            (new_key, v)
        })
        .collect();
    let nested = unflatten_to_json(&transformed);
    fs::create_dir_all(output_path.parent().unwrap())?;
    let output_json = serde_json::to_string_pretty(&nested)?;
    fs::write(output_path, output_json)?;

    Ok(())
}
fn extract_translations(json: &Value) -> Result<HashMap<String, String>> {
    let mut translations = HashMap::new();
    if let Some(trans_obj) = json.get("translations") {
        if let Some(locale_obj) = trans_obj.as_object() {
            if let Some((_, locale_data)) = locale_obj.iter().next() {
                extract_flat(locale_data, String::new(), &mut translations);
            }
        }
    } else if let Some(locales_obj) = json.get("locales") {
        if let Some(locale_obj) = locales_obj.as_object() {
            if let Some((_, locale_data)) = locale_obj.iter().next() {
                extract_flat(locale_data, String::new(), &mut translations);
            }
        }
    } else if let Some(strings_obj) = json.get("strings") {
        extract_flat(strings_obj, String::new(), &mut translations);
    } else {
        extract_flat(json, String::new(), &mut translations);
    }

    Ok(translations)
}
fn extract_flat(value: &Value, prefix: String, result: &mut HashMap<String, String>) {
    match value {
        Value::Object(map) => {
            for (key, val) in map {
                let new_prefix = if prefix.is_empty() {
                    key.clone()
                } else {
                    format!("{}.{}", prefix, key)
                };
                extract_flat(val, new_prefix, result);
            }
        }
        Value::String(s) => {
            result.insert(prefix, s.clone());
        }
        _ => {}
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::Value;
    use tempfile::TempDir;

    #[test]
    fn test_extract_translations_pattern1() {
        let json = serde_json::json!({
            "translations": {
                "en": {
                    "ui": {
                        "button": "Buy"
                    }
                }
            }
        });

        let result = extract_translations(&json).unwrap();
        assert_eq!(result.get("ui.button"), Some(&"Buy".to_string()));
    }

    #[test]
    fn test_extract_translations_pattern2() {
        let json = serde_json::json!({
            "locales": {
                "en": {
                    "ui": {
                        "button": "Buy"
                    }
                }
            }
        });

        let result = extract_translations(&json).unwrap();
        assert_eq!(result.get("ui.button"), Some(&"Buy".to_string()));
    }

    #[test]
    fn test_extract_translations_pattern3() {
        let json = serde_json::json!({
            "strings": {
                "ui": {
                    "button": "Buy"
                }
            }
        });

        let result = extract_translations(&json).unwrap();
        assert_eq!(result.get("ui.button"), Some(&"Buy".to_string()));
    }

    #[test]
    fn test_extract_translations_direct() {
        let json = serde_json::json!({
            "ui": {
                "button": "Buy"
            }
        });

        let result = extract_translations(&json).unwrap();
        assert_eq!(result.get("ui.button"), Some(&"Buy".to_string()));
    }

    #[test]
    fn test_migrate_custom_json() {
        let temp_dir = TempDir::new().unwrap();
        let input_path = temp_dir.path().join("input.json");
        let output_path = temp_dir.path().join("output.json");
        let input_json = serde_json::json!({
            "translations": {
                "en": {
                    "ui_button": "Buy"
                }
            }
        });
        fs::write(&input_path, serde_json::to_string(&input_json).unwrap()).unwrap();
        migrate_custom_json(&input_path, &output_path, KeyTransform::SnakeToCamel).unwrap();
        let output_content = fs::read_to_string(&output_path).unwrap();
        let output_json: Value = serde_json::from_str(&output_content).unwrap();

        assert_eq!(output_json["uiButton"], "Buy");
    }
}