feat(i18n): add localization foundation
Build & Deploy Pluriwave / Build APK + AAB release (push) Successful in 1m52s
Build & Deploy Pluriwave / Análisis de código (push) Successful in 24s

This commit is contained in:
2026-05-22 13:29:52 +02:00
parent d85dee6fa8
commit 3f548fd53e
13 changed files with 986 additions and 65 deletions
+74 -44
View File
@@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'estado/estado_radio.dart';
import 'estado/estado_alarmas.dart';
import 'estado/estado_idioma.dart';
import 'l10n/gen/app_localizations.dart';
import 'modelos/alarma_musical.dart';
import 'pantallas/pantalla_alarmas.dart';
import 'pantallas/pantalla_alarma_sonando.dart';
@@ -27,14 +29,21 @@ class PluriWaveApp extends StatelessWidget {
providers: [
ChangeNotifierProvider(create: (_) => EstadoRadio()),
ChangeNotifierProvider(create: (_) => EstadoAlarmas()),
ChangeNotifierProvider(create: (_) => EstadoIdioma()),
],
child: MaterialApp(
title: 'PluriWave',
debugShowCheckedModeBanner: false,
theme: PluriWaveTheme.dark(),
darkTheme: PluriWaveTheme.dark(),
themeMode: ThemeMode.dark,
home: const _PaginaPrincipal(),
child: Consumer<EstadoIdioma>(
builder:
(context, estadoIdioma, _) => MaterialApp(
title: 'PluriWave',
debugShowCheckedModeBanner: false,
theme: PluriWaveTheme.dark(),
darkTheme: PluriWaveTheme.dark(),
themeMode: ThemeMode.dark,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
locale: estadoIdioma.localeSeleccionado,
home: const _PaginaPrincipal(),
),
),
);
}
@@ -63,27 +72,12 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
PantallaAjustes(),
];
static const _navItems = [
PluriNavItem(
glyph: PluriIconGlyph.home,
label: 'Inicio',
),
PluriNavItem(
glyph: PluriIconGlyph.search,
label: 'Buscar',
),
PluriNavItem(
glyph: PluriIconGlyph.favorites,
label: 'Favoritos',
),
PluriNavItem(
glyph: PluriIconGlyph.alarm,
label: 'Alarmas',
),
PluriNavItem(
glyph: PluriIconGlyph.settings,
label: 'Ajustes',
),
List<PluriNavItem> _navItems(AppLocalizations l10n) => [
PluriNavItem(glyph: PluriIconGlyph.home, label: l10n.navHome),
PluriNavItem(glyph: PluriIconGlyph.search, label: l10n.navSearch),
PluriNavItem(glyph: PluriIconGlyph.favorites, label: l10n.navFavorites),
PluriNavItem(glyph: PluriIconGlyph.alarm, label: l10n.navAlarms),
PluriNavItem(glyph: PluriIconGlyph.settings, label: l10n.navSettings),
];
@override
@@ -101,7 +95,10 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
SnackBar(
content: Text(msg),
duration: const Duration(seconds: 3),
action: SnackBarAction(label: 'OK', onPressed: () {}),
action: SnackBarAction(
label: AppLocalizations.of(context).actionOk,
onPressed: () {},
),
),
);
});
@@ -133,13 +130,15 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return PluriWaveScaffold(
appBar: AppBar(
title: const Text('PluriWave'),
title: Text(l10n.appTitle),
actions: [
IconButton(
icon: const Icon(Icons.bedtime_outlined),
tooltip: 'Timer de sueño',
tooltip: l10n.sleepTimer,
onPressed: () => _mostrarTimerDialog(context),
),
],
@@ -177,7 +176,7 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
children: [
const MiniReproductor(),
PluriBottomNavigation(
items: _navItems,
items: _navItems(l10n),
selectedIndex: _indice,
onSelected: (i) => setState(() => _indice = i),
),
@@ -212,7 +211,11 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
setState(() => _indice = 3);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Omitida esta ejecución de ${alarma.nombre}.'),
content: Text(
AppLocalizations.of(context).skipCurrentAlarmExecution(
alarma.nombre,
),
),
),
);
return;
@@ -252,12 +255,12 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Timer de sueño',
AppLocalizations.of(ctx).sleepTimer,
style: Theme.of(ctx).textTheme.titleLarge,
),
const SizedBox(height: PluriLayout.sectionGap),
Text(
'Apagado suave de la radio con cuenta atrás exacta.',
AppLocalizations.of(ctx).sleepTimerDescription,
style: Theme.of(ctx).textTheme.bodySmall,
),
const SizedBox(height: PluriLayout.panelGap),
@@ -280,7 +283,9 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
estado.cancelarTimer();
Navigator.pop(ctx);
},
child: const Text('Cancelar timer'),
child: Text(
AppLocalizations.of(ctx).cancelTimer,
),
),
],
);
@@ -308,7 +313,9 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal> {
),
ActionChip(
avatar: const Icon(Icons.tune_rounded, size: 18),
label: const Text('Otro'),
label: Text(
AppLocalizations.of(ctx).optionOther,
),
onPressed: () async {
final duracion =
await _pedirDuracionPersonalizada(ctx);
@@ -382,7 +389,11 @@ class _TimerPersonalizadoSheetState extends State<_TimerPersonalizadoSheet> {
);
if (duracion <= Duration.zero) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Elegí una duración mayor que cero.')),
SnackBar(
content: Text(
AppLocalizations.of(context).durationGreaterThanZero,
),
),
);
return;
}
@@ -403,30 +414,49 @@ class _TimerPersonalizadoSheetState extends State<_TimerPersonalizadoSheet> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Duración personalizada',
AppLocalizations.of(context).customDurationTitle,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: PluriLayout.sectionGap),
Row(
children: [
Expanded(child: _campoTiempo(_horasCtrl, 'Horas')),
Expanded(
child: _campoTiempo(
_horasCtrl,
AppLocalizations.of(context).hoursLabel,
),
),
const SizedBox(width: PluriLayout.compactGap),
Expanded(child: _campoTiempo(_minutosCtrl, 'Minutos')),
Expanded(
child: _campoTiempo(
_minutosCtrl,
AppLocalizations.of(context).minutesLabel,
),
),
const SizedBox(width: PluriLayout.compactGap),
Expanded(child: _campoTiempo(_segundosCtrl, 'Segundos')),
Expanded(
child: _campoTiempo(
_segundosCtrl,
AppLocalizations.of(context).secondsLabel,
),
),
],
),
const SizedBox(height: PluriLayout.compactGap),
SwitchListTile.adaptive(
contentPadding: EdgeInsets.zero,
title: const Text('Guardar como acceso rápido'),
title: Text(
AppLocalizations.of(context).saveQuickAccess,
),
value: _guardarPreset,
onChanged: (value) => setState(() => _guardarPreset = value),
),
const SizedBox(height: PluriLayout.sectionGap),
FilledButton.icon(
icon: const Icon(Icons.bedtime_rounded),
label: const Text('Iniciar timer'),
label: Text(
AppLocalizations.of(context).startTimer,
),
onPressed: _confirmar,
),
],