roblox-slang 2.0.4

Type-safe internationalization for Roblox experiences
Documentation
use crate::config;
use crate::roblox::{AuthConfig, MergeStrategy, RobloxCloudClient, SyncOrchestrator};
use anyhow::{bail, Context, Result};
use colored::Colorize;
use std::path::Path;

/// Synchronize translations between local and cloud
pub async fn sync(table_id: Option<String>, strategy: Option<String>, dry_run: bool) -> Result<()> {
    // Load configuration
    let config_path = Path::new("slang-roblox.yaml");
    let config = config::load_config(config_path).context("Failed to load configuration")?;

    // Get table_id from CLI or config
    let table_id = table_id
        .or_else(|| config.cloud.as_ref().and_then(|c| c.table_id.clone()))
        .context("Table ID not provided. Specify via --table-id or set cloud.table_id in config")?;

    // Get merge strategy from CLI or config
    let strategy_str = strategy
        .or_else(|| config.cloud.as_ref().and_then(|c| c.strategy.clone()))
        .unwrap_or_else(|| "merge".to_string());

    let merge_strategy = match strategy_str.to_lowercase().as_str() {
        "overwrite" => MergeStrategy::Overwrite,
        "merge" => MergeStrategy::Merge,
        "skip-conflicts" | "skip_conflicts" => MergeStrategy::SkipConflicts,
        _ => {
            bail!(
                "Invalid merge strategy: '{}'\n\
                 \n\
                 Valid strategies:\n\
                 - overwrite: Replace all cloud translations with local\n\
                 - merge: Merge local and cloud, prefer cloud for conflicts\n\
                 - skip-conflicts: Only sync non-conflicting entries",
                strategy_str
            );
        }
    };

    // Load authentication
    let auth = AuthConfig::load(&config).context("Failed to load authentication")?;

    // Create client
    let client =
        RobloxCloudClient::new(auth.api_key).context("Failed to create Roblox Cloud client")?;

    // Create orchestrator
    let orchestrator = SyncOrchestrator::new(client, config.clone());

    // Sync
    if dry_run {
        println!("{} Dry-run mode: No changes will be made", "".cyan());
    }

    println!(
        "{} Synchronizing translations (strategy: {})...",
        "".blue(),
        strategy_str
    );
    println!("  Table ID: {}", table_id);
    println!("  Merge strategy: {}", strategy_str);

    let stats = orchestrator
        .sync(&table_id, merge_strategy, dry_run)
        .await
        .context("Sync failed")?;

    // Display statistics
    println!("\n{} Sync complete!", "".green().bold());
    println!("  Entries added: {}", stats.entries_added);
    println!("  Entries updated: {}", stats.entries_updated);
    println!("  Entries deleted: {}", stats.entries_deleted);

    if stats.conflicts_skipped > 0 {
        println!(
            "  {} Conflicts skipped: {}",
            "".yellow(),
            stats.conflicts_skipped
        );

        let conflicts_path = Path::new(&config.output_directory).join("conflicts.yaml");
        println!(
            "\n{} Conflicts saved to: {}",
            "".cyan(),
            conflicts_path.display()
        );
        println!("  Review and resolve conflicts manually");
    }

    println!("  Duration: {:.2}s", stats.duration.as_secs_f64());

    if dry_run {
        println!("\n{} This was a dry-run. No changes were made.", "".cyan());
    } else {
        println!("\n{} Translations successfully synchronized", "".green());
    }

    Ok(())
}