langcodec_cli/
formats.rs

1use std::str::FromStr;
2
3/// Custom format types that are not supported by the lib crate.
4/// These are one-way conversions only (to Resource format).
5#[derive(Debug, Clone, PartialEq, clap::ValueEnum)]
6#[allow(clippy::enum_variant_names)]
7pub enum CustomFormat {
8    /// A JSON file which contains a map of language codes to translations.
9    ///
10    /// The key is the localization code, and the value is the translation:
11    ///
12    /// ```json
13    /// {
14    ///     "key": "hello_world",
15    ///     "en": "Hello, World!",
16    ///     "fr": "Bonjour, le monde!"
17    /// }
18    /// ```
19    JSONLanguageMap,
20
21    /// A YAML file which contains a map of language codes to translations.
22    ///
23    /// The key is the localization code, and the value is the translation:
24    ///
25    /// ```yaml
26    /// key: hello_world
27    /// en: Hello, World!
28    /// fr: Bonjour, le monde!
29    /// ```
30    YAMLLanguageMap,
31
32    /// A JSON file which contains an array of language map objects.
33    ///
34    /// Each object contains a key and translations for different languages:
35    ///
36    /// ```json
37    /// [
38    ///     {
39    ///         "key": "hello_world",
40    ///         "en": "Hello, World!",
41    ///         "fr": "Bonjour, le monde!"
42    ///     },
43    ///     {
44    ///         "key": "welcome_message",
45    ///         "en": "Welcome to our app!",
46    ///         "fr": "Bienvenue dans notre application!"
47    ///     }
48    /// ]
49    /// ```
50    JSONArrayLanguageMap,
51}
52
53impl FromStr for CustomFormat {
54    type Err = String;
55
56    fn from_str(s: &str) -> Result<Self, Self::Err> {
57        let normalized = s.trim().to_ascii_lowercase().replace(['-', '_'], "");
58        //: cspell:disable
59        match normalized.as_str() {
60            "jsonlanguagemap" => Ok(CustomFormat::JSONLanguageMap),
61            "jsonarraylanguagemap" => Ok(CustomFormat::JSONArrayLanguageMap),
62            "yamllanguagemap" => Ok(CustomFormat::YAMLLanguageMap),
63            // "csvlanguages" => Ok(CustomFormat::CSVLanguages),
64            _ => Err(format!(
65                "Unknown custom format: '{}'. Supported formats: json-language-map, json-array-language-map, yaml-language-map",
66                s
67            )),
68        }
69        //: cspell:enable
70    }
71}
72
73/// Parse a custom format from a string, with helpful error messages.
74pub fn parse_custom_format(s: &str) -> Result<CustomFormat, String> {
75    CustomFormat::from_str(s)
76}
77
78/// Detect if a file is a custom format based on its content and extension.
79/// Returns the detected custom format if found, None otherwise.
80pub fn detect_custom_format(file_path: &str, file_content: &str) -> Option<CustomFormat> {
81    let extension = std::path::Path::new(file_path)
82        .extension()
83        .and_then(|ext| ext.to_str())
84        .unwrap_or("")
85        .to_lowercase();
86
87    match extension.as_str() {
88        "json" => {
89            // Try to parse as JSON object first (JSONLanguageMap)
90            if serde_json::from_str::<serde_json::Value>(file_content).is_ok() {
91                // Check if it's an object (not an array)
92                if let Ok(obj) = serde_json::from_str::<
93                    std::collections::HashMap<String, serde_json::Value>,
94                >(file_content)
95                {
96                    if !obj.is_empty() {
97                        return Some(CustomFormat::JSONLanguageMap);
98                    }
99                }
100                // Check if it's an array (JSONArrayLanguageMap)
101                if serde_json::from_str::<Vec<serde_json::Value>>(file_content).is_ok() {
102                    return Some(CustomFormat::JSONArrayLanguageMap);
103                }
104            }
105        }
106        "yaml" | "yml" => {
107            // Try to parse as YAML
108            if serde_yaml::from_str::<serde_yaml::Value>(file_content).is_ok() {
109                return Some(CustomFormat::YAMLLanguageMap);
110            }
111        }
112        _ => {}
113    }
114
115    None
116}
117
118/// Validate custom format file content
119pub fn validate_custom_format_content(
120    file_path: &str,
121    file_content: &str,
122) -> Result<CustomFormat, String> {
123    if file_content.trim().is_empty() {
124        return Err("File content is empty".to_string());
125    }
126
127    if let Some(format) = detect_custom_format(file_path, file_content) {
128        Ok(format)
129    } else {
130        Err(format!(
131            "Could not detect custom format from file content. Supported formats: {}",
132            get_supported_custom_formats()
133        ))
134    }
135}
136
137/// Get a list of all supported custom formats for help messages.
138pub fn get_supported_custom_formats() -> &'static str {
139    "json-language-map, json-array-language-map, yaml-language-map"
140}