roblox-slang 2.0.4

Type-safe internationalization for Roblox experiences
Documentation
use anyhow::{Context, Result};
use colored::Colorize;
use std::path::Path;

use crate::{config, generator, parser};

/// Build translations from source files
pub fn build(config_path: &Path) -> Result<()> {
    println!("{} Building translations...", "".blue());

    // Load config
    let config = config::load_config(config_path).context("Failed to load config")?;

    println!(
        "{} Loaded config from {}",
        "".green(),
        config_path.display()
    );

    // Parse translations for each locale
    let mut all_translations = Vec::new();
    let mut total_keys = 0;

    for locale in &config.supported_locales {
        // Try JSON first
        let json_path = Path::new(&config.input_directory).join(format!("{}.json", locale));

        // Try YAML if JSON doesn't exist
        let yaml_path = Path::new(&config.input_directory).join(format!("{}.yaml", locale));

        let yml_path = Path::new(&config.input_directory).join(format!("{}.yml", locale));

        let translations = if json_path.exists() {
            parser::parse_json_file(&json_path, locale)
                .context(format!("Failed to parse JSON for {}", locale))?
        } else if yaml_path.exists() {
            parser::parse_yaml_file(&yaml_path, locale)
                .context(format!("Failed to parse YAML for {}", locale))?
        } else if yml_path.exists() {
            parser::parse_yaml_file(&yml_path, locale)
                .context(format!("Failed to parse YAML for {}", locale))?
        } else {
            log::warn!("Translation file not found for locale: {}", locale);
            println!(
                "{} Translation file not found for locale: {} (tried .json, .yaml, .yml)",
                "".yellow(),
                locale
            );
            continue;
        };

        let key_count = translations.len();
        total_keys += key_count;
        all_translations.extend(translations);

        println!(
            "{} Parsed {} ({} keys)",
            "".green(),
            locale.cyan(),
            key_count
        );
    }

    if all_translations.is_empty() {
        println!("{} No translations found", "".yellow());
        return Ok(());
    }

    // Parse and merge overrides if enabled
    if let Some(override_config) = &config.overrides {
        if override_config.enabled {
            let override_path = Path::new(&override_config.file);

            if override_path.exists() {
                let overrides =
                    parser::parse_overrides(override_path).context("Failed to parse overrides")?;

                if !overrides.is_empty() {
                    println!(
                        "{} Loaded {} overrides from {}",
                        "".green(),
                        overrides.len(),
                        override_path.display()
                    );

                    all_translations = parser::merge_translations(all_translations, overrides);
                }
            } else {
                log::warn!("Override file not found: {}", override_path.display());
            }
        }
    }

    // Create output directory
    let output_dir = Path::new(&config.output_directory);
    std::fs::create_dir_all(output_dir).context("Failed to create output directory")?;

    // Generate Luau code with analytics and localization config
    let luau_code = generator::generate_luau_with_full_config(
        &all_translations,
        &config.base_locale,
        config.analytics.as_ref(),
        config.localization.as_ref(),
    )
    .context("Failed to generate Luau code")?;

    let output_file = output_dir.join("Translations.lua");
    std::fs::write(&output_file, luau_code).context("Failed to write Luau file")?;

    println!("{} Generated {}", "".green(), output_file.display());

    // Generate type definitions
    let types_dir = output_dir.join("types");
    std::fs::create_dir_all(&types_dir).context("Failed to create types directory")?;

    let type_defs = generator::generate_type_definitions(&all_translations, &config.base_locale)
        .context("Failed to generate type definitions")?;

    let types_file = types_dir.join("Translations.d.luau");
    std::fs::write(&types_file, type_defs).context("Failed to write type definitions")?;

    println!("{} Generated {}", "".green(), types_file.display());

    // Generate CSV for Roblox Cloud
    let csv_content = generator::generate_csv(
        &all_translations,
        &config.base_locale,
        &config.supported_locales,
    )
    .context("Failed to generate CSV")?;

    let csv_file = output_dir.join("roblox_upload.csv");
    std::fs::write(&csv_file, csv_content).context("Failed to write CSV file")?;

    println!("{} Generated {}", "".green(), csv_file.display());

    println!();
    println!("{} Build completed successfully!", "".green().bold());
    println!("  Total keys: {}", total_keys);
    println!("  Locales: {}", config.supported_locales.join(", "));
    println!();
    println!("Generated files:");
    println!("{} - Main translation module", output_file.display());
    println!(
        "{} - Type definitions for autocomplete",
        types_file.display()
    );
    println!("{} - CSV for Roblox Cloud upload", csv_file.display());

    Ok(())
}