Fork i18n + search + filtering- v0.2
at main 4.0 kB view raw
1use std::env; 2use std::fs; 3use std::path::Path; 4use std::process; 5use std::collections::HashMap; 6 7fn main() { 8 #[cfg(feature = "embed")] 9 { 10 minijinja_embed::embed_templates!("templates"); 11 } 12 13 // Only run i18n validation in debug builds or when explicitly requested 14 if env::var("CARGO_CFG_DEBUG_ASSERTIONS").is_ok() || env::var("VALIDATE_I18N").is_ok() { 15 validate_i18n_files(); 16 } 17} 18 19fn validate_i18n_files() { 20 let i18n_dir = Path::new("i18n"); 21 if !i18n_dir.exists() { 22 return; // Skip if no i18n directory 23 } 24 25 println!("cargo:rerun-if-changed=i18n/"); 26 27 // Check for duplicate keys 28 for entry in fs::read_dir(i18n_dir).unwrap() { 29 let lang_dir = entry.unwrap().path(); 30 if lang_dir.is_dir() { 31 if check_for_duplicates(&lang_dir) { 32 eprintln!("❌ Build failed: Duplicate translation keys found!"); 33 process::exit(1); 34 } 35 } 36 } 37 38 // Check synchronization between en-us and fr-ca 39 if check_synchronization() { 40 eprintln!("❌ Build failed: Translation files are not synchronized!"); 41 process::exit(1); 42 } 43 44 println!("✅ i18n validation passed"); 45} 46 47fn check_for_duplicates(dir: &Path) -> bool { 48 let mut has_duplicates = false; 49 50 for entry in fs::read_dir(dir).unwrap() { 51 let file = entry.unwrap().path(); 52 if file.extension().and_then(|s| s.to_str()) == Some("ftl") { 53 if let Ok(content) = fs::read_to_string(&file) { 54 let mut seen_keys = HashMap::new(); 55 56 for (line_num, line) in content.lines().enumerate() { 57 if let Some(key) = parse_translation_key(line) { 58 if let Some(prev_line) = seen_keys.insert(key.clone(), line_num + 1) { 59 eprintln!( 60 "Duplicate key '{}' in {}: line {} and line {}", 61 key, 62 file.display(), 63 prev_line, 64 line_num + 1 65 ); 66 has_duplicates = true; 67 } 68 } 69 } 70 } 71 } 72 } 73 74 has_duplicates 75} 76 77fn check_synchronization() -> bool { 78 let files = ["ui.ftl", "common.ftl", "actions.ftl", "errors.ftl", "forms.ftl"]; 79 let mut has_sync_issues = false; 80 81 for file in files.iter() { 82 let en_file = Path::new("i18n/en-us").join(file); 83 let fr_file = Path::new("i18n/fr-ca").join(file); 84 85 if en_file.exists() && fr_file.exists() { 86 let en_count = count_translation_keys(&en_file); 87 let fr_count = count_translation_keys(&fr_file); 88 89 if en_count != fr_count { 90 eprintln!( 91 "Key count mismatch in {}: EN={}, FR={}", 92 file, en_count, fr_count 93 ); 94 has_sync_issues = true; 95 } 96 } 97 } 98 99 has_sync_issues 100} 101 102fn count_translation_keys(file: &Path) -> usize { 103 if let Ok(content) = fs::read_to_string(file) { 104 content 105 .lines() 106 .filter(|line| parse_translation_key(line).is_some()) 107 .count() 108 } else { 109 0 110 } 111} 112 113fn parse_translation_key(line: &str) -> Option<String> { 114 let trimmed = line.trim(); 115 116 // Skip comments and empty lines 117 if trimmed.starts_with('#') || trimmed.is_empty() { 118 return None; 119 } 120 121 // Look for pattern: key = value 122 if let Some(eq_pos) = trimmed.find(" =") { 123 let key = &trimmed[..eq_pos]; 124 // Validate key format: alphanumeric, hyphens, underscores only 125 if key.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') && !key.is_empty() { 126 return Some(key.to_string()); 127 } 128 } 129 130 None 131}