langcodec_cli/
merge.rs

1use crate::formats::parse_custom_format;
2use crate::transformers::custom_format_to_resource;
3
4use langcodec::{Codec, converter};
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    // Read all input files into a single codec
30    let mut codec = Codec::new();
31    for (i, input) in inputs.iter().enumerate() {
32        println!("Reading file {}/{}: {}", i + 1, inputs.len(), input);
33
34        // Try standard format first
35        if let Ok(()) = codec.read_file_by_extension(input, lang.clone()) {
36            continue;
37        }
38
39        // If standard format fails, try custom format for JSON/YAML files
40        if input.ends_with(".json") || input.ends_with(".yaml") || input.ends_with(".yml") {
41            if let Ok(()) = try_custom_format_merge(input, lang.clone(), &mut codec) {
42                continue;
43            }
44        }
45
46        // If both fail, show error
47        println!("❌ Error reading input file");
48        eprintln!("Error reading {}: unsupported format", input);
49        std::process::exit(1);
50    }
51
52    // Skip validation for merge operations since we expect multiple resources with potentially duplicate languages
53
54    // Merge resources using the new lib crate method
55    println!("Merging resources...");
56    let conflict_strategy = match strategy {
57        ConflictStrategy::First => langcodec::types::ConflictStrategy::First,
58        ConflictStrategy::Last => langcodec::types::ConflictStrategy::Last,
59        ConflictStrategy::Skip => langcodec::types::ConflictStrategy::Skip,
60    };
61
62    let merge_count = codec.merge_resources(&conflict_strategy);
63    println!("Merged {} language groups", merge_count);
64
65    println!("Writing merged output...");
66    match converter::infer_format_from_path(output.clone()) {
67        Some(format) => {
68            println!("Converting resources to format: {:?}", format);
69            if let Err(e) = converter::convert_resources_to_format(codec.resources, &output, format)
70            {
71                println!("❌ Error converting resources to format");
72                eprintln!("Error converting to {}: {}", output, e);
73                std::process::exit(1);
74            }
75        }
76        None => {
77            if codec.resources.len() == 1 {
78                println!("Writing single resource to output file");
79                if let Some(resource) = codec.resources.first() {
80                    if let Err(e) = Codec::write_resource_to_file(resource, &output) {
81                        println!("❌ Error writing output file");
82                        eprintln!("Error writing to {}: {}", output, e);
83                        std::process::exit(1);
84                    }
85                }
86            } else {
87                println!("❌ Error writing output file");
88                eprintln!("Error writing to {}: multiple resources", output);
89                std::process::exit(1);
90            }
91        }
92    }
93
94    println!(
95        "✅ Successfully merged {} files into {}",
96        inputs.len(),
97        output
98    );
99}
100
101/// Try to read a custom format file and add it to the codec
102fn try_custom_format_merge(
103    input: &str,
104    _lang: Option<String>,
105    codec: &mut Codec,
106) -> Result<(), String> {
107    // Validate custom format file
108    crate::validation::validate_custom_format_file(input)?;
109
110    // Auto-detect format based on file content
111    let file_content = std::fs::read_to_string(input)
112        .map_err(|e| format!("Error reading file {}: {}", input, e))?;
113
114    // Validate file content
115    crate::formats::validate_custom_format_content(input, &file_content)?;
116
117    // Convert custom format to Resource
118    let resources =
119        custom_format_to_resource(input.to_string(), parse_custom_format("json-language-map")?)?;
120
121    // Add resources to codec
122    for resource in resources {
123        codec.add_resource(resource);
124    }
125
126    Ok(())
127}