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