v0.2.0: i18n 18 idiomas + pantalla ajustes + bancos multiidioma

Internacionalización completa:
- 18 ficheros .arb: es, en, fr, pt, de, it, ru, ja, ko, zh, zh_TW, ar, hi, tr, pl, nl, ca, eu
- Todos los strings extraídos de todas las pantallas
- Detección automática de idioma del sistema
- Selector manual en pantalla de ajustes

Pantalla de ajustes nueva:
- Selector de idioma con banderas emoji
- Vibración ON/OFF
- Acerca de (versión, desarrollador)

Bancos de palabras multiidioma:
- palabras.json (castellano, 1000 palabras)
- palabras_en.json (inglés, 1000 palabras)
- palabras_fr.json (francés, 1000 palabras)
- Fallback a castellano si no hay banco del idioma

13138 líneas Dart, 39 ficheros, 0 issues en flutter analyze
This commit is contained in:
ShanaiaBot
2026-04-04 01:18:09 +02:00
parent de2c8ffa18
commit 1bca50af1d
56 changed files with 14389 additions and 201 deletions

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:el_impostor/l10n/generated/app_localizations.dart';
import 'package:provider/provider.dart';
import '../estado/estado_juego.dart';
import '../modelos/palabra.dart';
@@ -11,6 +12,7 @@ class PantallaFinPartida extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final estado = context.watch<EstadoJuego>();
final partida = estado.partida;
if (partida == null) return const SizedBox.shrink();
@@ -21,7 +23,7 @@ class PantallaFinPartida extends StatelessWidget {
return Scaffold(
appBar: AppBar(
title: const Text('Fin de partida'),
title: Text(l10n.gameOver),
automaticallyImplyLeading: false,
),
body: SingleChildScrollView(
@@ -56,8 +58,8 @@ class PantallaFinPartida extends StatelessWidget {
const SizedBox(height: 16),
Text(
ganaronJugadores
? '¡Los jugadores ganan!'
: '¡Los impostores ganan!',
? l10n.playersWin
: l10n.impostorsWin,
style: Theme.of(context)
.textTheme
.headlineMedium
@@ -79,7 +81,7 @@ class PantallaFinPartida extends StatelessWidget {
padding: const EdgeInsets.all(20),
child: Column(
children: [
Text('🔍 La palabra era:',
Text(l10n.theSecretWordWas,
style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Text(
@@ -94,7 +96,7 @@ class PantallaFinPartida extends StatelessWidget {
),
const SizedBox(height: 4),
Text(
'Categoría: ${BancoPalabras.nombreBonitoCategoria(partida.categoriaReal)}',
l10n.categoryLabel(BancoPalabras.nombreBonitoCategoria(partida.categoriaReal, l10n)),
style: Theme.of(context).textTheme.bodyMedium,
),
],
@@ -110,7 +112,7 @@ class PantallaFinPartida extends StatelessWidget {
child: Column(
children: [
Text(
'🎭 ${impostores.length == 1 ? 'El impostor era:' : 'Los impostores eran:'}',
impostores.length == 1 ? l10n.theImpostorWas : l10n.theImpostorsWere,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
@@ -119,8 +121,8 @@ class PantallaFinPartida extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('🎭 ',
style: const TextStyle(fontSize: 18)),
const Text('🎭 ',
style: TextStyle(fontSize: 18)),
Text(
j.nombre,
style: Theme.of(context)
@@ -150,7 +152,7 @@ class PantallaFinPartida extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('📊 Historial de votaciones',
Text(l10n.votingHistory,
style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 12),
...partida.historialVotaciones
@@ -165,7 +167,7 @@ class PantallaFinPartida extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Ronda $ronda: ${resultado.eliminadoNombre} ${resultado.eraImpostor ? '🎭' : '😇'}',
'${l10n.roundElimination(ronda, resultado.eliminadoNombre)} ${resultado.eraImpostor ? '🎭' : '😇'}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: resultado.eraImpostor
@@ -210,7 +212,7 @@ class PantallaFinPartida extends StatelessWidget {
);
},
icon: const Icon(Icons.replay),
label: const Text('Revancha'),
label: Text(l10n.rematch),
),
),
const SizedBox(height: 12),
@@ -229,7 +231,7 @@ class PantallaFinPartida extends StatelessWidget {
);
},
icon: const Icon(Icons.home),
label: const Text('Menú principal'),
label: Text(l10n.mainMenu),
),
),
const SizedBox(height: 16),