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:
@@ -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';
|
||||
@@ -23,22 +24,25 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
final _controladorNombre = TextEditingController();
|
||||
|
||||
final _opcionesTiempo = <int?>[null, 60, 120, 180, 300];
|
||||
final _etiquetasTiempo = ['Sin límite', '1 min', '2 min', '3 min', '5 min'];
|
||||
|
||||
int get _maxImpostores => (_jugadores.length / 3).floor().clamp(1, 4);
|
||||
|
||||
List<String> _etiquetasTiempo(AppLocalizations l10n) =>
|
||||
[l10n.noLimit, l10n.oneMin, l10n.twoMin, l10n.threeMin, l10n.fiveMin];
|
||||
|
||||
void _agregarJugador() {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final nombre = _controladorNombre.text.trim();
|
||||
if (nombre.isEmpty) return;
|
||||
if (_jugadores.contains(nombre)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Ya existe un jugador con ese nombre')),
|
||||
SnackBar(content: Text(l10n.playerAlreadyExists)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (_jugadores.length >= 20) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Máximo 20 jugadores')),
|
||||
SnackBar(content: Text(l10n.maxPlayersReached)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -61,9 +65,10 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
}
|
||||
|
||||
void _iniciarPartida() {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
if (_jugadores.length < 3) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Se necesitan al menos 3 jugadores')),
|
||||
SnackBar(content: Text(l10n.minPlayersRequired)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -94,11 +99,13 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final estado = context.watch<EstadoJuego>();
|
||||
final categorias = ['todas', ...?estado.banco?.nombresCategorias];
|
||||
final etiquetas = _etiquetasTiempo(l10n);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Crear partida')),
|
||||
appBar: AppBar(title: Text(l10n.createGame)),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
@@ -111,20 +118,20 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Modo de juego',
|
||||
Text(l10n.gameMode,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 12),
|
||||
SegmentedButton<bool>(
|
||||
segments: const [
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: false,
|
||||
label: Text('Un solo móvil'),
|
||||
icon: Icon(Icons.phone_android),
|
||||
label: Text(l10n.singleDevice),
|
||||
icon: const Icon(Icons.phone_android),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: true,
|
||||
label: Text('Multimóvil'),
|
||||
icon: Icon(Icons.devices),
|
||||
label: Text(l10n.multiDevice),
|
||||
icon: const Icon(Icons.devices),
|
||||
),
|
||||
],
|
||||
selected: {_modoMultimovil},
|
||||
@@ -145,7 +152,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Categoría',
|
||||
Text(l10n.category,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
@@ -158,7 +165,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
items: categorias.map((c) {
|
||||
return DropdownMenuItem(
|
||||
value: c,
|
||||
child: Text(BancoPalabras.nombreBonitoCategoria(c)),
|
||||
child: Text(BancoPalabras.nombreBonitoCategoria(c, l10n)),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (v) => setState(() => _categoria = v!),
|
||||
@@ -180,9 +187,9 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Jugadores (${_jugadores.length})',
|
||||
Text(l10n.playersCount(_jugadores.length),
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
Text('3-20',
|
||||
Text(l10n.playersRangeHint,
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
],
|
||||
),
|
||||
@@ -192,9 +199,9 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controladorNombre,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Nombre del jugador',
|
||||
prefixIcon: Icon(Icons.person_add),
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.playerNameHint,
|
||||
prefixIcon: const Icon(Icons.person_add),
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
onSubmitted: (_) => _agregarJugador(),
|
||||
@@ -237,7 +244,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Configuración',
|
||||
Text(l10n.configuration,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -245,7 +252,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('🎭 Impostores'),
|
||||
Text(l10n.impostors),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
@@ -271,9 +278,8 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
|
||||
// Pista para impostor
|
||||
SwitchListTile(
|
||||
title: const Text('🔍 Pista para impostor'),
|
||||
subtitle: const Text(
|
||||
'El impostor conoce la categoría'),
|
||||
title: Text(l10n.impostorClue),
|
||||
subtitle: Text(l10n.impostorClueDescription),
|
||||
value: _pistaImpostor,
|
||||
onChanged: (v) =>
|
||||
setState(() => _pistaImpostor = v),
|
||||
@@ -284,14 +290,14 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('⏱️ Tiempo de debate'),
|
||||
Text(l10n.debateTime),
|
||||
DropdownButton<int?>(
|
||||
value: _tiempoDebate,
|
||||
items: List.generate(
|
||||
_opcionesTiempo.length,
|
||||
(i) => DropdownMenuItem(
|
||||
value: _opcionesTiempo[i],
|
||||
child: Text(_etiquetasTiempo[i]),
|
||||
child: Text(etiquetas[i]),
|
||||
),
|
||||
),
|
||||
onChanged: (v) =>
|
||||
@@ -312,7 +318,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _jugadores.length >= 3 ? _iniciarPartida : null,
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
label: const Text('Iniciar partida'),
|
||||
label: Text(l10n.startGame),
|
||||
style: ElevatedButton.styleFrom(
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 18,
|
||||
|
||||
Reference in New Issue
Block a user