langcodec_cli/
merge.rs

1use crate::formats::parse_custom_format;
2use crate::transformers::custom_format_to_resource;
3
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    // 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 merged_resource = match Codec::merge_resources(&codec.resources, conflict_strategy) {
63        Ok(resource) => resource,
64        Err(e) => {
65            println!("❌ Error merging resources");
66            eprintln!("Error: {}", e);
67            std::process::exit(1);
68        }
69    };
70
71    // Write merged resource to output file using the new lib crate method
72    println!("Writing merged output...");
73    if let Err(e) = Codec::write_resource_to_file(&merged_resource, &output) {
74        println!("❌ Error writing output file");
75        eprintln!("Error writing to {}: {}", output, e);
76        std::process::exit(1);
77    }
78
79    println!(
80        "✅ Successfully merged {} files into {}",
81        inputs.len(),
82        output
83    );
84}
85
86/// Try to read a custom format file and add it to the codec
87fn try_custom_format_merge(
88    input: &str,
89    _lang: Option<String>,
90    codec: &mut Codec,
91) -> Result<(), String> {
92    // Validate custom format file
93    crate::validation::validate_custom_format_file(input)?;
94
95    // Auto-detect format based on file content
96    let file_content = std::fs::read_to_string(input)
97        .map_err(|e| format!("Error reading file {}: {}", input, e))?;
98
99    // Validate file content
100    crate::formats::validate_custom_format_content(input, &file_content)?;
101
102    // Convert custom format to Resource
103    let resources =
104        custom_format_to_resource(input.to_string(), parse_custom_format("json-language-map")?)?;
105
106    // Add resources to codec
107    for resource in resources {
108        codec.add_resource(resource);
109    }
110
111    Ok(())
112}