#!/usr/bin/env dart import 'dart:convert'; import 'dart:io'; const String localizationsDir = 'lib/localizations'; const String referenceFile = 'en.json'; void main() async { print('šŸŒ Validating localization files...\n'); try { final result = await validateLocalizations(); if (result) { print('āœ… All localization files are valid!'); exit(0); } else { print('āŒ Localization validation failed!'); exit(1); } } catch (e) { print('šŸ’„ Error during validation: $e'); exit(1); } } Future validateLocalizations() async { // Get all JSON files in localizations directory final locDir = Directory(localizationsDir); if (!locDir.existsSync()) { print('āŒ Localizations directory not found: $localizationsDir'); return false; } final jsonFiles = locDir .listSync() .where((file) => file.path.endsWith('.json')) .map((file) => file.path.split('/').last) .toList(); if (jsonFiles.isEmpty) { print('āŒ No JSON localization files found'); return false; } print('šŸ“ Found ${jsonFiles.length} localization files:'); for (final file in jsonFiles) { print(' • $file'); } print(''); // Load reference file (English) final refFile = File('$localizationsDir/$referenceFile'); if (!refFile.existsSync()) { print('āŒ Reference file not found: $referenceFile'); return false; } Map referenceData; try { final refContent = await refFile.readAsString(); referenceData = json.decode(refContent) as Map; } catch (e) { print('āŒ Failed to parse reference file $referenceFile: $e'); return false; } final referenceKeys = _extractAllKeys(referenceData); print('šŸ”‘ Reference file ($referenceFile) has ${referenceKeys.length} keys'); bool allValid = true; // Validate each localization file for (final fileName in jsonFiles) { if (fileName == referenceFile) continue; // Skip reference file print('\nšŸ” Validating $fileName...'); final file = File('$localizationsDir/$fileName'); Map fileData; try { final content = await file.readAsString(); fileData = json.decode(content) as Map; } catch (e) { print(' āŒ Failed to parse $fileName: $e'); allValid = false; continue; } final fileKeys = _extractAllKeys(fileData); final validation = _validateKeys(referenceKeys, fileKeys, fileName); if (validation.isValid) { print(' āœ… Structure matches reference (${fileKeys.length} keys)'); } else { print(' āŒ Structure validation failed:'); for (final error in validation.errors) { print(' • $error'); } allValid = false; } } return allValid; } /// Extract all nested keys from a JSON object using dot notation /// Example: {"user": {"name": "John"}} -> ["user.name"] Set _extractAllKeys(Map data, {String prefix = ''}) { final keys = {}; for (final entry in data.entries) { final key = prefix.isEmpty ? entry.key : '$prefix.${entry.key}'; if (entry.value is Map) { // Recurse into nested objects keys.addAll(_extractAllKeys(entry.value as Map, prefix: key)); } else { // Add leaf key keys.add(key); } } return keys; } class ValidationResult { final bool isValid; final List errors; ValidationResult({required this.isValid, required this.errors}); } ValidationResult _validateKeys(Set referenceKeys, Set fileKeys, String fileName) { final errors = []; // Find missing keys final missingKeys = referenceKeys.difference(fileKeys); if (missingKeys.isNotEmpty) { errors.add('Missing ${missingKeys.length} keys: ${missingKeys.take(5).join(', ')}${missingKeys.length > 5 ? '...' : ''}'); } // Find extra keys final extraKeys = fileKeys.difference(referenceKeys); if (extraKeys.isNotEmpty) { errors.add('Extra ${extraKeys.length} keys not in reference: ${extraKeys.take(5).join(', ')}${extraKeys.length > 5 ? '...' : ''}'); } return ValidationResult( isValid: errors.isEmpty, errors: errors, ); }