import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; /// Guardas sobre los ficheros de traducción. /// /// El fallo que motivó estos tests: 21 claves se quedaron con el texto inglés /// dentro de árabe, japonés, coreano, chino, ruso, turco, hindi, neerlandés, /// polaco y euskera. Nada fallaba: la app mostraba inglés y ya está. void main() { const idiomaPlantilla = 'es'; /// Coincidencias reales entre idiomas: palabras que se escriben igual y por /// tanto no son un descuido. Cada entrada es "idioma:clave". const coincidenciasLegitimas = { // Nombre de la app y rangos numéricos, iguales en todas partes. 'de:appTitle', 'en:appTitle', 'fr:appTitle', 'it:appTitle', // "Animals", "Professions", "Impostors", "Notes" son catalán correcto. 'ca:categoryAnimals', 'ca:categoryProfessions', 'ca:impostors', 'ca:notes', 'ca:notesTitle', // "Vibration", "Version", "Name" son alemán correcto. 'de:vibration', 'de:version', 'de:profileName', // "Sports", "Configuration", "Notes", "Vibration", "Version" son francés. 'fr:categorySports', 'fr:configuration', 'fr:notes', 'fr:notesTitle', 'fr:vibration', 'fr:version', // "Round" se usa tal cual en italiano. 'it:roundNumber', 'it:roundElimination', }; Map cargar(String idioma) { final fichero = File('lib/l10n/app_$idioma.arb'); final datos = json.decode(fichero.readAsStringSync()) as Map; return { for (final e in datos.entries) if (!e.key.startsWith('@') && e.value is String) e.key: e.value as String, }; } final idiomas = Directory('lib/l10n') .listSync() .whereType() .where((f) => f.path.endsWith('.arb')) .map((f) => f.uri.pathSegments.last.replaceAll(RegExp(r'^app_|\.arb$'), '')) .toList() ..sort(); final plantilla = cargar(idiomaPlantilla); final ingles = cargar('en'); test('están los 18 idiomas soportados', () { expect(idiomas.length, 18); }); for (final idioma in idiomas) { group('app_$idioma.arb', () { final valores = cargar(idioma); test('no le falta ninguna clave de la plantilla', () { final faltan = plantilla.keys.where((k) => !valores.containsKey(k)); expect(faltan, isEmpty, reason: 'sin traducir: ${faltan.join(", ")}'); }); test('no tiene claves que la plantilla no conozca', () { final sobran = valores.keys.where((k) => !plantilla.containsKey(k)); expect(sobran, isEmpty, reason: 'huérfanas: ${sobran.join(", ")}'); }); test('ningún valor está vacío', () { final vacios = valores.entries .where((e) => e.value.trim().isEmpty) .map((e) => e.key); expect(vacios, isEmpty); }); if (idioma != 'en' && idioma != idiomaPlantilla) { test('no se ha quedado el texto en inglés', () { final enIngles = valores.entries .where((e) => ingles[e.key] == e.value && plantilla[e.key] != e.value && !coincidenciasLegitimas.contains('$idioma:${e.key}')) .map((e) => '${e.key} = "${e.value}"') .toList(); expect(enIngles, isEmpty, reason: enIngles.join('\n')); }); } test('conserva los marcadores de posición de la plantilla', () { final patron = RegExp(r'\{(\w+)\}'); final fallos = []; for (final entrada in valores.entries) { final esperados = patron .allMatches(plantilla[entrada.key] ?? '') .map((m) => m.group(1)!) .toSet(); final presentes = patron.allMatches(entrada.value).map((m) => m.group(1)!).toSet(); // Los plurales ICU declaran el contador fuera de las llaves simples. if (entrada.value.contains(', plural,')) continue; if (!presentes.containsAll(esperados)) { fallos.add('${entrada.key}: faltan ${esperados.difference(presentes)}'); } } expect(fallos, isEmpty, reason: fallos.join('\n')); }); }); } }