fix(i18n): normalize translations and fallbacks
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' show Locale;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../l10n/display_names.dart';
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../modelos/alarma_musical.dart';
|
||||
|
||||
class EventoAlarmaAndroid {
|
||||
@@ -114,6 +117,17 @@ class ServicioAlarmasAndroid implements PuertoAlarmasAndroid {
|
||||
static final _eventosController =
|
||||
StreamController<EventoAlarmaAndroid>.broadcast();
|
||||
static bool _handlerInstalado = false;
|
||||
static AppLocalizations? _l10n;
|
||||
|
||||
static AppLocalizations get _textos {
|
||||
final actual = _l10n;
|
||||
if (actual != null) return actual;
|
||||
return lookupAppLocalizations(const Locale('es'));
|
||||
}
|
||||
|
||||
static void configurarLocalizaciones(AppLocalizations l10n) {
|
||||
_l10n = l10n;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<EventoAlarmaAndroid> get eventosAlarma => _eventosController.stream;
|
||||
@@ -133,7 +147,7 @@ class ServicioAlarmasAndroid implements PuertoAlarmasAndroid {
|
||||
);
|
||||
final programada = await _channel.invokeMethod<bool>('scheduleAlarm', {
|
||||
'id': alarma.id,
|
||||
'title': alarma.nombre,
|
||||
'title': localizedAlarmName(_textos, alarma.nombre),
|
||||
'triggerAtMillis': proxima.millisecondsSinceEpoch,
|
||||
'preNoticeAtMillis':
|
||||
alarma.snoozeHasta == null
|
||||
@@ -150,15 +164,16 @@ class ServicioAlarmasAndroid implements PuertoAlarmasAndroid {
|
||||
'lastHandledAtMillis':
|
||||
alarma.ultimaEjecucionGestionada?.millisecondsSinceEpoch,
|
||||
'soundOnVacation': alarma.sonarEnVacaciones,
|
||||
'stationName': alarma.emisora?.nombre,
|
||||
'stationName':
|
||||
alarma.emisora == null
|
||||
? null
|
||||
: localizedStationName(_textos, alarma.emisora!.nombre),
|
||||
'stationUrl': alarma.emisora?.url,
|
||||
'fallbackSound': alarma.sonidoInterno.name,
|
||||
'volume': alarma.volumen,
|
||||
});
|
||||
if (programada != true) {
|
||||
throw StateError(
|
||||
'Android no pudo programar una alarma exacta. Revisa el permiso de alarmas exactas.',
|
||||
);
|
||||
throw StateError(_textos.androidExactAlarmScheduleError);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer' as developer;
|
||||
import 'dart:ui' show Locale;
|
||||
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
|
||||
import '../l10n/display_names.dart';
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../modelos/emisora.dart';
|
||||
import '../modelos/preset_ecualizador.dart';
|
||||
|
||||
@@ -31,6 +34,10 @@ class ServicioAudio {
|
||||
|
||||
Emisora? get emisoraActual => _handler.emisoraActual;
|
||||
|
||||
void configurarLocalizaciones(AppLocalizations l10n) {
|
||||
_handler.configurarLocalizaciones(l10n);
|
||||
}
|
||||
|
||||
Stream<EstadoReproduccion> get estadoStream =>
|
||||
_handler.playbackState.map((s) {
|
||||
if (s.processingState == AudioProcessingState.error) {
|
||||
@@ -50,7 +57,10 @@ class ServicioAudio {
|
||||
Future<void> reproducir(Emisora emisora) async {
|
||||
final item = MediaItem(
|
||||
id: emisora.url,
|
||||
title: emisora.nombre,
|
||||
title: localizedStationName(
|
||||
lookupAppLocalizations(const Locale('es')),
|
||||
emisora.nombre,
|
||||
),
|
||||
artist: emisora.pais ?? '',
|
||||
album: 'PluriWave',
|
||||
artUri:
|
||||
@@ -118,6 +128,7 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
Emisora? emisoraActual;
|
||||
double _volumen = 1.0;
|
||||
double get volumen => _volumen;
|
||||
AppLocalizations? _l10n;
|
||||
|
||||
AndroidEqualizer? get ecualizador => _eq;
|
||||
bool _eqDisponible = false;
|
||||
@@ -135,6 +146,16 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
_conectarStreamsPlayer();
|
||||
}
|
||||
|
||||
AppLocalizations get _textos {
|
||||
final actual = _l10n;
|
||||
if (actual != null) return actual;
|
||||
return lookupAppLocalizations(const Locale('es'));
|
||||
}
|
||||
|
||||
void configurarLocalizaciones(AppLocalizations l10n) {
|
||||
_l10n = l10n;
|
||||
}
|
||||
|
||||
AudioPlayer _crearPlayer() {
|
||||
return AudioPlayer(
|
||||
audioPipeline: AudioPipeline(androidAudioEffects: [_eq]),
|
||||
@@ -192,7 +213,7 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
mensaje = _mensajeAmigable(error);
|
||||
} else {
|
||||
codigoLog = 'Error desconocido: $error';
|
||||
mensaje = 'Error de reproducción';
|
||||
mensaje = _textos.audioErrorGeneric;
|
||||
}
|
||||
|
||||
developer.log(
|
||||
@@ -219,30 +240,30 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
final code = e.code;
|
||||
|
||||
if (code >= 2000 && code < 3000) {
|
||||
if (code == 2001) return 'Sin conexión a internet';
|
||||
if (code == 2002) return 'La URL de la radio no es válida';
|
||||
if (code == 2003) return 'La radio no está disponible (error 404)';
|
||||
if (code == 2004) return 'Tiempo de espera agotado al conectar';
|
||||
return 'No se puede conectar a la radio';
|
||||
if (code == 2001) return _textos.audioErrorNoInternet;
|
||||
if (code == 2002) return _textos.audioErrorInvalidUrl;
|
||||
if (code == 2003) return _textos.audioErrorNotFound;
|
||||
if (code == 2004) return _textos.audioErrorTimeout;
|
||||
return _textos.audioErrorCannotConnect;
|
||||
}
|
||||
|
||||
if (code >= 3000 && code < 4000) {
|
||||
return 'Formato de stream no compatible';
|
||||
return _textos.audioErrorUnsupportedFormat;
|
||||
}
|
||||
|
||||
if (code >= 4000 && code < 5000) {
|
||||
return 'Error al decodificar el stream de audio';
|
||||
return _textos.audioErrorDecode;
|
||||
}
|
||||
|
||||
final msg = e.message ?? '';
|
||||
if (msg.contains('Cleartext') || msg.contains('cleartext')) {
|
||||
return 'Esta radio usa HTTP sin cifrar (no permitido)';
|
||||
return _textos.audioErrorCleartext;
|
||||
}
|
||||
if (msg.contains('CERTIFICATE') || msg.contains('HandshakeException')) {
|
||||
return 'Certificado SSL inválido en la radio';
|
||||
return _textos.audioErrorSsl;
|
||||
}
|
||||
|
||||
return 'No se puede reproducir esta radio';
|
||||
return _textos.audioErrorCannotPlay;
|
||||
}
|
||||
|
||||
AudioProcessingState _mapProcState(ProcessingState state) {
|
||||
@@ -300,7 +321,7 @@ class PluriWaveAudioHandler extends BaseAudioHandler with SeekHandler {
|
||||
playbackState.value.copyWith(
|
||||
processingState: AudioProcessingState.error,
|
||||
playing: false,
|
||||
errorMessage: 'Error inesperado al reproducir',
|
||||
errorMessage: _textos.audioErrorUnexpectedPlayback,
|
||||
),
|
||||
);
|
||||
emisoraActual = null;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:ui' show Locale;
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../modelos/emisora.dart';
|
||||
|
||||
enum EstadoGrabacionRadioTipo {
|
||||
@@ -92,6 +94,7 @@ class ServicioGrabacionRadio {
|
||||
final Future<Directory> Function()? _resolverDirectorioBase;
|
||||
final DateTime Function() _reloj;
|
||||
final _estadoController = StreamController<EstadoGrabacionRadio>.broadcast();
|
||||
AppLocalizations? _l10n;
|
||||
|
||||
EstadoGrabacionRadio _estado = const EstadoGrabacionRadio.inactiva();
|
||||
StreamSubscription<List<int>>? _subscripcionStream;
|
||||
@@ -108,6 +111,16 @@ class ServicioGrabacionRadio {
|
||||
int get maxBytes => _maxBytes;
|
||||
File? get ultimoArchivo => _ultimoArchivo;
|
||||
|
||||
AppLocalizations get _textos {
|
||||
final actual = _l10n;
|
||||
if (actual != null) return actual;
|
||||
return lookupAppLocalizations(const Locale('es'));
|
||||
}
|
||||
|
||||
void configurarLocalizaciones(AppLocalizations l10n) {
|
||||
_l10n = l10n;
|
||||
}
|
||||
|
||||
Future<void> inicializar() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -134,7 +147,7 @@ class ServicioGrabacionRadio {
|
||||
Future<void> guardarDirectorio(String path) async {
|
||||
final normalizado = path.trim();
|
||||
if (normalizado.isEmpty) {
|
||||
throw ArgumentError('La ruta de grabación no puede estar vacía');
|
||||
throw ArgumentError(_textos.recordingPathEmptyError);
|
||||
}
|
||||
_directorioConfigurado = normalizado;
|
||||
try {
|
||||
@@ -155,7 +168,7 @@ class ServicioGrabacionRadio {
|
||||
|
||||
Future<void> guardarMaxBytes(int bytes) async {
|
||||
if (bytes <= 0) {
|
||||
throw ArgumentError('El tamaño máximo debe ser mayor que cero');
|
||||
throw ArgumentError(_textos.recordingMaxSizeInvalidError);
|
||||
}
|
||||
_maxBytes = bytes;
|
||||
try {
|
||||
@@ -171,7 +184,7 @@ class ServicioGrabacionRadio {
|
||||
String? directorio,
|
||||
}) async {
|
||||
if (_estado.activa) {
|
||||
throw StateError('Ya hay una grabación en curso');
|
||||
throw StateError(_textos.recordingAlreadyActiveError);
|
||||
}
|
||||
|
||||
final inicio = _reloj();
|
||||
|
||||
Reference in New Issue
Block a user