From ffd09a2179d1344446fd96a5d6f1892fe9847c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Bautista=20Fern=C3=A1ndez?= Date: Tue, 30 Jun 2026 15:41:29 +0200 Subject: [PATCH] i18n(alarm): localize all native notification, channel and chooser texts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../pluriwave/AlarmNotificationStrings.kt | 72 ++++++++++++ .../freetimelab/pluriwave/AlarmScheduler.kt | 54 ++------- .../es/freetimelab/pluriwave/MainActivity.kt | 24 ++-- .../pluriwave/PluriWaveAlarmReceiver.kt | 33 ++---- .../pluriwave/PluriWaveAlarmService.kt | 15 ++- lib/l10n/app_ar.arb | 7 ++ lib/l10n/app_bn.arb | 7 ++ lib/l10n/app_de.arb | 7 ++ lib/l10n/app_en.arb | 7 ++ lib/l10n/app_es.arb | 7 ++ lib/l10n/app_fr.arb | 7 ++ lib/l10n/app_hi.arb | 7 ++ lib/l10n/app_id.arb | 7 ++ lib/l10n/app_it.arb | 7 ++ lib/l10n/app_ja.arb | 7 ++ lib/l10n/app_pt.arb | 7 ++ lib/l10n/app_ru.arb | 7 ++ lib/l10n/app_zh.arb | 7 ++ lib/l10n/gen/app_localizations.dart | 42 +++++++ lib/l10n/gen/app_localizations_ar.dart | 22 ++++ lib/l10n/gen/app_localizations_bn.dart | 23 ++++ lib/l10n/gen/app_localizations_de.dart | 23 ++++ lib/l10n/gen/app_localizations_en.dart | 23 ++++ lib/l10n/gen/app_localizations_es.dart | 23 ++++ lib/l10n/gen/app_localizations_fr.dart | 23 ++++ lib/l10n/gen/app_localizations_hi.dart | 22 ++++ lib/l10n/gen/app_localizations_id.dart | 23 ++++ lib/l10n/gen/app_localizations_it.dart | 23 ++++ lib/l10n/gen/app_localizations_ja.dart | 21 ++++ lib/l10n/gen/app_localizations_pt.dart | 23 ++++ lib/l10n/gen/app_localizations_ru.dart | 23 ++++ lib/l10n/gen/app_localizations_zh.dart | 21 ++++ lib/servicios/servicio_alarmas_android.dart | 52 +++++---- ...icio_alarmas_pre_notice_template_test.dart | 107 +++++++++++------- 34 files changed, 639 insertions(+), 144 deletions(-) create mode 100644 android/app/src/main/kotlin/es/freetimelab/pluriwave/AlarmNotificationStrings.kt diff --git a/android/app/src/main/kotlin/es/freetimelab/pluriwave/AlarmNotificationStrings.kt b/android/app/src/main/kotlin/es/freetimelab/pluriwave/AlarmNotificationStrings.kt new file mode 100644 index 0000000..529fdf6 --- /dev/null +++ b/android/app/src/main/kotlin/es/freetimelab/pluriwave/AlarmNotificationStrings.kt @@ -0,0 +1,72 @@ +package es.freetimelab.pluriwave + +import android.content.Context + +/** + * Localized strings for native alarm notifications, channels and choosers. + * + * Flutter is the single source of truth for i18n: it pushes the current-locale + * strings via the `setNotificationStrings` MethodChannel whenever the app locale + * is (re)configured. They are persisted in device-protected storage so the + * native side can read them when building a notification or channel even while + * the Flutter engine is dead (alarm fired from a killed app, after reboot, in + * direct-boot). Every getter falls back to English when a value is unset. + */ +object AlarmNotificationStrings { + private const val PREFS = "pluriwave_alarm_strings" + + const val KEY_RING_TITLE = "ringTitle" + const val KEY_SNOOZE = "snoozeLabel" + const val KEY_STOP = "stopLabel" + const val KEY_SKIP = "skipLabel" + const val KEY_SNOOZE_AGAIN = "snoozeAgainLabel" + const val KEY_FIRE_CHANNEL_NAME = "fireChannelName" + const val KEY_FIRE_CHANNEL_DESC = "fireChannelDescription" + const val KEY_PRE_NOTICE_CHANNEL_NAME = "preNoticeChannelName" + const val KEY_PRE_NOTICE_CHANNEL_DESC = "preNoticeChannelDescription" + const val KEY_PRE_NOTICE_TEMPLATE = "preNoticeTemplate" + const val KEY_SNOOZE_COUNTDOWN_TEMPLATE = "snoozeCountdownTemplate" + const val KEY_OPEN_FOLDER = "openFolderTitle" + const val KEY_OPEN_RECORDING = "openRecordingTitle" + + /** Persists the localized strings pushed by Flutter. Blank values are removed. */ + fun save(context: Context, values: Map) { + val editor = prefs(context).edit() + for ((key, value) in values) { + val str = value as? String + if (str.isNullOrBlank()) editor.remove(key) else editor.putString(key, str) + } + editor.apply() + } + + fun ringTitle(context: Context) = get(context, KEY_RING_TITLE, "PluriWave alarm") + fun snoozeLabel(context: Context) = get(context, KEY_SNOOZE, "Snooze") + fun stopLabel(context: Context) = get(context, KEY_STOP, "Stop") + fun skipLabel(context: Context) = get(context, KEY_SKIP, "Skip this time") + fun snoozeAgainLabel(context: Context) = get(context, KEY_SNOOZE_AGAIN, "Snooze again") + fun fireChannelName(context: Context) = get(context, KEY_FIRE_CHANNEL_NAME, "Ringing alarms") + fun fireChannelDescription(context: Context) = + get(context, KEY_FIRE_CHANNEL_DESC, "Urgent sound and screen when a music alarm must ring") + fun preNoticeChannelName(context: Context) = + get(context, KEY_PRE_NOTICE_CHANNEL_NAME, "Alarm reminders") + fun preNoticeChannelDescription(context: Context) = + get(context, KEY_PRE_NOTICE_CHANNEL_DESC, "Silent notifications before the alarm") + fun openFolderTitle(context: Context) = get(context, KEY_OPEN_FOLDER, "Open folder") + fun openRecordingTitle(context: Context) = get(context, KEY_OPEN_RECORDING, "Open recording") + + fun preNoticeText(context: Context, minutes: Long): String = + format(get(context, KEY_PRE_NOTICE_TEMPLATE, "Starts in {minutes} min"), minutes) + + fun snoozeCountdownText(context: Context, minutes: Long): String = + format(get(context, KEY_SNOOZE_COUNTDOWN_TEMPLATE, "Rings in {minutes} min"), minutes) + + private fun format(template: String, minutes: Long): String = + template.replace("{minutes}", minutes.toString()) + + private fun get(context: Context, key: String, fallback: String): String = + prefs(context).getString(key, null)?.takeIf { it.isNotBlank() } ?: fallback + + private fun prefs(context: Context) = + context.applicationContext.createDeviceProtectedStorageContext() + .getSharedPreferences(PREFS, Context.MODE_PRIVATE) +} diff --git a/android/app/src/main/kotlin/es/freetimelab/pluriwave/AlarmScheduler.kt b/android/app/src/main/kotlin/es/freetimelab/pluriwave/AlarmScheduler.kt index 67fe616..2efefe7 100644 --- a/android/app/src/main/kotlin/es/freetimelab/pluriwave/AlarmScheduler.kt +++ b/android/app/src/main/kotlin/es/freetimelab/pluriwave/AlarmScheduler.kt @@ -42,11 +42,7 @@ class AlarmScheduler(private val context: Context) { snoozeMinutes: Int = 5, fallbackStationName: String? = null, fallbackStationUrl: String? = null, - fadeInSegundos: Int = 0, - preNoticeTemplate: String? = null, - snoozeCountdownTemplate: String? = null, - snoozeAgainLabel: String? = null, - snoozeStopLabel: String? = null + fadeInSegundos: Int = 0 ): Boolean { val existing = readSpec(id) val preservedSnooze = preserveNativeSnooze( @@ -77,11 +73,7 @@ class AlarmScheduler(private val context: Context) { fallbackSound = fallbackSound, volume = volume.coerceIn(0f, 1f), fadeInSegundos = fadeInSegundos.coerceIn(0, 60), - timezoneId = TimeZone.getDefault().id, - preNoticeTemplate = preNoticeTemplate, - snoozeCountdownTemplate = snoozeCountdownTemplate, - snoozeAgainLabel = snoozeAgainLabel, - snoozeStopLabel = snoozeStopLabel + timezoneId = TimeZone.getDefault().id ) return scheduleSpec(spec, persistOnSuccess = true) } @@ -168,7 +160,6 @@ class AlarmScheduler(private val context: Context) { PluriWaveAlarmReceiver.EXTRA_OCCURRENCE_AT, spec.snoozeOriginMillis ?: spec.triggerAtMillis ) - putExtra(EXTRA_PRE_NOTICE_TEMPLATE, spec.preNoticeTemplate) }, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) @@ -189,7 +180,6 @@ class AlarmScheduler(private val context: Context) { PluriWaveAlarmReceiver.EXTRA_OCCURRENCE_AT, spec.snoozeOriginMillis ?: spec.triggerAtMillis ) - putExtra(EXTRA_PRE_NOTICE_TEMPLATE, spec.preNoticeTemplate) } ) Log.d(tag, "alarm.schedule preNotice immediate id=${spec.id}") @@ -483,11 +473,7 @@ class AlarmScheduler(private val context: Context) { private fun postSnoozeCountdownNotification(spec: NativeAlarmSpec, remaining: Long) { ensurePreNoticeChannel() - val text = if (spec.snoozeCountdownTemplate.isNullOrBlank()) { - "Rings in $remaining min" - } else { - spec.snoozeCountdownTemplate.replace("{minutes}", remaining.toString()) - } + val text = AlarmNotificationStrings.snoozeCountdownText(appContext, remaining) val openIntent = PendingIntent.getActivity( appContext, @@ -531,8 +517,8 @@ class AlarmScheduler(private val context: Context) { .setOnlyAlertOnce(true) .setOngoing(true) .setContentIntent(openIntent) - .addAction(0, spec.snoozeAgainLabel ?: "Snooze again", againIntent) - .addAction(0, spec.snoozeStopLabel ?: "Stop", stopIntent) + .addAction(0, AlarmNotificationStrings.snoozeAgainLabel(appContext), againIntent) + .addAction(0, AlarmNotificationStrings.stopLabel(appContext), stopIntent) .build() try { @@ -548,12 +534,14 @@ class AlarmScheduler(private val context: Context) { private fun ensurePreNoticeChannel() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return val manager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - if (manager.getNotificationChannel(PluriWaveAlarmReceiver.CHANNEL_ID) != null) return + // Re-create each time so the localized name/description refresh after a + // locale change (Android updates them on an existing channel). val channel = NotificationChannel( PluriWaveAlarmReceiver.CHANNEL_ID, - "Preavisos de alarmas", + AlarmNotificationStrings.preNoticeChannelName(appContext), NotificationManager.IMPORTANCE_LOW ).apply { + description = AlarmNotificationStrings.preNoticeChannelDescription(appContext) setSound(null, null) enableVibration(false) } @@ -905,15 +893,7 @@ class AlarmScheduler(private val context: Context) { val fallbackSound: String?, val volume: Float, val fadeInSegundos: Int = 0, - val timezoneId: String, - // Nullable for backward compat: old persisted alarms without this field - // fall back to the English default in the receiver. Schema stays v3. - val preNoticeTemplate: String? = null, - // Localized template + button labels for the per-minute snooze countdown - // notification. Nullable so pre-existing persisted alarms keep working. - val snoozeCountdownTemplate: String? = null, - val snoozeAgainLabel: String? = null, - val snoozeStopLabel: String? = null + val timezoneId: String ) { fun toJson(): JSONObject = JSONObject().apply { put("schemaVersion", 3) @@ -940,10 +920,6 @@ class AlarmScheduler(private val context: Context) { put("volume", volume) put("fadeInSegundos", fadeInSegundos) put("timezoneId", timezoneId) - put("preNoticeTemplate", preNoticeTemplate) - put("snoozeCountdownTemplate", snoozeCountdownTemplate) - put("snoozeAgainLabel", snoozeAgainLabel) - put("snoozeStopLabel", snoozeStopLabel) } companion object { @@ -980,12 +956,7 @@ class AlarmScheduler(private val context: Context) { fallbackSound = json.optString("fallbackSound").takeIf { it.isNotBlank() }, volume = json.optDouble("volume", 0.85).toFloat(), fadeInSegundos = json.optInt("fadeInSegundos", 0).coerceIn(0, 60), - timezoneId = json.optString("timezoneId", TimeZone.getDefault().id), - preNoticeTemplate = json.optString("preNoticeTemplate").takeIf { it.isNotBlank() }, - snoozeCountdownTemplate = - json.optString("snoozeCountdownTemplate").takeIf { it.isNotBlank() }, - snoozeAgainLabel = json.optString("snoozeAgainLabel").takeIf { it.isNotBlank() }, - snoozeStopLabel = json.optString("snoozeStopLabel").takeIf { it.isNotBlank() } + timezoneId = json.optString("timezoneId", TimeZone.getDefault().id) ) } } @@ -1000,9 +971,6 @@ class AlarmScheduler(private val context: Context) { private const val PRE_NOTICE_MILLIS = 30 * 60 * 1000L private const val SCHEDULE_UNICA = "unica" private const val SCHEDULE_DIAS_SEMANA = "diasSemana" - // Intent extra key for the localized pre-notice template string. - // Declared once here; PluriWaveAlarmReceiver reads it via this constant. - const val EXTRA_PRE_NOTICE_TEMPLATE = "preNoticeTemplate" } } diff --git a/android/app/src/main/kotlin/es/freetimelab/pluriwave/MainActivity.kt b/android/app/src/main/kotlin/es/freetimelab/pluriwave/MainActivity.kt index 117c07d..35ec8f7 100644 --- a/android/app/src/main/kotlin/es/freetimelab/pluriwave/MainActivity.kt +++ b/android/app/src/main/kotlin/es/freetimelab/pluriwave/MainActivity.kt @@ -115,11 +115,7 @@ class MainActivity : AudioServiceActivity() { snoozeMinutes = call.argument("snoozeMinutes") ?: 5, fallbackStationName = call.argument("fallbackStationName"), fallbackStationUrl = call.argument("fallbackStationUrl"), - fadeInSegundos = call.argument("fadeInSegundos") ?: 0, - preNoticeTemplate = call.argument("preNoticeTemplate"), - snoozeCountdownTemplate = call.argument("snoozeCountdownTemplate"), - snoozeAgainLabel = call.argument("snoozeAgainLabel"), - snoozeStopLabel = call.argument("snoozeStopLabel") + fadeInSegundos = call.argument("fadeInSegundos") ?: 0 ) result.success(scheduled) } @@ -209,6 +205,16 @@ class MainActivity : AudioServiceActivity() { Log.d(tag, "alarm.channel getNativeSnoozeState") result.success(alarmScheduler.nativeSnoozeStates()) } + "setNotificationStrings" -> { + val args = call.arguments as? Map<*, *> + if (args != null) { + AlarmNotificationStrings.save( + this, + args.entries.associate { (k, v) -> k.toString() to v } + ) + } + result.success(null) + } else -> result.notImplemented() } } @@ -427,7 +433,9 @@ class MainActivity : AudioServiceActivity() { for (intent in candidates) { try { - startActivity(Intent.createChooser(intent, "Abrir carpeta")) + startActivity( + Intent.createChooser(intent, AlarmNotificationStrings.openFolderTitle(this)) + ) Log.d(tag, "file_actions.viewDirectory launched path=$path") return true } catch (_: ActivityNotFoundException) { @@ -471,7 +479,9 @@ class MainActivity : AudioServiceActivity() { clipData = ClipData.newUri(contentResolver, "recording", uri) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } - startActivity(Intent.createChooser(intent, "Abrir grabación")) + startActivity( + Intent.createChooser(intent, AlarmNotificationStrings.openRecordingTitle(this)) + ) Log.d(tag, "file_actions.openFile launched path=$path") true } catch (_: ActivityNotFoundException) { diff --git a/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmReceiver.kt b/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmReceiver.kt index d6db767..3171d08 100644 --- a/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmReceiver.kt +++ b/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmReceiver.kt @@ -51,8 +51,7 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() { title, snoozeMinutes, intent.getLongExtra(EXTRA_TRIGGER_AT, 0L), - intent.getLongExtra(EXTRA_OCCURRENCE_AT, 0L), - intent.getStringExtra(AlarmScheduler.EXTRA_PRE_NOTICE_TEMPLATE) + intent.getLongExtra(EXTRA_OCCURRENCE_AT, 0L) ) } ACTION_POSTPONE_NEXT -> { @@ -135,13 +134,12 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() { title: String, snoozeMinutes: Int, triggerAtMillis: Long, - occurrenceAtMillis: Long, - preNoticeTemplate: String? = null + occurrenceAtMillis: Long ) { ensureChannel(context) val remaining = computeRemainingMinutes(triggerAtMillis) - val contentText = formatPreNoticeText(preNoticeTemplate, remaining) + val contentText = AlarmNotificationStrings.preNoticeText(context, remaining) val openAppIntent = PendingIntent.getActivity( context, @@ -189,8 +187,8 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() { .setSilent(true) .setAutoCancel(true) .setContentIntent(openAppIntent) - .addAction(0, "Posponer", postponeNextIntent) - .addAction(0, "Omitir esta vez", skipNextIntent) + .addAction(0, AlarmNotificationStrings.snoozeLabel(context), postponeNextIntent) + .addAction(0, AlarmNotificationStrings.skipLabel(context), skipNextIntent) .build() try { @@ -208,30 +206,17 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() { private fun computeRemainingMinutes(triggerAtMillis: Long): Long = maxOf(1L, (triggerAtMillis - System.currentTimeMillis()) / 60_000L) - /** - * Formats the pre-notice notification text by replacing the `{minutes}` - * placeholder in [template] with [remaining]. Falls back to an English - * default if [template] is null or blank. - */ - private fun formatPreNoticeText(template: String?, remaining: Long): String { - if (template.isNullOrBlank()) { - return "Starts in $remaining min" - } - return template.replace("{minutes}", remaining.toString()) - } - private fun ensureChannel(context: Context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val existing = manager.getNotificationChannel(CHANNEL_ID) - if (existing != null) return - + // Re-create each time so the localized name/description refresh after a + // locale change (Android updates them on an existing channel). val channel = NotificationChannel( CHANNEL_ID, - "Preavisos de alarmas", + AlarmNotificationStrings.preNoticeChannelName(context), NotificationManager.IMPORTANCE_LOW ).apply { - description = "Notificaciones silenciosas 30 minutos antes de la alarma" + description = AlarmNotificationStrings.preNoticeChannelDescription(context) setSound(null, null) enableVibration(false) } diff --git a/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmService.kt b/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmService.kt index 3380698..a8da5cf 100644 --- a/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmService.kt +++ b/android/app/src/main/kotlin/es/freetimelab/pluriwave/PluriWaveAlarmService.kt @@ -388,7 +388,7 @@ class PluriWaveAlarmService : Service() { ) = NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(android.R.drawable.ic_lock_idle_alarm) - .setContentTitle("Alarma PluriWave") + .setContentTitle(AlarmNotificationStrings.ringTitle(this)) .setContentText( if (stationName.isNullOrBlank()) title else "$title - $stationName" ) @@ -399,8 +399,8 @@ class PluriWaveAlarmService : Service() { .setAutoCancel(false) .setFullScreenIntent(openAlarmPendingIntent(alarmId, title, snoozeMinutes), true) .setContentIntent(openAlarmPendingIntent(alarmId, title, snoozeMinutes)) - .addAction(0, "Posponer", snoozePendingIntent(alarmId, snoozeMinutes)) - .addAction(0, "Detener", stopPendingIntent(alarmId)) + .addAction(0, AlarmNotificationStrings.snoozeLabel(this), snoozePendingIntent(alarmId, snoozeMinutes)) + .addAction(0, AlarmNotificationStrings.stopLabel(this), stopPendingIntent(alarmId)) .build() private fun openAlarmPendingIntent( @@ -553,13 +553,16 @@ class PluriWaveAlarmService : Service() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager migrateLegacyChannels(context, manager) - if (manager.getNotificationChannel(CHANNEL_ID) != null) return + // Re-create each time (not early-returning when present) so the + // localized name/description refresh after a locale change. Android + // updates name + description on an existing channel; importance and + // sound stay fixed from first creation. val channel = NotificationChannel( CHANNEL_ID, - "Alarmas sonando", + AlarmNotificationStrings.fireChannelName(context), NotificationManager.IMPORTANCE_HIGH ).apply { - description = "Sonido y pantalla urgente cuando una alarma musical debe sonar" + description = AlarmNotificationStrings.fireChannelDescription(context) enableVibration(true) setSound( Settings.System.DEFAULT_ALARM_ALERT_URI, diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index 5e72261..20fb74f 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "غفوة مرة أخرى", + "alarmRingingNotificationTitle": "منبّه PluriWave", + "alarmFireChannelName": "المنبّهات الرنّانة", + "alarmFireChannelDescription": "صوت وشاشة عاجلة عندما يجب أن يرنّ منبّه موسيقي", + "alarmPreNoticeChannelName": "تنبيهات المنبّهات", + "alarmPreNoticeChannelDescription": "إشعارات صامتة قبل المنبّه", + "openFolderChooserTitle": "فتح المجلد", + "openRecordingChooserTitle": "فتح التسجيل", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_bn.arb b/lib/l10n/app_bn.arb index d666608..0fe4dc1 100644 --- a/lib/l10n/app_bn.arb +++ b/lib/l10n/app_bn.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "আবার স্নুজ করুন", + "alarmRingingNotificationTitle": "PluriWave অ্যালার্ম", + "alarmFireChannelName": "বাজতে থাকা অ্যালার্ম", + "alarmFireChannelDescription": "কোনো মিউজিক অ্যালার্ম বাজার সময় জরুরি শব্দ ও স্ক্রিন", + "alarmPreNoticeChannelName": "অ্যালার্ম রিমাইন্ডার", + "alarmPreNoticeChannelDescription": "অ্যালার্মের আগে নীরব বিজ্ঞপ্তি", + "openFolderChooserTitle": "ফোল্ডার খুলুন", + "openRecordingChooserTitle": "রেকর্ডিং খুলুন", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 453da3a..b408de8 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "Erneut schlummern", + "alarmRingingNotificationTitle": "PluriWave-Wecker", + "alarmFireChannelName": "Klingelnde Wecker", + "alarmFireChannelDescription": "Dringender Ton und Bildschirm, wenn ein Musikwecker klingeln soll", + "alarmPreNoticeChannelName": "Wecker-Vorankündigungen", + "alarmPreNoticeChannelDescription": "Lautlose Benachrichtigungen vor dem Wecker", + "openFolderChooserTitle": "Ordner öffnen", + "openRecordingChooserTitle": "Aufnahme öffnen", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d970916..ecd6674 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "Snooze again", + "alarmRingingNotificationTitle": "PluriWave alarm", + "alarmFireChannelName": "Ringing alarms", + "alarmFireChannelDescription": "Urgent sound and screen when a music alarm must ring", + "alarmPreNoticeChannelName": "Alarm reminders", + "alarmPreNoticeChannelDescription": "Silent notifications before the alarm", + "openFolderChooserTitle": "Open folder", + "openRecordingChooserTitle": "Open recording", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 2adba3a..58ad8ed 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -601,6 +601,13 @@ } }, "snoozeAgainAction": "Posponer otra vez", + "alarmRingingNotificationTitle": "Alarma PluriWave", + "alarmFireChannelName": "Alarmas sonando", + "alarmFireChannelDescription": "Sonido y pantalla urgente cuando una alarma musical debe sonar", + "alarmPreNoticeChannelName": "Preavisos de alarmas", + "alarmPreNoticeChannelDescription": "Notificaciones silenciosas antes de la alarma", + "openFolderChooserTitle": "Abrir carpeta", + "openRecordingChooserTitle": "Abrir grabación", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index cf1b819..7f7f173 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "Reporter encore", + "alarmRingingNotificationTitle": "Alarme PluriWave", + "alarmFireChannelName": "Alarmes en cours", + "alarmFireChannelDescription": "Son et écran urgents lorsqu'une alarme musicale doit sonner", + "alarmPreNoticeChannelName": "Rappels d'alarme", + "alarmPreNoticeChannelDescription": "Notifications silencieuses avant l'alarme", + "openFolderChooserTitle": "Ouvrir le dossier", + "openRecordingChooserTitle": "Ouvrir l'enregistrement", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index 84653c4..49e89c2 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "फिर से स्नूज़ करें", + "alarmRingingNotificationTitle": "PluriWave अलार्म", + "alarmFireChannelName": "बजते अलार्म", + "alarmFireChannelDescription": "जब कोई संगीत अलार्म बजना हो तो तत्काल ध्वनि और स्क्रीन", + "alarmPreNoticeChannelName": "अलार्म रिमाइंडर", + "alarmPreNoticeChannelDescription": "अलार्म से पहले मूक सूचनाएँ", + "openFolderChooserTitle": "फ़ोल्डर खोलें", + "openRecordingChooserTitle": "रिकॉर्डिंग खोलें", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index 9eb06fe..910ceca 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "Tunda lagi", + "alarmRingingNotificationTitle": "Alarm PluriWave", + "alarmFireChannelName": "Alarm berbunyi", + "alarmFireChannelDescription": "Suara dan layar mendesak saat alarm musik harus berbunyi", + "alarmPreNoticeChannelName": "Pengingat alarm", + "alarmPreNoticeChannelDescription": "Notifikasi senyap sebelum alarm", + "openFolderChooserTitle": "Buka folder", + "openRecordingChooserTitle": "Buka rekaman", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 35845a6..73dd0e3 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "Posponi di nuovo", + "alarmRingingNotificationTitle": "Sveglia PluriWave", + "alarmFireChannelName": "Sveglie in corso", + "alarmFireChannelDescription": "Suono e schermo urgenti quando una sveglia musicale deve suonare", + "alarmPreNoticeChannelName": "Promemoria sveglia", + "alarmPreNoticeChannelDescription": "Notifiche silenziose prima della sveglia", + "openFolderChooserTitle": "Apri cartella", + "openRecordingChooserTitle": "Apri registrazione", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index dd16fcd..fe0a69f 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "もう一度スヌーズ", + "alarmRingingNotificationTitle": "PluriWave アラーム", + "alarmFireChannelName": "鳴っているアラーム", + "alarmFireChannelDescription": "音楽アラームが鳴るときの緊急の音と画面", + "alarmPreNoticeChannelName": "アラームの事前通知", + "alarmPreNoticeChannelDescription": "アラーム前のサイレント通知", + "openFolderChooserTitle": "フォルダを開く", + "openRecordingChooserTitle": "録音を開く", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 6c778fa..90146cc 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "Adiar novamente", + "alarmRingingNotificationTitle": "Alarme PluriWave", + "alarmFireChannelName": "Alarmes tocando", + "alarmFireChannelDescription": "Som e tela urgentes quando um alarme musical deve tocar", + "alarmPreNoticeChannelName": "Avisos de alarme", + "alarmPreNoticeChannelDescription": "Notificações silenciosas antes do alarme", + "openFolderChooserTitle": "Abrir pasta", + "openRecordingChooserTitle": "Abrir gravação", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 5b96ef9..f0672cf 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "Отложить снова", + "alarmRingingNotificationTitle": "Будильник PluriWave", + "alarmFireChannelName": "Звонящие будильники", + "alarmFireChannelDescription": "Срочный звук и экран, когда должен сработать музыкальный будильник", + "alarmPreNoticeChannelName": "Напоминания о будильнике", + "alarmPreNoticeChannelDescription": "Беззвучные уведомления перед будильником", + "openFolderChooserTitle": "Открыть папку", + "openRecordingChooserTitle": "Открыть запись", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 0eaacff..95ddef1 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -638,6 +638,13 @@ } }, "snoozeAgainAction": "再次小睡", + "alarmRingingNotificationTitle": "PluriWave 闹钟", + "alarmFireChannelName": "响铃的闹钟", + "alarmFireChannelDescription": "音乐闹钟需要响起时的紧急声音和屏幕", + "alarmPreNoticeChannelName": "闹钟预告", + "alarmPreNoticeChannelDescription": "闹钟前的静音通知", + "openFolderChooserTitle": "打开文件夹", + "openRecordingChooserTitle": "打开录音", "@preNoticeCountdown": { "placeholders": { "minutes": { diff --git a/lib/l10n/gen/app_localizations.dart b/lib/l10n/gen/app_localizations.dart index 134c043..eb07965 100644 --- a/lib/l10n/gen/app_localizations.dart +++ b/lib/l10n/gen/app_localizations.dart @@ -2324,6 +2324,48 @@ abstract class AppLocalizations { /// **'Posponer otra vez'** String get snoozeAgainAction; + /// No description provided for @alarmRingingNotificationTitle. + /// + /// In es, this message translates to: + /// **'Alarma PluriWave'** + String get alarmRingingNotificationTitle; + + /// No description provided for @alarmFireChannelName. + /// + /// In es, this message translates to: + /// **'Alarmas sonando'** + String get alarmFireChannelName; + + /// No description provided for @alarmFireChannelDescription. + /// + /// In es, this message translates to: + /// **'Sonido y pantalla urgente cuando una alarma musical debe sonar'** + String get alarmFireChannelDescription; + + /// No description provided for @alarmPreNoticeChannelName. + /// + /// In es, this message translates to: + /// **'Preavisos de alarmas'** + String get alarmPreNoticeChannelName; + + /// No description provided for @alarmPreNoticeChannelDescription. + /// + /// In es, this message translates to: + /// **'Notificaciones silenciosas antes de la alarma'** + String get alarmPreNoticeChannelDescription; + + /// No description provided for @openFolderChooserTitle. + /// + /// In es, this message translates to: + /// **'Abrir carpeta'** + String get openFolderChooserTitle; + + /// No description provided for @openRecordingChooserTitle. + /// + /// In es, this message translates to: + /// **'Abrir grabación'** + String get openRecordingChooserTitle; + /// No description provided for @eqDeviceEditTitle. /// /// In es, this message translates to: diff --git a/lib/l10n/gen/app_localizations_ar.dart b/lib/l10n/gen/app_localizations_ar.dart index 3051bc8..c2ef6d4 100644 --- a/lib/l10n/gen/app_localizations_ar.dart +++ b/lib/l10n/gen/app_localizations_ar.dart @@ -1261,6 +1261,28 @@ class AppLocalizationsAr extends AppLocalizations { @override String get snoozeAgainAction => 'غفوة مرة أخرى'; + @override + String get alarmRingingNotificationTitle => 'منبّه PluriWave'; + + @override + String get alarmFireChannelName => 'المنبّهات الرنّانة'; + + @override + String get alarmFireChannelDescription => + 'صوت وشاشة عاجلة عندما يجب أن يرنّ منبّه موسيقي'; + + @override + String get alarmPreNoticeChannelName => 'تنبيهات المنبّهات'; + + @override + String get alarmPreNoticeChannelDescription => 'إشعارات صامتة قبل المنبّه'; + + @override + String get openFolderChooserTitle => 'فتح المجلد'; + + @override + String get openRecordingChooserTitle => 'فتح التسجيل'; + @override String get eqDeviceEditTitle => 'تعديل الجهاز'; diff --git a/lib/l10n/gen/app_localizations_bn.dart b/lib/l10n/gen/app_localizations_bn.dart index fc92ca0..aeac725 100644 --- a/lib/l10n/gen/app_localizations_bn.dart +++ b/lib/l10n/gen/app_localizations_bn.dart @@ -1268,6 +1268,29 @@ class AppLocalizationsBn extends AppLocalizations { @override String get snoozeAgainAction => 'আবার স্নুজ করুন'; + @override + String get alarmRingingNotificationTitle => 'PluriWave অ্যালার্ম'; + + @override + String get alarmFireChannelName => 'বাজতে থাকা অ্যালার্ম'; + + @override + String get alarmFireChannelDescription => + 'কোনো মিউজিক অ্যালার্ম বাজার সময় জরুরি শব্দ ও স্ক্রিন'; + + @override + String get alarmPreNoticeChannelName => 'অ্যালার্ম রিমাইন্ডার'; + + @override + String get alarmPreNoticeChannelDescription => + 'অ্যালার্মের আগে নীরব বিজ্ঞপ্তি'; + + @override + String get openFolderChooserTitle => 'ফোল্ডার খুলুন'; + + @override + String get openRecordingChooserTitle => 'রেকর্ডিং খুলুন'; + @override String get eqDeviceEditTitle => 'ডিভাইস সম্পাদনা করুন'; diff --git a/lib/l10n/gen/app_localizations_de.dart b/lib/l10n/gen/app_localizations_de.dart index da699f9..e380be1 100644 --- a/lib/l10n/gen/app_localizations_de.dart +++ b/lib/l10n/gen/app_localizations_de.dart @@ -1278,6 +1278,29 @@ class AppLocalizationsDe extends AppLocalizations { @override String get snoozeAgainAction => 'Erneut schlummern'; + @override + String get alarmRingingNotificationTitle => 'PluriWave-Wecker'; + + @override + String get alarmFireChannelName => 'Klingelnde Wecker'; + + @override + String get alarmFireChannelDescription => + 'Dringender Ton und Bildschirm, wenn ein Musikwecker klingeln soll'; + + @override + String get alarmPreNoticeChannelName => 'Wecker-Vorankündigungen'; + + @override + String get alarmPreNoticeChannelDescription => + 'Lautlose Benachrichtigungen vor dem Wecker'; + + @override + String get openFolderChooserTitle => 'Ordner öffnen'; + + @override + String get openRecordingChooserTitle => 'Aufnahme öffnen'; + @override String get eqDeviceEditTitle => 'Gerät bearbeiten'; diff --git a/lib/l10n/gen/app_localizations_en.dart b/lib/l10n/gen/app_localizations_en.dart index e434ddb..b56f0e1 100644 --- a/lib/l10n/gen/app_localizations_en.dart +++ b/lib/l10n/gen/app_localizations_en.dart @@ -1264,6 +1264,29 @@ class AppLocalizationsEn extends AppLocalizations { @override String get snoozeAgainAction => 'Snooze again'; + @override + String get alarmRingingNotificationTitle => 'PluriWave alarm'; + + @override + String get alarmFireChannelName => 'Ringing alarms'; + + @override + String get alarmFireChannelDescription => + 'Urgent sound and screen when a music alarm must ring'; + + @override + String get alarmPreNoticeChannelName => 'Alarm reminders'; + + @override + String get alarmPreNoticeChannelDescription => + 'Silent notifications before the alarm'; + + @override + String get openFolderChooserTitle => 'Open folder'; + + @override + String get openRecordingChooserTitle => 'Open recording'; + @override String get eqDeviceEditTitle => 'Edit device'; diff --git a/lib/l10n/gen/app_localizations_es.dart b/lib/l10n/gen/app_localizations_es.dart index 501ceea..8a700d8 100644 --- a/lib/l10n/gen/app_localizations_es.dart +++ b/lib/l10n/gen/app_localizations_es.dart @@ -1273,6 +1273,29 @@ class AppLocalizationsEs extends AppLocalizations { @override String get snoozeAgainAction => 'Posponer otra vez'; + @override + String get alarmRingingNotificationTitle => 'Alarma PluriWave'; + + @override + String get alarmFireChannelName => 'Alarmas sonando'; + + @override + String get alarmFireChannelDescription => + 'Sonido y pantalla urgente cuando una alarma musical debe sonar'; + + @override + String get alarmPreNoticeChannelName => 'Preavisos de alarmas'; + + @override + String get alarmPreNoticeChannelDescription => + 'Notificaciones silenciosas antes de la alarma'; + + @override + String get openFolderChooserTitle => 'Abrir carpeta'; + + @override + String get openRecordingChooserTitle => 'Abrir grabación'; + @override String get eqDeviceEditTitle => 'Editar dispositivo'; diff --git a/lib/l10n/gen/app_localizations_fr.dart b/lib/l10n/gen/app_localizations_fr.dart index 7a51628..a94fdfa 100644 --- a/lib/l10n/gen/app_localizations_fr.dart +++ b/lib/l10n/gen/app_localizations_fr.dart @@ -1283,6 +1283,29 @@ class AppLocalizationsFr extends AppLocalizations { @override String get snoozeAgainAction => 'Reporter encore'; + @override + String get alarmRingingNotificationTitle => 'Alarme PluriWave'; + + @override + String get alarmFireChannelName => 'Alarmes en cours'; + + @override + String get alarmFireChannelDescription => + 'Son et écran urgents lorsqu\'une alarme musicale doit sonner'; + + @override + String get alarmPreNoticeChannelName => 'Rappels d\'alarme'; + + @override + String get alarmPreNoticeChannelDescription => + 'Notifications silencieuses avant l\'alarme'; + + @override + String get openFolderChooserTitle => 'Ouvrir le dossier'; + + @override + String get openRecordingChooserTitle => 'Ouvrir l\'enregistrement'; + @override String get eqDeviceEditTitle => 'Modifier l\'appareil'; diff --git a/lib/l10n/gen/app_localizations_hi.dart b/lib/l10n/gen/app_localizations_hi.dart index 6b1dbc9..6c3146d 100644 --- a/lib/l10n/gen/app_localizations_hi.dart +++ b/lib/l10n/gen/app_localizations_hi.dart @@ -1267,6 +1267,28 @@ class AppLocalizationsHi extends AppLocalizations { @override String get snoozeAgainAction => 'फिर से स्नूज़ करें'; + @override + String get alarmRingingNotificationTitle => 'PluriWave अलार्म'; + + @override + String get alarmFireChannelName => 'बजते अलार्म'; + + @override + String get alarmFireChannelDescription => + 'जब कोई संगीत अलार्म बजना हो तो तत्काल ध्वनि और स्क्रीन'; + + @override + String get alarmPreNoticeChannelName => 'अलार्म रिमाइंडर'; + + @override + String get alarmPreNoticeChannelDescription => 'अलार्म से पहले मूक सूचनाएँ'; + + @override + String get openFolderChooserTitle => 'फ़ोल्डर खोलें'; + + @override + String get openRecordingChooserTitle => 'रिकॉर्डिंग खोलें'; + @override String get eqDeviceEditTitle => 'डिवाइस संपादित करें'; diff --git a/lib/l10n/gen/app_localizations_id.dart b/lib/l10n/gen/app_localizations_id.dart index 2a9a3f1..06db23a 100644 --- a/lib/l10n/gen/app_localizations_id.dart +++ b/lib/l10n/gen/app_localizations_id.dart @@ -1272,6 +1272,29 @@ class AppLocalizationsId extends AppLocalizations { @override String get snoozeAgainAction => 'Tunda lagi'; + @override + String get alarmRingingNotificationTitle => 'Alarm PluriWave'; + + @override + String get alarmFireChannelName => 'Alarm berbunyi'; + + @override + String get alarmFireChannelDescription => + 'Suara dan layar mendesak saat alarm musik harus berbunyi'; + + @override + String get alarmPreNoticeChannelName => 'Pengingat alarm'; + + @override + String get alarmPreNoticeChannelDescription => + 'Notifikasi senyap sebelum alarm'; + + @override + String get openFolderChooserTitle => 'Buka folder'; + + @override + String get openRecordingChooserTitle => 'Buka rekaman'; + @override String get eqDeviceEditTitle => 'Edit perangkat'; diff --git a/lib/l10n/gen/app_localizations_it.dart b/lib/l10n/gen/app_localizations_it.dart index ffa22dd..41e5b7f 100644 --- a/lib/l10n/gen/app_localizations_it.dart +++ b/lib/l10n/gen/app_localizations_it.dart @@ -1278,6 +1278,29 @@ class AppLocalizationsIt extends AppLocalizations { @override String get snoozeAgainAction => 'Posponi di nuovo'; + @override + String get alarmRingingNotificationTitle => 'Sveglia PluriWave'; + + @override + String get alarmFireChannelName => 'Sveglie in corso'; + + @override + String get alarmFireChannelDescription => + 'Suono e schermo urgenti quando una sveglia musicale deve suonare'; + + @override + String get alarmPreNoticeChannelName => 'Promemoria sveglia'; + + @override + String get alarmPreNoticeChannelDescription => + 'Notifiche silenziose prima della sveglia'; + + @override + String get openFolderChooserTitle => 'Apri cartella'; + + @override + String get openRecordingChooserTitle => 'Apri registrazione'; + @override String get eqDeviceEditTitle => 'Modifica dispositivo'; diff --git a/lib/l10n/gen/app_localizations_ja.dart b/lib/l10n/gen/app_localizations_ja.dart index f96f012..7192358 100644 --- a/lib/l10n/gen/app_localizations_ja.dart +++ b/lib/l10n/gen/app_localizations_ja.dart @@ -1228,6 +1228,27 @@ class AppLocalizationsJa extends AppLocalizations { @override String get snoozeAgainAction => 'もう一度スヌーズ'; + @override + String get alarmRingingNotificationTitle => 'PluriWave アラーム'; + + @override + String get alarmFireChannelName => '鳴っているアラーム'; + + @override + String get alarmFireChannelDescription => '音楽アラームが鳴るときの緊急の音と画面'; + + @override + String get alarmPreNoticeChannelName => 'アラームの事前通知'; + + @override + String get alarmPreNoticeChannelDescription => 'アラーム前のサイレント通知'; + + @override + String get openFolderChooserTitle => 'フォルダを開く'; + + @override + String get openRecordingChooserTitle => '録音を開く'; + @override String get eqDeviceEditTitle => 'デバイスを編集'; diff --git a/lib/l10n/gen/app_localizations_pt.dart b/lib/l10n/gen/app_localizations_pt.dart index 908e133..16e3606 100644 --- a/lib/l10n/gen/app_localizations_pt.dart +++ b/lib/l10n/gen/app_localizations_pt.dart @@ -1270,6 +1270,29 @@ class AppLocalizationsPt extends AppLocalizations { @override String get snoozeAgainAction => 'Adiar novamente'; + @override + String get alarmRingingNotificationTitle => 'Alarme PluriWave'; + + @override + String get alarmFireChannelName => 'Alarmes tocando'; + + @override + String get alarmFireChannelDescription => + 'Som e tela urgentes quando um alarme musical deve tocar'; + + @override + String get alarmPreNoticeChannelName => 'Avisos de alarme'; + + @override + String get alarmPreNoticeChannelDescription => + 'Notificações silenciosas antes do alarme'; + + @override + String get openFolderChooserTitle => 'Abrir pasta'; + + @override + String get openRecordingChooserTitle => 'Abrir gravação'; + @override String get eqDeviceEditTitle => 'Editar dispositivo'; diff --git a/lib/l10n/gen/app_localizations_ru.dart b/lib/l10n/gen/app_localizations_ru.dart index b42dee4..1158c50 100644 --- a/lib/l10n/gen/app_localizations_ru.dart +++ b/lib/l10n/gen/app_localizations_ru.dart @@ -1274,6 +1274,29 @@ class AppLocalizationsRu extends AppLocalizations { @override String get snoozeAgainAction => 'Отложить снова'; + @override + String get alarmRingingNotificationTitle => 'Будильник PluriWave'; + + @override + String get alarmFireChannelName => 'Звонящие будильники'; + + @override + String get alarmFireChannelDescription => + 'Срочный звук и экран, когда должен сработать музыкальный будильник'; + + @override + String get alarmPreNoticeChannelName => 'Напоминания о будильнике'; + + @override + String get alarmPreNoticeChannelDescription => + 'Беззвучные уведомления перед будильником'; + + @override + String get openFolderChooserTitle => 'Открыть папку'; + + @override + String get openRecordingChooserTitle => 'Открыть запись'; + @override String get eqDeviceEditTitle => 'Изменить устройство'; diff --git a/lib/l10n/gen/app_localizations_zh.dart b/lib/l10n/gen/app_localizations_zh.dart index 8a06489..bafff6c 100644 --- a/lib/l10n/gen/app_localizations_zh.dart +++ b/lib/l10n/gen/app_localizations_zh.dart @@ -1221,6 +1221,27 @@ class AppLocalizationsZh extends AppLocalizations { @override String get snoozeAgainAction => '再次小睡'; + @override + String get alarmRingingNotificationTitle => 'PluriWave 闹钟'; + + @override + String get alarmFireChannelName => '响铃的闹钟'; + + @override + String get alarmFireChannelDescription => '音乐闹钟需要响起时的紧急声音和屏幕'; + + @override + String get alarmPreNoticeChannelName => '闹钟预告'; + + @override + String get alarmPreNoticeChannelDescription => '闹钟前的静音通知'; + + @override + String get openFolderChooserTitle => '打开文件夹'; + + @override + String get openRecordingChooserTitle => '打开录音'; + @override String get eqDeviceEditTitle => '编辑设备'; diff --git a/lib/servicios/servicio_alarmas_android.dart b/lib/servicios/servicio_alarmas_android.dart index 7a9b944..c686727 100644 --- a/lib/servicios/servicio_alarmas_android.dart +++ b/lib/servicios/servicio_alarmas_android.dart @@ -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 _enviarTextosNotificacion(AppLocalizations l10n) async { + try { + await _channel.invokeMethod('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('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 diff --git a/test/servicios/servicio_alarmas_pre_notice_template_test.dart b/test/servicios/servicio_alarmas_pre_notice_template_test.dart index 737f610..9c7fab3 100644 --- a/test/servicios/servicio_alarmas_pre_notice_template_test.dart +++ b/test/servicios/servicio_alarmas_pre_notice_template_test.dart @@ -1,5 +1,8 @@ +import 'dart:ui' show Locale; + import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:pluriwave/l10n/gen/app_localizations.dart'; import 'package:pluriwave/modelos/alarma_musical.dart'; import 'package:pluriwave/servicios/servicio_alarmas_android.dart'; @@ -25,55 +28,75 @@ void main() { }); test( - 'programar includes preNoticeTemplate with {minutes} placeholder in MethodChannel call', + 'configurarLocalizaciones pushes localized notification strings with {minutes} templates', () async { final servicio = ServicioAlarmasAndroid(channel: channel); - final alarma = AlarmaMusical( - id: 'test-alarm', - nombre: 'Morning alarm', - hora: 7, - minuto: 0, - tipoProgramacion: TipoProgramacionAlarma.diaria, - diasSemana: const [], - proximaEjecucion: DateTime(2099, 1, 1, 7, 0), + final l10n = lookupAppLocalizations(const Locale('es')); + + servicio.configurarLocalizaciones(l10n); + // configurarLocalizaciones fires setNotificationStrings as unawaited; let + // the microtask/event queue drain so the MethodChannel call is recorded. + await Future.delayed(Duration.zero); + + final llamada = llamadas.singleWhere( + (c) => c.method == 'setNotificationStrings', + ); + final args = llamada.arguments as Map; + + expect( + args['preNoticeTemplate'], + contains('{minutes}'), + reason: 'pre-notice template must keep the {minutes} placeholder', + ); + expect( + args['snoozeCountdownTemplate'], + contains('{minutes}'), + reason: 'snooze countdown template must keep the {minutes} placeholder', ); - await servicio.programar(alarma); - - final llamada = llamadas.singleWhere((c) => c.method == 'scheduleAlarm'); - final args = llamada.arguments as Map; - expect(args.containsKey('preNoticeTemplate'), isTrue, - reason: 'preNoticeTemplate must be present in scheduleAlarm args'); - final template = args['preNoticeTemplate'] as String?; - expect(template, isNotNull, - reason: 'preNoticeTemplate must not be null'); - expect(template, contains('{minutes}'), - reason: 'preNoticeTemplate must contain the {minutes} placeholder'); + // Every notification/channel/chooser string must be present and non-empty + // so the native side never falls back to English for a configured locale. + const claves = [ + 'ringTitle', + 'snoozeLabel', + 'stopLabel', + 'skipLabel', + 'snoozeAgainLabel', + 'fireChannelName', + 'fireChannelDescription', + 'preNoticeChannelName', + 'preNoticeChannelDescription', + 'openFolderTitle', + 'openRecordingTitle', + ]; + for (final clave in claves) { + expect(args[clave], isA(), reason: '$clave missing'); + expect( + (args[clave] as String).isNotEmpty, + isTrue, + reason: '$clave is empty', + ); + } }, ); - test( - 'programar preNoticeTemplate uses default locale fallback when no l10n configured', - () async { - // ServicioAlarmasAndroid falls back to es locale when no l10n is configured - final servicio = ServicioAlarmasAndroid(channel: channel); - final alarma = AlarmaMusical( - id: 'test-alarm-2', - nombre: 'Alarm', - hora: 8, - minuto: 30, - tipoProgramacion: TipoProgramacionAlarma.diaria, - diasSemana: const [], - proximaEjecucion: DateTime(2099, 1, 2, 8, 30), - ); + test('scheduleAlarm no longer carries notification string templates', () async { + final servicio = ServicioAlarmasAndroid(channel: channel); + final alarma = AlarmaMusical( + id: 'no-template', + nombre: 'Alarm', + hora: 8, + minuto: 30, + tipoProgramacion: TipoProgramacionAlarma.diaria, + diasSemana: const [], + proximaEjecucion: DateTime(2099, 1, 2, 8, 30), + ); - await servicio.programar(alarma); + await servicio.programar(alarma); - final llamada = llamadas.singleWhere((c) => c.method == 'scheduleAlarm'); - final args = llamada.arguments as Map; - final template = args['preNoticeTemplate'] as String?; - // The template must contain the literal placeholder string - expect(template, contains('{minutes}')); - }, - ); + final llamada = llamadas.singleWhere((c) => c.method == 'scheduleAlarm'); + final args = llamada.arguments as Map; + expect(args.containsKey('preNoticeTemplate'), isFalse); + expect(args.containsKey('snoozeCountdownTemplate'), isFalse); + }); }