diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index d459f3f..6b203cb 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -51,6 +51,9 @@ jobs: - name: Install dependencies run: flutter pub get + - name: Validate localizations + run: dart run scripts/validate_localizations.dart + - name: Generate icons and splash screens run: | dart run flutter_launcher_icons @@ -100,6 +103,9 @@ jobs: - name: Install dependencies run: flutter pub get + - name: Validate localizations + run: dart run scripts/validate_localizations.dart + - name: Generate icons and splash screens run: | dart run flutter_launcher_icons @@ -142,6 +148,9 @@ jobs: - name: Install dependencies run: flutter pub get + - name: Validate localizations + run: dart run scripts/validate_localizations.dart + - name: Generate icons and splash screens run: | dart run flutter_launcher_icons diff --git a/assets/changelog.json b/assets/changelog.json index 9392063..b761c58 100644 --- a/assets/changelog.json +++ b/assets/changelog.json @@ -1,7 +1,6 @@ { "1.2.5": { - "content": "• Smart area caching: Loads 3x larger areas and refreshes data every 60 seconds for much faster browsing\n• Enhanced tile loading: Increased retry attempts with faster delays - tiles load much more reliably\n• Improved error handling: Overpass queries now automatically split on timeouts and node limits\n• Better network status: Streamlined loading indicator that works with all data refresh types\n• Instant node display: Surveillance devices now appear immediately when data finishes loading\n• Node limit alerts: Get notified when too many devices are found (increase limit in settings to see more)", - "content": "• NEW: Compass indicator shows map orientation and enables north-lock mode\n• NEW: North-lock keeps map pointing north while following your location\n• IMPROVED: Follow-me mode renamed for clarity (was confusingly called 'north up')\n• IMPROVED: Smart rotation detection ignores zoom gestures but responds to intentional map rotation\n• Smart area caching: Loads 3x larger areas and refreshes data every 60 seconds for much faster browsing\n• Enhanced tile loading: Increased retry attempts with faster delays - tiles load much more reliably\n• Improved error handling: Overpass queries now automatically split on timeouts and node limits\n• Better network status: Streamlined loading indicator that works with all data refresh types\n• Instant node display: Surveillance devices now appear immediately when data finishes loading\n• Node limit alerts: Get notified when too many devices are found (increase limit in settings to see more)" + "content": "• NEW: Compass indicator shows map orientation and enables north-lock mode\n• Smart area caching: Loads 3x larger areas and refreshes data every 60 seconds for much faster browsing\n• Enhanced tile loading: Increased retry attempts with faster delays - tiles load much more reliably\n• Better network status: Simplified loading indicator logic\n• Instant node display: Surveillance devices now appear immediately when data finishes loading\n• Node limit alerts: Get notified when some nodes are not drawn" }, "1.2.4": { "content": "• New welcome popup for first-time users with essential privacy information\n• Automatic changelog display when app updates (like this one!)\n• Added Release Notes viewer in Settings > About\n• Enhanced user onboarding and transparency about data handling\n• Improved documentation for contributors" diff --git a/do_builds.sh b/do_builds.sh index f160241..09c0061 100755 --- a/do_builds.sh +++ b/do_builds.sh @@ -80,6 +80,11 @@ fi # Build the dart-define arguments DART_DEFINE_ARGS="--dart-define=OSM_PROD_CLIENTID=$OSM_PROD_CLIENTID --dart-define=OSM_SANDBOX_CLIENTID=$OSM_SANDBOX_CLIENTID" +# Validate localizations before building +echo "Validating localizations..." +dart run scripts/validate_localizations.dart || exit 1 +echo + appver=$(grep "version:" pubspec.yaml | head -1 | cut -d ':' -f 2 | tr -d ' ' | cut -d '+' -f 1) echo echo "Building app version ${appver}..." diff --git a/scripts/validate_localizations.dart b/scripts/validate_localizations.dart index e69de29..aad88ff 100644 --- a/scripts/validate_localizations.dart +++ b/scripts/validate_localizations.dart @@ -0,0 +1,154 @@ +#!/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, + ); +} \ No newline at end of file