fix(i18n): normalize translations and fallbacks
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../modelos/preset_ecualizador.dart';
|
||||
import '../tema/pluriwave_theme.dart';
|
||||
import 'pluri_glass_surface.dart';
|
||||
@@ -41,6 +42,7 @@ class _EcualizadorWidgetState extends State<EcualizadorWidget> {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final tokens = context.pluriTokens;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
|
||||
return PluriGlassSurface(
|
||||
borderRadius: BorderRadius.circular(tokens.radiusLg),
|
||||
@@ -50,10 +52,10 @@ class _EcualizadorWidgetState extends State<EcualizadorWidget> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text('Ecualizador', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700)),
|
||||
Text(l10n.equalizerTitle, style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700)),
|
||||
const Spacer(),
|
||||
Chip(
|
||||
label: Text(widget.preset.nombre, style: theme.textTheme.labelMedium),
|
||||
label: Text(_nombrePreset(l10n, widget.preset.nombre), style: theme.textTheme.labelMedium),
|
||||
backgroundColor: theme.colorScheme.secondaryContainer.withValues(alpha: 0.75),
|
||||
),
|
||||
],
|
||||
@@ -77,8 +79,8 @@ class _EcualizadorWidgetState extends State<EcualizadorWidget> {
|
||||
height: 152,
|
||||
child: Semantics(
|
||||
slider: true,
|
||||
label: 'Banda ${_etiquetas[i]}',
|
||||
value: '${_bandas[i].toStringAsFixed(1)} decibelios',
|
||||
label: l10n.equalizerBandLabel(_etiquetas[i]),
|
||||
value: l10n.equalizerBandValue(_bandas[i].toStringAsFixed(1)),
|
||||
child: RotatedBox(
|
||||
quarterTurns: 3,
|
||||
child: Slider(
|
||||
@@ -110,6 +112,19 @@ class _EcualizadorWidgetState extends State<EcualizadorWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
String _nombrePreset(AppLocalizations l10n, String nombre) {
|
||||
return switch (nombre) {
|
||||
'Flat' => l10n.equalizerPresetFlat,
|
||||
'Rock' => l10n.equalizerPresetRock,
|
||||
'Pop' => l10n.equalizerPresetPop,
|
||||
'Bass Boost' => l10n.equalizerPresetBassBoost,
|
||||
'Jazz' => l10n.equalizerPresetJazz,
|
||||
'Voz' => l10n.equalizerPresetVoice,
|
||||
'Personalizado' => l10n.equalizerPresetCustom,
|
||||
_ => nombre,
|
||||
};
|
||||
}
|
||||
|
||||
class PresetsEcualizadorWidget extends StatelessWidget {
|
||||
final PresetEcualizador presetActual;
|
||||
final void Function(PresetEcualizador) onSeleccionar;
|
||||
@@ -123,13 +138,14 @@ class PresetsEcualizadorWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final l10n = AppLocalizations.of(context);
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 6,
|
||||
children: PresetEcualizador.presets.map((p) {
|
||||
final selected = p.nombre == presetActual.nombre;
|
||||
return ChoiceChip(
|
||||
label: Text(p.nombre),
|
||||
label: Text(_nombrePreset(l10n, p.nombre)),
|
||||
selected: selected,
|
||||
showCheckmark: false,
|
||||
selectedColor: theme.colorScheme.primaryContainer,
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../estado/estado_radio.dart';
|
||||
import '../l10n/display_names.dart';
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../pantallas/pantalla_reproductor.dart';
|
||||
import '../servicios/servicio_audio.dart';
|
||||
import '../tema/pluriwave_theme.dart';
|
||||
@@ -17,11 +19,14 @@ class MiniReproductor extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final estado = context.watch<EstadoRadio>();
|
||||
final l10n = AppLocalizations.of(context);
|
||||
estado.configurarLocalizaciones(l10n);
|
||||
final emisora = estado.emisoraActual;
|
||||
|
||||
if (emisora == null) return const SizedBox.shrink();
|
||||
|
||||
final t = context.pluriTokens;
|
||||
final stationName = localizedStationName(l10n, emisora.nombre);
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
@@ -43,7 +48,7 @@ class MiniReproductor extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
label: 'Abrir reproductor de ${emisora.nombre}',
|
||||
label: l10n.miniPlayerOpenLabel(stationName),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
@@ -74,7 +79,7 @@ class MiniReproductor extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
emisora.nombre,
|
||||
stationName,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall
|
||||
@@ -91,7 +96,7 @@ class MiniReproductor extends StatelessWidget {
|
||||
final activo =
|
||||
s == EstadoReproduccion.reproduciendo;
|
||||
return Text(
|
||||
_labelEstado(s),
|
||||
_labelEstado(l10n, s),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(
|
||||
@@ -117,7 +122,7 @@ class MiniReproductor extends StatelessWidget {
|
||||
glyph: PluriIconGlyph.player,
|
||||
variant: PluriIconVariant.activeGlow,
|
||||
size: 18,
|
||||
semanticLabel: 'Reproductor',
|
||||
semanticLabel: l10n.playerIconLabel,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -144,7 +149,7 @@ class MiniReproductor extends StatelessWidget {
|
||||
if (s == EstadoReproduccion.error) {
|
||||
final emisoraActual = estado.emisoraActual;
|
||||
return IconButton(
|
||||
tooltip: 'Reintentar',
|
||||
tooltip: l10n.retryAction,
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
onPressed:
|
||||
emisoraActual != null
|
||||
@@ -161,13 +166,13 @@ class MiniReproductor extends StatelessWidget {
|
||||
button: true,
|
||||
label:
|
||||
s == EstadoReproduccion.reproduciendo
|
||||
? 'Pausar'
|
||||
: 'Reproducir',
|
||||
? l10n.pauseAction
|
||||
: l10n.playAction,
|
||||
child: IconButton(
|
||||
tooltip:
|
||||
s == EstadoReproduccion.reproduciendo
|
||||
? 'Pausar'
|
||||
: 'Reproducir',
|
||||
? l10n.pauseAction
|
||||
: l10n.playAction,
|
||||
icon: Icon(
|
||||
s == EstadoReproduccion.reproduciendo
|
||||
? Icons.pause_circle_filled_rounded
|
||||
@@ -190,13 +195,13 @@ class MiniReproductor extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
String _labelEstado(EstadoReproduccion estado) {
|
||||
String _labelEstado(AppLocalizations l10n, EstadoReproduccion estado) {
|
||||
return switch (estado) {
|
||||
EstadoReproduccion.cargando => 'Conectando...',
|
||||
EstadoReproduccion.reproduciendo => 'En directo',
|
||||
EstadoReproduccion.pausado => 'Pausado',
|
||||
EstadoReproduccion.error => 'Error de conexión',
|
||||
EstadoReproduccion.detenido => 'Detenido',
|
||||
EstadoReproduccion.cargando => l10n.playbackStatusConnecting,
|
||||
EstadoReproduccion.reproduciendo => l10n.playbackStatusLive,
|
||||
EstadoReproduccion.pausado => l10n.playbackStatusPaused,
|
||||
EstadoReproduccion.error => l10n.playbackStatusConnectionError,
|
||||
EstadoReproduccion.detenido => l10n.playbackStatusStopped,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../tema/pluriwave_tokens.dart';
|
||||
import '../tema/pluriwave_theme.dart';
|
||||
|
||||
@@ -62,7 +63,7 @@ class PluriIcon extends StatelessWidget {
|
||||
: icon;
|
||||
|
||||
return Semantics(
|
||||
label: semanticLabel ?? _fallbackLabel(glyph),
|
||||
label: semanticLabel ?? _fallbackLabel(AppLocalizations.of(context), glyph),
|
||||
image: true,
|
||||
child: ExcludeSemantics(child: child),
|
||||
);
|
||||
@@ -108,14 +109,14 @@ class PluriIcon extends StatelessWidget {
|
||||
};
|
||||
}
|
||||
|
||||
String _fallbackLabel(PluriIconGlyph glyph) {
|
||||
String _fallbackLabel(AppLocalizations l10n, PluriIconGlyph glyph) {
|
||||
return switch (glyph) {
|
||||
PluriIconGlyph.home => 'Inicio',
|
||||
PluriIconGlyph.search => 'Buscar',
|
||||
PluriIconGlyph.favorites => 'Favoritos',
|
||||
PluriIconGlyph.alarm => 'Alarmas',
|
||||
PluriIconGlyph.player => 'Reproductor',
|
||||
PluriIconGlyph.settings => 'Ajustes',
|
||||
PluriIconGlyph.home => l10n.navHome,
|
||||
PluriIconGlyph.search => l10n.navSearch,
|
||||
PluriIconGlyph.favorites => l10n.navFavorites,
|
||||
PluriIconGlyph.alarm => l10n.navAlarms,
|
||||
PluriIconGlyph.player => l10n.playerIconLabel,
|
||||
PluriIconGlyph.settings => l10n.navSettings,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../servicios/servicio_contenido_app.dart';
|
||||
import 'pluri_glass_surface.dart';
|
||||
import 'pluri_markdown.dart';
|
||||
@@ -41,7 +42,7 @@ class _PluriOnboardingContent extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labels = _labels(Localizations.localeOf(context).languageCode);
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
return Dialog(
|
||||
insetPadding: const EdgeInsets.all(16),
|
||||
@@ -80,14 +81,14 @@ class _PluriOnboardingContent extends StatelessWidget {
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Text(
|
||||
labels.title,
|
||||
l10n.onboardingTitle,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: labels.close,
|
||||
tooltip: l10n.onboardingCloseTooltip,
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.close_rounded),
|
||||
),
|
||||
@@ -102,7 +103,7 @@ class _PluriOnboardingContent extends StatelessWidget {
|
||||
if (contenido.notas.isNotEmpty) ...[
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
labels.news,
|
||||
l10n.onboardingNewsTitle,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
@@ -131,7 +132,7 @@ class _PluriOnboardingContent extends StatelessWidget {
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.check_rounded),
|
||||
label: Text(labels.start),
|
||||
label: Text(l10n.onboardingStartAction),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -141,58 +142,3 @@ class _PluriOnboardingContent extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_OnboardingLabels _labels(String languageCode) {
|
||||
return switch (languageCode) {
|
||||
'es' => const _OnboardingLabels(
|
||||
title: 'Bienvenido a PluriWave',
|
||||
news: 'Novedades',
|
||||
start: 'Empezar',
|
||||
close: 'Cerrar',
|
||||
),
|
||||
'fr' => const _OnboardingLabels(
|
||||
title: 'Bienvenue sur PluriWave',
|
||||
news: 'Nouveautés',
|
||||
start: 'Commencer',
|
||||
close: 'Fermer',
|
||||
),
|
||||
'de' => const _OnboardingLabels(
|
||||
title: 'Willkommen bei PluriWave',
|
||||
news: 'Neuigkeiten',
|
||||
start: 'Starten',
|
||||
close: 'Schließen',
|
||||
),
|
||||
'it' => const _OnboardingLabels(
|
||||
title: 'Benvenuto in PluriWave',
|
||||
news: 'Novità',
|
||||
start: 'Inizia',
|
||||
close: 'Chiudi',
|
||||
),
|
||||
'pt' => const _OnboardingLabels(
|
||||
title: 'Bem-vindo ao PluriWave',
|
||||
news: 'Novidades',
|
||||
start: 'Começar',
|
||||
close: 'Fechar',
|
||||
),
|
||||
_ => const _OnboardingLabels(
|
||||
title: 'Welcome to PluriWave',
|
||||
news: 'What’s new',
|
||||
start: 'Start',
|
||||
close: 'Close',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
class _OnboardingLabels {
|
||||
const _OnboardingLabels({
|
||||
required this.title,
|
||||
required this.news,
|
||||
required this.start,
|
||||
required this.close,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String news;
|
||||
final String start;
|
||||
final String close;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:provider/provider.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../estado/estado_radio.dart';
|
||||
import '../l10n/display_names.dart';
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../modelos/emisora.dart';
|
||||
import '../tema/pluriwave_theme.dart';
|
||||
import 'pluri_glass_surface.dart';
|
||||
@@ -37,12 +39,14 @@ class _TarjetaEmisoraState extends State<TarjetaEmisora> {
|
||||
final esFav = await estado.toggleFavorito(widget.emisora);
|
||||
if (mounted) setState(() => _toggling = false);
|
||||
if (mounted) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final stationName = localizedStationName(l10n, widget.emisora.nombre);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
esFav
|
||||
? '${widget.emisora.nombre} añadida a favoritos'
|
||||
: '${widget.emisora.nombre} eliminada de favoritos',
|
||||
? l10n.favoritesAddedMessage(stationName)
|
||||
: l10n.favoritesRemovedMessage(stationName),
|
||||
),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
@@ -53,9 +57,11 @@ class _TarjetaEmisoraState extends State<TarjetaEmisora> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.pluriTokens;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final stationName = localizedStationName(l10n, widget.emisora.nombre);
|
||||
return Semantics(
|
||||
button: widget.onTap != null,
|
||||
label: 'Emisora ${widget.emisora.nombre}',
|
||||
label: l10n.stationSemanticLabel(stationName),
|
||||
child: PluriGlassSurface(
|
||||
padding: EdgeInsets.zero,
|
||||
borderRadius: BorderRadius.circular(
|
||||
@@ -74,6 +80,10 @@ class _TarjetaEmisoraState extends State<TarjetaEmisora> {
|
||||
|
||||
Widget _buildCompleta() {
|
||||
final t = context.pluriTokens;
|
||||
final stationName = localizedStationName(
|
||||
AppLocalizations.of(context),
|
||||
widget.emisora.nombre,
|
||||
);
|
||||
return Stack(
|
||||
children: [
|
||||
Column(
|
||||
@@ -116,7 +126,7 @@ class _TarjetaEmisoraState extends State<TarjetaEmisora> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.emisora.nombre,
|
||||
stationName,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
@@ -153,6 +163,10 @@ class _TarjetaEmisoraState extends State<TarjetaEmisora> {
|
||||
|
||||
Widget _buildCompacta() {
|
||||
final t = context.pluriTokens;
|
||||
final stationName = localizedStationName(
|
||||
AppLocalizations.of(context),
|
||||
widget.emisora.nombre,
|
||||
);
|
||||
final subtitulo = [
|
||||
widget.emisora.pais,
|
||||
widget.emisora.idioma,
|
||||
@@ -192,7 +206,7 @@ class _TarjetaEmisoraState extends State<TarjetaEmisora> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.emisora.nombre,
|
||||
stationName,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700),
|
||||
@@ -223,6 +237,7 @@ class _TarjetaEmisoraState extends State<TarjetaEmisora> {
|
||||
|
||||
Widget _botonFavorito({required bool mini}) {
|
||||
final t = context.pluriTokens;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final esFavorito = context.select<EstadoRadio, bool>(
|
||||
(estado) =>
|
||||
estado.listaFavoritos.any((e) => e.uuid == widget.emisora.uuid),
|
||||
@@ -248,13 +263,16 @@ class _TarjetaEmisoraState extends State<TarjetaEmisora> {
|
||||
: PluriIconVariant.outline,
|
||||
size: 20,
|
||||
semanticLabel:
|
||||
esFavorito ? 'Quitar de favoritos' : 'Añadir a favoritos',
|
||||
esFavorito
|
||||
? l10n.favoritesRemoveTooltip
|
||||
: l10n.favoritesAddTooltip,
|
||||
);
|
||||
|
||||
return Semantics(
|
||||
button: true,
|
||||
toggled: esFavorito,
|
||||
label: esFavorito ? 'Quitar de favoritos' : 'Añadir a favoritos',
|
||||
label:
|
||||
esFavorito ? l10n.favoritesRemoveTooltip : l10n.favoritesAddTooltip,
|
||||
child: Material(
|
||||
color: mini ? t.glassSurface : Colors.transparent,
|
||||
shape: const CircleBorder(),
|
||||
@@ -318,7 +336,7 @@ class _TarjetaEmisoraState extends State<TarjetaEmisora> {
|
||||
glyph: PluriIconGlyph.player,
|
||||
variant: PluriIconVariant.activeGlow,
|
||||
size: size,
|
||||
semanticLabel: 'Icono de emisora',
|
||||
semanticLabel: AppLocalizations.of(context).stationIconLabel,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -345,6 +363,7 @@ class _LiveBadge extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = Theme.of(context).colorScheme.secondary;
|
||||
final l10n = AppLocalizations.of(context);
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: mini ? 8 : 6, vertical: mini ? 5 : 4),
|
||||
decoration: BoxDecoration(
|
||||
@@ -358,7 +377,7 @@ class _LiveBadge extends StatelessWidget {
|
||||
Icon(Icons.fiber_manual_record_rounded, size: mini ? 10 : 8, color: color),
|
||||
if (mini) ...[
|
||||
const SizedBox(width: 5),
|
||||
Text('Live', style: Theme.of(context).textTheme.labelSmall?.copyWith(fontWeight: FontWeight.w900)),
|
||||
Text(l10n.liveNow, style: Theme.of(context).textTheme.labelSmall?.copyWith(fontWeight: FontWeight.w900)),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user