i18n(alarm): localize all native notification, channel and chooser texts
Build & Deploy PluriWave / Análisis de código (push) Successful in 40s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m33s

Centralize every native-side user-facing string in a single
AlarmNotificationStrings store written by Flutter via a new
setNotificationStrings MethodChannel whenever the app locale changes,
and read at notification/channel build time (with English fallbacks)
even when the engine is dead. This replaces the hardcoded Spanish text
in the ringing notification ("Alarma PluriWave", "Posponer", "Detener"),
the pre-notice notification ("Posponer", "Omitir esta vez"), both
notification channels (names + descriptions) and the file-action
choosers ("Abrir carpeta", "Abrir grabación").

The per-alarm preNoticeTemplate/snoozeCountdown template+label args are
dropped from scheduleAlarm and the persisted spec and folded into the
shared store, so a locale change now also relocalizes already-scheduled
alarms. Channels are re-created on each use so their name/description
refresh after a language switch.

Adds alarmRingingNotificationTitle, alarmFire/PreNoticeChannelName,
alarmFire/PreNoticeChannelDescription and openFolder/openRecording
chooser keys across all 13 locales (reusing snoozeAction, stopAlarmAction,
skipNextAction, snoozeAgainAction). Rewrites the template test around
setNotificationStrings. Kotlin is static-reviewed only; no Android build
environment available here.
This commit is contained in:
Javier Bautista Fernández
2026-06-30 15:41:29 +02:00
parent 481944815f
commit ffd09a2179
34 changed files with 639 additions and 144 deletions
+30 -22
View File
@@ -172,29 +172,41 @@ class ServicioAlarmasAndroid implements PuertoAlarmasAndroid {
@override
void configurarLocalizaciones(AppLocalizations l10n) {
_l10n = l10n;
// Push every localized notification/channel/chooser string to the native
// side so it can build localized notifications even when the Flutter engine
// is dead (alarm fired from a killed app). Fire-and-forget; runs once per
// locale change (callers guard against per-rebuild churn).
unawaited(_enviarTextosNotificacion(l10n));
}
/// Builds a pre-notice template string with a literal `{minutes}` placeholder
/// for Kotlin to replace at broadcast-receiver fire time.
///
/// Strategy: call [preNoticeCountdown] with a unique sentinel integer and
/// replace the sentinel's string representation with `{minutes}`.
static String _preNoticeTemplate(AppLocalizations l10n) {
const sentinel = 42424242;
return l10n.preNoticeCountdown(sentinel).replaceFirst(
sentinel.toString(),
'{minutes}',
);
Future<void> _enviarTextosNotificacion(AppLocalizations l10n) async {
try {
await _channel.invokeMethod<void>('setNotificationStrings', {
'ringTitle': l10n.alarmRingingNotificationTitle,
'snoozeLabel': l10n.snoozeAction,
'stopLabel': l10n.stopAlarmAction,
'skipLabel': l10n.skipNextAction,
'snoozeAgainLabel': l10n.snoozeAgainAction,
'fireChannelName': l10n.alarmFireChannelName,
'fireChannelDescription': l10n.alarmFireChannelDescription,
'preNoticeChannelName': l10n.alarmPreNoticeChannelName,
'preNoticeChannelDescription': l10n.alarmPreNoticeChannelDescription,
'preNoticeTemplate': _plantillaMinutos(l10n.preNoticeCountdown),
'snoozeCountdownTemplate': _plantillaMinutos(l10n.snoozeCountdown),
'openFolderTitle': l10n.openFolderChooserTitle,
'openRecordingTitle': l10n.openRecordingChooserTitle,
});
} catch (e) {
debugPrint('[PluriWave][alarmas] setNotificationStrings ERROR $e');
}
}
/// Same sentinel strategy as [_preNoticeTemplate], but for the snooze
/// countdown notification re-posted every minute while an alarm is snoozed.
static String _snoozeCountdownTemplate(AppLocalizations l10n) {
/// Turns a localized `{int} -> String` message into a template with a literal
/// `{minutes}` placeholder for Kotlin to fill at fire time: it calls the
/// message with a unique sentinel and swaps the sentinel back for `{minutes}`.
static String _plantillaMinutos(String Function(int) traducir) {
const sentinel = 42424242;
return l10n.snoozeCountdown(sentinel).replaceFirst(
sentinel.toString(),
'{minutes}',
);
return traducir(sentinel).replaceFirst(sentinel.toString(), '{minutes}');
}
@override
@@ -216,10 +228,6 @@ class ServicioAlarmasAndroid implements PuertoAlarmasAndroid {
final programada = await _channel.invokeMethod<bool>('scheduleAlarm', {
'id': alarma.id,
'title': localizedAlarmName(_textos, alarma.nombre),
'preNoticeTemplate': _preNoticeTemplate(_textos),
'snoozeCountdownTemplate': _snoozeCountdownTemplate(_textos),
'snoozeAgainLabel': _textos.snoozeAgainAction,
'snoozeStopLabel': _textos.stopAlarmAction,
'triggerAtMillis': proxima.millisecondsSinceEpoch,
'preNoticeAtMillis':
alarma.snoozeHasta == null