fix(i18n): normalize translations and fallbacks
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m34s

This commit is contained in:
2026-06-03 21:20:08 +02:00
parent a5475ce118
commit 089b8b4227
46 changed files with 17720 additions and 4869 deletions
+85 -49
View File
@@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import '../estado/estado_alarmas.dart';
import '../estado/estado_radio.dart';
import '../l10n/display_names.dart';
import '../l10n/app_localizations_ext.dart';
import '../l10n/gen/app_localizations.dart';
import '../modelos/alarma_musical.dart';
@@ -83,9 +84,10 @@ class _PanelProximaAlarma extends StatelessWidget {
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final proxima = estado.proximaAlarma;
final activasSinProxima = estado.alarmas
.where((a) => a.activa && a.proximaProgramable == null)
.length;
final activasSinProxima =
estado.alarmas
.where((a) => a.activa && a.proximaProgramable == null)
.length;
final proximaProgramable = proxima?.proximaProgramable;
return PluriGlassSurface(
@@ -102,24 +104,21 @@ class _PanelProximaAlarma extends StatelessWidget {
proxima == null
? activasSinProxima > 0
? l10n.activeAlarmsWithoutNextTitle
: l10n.activeAlarmsNoneTitle
: l10n.noActiveAlarms
: l10n.nextAlarmTitle,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w900,
),
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 4),
Text(
proxima == null
? activasSinProxima > 0
? l10n.activeAlarmsWithoutNextSubtitle(
activasSinProxima,
)
activasSinProxima,
)
: l10n.createAlarmHint
: l10n.alarmNextSummary(
proxima.nombre,
_fechaHora(l10n, proximaProgramable!),
),
: '${_nombreVisibleAlarma(l10n, proxima)} · ${_fechaHora(l10n, proximaProgramable!)}',
),
],
),
@@ -166,7 +165,7 @@ class _TarjetaAlarma extends StatelessWidget {
letterSpacing: -1,
),
),
Text(alarma.nombre),
Text(_nombreVisibleAlarma(l10n, alarma)),
],
),
),
@@ -198,7 +197,7 @@ class _TarjetaAlarma extends StatelessWidget {
),
_InfoChip(
icon: Icons.trending_up_rounded,
label: l10n.fadeInSeconds(alarma.fadeInSegundos),
label: l10n.alarmFadeInLabel(alarma.fadeInSegundos),
),
],
),
@@ -263,11 +262,11 @@ class _TarjetaAlarma extends StatelessWidget {
actualizada?.proximaProgramable == null
? l10n.alarmSkippedNoNextSnackbar
: l10n.alarmSkippedReturnsSnackbar(
_fechaHora(
l10n,
actualizada!.proximaProgramable!,
),
_fechaHora(
l10n,
actualizada!.proximaProgramable!,
),
),
),
),
);
@@ -302,10 +301,12 @@ class _TarjetaAlarma extends StatelessWidget {
}
if (actual != null) {
if (alarma.proximaProgramable == null) {
return l10n.alarmVacationPausedNoNext(actual.nombre);
return l10n.alarmVacationPausedNoNext(
_nombreVisibleVacaciones(l10n, actual),
);
}
return l10n.alarmVacationPausedReturns(
actual.nombre,
_nombreVisibleVacaciones(l10n, actual),
_fechaHora(l10n, alarma.proximaProgramable!),
);
}
@@ -357,7 +358,10 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
final l10n = AppLocalizations.of(context);
final ahora = DateTime.now().add(const Duration(minutes: 5));
_nombreController = TextEditingController(
text: alarma?.nombre ?? l10n.defaultAlarmName,
text:
alarma == null
? l10n.defaultAlarmName
: _nombreVisibleAlarma(l10n, alarma),
);
_hora = TimeOfDay(
hour: alarma?.hora ?? ahora.hour,
@@ -419,7 +423,9 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
const SizedBox(width: 12),
Expanded(
child: Text(
widget.alarma == null ? l10n.newAlarmTitle : l10n.editAlarmTitle,
widget.alarma == null
? l10n.newAlarmTitle
: l10n.editAlarmTitle,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w900,
),
@@ -442,7 +448,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
Expanded(
child: _PickerButton(
icon: Icons.schedule_rounded,
label: l10n.timeLabel,
label: l10n.timeField,
value: _hora.format(context),
onTap: _elegirHora,
),
@@ -451,7 +457,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
Expanded(
child: _PickerButton(
icon: Icons.event_rounded,
label: l10n.dateLabel,
label: l10n.dateField,
value: _fechaCorta(_fecha),
onTap:
_tipo == TipoProgramacionAlarma.unica
@@ -488,7 +494,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
children: [
for (var i = DateTime.monday; i <= DateTime.sunday; i++)
FilterChip(
label: Text(l10n.weekdayShort(i)),
label: Text(_weekdayShort(l10n, i)),
selected: _diasSemana.contains(i),
onSelected:
(selected) => setState(() {
@@ -503,7 +509,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
const SizedBox(height: 14),
_SectionLabel(
icon: 'assets/icons/alarmas/fallback_sound.png',
text: l10n.soundAndVolumeTitle,
text: l10n.soundAndVolumeSection,
),
Slider(
value: _volumen,
@@ -520,7 +526,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
subtitle: Text(
_fadeInSegundos == 0
? l10n.alarmFadeInOff
: l10n.alarmFadeInProgress(_fadeInSegundos),
: l10n.alarmFadeInSummary(_fadeInSegundos),
),
),
Slider(
@@ -530,13 +536,12 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
divisions: 60,
label: '${_fadeInSegundos}s',
onChanged:
(value) =>
setState(() => _fadeInSegundos = value.round()),
(value) => setState(() => _fadeInSegundos = value.round()),
),
DropdownButtonFormField<SonidoInternoAlarma>(
initialValue: _sonidoInterno,
decoration: InputDecoration(
labelText: l10n.soundInternalSafe,
labelText: l10n.internalSafeSoundLabel,
),
items: [
DropdownMenuItem(
@@ -574,7 +579,7 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
DropdownMenuItem<String>(
value: emisora.uuid,
child: Text(
emisora.nombre,
localizedStationName(l10n, emisora.nombre),
overflow: TextOverflow.ellipsis,
),
),
@@ -608,7 +613,8 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
SwitchListTile.adaptive(
contentPadding: EdgeInsets.zero,
value: _sonarEnVacaciones,
onChanged: (value) => setState(() => _sonarEnVacaciones = value),
onChanged:
(value) => setState(() => _sonarEnVacaciones = value),
secondary: const _AssetIcon(
'assets/icons/alarmas/vacation_wave.png',
size: 42,
@@ -648,7 +654,9 @@ class _EditorAlarmaSheetState extends State<_EditorAlarmaSheet> {
Future<void> _guardar() async {
if (_tipo == TipoProgramacionAlarma.diasSemana && _diasSemana.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(AppLocalizations.of(context).chooseOneWeekdayError)),
SnackBar(
content: Text(AppLocalizations.of(context).chooseOneWeekdayError),
),
);
return;
}
@@ -711,9 +719,18 @@ class _AccesoDiagnostico extends StatelessWidget {
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final diag = estado.diagnostico;
final exactStatus = diag?.puedeProgramarExactas == true ? l10n.statusOk : l10n.statusPending;
final notificationStatus = diag?.notificacionesPermitidas == true ? l10n.statusOk : l10n.statusPending;
final screenStatus = diag?.puedeUsarPantallaCompleta == true ? l10n.statusOk : l10n.statusPending;
final exactStatus =
diag?.puedeProgramarExactas == true
? l10n.statusOk
: l10n.statusPending;
final notificationStatus =
diag?.notificacionesPermitidas == true
? l10n.statusOk
: l10n.statusPending;
final screenStatus =
diag?.puedeUsarPantallaCompleta == true
? l10n.statusOk
: l10n.statusPending;
return TextButton.icon(
icon: const _AssetIcon(
'assets/icons/alarmas/android_reliability.png',
@@ -723,10 +740,10 @@ class _AccesoDiagnostico extends StatelessWidget {
diag == null
? l10n.androidReliabilityTitle
: l10n.androidReliabilityStatus(
exactStatus,
notificationStatus,
screenStatus,
),
exactStatus,
notificationStatus,
screenStatus,
),
),
onPressed: () async {
if (diag != null && !diag.puedeProgramarExactas) {
@@ -784,18 +801,18 @@ class _PanelVacaciones extends StatelessWidget {
const SizedBox(height: 8),
Text(l10n.vacationRangesHint),
if (vacaciones.isEmpty)
Text(l10n.vacationRangesEmpty)
Text(l10n.noVacationRangesLoaded)
else
for (final rango in vacaciones)
ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.event_busy_rounded),
title: Text(rango.nombre),
title: Text(_nombreVisibleVacaciones(l10n, rango)),
subtitle: Text(
'${_fechaCorta(rango.inicioDia)}${_fechaCorta(rango.finDia)}',
),
trailing: IconButton(
tooltip: l10n.deleteRangeAction,
tooltip: l10n.deleteRangeTooltip,
onPressed: () => estado.eliminarRangoVacaciones(rango.id),
icon: const Icon(Icons.delete_outline_rounded),
),
@@ -1057,23 +1074,42 @@ class _EmptyAlarmas extends StatelessWidget {
}
}
String _nombreVisibleAlarma(AppLocalizations l10n, AlarmaMusical alarma) {
return localizedAlarmName(l10n, alarma.nombre);
}
String _nombreVisibleVacaciones(AppLocalizations l10n, RangoVacaciones rango) {
return localizedVacationName(l10n, rango.nombre);
}
String _hora(AlarmaMusical alarma) =>
'${alarma.hora.toString().padLeft(2, '0')}:${alarma.minuto.toString().padLeft(2, '0')}';
String _programacion(AppLocalizations l10n, AlarmaMusical alarma) {
return switch (alarma.tipoProgramacion) {
TipoProgramacionAlarma.unica =>
l10n.alarmScheduleOnce(_fechaCorta(alarma.fechaUnica ?? DateTime.now())),
TipoProgramacionAlarma.unica => l10n.alarmScheduleOnce(
_fechaCorta(alarma.fechaUnica ?? DateTime.now()),
),
TipoProgramacionAlarma.diaria => l10n.dailyOption,
TipoProgramacionAlarma.diasSemana =>
l10n.alarmScheduleWeekdays(
alarma.diasSemana.map(l10n.weekdayShort).join(', '),
),
TipoProgramacionAlarma.diasSemana => l10n.alarmScheduleWeekdays(
alarma.diasSemana.map((day) => _weekdayShort(l10n, day)).join(', '),
),
};
}
String _fechaHora(AppLocalizations l10n, DateTime fecha) =>
l10n.dateTimeSentence(fecha);
String _weekdayShort(AppLocalizations l10n, int day) => switch (day) {
DateTime.monday => l10n.weekdayShortMonday,
DateTime.tuesday => l10n.weekdayShortTuesday,
DateTime.wednesday => l10n.weekdayShortWednesday,
DateTime.thursday => l10n.weekdayShortThursday,
DateTime.friday => l10n.weekdayShortFriday,
DateTime.saturday => l10n.weekdayShortSaturday,
DateTime.sunday => l10n.weekdayShortSunday,
_ => '?',
};
String _fechaCorta(DateTime fecha) =>
'${fecha.day.toString().padLeft(2, '0')}/${fecha.month.toString().padLeft(2, '0')}/${fecha.year}';