langcodec_cli/
merge.rs

1use crate::formats::parse_custom_format;
2use crate::transformers::custom_format_to_resource;
3use indicatif::{ProgressBar, ProgressStyle};
4use langcodec::Codec;
5
6/// Strategy for handling conflicts when merging localization files.
7#[derive(Debug, Clone, PartialEq, clap::ValueEnum)]
8pub enum ConflictStrategy {
9    /// Keep the first occurrence of a key
10    First,
11    /// Keep the last occurrence of a key (default)
12    Last,
13    /// Skip conflicting entries
14    Skip,
15}
16
17/// Run the merge command: merge multiple localization files into one output file.
18pub fn run_merge_command(
19    inputs: Vec<String>,
20    output: String,
21    strategy: ConflictStrategy,
22    lang: Option<String>,
23) {
24    if inputs.is_empty() {
25        eprintln!("Error: At least one input file is required.");
26        std::process::exit(1);
27    }
28
29    // Create progress bar
30    let progress_bar = ProgressBar::new_spinner();
31    progress_bar.set_style(
32        ProgressStyle::default_spinner()
33            .template("{spinner:.green} {wide_msg}")
34            .unwrap(),
35    );
36
37    // Read all input files into a single codec
38    let mut codec = Codec::new();
39    for (i, input) in inputs.iter().enumerate() {
40        progress_bar.set_message(format!(
41            "Reading file {}/{}: {}",
42            i + 1,
43            inputs.len(),
44            input
45        ));
46
47        // Try standard format first
48        if let Ok(()) = codec.read_file_by_extension(input, lang.clone()) {
49            continue;
50        }
51
52        // If standard format fails, try custom format for JSON/YAML files
53        if input.ends_with(".json") || input.ends_with(".yaml") || input.ends_with(".yml") {
54            if let Ok(()) = try_custom_format_merge(input, lang.clone(), &mut codec) {
55                continue;
56            }
57        }
58
59        // If both fail, show error
60        progress_bar.finish_with_message("❌ Error reading input file");
61        eprintln!("Error reading {}: unsupported format", input);
62        std::process::exit(1);
63    }
64
65    // Skip validation for merge operations since we expect multiple resources with potentially duplicate languages
66
67    // Merge resources using the new lib crate method
68    progress_bar.set_message("Merging resources...");
69    let conflict_strategy = match strategy {
70        ConflictStrategy::First => langcodec::types::ConflictStrategy::First,
71        ConflictStrategy::Last => langcodec::types::ConflictStrategy::Last,
72        ConflictStrategy::Skip => langcodec::types::ConflictStrategy::Skip,
73    };
74
75    let merged_resource = match Codec::merge_resources(&codec.resources, conflict_strategy) {
76        Ok(resource) => resource,
77        Err(e) => {
78            progress_bar.finish_with_message("❌ Error merging resources");
79            eprintln!("Error: {}", e);
80            std::process::exit(1);
81        }
82    };
83
84    // Write merged resource to output file using the new lib crate method
85    progress_bar.set_message("Writing merged output...");
86    if let Err(e) = Codec::write_resource_to_file(&merged_resource, &output) {
87        progress_bar.finish_with_message("❌ Error writing output file");
88        eprintln!("Error writing to {}: {}", output, e);
89        std::process::exit(1);
90    }
91
92    progress_bar.finish_with_message(format!(
93        "✅ Successfully merged {} files into {}",
94        inputs.len(),
95        output
96    ));
97}
98
99/// Try to read a custom format file and add it to the codec
100fn try_custom_format_merge(
101    input: &str,
102    _lang: Option<String>,
103    codec: &mut Codec,
104) -> Result<(), String> {
105    // Validate custom format file
106    crate::validation::validate_custom_format_file(input)?;
107
108    // Auto-detect format based on file content
109    let file_content = std::fs::read_to_string(input)
110        .map_err(|e| format!("Error reading file {}: {}", input, e))?;
111
112    // Validate file content
113    crate::formats::validate_custom_format_content(input, &file_content)?;
114
115    // Convert custom format to Resource
116    let resources =
117        custom_format_to_resource(input.to_string(), parse_custom_format("json-language-map")?)?;
118
119    // Add resources to codec
120    for resource in resources {
121        codec.add_resource(resource);
122    }
123
124    Ok(())
125}