Author SHA1 Message Date
FreeTLab 3ec41bb31e feat(i18n): add redesign strings and translate Escuchar rename to 11 locales 2026-07-29 16:35:30 +02:00
FreeTLab 2959941485 fix(bienvenida): wire the welcome screen into the first-launch flow 2026-07-29 15:49:44 +02:00
FreeTLab f3d744aeed fix(ajustes): dispose sheet text controllers after the close animation
Pre-existing bug, reproduces identically before this branch's changes:
_editarGrupo (pantalla_ajustes_grupos_favoritos.dart) and
_editarTamanoMaximo (pantalla_ajustes_grabaciones.dart) each created a
TextEditingController, awaited showModalBottomSheet, then disposed the
controller immediately on resolve - racing the sheet's own close
animation, which still holds a bound TextField for a couple more
frames. Manifests as "A TextEditingController was used after being
disposed" plus a couple of cascading framework-internal symptoms.

Fix: extract each sheet's content into its own StatefulWidget
(_HojaEditarGrupo, _HojaTamanoMaximo) that owns the controller in its
own State. Flutter only calls State.dispose() once the widget is
actually removed from the tree, i.e. after the close animation
finishes, so there is no dispose-timing decision left for the caller
to get wrong.

_editarGrupo's own test previously suppressed the crash via a
FlutterError.onError override instead of fixing it; that suppression
is removed here. _editarTamanoMaximo had no coverage at all for this
interaction; added it. Strict TDD: confirmed both sites fail without
the fix (RED) before applying it (GREEN).
2026-07-29 15:22:15 +02:00
FreeTLab 9b415e73b0 feat(bienvenida): add monetization-free welcome screen
Build the first-run welcome surface from mockup screen 14, stripped of
its entire monetization block: no PRO pill, no "14 dias PRO gratis"
trial line, no pricing card, no secondary "free version" link. Ships
only logo, headline, body copy, exactly 3 feature bullets, and the
single "Empezar a escuchar" CTA, as a full-screen route (not a modal).

Spanish copy is re-cast from the mockup's "tu" form to the app's
established voseo register (matching ~750 existing app_es.arb lines),
using "auto" instead of "coche" per the one existing precedent. The 3
bullet icons reuse existing tokens (electricMagenta/liveGreen/
warmCoral) that already match the mockup's own hex values for them.

CTA switches to the Escuchar tab and pops the route. Wiring this
screen into the real first-launch flow (main.dart/app.dart) is left
for a follow-up unit, same shape as WU15/WU15b - this WU only covers
the isolated, tested screen per its own task list and verify command.

WU17.
2026-07-29 15:04:44 +02:00
FreeTLab 862197ab48 feat(connectivity): restyle offline and reconnect banners
Tint the mini player's reconnecting/error sub-states with the
offlineAccent token (added in WU1, unused until now): the status
label, the reconnect spinner, and the error retry icon now read as
visually distinct "connectivity trouble" states instead of blending
into the ordinary loading/paused look. Plain buffering keeps the
default colour, confirmed by a dedicated regression test.

Verify-first gate (task 16.1): ControladorReconexion.intentos exists,
but ServicioAudio never surfaces it past a debug log line, and its
estadoStream only carries the EstadoReproduccion enum. Adding an
attempt-count label would require a getter/stream on
servicio_audio.dart, one of the files this change must keep at an
empty diff against main. Ship the restyle without the counter, per
the risk register's own fallback.

WU16.
2026-07-29 14:43:34 +02:00
44 changed files with 4047 additions and 974 deletions
+21 -4
View File
@@ -14,6 +14,7 @@ import 'l10n/gen/app_localizations.dart';
import 'modelos/alarma_musical.dart';
import 'pantallas/pantalla_alarmas.dart';
import 'pantallas/pantalla_alarma_sonando.dart';
import 'pantallas/pantalla_bienvenida.dart';
import 'pantallas/pantalla_inicio.dart';
import 'pantallas/pantalla_buscar.dart';
import 'pantallas/pantalla_favoritos.dart';
@@ -108,7 +109,10 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal>
EstadoRadio? _estadoSuscrito;
bool _alarmaInicialProcesada = false;
bool _alarmaSonandoActiva = false;
bool _onboardingInicialSolicitado = false;
// WU17b: renamed from `_onboardingInicialSolicitado` — this single guard
// now covers the whole first-launch sequence (welcome screen, then the
// pre-existing what's-new dialog), not only the dialog.
bool _flujoPrimerLanzamientoSolicitado = false;
String? _alarmaSonandoId;
Locale? _localeAlarmasConfigurado;
@@ -191,9 +195,9 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal>
_alarmaInicialProcesada = true;
unawaited(_procesarAlarmaInicial(alarmas));
}
if (!_onboardingInicialSolicitado) {
_onboardingInicialSolicitado = true;
unawaited(_mostrarOnboardingInicial());
if (!_flujoPrimerLanzamientoSolicitado) {
_flujoPrimerLanzamientoSolicitado = true;
unawaited(_mostrarFlujoPrimerLanzamiento());
}
}
@@ -278,6 +282,19 @@ class _PaginaPrincipalState extends State<_PaginaPrincipal>
}
}
// WU17b: runs the welcome screen's once-ever check BEFORE the recurring
// what's-new dialog, so the two never show at the same time. The welcome
// screen (PantallaBienvenida) is the genuine first-run surface; the
// pre-existing PluriOnboardingDialog is an unrelated "what's new"/help
// modal that keeps its own independent per-version due-or-not logic,
// completely unchanged by this sequencing.
Future<void> _mostrarFlujoPrimerLanzamiento() async {
if (mounted) {
await PantallaBienvenida.mostrarSiProcede(context);
}
await _mostrarOnboardingInicial();
}
Future<void> _mostrarOnboardingInicial() async {
await Future<void>.delayed(const Duration(milliseconds: 900));
if (!mounted || _alarmaSonandoActiva) return;
+124 -3
View File
@@ -1,7 +1,7 @@
{
"@@locale": "ar",
"appTitle": "PluriWave",
"navHome": "الرئيسية",
"navHome": "استماع",
"navSearch": "بحث",
"navFavorites": "المفضلة",
"navAlarms": "المنبهات",
@@ -687,5 +687,126 @@
"error": {}
}
},
"localMusicFolderGenericName": "مجلد محدد"
}
"localMusicFolderGenericName": "مجلد محدد",
"addVacationRangeCta": "إضافة فترة",
"alarmAdvancedSectionTitle": "متقدم",
"alarmDeleteConfirmMessage": "لا يمكن التراجع عن هذا الإجراء.",
"alarmDeleteConfirmTitle": "هل تريد حذف المنبه؟",
"alarmHeroSkipAction": "تخطي",
"alarmInlineHourLabel": "الساعة",
"alarmInlineMinuteLabel": "الدقيقة",
"alarmVolumeRisingStatus": "رفع مستوى الصوت تدريجيًا",
"countriesAllTitle": "كل الدول",
"countriesScreenTitle": "الدول",
"countriesYourLanguagesTitle": "لغاتك",
"customStationsAddCta": "إضافة محطة مخصصة",
"equalizerActiveOutputDefault": "مكبر صوت هذا الجهاز",
"equalizerActiveOutputLabel": "المخرج النشط",
"equalizerBaseExplainer": "هذا هو المعادل الأساسي: يُطبَّق على كل محطة لا تملك إعدادًا خاصًا بها. يُحدَّد معادل محطة معينة من شاشة تشغيلها الخاصة، وله الأولوية على هذا المعادل.",
"equalizerSaveAsPresetAction": "حفظ كإعداد مسبق",
"equalizerSavePresetConfirm": "حفظ",
"equalizerSavePresetDialogTitle": "حفظ كإعداد مسبق",
"equalizerSavePresetEmptyNameError": "أدخل اسمًا للإعداد المسبق.",
"equalizerSavePresetNameLabel": "اسم الإعداد المسبق",
"equalizerStationsWithOwnEqEmpty": "لا توجد محطة لديها معادل خاص حتى الآن.",
"equalizerStationsWithOwnEqSubtitle": "تتجاهل هذا المعادل الأساسي",
"equalizerStationsWithOwnEqTitle": "محطات لديها معادل خاص",
"favoriteGroupsChipLabel": "{groupName} · {count}",
"@favoriteGroupsChipLabel": {
"placeholders": {
"groupName": {},
"count": {
"type": "int"
}
}
},
"favoriteGroupsManage": "إدارة القوائم",
"favoritesFilterAllLabel": "الكل",
"infoSectionTitle": "معلومات",
"nothingPlayingSubtitle": "اختر محطة من محطاتك أو ابحث عن واحدة للبدء.",
"nothingPlayingTitle": "لا تستمع إلى شيء بعد",
"openFullPlayerTooltip": "فتح المشغل الكامل",
"playerQualityChangeAction": "تغيير",
"playerToolEqLabel": "معادل خاص",
"radioCountriesError": "تعذّر تحميل الدول.",
"recordingActionDelete": "حذف",
"recordingActionRename": "إعادة تسمية",
"recordingActionShare": "مشاركة",
"recordingDeleteConfirmMessage": "لا يمكن التراجع عن هذا الإجراء.",
"recordingDeleteConfirmTitle": "هل تريد حذف التسجيل؟",
"recordingRenameDialogTitle": "إعادة تسمية التسجيل",
"recordingRenameEmptyError": "أدخل اسمًا",
"recordingRenameLabel": "الاسم",
"recordingsLibraryEmptySubtitle": "التسجيلات التي تحفظها ستظهر هنا.",
"recordingsLibraryEmptyTitle": "لا توجد تسجيلات بعد",
"recordingsLibrarySettingsTooltip": "إعدادات التسجيل",
"recordingsLibraryStorageCaption": "تم استخدام {usedMb} ميغابايت من أصل {totalMb} ميغابايت",
"@recordingsLibraryStorageCaption": {
"placeholders": {
"usedMb": {
"type": "int"
},
"totalMb": {
"type": "int"
}
}
},
"recordingsLibraryTitle": "تسجيلاتي",
"searchClearFiltersAction": "{count, plural, one{إزالة الفلتر} two{إزالة الفلترين} few{إزالة {count} فلاتر} other{إزالة {count} فلتر}}",
"searchResultsCount": "{count, plural, one{نتيجة واحدة} two{نتيجتان} few{{count} نتائج} other{{count} نتيجة}}",
"seeAllAction": "عرض الكل",
"settingsGroupApplicationTitle": "التطبيق",
"settingsGroupAudioTitle": "الصوت",
"settingsGroupRecordingsTitle": "التسجيلات والموسيقى",
"settingsGroupStationsTitle": "المحطات",
"stationOrderByPopularity": "حسب الشهرة",
"vacationImpactContinuesLabel": "يستمر الرنين: {times}",
"@vacationImpactContinuesLabel": {
"placeholders": {
"times": {}
}
},
"vacationImpactPausedLabel": "إيقاف مؤقت: {times}",
"@vacationImpactPausedLabel": {
"placeholders": {
"times": {}
}
},
"vacationNoActiveRangeHint": "لا توجد فترة إجازة نشطة الآن.",
"vacationPastSectionTitle": "الفترات السابقة",
"vacationRangesCount": "{count} فترة",
"@vacationRangesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"vacationSummaryActiveCountdown": "جارٍ الآن · تبقّى {days} يوم",
"@vacationSummaryActiveCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationSummaryUpcomingCountdown": "الفترة التالية خلال {days} يوم",
"@vacationSummaryUpcomingCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationUpcomingSectionTitle": "المجدولة",
"welcomeBody": "53,412 محطة من 238 دولة. احفظ محطاتك المفضلة، خذها معك في السيارة، واستيقظ عليها.",
"welcomeBullet1Subtitle": "بالإضافة إلى إعداد مسبق لكل جهاز خرج",
"welcomeBullet1Title": "معادل لكل محطة",
"welcomeBullet2Subtitle": "محطاتك المفضلة وموسيقاك المحلية في السيارة",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet3Subtitle": "مع رفع تدريجي للصوت ووضع الإجازة",
"welcomeBullet3Title": "منبهات موسيقية",
"welcomeCtaLabel": "ابدأ الاستماع",
"welcomeHeadline": "عالمك، على الهواء مباشرة",
"yourStationsTitle": "محطاتك"
}
+124 -3
View File
@@ -1,7 +1,7 @@
{
"@@locale": "bn",
"appTitle": "PluriWave",
"navHome": "হোম",
"navHome": "শোনা",
"navSearch": "অনুসন্ধান",
"navFavorites": "প্রিয়",
"navAlarms": "অ্যালার্ম",
@@ -687,5 +687,126 @@
"error": {}
}
},
"localMusicFolderGenericName": "নির্বাচিত ফোল্ডার"
}
"localMusicFolderGenericName": "নির্বাচিত ফোল্ডার",
"addVacationRangeCta": "সময়সীমা যোগ করুন",
"alarmAdvancedSectionTitle": "উন্নত",
"alarmDeleteConfirmMessage": "এই পদক্ষেপ ফিরিয়ে নেওয়া যাবে না।",
"alarmDeleteConfirmTitle": "অ্যালার্ম মুছবেন?",
"alarmHeroSkipAction": "এড়িয়ে যান",
"alarmInlineHourLabel": "ঘণ্টা",
"alarmInlineMinuteLabel": "মিনিট",
"alarmVolumeRisingStatus": "ভলিউম বাড়ছে",
"countriesAllTitle": "সব দেশ",
"countriesScreenTitle": "দেশসমূহ",
"countriesYourLanguagesTitle": "আপনার ভাষাসমূহ",
"customStationsAddCta": "নিজস্ব স্টেশন যোগ করুন",
"equalizerActiveOutputDefault": "এই ডিভাইসের স্পিকার",
"equalizerActiveOutputLabel": "সক্রিয় আউটপুট",
"equalizerBaseExplainer": "এটি বেস ইকুয়ালাইজার: এটি নিজস্ব সেটিং নেই এমন সব স্টেশনে প্রযোজ্য। কোনো নির্দিষ্ট স্টেশনের নিজস্ব EQ তার প্লেব্যাক স্ক্রিন থেকে ঠিক করা হয় এবং এটির চেয়ে অগ্রাধিকার পায়।",
"equalizerSaveAsPresetAction": "প্রিসেট হিসেবে সংরক্ষণ করুন",
"equalizerSavePresetConfirm": "সংরক্ষণ করুন",
"equalizerSavePresetDialogTitle": "প্রিসেট হিসেবে সংরক্ষণ করুন",
"equalizerSavePresetEmptyNameError": "প্রিসেটের জন্য একটি নাম লিখুন।",
"equalizerSavePresetNameLabel": "প্রিসেটের নাম",
"equalizerStationsWithOwnEqEmpty": "এখনও কোনো স্টেশনের নিজস্ব EQ নেই।",
"equalizerStationsWithOwnEqSubtitle": "এই বেস ইকুয়ালাইজার উপেক্ষা করে",
"equalizerStationsWithOwnEqTitle": "নিজস্ব EQ থাকা স্টেশন",
"favoriteGroupsChipLabel": "{groupName} · {count}",
"@favoriteGroupsChipLabel": {
"placeholders": {
"groupName": {},
"count": {
"type": "int"
}
}
},
"favoriteGroupsManage": "তালিকা পরিচালনা করুন",
"favoritesFilterAllLabel": "সব",
"infoSectionTitle": "তথ্য",
"nothingPlayingSubtitle": "শুরু করতে আপনার স্টেশন থেকে একটি বেছে নিন বা একটি খুঁজুন।",
"nothingPlayingTitle": "এখনও কিছু চলছে না",
"openFullPlayerTooltip": "সম্পূর্ণ প্লেয়ার খুলুন",
"playerQualityChangeAction": "পরিবর্তন করুন",
"playerToolEqLabel": "নিজস্ব EQ",
"radioCountriesError": "দেশগুলো লোড করা যায়নি।",
"recordingActionDelete": "মুছে ফেলুন",
"recordingActionRename": "নাম পরিবর্তন করুন",
"recordingActionShare": "শেয়ার করুন",
"recordingDeleteConfirmMessage": "এই পদক্ষেপ ফিরিয়ে নেওয়া যাবে না।",
"recordingDeleteConfirmTitle": "রেকর্ডিং মুছবেন?",
"recordingRenameDialogTitle": "রেকর্ডিং-এর নাম পরিবর্তন করুন",
"recordingRenameEmptyError": "একটি নাম লিখুন",
"recordingRenameLabel": "নাম",
"recordingsLibraryEmptySubtitle": "আপনি যেসব রেকর্ডিং সংরক্ষণ করবেন তা এখানে দেখা যাবে।",
"recordingsLibraryEmptyTitle": "এখনও কোনো রেকর্ডিং নেই",
"recordingsLibrarySettingsTooltip": "রেকর্ডিং সেটিংস",
"recordingsLibraryStorageCaption": "{totalMb} MB-এর মধ্যে {usedMb} MB ব্যবহৃত",
"@recordingsLibraryStorageCaption": {
"placeholders": {
"usedMb": {
"type": "int"
},
"totalMb": {
"type": "int"
}
}
},
"recordingsLibraryTitle": "আমার রেকর্ডিং",
"searchClearFiltersAction": "{count, plural, =1{ফিল্টার সরান} other{{count}টি ফিল্টার সরান}}",
"searchResultsCount": "{count, plural, =1{১টি ফলাফল} other{{count}টি ফলাফল}}",
"seeAllAction": "সব দেখুন",
"settingsGroupApplicationTitle": "অ্যাপ্লিকেশন",
"settingsGroupAudioTitle": "অডিও",
"settingsGroupRecordingsTitle": "রেকর্ডিং ও সংগীত",
"settingsGroupStationsTitle": "স্টেশন",
"stationOrderByPopularity": "জনপ্রিয়তা অনুসারে",
"vacationImpactContinuesLabel": "বাজতেই থাকবে: {times}",
"@vacationImpactContinuesLabel": {
"placeholders": {
"times": {}
}
},
"vacationImpactPausedLabel": "বিরতি: {times}",
"@vacationImpactPausedLabel": {
"placeholders": {
"times": {}
}
},
"vacationNoActiveRangeHint": "এই মুহূর্তে কোনো সক্রিয় ছুটির সময়সীমা নেই।",
"vacationPastSectionTitle": "অতীত সময়সীমা",
"vacationRangesCount": "{count}টি সময়সীমা",
"@vacationRangesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"vacationSummaryActiveCountdown": "চলমান · আর {days} দিন বাকি",
"@vacationSummaryActiveCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationSummaryUpcomingCountdown": "পরবর্তী সময়সীমা {days} দিনে",
"@vacationSummaryUpcomingCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationUpcomingSectionTitle": "নির্ধারিত",
"welcomeBody": "২৩৮টি দেশের ৫৩,৪১২টি স্টেশন। আপনার প্রিয় স্টেশন সংরক্ষণ করুন, গাড়িতে নিয়ে যান এবং সেগুলো দিয়ে ঘুম থেকে উঠুন।",
"welcomeBullet1Subtitle": "এবং প্রতিটি আউটপুট ডিভাইসের জন্য একটি প্রিসেট",
"welcomeBullet1Title": "স্টেশন অনুযায়ী ইকুয়ালাইজার",
"welcomeBullet2Subtitle": "গাড়িতে আপনার প্রিয় ও স্থানীয় সংগীত",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet3Subtitle": "ধীরে ভলিউম বৃদ্ধি ও ছুটির মোড সহ",
"welcomeBullet3Title": "সুরেলা অ্যালার্ম",
"welcomeCtaLabel": "শোনা শুরু করুন",
"welcomeHeadline": "আপনার বিশ্ব, সরাসরি",
"yourStationsTitle": "আপনার স্টেশন"
}
+124 -3
View File
@@ -1,7 +1,7 @@
{
"@@locale": "de",
"appTitle": "PluriWave",
"navHome": "Start",
"navHome": "Hören",
"navSearch": "Suche",
"navFavorites": "Favoriten",
"navAlarms": "Alarme",
@@ -687,5 +687,126 @@
"error": {}
}
},
"localMusicFolderGenericName": "Ausgewählter Ordner"
}
"localMusicFolderGenericName": "Ausgewählter Ordner",
"addVacationRangeCta": "Zeitraum hinzufügen",
"alarmAdvancedSectionTitle": "Erweitert",
"alarmDeleteConfirmMessage": "Dies kann nicht rückgängig gemacht werden.",
"alarmDeleteConfirmTitle": "Alarm löschen?",
"alarmHeroSkipAction": "Überspringen",
"alarmInlineHourLabel": "Stunde",
"alarmInlineMinuteLabel": "Minute",
"alarmVolumeRisingStatus": "Lautstärke wird erhöht",
"countriesAllTitle": "Alle Länder",
"countriesScreenTitle": "Länder",
"countriesYourLanguagesTitle": "Deine Sprachen",
"customStationsAddCta": "Benutzerdefinierten Sender hinzufügen",
"equalizerActiveOutputDefault": "Der Lautsprecher dieses Geräts",
"equalizerActiveOutputLabel": "Aktiver Ausgang",
"equalizerBaseExplainer": "Dies ist der Basis-Equalizer: Er gilt für alle Sender ohne eigene Einstellung. Der eigene EQ eines Senders wird auf dessen eigenem Wiedergabebildschirm festgelegt und hat Vorrang vor diesem hier.",
"equalizerSaveAsPresetAction": "Als Preset speichern",
"equalizerSavePresetConfirm": "Speichern",
"equalizerSavePresetDialogTitle": "Als Preset speichern",
"equalizerSavePresetEmptyNameError": "Gib einen Namen für das Preset ein.",
"equalizerSavePresetNameLabel": "Preset-Name",
"equalizerStationsWithOwnEqEmpty": "Noch kein Sender hat einen eigenen EQ.",
"equalizerStationsWithOwnEqSubtitle": "Ignorieren diesen Basis-Equalizer",
"equalizerStationsWithOwnEqTitle": "Sender mit eigenem EQ",
"favoriteGroupsChipLabel": "{groupName} · {count}",
"@favoriteGroupsChipLabel": {
"placeholders": {
"groupName": {},
"count": {
"type": "int"
}
}
},
"favoriteGroupsManage": "Listen verwalten",
"favoritesFilterAllLabel": "Alle",
"infoSectionTitle": "Info",
"nothingPlayingSubtitle": "Wähle einen Sender aus Deine Sender oder suche einen, um zu starten.",
"nothingPlayingTitle": "Es läuft noch nichts",
"openFullPlayerTooltip": "Vollständigen Player öffnen",
"playerQualityChangeAction": "Ändern",
"playerToolEqLabel": "Eigener EQ",
"radioCountriesError": "Die Länder konnten nicht geladen werden.",
"recordingActionDelete": "Löschen",
"recordingActionRename": "Umbenennen",
"recordingActionShare": "Teilen",
"recordingDeleteConfirmMessage": "Dies kann nicht rückgängig gemacht werden.",
"recordingDeleteConfirmTitle": "Aufnahme löschen?",
"recordingRenameDialogTitle": "Aufnahme umbenennen",
"recordingRenameEmptyError": "Gib einen Namen ein",
"recordingRenameLabel": "Name",
"recordingsLibraryEmptySubtitle": "Aufnahmen, die du speicherst, erscheinen hier.",
"recordingsLibraryEmptyTitle": "Noch keine Aufnahmen",
"recordingsLibrarySettingsTooltip": "Aufnahmeeinstellungen",
"recordingsLibraryStorageCaption": "{usedMb} MB von {totalMb} MB belegt",
"@recordingsLibraryStorageCaption": {
"placeholders": {
"usedMb": {
"type": "int"
},
"totalMb": {
"type": "int"
}
}
},
"recordingsLibraryTitle": "Meine Aufnahmen",
"searchClearFiltersAction": "{count, plural, =1{Filter entfernen} other{{count} Filter entfernen}}",
"searchResultsCount": "{count, plural, =1{1 Ergebnis} other{{count} Ergebnisse}}",
"seeAllAction": "Alle ansehen",
"settingsGroupApplicationTitle": "ANWENDUNG",
"settingsGroupAudioTitle": "AUDIO",
"settingsGroupRecordingsTitle": "AUFNAHMEN & MUSIK",
"settingsGroupStationsTitle": "SENDER",
"stationOrderByPopularity": "Nach Beliebtheit",
"vacationImpactContinuesLabel": "Klingelt weiter: {times}",
"@vacationImpactContinuesLabel": {
"placeholders": {
"times": {}
}
},
"vacationImpactPausedLabel": "Pausiert: {times}",
"@vacationImpactPausedLabel": {
"placeholders": {
"times": {}
}
},
"vacationNoActiveRangeHint": "Momentan ist kein aktiver Urlaubszeitraum vorhanden.",
"vacationPastSectionTitle": "Vergangene Zeiträume",
"vacationRangesCount": "{count} Zeiträume",
"@vacationRangesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"vacationSummaryActiveCountdown": "Läuft gerade · noch {days} Tage",
"@vacationSummaryActiveCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationSummaryUpcomingCountdown": "Nächster Zeitraum in {days} Tagen",
"@vacationSummaryUpcomingCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationUpcomingSectionTitle": "GEPLANT",
"welcomeBody": "53.412 Sender aus 238 Ländern. Speichere deine Favoriten, nimm sie mit ins Auto und wache mit ihnen auf.",
"welcomeBullet1Subtitle": "Plus ein Preset pro Ausgabegerät",
"welcomeBullet1Title": "Equalizer pro Sender",
"welcomeBullet2Subtitle": "Deine Favoriten und lokale Musik im Auto",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet3Subtitle": "Mit sanftem Lautstärkeanstieg und Urlaubsmodus",
"welcomeBullet3Title": "Musikalarme",
"welcomeCtaLabel": "Jetzt hören",
"welcomeHeadline": "Deine Welt, live",
"yourStationsTitle": "Deine Sender"
}
+10 -1
View File
@@ -799,5 +799,14 @@
"error": {}
}
},
"localMusicFolderGenericName": "Selected folder"
"localMusicFolderGenericName": "Selected folder",
"welcomeHeadline": "Your world, live",
"welcomeBody": "53,412 stations from 238 countries. Save your favorites, take them to the car, and wake up to them.",
"welcomeBullet1Title": "Per-station equalizer",
"welcomeBullet1Subtitle": "Plus a preset per output device",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet2Subtitle": "Your favorites and local music in the car",
"welcomeBullet3Title": "Music alarms",
"welcomeBullet3Subtitle": "With gradual volume rise and vacation mode",
"welcomeCtaLabel": "Start listening"
}
+10 -1
View File
@@ -758,5 +758,14 @@
"error": {}
}
},
"localMusicFolderGenericName": "Carpeta seleccionada"
"localMusicFolderGenericName": "Carpeta seleccionada",
"welcomeHeadline": "Tu mundo, en directo",
"welcomeBody": "53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.",
"welcomeBullet1Title": "Ecualizador por emisora",
"welcomeBullet1Subtitle": "Y un preset por dispositivo de salida",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet2Subtitle": "Tus favoritas y tu música local en el auto",
"welcomeBullet3Title": "Alarmas musicales",
"welcomeBullet3Subtitle": "Con subida progresiva y modo vacaciones",
"welcomeCtaLabel": "Empezar a escuchar"
}
+124 -3
View File
@@ -1,7 +1,7 @@
{
"@@locale": "fr",
"appTitle": "PluriWave",
"navHome": "Accueil",
"navHome": "Écouter",
"navSearch": "Recherche",
"navFavorites": "Favoris",
"navAlarms": "Alarmes",
@@ -687,5 +687,126 @@
"error": {}
}
},
"localMusicFolderGenericName": "Dossier sélectionné"
}
"localMusicFolderGenericName": "Dossier sélectionné",
"addVacationRangeCta": "Ajouter une période",
"alarmAdvancedSectionTitle": "Avancé",
"alarmDeleteConfirmMessage": "Cette action est irréversible.",
"alarmDeleteConfirmTitle": "Supprimer l'alarme ?",
"alarmHeroSkipAction": "Ignorer",
"alarmInlineHourLabel": "Heure",
"alarmInlineMinuteLabel": "Minute",
"alarmVolumeRisingStatus": "Augmentation du volume",
"countriesAllTitle": "Tous les pays",
"countriesScreenTitle": "Pays",
"countriesYourLanguagesTitle": "Vos langues",
"customStationsAddCta": "Ajouter une station personnalisée",
"equalizerActiveOutputDefault": "Le haut-parleur de cet appareil",
"equalizerActiveOutputLabel": "Sortie active",
"equalizerBaseExplainer": "Voici l'égaliseur de base : il s'applique à toutes les stations sans réglage propre. L'EQ propre à une station est défini depuis son propre écran de lecture et prime sur celui-ci.",
"equalizerSaveAsPresetAction": "Enregistrer comme Preset",
"equalizerSavePresetConfirm": "Enregistrer",
"equalizerSavePresetDialogTitle": "Enregistrer comme Preset",
"equalizerSavePresetEmptyNameError": "Saisissez un nom pour le Preset.",
"equalizerSavePresetNameLabel": "Nom du Preset",
"equalizerStationsWithOwnEqEmpty": "Aucune station n'a encore d'EQ propre.",
"equalizerStationsWithOwnEqSubtitle": "Ignorent cet égaliseur de base",
"equalizerStationsWithOwnEqTitle": "Stations avec un EQ propre",
"favoriteGroupsChipLabel": "{groupName} · {count}",
"@favoriteGroupsChipLabel": {
"placeholders": {
"groupName": {},
"count": {
"type": "int"
}
}
},
"favoriteGroupsManage": "Gérer les listes",
"favoritesFilterAllLabel": "Toutes",
"infoSectionTitle": "Infos",
"nothingPlayingSubtitle": "Choisissez une station dans Vos stations ou recherchez-en une pour commencer.",
"nothingPlayingTitle": "Rien n'est en cours de lecture",
"openFullPlayerTooltip": "Ouvrir le lecteur complet",
"playerQualityChangeAction": "Modifier",
"playerToolEqLabel": "EQ propre",
"radioCountriesError": "Impossible de charger les pays.",
"recordingActionDelete": "Supprimer",
"recordingActionRename": "Renommer",
"recordingActionShare": "Partager",
"recordingDeleteConfirmMessage": "Cette action est irréversible.",
"recordingDeleteConfirmTitle": "Supprimer l'enregistrement ?",
"recordingRenameDialogTitle": "Renommer l'enregistrement",
"recordingRenameEmptyError": "Saisissez un nom",
"recordingRenameLabel": "Nom",
"recordingsLibraryEmptySubtitle": "Les enregistrements que vous sauvegardez apparaîtront ici.",
"recordingsLibraryEmptyTitle": "Aucun enregistrement pour l'instant",
"recordingsLibrarySettingsTooltip": "Paramètres d'enregistrement",
"recordingsLibraryStorageCaption": "{usedMb} Mo sur {totalMb} Mo utilisés",
"@recordingsLibraryStorageCaption": {
"placeholders": {
"usedMb": {
"type": "int"
},
"totalMb": {
"type": "int"
}
}
},
"recordingsLibraryTitle": "Mes enregistrements",
"searchClearFiltersAction": "{count, plural, =1{Retirer le filtre} other{Retirer les {count} filtres}}",
"searchResultsCount": "{count, plural, =1{1 résultat} other{{count} résultats}}",
"seeAllAction": "Tout voir",
"settingsGroupApplicationTitle": "APPLICATION",
"settingsGroupAudioTitle": "AUDIO",
"settingsGroupRecordingsTitle": "ENREGISTREMENTS ET MUSIQUE",
"settingsGroupStationsTitle": "STATIONS",
"stationOrderByPopularity": "Par popularité",
"vacationImpactContinuesLabel": "Continue de sonner : {times}",
"@vacationImpactContinuesLabel": {
"placeholders": {
"times": {}
}
},
"vacationImpactPausedLabel": "En pause : {times}",
"@vacationImpactPausedLabel": {
"placeholders": {
"times": {}
}
},
"vacationNoActiveRangeHint": "Aucune période de vacances active pour le moment.",
"vacationPastSectionTitle": "Périodes passées",
"vacationRangesCount": "{count} périodes",
"@vacationRangesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"vacationSummaryActiveCountdown": "En cours · encore {days} jours",
"@vacationSummaryActiveCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationSummaryUpcomingCountdown": "Prochaine période dans {days} jours",
"@vacationSummaryUpcomingCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationUpcomingSectionTitle": "PROGRAMMÉES",
"welcomeBody": "53412 stations de 238 pays. Enregistrez vos favorites, emmenez-les en voiture et réveillez-vous avec elles.",
"welcomeBullet1Subtitle": "Plus un Preset par périphérique de sortie",
"welcomeBullet1Title": "Égaliseur par station",
"welcomeBullet2Subtitle": "Vos favorites et votre musique locale en voiture",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet3Subtitle": "Avec montée progressive du volume et mode vacances",
"welcomeBullet3Title": "Alarmes musicales",
"welcomeCtaLabel": "Commencer à écouter",
"welcomeHeadline": "Votre monde, en direct",
"yourStationsTitle": "Vos stations"
}
+124 -3
View File
@@ -1,7 +1,7 @@
{
"@@locale": "hi",
"appTitle": "PluriWave",
"navHome": "मुखपृष्ठ",
"navHome": "सुनना",
"navSearch": "खोज",
"navFavorites": "पसंदीदा",
"navAlarms": "अलार्म",
@@ -687,5 +687,126 @@
"error": {}
}
},
"localMusicFolderGenericName": "चयनित फ़ोल्डर"
}
"localMusicFolderGenericName": "चयनित फ़ोल्डर",
"addVacationRangeCta": "अवधि जोड़ें",
"alarmAdvancedSectionTitle": "उन्नत",
"alarmDeleteConfirmMessage": "इसे वापस नहीं लिया जा सकता।",
"alarmDeleteConfirmTitle": "अलार्म हटाएं?",
"alarmHeroSkipAction": "छोड़ें",
"alarmInlineHourLabel": "घंटा",
"alarmInlineMinuteLabel": "मिनट",
"alarmVolumeRisingStatus": "आवाज़ बढ़ाई जा रही है",
"countriesAllTitle": "सभी देश",
"countriesScreenTitle": "देश",
"countriesYourLanguagesTitle": "आपकी भाषाएं",
"customStationsAddCta": "मनचाहा स्टेशन जोड़ें",
"equalizerActiveOutputDefault": "इस डिवाइस का स्पीकर",
"equalizerActiveOutputLabel": "सक्रिय आउटपुट",
"equalizerBaseExplainer": "यह बेस इक्वलाइज़र है: यह उन सभी स्टेशन पर लागू होता है जिनकी अपनी सेटिंग नहीं है। किसी खास स्टेशन का अपना EQ उसकी प्लेबैक स्क्रीन से तय होता है और इस पर प्राथमिकता रखता है।",
"equalizerSaveAsPresetAction": "प्रीसेट के रूप में सहेजें",
"equalizerSavePresetConfirm": "सहेजें",
"equalizerSavePresetDialogTitle": "प्रीसेट के रूप में सहेजें",
"equalizerSavePresetEmptyNameError": "प्रीसेट के लिए एक नाम डालें।",
"equalizerSavePresetNameLabel": "प्रीसेट का नाम",
"equalizerStationsWithOwnEqEmpty": "अभी तक किसी स्टेशन का अपना EQ नहीं है।",
"equalizerStationsWithOwnEqSubtitle": "यह बेस इक्वलाइज़र नज़रअंदाज़ करते हैं",
"equalizerStationsWithOwnEqTitle": "अपना EQ रखने वाले स्टेशन",
"favoriteGroupsChipLabel": "{groupName} · {count}",
"@favoriteGroupsChipLabel": {
"placeholders": {
"groupName": {},
"count": {
"type": "int"
}
}
},
"favoriteGroupsManage": "सूचियां प्रबंधित करें",
"favoritesFilterAllLabel": "सभी",
"infoSectionTitle": "जानकारी",
"nothingPlayingSubtitle": "शुरू करने के लिए आपके स्टेशन में से कोई एक चुनें या खोजें।",
"nothingPlayingTitle": "अभी तक कुछ नहीं चल रहा",
"openFullPlayerTooltip": "पूरा प्लेयर खोलें",
"playerQualityChangeAction": "बदलें",
"playerToolEqLabel": "अपना EQ",
"radioCountriesError": "देश लोड नहीं हो सके।",
"recordingActionDelete": "हटाएं",
"recordingActionRename": "नाम बदलें",
"recordingActionShare": "शेयर करें",
"recordingDeleteConfirmMessage": "इसे वापस नहीं लिया जा सकता।",
"recordingDeleteConfirmTitle": "रिकॉर्डिंग हटाएं?",
"recordingRenameDialogTitle": "रिकॉर्डिंग का नाम बदलें",
"recordingRenameEmptyError": "एक नाम डालें",
"recordingRenameLabel": "नाम",
"recordingsLibraryEmptySubtitle": "आप जो रिकॉर्डिंग सहेजेंगे, वे यहां दिखाई देंगी।",
"recordingsLibraryEmptyTitle": "अभी तक कोई रिकॉर्डिंग नहीं",
"recordingsLibrarySettingsTooltip": "रिकॉर्डिंग सेटिंग्स",
"recordingsLibraryStorageCaption": "{totalMb} MB में से {usedMb} MB इस्तेमाल हुआ",
"@recordingsLibraryStorageCaption": {
"placeholders": {
"usedMb": {
"type": "int"
},
"totalMb": {
"type": "int"
}
}
},
"recordingsLibraryTitle": "मेरी रिकॉर्डिंग",
"searchClearFiltersAction": "{count, plural, =1{फ़िल्टर हटाएं} other{{count} फ़िल्टर हटाएं}}",
"searchResultsCount": "{count, plural, =1{1 परिणाम} other{{count} परिणाम}}",
"seeAllAction": "सभी देखें",
"settingsGroupApplicationTitle": "ऐप्लिकेशन",
"settingsGroupAudioTitle": "ऑडियो",
"settingsGroupRecordingsTitle": "रिकॉर्डिंग और संगीत",
"settingsGroupStationsTitle": "स्टेशन",
"stationOrderByPopularity": "लोकप्रियता के अनुसार",
"vacationImpactContinuesLabel": "बजना जारी: {times}",
"@vacationImpactContinuesLabel": {
"placeholders": {
"times": {}
}
},
"vacationImpactPausedLabel": "रोका गया: {times}",
"@vacationImpactPausedLabel": {
"placeholders": {
"times": {}
}
},
"vacationNoActiveRangeHint": "अभी कोई सक्रिय छुट्टी अवधि नहीं है।",
"vacationPastSectionTitle": "पिछली अवधियां",
"vacationRangesCount": "{count} अवधियां",
"@vacationRangesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"vacationSummaryActiveCountdown": "जारी है · {days} दिन बाकी",
"@vacationSummaryActiveCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationSummaryUpcomingCountdown": "अगली अवधि {days} दिन में",
"@vacationSummaryUpcomingCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationUpcomingSectionTitle": "निर्धारित",
"welcomeBody": "238 देशों के 53,412 स्टेशन। अपने पसंदीदा स्टेशन सहेजें, उन्हें गाड़ी में ले जाएं और उनके साथ जागें।",
"welcomeBullet1Subtitle": "और हर आउटपुट डिवाइस के लिए एक प्रीसेट",
"welcomeBullet1Title": "प्रति स्टेशन इक्वलाइज़र",
"welcomeBullet2Subtitle": "गाड़ी में आपके पसंदीदा और लोकल संगीत",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet3Subtitle": "धीरे-धीरे आवाज़ बढ़ाने और छुट्टी मोड के साथ",
"welcomeBullet3Title": "संगीत अलार्म",
"welcomeCtaLabel": "सुनना शुरू करें",
"welcomeHeadline": "आपकी दुनिया, लाइव",
"yourStationsTitle": "आपके स्टेशन"
}
+124 -3
View File
@@ -1,7 +1,7 @@
{
"@@locale": "id",
"appTitle": "PluriWave",
"navHome": "Beranda",
"navHome": "Dengar",
"navSearch": "Cari",
"navFavorites": "Favorit",
"navAlarms": "Alarm",
@@ -687,5 +687,126 @@
"error": {}
}
},
"localMusicFolderGenericName": "Folder terpilih"
}
"localMusicFolderGenericName": "Folder terpilih",
"addVacationRangeCta": "Tambahkan periode",
"alarmAdvancedSectionTitle": "Lanjutan",
"alarmDeleteConfirmMessage": "Tindakan ini tidak dapat dibatalkan.",
"alarmDeleteConfirmTitle": "Hapus alarm?",
"alarmHeroSkipAction": "Lewati",
"alarmInlineHourLabel": "Jam",
"alarmInlineMinuteLabel": "Menit",
"alarmVolumeRisingStatus": "Menaikkan volume",
"countriesAllTitle": "Semua negara",
"countriesScreenTitle": "Negara",
"countriesYourLanguagesTitle": "Bahasa Anda",
"customStationsAddCta": "Tambahkan stasiun khusus",
"equalizerActiveOutputDefault": "Speaker perangkat ini",
"equalizerActiveOutputLabel": "Output aktif",
"equalizerBaseExplainer": "Ini adalah equalizer dasar: berlaku untuk semua stasiun yang belum punya pengaturan sendiri. EQ khusus suatu stasiun diatur dari layar pemutarannya sendiri dan mengesampingkan yang ini.",
"equalizerSaveAsPresetAction": "Simpan sebagai Preset",
"equalizerSavePresetConfirm": "Simpan",
"equalizerSavePresetDialogTitle": "Simpan sebagai Preset",
"equalizerSavePresetEmptyNameError": "Masukkan nama untuk Preset.",
"equalizerSavePresetNameLabel": "Nama Preset",
"equalizerStationsWithOwnEqEmpty": "Belum ada stasiun yang punya EQ sendiri.",
"equalizerStationsWithOwnEqSubtitle": "Mengabaikan equalizer dasar ini",
"equalizerStationsWithOwnEqTitle": "Stasiun dengan EQ sendiri",
"favoriteGroupsChipLabel": "{groupName} · {count}",
"@favoriteGroupsChipLabel": {
"placeholders": {
"groupName": {},
"count": {
"type": "int"
}
}
},
"favoriteGroupsManage": "Kelola daftar",
"favoritesFilterAllLabel": "Semua",
"infoSectionTitle": "Info",
"nothingPlayingSubtitle": "Pilih stasiun dari Stasiun Anda atau cari salah satu untuk mulai.",
"nothingPlayingTitle": "Belum ada yang diputar",
"openFullPlayerTooltip": "Buka pemutar lengkap",
"playerQualityChangeAction": "Ubah",
"playerToolEqLabel": "EQ sendiri",
"radioCountriesError": "Negara tidak dapat dimuat.",
"recordingActionDelete": "Hapus",
"recordingActionRename": "Ganti nama",
"recordingActionShare": "Bagikan",
"recordingDeleteConfirmMessage": "Tindakan ini tidak dapat dibatalkan.",
"recordingDeleteConfirmTitle": "Hapus rekaman?",
"recordingRenameDialogTitle": "Ganti nama rekaman",
"recordingRenameEmptyError": "Masukkan nama",
"recordingRenameLabel": "Nama",
"recordingsLibraryEmptySubtitle": "Rekaman yang Anda simpan akan muncul di sini.",
"recordingsLibraryEmptyTitle": "Belum ada rekaman",
"recordingsLibrarySettingsTooltip": "Pengaturan rekaman",
"recordingsLibraryStorageCaption": "{usedMb} MB dari {totalMb} MB terpakai",
"@recordingsLibraryStorageCaption": {
"placeholders": {
"usedMb": {
"type": "int"
},
"totalMb": {
"type": "int"
}
}
},
"recordingsLibraryTitle": "Rekaman saya",
"searchClearFiltersAction": "{count, plural, =1{Hapus filter} other{Hapus {count} filter}}",
"searchResultsCount": "{count, plural, other{{count} hasil}}",
"seeAllAction": "Lihat semua",
"settingsGroupApplicationTitle": "APLIKASI",
"settingsGroupAudioTitle": "AUDIO",
"settingsGroupRecordingsTitle": "REKAMAN & MUSIK",
"settingsGroupStationsTitle": "STASIUN",
"stationOrderByPopularity": "Berdasarkan popularitas",
"vacationImpactContinuesLabel": "Tetap berbunyi: {times}",
"@vacationImpactContinuesLabel": {
"placeholders": {
"times": {}
}
},
"vacationImpactPausedLabel": "Dijeda: {times}",
"@vacationImpactPausedLabel": {
"placeholders": {
"times": {}
}
},
"vacationNoActiveRangeHint": "Tidak ada periode liburan aktif saat ini.",
"vacationPastSectionTitle": "Periode lampau",
"vacationRangesCount": "{count} periode",
"@vacationRangesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"vacationSummaryActiveCountdown": "Sedang berlangsung · {days} hari lagi",
"@vacationSummaryActiveCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationSummaryUpcomingCountdown": "Periode berikutnya dalam {days} hari",
"@vacationSummaryUpcomingCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationUpcomingSectionTitle": "TERJADWAL",
"welcomeBody": "53.412 stasiun dari 238 negara. Simpan favorit Anda, bawa ke mobil, dan bangun bersamanya.",
"welcomeBullet1Subtitle": "Plus satu Preset per perangkat output",
"welcomeBullet1Title": "Equalizer per stasiun",
"welcomeBullet2Subtitle": "Favorit dan musik lokal Anda di mobil",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet3Subtitle": "Dengan kenaikan volume bertahap dan mode liburan",
"welcomeBullet3Title": "Alarm musik",
"welcomeCtaLabel": "Mulai mendengarkan",
"welcomeHeadline": "Duniamu, secara langsung",
"yourStationsTitle": "Stasiun Anda"
}
+124 -3
View File
@@ -1,7 +1,7 @@
{
"@@locale": "it",
"appTitle": "PluriWave",
"navHome": "Inizio",
"navHome": "Ascolta",
"navSearch": "Cerca",
"navFavorites": "Preferiti",
"navAlarms": "Sveglie",
@@ -687,5 +687,126 @@
"error": {}
}
},
"localMusicFolderGenericName": "Cartella selezionata"
}
"localMusicFolderGenericName": "Cartella selezionata",
"addVacationRangeCta": "Aggiungi periodo",
"alarmAdvancedSectionTitle": "Avanzate",
"alarmDeleteConfirmMessage": "Questa azione non può essere annullata.",
"alarmDeleteConfirmTitle": "Eliminare la sveglia?",
"alarmHeroSkipAction": "Salta",
"alarmInlineHourLabel": "Ora",
"alarmInlineMinuteLabel": "Minuto",
"alarmVolumeRisingStatus": "Aumento del volume",
"countriesAllTitle": "Tutti i paesi",
"countriesScreenTitle": "Paesi",
"countriesYourLanguagesTitle": "Le tue lingue",
"customStationsAddCta": "Aggiungi emittente personalizzata",
"equalizerActiveOutputDefault": "L'altoparlante di questo dispositivo",
"equalizerActiveOutputLabel": "Uscita attiva",
"equalizerBaseExplainer": "Questo è l'equalizzatore di base: si applica a tutte le emittenti senza un'impostazione propria. L'EQ di un'emittente specifica si imposta dalla sua schermata di riproduzione e ha la precedenza su questo.",
"equalizerSaveAsPresetAction": "Salva come Preset",
"equalizerSavePresetConfirm": "Salva",
"equalizerSavePresetDialogTitle": "Salva come Preset",
"equalizerSavePresetEmptyNameError": "Inserisci un nome per il Preset.",
"equalizerSavePresetNameLabel": "Nome del Preset",
"equalizerStationsWithOwnEqEmpty": "Nessuna emittente ha ancora un EQ proprio.",
"equalizerStationsWithOwnEqSubtitle": "Ignorano questo equalizzatore di base",
"equalizerStationsWithOwnEqTitle": "Emittenti con EQ proprio",
"favoriteGroupsChipLabel": "{groupName} · {count}",
"@favoriteGroupsChipLabel": {
"placeholders": {
"groupName": {},
"count": {
"type": "int"
}
}
},
"favoriteGroupsManage": "Gestisci liste",
"favoritesFilterAllLabel": "Tutte",
"infoSectionTitle": "Informazioni",
"nothingPlayingSubtitle": "Scegli un'emittente da Le tue emittenti o cercane una per iniziare.",
"nothingPlayingTitle": "Non stai ancora ascoltando nulla",
"openFullPlayerTooltip": "Apri il player completo",
"playerQualityChangeAction": "Cambia",
"playerToolEqLabel": "EQ proprio",
"radioCountriesError": "Non è stato possibile caricare i paesi.",
"recordingActionDelete": "Elimina",
"recordingActionRename": "Rinomina",
"recordingActionShare": "Condividi",
"recordingDeleteConfirmMessage": "Questa azione non può essere annullata.",
"recordingDeleteConfirmTitle": "Eliminare la registrazione?",
"recordingRenameDialogTitle": "Rinomina registrazione",
"recordingRenameEmptyError": "Inserisci un nome",
"recordingRenameLabel": "Nome",
"recordingsLibraryEmptySubtitle": "Le registrazioni che salvi appariranno qui.",
"recordingsLibraryEmptyTitle": "Ancora nessuna registrazione",
"recordingsLibrarySettingsTooltip": "Impostazioni di registrazione",
"recordingsLibraryStorageCaption": "{usedMb} MB di {totalMb} MB usati",
"@recordingsLibraryStorageCaption": {
"placeholders": {
"usedMb": {
"type": "int"
},
"totalMb": {
"type": "int"
}
}
},
"recordingsLibraryTitle": "Le mie registrazioni",
"searchClearFiltersAction": "{count, plural, =1{Rimuovi filtro} other{Rimuovi {count} filtri}}",
"searchResultsCount": "{count, plural, =1{1 risultato} other{{count} risultati}}",
"seeAllAction": "Vedi tutte",
"settingsGroupApplicationTitle": "APPLICAZIONE",
"settingsGroupAudioTitle": "AUDIO",
"settingsGroupRecordingsTitle": "REGISTRAZIONI E MUSICA",
"settingsGroupStationsTitle": "EMITTENTI",
"stationOrderByPopularity": "Per popolarità",
"vacationImpactContinuesLabel": "Continua a suonare: {times}",
"@vacationImpactContinuesLabel": {
"placeholders": {
"times": {}
}
},
"vacationImpactPausedLabel": "In pausa: {times}",
"@vacationImpactPausedLabel": {
"placeholders": {
"times": {}
}
},
"vacationNoActiveRangeHint": "Nessun periodo di vacanza attivo al momento.",
"vacationPastSectionTitle": "Periodi passati",
"vacationRangesCount": "{count} periodi",
"@vacationRangesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"vacationSummaryActiveCountdown": "In corso · mancano {days} giorni",
"@vacationSummaryActiveCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationSummaryUpcomingCountdown": "Prossimo periodo tra {days} giorni",
"@vacationSummaryUpcomingCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationUpcomingSectionTitle": "PROGRAMMATI",
"welcomeBody": "53.412 emittenti da 238 paesi. Salva le tue preferite, portale in auto e svegliati con loro.",
"welcomeBullet1Subtitle": "Più un Preset per ogni dispositivo di uscita",
"welcomeBullet1Title": "Equalizzatore per emittente",
"welcomeBullet2Subtitle": "Le tue preferite e la tua musica locale in auto",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet3Subtitle": "Con aumento graduale del volume e modalità vacanza",
"welcomeBullet3Title": "Sveglie musicali",
"welcomeCtaLabel": "Inizia ad ascoltare",
"welcomeHeadline": "Il tuo mondo, in diretta",
"yourStationsTitle": "Le tue emittenti"
}
+124 -3
View File
@@ -1,7 +1,7 @@
{
"@@locale": "ja",
"appTitle": "PluriWave",
"navHome": "ホーム",
"navHome": "聴く",
"navSearch": "検索",
"navFavorites": "お気に入り",
"navAlarms": "アラーム",
@@ -687,5 +687,126 @@
"error": {}
}
},
"localMusicFolderGenericName": "選択したフォルダー"
}
"localMusicFolderGenericName": "選択したフォルダー",
"addVacationRangeCta": "期間を追加",
"alarmAdvancedSectionTitle": "詳細設定",
"alarmDeleteConfirmMessage": "この操作は元に戻せません。",
"alarmDeleteConfirmTitle": "アラームを削除しますか?",
"alarmHeroSkipAction": "スキップ",
"alarmInlineHourLabel": "時",
"alarmInlineMinuteLabel": "分",
"alarmVolumeRisingStatus": "音量を上げています",
"countriesAllTitle": "すべての国",
"countriesScreenTitle": "国",
"countriesYourLanguagesTitle": "あなたの言語",
"customStationsAddCta": "カスタム局を追加",
"equalizerActiveOutputDefault": "この端末のスピーカー",
"equalizerActiveOutputLabel": "アクティブな出力",
"equalizerBaseExplainer": "これはベースイコライザーです。専用設定を持たないすべての局に適用されます。特定の局専用のEQはその局の再生画面から設定でき、こちらより優先されます。",
"equalizerSaveAsPresetAction": "プリセットとして保存",
"equalizerSavePresetConfirm": "保存",
"equalizerSavePresetDialogTitle": "プリセットとして保存",
"equalizerSavePresetEmptyNameError": "プリセットの名前を入力してください。",
"equalizerSavePresetNameLabel": "プリセット名",
"equalizerStationsWithOwnEqEmpty": "専用EQを持つ局はまだありません。",
"equalizerStationsWithOwnEqSubtitle": "このベースイコライザーを無視します",
"equalizerStationsWithOwnEqTitle": "専用EQを持つ局",
"favoriteGroupsChipLabel": "{groupName} · {count}",
"@favoriteGroupsChipLabel": {
"placeholders": {
"groupName": {},
"count": {
"type": "int"
}
}
},
"favoriteGroupsManage": "リストを管理",
"favoritesFilterAllLabel": "すべて",
"infoSectionTitle": "情報",
"nothingPlayingSubtitle": "「あなたの局」から局を選ぶか、検索して再生を始めましょう。",
"nothingPlayingTitle": "まだ何も再生されていません",
"openFullPlayerTooltip": "フルプレーヤーを開く",
"playerQualityChangeAction": "変更",
"playerToolEqLabel": "専用EQ",
"radioCountriesError": "国の一覧を読み込めませんでした。",
"recordingActionDelete": "削除",
"recordingActionRename": "名前を変更",
"recordingActionShare": "共有",
"recordingDeleteConfirmMessage": "この操作は元に戻せません。",
"recordingDeleteConfirmTitle": "録音を削除しますか?",
"recordingRenameDialogTitle": "録音の名前を変更",
"recordingRenameEmptyError": "名前を入力してください",
"recordingRenameLabel": "名前",
"recordingsLibraryEmptySubtitle": "保存した録音はここに表示されます。",
"recordingsLibraryEmptyTitle": "まだ録音がありません",
"recordingsLibrarySettingsTooltip": "録音設定",
"recordingsLibraryStorageCaption": "{totalMb} MB 中 {usedMb} MB 使用済み",
"@recordingsLibraryStorageCaption": {
"placeholders": {
"usedMb": {
"type": "int"
},
"totalMb": {
"type": "int"
}
}
},
"recordingsLibraryTitle": "マイ録音",
"searchClearFiltersAction": "{count, plural, =1{フィルターをクリア} other{{count}件のフィルターをクリア}}",
"searchResultsCount": "{count, plural, other{{count}件の結果}}",
"seeAllAction": "すべて見る",
"settingsGroupApplicationTitle": "アプリ",
"settingsGroupAudioTitle": "オーディオ",
"settingsGroupRecordingsTitle": "録音と音楽",
"settingsGroupStationsTitle": "局",
"stationOrderByPopularity": "人気順",
"vacationImpactContinuesLabel": "引き続き鳴動: {times}",
"@vacationImpactContinuesLabel": {
"placeholders": {
"times": {}
}
},
"vacationImpactPausedLabel": "一時停止: {times}",
"@vacationImpactPausedLabel": {
"placeholders": {
"times": {}
}
},
"vacationNoActiveRangeHint": "現在アクティブな休暇期間はありません。",
"vacationPastSectionTitle": "過去の期間",
"vacationRangesCount": "{count} 件の期間",
"@vacationRangesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"vacationSummaryActiveCountdown": "進行中 · 残り{days}日",
"@vacationSummaryActiveCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationSummaryUpcomingCountdown": "次の期間まであと{days}日",
"@vacationSummaryUpcomingCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationUpcomingSectionTitle": "予定",
"welcomeBody": "238か国、53,412局のラジオ。お気に入りを保存して車に持ち出し、それで目を覚まそう。",
"welcomeBullet1Subtitle": "さらに出力デバイスごとのプリセット",
"welcomeBullet1Title": "局ごとのイコライザー",
"welcomeBullet2Subtitle": "車の中でお気に入りとローカル音楽を",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet3Subtitle": "音量の緩やかな上昇と休暇モード付き",
"welcomeBullet3Title": "音楽アラーム",
"welcomeCtaLabel": "聴き始める",
"welcomeHeadline": "あなたの世界を、ライブで",
"yourStationsTitle": "あなたの局"
}
+124 -3
View File
@@ -1,7 +1,7 @@
{
"@@locale": "pt",
"appTitle": "PluriWave",
"navHome": "Início",
"navHome": "Ouvir",
"navSearch": "Buscar",
"navFavorites": "Favoritos",
"navAlarms": "Alarmes",
@@ -687,5 +687,126 @@
"error": {}
}
},
"localMusicFolderGenericName": "Pasta selecionada"
}
"localMusicFolderGenericName": "Pasta selecionada",
"addVacationRangeCta": "Adicionar período",
"alarmAdvancedSectionTitle": "Avançado",
"alarmDeleteConfirmMessage": "Esta ação não pode ser desfeita.",
"alarmDeleteConfirmTitle": "Excluir alarme?",
"alarmHeroSkipAction": "Pular",
"alarmInlineHourLabel": "Hora",
"alarmInlineMinuteLabel": "Minuto",
"alarmVolumeRisingStatus": "Aumentando o volume",
"countriesAllTitle": "Todos os países",
"countriesScreenTitle": "Países",
"countriesYourLanguagesTitle": "Seus idiomas",
"customStationsAddCta": "Adicionar estação personalizada",
"equalizerActiveOutputDefault": "O alto-falante deste dispositivo",
"equalizerActiveOutputLabel": "Saída ativa",
"equalizerBaseExplainer": "Este é o equalizador base: aplica-se a todas as estações sem ajuste próprio. O EQ próprio de uma estação é definido na sua própria tela de reprodução e tem prioridade sobre este.",
"equalizerSaveAsPresetAction": "Salvar como Preset",
"equalizerSavePresetConfirm": "Salvar",
"equalizerSavePresetDialogTitle": "Salvar como Preset",
"equalizerSavePresetEmptyNameError": "Digite um nome para o Preset.",
"equalizerSavePresetNameLabel": "Nome do Preset",
"equalizerStationsWithOwnEqEmpty": "Nenhuma estação tem EQ próprio ainda.",
"equalizerStationsWithOwnEqSubtitle": "Ignoram este equalizador base",
"equalizerStationsWithOwnEqTitle": "Estações com EQ próprio",
"favoriteGroupsChipLabel": "{groupName} · {count}",
"@favoriteGroupsChipLabel": {
"placeholders": {
"groupName": {},
"count": {
"type": "int"
}
}
},
"favoriteGroupsManage": "Gerenciar listas",
"favoritesFilterAllLabel": "Todas",
"infoSectionTitle": "Informações",
"nothingPlayingSubtitle": "Escolha uma estação em Suas estações ou pesquise uma para começar.",
"nothingPlayingTitle": "Ainda não há nada tocando",
"openFullPlayerTooltip": "Abrir player completo",
"playerQualityChangeAction": "Alterar",
"playerToolEqLabel": "EQ próprio",
"radioCountriesError": "Não foi possível carregar os países.",
"recordingActionDelete": "Excluir",
"recordingActionRename": "Renomear",
"recordingActionShare": "Compartilhar",
"recordingDeleteConfirmMessage": "Esta ação não pode ser desfeita.",
"recordingDeleteConfirmTitle": "Excluir gravação?",
"recordingRenameDialogTitle": "Renomear gravação",
"recordingRenameEmptyError": "Digite um nome",
"recordingRenameLabel": "Nome",
"recordingsLibraryEmptySubtitle": "As gravações que você salvar vão aparecer aqui.",
"recordingsLibraryEmptyTitle": "Ainda não há gravações",
"recordingsLibrarySettingsTooltip": "Configurações de gravação",
"recordingsLibraryStorageCaption": "{usedMb} MB de {totalMb} MB usados",
"@recordingsLibraryStorageCaption": {
"placeholders": {
"usedMb": {
"type": "int"
},
"totalMb": {
"type": "int"
}
}
},
"recordingsLibraryTitle": "Minhas gravações",
"searchClearFiltersAction": "{count, plural, =1{Remover filtro} other{Remover {count} filtros}}",
"searchResultsCount": "{count, plural, =1{1 resultado} other{{count} resultados}}",
"seeAllAction": "Ver todas",
"settingsGroupApplicationTitle": "APLICATIVO",
"settingsGroupAudioTitle": "ÁUDIO",
"settingsGroupRecordingsTitle": "GRAVAÇÕES E MÚSICA",
"settingsGroupStationsTitle": "ESTAÇÕES",
"stationOrderByPopularity": "Por popularidade",
"vacationImpactContinuesLabel": "Continua tocando: {times}",
"@vacationImpactContinuesLabel": {
"placeholders": {
"times": {}
}
},
"vacationImpactPausedLabel": "Pausado: {times}",
"@vacationImpactPausedLabel": {
"placeholders": {
"times": {}
}
},
"vacationNoActiveRangeHint": "Não há período de férias ativo no momento.",
"vacationPastSectionTitle": "Períodos passados",
"vacationRangesCount": "{count} períodos",
"@vacationRangesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"vacationSummaryActiveCountdown": "Em andamento · faltam {days} dias",
"@vacationSummaryActiveCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationSummaryUpcomingCountdown": "Próximo período em {days} dias",
"@vacationSummaryUpcomingCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationUpcomingSectionTitle": "PROGRAMADOS",
"welcomeBody": "53.412 estações de 238 países. Salve suas favoritas, leve-as para o carro e acorde com elas.",
"welcomeBullet1Subtitle": "Além de um Preset por dispositivo de saída",
"welcomeBullet1Title": "Equalizador por estação",
"welcomeBullet2Subtitle": "Suas favoritas e sua música local no carro",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet3Subtitle": "Com aumento progressivo do volume e modo férias",
"welcomeBullet3Title": "Alarmes musicais",
"welcomeCtaLabel": "Começar a ouvir",
"welcomeHeadline": "Seu mundo, ao vivo",
"yourStationsTitle": "Suas estações"
}
+124 -3
View File
@@ -1,7 +1,7 @@
{
"@@locale": "ru",
"appTitle": "PluriWave",
"navHome": "Главная",
"navHome": "Слушать",
"navSearch": "Поиск",
"navFavorites": "Избранное",
"navAlarms": "Будильники",
@@ -687,5 +687,126 @@
"error": {}
}
},
"localMusicFolderGenericName": "Выбранная папка"
}
"localMusicFolderGenericName": "Выбранная папка",
"addVacationRangeCta": "Добавить период",
"alarmAdvancedSectionTitle": "Дополнительно",
"alarmDeleteConfirmMessage": "Это действие нельзя отменить.",
"alarmDeleteConfirmTitle": "Удалить будильник?",
"alarmHeroSkipAction": "Пропустить",
"alarmInlineHourLabel": "Час",
"alarmInlineMinuteLabel": "Минута",
"alarmVolumeRisingStatus": "Громкость нарастает",
"countriesAllTitle": "Все страны",
"countriesScreenTitle": "Страны",
"countriesYourLanguagesTitle": "Ваши языки",
"customStationsAddCta": "Добавить пользовательскую станцию",
"equalizerActiveOutputDefault": "Динамик этого устройства",
"equalizerActiveOutputLabel": "Активный выход",
"equalizerBaseExplainer": "Это базовый эквалайзер: он применяется ко всем станциям без собственной настройки. Свой EQ конкретной станции задаётся на экране её воспроизведения и имеет приоритет над этим.",
"equalizerSaveAsPresetAction": "Сохранить как пресет",
"equalizerSavePresetConfirm": "Сохранить",
"equalizerSavePresetDialogTitle": "Сохранить как пресет",
"equalizerSavePresetEmptyNameError": "Введите название пресета.",
"equalizerSavePresetNameLabel": "Название пресета",
"equalizerStationsWithOwnEqEmpty": "Пока ни у одной станции нет своего EQ.",
"equalizerStationsWithOwnEqSubtitle": "Игнорируют этот базовый эквалайзер",
"equalizerStationsWithOwnEqTitle": "Станции со своим EQ",
"favoriteGroupsChipLabel": "{groupName} · {count}",
"@favoriteGroupsChipLabel": {
"placeholders": {
"groupName": {},
"count": {
"type": "int"
}
}
},
"favoriteGroupsManage": "Управление списками",
"favoritesFilterAllLabel": "Все",
"infoSectionTitle": "Информация",
"nothingPlayingSubtitle": "Выберите станцию из раздела «Ваши станции» или найдите её, чтобы начать.",
"nothingPlayingTitle": "Пока ничего не воспроизводится",
"openFullPlayerTooltip": "Открыть полный плеер",
"playerQualityChangeAction": "Изменить",
"playerToolEqLabel": "Свой EQ",
"radioCountriesError": "Не удалось загрузить страны.",
"recordingActionDelete": "Удалить",
"recordingActionRename": "Переименовать",
"recordingActionShare": "Поделиться",
"recordingDeleteConfirmMessage": "Это действие нельзя отменить.",
"recordingDeleteConfirmTitle": "Удалить запись?",
"recordingRenameDialogTitle": "Переименовать запись",
"recordingRenameEmptyError": "Введите название",
"recordingRenameLabel": "Название",
"recordingsLibraryEmptySubtitle": "Записи, которые вы сохраните, появятся здесь.",
"recordingsLibraryEmptyTitle": "Записей пока нет",
"recordingsLibrarySettingsTooltip": "Настройки записи",
"recordingsLibraryStorageCaption": "Использовано {usedMb} МБ из {totalMb} МБ",
"@recordingsLibraryStorageCaption": {
"placeholders": {
"usedMb": {
"type": "int"
},
"totalMb": {
"type": "int"
}
}
},
"recordingsLibraryTitle": "Мои записи",
"searchClearFiltersAction": "{count, plural, =1{Убрать фильтр} few{Убрать {count} фильтра} other{Убрать {count} фильтров}}",
"searchResultsCount": "{count, plural, one{{count} результат} few{{count} результата} other{{count} результатов}}",
"seeAllAction": "Смотреть все",
"settingsGroupApplicationTitle": "ПРИЛОЖЕНИЕ",
"settingsGroupAudioTitle": "АУДИО",
"settingsGroupRecordingsTitle": "ЗАПИСИ И МУЗЫКА",
"settingsGroupStationsTitle": "СТАНЦИИ",
"stationOrderByPopularity": "По популярности",
"vacationImpactContinuesLabel": "Продолжает звонить: {times}",
"@vacationImpactContinuesLabel": {
"placeholders": {
"times": {}
}
},
"vacationImpactPausedLabel": "Пауза: {times}",
"@vacationImpactPausedLabel": {
"placeholders": {
"times": {}
}
},
"vacationNoActiveRangeHint": "Сейчас нет активного периода отпуска.",
"vacationPastSectionTitle": "Прошедшие периоды",
"vacationRangesCount": "{count} периодов",
"@vacationRangesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"vacationSummaryActiveCountdown": "Идёт сейчас · осталось {days} дн.",
"@vacationSummaryActiveCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationSummaryUpcomingCountdown": "Следующий период через {days} дн.",
"@vacationSummaryUpcomingCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationUpcomingSectionTitle": "ЗАПЛАНИРОВАНО",
"welcomeBody": "53 412 станций из 238 стран. Сохраняйте любимые, берите их с собой в машину и просыпайтесь под них.",
"welcomeBullet1Subtitle": "А также пресет для каждого устройства вывода",
"welcomeBullet1Title": "Эквалайзер для каждой станции",
"welcomeBullet2Subtitle": "Ваши избранные и локальная музыка в машине",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet3Subtitle": "С плавным нарастанием громкости и режимом отпуска",
"welcomeBullet3Title": "Музыкальные будильники",
"welcomeCtaLabel": "Начать слушать",
"welcomeHeadline": "Ваш мир, в прямом эфире",
"yourStationsTitle": "Ваши станции"
}
+124 -3
View File
@@ -1,7 +1,7 @@
{
"@@locale": "zh",
"appTitle": "PluriWave",
"navHome": "首页",
"navHome": "收听",
"navSearch": "搜索",
"navFavorites": "收藏",
"navAlarms": "闹钟",
@@ -687,5 +687,126 @@
"error": {}
}
},
"localMusicFolderGenericName": "已选文件夹"
}
"localMusicFolderGenericName": "已选文件夹",
"addVacationRangeCta": "添加时间段",
"alarmAdvancedSectionTitle": "高级",
"alarmDeleteConfirmMessage": "此操作无法撤销。",
"alarmDeleteConfirmTitle": "删除闹钟?",
"alarmHeroSkipAction": "跳过",
"alarmInlineHourLabel": "时",
"alarmInlineMinuteLabel": "分",
"alarmVolumeRisingStatus": "音量渐强中",
"countriesAllTitle": "所有国家",
"countriesScreenTitle": "国家",
"countriesYourLanguagesTitle": "你的语言",
"customStationsAddCta": "添加自定义电台",
"equalizerActiveOutputDefault": "此设备的扬声器",
"equalizerActiveOutputLabel": "当前输出",
"equalizerBaseExplainer": "这是基础均衡器:适用于所有没有专属设置的电台。某个电台的专属EQ可在其自己的播放界面中设置,并优先于此设置生效。",
"equalizerSaveAsPresetAction": "保存为预设",
"equalizerSavePresetConfirm": "保存",
"equalizerSavePresetDialogTitle": "保存为预设",
"equalizerSavePresetEmptyNameError": "请输入预设名称。",
"equalizerSavePresetNameLabel": "预设名称",
"equalizerStationsWithOwnEqEmpty": "目前还没有电台设置专属EQ。",
"equalizerStationsWithOwnEqSubtitle": "忽略此基础均衡器",
"equalizerStationsWithOwnEqTitle": "拥有专属EQ的电台",
"favoriteGroupsChipLabel": "{groupName} · {count}",
"@favoriteGroupsChipLabel": {
"placeholders": {
"groupName": {},
"count": {
"type": "int"
}
}
},
"favoriteGroupsManage": "管理列表",
"favoritesFilterAllLabel": "全部",
"infoSectionTitle": "信息",
"nothingPlayingSubtitle": "从“你的电台”中选择一个电台,或搜索一个开始收听。",
"nothingPlayingTitle": "目前没有正在播放的内容",
"openFullPlayerTooltip": "打开完整播放器",
"playerQualityChangeAction": "更改",
"playerToolEqLabel": "专属EQ",
"radioCountriesError": "无法加载国家列表。",
"recordingActionDelete": "删除",
"recordingActionRename": "重命名",
"recordingActionShare": "分享",
"recordingDeleteConfirmMessage": "此操作无法撤销。",
"recordingDeleteConfirmTitle": "删除录音?",
"recordingRenameDialogTitle": "重命名录音",
"recordingRenameEmptyError": "请输入名称",
"recordingRenameLabel": "名称",
"recordingsLibraryEmptySubtitle": "你保存的录音会显示在这里。",
"recordingsLibraryEmptyTitle": "暂无录音",
"recordingsLibrarySettingsTooltip": "录音设置",
"recordingsLibraryStorageCaption": "已使用 {usedMb} MB / 共 {totalMb} MB",
"@recordingsLibraryStorageCaption": {
"placeholders": {
"usedMb": {
"type": "int"
},
"totalMb": {
"type": "int"
}
}
},
"recordingsLibraryTitle": "我的录音",
"searchClearFiltersAction": "{count, plural, =1{清除筛选条件} other{清除 {count} 个筛选条件}}",
"searchResultsCount": "{count, plural, other{{count} 个结果}}",
"seeAllAction": "查看全部",
"settingsGroupApplicationTitle": "应用",
"settingsGroupAudioTitle": "音频",
"settingsGroupRecordingsTitle": "录音与音乐",
"settingsGroupStationsTitle": "电台",
"stationOrderByPopularity": "按热度",
"vacationImpactContinuesLabel": "仍会响铃:{times}",
"@vacationImpactContinuesLabel": {
"placeholders": {
"times": {}
}
},
"vacationImpactPausedLabel": "暂停:{times}",
"@vacationImpactPausedLabel": {
"placeholders": {
"times": {}
}
},
"vacationNoActiveRangeHint": "目前没有正在生效的假期时间段。",
"vacationPastSectionTitle": "过往时间段",
"vacationRangesCount": "{count} 个时间段",
"@vacationRangesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"vacationSummaryActiveCountdown": "进行中 · 还剩 {days} 天",
"@vacationSummaryActiveCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationSummaryUpcomingCountdown": "下一个时间段在 {days} 天后",
"@vacationSummaryUpcomingCountdown": {
"placeholders": {
"days": {
"type": "int"
}
}
},
"vacationUpcomingSectionTitle": "已安排",
"welcomeBody": "来自238个国家的53,412个电台。保存你喜爱的电台,带上车,用它们唤醒你的每一天。",
"welcomeBullet1Subtitle": "还支持为每个输出设备设置独立预设",
"welcomeBullet1Title": "分电台均衡器",
"welcomeBullet2Subtitle": "车内畅享你的收藏电台和本地音乐",
"welcomeBullet2Title": "Android Auto",
"welcomeBullet3Subtitle": "支持音量渐强唤醒和假期模式",
"welcomeBullet3Title": "音乐闹钟",
"welcomeCtaLabel": "开始收听",
"welcomeHeadline": "你的世界,直播中",
"yourStationsTitle": "你的电台"
}
+54
View File
@@ -2869,6 +2869,60 @@ abstract class AppLocalizations {
/// In es, this message translates to:
/// **'Carpeta seleccionada'**
String get localMusicFolderGenericName;
/// No description provided for @welcomeHeadline.
///
/// In es, this message translates to:
/// **'Tu mundo, en directo'**
String get welcomeHeadline;
/// No description provided for @welcomeBody.
///
/// In es, this message translates to:
/// **'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.'**
String get welcomeBody;
/// No description provided for @welcomeBullet1Title.
///
/// In es, this message translates to:
/// **'Ecualizador por emisora'**
String get welcomeBullet1Title;
/// No description provided for @welcomeBullet1Subtitle.
///
/// In es, this message translates to:
/// **'Y un preset por dispositivo de salida'**
String get welcomeBullet1Subtitle;
/// No description provided for @welcomeBullet2Title.
///
/// In es, this message translates to:
/// **'Android Auto'**
String get welcomeBullet2Title;
/// No description provided for @welcomeBullet2Subtitle.
///
/// In es, this message translates to:
/// **'Tus favoritas y tu música local en el auto'**
String get welcomeBullet2Subtitle;
/// No description provided for @welcomeBullet3Title.
///
/// In es, this message translates to:
/// **'Alarmas musicales'**
String get welcomeBullet3Title;
/// No description provided for @welcomeBullet3Subtitle.
///
/// In es, this message translates to:
/// **'Con subida progresiva y modo vacaciones'**
String get welcomeBullet3Subtitle;
/// No description provided for @welcomeCtaLabel.
///
/// In es, this message translates to:
/// **'Empezar a escuchar'**
String get welcomeCtaLabel;
}
class _AppLocalizationsDelegate
+98 -69
View File
@@ -12,7 +12,7 @@ class AppLocalizationsAr extends AppLocalizations {
String get appTitle => 'PluriWave';
@override
String get navHome => 'الرئيسية';
String get navHome => 'استماع';
@override
String get navSearch => 'بحث';
@@ -100,19 +100,19 @@ class AppLocalizationsAr extends AppLocalizations {
'تحكم دقيق في الصوت والنسخ الاحتياطية والمحطات المخصصة.';
@override
String get settingsGroupAudioTitle => 'AUDIO';
String get settingsGroupAudioTitle => 'الصوت';
@override
String get settingsGroupStationsTitle => 'EMISORAS';
String get settingsGroupStationsTitle => 'المحطات';
@override
String get settingsGroupRecordingsTitle => 'GRABACIONES Y MÚSICA';
String get settingsGroupRecordingsTitle => 'التسجيلات والموسيقى';
@override
String get settingsGroupApplicationTitle => 'APLICACIÓN';
String get settingsGroupApplicationTitle => 'التطبيق';
@override
String get infoSectionTitle => 'Información';
String get infoSectionTitle => 'معلومات';
@override
String get languageSectionTitle => 'اللغة';
@@ -225,40 +225,38 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get equalizerBaseExplainer =>
'Este es el ecualizador base: se aplica a todas las emisoras que no tengan un ajuste propio. El de una emisora concreta se define desde su propia reproducción y manda sobre este.';
'هذا هو المعادل الأساسي: يُطبَّق على كل محطة لا تملك إعدادًا خاصًا بها. يُحدَّد معادل محطة معينة من شاشة تشغيلها الخاصة، وله الأولوية على هذا المعادل.';
@override
String get equalizerActiveOutputLabel => 'Salida activa';
String get equalizerActiveOutputLabel => 'المخرج النشط';
@override
String get equalizerActiveOutputDefault => 'El altavoz de este dispositivo';
String get equalizerActiveOutputDefault => 'مكبر صوت هذا الجهاز';
@override
String get equalizerStationsWithOwnEqTitle => 'Emisoras con ajuste propio';
String get equalizerStationsWithOwnEqTitle => 'محطات لديها معادل خاص';
@override
String get equalizerStationsWithOwnEqSubtitle =>
'Ignoran este ecualizador base';
String get equalizerStationsWithOwnEqSubtitle => 'تتجاهل هذا المعادل الأساسي';
@override
String get equalizerStationsWithOwnEqEmpty =>
'Ninguna emisora tiene ajuste propio todavía.';
'لا توجد محطة لديها معادل خاص حتى الآن.';
@override
String get equalizerSaveAsPresetAction => 'Guardar como preset';
String get equalizerSaveAsPresetAction => 'حفظ كإعداد مسبق';
@override
String get equalizerSavePresetDialogTitle => 'Guardar como preset';
String get equalizerSavePresetDialogTitle => 'حفظ كإعداد مسبق';
@override
String get equalizerSavePresetNameLabel => 'Nombre del preset';
String get equalizerSavePresetNameLabel => 'اسم الإعداد المسبق';
@override
String get equalizerSavePresetEmptyNameError =>
'Ingresá un nombre para el preset.';
String get equalizerSavePresetEmptyNameError => 'أدخل اسمًا للإعداد المسبق.';
@override
String get equalizerSavePresetConfirm => 'Guardar';
String get equalizerSavePresetConfirm => 'حفظ';
@override
String get preferredStationTitle => 'المحطة المفضلة';
@@ -433,47 +431,46 @@ class AppLocalizationsAr extends AppLocalizations {
}
@override
String get recordingsLibraryTitle => 'Mis grabaciones';
String get recordingsLibraryTitle => 'تسجيلاتي';
@override
String recordingsLibraryStorageCaption(int usedMb, int totalMb) {
return '$usedMb MB de $totalMb MB usados';
return 'تم استخدام $usedMb ميغابايت من أصل $totalMb ميغابايت';
}
@override
String get recordingsLibraryEmptyTitle => 'Todavía no hay grabaciones';
String get recordingsLibraryEmptyTitle => 'لا توجد تسجيلات بعد';
@override
String get recordingsLibraryEmptySubtitle =>
'Las grabaciones que guardes van a aparecer acá.';
'التسجيلات التي تحفظها ستظهر هنا.';
@override
String get recordingActionRename => 'Renombrar';
String get recordingActionRename => 'إعادة تسمية';
@override
String get recordingActionShare => 'Compartir';
String get recordingActionShare => 'مشاركة';
@override
String get recordingActionDelete => 'Eliminar';
String get recordingActionDelete => 'حذف';
@override
String get recordingRenameDialogTitle => 'Renombrar grabación';
String get recordingRenameDialogTitle => 'إعادة تسمية التسجيل';
@override
String get recordingRenameLabel => 'Nombre';
String get recordingRenameLabel => 'الاسم';
@override
String get recordingRenameEmptyError => 'Ingresá un nombre';
String get recordingRenameEmptyError => 'أدخل اسمًا';
@override
String get recordingDeleteConfirmTitle => '¿Eliminar grabación?';
String get recordingDeleteConfirmTitle => 'هل تريد حذف التسجيل؟';
@override
String get recordingDeleteConfirmMessage =>
'Esta acción no se puede deshacer.';
String get recordingDeleteConfirmMessage => 'لا يمكن التراجع عن هذا الإجراء.';
@override
String get recordingsLibrarySettingsTooltip => 'Ajustes de grabación';
String get recordingsLibrarySettingsTooltip => 'إعدادات التسجيل';
@override
String get stationOrderTitle => 'ترتيب المحطات';
@@ -485,7 +482,7 @@ class AppLocalizationsAr extends AppLocalizations {
String get stationOrderByQuality => 'حسب الجودة';
@override
String get stationOrderByPopularity => 'Por popularidad';
String get stationOrderByPopularity => 'حسب الشهرة';
@override
String get stationOrderScopeDescription =>
@@ -574,7 +571,7 @@ class AppLocalizationsAr extends AppLocalizations {
}
@override
String get favoritesFilterAllLabel => 'Todas';
String get favoritesFilterAllLabel => 'الكل';
@override
String favoriteGroupsChipLabel(Object groupName, int count) {
@@ -582,10 +579,10 @@ class AppLocalizationsAr extends AppLocalizations {
}
@override
String get favoriteGroupsManage => 'Gestionar listas';
String get favoriteGroupsManage => 'إدارة القوائم';
@override
String get customStationsAddCta => 'Añadir emisora personalizada';
String get customStationsAddCta => 'إضافة محطة مخصصة';
@override
String get alarmPostponedCurrentExecution => 'تم تأجيل المنبه لهذا التشغيل.';
@@ -631,8 +628,10 @@ class AppLocalizationsAr extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count resultados',
one: '1 resultado',
other: '$count نتيجة',
few: '$count نتائج',
two: 'نتيجتان',
one: 'نتيجة واحدة',
);
return '$_temp0';
}
@@ -642,23 +641,25 @@ class AppLocalizationsAr extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Quitar los $count filtros',
one: 'Quitar el filtro',
other: 'إزالة $count فلتر',
few: 'إزالة $count فلاتر',
two: 'إزالة الفلترين',
one: 'إزالة الفلتر',
);
return '$_temp0';
}
@override
String get countriesScreenTitle => 'Países';
String get countriesScreenTitle => 'الدول';
@override
String get countriesYourLanguagesTitle => 'Tus idiomas';
String get countriesYourLanguagesTitle => 'لغاتك';
@override
String get countriesAllTitle => 'Todos los países';
String get countriesAllTitle => 'كل الدول';
@override
String get radioCountriesError => 'No pudimos cargar los países.';
String get radioCountriesError => 'تعذّر تحميل الدول.';
@override
String get countrySpain => 'إسبانيا';
@@ -733,20 +734,20 @@ class AppLocalizationsAr extends AppLocalizations {
String get qualityHd => 'جودة HD';
@override
String get yourStationsTitle => 'Tus emisoras';
String get yourStationsTitle => 'محطاتك';
@override
String get seeAllAction => 'Ver todas';
String get seeAllAction => 'عرض الكل';
@override
String get openFullPlayerTooltip => 'Abrir reproductor completo';
String get openFullPlayerTooltip => 'فتح المشغل الكامل';
@override
String get nothingPlayingTitle => 'Todavía no estás escuchando nada';
String get nothingPlayingTitle => 'لا تستمع إلى شيء بعد';
@override
String get nothingPlayingSubtitle =>
'Elegí una emisora de Tus emisoras o buscá una para empezar.';
'اختر محطة من محطاتك أو ابحث عن واحدة للبدء.';
@override
String get nearYou => 'بالقرب منك';
@@ -878,13 +879,13 @@ class AppLocalizationsAr extends AppLocalizations {
String get deleteTooltip => 'حذف';
@override
String get alarmHeroSkipAction => 'Saltar';
String get alarmHeroSkipAction => 'تخطي';
@override
String get alarmDeleteConfirmTitle => '¿Eliminar alarma?';
String get alarmDeleteConfirmTitle => 'هل تريد حذف المنبه؟';
@override
String get alarmDeleteConfirmMessage => 'Esta acción no se puede deshacer.';
String get alarmDeleteConfirmMessage => 'لا يمكن التراجع عن هذا الإجراء.';
@override
String get alarmSkippedNoNextSnackbar =>
@@ -1042,41 +1043,40 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String vacationRangesCount(int count) {
return '$count rangos';
return '$count فترة';
}
@override
String vacationSummaryActiveCountdown(int days) {
return 'En curso · quedan $days días';
return 'جارٍ الآن · تبقّى $days يوم';
}
@override
String vacationSummaryUpcomingCountdown(int days) {
return 'Próximo rango en $days días';
return 'الفترة التالية خلال $days يوم';
}
@override
String vacationImpactPausedLabel(Object times) {
return 'Pausa: $times';
return 'إيقاف مؤقت: $times';
}
@override
String vacationImpactContinuesLabel(Object times) {
return 'Sigue sonando: $times';
return 'يستمر الرنين: $times';
}
@override
String get vacationUpcomingSectionTitle => 'PROGRAMADOS';
String get vacationUpcomingSectionTitle => 'المجدولة';
@override
String get vacationPastSectionTitle => 'Rangos pasados';
String get vacationPastSectionTitle => 'الفترات السابقة';
@override
String get addVacationRangeCta => 'Añadir rango';
String get addVacationRangeCta => 'إضافة فترة';
@override
String get vacationNoActiveRangeHint =>
'No hay un rango de vacaciones activo ahora mismo.';
String get vacationNoActiveRangeHint => 'لا توجد فترة إجازة نشطة الآن.';
@override
String get vacationsDefaultName => 'إجازات';
@@ -1361,10 +1361,10 @@ class AppLocalizationsAr extends AppLocalizations {
String get pausePlaybackTooltip => 'إيقاف مؤقت';
@override
String get playerQualityChangeAction => 'Cambiar';
String get playerQualityChangeAction => 'تغيير';
@override
String get playerToolEqLabel => 'EQ propio';
String get playerToolEqLabel => 'معادل خاص';
@override
String qualityOriginal(Object quality) {
@@ -1454,16 +1454,16 @@ class AppLocalizationsAr extends AppLocalizations {
String get vacationIconLabel => 'وضع الإجازة';
@override
String get alarmAdvancedSectionTitle => 'Avanzado';
String get alarmAdvancedSectionTitle => 'متقدم';
@override
String get alarmInlineHourLabel => 'Hora';
String get alarmInlineHourLabel => 'الساعة';
@override
String get alarmInlineMinuteLabel => 'Minuto';
String get alarmInlineMinuteLabel => 'الدقيقة';
@override
String get alarmVolumeRisingStatus => 'Subiendo volumen';
String get alarmVolumeRisingStatus => 'رفع مستوى الصوت تدريجيًا';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
@@ -1580,4 +1580,33 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get localMusicFolderGenericName => 'مجلد محدد';
@override
String get welcomeHeadline => 'عالمك، على الهواء مباشرة';
@override
String get welcomeBody =>
'53,412 محطة من 238 دولة. احفظ محطاتك المفضلة، خذها معك في السيارة، واستيقظ عليها.';
@override
String get welcomeBullet1Title => 'معادل لكل محطة';
@override
String get welcomeBullet1Subtitle => 'بالإضافة إلى إعداد مسبق لكل جهاز خرج';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle =>
'محطاتك المفضلة وموسيقاك المحلية في السيارة';
@override
String get welcomeBullet3Title => 'منبهات موسيقية';
@override
String get welcomeBullet3Subtitle => 'مع رفع تدريجي للصوت ووضع الإجازة';
@override
String get welcomeCtaLabel => 'ابدأ الاستماع';
}
+94 -65
View File
@@ -12,7 +12,7 @@ class AppLocalizationsBn extends AppLocalizations {
String get appTitle => 'PluriWave';
@override
String get navHome => 'হোম';
String get navHome => 'শোনা';
@override
String get navSearch => 'অনুসন্ধান';
@@ -101,19 +101,19 @@ class AppLocalizationsBn extends AppLocalizations {
'শব্দ, ব্যাকআপ এবং নিজস্ব স্টেশনের সূক্ষ্ম নিয়ন্ত্রণ।';
@override
String get settingsGroupAudioTitle => 'AUDIO';
String get settingsGroupAudioTitle => 'অডিও';
@override
String get settingsGroupStationsTitle => 'EMISORAS';
String get settingsGroupStationsTitle => 'স্টেশন';
@override
String get settingsGroupRecordingsTitle => 'GRABACIONES Y MÚSICA';
String get settingsGroupRecordingsTitle => 'রেকর্ডিং ও সংগীত';
@override
String get settingsGroupApplicationTitle => 'APLICACIÓN';
String get settingsGroupApplicationTitle => 'অ্যাপ্লিকেশন';
@override
String get infoSectionTitle => 'Información';
String get infoSectionTitle => 'তথ্য';
@override
String get languageSectionTitle => 'ভাষা';
@@ -228,40 +228,40 @@ class AppLocalizationsBn extends AppLocalizations {
@override
String get equalizerBaseExplainer =>
'Este es el ecualizador base: se aplica a todas las emisoras que no tengan un ajuste propio. El de una emisora concreta se define desde su propia reproducción y manda sobre este.';
'এটি বেস ইকুয়ালাইজার: এটি নিজস্ব সেটিং নেই এমন সব স্টেশনে প্রযোজ্য। কোনো নির্দিষ্ট স্টেশনের নিজস্ব EQ তার প্লেব্যাক স্ক্রিন থেকে ঠিক করা হয় এবং এটির চেয়ে অগ্রাধিকার পায়।';
@override
String get equalizerActiveOutputLabel => 'Salida activa';
String get equalizerActiveOutputLabel => 'সক্রিয় আউটপুট';
@override
String get equalizerActiveOutputDefault => 'El altavoz de este dispositivo';
String get equalizerActiveOutputDefault => 'এই ডিভাইসের স্পিকার';
@override
String get equalizerStationsWithOwnEqTitle => 'Emisoras con ajuste propio';
String get equalizerStationsWithOwnEqTitle => 'নিজস্ব EQ থাকা স্টেশন';
@override
String get equalizerStationsWithOwnEqSubtitle =>
'Ignoran este ecualizador base';
'এই বেস ইকুয়ালাইজার উপেক্ষা করে';
@override
String get equalizerStationsWithOwnEqEmpty =>
'Ninguna emisora tiene ajuste propio todavía.';
'এখনও কোনো স্টেশনের নিজস্ব EQ নেই।';
@override
String get equalizerSaveAsPresetAction => 'Guardar como preset';
String get equalizerSaveAsPresetAction => 'প্রিসেট হিসেবে সংরক্ষণ করুন';
@override
String get equalizerSavePresetDialogTitle => 'Guardar como preset';
String get equalizerSavePresetDialogTitle => 'প্রিসেট হিসেবে সংরক্ষণ করুন';
@override
String get equalizerSavePresetNameLabel => 'Nombre del preset';
String get equalizerSavePresetNameLabel => 'প্রিসেটের নাম';
@override
String get equalizerSavePresetEmptyNameError =>
'Ingresá un nombre para el preset.';
'প্রিসেটের জন্য একটি নাম লিখুন।';
@override
String get equalizerSavePresetConfirm => 'Guardar';
String get equalizerSavePresetConfirm => 'সংরক্ষণ করুন';
@override
String get preferredStationTitle => 'পছন্দের স্টেশন';
@@ -436,47 +436,47 @@ class AppLocalizationsBn extends AppLocalizations {
}
@override
String get recordingsLibraryTitle => 'Mis grabaciones';
String get recordingsLibraryTitle => 'আমার রেকর্ডিং';
@override
String recordingsLibraryStorageCaption(int usedMb, int totalMb) {
return '$usedMb MB de $totalMb MB usados';
return '$totalMb MB-এর মধ্যে $usedMb MB ব্যবহৃত';
}
@override
String get recordingsLibraryEmptyTitle => 'Todavía no hay grabaciones';
String get recordingsLibraryEmptyTitle => 'এখনও কোনো রেকর্ডিং নেই';
@override
String get recordingsLibraryEmptySubtitle =>
'Las grabaciones que guardes van a aparecer acá.';
'আপনি যেসব রেকর্ডিং সংরক্ষণ করবেন তা এখানে দেখা যাবে।';
@override
String get recordingActionRename => 'Renombrar';
String get recordingActionRename => 'নাম পরিবর্তন করুন';
@override
String get recordingActionShare => 'Compartir';
String get recordingActionShare => 'শেয়ার করুন';
@override
String get recordingActionDelete => 'Eliminar';
String get recordingActionDelete => 'মুছে ফেলুন';
@override
String get recordingRenameDialogTitle => 'Renombrar grabación';
String get recordingRenameDialogTitle => 'রেকর্ডিং-এর নাম পরিবর্তন করুন';
@override
String get recordingRenameLabel => 'Nombre';
String get recordingRenameLabel => 'নাম';
@override
String get recordingRenameEmptyError => 'Ingresá un nombre';
String get recordingRenameEmptyError => 'একটি নাম লিখুন';
@override
String get recordingDeleteConfirmTitle => '¿Eliminar grabación?';
String get recordingDeleteConfirmTitle => 'রেকর্ডিং মুছবেন?';
@override
String get recordingDeleteConfirmMessage =>
'Esta acción no se puede deshacer.';
'এই পদক্ষেপ ফিরিয়ে নেওয়া যাবে না।';
@override
String get recordingsLibrarySettingsTooltip => 'Ajustes de grabación';
String get recordingsLibrarySettingsTooltip => 'রেকর্ডিং সেটিংস';
@override
String get stationOrderTitle => 'স্টেশনের ক্রম';
@@ -488,7 +488,7 @@ class AppLocalizationsBn extends AppLocalizations {
String get stationOrderByQuality => 'গুণমান অনুযায়ী';
@override
String get stationOrderByPopularity => 'Por popularidad';
String get stationOrderByPopularity => 'জনপ্রিয়তা অনুসারে';
@override
String get stationOrderScopeDescription =>
@@ -577,7 +577,7 @@ class AppLocalizationsBn extends AppLocalizations {
}
@override
String get favoritesFilterAllLabel => 'Todas';
String get favoritesFilterAllLabel => 'সব';
@override
String favoriteGroupsChipLabel(Object groupName, int count) {
@@ -585,10 +585,10 @@ class AppLocalizationsBn extends AppLocalizations {
}
@override
String get favoriteGroupsManage => 'Gestionar listas';
String get favoriteGroupsManage => 'তালিকা পরিচালনা করুন';
@override
String get customStationsAddCta => 'Añadir emisora personalizada';
String get customStationsAddCta => 'নিজস্ব স্টেশন যোগ করুন';
@override
String get alarmPostponedCurrentExecution =>
@@ -635,8 +635,8 @@ class AppLocalizationsBn extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count resultados',
one: '1 resultado',
other: '$countটি ফলাফল',
one: '১টি ফলাফল',
);
return '$_temp0';
}
@@ -646,23 +646,23 @@ class AppLocalizationsBn extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Quitar los $count filtros',
one: 'Quitar el filtro',
other: '$countটি ফিল্টার সরান',
one: 'ফিল্টার সরান',
);
return '$_temp0';
}
@override
String get countriesScreenTitle => 'Países';
String get countriesScreenTitle => 'দেশসমূহ';
@override
String get countriesYourLanguagesTitle => 'Tus idiomas';
String get countriesYourLanguagesTitle => 'আপনার ভাষাসমূহ';
@override
String get countriesAllTitle => 'Todos los países';
String get countriesAllTitle => 'সব দেশ';
@override
String get radioCountriesError => 'No pudimos cargar los países.';
String get radioCountriesError => 'দেশগুলো লোড করা যায়নি।';
@override
String get countrySpain => 'স্পেন';
@@ -737,20 +737,20 @@ class AppLocalizationsBn extends AppLocalizations {
String get qualityHd => 'HD গুণমান';
@override
String get yourStationsTitle => 'Tus emisoras';
String get yourStationsTitle => 'আপনার স্টেশন';
@override
String get seeAllAction => 'Ver todas';
String get seeAllAction => 'সব দেখুন';
@override
String get openFullPlayerTooltip => 'Abrir reproductor completo';
String get openFullPlayerTooltip => 'সম্পূর্ণ প্লেয়ার খুলুন';
@override
String get nothingPlayingTitle => 'Todavía no estás escuchando nada';
String get nothingPlayingTitle => 'এখনও কিছু চলছে না';
@override
String get nothingPlayingSubtitle =>
'Elegí una emisora de Tus emisoras o buscá una para empezar.';
'শুরু করতে আপনার স্টেশন থেকে একটি বেছে নিন বা একটি খুঁজুন।';
@override
String get nearYou => 'আপনার কাছাকাছি';
@@ -883,13 +883,13 @@ class AppLocalizationsBn extends AppLocalizations {
String get deleteTooltip => 'মুছুন';
@override
String get alarmHeroSkipAction => 'Saltar';
String get alarmHeroSkipAction => 'এড়িয়ে যান';
@override
String get alarmDeleteConfirmTitle => '¿Eliminar alarma?';
String get alarmDeleteConfirmTitle => 'অ্যালার্ম মুছবেন?';
@override
String get alarmDeleteConfirmMessage => 'Esta acción no se puede deshacer.';
String get alarmDeleteConfirmMessage => 'এই পদক্ষেপ ফিরিয়ে নেওয়া যাবে না।';
@override
String get alarmSkippedNoNextSnackbar =>
@@ -1049,41 +1049,41 @@ class AppLocalizationsBn extends AppLocalizations {
@override
String vacationRangesCount(int count) {
return '$count rangos';
return '$countটি সময়সীমা';
}
@override
String vacationSummaryActiveCountdown(int days) {
return 'En curso · quedan $days días';
return 'চলমান · আর $days দিন বাকি';
}
@override
String vacationSummaryUpcomingCountdown(int days) {
return 'Próximo rango en $days días';
return 'পরবর্তী সময়সীমা $days দিনে';
}
@override
String vacationImpactPausedLabel(Object times) {
return 'Pausa: $times';
return 'বিরতি: $times';
}
@override
String vacationImpactContinuesLabel(Object times) {
return 'Sigue sonando: $times';
return 'বাজতেই থাকবে: $times';
}
@override
String get vacationUpcomingSectionTitle => 'PROGRAMADOS';
String get vacationUpcomingSectionTitle => 'নির্ধারিত';
@override
String get vacationPastSectionTitle => 'Rangos pasados';
String get vacationPastSectionTitle => 'অতীত সময়সীমা';
@override
String get addVacationRangeCta => 'Añadir rango';
String get addVacationRangeCta => 'সময়সীমা যোগ করুন';
@override
String get vacationNoActiveRangeHint =>
'No hay un rango de vacaciones activo ahora mismo.';
'এই মুহূর্তে কোনো সক্রিয় ছুটির সময়সীমা নেই।';
@override
String get vacationsDefaultName => 'ছুটি';
@@ -1370,10 +1370,10 @@ class AppLocalizationsBn extends AppLocalizations {
String get pausePlaybackTooltip => 'প্লেব্যাক বিরতি';
@override
String get playerQualityChangeAction => 'Cambiar';
String get playerQualityChangeAction => 'পরিবর্তন করুন';
@override
String get playerToolEqLabel => 'EQ propio';
String get playerToolEqLabel => 'নিজস্ব EQ';
@override
String qualityOriginal(Object quality) {
@@ -1461,16 +1461,16 @@ class AppLocalizationsBn extends AppLocalizations {
String get vacationIconLabel => 'ছুটির মোড';
@override
String get alarmAdvancedSectionTitle => 'Avanzado';
String get alarmAdvancedSectionTitle => 'উন্নত';
@override
String get alarmInlineHourLabel => 'Hora';
String get alarmInlineHourLabel => 'ঘণ্টা';
@override
String get alarmInlineMinuteLabel => 'Minuto';
String get alarmInlineMinuteLabel => 'মিনিট';
@override
String get alarmVolumeRisingStatus => 'Subiendo volumen';
String get alarmVolumeRisingStatus => 'ভলিউম বাড়ছে';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
@@ -1588,4 +1588,33 @@ class AppLocalizationsBn extends AppLocalizations {
@override
String get localMusicFolderGenericName => 'নির্বাচিত ফোল্ডার';
@override
String get welcomeHeadline => 'আপনার বিশ্ব, সরাসরি';
@override
String get welcomeBody =>
'২৩৮টি দেশের ৫৩,৪১২টি স্টেশন। আপনার প্রিয় স্টেশন সংরক্ষণ করুন, গাড়িতে নিয়ে যান এবং সেগুলো দিয়ে ঘুম থেকে উঠুন।';
@override
String get welcomeBullet1Title => 'স্টেশন অনুযায়ী ইকুয়ালাইজার';
@override
String get welcomeBullet1Subtitle =>
'এবং প্রতিটি আউটপুট ডিভাইসের জন্য একটি প্রিসেট';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle => 'গাড়িতে আপনার প্রিয় ও স্থানীয় সংগীত';
@override
String get welcomeBullet3Title => 'সুরেলা অ্যালার্ম';
@override
String get welcomeBullet3Subtitle => 'ধীরে ভলিউম বৃদ্ধি ও ছুটির মোড সহ';
@override
String get welcomeCtaLabel => 'শোনা শুরু করুন';
}
+95 -64
View File
@@ -12,7 +12,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get appTitle => 'PluriWave';
@override
String get navHome => 'Start';
String get navHome => 'Hören';
@override
String get navSearch => 'Suche';
@@ -103,16 +103,16 @@ class AppLocalizationsDe extends AppLocalizations {
String get settingsGroupAudioTitle => 'AUDIO';
@override
String get settingsGroupStationsTitle => 'EMISORAS';
String get settingsGroupStationsTitle => 'SENDER';
@override
String get settingsGroupRecordingsTitle => 'GRABACIONES Y MÚSICA';
String get settingsGroupRecordingsTitle => 'AUFNAHMEN & MUSIK';
@override
String get settingsGroupApplicationTitle => 'APLICACIÓN';
String get settingsGroupApplicationTitle => 'ANWENDUNG';
@override
String get infoSectionTitle => 'Información';
String get infoSectionTitle => 'Info';
@override
String get languageSectionTitle => 'Sprache';
@@ -228,40 +228,40 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get equalizerBaseExplainer =>
'Este es el ecualizador base: se aplica a todas las emisoras que no tengan un ajuste propio. El de una emisora concreta se define desde su propia reproducción y manda sobre este.';
'Dies ist der Basis-Equalizer: Er gilt für alle Sender ohne eigene Einstellung. Der eigene EQ eines Senders wird auf dessen eigenem Wiedergabebildschirm festgelegt und hat Vorrang vor diesem hier.';
@override
String get equalizerActiveOutputLabel => 'Salida activa';
String get equalizerActiveOutputLabel => 'Aktiver Ausgang';
@override
String get equalizerActiveOutputDefault => 'El altavoz de este dispositivo';
String get equalizerActiveOutputDefault => 'Der Lautsprecher dieses Geräts';
@override
String get equalizerStationsWithOwnEqTitle => 'Emisoras con ajuste propio';
String get equalizerStationsWithOwnEqTitle => 'Sender mit eigenem EQ';
@override
String get equalizerStationsWithOwnEqSubtitle =>
'Ignoran este ecualizador base';
'Ignorieren diesen Basis-Equalizer';
@override
String get equalizerStationsWithOwnEqEmpty =>
'Ninguna emisora tiene ajuste propio todavía.';
'Noch kein Sender hat einen eigenen EQ.';
@override
String get equalizerSaveAsPresetAction => 'Guardar como preset';
String get equalizerSaveAsPresetAction => 'Als Preset speichern';
@override
String get equalizerSavePresetDialogTitle => 'Guardar como preset';
String get equalizerSavePresetDialogTitle => 'Als Preset speichern';
@override
String get equalizerSavePresetNameLabel => 'Nombre del preset';
String get equalizerSavePresetNameLabel => 'Preset-Name';
@override
String get equalizerSavePresetEmptyNameError =>
'Ingresá un nombre para el preset.';
'Gib einen Namen für das Preset ein.';
@override
String get equalizerSavePresetConfirm => 'Guardar';
String get equalizerSavePresetConfirm => 'Speichern';
@override
String get preferredStationTitle => 'Bevorzugter Sender';
@@ -439,47 +439,47 @@ class AppLocalizationsDe extends AppLocalizations {
}
@override
String get recordingsLibraryTitle => 'Mis grabaciones';
String get recordingsLibraryTitle => 'Meine Aufnahmen';
@override
String recordingsLibraryStorageCaption(int usedMb, int totalMb) {
return '$usedMb MB de $totalMb MB usados';
return '$usedMb MB von $totalMb MB belegt';
}
@override
String get recordingsLibraryEmptyTitle => 'Todavía no hay grabaciones';
String get recordingsLibraryEmptyTitle => 'Noch keine Aufnahmen';
@override
String get recordingsLibraryEmptySubtitle =>
'Las grabaciones que guardes van a aparecer acá.';
'Aufnahmen, die du speicherst, erscheinen hier.';
@override
String get recordingActionRename => 'Renombrar';
String get recordingActionRename => 'Umbenennen';
@override
String get recordingActionShare => 'Compartir';
String get recordingActionShare => 'Teilen';
@override
String get recordingActionDelete => 'Eliminar';
String get recordingActionDelete => 'Löschen';
@override
String get recordingRenameDialogTitle => 'Renombrar grabación';
String get recordingRenameDialogTitle => 'Aufnahme umbenennen';
@override
String get recordingRenameLabel => 'Nombre';
String get recordingRenameLabel => 'Name';
@override
String get recordingRenameEmptyError => 'Ingresá un nombre';
String get recordingRenameEmptyError => 'Gib einen Namen ein';
@override
String get recordingDeleteConfirmTitle => '¿Eliminar grabación?';
String get recordingDeleteConfirmTitle => 'Aufnahme löschen?';
@override
String get recordingDeleteConfirmMessage =>
'Esta acción no se puede deshacer.';
'Dies kann nicht rückgängig gemacht werden.';
@override
String get recordingsLibrarySettingsTooltip => 'Ajustes de grabación';
String get recordingsLibrarySettingsTooltip => 'Aufnahmeeinstellungen';
@override
String get stationOrderTitle => 'Senderreihenfolge';
@@ -491,7 +491,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get stationOrderByQuality => 'Nach Qualität';
@override
String get stationOrderByPopularity => 'Por popularidad';
String get stationOrderByPopularity => 'Nach Beliebtheit';
@override
String get stationOrderScopeDescription =>
@@ -580,7 +580,7 @@ class AppLocalizationsDe extends AppLocalizations {
}
@override
String get favoritesFilterAllLabel => 'Todas';
String get favoritesFilterAllLabel => 'Alle';
@override
String favoriteGroupsChipLabel(Object groupName, int count) {
@@ -588,10 +588,10 @@ class AppLocalizationsDe extends AppLocalizations {
}
@override
String get favoriteGroupsManage => 'Gestionar listas';
String get favoriteGroupsManage => 'Listen verwalten';
@override
String get customStationsAddCta => 'Añadir emisora personalizada';
String get customStationsAddCta => 'Benutzerdefinierten Sender hinzufügen';
@override
String get alarmPostponedCurrentExecution =>
@@ -638,8 +638,8 @@ class AppLocalizationsDe extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count resultados',
one: '1 resultado',
other: '$count Ergebnisse',
one: '1 Ergebnis',
);
return '$_temp0';
}
@@ -649,23 +649,23 @@ class AppLocalizationsDe extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Quitar los $count filtros',
one: 'Quitar el filtro',
other: '$count Filter entfernen',
one: 'Filter entfernen',
);
return '$_temp0';
}
@override
String get countriesScreenTitle => 'Países';
String get countriesScreenTitle => 'Länder';
@override
String get countriesYourLanguagesTitle => 'Tus idiomas';
String get countriesYourLanguagesTitle => 'Deine Sprachen';
@override
String get countriesAllTitle => 'Todos los países';
String get countriesAllTitle => 'Alle Länder';
@override
String get radioCountriesError => 'No pudimos cargar los países.';
String get radioCountriesError => 'Die Länder konnten nicht geladen werden.';
@override
String get countrySpain => 'Spanien';
@@ -740,20 +740,20 @@ class AppLocalizationsDe extends AppLocalizations {
String get qualityHd => 'HD-Qualität';
@override
String get yourStationsTitle => 'Tus emisoras';
String get yourStationsTitle => 'Deine Sender';
@override
String get seeAllAction => 'Ver todas';
String get seeAllAction => 'Alle ansehen';
@override
String get openFullPlayerTooltip => 'Abrir reproductor completo';
String get openFullPlayerTooltip => 'Vollständigen Player öffnen';
@override
String get nothingPlayingTitle => 'Todavía no estás escuchando nada';
String get nothingPlayingTitle => 'Es läuft noch nichts';
@override
String get nothingPlayingSubtitle =>
'Elegí una emisora de Tus emisoras o buscá una para empezar.';
'Wähle einen Sender aus Deine Sender oder suche einen, um zu starten.';
@override
String get nearYou => 'In deiner Nähe';
@@ -886,13 +886,14 @@ class AppLocalizationsDe extends AppLocalizations {
String get deleteTooltip => 'Löschen';
@override
String get alarmHeroSkipAction => 'Saltar';
String get alarmHeroSkipAction => 'Überspringen';
@override
String get alarmDeleteConfirmTitle => '¿Eliminar alarma?';
String get alarmDeleteConfirmTitle => 'Alarm löschen?';
@override
String get alarmDeleteConfirmMessage => 'Esta acción no se puede deshacer.';
String get alarmDeleteConfirmMessage =>
'Dies kann nicht rückgängig gemacht werden.';
@override
String get alarmSkippedNoNextSnackbar =>
@@ -1050,41 +1051,41 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String vacationRangesCount(int count) {
return '$count rangos';
return '$count Zeiträume';
}
@override
String vacationSummaryActiveCountdown(int days) {
return 'En curso · quedan $days días';
return 'Läuft gerade · noch $days Tage';
}
@override
String vacationSummaryUpcomingCountdown(int days) {
return 'Próximo rango en $days días';
return 'Nächster Zeitraum in $days Tagen';
}
@override
String vacationImpactPausedLabel(Object times) {
return 'Pausa: $times';
return 'Pausiert: $times';
}
@override
String vacationImpactContinuesLabel(Object times) {
return 'Sigue sonando: $times';
return 'Klingelt weiter: $times';
}
@override
String get vacationUpcomingSectionTitle => 'PROGRAMADOS';
String get vacationUpcomingSectionTitle => 'GEPLANT';
@override
String get vacationPastSectionTitle => 'Rangos pasados';
String get vacationPastSectionTitle => 'Vergangene Zeiträume';
@override
String get addVacationRangeCta => 'Añadir rango';
String get addVacationRangeCta => 'Zeitraum hinzufügen';
@override
String get vacationNoActiveRangeHint =>
'No hay un rango de vacaciones activo ahora mismo.';
'Momentan ist kein aktiver Urlaubszeitraum vorhanden.';
@override
String get vacationsDefaultName => 'Ferien';
@@ -1378,10 +1379,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get pausePlaybackTooltip => 'Wiedergabe pausieren';
@override
String get playerQualityChangeAction => 'Cambiar';
String get playerQualityChangeAction => 'Ändern';
@override
String get playerToolEqLabel => 'EQ propio';
String get playerToolEqLabel => 'Eigener EQ';
@override
String qualityOriginal(Object quality) {
@@ -1471,16 +1472,16 @@ class AppLocalizationsDe extends AppLocalizations {
String get vacationIconLabel => 'Urlaubsmodus';
@override
String get alarmAdvancedSectionTitle => 'Avanzado';
String get alarmAdvancedSectionTitle => 'Erweitert';
@override
String get alarmInlineHourLabel => 'Hora';
String get alarmInlineHourLabel => 'Stunde';
@override
String get alarmInlineMinuteLabel => 'Minuto';
String get alarmInlineMinuteLabel => 'Minute';
@override
String get alarmVolumeRisingStatus => 'Subiendo volumen';
String get alarmVolumeRisingStatus => 'Lautstärke wird erhöht';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
@@ -1599,4 +1600,34 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get localMusicFolderGenericName => 'Ausgewählter Ordner';
@override
String get welcomeHeadline => 'Deine Welt, live';
@override
String get welcomeBody =>
'53.412 Sender aus 238 Ländern. Speichere deine Favoriten, nimm sie mit ins Auto und wache mit ihnen auf.';
@override
String get welcomeBullet1Title => 'Equalizer pro Sender';
@override
String get welcomeBullet1Subtitle => 'Plus ein Preset pro Ausgabegerät';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle =>
'Deine Favoriten und lokale Musik im Auto';
@override
String get welcomeBullet3Title => 'Musikalarme';
@override
String get welcomeBullet3Subtitle =>
'Mit sanftem Lautstärkeanstieg und Urlaubsmodus';
@override
String get welcomeCtaLabel => 'Jetzt hören';
}
+30
View File
@@ -1581,4 +1581,34 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get localMusicFolderGenericName => 'Selected folder';
@override
String get welcomeHeadline => 'Your world, live';
@override
String get welcomeBody =>
'53,412 stations from 238 countries. Save your favorites, take them to the car, and wake up to them.';
@override
String get welcomeBullet1Title => 'Per-station equalizer';
@override
String get welcomeBullet1Subtitle => 'Plus a preset per output device';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle =>
'Your favorites and local music in the car';
@override
String get welcomeBullet3Title => 'Music alarms';
@override
String get welcomeBullet3Subtitle =>
'With gradual volume rise and vacation mode';
@override
String get welcomeCtaLabel => 'Start listening';
}
+30
View File
@@ -1594,4 +1594,34 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get localMusicFolderGenericName => 'Carpeta seleccionada';
@override
String get welcomeHeadline => 'Tu mundo, en directo';
@override
String get welcomeBody =>
'53.412 emisoras de 238 países. Guardá tus favoritas, llevalas al auto y despertate con ellas.';
@override
String get welcomeBullet1Title => 'Ecualizador por emisora';
@override
String get welcomeBullet1Subtitle => 'Y un preset por dispositivo de salida';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle =>
'Tus favoritas y tu música local en el auto';
@override
String get welcomeBullet3Title => 'Alarmas musicales';
@override
String get welcomeBullet3Subtitle =>
'Con subida progresiva y modo vacaciones';
@override
String get welcomeCtaLabel => 'Empezar a escuchar';
}
+96 -65
View File
@@ -12,7 +12,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get appTitle => 'PluriWave';
@override
String get navHome => 'Accueil';
String get navHome => 'Écouter';
@override
String get navSearch => 'Recherche';
@@ -104,16 +104,16 @@ class AppLocalizationsFr extends AppLocalizations {
String get settingsGroupAudioTitle => 'AUDIO';
@override
String get settingsGroupStationsTitle => 'EMISORAS';
String get settingsGroupStationsTitle => 'STATIONS';
@override
String get settingsGroupRecordingsTitle => 'GRABACIONES Y MÚSICA';
String get settingsGroupRecordingsTitle => 'ENREGISTREMENTS ET MUSIQUE';
@override
String get settingsGroupApplicationTitle => 'APLICACIÓN';
String get settingsGroupApplicationTitle => 'APPLICATION';
@override
String get infoSectionTitle => 'Información';
String get infoSectionTitle => 'Infos';
@override
String get languageSectionTitle => 'Langue';
@@ -229,40 +229,40 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get equalizerBaseExplainer =>
'Este es el ecualizador base: se aplica a todas las emisoras que no tengan un ajuste propio. El de una emisora concreta se define desde su propia reproducción y manda sobre este.';
'Voici l\'égaliseur de base : il s\'applique à toutes les stations sans réglage propre. L\'EQ propre à une station est défini depuis son propre écran de lecture et prime sur celui-ci.';
@override
String get equalizerActiveOutputLabel => 'Salida activa';
String get equalizerActiveOutputLabel => 'Sortie active';
@override
String get equalizerActiveOutputDefault => 'El altavoz de este dispositivo';
String get equalizerActiveOutputDefault => 'Le haut-parleur de cet appareil';
@override
String get equalizerStationsWithOwnEqTitle => 'Emisoras con ajuste propio';
String get equalizerStationsWithOwnEqTitle => 'Stations avec un EQ propre';
@override
String get equalizerStationsWithOwnEqSubtitle =>
'Ignoran este ecualizador base';
'Ignorent cet égaliseur de base';
@override
String get equalizerStationsWithOwnEqEmpty =>
'Ninguna emisora tiene ajuste propio todavía.';
'Aucune station n\'a encore d\'EQ propre.';
@override
String get equalizerSaveAsPresetAction => 'Guardar como preset';
String get equalizerSaveAsPresetAction => 'Enregistrer comme Preset';
@override
String get equalizerSavePresetDialogTitle => 'Guardar como preset';
String get equalizerSavePresetDialogTitle => 'Enregistrer comme Preset';
@override
String get equalizerSavePresetNameLabel => 'Nombre del preset';
String get equalizerSavePresetNameLabel => 'Nom du Preset';
@override
String get equalizerSavePresetEmptyNameError =>
'Ingresá un nombre para el preset.';
'Saisissez un nom pour le Preset.';
@override
String get equalizerSavePresetConfirm => 'Guardar';
String get equalizerSavePresetConfirm => 'Enregistrer';
@override
String get preferredStationTitle => 'Station préférée';
@@ -441,47 +441,47 @@ class AppLocalizationsFr extends AppLocalizations {
}
@override
String get recordingsLibraryTitle => 'Mis grabaciones';
String get recordingsLibraryTitle => 'Mes enregistrements';
@override
String recordingsLibraryStorageCaption(int usedMb, int totalMb) {
return '$usedMb MB de $totalMb MB usados';
return '$usedMb Mo sur $totalMb Mo utilisés';
}
@override
String get recordingsLibraryEmptyTitle => 'Todavía no hay grabaciones';
String get recordingsLibraryEmptyTitle =>
'Aucun enregistrement pour l\'instant';
@override
String get recordingsLibraryEmptySubtitle =>
'Las grabaciones que guardes van a aparecer acá.';
'Les enregistrements que vous sauvegardez apparaîtront ici.';
@override
String get recordingActionRename => 'Renombrar';
String get recordingActionRename => 'Renommer';
@override
String get recordingActionShare => 'Compartir';
String get recordingActionShare => 'Partager';
@override
String get recordingActionDelete => 'Eliminar';
String get recordingActionDelete => 'Supprimer';
@override
String get recordingRenameDialogTitle => 'Renombrar grabación';
String get recordingRenameDialogTitle => 'Renommer l\'enregistrement';
@override
String get recordingRenameLabel => 'Nombre';
String get recordingRenameLabel => 'Nom';
@override
String get recordingRenameEmptyError => 'Ingresá un nombre';
String get recordingRenameEmptyError => 'Saisissez un nom';
@override
String get recordingDeleteConfirmTitle => '¿Eliminar grabación?';
String get recordingDeleteConfirmTitle => 'Supprimer l\'enregistrement ?';
@override
String get recordingDeleteConfirmMessage =>
'Esta acción no se puede deshacer.';
String get recordingDeleteConfirmMessage => 'Cette action est irréversible.';
@override
String get recordingsLibrarySettingsTooltip => 'Ajustes de grabación';
String get recordingsLibrarySettingsTooltip => 'Paramètres d\'enregistrement';
@override
String get stationOrderTitle => 'Ordre des stations';
@@ -493,7 +493,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get stationOrderByQuality => 'Par qualité';
@override
String get stationOrderByPopularity => 'Por popularidad';
String get stationOrderByPopularity => 'Par populari';
@override
String get stationOrderScopeDescription =>
@@ -582,7 +582,7 @@ class AppLocalizationsFr extends AppLocalizations {
}
@override
String get favoritesFilterAllLabel => 'Todas';
String get favoritesFilterAllLabel => 'Toutes';
@override
String favoriteGroupsChipLabel(Object groupName, int count) {
@@ -590,10 +590,10 @@ class AppLocalizationsFr extends AppLocalizations {
}
@override
String get favoriteGroupsManage => 'Gestionar listas';
String get favoriteGroupsManage => 'Gérer les listes';
@override
String get customStationsAddCta => 'Añadir emisora personalizada';
String get customStationsAddCta => 'Ajouter une station personnalisée';
@override
String get alarmPostponedCurrentExecution =>
@@ -640,8 +640,8 @@ class AppLocalizationsFr extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count resultados',
one: '1 resultado',
other: '$count résultats',
one: '1 résultat',
);
return '$_temp0';
}
@@ -651,23 +651,23 @@ class AppLocalizationsFr extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Quitar los $count filtros',
one: 'Quitar el filtro',
other: 'Retirer les $count filtres',
one: 'Retirer le filtre',
);
return '$_temp0';
}
@override
String get countriesScreenTitle => 'Países';
String get countriesScreenTitle => 'Pays';
@override
String get countriesYourLanguagesTitle => 'Tus idiomas';
String get countriesYourLanguagesTitle => 'Vos langues';
@override
String get countriesAllTitle => 'Todos los países';
String get countriesAllTitle => 'Tous les pays';
@override
String get radioCountriesError => 'No pudimos cargar los países.';
String get radioCountriesError => 'Impossible de charger les pays.';
@override
String get countrySpain => 'Espagne';
@@ -742,20 +742,20 @@ class AppLocalizationsFr extends AppLocalizations {
String get qualityHd => 'Qualité HD';
@override
String get yourStationsTitle => 'Tus emisoras';
String get yourStationsTitle => 'Vos stations';
@override
String get seeAllAction => 'Ver todas';
String get seeAllAction => 'Tout voir';
@override
String get openFullPlayerTooltip => 'Abrir reproductor completo';
String get openFullPlayerTooltip => 'Ouvrir le lecteur complet';
@override
String get nothingPlayingTitle => 'Todavía no estás escuchando nada';
String get nothingPlayingTitle => 'Rien n\'est en cours de lecture';
@override
String get nothingPlayingSubtitle =>
'Elegí una emisora de Tus emisoras o buscá una para empezar.';
'Choisissez une station dans Vos stations ou recherchez-en une pour commencer.';
@override
String get nearYou => 'Près de vous';
@@ -888,13 +888,13 @@ class AppLocalizationsFr extends AppLocalizations {
String get deleteTooltip => 'Supprimer';
@override
String get alarmHeroSkipAction => 'Saltar';
String get alarmHeroSkipAction => 'Ignorer';
@override
String get alarmDeleteConfirmTitle => '¿Eliminar alarma?';
String get alarmDeleteConfirmTitle => 'Supprimer l\'alarme ?';
@override
String get alarmDeleteConfirmMessage => 'Esta acción no se puede deshacer.';
String get alarmDeleteConfirmMessage => 'Cette action est irréversible.';
@override
String get alarmSkippedNoNextSnackbar =>
@@ -1054,41 +1054,41 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String vacationRangesCount(int count) {
return '$count rangos';
return '$count périodes';
}
@override
String vacationSummaryActiveCountdown(int days) {
return 'En curso · quedan $days días';
return 'En cours · encore $days jours';
}
@override
String vacationSummaryUpcomingCountdown(int days) {
return 'Próximo rango en $days días';
return 'Prochaine période dans $days jours';
}
@override
String vacationImpactPausedLabel(Object times) {
return 'Pausa: $times';
return 'En pause : $times';
}
@override
String vacationImpactContinuesLabel(Object times) {
return 'Sigue sonando: $times';
return 'Continue de sonner : $times';
}
@override
String get vacationUpcomingSectionTitle => 'PROGRAMADOS';
String get vacationUpcomingSectionTitle => 'PROGRAMMÉES';
@override
String get vacationPastSectionTitle => 'Rangos pasados';
String get vacationPastSectionTitle => 'Périodes passées';
@override
String get addVacationRangeCta => 'Añadir rango';
String get addVacationRangeCta => 'Ajouter une période';
@override
String get vacationNoActiveRangeHint =>
'No hay un rango de vacaciones activo ahora mismo.';
'Aucune période de vacances active pour le moment.';
@override
String get vacationsDefaultName => 'Vacances';
@@ -1383,10 +1383,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get pausePlaybackTooltip => 'Mettre en pause';
@override
String get playerQualityChangeAction => 'Cambiar';
String get playerQualityChangeAction => 'Modifier';
@override
String get playerToolEqLabel => 'EQ propio';
String get playerToolEqLabel => 'EQ propre';
@override
String qualityOriginal(Object quality) {
@@ -1476,16 +1476,16 @@ class AppLocalizationsFr extends AppLocalizations {
String get vacationIconLabel => 'Mode vacances';
@override
String get alarmAdvancedSectionTitle => 'Avanzado';
String get alarmAdvancedSectionTitle => 'Avancé';
@override
String get alarmInlineHourLabel => 'Hora';
String get alarmInlineHourLabel => 'Heure';
@override
String get alarmInlineMinuteLabel => 'Minuto';
String get alarmInlineMinuteLabel => 'Minute';
@override
String get alarmVolumeRisingStatus => 'Subiendo volumen';
String get alarmVolumeRisingStatus => 'Augmentation du volume';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
@@ -1603,4 +1603,35 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get localMusicFolderGenericName => 'Dossier sélectionné';
@override
String get welcomeHeadline => 'Votre monde, en direct';
@override
String get welcomeBody =>
'53412 stations de 238 pays. Enregistrez vos favorites, emmenez-les en voiture et réveillez-vous avec elles.';
@override
String get welcomeBullet1Title => 'Égaliseur par station';
@override
String get welcomeBullet1Subtitle =>
'Plus un Preset par périphérique de sortie';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle =>
'Vos favorites et votre musique locale en voiture';
@override
String get welcomeBullet3Title => 'Alarmes musicales';
@override
String get welcomeBullet3Subtitle =>
'Avec montée progressive du volume et mode vacances';
@override
String get welcomeCtaLabel => 'Commencer à écouter';
}
+94 -67
View File
@@ -12,7 +12,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get appTitle => 'PluriWave';
@override
String get navHome => 'मुखपृष्ठ';
String get navHome => 'सुनना';
@override
String get navSearch => 'खोज';
@@ -100,19 +100,19 @@ class AppLocalizationsHi extends AppLocalizations {
'ध्वनि, बैकअप और मनचाहे स्टेशनों पर बारीक नियंत्रण।';
@override
String get settingsGroupAudioTitle => 'AUDIO';
String get settingsGroupAudioTitle => 'ऑडियो';
@override
String get settingsGroupStationsTitle => 'EMISORAS';
String get settingsGroupStationsTitle => 'स्टेशन';
@override
String get settingsGroupRecordingsTitle => 'GRABACIONES Y MÚSICA';
String get settingsGroupRecordingsTitle => 'रिकॉर्डिंग और संगीत';
@override
String get settingsGroupApplicationTitle => 'APLICACIÓN';
String get settingsGroupApplicationTitle => 'ऐप्लिकेशन';
@override
String get infoSectionTitle => 'Información';
String get infoSectionTitle => 'जानकारी';
@override
String get languageSectionTitle => 'भाषा';
@@ -227,40 +227,40 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get equalizerBaseExplainer =>
'Este es el ecualizador base: se aplica a todas las emisoras que no tengan un ajuste propio. El de una emisora concreta se define desde su propia reproducción y manda sobre este.';
'यह बेस इक्वलाइज़र है: यह उन सभी स्टेशन पर लागू होता है जिनकी अपनी सेटिंग नहीं है। किसी खास स्टेशन का अपना EQ उसकी प्लेबैक स्क्रीन से तय होता है और इस पर प्राथमिकता रखता है।';
@override
String get equalizerActiveOutputLabel => 'Salida activa';
String get equalizerActiveOutputLabel => 'सक्रिय आउटपुट';
@override
String get equalizerActiveOutputDefault => 'El altavoz de este dispositivo';
String get equalizerActiveOutputDefault => 'इस डिवाइस का स्पीकर';
@override
String get equalizerStationsWithOwnEqTitle => 'Emisoras con ajuste propio';
String get equalizerStationsWithOwnEqTitle => 'अपना EQ रखने वाले स्टेशन';
@override
String get equalizerStationsWithOwnEqSubtitle =>
'Ignoran este ecualizador base';
'यह बेस इक्वलाइज़र नज़रअंदाज़ करते हैं';
@override
String get equalizerStationsWithOwnEqEmpty =>
'Ninguna emisora tiene ajuste propio todavía.';
'अभी तक किसी स्टेशन का अपना EQ नहीं है।';
@override
String get equalizerSaveAsPresetAction => 'Guardar como preset';
String get equalizerSaveAsPresetAction => 'प्रीसेट के रूप में सहेजें';
@override
String get equalizerSavePresetDialogTitle => 'Guardar como preset';
String get equalizerSavePresetDialogTitle => 'प्रीसेट के रूप में सहेजें';
@override
String get equalizerSavePresetNameLabel => 'Nombre del preset';
String get equalizerSavePresetNameLabel => 'प्रीसेट का नाम';
@override
String get equalizerSavePresetEmptyNameError =>
'Ingresá un nombre para el preset.';
'प्रीसेट के लिए एक नाम डालें।';
@override
String get equalizerSavePresetConfirm => 'Guardar';
String get equalizerSavePresetConfirm => 'सहेजें';
@override
String get preferredStationTitle => 'पसंदीदा स्टेशन';
@@ -434,47 +434,46 @@ class AppLocalizationsHi extends AppLocalizations {
}
@override
String get recordingsLibraryTitle => 'Mis grabaciones';
String get recordingsLibraryTitle => 'मेरी रिकॉर्डिंग';
@override
String recordingsLibraryStorageCaption(int usedMb, int totalMb) {
return '$usedMb MB de $totalMb MB usados';
return '$totalMb MB में से $usedMb MB इस्तेमाल हुआ';
}
@override
String get recordingsLibraryEmptyTitle => 'Todavía no hay grabaciones';
String get recordingsLibraryEmptyTitle => 'अभी तक कोई रिकॉर्डिंग नहीं';
@override
String get recordingsLibraryEmptySubtitle =>
'Las grabaciones que guardes van a aparecer acá.';
'आप जो रिकॉर्डिंग सहेजेंगे, वे यहां दिखाई देंगी।';
@override
String get recordingActionRename => 'Renombrar';
String get recordingActionRename => 'नाम बदलें';
@override
String get recordingActionShare => 'Compartir';
String get recordingActionShare => 'शेयर करें';
@override
String get recordingActionDelete => 'Eliminar';
String get recordingActionDelete => 'हटाएं';
@override
String get recordingRenameDialogTitle => 'Renombrar grabación';
String get recordingRenameDialogTitle => 'रिकॉर्डिंग का नाम बदलें';
@override
String get recordingRenameLabel => 'Nombre';
String get recordingRenameLabel => 'नाम';
@override
String get recordingRenameEmptyError => 'Ingresá un nombre';
String get recordingRenameEmptyError => 'एक नाम डालें';
@override
String get recordingDeleteConfirmTitle => '¿Eliminar grabación?';
String get recordingDeleteConfirmTitle => 'रिकॉर्डिंग हटाएं?';
@override
String get recordingDeleteConfirmMessage =>
'Esta acción no se puede deshacer.';
String get recordingDeleteConfirmMessage => 'इसे वापस नहीं लिया जा सकता।';
@override
String get recordingsLibrarySettingsTooltip => 'Ajustes de grabación';
String get recordingsLibrarySettingsTooltip => 'रिकॉर्डिंग सेटिंग्स';
@override
String get stationOrderTitle => 'स्टेशन क्रम';
@@ -486,7 +485,7 @@ class AppLocalizationsHi extends AppLocalizations {
String get stationOrderByQuality => 'गुणवत्ता से';
@override
String get stationOrderByPopularity => 'Por popularidad';
String get stationOrderByPopularity => 'लोकप्रियता के अनुसार';
@override
String get stationOrderScopeDescription =>
@@ -575,7 +574,7 @@ class AppLocalizationsHi extends AppLocalizations {
}
@override
String get favoritesFilterAllLabel => 'Todas';
String get favoritesFilterAllLabel => 'सभी';
@override
String favoriteGroupsChipLabel(Object groupName, int count) {
@@ -583,10 +582,10 @@ class AppLocalizationsHi extends AppLocalizations {
}
@override
String get favoriteGroupsManage => 'Gestionar listas';
String get favoriteGroupsManage => 'सूचियां प्रबंधित करें';
@override
String get customStationsAddCta => 'Añadir emisora personalizada';
String get customStationsAddCta => 'मनचाहा स्टेशन जोड़ें';
@override
String get alarmPostponedCurrentExecution =>
@@ -633,8 +632,8 @@ class AppLocalizationsHi extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count resultados',
one: '1 resultado',
other: '$count परिणाम',
one: '1 परिणाम',
);
return '$_temp0';
}
@@ -644,23 +643,23 @@ class AppLocalizationsHi extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Quitar los $count filtros',
one: 'Quitar el filtro',
other: '$count फ़िल्टर हटाएं',
one: 'फ़िल्टर हटाएं',
);
return '$_temp0';
}
@override
String get countriesScreenTitle => 'Países';
String get countriesScreenTitle => 'देश';
@override
String get countriesYourLanguagesTitle => 'Tus idiomas';
String get countriesYourLanguagesTitle => 'आपकी भाषाएं';
@override
String get countriesAllTitle => 'Todos los países';
String get countriesAllTitle => 'सभी देश';
@override
String get radioCountriesError => 'No pudimos cargar los países.';
String get radioCountriesError => 'देश लोड नहीं हो सके।';
@override
String get countrySpain => 'स्पेन';
@@ -735,20 +734,20 @@ class AppLocalizationsHi extends AppLocalizations {
String get qualityHd => 'HD गुणवत्ता';
@override
String get yourStationsTitle => 'Tus emisoras';
String get yourStationsTitle => 'आपके स्टेशन';
@override
String get seeAllAction => 'Ver todas';
String get seeAllAction => 'सभी देखें';
@override
String get openFullPlayerTooltip => 'Abrir reproductor completo';
String get openFullPlayerTooltip => 'पूरा प्लेयर खोलें';
@override
String get nothingPlayingTitle => 'Todavía no estás escuchando nada';
String get nothingPlayingTitle => 'अभी तक कुछ नहीं चल रहा';
@override
String get nothingPlayingSubtitle =>
'Elegí una emisora de Tus emisoras o buscá una para empezar.';
'शुरू करने के लिए आपके स्टेशन में से कोई एक चुनें या खोजें।';
@override
String get nearYou => 'आपके पास';
@@ -880,13 +879,13 @@ class AppLocalizationsHi extends AppLocalizations {
String get deleteTooltip => 'हटाएँ';
@override
String get alarmHeroSkipAction => 'Saltar';
String get alarmHeroSkipAction => 'छोड़ें';
@override
String get alarmDeleteConfirmTitle => '¿Eliminar alarma?';
String get alarmDeleteConfirmTitle => 'अलार्म हटाएं?';
@override
String get alarmDeleteConfirmMessage => 'Esta acción no se puede deshacer.';
String get alarmDeleteConfirmMessage => 'इसे वापस नहीं लिया जा सकता।';
@override
String get alarmSkippedNoNextSnackbar =>
@@ -1045,41 +1044,40 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String vacationRangesCount(int count) {
return '$count rangos';
return '$count अवधियां';
}
@override
String vacationSummaryActiveCountdown(int days) {
return 'En curso · quedan $days días';
return 'जारी है · $days दिन बाकी';
}
@override
String vacationSummaryUpcomingCountdown(int days) {
return 'Próximo rango en $days días';
return 'अगली अवधि $days दिन में';
}
@override
String vacationImpactPausedLabel(Object times) {
return 'Pausa: $times';
return 'रोका गया: $times';
}
@override
String vacationImpactContinuesLabel(Object times) {
return 'Sigue sonando: $times';
return 'बजना जारी: $times';
}
@override
String get vacationUpcomingSectionTitle => 'PROGRAMADOS';
String get vacationUpcomingSectionTitle => 'निर्धारित';
@override
String get vacationPastSectionTitle => 'Rangos pasados';
String get vacationPastSectionTitle => 'पिछली अवधियां';
@override
String get addVacationRangeCta => 'Añadir rango';
String get addVacationRangeCta => 'अवधि जोड़ें';
@override
String get vacationNoActiveRangeHint =>
'No hay un rango de vacaciones activo ahora mismo.';
String get vacationNoActiveRangeHint => 'अभी कोई सक्रिय छुट्टी अवधि नहीं है।';
@override
String get vacationsDefaultName => 'छुट्टियाँ';
@@ -1368,10 +1366,10 @@ class AppLocalizationsHi extends AppLocalizations {
String get pausePlaybackTooltip => 'प्लेबैक रोकें';
@override
String get playerQualityChangeAction => 'Cambiar';
String get playerQualityChangeAction => 'बदलें';
@override
String get playerToolEqLabel => 'EQ propio';
String get playerToolEqLabel => 'अपना EQ';
@override
String qualityOriginal(Object quality) {
@@ -1460,16 +1458,16 @@ class AppLocalizationsHi extends AppLocalizations {
String get vacationIconLabel => 'अवकाश मोड';
@override
String get alarmAdvancedSectionTitle => 'Avanzado';
String get alarmAdvancedSectionTitle => 'उन्नत';
@override
String get alarmInlineHourLabel => 'Hora';
String get alarmInlineHourLabel => 'घंटा';
@override
String get alarmInlineMinuteLabel => 'Minuto';
String get alarmInlineMinuteLabel => 'मिनट';
@override
String get alarmVolumeRisingStatus => 'Subiendo volumen';
String get alarmVolumeRisingStatus => 'आवाज़ बढ़ाई जा रही है';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
@@ -1586,4 +1584,33 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get localMusicFolderGenericName => 'चयनित फ़ोल्डर';
@override
String get welcomeHeadline => 'आपकी दुनिया, लाइव';
@override
String get welcomeBody =>
'238 देशों के 53,412 स्टेशन। अपने पसंदीदा स्टेशन सहेजें, उन्हें गाड़ी में ले जाएं और उनके साथ जागें।';
@override
String get welcomeBullet1Title => 'प्रति स्टेशन इक्वलाइज़र';
@override
String get welcomeBullet1Subtitle => 'और हर आउटपुट डिवाइस के लिए एक प्रीसेट';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle => 'गाड़ी में आपके पसंदीदा और लोकल संगीत';
@override
String get welcomeBullet3Title => 'संगीत अलार्म';
@override
String get welcomeBullet3Subtitle =>
'धीरे-धीरे आवाज़ बढ़ाने और छुट्टी मोड के साथ';
@override
String get welcomeCtaLabel => 'सुनना शुरू करें';
}
+93 -65
View File
@@ -12,7 +12,7 @@ class AppLocalizationsId extends AppLocalizations {
String get appTitle => 'PluriWave';
@override
String get navHome => 'Beranda';
String get navHome => 'Dengar';
@override
String get navSearch => 'Cari';
@@ -104,16 +104,16 @@ class AppLocalizationsId extends AppLocalizations {
String get settingsGroupAudioTitle => 'AUDIO';
@override
String get settingsGroupStationsTitle => 'EMISORAS';
String get settingsGroupStationsTitle => 'STASIUN';
@override
String get settingsGroupRecordingsTitle => 'GRABACIONES Y MÚSICA';
String get settingsGroupRecordingsTitle => 'REKAMAN & MUSIK';
@override
String get settingsGroupApplicationTitle => 'APLICACIÓN';
String get settingsGroupApplicationTitle => 'APLIKASI';
@override
String get infoSectionTitle => 'Información';
String get infoSectionTitle => 'Info';
@override
String get languageSectionTitle => 'Bahasa';
@@ -227,40 +227,39 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get equalizerBaseExplainer =>
'Este es el ecualizador base: se aplica a todas las emisoras que no tengan un ajuste propio. El de una emisora concreta se define desde su propia reproducción y manda sobre este.';
'Ini adalah equalizer dasar: berlaku untuk semua stasiun yang belum punya pengaturan sendiri. EQ khusus suatu stasiun diatur dari layar pemutarannya sendiri dan mengesampingkan yang ini.';
@override
String get equalizerActiveOutputLabel => 'Salida activa';
String get equalizerActiveOutputLabel => 'Output aktif';
@override
String get equalizerActiveOutputDefault => 'El altavoz de este dispositivo';
String get equalizerActiveOutputDefault => 'Speaker perangkat ini';
@override
String get equalizerStationsWithOwnEqTitle => 'Emisoras con ajuste propio';
String get equalizerStationsWithOwnEqTitle => 'Stasiun dengan EQ sendiri';
@override
String get equalizerStationsWithOwnEqSubtitle =>
'Ignoran este ecualizador base';
'Mengabaikan equalizer dasar ini';
@override
String get equalizerStationsWithOwnEqEmpty =>
'Ninguna emisora tiene ajuste propio todavía.';
'Belum ada stasiun yang punya EQ sendiri.';
@override
String get equalizerSaveAsPresetAction => 'Guardar como preset';
String get equalizerSaveAsPresetAction => 'Simpan sebagai Preset';
@override
String get equalizerSavePresetDialogTitle => 'Guardar como preset';
String get equalizerSavePresetDialogTitle => 'Simpan sebagai Preset';
@override
String get equalizerSavePresetNameLabel => 'Nombre del preset';
String get equalizerSavePresetNameLabel => 'Nama Preset';
@override
String get equalizerSavePresetEmptyNameError =>
'Ingresá un nombre para el preset.';
String get equalizerSavePresetEmptyNameError => 'Masukkan nama untuk Preset.';
@override
String get equalizerSavePresetConfirm => 'Guardar';
String get equalizerSavePresetConfirm => 'Simpan';
@override
String get preferredStationTitle => 'Stasiun pilihan';
@@ -435,47 +434,47 @@ class AppLocalizationsId extends AppLocalizations {
}
@override
String get recordingsLibraryTitle => 'Mis grabaciones';
String get recordingsLibraryTitle => 'Rekaman saya';
@override
String recordingsLibraryStorageCaption(int usedMb, int totalMb) {
return '$usedMb MB de $totalMb MB usados';
return '$usedMb MB dari $totalMb MB terpakai';
}
@override
String get recordingsLibraryEmptyTitle => 'Todavía no hay grabaciones';
String get recordingsLibraryEmptyTitle => 'Belum ada rekaman';
@override
String get recordingsLibraryEmptySubtitle =>
'Las grabaciones que guardes van a aparecer acá.';
'Rekaman yang Anda simpan akan muncul di sini.';
@override
String get recordingActionRename => 'Renombrar';
String get recordingActionRename => 'Ganti nama';
@override
String get recordingActionShare => 'Compartir';
String get recordingActionShare => 'Bagikan';
@override
String get recordingActionDelete => 'Eliminar';
String get recordingActionDelete => 'Hapus';
@override
String get recordingRenameDialogTitle => 'Renombrar grabación';
String get recordingRenameDialogTitle => 'Ganti nama rekaman';
@override
String get recordingRenameLabel => 'Nombre';
String get recordingRenameLabel => 'Nama';
@override
String get recordingRenameEmptyError => 'Ingresá un nombre';
String get recordingRenameEmptyError => 'Masukkan nama';
@override
String get recordingDeleteConfirmTitle => '¿Eliminar grabación?';
String get recordingDeleteConfirmTitle => 'Hapus rekaman?';
@override
String get recordingDeleteConfirmMessage =>
'Esta acción no se puede deshacer.';
'Tindakan ini tidak dapat dibatalkan.';
@override
String get recordingsLibrarySettingsTooltip => 'Ajustes de grabación';
String get recordingsLibrarySettingsTooltip => 'Pengaturan rekaman';
@override
String get stationOrderTitle => 'Urutan stasiun';
@@ -487,7 +486,7 @@ class AppLocalizationsId extends AppLocalizations {
String get stationOrderByQuality => 'Berdasarkan kualitas';
@override
String get stationOrderByPopularity => 'Por popularidad';
String get stationOrderByPopularity => 'Berdasarkan popularitas';
@override
String get stationOrderScopeDescription =>
@@ -576,7 +575,7 @@ class AppLocalizationsId extends AppLocalizations {
}
@override
String get favoritesFilterAllLabel => 'Todas';
String get favoritesFilterAllLabel => 'Semua';
@override
String favoriteGroupsChipLabel(Object groupName, int count) {
@@ -584,10 +583,10 @@ class AppLocalizationsId extends AppLocalizations {
}
@override
String get favoriteGroupsManage => 'Gestionar listas';
String get favoriteGroupsManage => 'Kelola daftar';
@override
String get customStationsAddCta => 'Añadir emisora personalizada';
String get customStationsAddCta => 'Tambahkan stasiun khusus';
@override
String get alarmPostponedCurrentExecution =>
@@ -634,8 +633,7 @@ class AppLocalizationsId extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count resultados',
one: '1 resultado',
other: '$count hasil',
);
return '$_temp0';
}
@@ -645,23 +643,23 @@ class AppLocalizationsId extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Quitar los $count filtros',
one: 'Quitar el filtro',
other: 'Hapus $count filter',
one: 'Hapus filter',
);
return '$_temp0';
}
@override
String get countriesScreenTitle => 'Países';
String get countriesScreenTitle => 'Negara';
@override
String get countriesYourLanguagesTitle => 'Tus idiomas';
String get countriesYourLanguagesTitle => 'Bahasa Anda';
@override
String get countriesAllTitle => 'Todos los países';
String get countriesAllTitle => 'Semua negara';
@override
String get radioCountriesError => 'No pudimos cargar los países.';
String get radioCountriesError => 'Negara tidak dapat dimuat.';
@override
String get countrySpain => 'Spanyol';
@@ -736,20 +734,20 @@ class AppLocalizationsId extends AppLocalizations {
String get qualityHd => 'Kualitas HD';
@override
String get yourStationsTitle => 'Tus emisoras';
String get yourStationsTitle => 'Stasiun Anda';
@override
String get seeAllAction => 'Ver todas';
String get seeAllAction => 'Lihat semua';
@override
String get openFullPlayerTooltip => 'Abrir reproductor completo';
String get openFullPlayerTooltip => 'Buka pemutar lengkap';
@override
String get nothingPlayingTitle => 'Todavía no estás escuchando nada';
String get nothingPlayingTitle => 'Belum ada yang diputar';
@override
String get nothingPlayingSubtitle =>
'Elegí una emisora de Tus emisoras o buscá una para empezar.';
'Pilih stasiun dari Stasiun Anda atau cari salah satu untuk mulai.';
@override
String get nearYou => 'Di dekat Anda';
@@ -883,13 +881,14 @@ class AppLocalizationsId extends AppLocalizations {
String get deleteTooltip => 'Hapus';
@override
String get alarmHeroSkipAction => 'Saltar';
String get alarmHeroSkipAction => 'Lewati';
@override
String get alarmDeleteConfirmTitle => '¿Eliminar alarma?';
String get alarmDeleteConfirmTitle => 'Hapus alarm?';
@override
String get alarmDeleteConfirmMessage => 'Esta acción no se puede deshacer.';
String get alarmDeleteConfirmMessage =>
'Tindakan ini tidak dapat dibatalkan.';
@override
String get alarmSkippedNoNextSnackbar =>
@@ -1049,41 +1048,41 @@ class AppLocalizationsId extends AppLocalizations {
@override
String vacationRangesCount(int count) {
return '$count rangos';
return '$count periode';
}
@override
String vacationSummaryActiveCountdown(int days) {
return 'En curso · quedan $days días';
return 'Sedang berlangsung · $days hari lagi';
}
@override
String vacationSummaryUpcomingCountdown(int days) {
return 'Próximo rango en $days días';
return 'Periode berikutnya dalam $days hari';
}
@override
String vacationImpactPausedLabel(Object times) {
return 'Pausa: $times';
return 'Dijeda: $times';
}
@override
String vacationImpactContinuesLabel(Object times) {
return 'Sigue sonando: $times';
return 'Tetap berbunyi: $times';
}
@override
String get vacationUpcomingSectionTitle => 'PROGRAMADOS';
String get vacationUpcomingSectionTitle => 'TERJADWAL';
@override
String get vacationPastSectionTitle => 'Rangos pasados';
String get vacationPastSectionTitle => 'Periode lampau';
@override
String get addVacationRangeCta => 'Añadir rango';
String get addVacationRangeCta => 'Tambahkan periode';
@override
String get vacationNoActiveRangeHint =>
'No hay un rango de vacaciones activo ahora mismo.';
'Tidak ada periode liburan aktif saat ini.';
@override
String get vacationsDefaultName => 'Liburan';
@@ -1374,10 +1373,10 @@ class AppLocalizationsId extends AppLocalizations {
String get pausePlaybackTooltip => 'Jeda pemutaran';
@override
String get playerQualityChangeAction => 'Cambiar';
String get playerQualityChangeAction => 'Ubah';
@override
String get playerToolEqLabel => 'EQ propio';
String get playerToolEqLabel => 'EQ sendiri';
@override
String qualityOriginal(Object quality) {
@@ -1465,16 +1464,16 @@ class AppLocalizationsId extends AppLocalizations {
String get vacationIconLabel => 'Mode liburan';
@override
String get alarmAdvancedSectionTitle => 'Avanzado';
String get alarmAdvancedSectionTitle => 'Lanjutan';
@override
String get alarmInlineHourLabel => 'Hora';
String get alarmInlineHourLabel => 'Jam';
@override
String get alarmInlineMinuteLabel => 'Minuto';
String get alarmInlineMinuteLabel => 'Menit';
@override
String get alarmVolumeRisingStatus => 'Subiendo volumen';
String get alarmVolumeRisingStatus => 'Menaikkan volume';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
@@ -1592,4 +1591,33 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get localMusicFolderGenericName => 'Folder terpilih';
@override
String get welcomeHeadline => 'Duniamu, secara langsung';
@override
String get welcomeBody =>
'53.412 stasiun dari 238 negara. Simpan favorit Anda, bawa ke mobil, dan bangun bersamanya.';
@override
String get welcomeBullet1Title => 'Equalizer per stasiun';
@override
String get welcomeBullet1Subtitle => 'Plus satu Preset per perangkat output';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle => 'Favorit dan musik lokal Anda di mobil';
@override
String get welcomeBullet3Title => 'Alarm musik';
@override
String get welcomeBullet3Subtitle =>
'Dengan kenaikan volume bertahap dan mode liburan';
@override
String get welcomeCtaLabel => 'Mulai mendengarkan';
}
+97 -63
View File
@@ -12,7 +12,7 @@ class AppLocalizationsIt extends AppLocalizations {
String get appTitle => 'PluriWave';
@override
String get navHome => 'Inizio';
String get navHome => 'Ascolta';
@override
String get navSearch => 'Cerca';
@@ -103,16 +103,16 @@ class AppLocalizationsIt extends AppLocalizations {
String get settingsGroupAudioTitle => 'AUDIO';
@override
String get settingsGroupStationsTitle => 'EMISORAS';
String get settingsGroupStationsTitle => 'EMITTENTI';
@override
String get settingsGroupRecordingsTitle => 'GRABACIONES Y MÚSICA';
String get settingsGroupRecordingsTitle => 'REGISTRAZIONI E MUSICA';
@override
String get settingsGroupApplicationTitle => 'APLICACIÓN';
String get settingsGroupApplicationTitle => 'APPLICAZIONE';
@override
String get infoSectionTitle => 'Información';
String get infoSectionTitle => 'Informazioni';
@override
String get languageSectionTitle => 'Lingua';
@@ -227,40 +227,41 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get equalizerBaseExplainer =>
'Este es el ecualizador base: se aplica a todas las emisoras que no tengan un ajuste propio. El de una emisora concreta se define desde su propia reproducción y manda sobre este.';
'Questo è l\'equalizzatore di base: si applica a tutte le emittenti senza un\'impostazione propria. L\'EQ di un\'emittente specifica si imposta dalla sua schermata di riproduzione e ha la precedenza su questo.';
@override
String get equalizerActiveOutputLabel => 'Salida activa';
String get equalizerActiveOutputLabel => 'Uscita attiva';
@override
String get equalizerActiveOutputDefault => 'El altavoz de este dispositivo';
String get equalizerActiveOutputDefault =>
'L\'altoparlante di questo dispositivo';
@override
String get equalizerStationsWithOwnEqTitle => 'Emisoras con ajuste propio';
String get equalizerStationsWithOwnEqTitle => 'Emittenti con EQ proprio';
@override
String get equalizerStationsWithOwnEqSubtitle =>
'Ignoran este ecualizador base';
'Ignorano questo equalizzatore di base';
@override
String get equalizerStationsWithOwnEqEmpty =>
'Ninguna emisora tiene ajuste propio todavía.';
'Nessuna emittente ha ancora un EQ proprio.';
@override
String get equalizerSaveAsPresetAction => 'Guardar como preset';
String get equalizerSaveAsPresetAction => 'Salva come Preset';
@override
String get equalizerSavePresetDialogTitle => 'Guardar como preset';
String get equalizerSavePresetDialogTitle => 'Salva come Preset';
@override
String get equalizerSavePresetNameLabel => 'Nombre del preset';
String get equalizerSavePresetNameLabel => 'Nome del Preset';
@override
String get equalizerSavePresetEmptyNameError =>
'Ingresá un nombre para el preset.';
'Inserisci un nome per il Preset.';
@override
String get equalizerSavePresetConfirm => 'Guardar';
String get equalizerSavePresetConfirm => 'Salva';
@override
String get preferredStationTitle => 'Emittente preferita';
@@ -437,47 +438,48 @@ class AppLocalizationsIt extends AppLocalizations {
}
@override
String get recordingsLibraryTitle => 'Mis grabaciones';
String get recordingsLibraryTitle => 'Le mie registrazioni';
@override
String recordingsLibraryStorageCaption(int usedMb, int totalMb) {
return '$usedMb MB de $totalMb MB usados';
return '$usedMb MB di $totalMb MB usati';
}
@override
String get recordingsLibraryEmptyTitle => 'Todavía no hay grabaciones';
String get recordingsLibraryEmptyTitle => 'Ancora nessuna registrazione';
@override
String get recordingsLibraryEmptySubtitle =>
'Las grabaciones que guardes van a aparecer acá.';
'Le registrazioni che salvi appariranno qui.';
@override
String get recordingActionRename => 'Renombrar';
String get recordingActionRename => 'Rinomina';
@override
String get recordingActionShare => 'Compartir';
String get recordingActionShare => 'Condividi';
@override
String get recordingActionDelete => 'Eliminar';
String get recordingActionDelete => 'Elimina';
@override
String get recordingRenameDialogTitle => 'Renombrar grabación';
String get recordingRenameDialogTitle => 'Rinomina registrazione';
@override
String get recordingRenameLabel => 'Nombre';
String get recordingRenameLabel => 'Nome';
@override
String get recordingRenameEmptyError => 'Ingresá un nombre';
String get recordingRenameEmptyError => 'Inserisci un nome';
@override
String get recordingDeleteConfirmTitle => '¿Eliminar grabación?';
String get recordingDeleteConfirmTitle => 'Eliminare la registrazione?';
@override
String get recordingDeleteConfirmMessage =>
'Esta acción no se puede deshacer.';
'Questa azione non può essere annullata.';
@override
String get recordingsLibrarySettingsTooltip => 'Ajustes de grabación';
String get recordingsLibrarySettingsTooltip =>
'Impostazioni di registrazione';
@override
String get stationOrderTitle => 'Ordine emittenti';
@@ -489,7 +491,7 @@ class AppLocalizationsIt extends AppLocalizations {
String get stationOrderByQuality => 'Per qualità';
@override
String get stationOrderByPopularity => 'Por popularidad';
String get stationOrderByPopularity => 'Per popolari';
@override
String get stationOrderScopeDescription =>
@@ -578,7 +580,7 @@ class AppLocalizationsIt extends AppLocalizations {
}
@override
String get favoritesFilterAllLabel => 'Todas';
String get favoritesFilterAllLabel => 'Tutte';
@override
String favoriteGroupsChipLabel(Object groupName, int count) {
@@ -586,10 +588,10 @@ class AppLocalizationsIt extends AppLocalizations {
}
@override
String get favoriteGroupsManage => 'Gestionar listas';
String get favoriteGroupsManage => 'Gestisci liste';
@override
String get customStationsAddCta => 'Añadir emisora personalizada';
String get customStationsAddCta => 'Aggiungi emittente personalizzata';
@override
String get alarmPostponedCurrentExecution =>
@@ -636,8 +638,8 @@ class AppLocalizationsIt extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count resultados',
one: '1 resultado',
other: '$count risultati',
one: '1 risultato',
);
return '$_temp0';
}
@@ -647,23 +649,23 @@ class AppLocalizationsIt extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Quitar los $count filtros',
one: 'Quitar el filtro',
other: 'Rimuovi $count filtri',
one: 'Rimuovi filtro',
);
return '$_temp0';
}
@override
String get countriesScreenTitle => 'Países';
String get countriesScreenTitle => 'Paesi';
@override
String get countriesYourLanguagesTitle => 'Tus idiomas';
String get countriesYourLanguagesTitle => 'Le tue lingue';
@override
String get countriesAllTitle => 'Todos los países';
String get countriesAllTitle => 'Tutti i paesi';
@override
String get radioCountriesError => 'No pudimos cargar los países.';
String get radioCountriesError => 'Non è stato possibile caricare i paesi.';
@override
String get countrySpain => 'Spagna';
@@ -738,20 +740,20 @@ class AppLocalizationsIt extends AppLocalizations {
String get qualityHd => 'Qualità HD';
@override
String get yourStationsTitle => 'Tus emisoras';
String get yourStationsTitle => 'Le tue emittenti';
@override
String get seeAllAction => 'Ver todas';
String get seeAllAction => 'Vedi tutte';
@override
String get openFullPlayerTooltip => 'Abrir reproductor completo';
String get openFullPlayerTooltip => 'Apri il player completo';
@override
String get nothingPlayingTitle => 'Todavía no estás escuchando nada';
String get nothingPlayingTitle => 'Non stai ancora ascoltando nulla';
@override
String get nothingPlayingSubtitle =>
'Elegí una emisora de Tus emisoras o buscá una para empezar.';
'Scegli un\'emittente da Le tue emittenti o cercane una per iniziare.';
@override
String get nearYou => 'Vicino a te';
@@ -884,13 +886,14 @@ class AppLocalizationsIt extends AppLocalizations {
String get deleteTooltip => 'Elimina';
@override
String get alarmHeroSkipAction => 'Saltar';
String get alarmHeroSkipAction => 'Salta';
@override
String get alarmDeleteConfirmTitle => '¿Eliminar alarma?';
String get alarmDeleteConfirmTitle => 'Eliminare la sveglia?';
@override
String get alarmDeleteConfirmMessage => 'Esta acción no se puede deshacer.';
String get alarmDeleteConfirmMessage =>
'Questa azione non può essere annullata.';
@override
String get alarmSkippedNoNextSnackbar =>
@@ -1050,41 +1053,41 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String vacationRangesCount(int count) {
return '$count rangos';
return '$count periodi';
}
@override
String vacationSummaryActiveCountdown(int days) {
return 'En curso · quedan $days días';
return 'In corso · mancano $days giorni';
}
@override
String vacationSummaryUpcomingCountdown(int days) {
return 'Próximo rango en $days días';
return 'Prossimo periodo tra $days giorni';
}
@override
String vacationImpactPausedLabel(Object times) {
return 'Pausa: $times';
return 'In pausa: $times';
}
@override
String vacationImpactContinuesLabel(Object times) {
return 'Sigue sonando: $times';
return 'Continua a suonare: $times';
}
@override
String get vacationUpcomingSectionTitle => 'PROGRAMADOS';
String get vacationUpcomingSectionTitle => 'PROGRAMMATI';
@override
String get vacationPastSectionTitle => 'Rangos pasados';
String get vacationPastSectionTitle => 'Periodi passati';
@override
String get addVacationRangeCta => 'Añadir rango';
String get addVacationRangeCta => 'Aggiungi periodo';
@override
String get vacationNoActiveRangeHint =>
'No hay un rango de vacaciones activo ahora mismo.';
'Nessun periodo di vacanza attivo al momento.';
@override
String get vacationsDefaultName => 'Vacanze';
@@ -1378,10 +1381,10 @@ class AppLocalizationsIt extends AppLocalizations {
String get pausePlaybackTooltip => 'Pausa riproduzione';
@override
String get playerQualityChangeAction => 'Cambiar';
String get playerQualityChangeAction => 'Cambia';
@override
String get playerToolEqLabel => 'EQ propio';
String get playerToolEqLabel => 'EQ proprio';
@override
String qualityOriginal(Object quality) {
@@ -1471,16 +1474,16 @@ class AppLocalizationsIt extends AppLocalizations {
String get vacationIconLabel => 'Modalità vacanza';
@override
String get alarmAdvancedSectionTitle => 'Avanzado';
String get alarmAdvancedSectionTitle => 'Avanzate';
@override
String get alarmInlineHourLabel => 'Hora';
String get alarmInlineHourLabel => 'Ora';
@override
String get alarmInlineMinuteLabel => 'Minuto';
@override
String get alarmVolumeRisingStatus => 'Subiendo volumen';
String get alarmVolumeRisingStatus => 'Aumento del volume';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
@@ -1598,4 +1601,35 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get localMusicFolderGenericName => 'Cartella selezionata';
@override
String get welcomeHeadline => 'Il tuo mondo, in diretta';
@override
String get welcomeBody =>
'53.412 emittenti da 238 paesi. Salva le tue preferite, portale in auto e svegliati con loro.';
@override
String get welcomeBullet1Title => 'Equalizzatore per emittente';
@override
String get welcomeBullet1Subtitle =>
'Più un Preset per ogni dispositivo di uscita';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle =>
'Le tue preferite e la tua musica locale in auto';
@override
String get welcomeBullet3Title => 'Sveglie musicali';
@override
String get welcomeBullet3Subtitle =>
'Con aumento graduale del volume e modalità vacanza';
@override
String get welcomeCtaLabel => 'Inizia ad ascoltare';
}
+91 -72
View File
@@ -12,7 +12,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get appTitle => 'PluriWave';
@override
String get navHome => 'ホーム';
String get navHome => '聴く';
@override
String get navSearch => '検索';
@@ -98,19 +98,19 @@ class AppLocalizationsJa extends AppLocalizations {
String get settingsSubtitle => '音質、バックアップ、カスタム局を細かく管理します。';
@override
String get settingsGroupAudioTitle => 'AUDIO';
String get settingsGroupAudioTitle => 'オーディオ';
@override
String get settingsGroupStationsTitle => 'EMISORAS';
String get settingsGroupStationsTitle => '';
@override
String get settingsGroupRecordingsTitle => 'GRABACIONES Y MÚSICA';
String get settingsGroupRecordingsTitle => '録音と音楽';
@override
String get settingsGroupApplicationTitle => 'APLICACIÓN';
String get settingsGroupApplicationTitle => 'アプリ';
@override
String get infoSectionTitle => 'Información';
String get infoSectionTitle => '情報';
@override
String get languageSectionTitle => '言語';
@@ -219,40 +219,37 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get equalizerBaseExplainer =>
'Este es el ecualizador base: se aplica a todas las emisoras que no tengan un ajuste propio. El de una emisora concreta se define desde su propia reproducción y manda sobre este.';
'これはベースイコライザーです。専用設定を持たないすべての局に適用されます。特定の局専用のEQはその局の再生画面から設定でき、こちらより優先されます。';
@override
String get equalizerActiveOutputLabel => 'Salida activa';
String get equalizerActiveOutputLabel => 'アクティブな出力';
@override
String get equalizerActiveOutputDefault => 'El altavoz de este dispositivo';
String get equalizerActiveOutputDefault => 'この端末のスピーカー';
@override
String get equalizerStationsWithOwnEqTitle => 'Emisoras con ajuste propio';
String get equalizerStationsWithOwnEqTitle => '専用EQを持つ局';
@override
String get equalizerStationsWithOwnEqSubtitle =>
'Ignoran este ecualizador base';
String get equalizerStationsWithOwnEqSubtitle => 'このベースイコライザーを無視します';
@override
String get equalizerStationsWithOwnEqEmpty =>
'Ninguna emisora tiene ajuste propio todavía.';
String get equalizerStationsWithOwnEqEmpty => '専用EQを持つ局はまだありません。';
@override
String get equalizerSaveAsPresetAction => 'Guardar como preset';
String get equalizerSaveAsPresetAction => 'プリセットとして保存';
@override
String get equalizerSavePresetDialogTitle => 'Guardar como preset';
String get equalizerSavePresetDialogTitle => 'プリセットとして保存';
@override
String get equalizerSavePresetNameLabel => 'Nombre del preset';
String get equalizerSavePresetNameLabel => 'プリセット名';
@override
String get equalizerSavePresetEmptyNameError =>
'Ingresá un nombre para el preset.';
String get equalizerSavePresetEmptyNameError => 'プリセットの名前を入力してください。';
@override
String get equalizerSavePresetConfirm => 'Guardar';
String get equalizerSavePresetConfirm => '保存';
@override
String get preferredStationTitle => '優先局';
@@ -424,47 +421,45 @@ class AppLocalizationsJa extends AppLocalizations {
}
@override
String get recordingsLibraryTitle => 'Mis grabaciones';
String get recordingsLibraryTitle => 'マイ録音';
@override
String recordingsLibraryStorageCaption(int usedMb, int totalMb) {
return '$usedMb MB de $totalMb MB usados';
return '$totalMb MB $usedMb MB 使用済み';
}
@override
String get recordingsLibraryEmptyTitle => 'Todavía no hay grabaciones';
String get recordingsLibraryEmptyTitle => 'まだ録音がありません';
@override
String get recordingsLibraryEmptySubtitle =>
'Las grabaciones que guardes van a aparecer acá.';
String get recordingsLibraryEmptySubtitle => '保存した録音はここに表示されます。';
@override
String get recordingActionRename => 'Renombrar';
String get recordingActionRename => '名前を変更';
@override
String get recordingActionShare => 'Compartir';
String get recordingActionShare => '共有';
@override
String get recordingActionDelete => 'Eliminar';
String get recordingActionDelete => '削除';
@override
String get recordingRenameDialogTitle => 'Renombrar grabación';
String get recordingRenameDialogTitle => '録音の名前を変更';
@override
String get recordingRenameLabel => 'Nombre';
String get recordingRenameLabel => '名前';
@override
String get recordingRenameEmptyError => 'Ingresá un nombre';
String get recordingRenameEmptyError => '名前を入力してください';
@override
String get recordingDeleteConfirmTitle => '¿Eliminar grabación?';
String get recordingDeleteConfirmTitle => '録音を削除しますか?';
@override
String get recordingDeleteConfirmMessage =>
'Esta acción no se puede deshacer.';
String get recordingDeleteConfirmMessage => 'この操作は元に戻せません。';
@override
String get recordingsLibrarySettingsTooltip => 'Ajustes de grabación';
String get recordingsLibrarySettingsTooltip => '録音設定';
@override
String get stationOrderTitle => '局の並び順';
@@ -476,7 +471,7 @@ class AppLocalizationsJa extends AppLocalizations {
String get stationOrderByQuality => '品質順';
@override
String get stationOrderByPopularity => 'Por popularidad';
String get stationOrderByPopularity => '人気順';
@override
String get stationOrderScopeDescription => 'お気に入り、検索、近くの局、クイック一覧に適用されます。';
@@ -559,7 +554,7 @@ class AppLocalizationsJa extends AppLocalizations {
}
@override
String get favoritesFilterAllLabel => 'Todas';
String get favoritesFilterAllLabel => 'すべて';
@override
String favoriteGroupsChipLabel(Object groupName, int count) {
@@ -567,10 +562,10 @@ class AppLocalizationsJa extends AppLocalizations {
}
@override
String get favoriteGroupsManage => 'Gestionar listas';
String get favoriteGroupsManage => 'リストを管理';
@override
String get customStationsAddCta => 'Añadir emisora personalizada';
String get customStationsAddCta => 'カスタム局を追加';
@override
String get alarmPostponedCurrentExecution => 'この実行のアラームを延期しました。';
@@ -613,8 +608,7 @@ class AppLocalizationsJa extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count resultados',
one: '1 resultado',
other: '$count件の結果',
);
return '$_temp0';
}
@@ -624,23 +618,23 @@ class AppLocalizationsJa extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Quitar los $count filtros',
one: 'Quitar el filtro',
other: '$count件のフィルターをクリア',
one: 'フィルターをクリア',
);
return '$_temp0';
}
@override
String get countriesScreenTitle => 'Países';
String get countriesScreenTitle => '';
@override
String get countriesYourLanguagesTitle => 'Tus idiomas';
String get countriesYourLanguagesTitle => 'あなたの言語';
@override
String get countriesAllTitle => 'Todos los países';
String get countriesAllTitle => 'すべての国';
@override
String get radioCountriesError => 'No pudimos cargar los países.';
String get radioCountriesError => '国の一覧を読み込めませんでした。';
@override
String get countrySpain => 'スペイン';
@@ -715,20 +709,19 @@ class AppLocalizationsJa extends AppLocalizations {
String get qualityHd => 'HD品質';
@override
String get yourStationsTitle => 'Tus emisoras';
String get yourStationsTitle => 'あなたの局';
@override
String get seeAllAction => 'Ver todas';
String get seeAllAction => 'すべて見る';
@override
String get openFullPlayerTooltip => 'Abrir reproductor completo';
String get openFullPlayerTooltip => 'フルプレーヤーを開く';
@override
String get nothingPlayingTitle => 'Todavía no estás escuchando nada';
String get nothingPlayingTitle => 'まだ何も再生されていません';
@override
String get nothingPlayingSubtitle =>
'Elegí una emisora de Tus emisoras o buscá una para empezar.';
String get nothingPlayingSubtitle => '「あなたの局」から局を選ぶか、検索して再生を始めましょう。';
@override
String get nearYou => '近く';
@@ -858,13 +851,13 @@ class AppLocalizationsJa extends AppLocalizations {
String get deleteTooltip => '削除';
@override
String get alarmHeroSkipAction => 'Saltar';
String get alarmHeroSkipAction => 'スキップ';
@override
String get alarmDeleteConfirmTitle => '¿Eliminar alarma?';
String get alarmDeleteConfirmTitle => 'アラームを削除しますか?';
@override
String get alarmDeleteConfirmMessage => 'Esta acción no se puede deshacer.';
String get alarmDeleteConfirmMessage => 'この操作は元に戻せません。';
@override
String get alarmSkippedNoNextSnackbar => 'アラームをスキップしました。次回実行は残っていません。';
@@ -1018,41 +1011,40 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String vacationRangesCount(int count) {
return '$count rangos';
return '$count 件の期間';
}
@override
String vacationSummaryActiveCountdown(int days) {
return 'En curso · quedan $days días';
return '進行中 · 残り$days日';
}
@override
String vacationSummaryUpcomingCountdown(int days) {
return 'Próximo rango en $days días';
return '次の期間まであと$days日';
}
@override
String vacationImpactPausedLabel(Object times) {
return 'Pausa: $times';
return '一時停止: $times';
}
@override
String vacationImpactContinuesLabel(Object times) {
return 'Sigue sonando: $times';
return '引き続き鳴動: $times';
}
@override
String get vacationUpcomingSectionTitle => 'PROGRAMADOS';
String get vacationUpcomingSectionTitle => '予定';
@override
String get vacationPastSectionTitle => 'Rangos pasados';
String get vacationPastSectionTitle => '過去の期間';
@override
String get addVacationRangeCta => 'Añadir rango';
String get addVacationRangeCta => '期間を追加';
@override
String get vacationNoActiveRangeHint =>
'No hay un rango de vacaciones activo ahora mismo.';
String get vacationNoActiveRangeHint => '現在アクティブな休暇期間はありません。';
@override
String get vacationsDefaultName => '休暇';
@@ -1330,10 +1322,10 @@ class AppLocalizationsJa extends AppLocalizations {
String get pausePlaybackTooltip => '再生を一時停止';
@override
String get playerQualityChangeAction => 'Cambiar';
String get playerQualityChangeAction => '変更';
@override
String get playerToolEqLabel => 'EQ propio';
String get playerToolEqLabel => '専用EQ';
@override
String qualityOriginal(Object quality) {
@@ -1420,16 +1412,16 @@ class AppLocalizationsJa extends AppLocalizations {
String get vacationIconLabel => '休暇モード';
@override
String get alarmAdvancedSectionTitle => 'Avanzado';
String get alarmAdvancedSectionTitle => '詳細設定';
@override
String get alarmInlineHourLabel => 'Hora';
String get alarmInlineHourLabel => '';
@override
String get alarmInlineMinuteLabel => 'Minuto';
String get alarmInlineMinuteLabel => '';
@override
String get alarmVolumeRisingStatus => 'Subiendo volumen';
String get alarmVolumeRisingStatus => '音量を上げています';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
@@ -1545,4 +1537,31 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get localMusicFolderGenericName => '選択したフォルダー';
@override
String get welcomeHeadline => 'あなたの世界を、ライブで';
@override
String get welcomeBody => '238か国、53,412局のラジオ。お気に入りを保存して車に持ち出し、それで目を覚まそう。';
@override
String get welcomeBullet1Title => '局ごとのイコライザー';
@override
String get welcomeBullet1Subtitle => 'さらに出力デバイスごとのプリセット';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle => '車の中でお気に入りとローカル音楽を';
@override
String get welcomeBullet3Title => '音楽アラーム';
@override
String get welcomeBullet3Subtitle => '音量の緩やかな上昇と休暇モード付き';
@override
String get welcomeCtaLabel => '聴き始める';
}
+87 -56
View File
@@ -12,7 +12,7 @@ class AppLocalizationsPt extends AppLocalizations {
String get appTitle => 'PluriWave';
@override
String get navHome => 'Início';
String get navHome => 'Ouvir';
@override
String get navSearch => 'Buscar';
@@ -100,19 +100,19 @@ class AppLocalizationsPt extends AppLocalizations {
'Controle fino de som, backups e estações personalizadas.';
@override
String get settingsGroupAudioTitle => 'AUDIO';
String get settingsGroupAudioTitle => 'ÁUDIO';
@override
String get settingsGroupStationsTitle => 'EMISORAS';
String get settingsGroupStationsTitle => 'ESTAÇÕES';
@override
String get settingsGroupRecordingsTitle => 'GRABACIONES Y MÚSICA';
String get settingsGroupRecordingsTitle => 'GRAVAÇÕES E MÚSICA';
@override
String get settingsGroupApplicationTitle => 'APLICACIÓN';
String get settingsGroupApplicationTitle => 'APLICATIVO';
@override
String get infoSectionTitle => 'Información';
String get infoSectionTitle => 'Informações';
@override
String get languageSectionTitle => 'Idioma';
@@ -226,40 +226,40 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get equalizerBaseExplainer =>
'Este es el ecualizador base: se aplica a todas las emisoras que no tengan un ajuste propio. El de una emisora concreta se define desde su propia reproducción y manda sobre este.';
'Este é o equalizador base: aplica-se a todas as estações sem ajuste próprio. O EQ próprio de uma estação é definido na sua própria tela de reprodução e tem prioridade sobre este.';
@override
String get equalizerActiveOutputLabel => 'Salida activa';
String get equalizerActiveOutputLabel => 'Saída ativa';
@override
String get equalizerActiveOutputDefault => 'El altavoz de este dispositivo';
String get equalizerActiveOutputDefault => 'O alto-falante deste dispositivo';
@override
String get equalizerStationsWithOwnEqTitle => 'Emisoras con ajuste propio';
String get equalizerStationsWithOwnEqTitle => 'Estações com EQ próprio';
@override
String get equalizerStationsWithOwnEqSubtitle =>
'Ignoran este ecualizador base';
'Ignoram este equalizador base';
@override
String get equalizerStationsWithOwnEqEmpty =>
'Ninguna emisora tiene ajuste propio todavía.';
'Nenhuma estação tem EQ próprio ainda.';
@override
String get equalizerSaveAsPresetAction => 'Guardar como preset';
String get equalizerSaveAsPresetAction => 'Salvar como Preset';
@override
String get equalizerSavePresetDialogTitle => 'Guardar como preset';
String get equalizerSavePresetDialogTitle => 'Salvar como Preset';
@override
String get equalizerSavePresetNameLabel => 'Nombre del preset';
String get equalizerSavePresetNameLabel => 'Nome do Preset';
@override
String get equalizerSavePresetEmptyNameError =>
'Ingresá un nombre para el preset.';
'Digite um nome para o Preset.';
@override
String get equalizerSavePresetConfirm => 'Guardar';
String get equalizerSavePresetConfirm => 'Salvar';
@override
String get preferredStationTitle => 'Estação preferida';
@@ -436,7 +436,7 @@ class AppLocalizationsPt extends AppLocalizations {
}
@override
String get recordingsLibraryTitle => 'Mis grabaciones';
String get recordingsLibraryTitle => 'Minhas gravações';
@override
String recordingsLibraryStorageCaption(int usedMb, int totalMb) {
@@ -444,39 +444,39 @@ class AppLocalizationsPt extends AppLocalizations {
}
@override
String get recordingsLibraryEmptyTitle => 'Todavía no hay grabaciones';
String get recordingsLibraryEmptyTitle => 'Ainda não há gravações';
@override
String get recordingsLibraryEmptySubtitle =>
'Las grabaciones que guardes van a aparecer a.';
'As gravações que você salvar vão aparecer aqui.';
@override
String get recordingActionRename => 'Renombrar';
String get recordingActionRename => 'Renomear';
@override
String get recordingActionShare => 'Compartir';
String get recordingActionShare => 'Compartilhar';
@override
String get recordingActionDelete => 'Eliminar';
String get recordingActionDelete => 'Excluir';
@override
String get recordingRenameDialogTitle => 'Renombrar grabación';
String get recordingRenameDialogTitle => 'Renomear gravação';
@override
String get recordingRenameLabel => 'Nombre';
String get recordingRenameLabel => 'Nome';
@override
String get recordingRenameEmptyError => 'Ingresá un nombre';
String get recordingRenameEmptyError => 'Digite um nome';
@override
String get recordingDeleteConfirmTitle => '¿Eliminar grabación?';
String get recordingDeleteConfirmTitle => 'Excluir gravação?';
@override
String get recordingDeleteConfirmMessage =>
'Esta acción no se puede deshacer.';
'Esta ação não pode ser desfeita.';
@override
String get recordingsLibrarySettingsTooltip => 'Ajustes de grabación';
String get recordingsLibrarySettingsTooltip => 'Configurações de gravação';
@override
String get stationOrderTitle => 'Ordem das estações';
@@ -488,7 +488,7 @@ class AppLocalizationsPt extends AppLocalizations {
String get stationOrderByQuality => 'Por qualidade';
@override
String get stationOrderByPopularity => 'Por popularidad';
String get stationOrderByPopularity => 'Por popularidade';
@override
String get stationOrderScopeDescription =>
@@ -585,10 +585,10 @@ class AppLocalizationsPt extends AppLocalizations {
}
@override
String get favoriteGroupsManage => 'Gestionar listas';
String get favoriteGroupsManage => 'Gerenciar listas';
@override
String get customStationsAddCta => 'Añadir emisora personalizada';
String get customStationsAddCta => 'Adicionar estação personalizada';
@override
String get alarmPostponedCurrentExecution =>
@@ -646,8 +646,8 @@ class AppLocalizationsPt extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Quitar los $count filtros',
one: 'Quitar el filtro',
other: 'Remover $count filtros',
one: 'Remover filtro',
);
return '$_temp0';
}
@@ -656,13 +656,13 @@ class AppLocalizationsPt extends AppLocalizations {
String get countriesScreenTitle => 'Países';
@override
String get countriesYourLanguagesTitle => 'Tus idiomas';
String get countriesYourLanguagesTitle => 'Seus idiomas';
@override
String get countriesAllTitle => 'Todos los países';
String get countriesAllTitle => 'Todos os países';
@override
String get radioCountriesError => 'No pudimos cargar los países.';
String get radioCountriesError => 'Não foi possível carregar os países.';
@override
String get countrySpain => 'Espanha';
@@ -737,20 +737,20 @@ class AppLocalizationsPt extends AppLocalizations {
String get qualityHd => 'Qualidade HD';
@override
String get yourStationsTitle => 'Tus emisoras';
String get yourStationsTitle => 'Suas estações';
@override
String get seeAllAction => 'Ver todas';
@override
String get openFullPlayerTooltip => 'Abrir reproductor completo';
String get openFullPlayerTooltip => 'Abrir player completo';
@override
String get nothingPlayingTitle => 'Todavía no estás escuchando nada';
String get nothingPlayingTitle => 'Ainda não há nada tocando';
@override
String get nothingPlayingSubtitle =>
'Elegí una emisora de Tus emisoras o buscá una para empezar.';
'Escolha uma estação em Suas estações ou pesquise uma para começar.';
@override
String get nearYou => 'Perto de você';
@@ -883,13 +883,13 @@ class AppLocalizationsPt extends AppLocalizations {
String get deleteTooltip => 'Excluir';
@override
String get alarmHeroSkipAction => 'Saltar';
String get alarmHeroSkipAction => 'Pular';
@override
String get alarmDeleteConfirmTitle => '¿Eliminar alarma?';
String get alarmDeleteConfirmTitle => 'Excluir alarme?';
@override
String get alarmDeleteConfirmMessage => 'Esta acción no se puede deshacer.';
String get alarmDeleteConfirmMessage => 'Esta ação não pode ser desfeita.';
@override
String get alarmSkippedNoNextSnackbar =>
@@ -1047,41 +1047,41 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String vacationRangesCount(int count) {
return '$count rangos';
return '$count períodos';
}
@override
String vacationSummaryActiveCountdown(int days) {
return 'En curso · quedan $days días';
return 'Em andamento · faltam $days dias';
}
@override
String vacationSummaryUpcomingCountdown(int days) {
return 'Próximo rango en $days días';
return 'Próximo período em $days dias';
}
@override
String vacationImpactPausedLabel(Object times) {
return 'Pausa: $times';
return 'Pausado: $times';
}
@override
String vacationImpactContinuesLabel(Object times) {
return 'Sigue sonando: $times';
return 'Continua tocando: $times';
}
@override
String get vacationUpcomingSectionTitle => 'PROGRAMADOS';
@override
String get vacationPastSectionTitle => 'Rangos pasados';
String get vacationPastSectionTitle => 'Períodos passados';
@override
String get addVacationRangeCta => 'Añadir rango';
String get addVacationRangeCta => 'Adicionar período';
@override
String get vacationNoActiveRangeHint =>
'No hay un rango de vacaciones activo ahora mismo.';
'Não há período de férias ativo no momento.';
@override
String get vacationsDefaultName => 'Férias';
@@ -1370,10 +1370,10 @@ class AppLocalizationsPt extends AppLocalizations {
String get pausePlaybackTooltip => 'Pausar reprodução';
@override
String get playerQualityChangeAction => 'Cambiar';
String get playerQualityChangeAction => 'Alterar';
@override
String get playerToolEqLabel => 'EQ propio';
String get playerToolEqLabel => 'EQ próprio';
@override
String qualityOriginal(Object quality) {
@@ -1463,7 +1463,7 @@ class AppLocalizationsPt extends AppLocalizations {
String get vacationIconLabel => 'Modo férias';
@override
String get alarmAdvancedSectionTitle => 'Avanzado';
String get alarmAdvancedSectionTitle => 'Avançado';
@override
String get alarmInlineHourLabel => 'Hora';
@@ -1472,7 +1472,7 @@ class AppLocalizationsPt extends AppLocalizations {
String get alarmInlineMinuteLabel => 'Minuto';
@override
String get alarmVolumeRisingStatus => 'Subiendo volumen';
String get alarmVolumeRisingStatus => 'Aumentando o volume';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
@@ -1590,4 +1590,35 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get localMusicFolderGenericName => 'Pasta selecionada';
@override
String get welcomeHeadline => 'Seu mundo, ao vivo';
@override
String get welcomeBody =>
'53.412 estações de 238 países. Salve suas favoritas, leve-as para o carro e acorde com elas.';
@override
String get welcomeBullet1Title => 'Equalizador por estação';
@override
String get welcomeBullet1Subtitle =>
'Além de um Preset por dispositivo de saída';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle =>
'Suas favoritas e sua música local no carro';
@override
String get welcomeBullet3Title => 'Alarmes musicais';
@override
String get welcomeBullet3Subtitle =>
'Com aumento progressivo do volume e modo férias';
@override
String get welcomeCtaLabel => 'Começar a ouvir';
}
+98 -67
View File
@@ -12,7 +12,7 @@ class AppLocalizationsRu extends AppLocalizations {
String get appTitle => 'PluriWave';
@override
String get navHome => 'Главная';
String get navHome => 'Слушать';
@override
String get navSearch => 'Поиск';
@@ -100,19 +100,19 @@ class AppLocalizationsRu extends AppLocalizations {
'Точная настройка звука, резервных копий и пользовательских станций.';
@override
String get settingsGroupAudioTitle => 'AUDIO';
String get settingsGroupAudioTitle => 'АУДИО';
@override
String get settingsGroupStationsTitle => 'EMISORAS';
String get settingsGroupStationsTitle => 'СТАНЦИИ';
@override
String get settingsGroupRecordingsTitle => 'GRABACIONES Y MÚSICA';
String get settingsGroupRecordingsTitle => 'ЗАПИСИ И МУЗЫКА';
@override
String get settingsGroupApplicationTitle => 'APLICACIÓN';
String get settingsGroupApplicationTitle => 'ПРИЛОЖЕНИЕ';
@override
String get infoSectionTitle => 'Información';
String get infoSectionTitle => 'Информация';
@override
String get languageSectionTitle => 'Язык';
@@ -228,40 +228,39 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get equalizerBaseExplainer =>
'Este es el ecualizador base: se aplica a todas las emisoras que no tengan un ajuste propio. El de una emisora concreta se define desde su propia reproducción y manda sobre este.';
'Это базовый эквалайзер: он применяется ко всем станциям без собственной настройки. Свой EQ конкретной станции задаётся на экране её воспроизведения и имеет приоритет над этим.';
@override
String get equalizerActiveOutputLabel => 'Salida activa';
String get equalizerActiveOutputLabel => 'Активный выход';
@override
String get equalizerActiveOutputDefault => 'El altavoz de este dispositivo';
String get equalizerActiveOutputDefault => 'Динамик этого устройства';
@override
String get equalizerStationsWithOwnEqTitle => 'Emisoras con ajuste propio';
String get equalizerStationsWithOwnEqTitle => 'Станции со своим EQ';
@override
String get equalizerStationsWithOwnEqSubtitle =>
'Ignoran este ecualizador base';
'Игнорируют этот базовый эквалайзер';
@override
String get equalizerStationsWithOwnEqEmpty =>
'Ninguna emisora tiene ajuste propio todavía.';
'Пока ни у одной станции нет своего EQ.';
@override
String get equalizerSaveAsPresetAction => 'Guardar como preset';
String get equalizerSaveAsPresetAction => 'Сохранить как пресет';
@override
String get equalizerSavePresetDialogTitle => 'Guardar como preset';
String get equalizerSavePresetDialogTitle => 'Сохранить как пресет';
@override
String get equalizerSavePresetNameLabel => 'Nombre del preset';
String get equalizerSavePresetNameLabel => 'Название пресета';
@override
String get equalizerSavePresetEmptyNameError =>
'Ingresá un nombre para el preset.';
String get equalizerSavePresetEmptyNameError => 'Введите название пресета.';
@override
String get equalizerSavePresetConfirm => 'Guardar';
String get equalizerSavePresetConfirm => 'Сохранить';
@override
String get preferredStationTitle => 'Предпочитаемая станция';
@@ -437,47 +436,46 @@ class AppLocalizationsRu extends AppLocalizations {
}
@override
String get recordingsLibraryTitle => 'Mis grabaciones';
String get recordingsLibraryTitle => 'Мои записи';
@override
String recordingsLibraryStorageCaption(int usedMb, int totalMb) {
return '$usedMb MB de $totalMb MB usados';
return 'Использовано $usedMb МБ из $totalMb МБ';
}
@override
String get recordingsLibraryEmptyTitle => 'Todavía no hay grabaciones';
String get recordingsLibraryEmptyTitle => 'Записей пока нет';
@override
String get recordingsLibraryEmptySubtitle =>
'Las grabaciones que guardes van a aparecer acá.';
'Записи, которые вы сохраните, появятся здесь.';
@override
String get recordingActionRename => 'Renombrar';
String get recordingActionRename => 'Переименовать';
@override
String get recordingActionShare => 'Compartir';
String get recordingActionShare => 'Поделиться';
@override
String get recordingActionDelete => 'Eliminar';
String get recordingActionDelete => 'Удалить';
@override
String get recordingRenameDialogTitle => 'Renombrar grabación';
String get recordingRenameDialogTitle => 'Переименовать запись';
@override
String get recordingRenameLabel => 'Nombre';
String get recordingRenameLabel => 'Название';
@override
String get recordingRenameEmptyError => 'Ingresá un nombre';
String get recordingRenameEmptyError => 'Введите название';
@override
String get recordingDeleteConfirmTitle => '¿Eliminar grabación?';
String get recordingDeleteConfirmTitle => 'Удалить запись?';
@override
String get recordingDeleteConfirmMessage =>
'Esta acción no se puede deshacer.';
String get recordingDeleteConfirmMessage => 'Это действие нельзя отменить.';
@override
String get recordingsLibrarySettingsTooltip => 'Ajustes de grabación';
String get recordingsLibrarySettingsTooltip => 'Настройки записи';
@override
String get stationOrderTitle => 'Порядок станций';
@@ -489,7 +487,7 @@ class AppLocalizationsRu extends AppLocalizations {
String get stationOrderByQuality => 'По качеству';
@override
String get stationOrderByPopularity => 'Por popularidad';
String get stationOrderByPopularity => 'По популярности';
@override
String get stationOrderScopeDescription =>
@@ -578,7 +576,7 @@ class AppLocalizationsRu extends AppLocalizations {
}
@override
String get favoritesFilterAllLabel => 'Todas';
String get favoritesFilterAllLabel => 'Все';
@override
String favoriteGroupsChipLabel(Object groupName, int count) {
@@ -586,10 +584,10 @@ class AppLocalizationsRu extends AppLocalizations {
}
@override
String get favoriteGroupsManage => 'Gestionar listas';
String get favoriteGroupsManage => 'Управление списками';
@override
String get customStationsAddCta => 'Añadir emisora personalizada';
String get customStationsAddCta => 'Добавить пользовательскую станцию';
@override
String get alarmPostponedCurrentExecution =>
@@ -636,8 +634,9 @@ class AppLocalizationsRu extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count resultados',
one: '1 resultado',
other: '$count результатов',
few: '$count результата',
one: '$count результат',
);
return '$_temp0';
}
@@ -647,23 +646,24 @@ class AppLocalizationsRu extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Quitar los $count filtros',
one: 'Quitar el filtro',
other: 'Убрать $count фильтров',
few: 'Убрать $count фильтра',
one: 'Убрать фильтр',
);
return '$_temp0';
}
@override
String get countriesScreenTitle => 'Países';
String get countriesScreenTitle => 'Страны';
@override
String get countriesYourLanguagesTitle => 'Tus idiomas';
String get countriesYourLanguagesTitle => 'Ваши языки';
@override
String get countriesAllTitle => 'Todos los países';
String get countriesAllTitle => 'Все страны';
@override
String get radioCountriesError => 'No pudimos cargar los países.';
String get radioCountriesError => 'Не удалось загрузить страны.';
@override
String get countrySpain => 'Испания';
@@ -738,20 +738,20 @@ class AppLocalizationsRu extends AppLocalizations {
String get qualityHd => 'HD-качество';
@override
String get yourStationsTitle => 'Tus emisoras';
String get yourStationsTitle => 'Ваши станции';
@override
String get seeAllAction => 'Ver todas';
String get seeAllAction => 'Смотреть все';
@override
String get openFullPlayerTooltip => 'Abrir reproductor completo';
String get openFullPlayerTooltip => 'Открыть полный плеер';
@override
String get nothingPlayingTitle => 'Todavía no estás escuchando nada';
String get nothingPlayingTitle => 'Пока ничего не воспроизводится';
@override
String get nothingPlayingSubtitle =>
'Elegí una emisora de Tus emisoras o buscá una para empezar.';
'Выберите станцию из раздела «Ваши станции» или найдите её, чтобы начать.';
@override
String get nearYou => 'Рядом с вами';
@@ -884,13 +884,13 @@ class AppLocalizationsRu extends AppLocalizations {
String get deleteTooltip => 'Удалить';
@override
String get alarmHeroSkipAction => 'Saltar';
String get alarmHeroSkipAction => 'Пропустить';
@override
String get alarmDeleteConfirmTitle => '¿Eliminar alarma?';
String get alarmDeleteConfirmTitle => 'Удалить будильник?';
@override
String get alarmDeleteConfirmMessage => 'Esta acción no se puede deshacer.';
String get alarmDeleteConfirmMessage => 'Это действие нельзя отменить.';
@override
String get alarmSkippedNoNextSnackbar =>
@@ -1049,41 +1049,41 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String vacationRangesCount(int count) {
return '$count rangos';
return '$count периодов';
}
@override
String vacationSummaryActiveCountdown(int days) {
return 'En curso · quedan $days días';
return 'Идёт сейчас · осталось $days дн.';
}
@override
String vacationSummaryUpcomingCountdown(int days) {
return 'Próximo rango en $days días';
return 'Следующий период через $days дн.';
}
@override
String vacationImpactPausedLabel(Object times) {
return 'Pausa: $times';
return 'Пауза: $times';
}
@override
String vacationImpactContinuesLabel(Object times) {
return 'Sigue sonando: $times';
return 'Продолжает звонить: $times';
}
@override
String get vacationUpcomingSectionTitle => 'PROGRAMADOS';
String get vacationUpcomingSectionTitle => 'ЗАПЛАНИРОВАНО';
@override
String get vacationPastSectionTitle => 'Rangos pasados';
String get vacationPastSectionTitle => 'Прошедшие периоды';
@override
String get addVacationRangeCta => 'Añadir rango';
String get addVacationRangeCta => 'Добавить период';
@override
String get vacationNoActiveRangeHint =>
'No hay un rango de vacaciones activo ahora mismo.';
'Сейчас нет активного периода отпуска.';
@override
String get vacationsDefaultName => 'Отпуск';
@@ -1374,10 +1374,10 @@ class AppLocalizationsRu extends AppLocalizations {
String get pausePlaybackTooltip => 'Пауза';
@override
String get playerQualityChangeAction => 'Cambiar';
String get playerQualityChangeAction => 'Изменить';
@override
String get playerToolEqLabel => 'EQ propio';
String get playerToolEqLabel => 'Свой EQ';
@override
String qualityOriginal(Object quality) {
@@ -1467,16 +1467,16 @@ class AppLocalizationsRu extends AppLocalizations {
String get vacationIconLabel => 'Режим отпуска';
@override
String get alarmAdvancedSectionTitle => 'Avanzado';
String get alarmAdvancedSectionTitle => 'Дополнительно';
@override
String get alarmInlineHourLabel => 'Hora';
String get alarmInlineHourLabel => 'Час';
@override
String get alarmInlineMinuteLabel => 'Minuto';
String get alarmInlineMinuteLabel => 'Минута';
@override
String get alarmVolumeRisingStatus => 'Subiendo volumen';
String get alarmVolumeRisingStatus => 'Громкость нарастает';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
@@ -1594,4 +1594,35 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get localMusicFolderGenericName => 'Выбранная папка';
@override
String get welcomeHeadline => 'Ваш мир, в прямом эфире';
@override
String get welcomeBody =>
'53 412 станций из 238 стран. Сохраняйте любимые, берите их с собой в машину и просыпайтесь под них.';
@override
String get welcomeBullet1Title => 'Эквалайзер для каждой станции';
@override
String get welcomeBullet1Subtitle =>
'А также пресет для каждого устройства вывода';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle =>
'Ваши избранные и локальная музыка в машине';
@override
String get welcomeBullet3Title => 'Музыкальные будильники';
@override
String get welcomeBullet3Subtitle =>
'С плавным нарастанием громкости и режимом отпуска';
@override
String get welcomeCtaLabel => 'Начать слушать';
}
+91 -72
View File
@@ -12,7 +12,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get appTitle => 'PluriWave';
@override
String get navHome => '首页';
String get navHome => '收听';
@override
String get navSearch => '搜索';
@@ -98,19 +98,19 @@ class AppLocalizationsZh extends AppLocalizations {
String get settingsSubtitle => '精细控制声音、备份和自定义电台。';
@override
String get settingsGroupAudioTitle => 'AUDIO';
String get settingsGroupAudioTitle => '音频';
@override
String get settingsGroupStationsTitle => 'EMISORAS';
String get settingsGroupStationsTitle => '电台';
@override
String get settingsGroupRecordingsTitle => 'GRABACIONES Y MÚSICA';
String get settingsGroupRecordingsTitle => '录音与音乐';
@override
String get settingsGroupApplicationTitle => 'APLICACIÓN';
String get settingsGroupApplicationTitle => '应用';
@override
String get infoSectionTitle => 'Información';
String get infoSectionTitle => '信息';
@override
String get languageSectionTitle => '语言';
@@ -218,40 +218,37 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get equalizerBaseExplainer =>
'Este es el ecualizador base: se aplica a todas las emisoras que no tengan un ajuste propio. El de una emisora concreta se define desde su propia reproducción y manda sobre este.';
'这是基础均衡器:适用于所有没有专属设置的电台。某个电台的专属EQ可在其自己的播放界面中设置,并优先于此设置生效。';
@override
String get equalizerActiveOutputLabel => 'Salida activa';
String get equalizerActiveOutputLabel => '当前输出';
@override
String get equalizerActiveOutputDefault => 'El altavoz de este dispositivo';
String get equalizerActiveOutputDefault => '此设备的扬声器';
@override
String get equalizerStationsWithOwnEqTitle => 'Emisoras con ajuste propio';
String get equalizerStationsWithOwnEqTitle => '拥有专属EQ的电台';
@override
String get equalizerStationsWithOwnEqSubtitle =>
'Ignoran este ecualizador base';
String get equalizerStationsWithOwnEqSubtitle => '忽略此基础均衡器';
@override
String get equalizerStationsWithOwnEqEmpty =>
'Ninguna emisora tiene ajuste propio todavía.';
String get equalizerStationsWithOwnEqEmpty => '目前还没有电台设置专属EQ。';
@override
String get equalizerSaveAsPresetAction => 'Guardar como preset';
String get equalizerSaveAsPresetAction => '保存为预设';
@override
String get equalizerSavePresetDialogTitle => 'Guardar como preset';
String get equalizerSavePresetDialogTitle => '保存为预设';
@override
String get equalizerSavePresetNameLabel => 'Nombre del preset';
String get equalizerSavePresetNameLabel => '预设名称';
@override
String get equalizerSavePresetEmptyNameError =>
'Ingresá un nombre para el preset.';
String get equalizerSavePresetEmptyNameError => '请输入预设名称。';
@override
String get equalizerSavePresetConfirm => 'Guardar';
String get equalizerSavePresetConfirm => '保存';
@override
String get preferredStationTitle => '首选电台';
@@ -422,47 +419,45 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
String get recordingsLibraryTitle => 'Mis grabaciones';
String get recordingsLibraryTitle => '我的录音';
@override
String recordingsLibraryStorageCaption(int usedMb, int totalMb) {
return '$usedMb MB de $totalMb MB usados';
return '已使用 $usedMb MB / 共 $totalMb MB';
}
@override
String get recordingsLibraryEmptyTitle => 'Todavía no hay grabaciones';
String get recordingsLibraryEmptyTitle => '暂无录音';
@override
String get recordingsLibraryEmptySubtitle =>
'Las grabaciones que guardes van a aparecer acá.';
String get recordingsLibraryEmptySubtitle => '你保存的录音会显示在这里。';
@override
String get recordingActionRename => 'Renombrar';
String get recordingActionRename => '重命名';
@override
String get recordingActionShare => 'Compartir';
String get recordingActionShare => '分享';
@override
String get recordingActionDelete => 'Eliminar';
String get recordingActionDelete => '删除';
@override
String get recordingRenameDialogTitle => 'Renombrar grabación';
String get recordingRenameDialogTitle => '重命名录音';
@override
String get recordingRenameLabel => 'Nombre';
String get recordingRenameLabel => '名称';
@override
String get recordingRenameEmptyError => 'Ingresá un nombre';
String get recordingRenameEmptyError => '请输入名称';
@override
String get recordingDeleteConfirmTitle => '¿Eliminar grabación?';
String get recordingDeleteConfirmTitle => '删除录音?';
@override
String get recordingDeleteConfirmMessage =>
'Esta acción no se puede deshacer.';
String get recordingDeleteConfirmMessage => '此操作无法撤销。';
@override
String get recordingsLibrarySettingsTooltip => 'Ajustes de grabación';
String get recordingsLibrarySettingsTooltip => '录音设置';
@override
String get stationOrderTitle => '电台排序';
@@ -474,7 +469,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get stationOrderByQuality => '按质量';
@override
String get stationOrderByPopularity => 'Por popularidad';
String get stationOrderByPopularity => '按热度';
@override
String get stationOrderScopeDescription => '适用于收藏、搜索、附近电台和快捷列表。';
@@ -557,7 +552,7 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
String get favoritesFilterAllLabel => 'Todas';
String get favoritesFilterAllLabel => '全部';
@override
String favoriteGroupsChipLabel(Object groupName, int count) {
@@ -565,10 +560,10 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
String get favoriteGroupsManage => 'Gestionar listas';
String get favoriteGroupsManage => '管理列表';
@override
String get customStationsAddCta => 'Añadir emisora personalizada';
String get customStationsAddCta => '添加自定义电台';
@override
String get alarmPostponedCurrentExecution => '本次闹钟已推迟。';
@@ -611,8 +606,7 @@ class AppLocalizationsZh extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count resultados',
one: '1 resultado',
other: '$count 个结果',
);
return '$_temp0';
}
@@ -622,23 +616,23 @@ class AppLocalizationsZh extends AppLocalizations {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'Quitar los $count filtros',
one: 'Quitar el filtro',
other: '清除 $count 个筛选条件',
one: '清除筛选条件',
);
return '$_temp0';
}
@override
String get countriesScreenTitle => 'Países';
String get countriesScreenTitle => '国家';
@override
String get countriesYourLanguagesTitle => 'Tus idiomas';
String get countriesYourLanguagesTitle => '你的语言';
@override
String get countriesAllTitle => 'Todos los países';
String get countriesAllTitle => '所有国家';
@override
String get radioCountriesError => 'No pudimos cargar los países.';
String get radioCountriesError => '无法加载国家列表。';
@override
String get countrySpain => '西班牙';
@@ -712,20 +706,19 @@ class AppLocalizationsZh extends AppLocalizations {
String get qualityHd => '高清音质';
@override
String get yourStationsTitle => 'Tus emisoras';
String get yourStationsTitle => '你的电台';
@override
String get seeAllAction => 'Ver todas';
String get seeAllAction => '查看全部';
@override
String get openFullPlayerTooltip => 'Abrir reproductor completo';
String get openFullPlayerTooltip => '打开完整播放器';
@override
String get nothingPlayingTitle => 'Todavía no estás escuchando nada';
String get nothingPlayingTitle => '目前没有正在播放的内容';
@override
String get nothingPlayingSubtitle =>
'Elegí una emisora de Tus emisoras o buscá una para empezar.';
String get nothingPlayingSubtitle => '从“你的电台”中选择一个电台,或搜索一个开始收听。';
@override
String get nearYou => '你附近';
@@ -854,13 +847,13 @@ class AppLocalizationsZh extends AppLocalizations {
String get deleteTooltip => '删除';
@override
String get alarmHeroSkipAction => 'Saltar';
String get alarmHeroSkipAction => '跳过';
@override
String get alarmDeleteConfirmTitle => '¿Eliminar alarma?';
String get alarmDeleteConfirmTitle => '删除闹钟?';
@override
String get alarmDeleteConfirmMessage => 'Esta acción no se puede deshacer.';
String get alarmDeleteConfirmMessage => '此操作无法撤销。';
@override
String get alarmSkippedNoNextSnackbar => '已跳过闹钟。没有剩余的下次执行。';
@@ -1014,41 +1007,40 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String vacationRangesCount(int count) {
return '$count rangos';
return '$count 个时间段';
}
@override
String vacationSummaryActiveCountdown(int days) {
return 'En curso · quedan $days días';
return '进行中 · 还剩 $days ';
}
@override
String vacationSummaryUpcomingCountdown(int days) {
return 'Próximo rango en $days días';
return '下一个时间段在 $days 天后';
}
@override
String vacationImpactPausedLabel(Object times) {
return 'Pausa: $times';
return '暂停:$times';
}
@override
String vacationImpactContinuesLabel(Object times) {
return 'Sigue sonando: $times';
return '仍会响铃:$times';
}
@override
String get vacationUpcomingSectionTitle => 'PROGRAMADOS';
String get vacationUpcomingSectionTitle => '已安排';
@override
String get vacationPastSectionTitle => 'Rangos pasados';
String get vacationPastSectionTitle => '过往时间段';
@override
String get addVacationRangeCta => 'Añadir rango';
String get addVacationRangeCta => '添加时间段';
@override
String get vacationNoActiveRangeHint =>
'No hay un rango de vacaciones activo ahora mismo.';
String get vacationNoActiveRangeHint => '目前没有正在生效的假期时间段。';
@override
String get vacationsDefaultName => '假期';
@@ -1325,10 +1317,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get pausePlaybackTooltip => '暂停播放';
@override
String get playerQualityChangeAction => 'Cambiar';
String get playerQualityChangeAction => '更改';
@override
String get playerToolEqLabel => 'EQ propio';
String get playerToolEqLabel => '专属EQ';
@override
String qualityOriginal(Object quality) {
@@ -1415,16 +1407,16 @@ class AppLocalizationsZh extends AppLocalizations {
String get vacationIconLabel => '假期模式';
@override
String get alarmAdvancedSectionTitle => 'Avanzado';
String get alarmAdvancedSectionTitle => '高级';
@override
String get alarmInlineHourLabel => 'Hora';
String get alarmInlineHourLabel => '';
@override
String get alarmInlineMinuteLabel => 'Minuto';
String get alarmInlineMinuteLabel => '';
@override
String get alarmVolumeRisingStatus => 'Subiendo volumen';
String get alarmVolumeRisingStatus => '音量渐强中';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
@@ -1537,4 +1529,31 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get localMusicFolderGenericName => '已选文件夹';
@override
String get welcomeHeadline => '你的世界,直播中';
@override
String get welcomeBody => '来自238个国家的53,412个电台。保存你喜爱的电台,带上车,用它们唤醒你的每一天。';
@override
String get welcomeBullet1Title => '分电台均衡器';
@override
String get welcomeBullet1Subtitle => '还支持为每个输出设备设置独立预设';
@override
String get welcomeBullet2Title => 'Android Auto';
@override
String get welcomeBullet2Subtitle => '车内畅享你的收藏电台和本地音乐';
@override
String get welcomeBullet3Title => '音乐闹钟';
@override
String get welcomeBullet3Subtitle => '支持音量渐强唤醒和假期模式';
@override
String get welcomeCtaLabel => '开始收听';
}
@@ -12,12 +12,6 @@ import '../../widgets/pluri_push_scaffold.dart';
/// verbatim from the former `_SeccionGrabaciones` in `pantalla_ajustes.dart`
/// — only the panel header's icon and title were removed (the pushed
/// screen's title now carries them); every method below is unchanged.
///
/// Known pre-existing bug, deliberately NOT fixed here (out of scope, moved
/// verbatim, tracked separately): [_editarTamanoMaximo] disposes its
/// [TextEditingController] immediately after `showModalBottomSheet` resolves,
/// racing the sheet's own close animation — the same shape of bug already
/// documented for `_editarGrupo` in `pantalla_ajustes_grupos_favoritos.dart`.
class PantallaAjustesGrabaciones extends StatelessWidget {
const PantallaAjustesGrabaciones({super.key});
@@ -92,53 +86,16 @@ class _CuerpoGrabaciones extends StatelessWidget {
Future<void> _editarTamanoMaximo(BuildContext context) async {
final estado = context.read<EstadoGrabacion>();
final l10n = AppLocalizations.of(context);
final actualMb = _bytesAMegabytes(estado.maxBytes);
final controller = TextEditingController(text: actualMb.toString());
final nuevoMb = await showModalBottomSheet<int>(
context: context,
isScrollControlled: true,
showDragHandle: true,
builder: (ctx) {
final bottom = MediaQuery.viewInsetsOf(ctx).bottom;
return Padding(
padding: EdgeInsets.fromLTRB(20, 0, 20, bottom + 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.recordingsMaxSizeDialogTitle,
style: Theme.of(ctx).textTheme.titleLarge,
),
const SizedBox(height: 16),
TextField(
controller: controller,
autofocus: true,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: l10n.recordingsMaxSizeMbLabel,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: () {
final value = int.tryParse(controller.text.trim());
if (value == null || value <= 0) return;
Navigator.of(ctx).pop(value);
},
icon: const Icon(Icons.save_rounded),
label: Text(l10n.saveQuickAccessButton),
),
],
),
);
},
builder: (ctx) => _HojaTamanoMaximo(actualMb: actualMb),
);
controller.dispose();
if (nuevoMb == null || !context.mounted) return;
final l10n = AppLocalizations.of(context);
await estado.cambiarMaxBytes(nuevoMb * 1024 * 1024);
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
@@ -215,3 +172,74 @@ class _CuerpoGrabaciones extends StatelessWidget {
);
}
}
/// The "maximum recording size" bottom sheet's own content, as a
/// `StatefulWidget` so its `TextEditingController` is owned by the SHEET's
/// `State`, not by the caller's `async` function — same bugfix shape as
/// `_HojaEditarGrupo` in `pantalla_ajustes_grupos_favoritos.dart` (a
/// controller disposed immediately after `showModalBottomSheet` resolves
/// races the sheet's own close animation, which still holds a bound
/// `TextField` for a couple more frames). Flutter only calls
/// `State.dispose()` once this widget is actually removed from the tree,
/// i.e. after the close animation finishes.
class _HojaTamanoMaximo extends StatefulWidget {
const _HojaTamanoMaximo({required this.actualMb});
final int actualMb;
@override
State<_HojaTamanoMaximo> createState() => _HojaTamanoMaximoState();
}
class _HojaTamanoMaximoState extends State<_HojaTamanoMaximo> {
late final TextEditingController _controller = TextEditingController(
text: widget.actualMb.toString(),
);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _guardar() {
final value = int.tryParse(_controller.text.trim());
if (value == null || value <= 0) return;
Navigator.of(context).pop(value);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final bottom = MediaQuery.viewInsetsOf(context).bottom;
return Padding(
padding: EdgeInsets.fromLTRB(20, 0, 20, bottom + 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.recordingsMaxSizeDialogTitle,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
TextField(
controller: _controller,
autofocus: true,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: l10n.recordingsMaxSizeMbLabel,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: _guardar,
icon: const Icon(Icons.save_rounded),
label: Text(l10n.saveQuickAccessButton),
),
],
),
);
}
}
@@ -34,54 +34,14 @@ class _CuerpoGruposFavoritos extends StatelessWidget {
BuildContext context, [
GrupoFavoritos? grupo,
]) async {
final l10n = AppLocalizations.of(context);
final controller = TextEditingController(text: grupo?.nombre ?? '');
final nombre = await showModalBottomSheet<String>(
context: context,
isScrollControlled: true,
showDragHandle: true,
builder: (ctx) {
final bottom = MediaQuery.viewInsetsOf(ctx).bottom;
return Padding(
padding: EdgeInsets.fromLTRB(20, 0, 20, bottom + 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
grupo == null
? l10n.favoriteGroupsAdd
: l10n.favoriteGroupsEdit,
style: Theme.of(ctx).textTheme.titleLarge,
),
const SizedBox(height: 16),
TextField(
controller: controller,
autofocus: true,
maxLength: 28,
decoration: InputDecoration(
labelText: l10n.favoriteGroupsNameLabel,
helperText: l10n.favoriteGroupsNameTooLong,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 12),
FilledButton.icon(
icon: const Icon(Icons.save_rounded),
label: Text(AppLocalizations.of(ctx).saveQuickAccessButton),
onPressed: () {
final value = controller.text.trim();
if (value.isEmpty || value.length > 28) return;
Navigator.pop(ctx, value);
},
),
],
),
);
},
builder: (ctx) => _HojaEditarGrupo(grupo: grupo),
);
controller.dispose();
if (nombre == null || !context.mounted) return;
final l10n = AppLocalizations.of(context);
final estado = context.read<EstadoRadio>();
if (grupo == null) {
await estado.crearGrupoFavoritos(nombre);
@@ -172,3 +132,77 @@ class _CuerpoGruposFavoritos extends StatelessWidget {
);
}
}
/// The add/rename bottom sheet's own content, as a `StatefulWidget` so its
/// `TextEditingController` is owned by the SHEET's `State`, not by the
/// caller's `async` function (bugfix: a `TextEditingController` disposed
/// immediately after `showModalBottomSheet` resolves races the sheet's own
/// close animation, which still holds a `TextField` bound to that controller
/// for a couple more frames — "A TextEditingController was used after being
/// disposed"). Flutter only calls `State.dispose()` once this widget is
/// actually removed from the tree, i.e. after the close animation finishes,
/// so there is no dispose-timing decision left for the caller to get wrong.
class _HojaEditarGrupo extends StatefulWidget {
const _HojaEditarGrupo({required this.grupo});
final GrupoFavoritos? grupo;
@override
State<_HojaEditarGrupo> createState() => _HojaEditarGrupoState();
}
class _HojaEditarGrupoState extends State<_HojaEditarGrupo> {
late final TextEditingController _controller = TextEditingController(
text: widget.grupo?.nombre ?? '',
);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _guardar() {
final value = _controller.text.trim();
if (value.isEmpty || value.length > 28) return;
Navigator.pop(context, value);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final bottom = MediaQuery.viewInsetsOf(context).bottom;
return Padding(
padding: EdgeInsets.fromLTRB(20, 0, 20, bottom + 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.grupo == null
? l10n.favoriteGroupsAdd
: l10n.favoriteGroupsEdit,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
TextField(
controller: _controller,
autofocus: true,
maxLength: 28,
decoration: InputDecoration(
labelText: l10n.favoriteGroupsNameLabel,
helperText: l10n.favoriteGroupsNameTooLong,
border: const OutlineInputBorder(),
),
),
const SizedBox(height: 12),
FilledButton.icon(
icon: const Icon(Icons.save_rounded),
label: Text(l10n.saveQuickAccessButton),
onPressed: _guardar,
),
],
),
);
}
}
+188
View File
@@ -0,0 +1,188 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../estado/estado_navegacion.dart';
import '../l10n/gen/app_localizations.dart';
import '../servicios/servicio_bienvenida.dart';
import '../tema/pluriwave_theme.dart';
/// WU17: first-run welcome surface (`onboarding-welcome` spec, mockup
/// screen 14) rebuilt WITHOUT its monetization content — no "PRO" pill, no
/// "14 días" trial line, no pricing card, and no secondary "free version"
/// link (binding no-monetization requirement). Content only: logo,
/// headline, body copy, exactly 3 feature bullets, and the single
/// "Empezar a escuchar" CTA.
///
/// A full-screen ROUTE, not a modal `Dialog` — deliberately distinct from
/// the pre-existing `PluriOnboardingDialog` (an unrelated "what's new"
/// help-content modal already shown from `app.dart`'s launch flow). Both
/// surfaces coexist: [mostrarSiProcede] (WU17b) is what actually wires this
/// screen into the genuine first-launch flow, called from `app.dart` BEFORE
/// `PluriOnboardingDialog.mostrarSiProcede` on every cold start, so the two
/// never race — the once-ever welcome resolves first, then the recurring
/// what's-new dialog runs its own unrelated per-version check exactly as
/// before.
class PantallaBienvenida extends StatelessWidget {
const PantallaBienvenida({super.key});
static final ServicioBienvenida _servicio = ServicioBienvenida();
/// WU17b: shows this screen once, on the genuine first launch, then never
/// again. Mirrors `PluriOnboardingDialog.mostrarSiProcede`'s shape
/// (check-then-show-then-mark-seen) so both first-launch surfaces share
/// the same call convention from `app.dart`.
static Future<void> mostrarSiProcede(BuildContext context) async {
if (!await _servicio.debeMostrarBienvenida()) return;
if (!context.mounted) return;
await Navigator.of(
context,
).push(MaterialPageRoute<void>(builder: (_) => const PantallaBienvenida()));
await _servicio.marcarBienvenidaVista();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final t = context.pluriTokens;
final theme = Theme.of(context);
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(24, 48, 24, 28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Image.asset(
'assets/icons/pluriwave_app_mark.png',
width: 76,
height: 76,
errorBuilder:
(_, __, ___) => Icon(
Icons.graphic_eq_rounded,
size: 76,
color: t.electricMagenta,
),
),
const SizedBox(height: 22),
Text(
l10n.welcomeHeadline,
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.w800,
height: 1.05,
letterSpacing: -1.0,
),
),
const SizedBox(height: 12),
Text(
l10n.welcomeBody,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withValues(alpha: 0.72),
height: 1.5,
),
),
const SizedBox(height: 28),
FilaCaracteristicaBienvenida(
icon: Icons.equalizer_rounded,
iconColor: t.electricMagenta,
titulo: l10n.welcomeBullet1Title,
subtitulo: l10n.welcomeBullet1Subtitle,
),
const SizedBox(height: 16),
FilaCaracteristicaBienvenida(
icon: Icons.directions_car_rounded,
iconColor: t.liveGreen,
titulo: l10n.welcomeBullet2Title,
subtitulo: l10n.welcomeBullet2Subtitle,
),
const SizedBox(height: 16),
FilaCaracteristicaBienvenida(
icon: Icons.alarm_rounded,
iconColor: t.warmCoral,
titulo: l10n.welcomeBullet3Title,
subtitulo: l10n.welcomeBullet3Subtitle,
),
const SizedBox(height: 32),
SizedBox(
height: 58,
child: FilledButton(
onPressed: () => _empezar(context),
child: Text(
l10n.welcomeCtaLabel,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
),
),
),
),
],
),
),
),
);
}
void _empezar(BuildContext context) {
context.read<EstadoNavegacionRaiz>().irA(RaizPluriWave.escuchar);
Navigator.of(context).pop();
}
}
/// One icon-badge + title/subtitle row. Public (not `_FilaCaracteristica`)
/// so the structural "exactly 3 feature bullets" guard in
/// `pantalla_bienvenida_test.dart` can target it via `find.byType` — the
/// same reason WU4 made `FormularioEmisoraPersonalizada` public.
class FilaCaracteristicaBienvenida extends StatelessWidget {
const FilaCaracteristicaBienvenida({
super.key,
required this.icon,
required this.iconColor,
required this.titulo,
required this.subtitulo,
});
final IconData icon;
final Color iconColor;
final String titulo;
final String subtitulo;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 38,
height: 38,
decoration: BoxDecoration(
color: iconColor.withValues(alpha: 0.16),
borderRadius: BorderRadius.circular(11),
),
child: Icon(icon, size: 20, color: iconColor),
),
const SizedBox(width: 13),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
titulo,
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w800,
),
),
Text(
subtitulo,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withValues(alpha: 0.58),
),
),
],
),
),
],
);
}
}
+32
View File
@@ -0,0 +1,32 @@
import 'package:shared_preferences/shared_preferences.dart';
/// WU17b: persists whether the first-run welcome screen (`PantallaBienvenida`,
/// `onboarding-welcome` spec) has already been shown, so it renders once
/// rather than on every launch.
///
/// Same injectable-`SharedPreferences`, versioned-key convention as
/// `ServicioContenidoApp` (S3-R4) — but a plain one-time boolean flag, since
/// this welcome screen is a single first-impression surface, not something
/// that re-triggers per app version the way the "what's new" onboarding
/// dialog does.
class ServicioBienvenida {
ServicioBienvenida({SharedPreferences? prefs}) : _prefs = prefs;
static const _keyBienvenidaVista = 'pluri_bienvenida_vista_v1';
final SharedPreferences? _prefs;
/// Injected startup instance (S3-R4); getInstance() is only a fallback.
Future<SharedPreferences> _resolverPrefs() async =>
_prefs ?? SharedPreferences.getInstance();
Future<bool> debeMostrarBienvenida() async {
final prefs = await _resolverPrefs();
return !(prefs.getBool(_keyBienvenidaVista) ?? false);
}
Future<void> marcarBienvenidaVista() async {
final prefs = await _resolverPrefs();
await prefs.setBool(_keyBienvenidaVista, true);
}
}
+25 -6
View File
@@ -129,20 +129,31 @@ class _MiniReproductorState extends State<MiniReproductor> {
EstadoReproduccion.detenido;
final activo =
s == EstadoReproduccion.reproduciendo;
// WU16: reconectando/error are
// "connectivity trouble" states —
// tinted with offlineAccent so they
// read as visually distinct from
// ordinary loading/paused/stopped.
final conexionEnProblema =
s ==
EstadoReproduccion.reconectando ||
s == EstadoReproduccion.error;
return Text(
_labelEstado(l10n, s),
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(
color:
activo
conexionEnProblema
? t.offlineAccent
: activo
? t.warmCoral
: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.7),
fontWeight:
activo
(activo || conexionEnProblema)
? FontWeight.w600
: FontWeight.w400,
),
@@ -173,12 +184,20 @@ class _MiniReproductorState extends State<MiniReproductor> {
// cargando (spinner), never as the error/retry affordance.
if (s == EstadoReproduccion.cargando ||
s == EstadoReproduccion.reconectando) {
return const SizedBox(
// WU16: only the reconectando sub-state gets the offline
// accent — plain cargando (e.g. the very first play)
// keeps the default spinner colour, since it is not a
// connectivity problem.
final reconectando = s == EstadoReproduccion.reconectando;
return SizedBox(
width: 48,
height: 48,
child: Padding(
padding: EdgeInsets.all(12),
child: CircularProgressIndicator(strokeWidth: 2),
padding: const EdgeInsets.all(12),
child: CircularProgressIndicator(
strokeWidth: 2,
color: reconectando ? t.offlineAccent : null,
),
),
);
}
@@ -187,7 +206,7 @@ class _MiniReproductorState extends State<MiniReproductor> {
final emisoraActual = estado.emisoraActual;
return IconButton(
tooltip: l10n.retryAction,
icon: const Icon(Icons.refresh_rounded),
icon: Icon(Icons.refresh_rounded, color: t.offlineAccent),
onPressed:
emisoraActual != null
? () => estado.reproducir(emisoraActual)
+216 -75
View File
@@ -16,6 +16,11 @@
> **WU15b was added mid-apply, not planned upfront** — WU15 shipped `PantallaGrabaciones` (the recordings library)
> fully tested but reachable from nowhere in the app. WU15b (below, after WU15's section) is the coordinator-ruled fix
> that wires it into Settings navigation. It is small and does not change the 18-commit delivery model's shape.
> **WU17b was added mid-apply, not planned upfront — same shape as WU15b.** WU17 shipped `PantallaBienvenida` (the
> welcome screen) fully tested but reachable from nowhere in the app (`rg "PantallaBienvenida" lib/app.dart lib/main.dart`
> found nothing). WU17b (below, after WU17's section) wires it into the genuine first-launch flow and establishes how
> it coexists with the pre-existing, unrelated `PluriOnboardingDialog` ("what's new" modal). It is small and does not
> change the 18-commit delivery model's shape.
> Strict TDD is ON. Runner: `flutter test`. `flutter analyze` and a **scoped** `dart format` gate every commit.
> **`flutter build` is never run.**
>
@@ -56,8 +61,8 @@
| 15 | `feat(grabaciones): add recordings library screen` | 3b | ~~300-400~~**REALIZED: 1,767** (1,767+ / 0-, 22 files) | Medium | Monitor§ |
| 15b | `fix(grabaciones): wire the recordings library into Settings navigation` | 15 | 60-100 | Low | No |
| 16 | `feat(connectivity): restyle offline and reconnect banners` | 1 | 150-250 | Low | No |
| 17 | `feat(bienvenida): add monetization-free welcome screen` | 1 | 150-200 | Low | No |
| 18 | `feat(i18n): add redesign strings and translate Escuchar rename to 11 locales` | all | ~0 eng. / 400-600 data | Medium (data volume, low logic risk) | No |
| 17 | `feat(bienvenida): add monetization-free welcome screen` | 1 | ~~150-200~~**REALIZED: 361** (screen + test, 313 / ARB 22 / docs 48) — see the WU15-precedent note below | Low | No — same reasoning as WU15§ |
| 18 | `feat(i18n): add redesign strings and translate Escuchar rename to 11 locales` | all | ~~~0 eng. / 400-600 data~~ → **REALIZED: ~3,622** (2,714+ / 908-, 26 files: 11 ARB + 11 regenerated `gen/*.dart` + 4 new `test/l10n/*.dart` + this docs update) | Medium (data volume, low logic risk) | No — same estimating-lesson pattern as WU3a/WU7/WU15§, not a design risk |
\* **Re-derived after WU3a landed.** The original 500-700 figure was scaled pro-rata from WU3a's *estimate*, which
turned out to be wrong by roughly 4x — so the old WU3b number inherited the same error and has been replaced.
@@ -85,6 +90,11 @@ unlike WU3a/WU3b's move-only reuse), or (c) the new `servicio_grabacion_radio.da
group. Realized 1,767 changed lines / 22 files, all additions (no deletions — nothing pre-existing was touched
beyond the 3 new `ServicioGrabacionRadio`/`EstadoGrabacion` methods). Not recorded as `size:exception` since the
commit is still a single, cleanly-scoped deliverable (one new screen, its one dependency, no split candidate).
**WU17 hit the identical pattern**: 150-200 covered only the screen itself; realized 361 lines across the screen
(169) + its test (144) + 9 new ARB keys × 2 locales (22) + this docs update (48), PLUS 13 regenerated
`lib/l10n/gen/*.dart` files (~430 more lines, untouched by hand) for a raw `git diff --stat` total of 814. Same
ruling: not `size:exception` — one new, cleanly-scoped screen with no split candidate, all-additions except the ARB
files (which gain lines, not churn).
```text
Decision needed before apply: No
@@ -119,15 +129,19 @@ the proposal's narrative and dependency table.
## Non-Negotiable Guards (master checklist — verify before the branch is considered done)
- [ ] `test/pantallas/pantalla_alarma_sonando_dismiss_guard_test.dart` passes **unmodified** (touched by WU9, WU11 — if
a restyle appears to require editing it, STOP and escalate; do not edit the test).
- [ ] `git diff` is **empty** for `lib/servicios/navegacion_auto.dart` (WU2), `lib/servicios/servicio_ecualizador.dart`
(WU13), and the band-application block at `lib/servicios/servicio_audio.dart:749-762` (WU13).
- [ ] The equalizer renders exactly **5** sliders (WU13, task 13.1).
- [ ] No shipped string contains "PRO", a trial duration, or a price (WU17 task 17.2/17.6; WU18 task 18.6 final
whole-app grep).
- [ ] All 13 ARB files keep identical key sets (WU18 tasks 18.1, 18.5 — final parity check; WU1 only touches values
in 2 of the 13 files, so it cannot break parity on its own).
- [x] `test/pantallas/pantalla_alarma_sonando_dismiss_guard_test.dart` passes **unmodified** (touched by WU9, WU11 — if
a restyle appears to require editing it, STOP and escalate; do not edit the test). Re-confirmed in isolation
after WU18: 13/13 green, file diff empty vs `main`.
- [x] `git diff` is **empty** for `lib/servicios/navegacion_auto.dart` (WU2), `lib/servicios/servicio_ecualizador.dart`
(WU13), and the band-application block at `lib/servicios/servicio_audio.dart:749-762` (WU13). Re-confirmed
after WU18 (plus `lib/modelos/preset_ecualizador.dart` and the 3 other guarded test files — all 8 empty).
- [x] The equalizer renders exactly **5** sliders (WU13, task 13.1). Re-confirmed after WU18.
- [x] No shipped string contains "PRO", a trial duration, or a price (WU17 task 17.2/17.6; WU18 task 18.6 final
whole-app grep). Final ARB-only grep (18.6): zero hits for `\bPRO\b`, €, $, gratis/gratuit/"free version", or
a day-count trial phrase in any of the 13 `app_*.arb` files.
- [x] All 13 ARB files keep identical key sets (WU18 tasks 18.1, 18.5 — final parity check; WU1 only touches values
in 2 of the 13 files, so it cannot break parity on its own). Confirmed: 466 real keys in every one of the 13
files, zero missing, zero extra.
---
@@ -971,7 +985,7 @@ real capability living in the header, not decorative chrome).
- [x] 15b.6 Verify — scoped suite green: 605/605 (up from 604), 2 skipped (unchanged, both pre-existing WU15
documented skips). `flutter analyze`: 1 issue, identical to baseline. Literal-encoding scan: zero hits.
## WU16 — Connectivity banner restyle (offline / reconnect)
**Commit**: `feat(connectivity): restyle offline and reconnect banners`
**Depends on**: WU1
@@ -979,16 +993,31 @@ real capability living in the header, not decorative chrome).
**Verify**: `flutter test test/widgets/reconnect_ui_test.dart test/servicios/servicio_audio_reconnect_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**Modified tests**: `reconnect_ui_test.dart`. `servicio_audio_reconnect_test.dart` must pass unmodified.
- [ ] 16.1 Verify-first — run `servicio_audio_reconnect_test.dart` and inspect the reconnect controller: confirm
whether it tracks an attempt count.
- [ ] 16.2 RED — restyle test for the offline banner (visual only) and, **only if 16.1 confirms an attempt count
exists**, a test for the "Reconectando · intento N de M" label.
- [ ] 16.3 GREEN — restyle the offline banner to the new visual language.
- [ ] 16.4 GREEN — **conditionally**: wire the attempt-count label only if the controller exposes one; otherwise
ship the restyle without the counter — do not add new plumbing to invent one.
- [ ] 16.5 REFACTOR — confirm shimmer skeletons (`TarjetaEmisoraShimmer`) are unchanged.
- [ ] 16.6 Verify — `reconnect_ui_test.dart` updated and green; `servicio_audio_reconnect_test.dart` unmodified and
green.
- [x] 16.1 Verify-first — run `servicio_audio_reconnect_test.dart` and inspect the reconnect controller: confirm
whether it tracks an attempt count. **Result: it does NOT reach the UI.** `ControladorReconexion.intentos`
exists as a getter, but `ServicioAudio` (`lib/servicios/servicio_audio.dart`) only ever reads it inside a
`developer.log` debug line (line 413) — its public `estadoStream` maps every playback state to the
`EstadoReproduccion` enum (no attempt-count payload) and `_handler.reconectando` is a bare bool. Wiring a
count to the UI would require adding a getter/stream to `servicio_audio.dart`, which is one of the four
files this change must keep at an **empty git diff vs `main`** — structurally blocked, not just untested.
Shipping without the counter per 16.4, exactly as the risk register anticipated.
- [x] 16.2 RED — 3 new `testWidgets` cases added to `reconnect_ui_test.dart` (no attempt-count test, per 16.1):
reconnecting tints the spinner + status label with `offlineAccent`; plain `cargando` keeps the default
spinner colour (regression guard proving the tint is reconnect-specific, not blanket-loading); error tints
the retry icon + status label with `offlineAccent`. Confirmed RED: 2 of the 3 failed (`Actual: <null>`)
against the pre-restyle widget.
- [x] 16.3 GREEN — `lib/widgets/mini_reproductor.dart`: the status-label `Text` and the reconnect spinner /
error retry icon now read `context.pluriTokens.offlineAccent` (WU1's previously-unused token, whose own
doc comment already named it for this WU) whenever the stream reports `reconectando` or `error`. Plain
`cargando` explicitly keeps `color: null` (the theme default) — verified by 16.2's regression test.
- [x] 16.4 GREEN — conditional step confirmed moot: no attempt-count label added, no new plumbing introduced.
- [x] 16.5 REFACTOR — `TarjetaEmisoraShimmer` (`lib/widgets/tarjeta_emisora.dart`, consumed by
`pantalla_buscar.dart`) is untouched by this commit — confirmed via `git diff --stat`, zero lines.
- [x] 16.6 Verify — scoped suite green: 16/16 (5 `reconnect_ui_test.dart` + 8 `servicio_audio_reconnect_test.dart`
[byte-identical, unmodified] + 3 `mini_reproductor_configurar_test.dart` re-run as an adjacent-file
regression check). `flutter analyze`: 1 issue, identical to baseline. Scoped `dart format`: reformatted the
hand-written production file once (whitespace only), stable on re-run. Literal-encoding scan: zero hits on
the 2 touched files.
## WU17 — Welcome / onboarding screen
@@ -998,19 +1027,100 @@ real capability living in the header, not decorative chrome).
**Verify**: `flutter test test/pantallas/pantalla_bienvenida_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**New tests**: `pantalla_bienvenida_test.dart`
- [ ] 17.1 RED — the welcome screen renders as a full-screen route (not a modal `Dialog`) with a logo, headline,
body copy, exactly 3 feature bullets, and exactly 1 primary CTA.
- [ ] 17.2 RED — binding no-monetization guard: scan the rendered widget tree's text content and assert it contains
- [x] 17.1 RED — the welcome screen renders as a full-screen route (not a modal `Dialog`) with a logo, headline,
body copy, exactly 3 feature bullets, and exactly 1 primary CTA. **Discovered at apply time**: a pre-existing,
UNRELATED `PluriOnboardingDialog` (a "what's new"/help-content modal loaded from markdown assets, shown from
`app.dart`'s launch flow) already exists — it is NOT the mockup's welcome screen and is not touched or
replaced by this WU.
- [x] 17.2 RED — binding no-monetization guard: scan the rendered widget tree's text content and assert it contains
none of: the substring "PRO", a currency amount, or a day-count trial phrase; no secondary "free version"
link.
- [ ] 17.3 RED — tapping "Empezar a escuchar" lands on the Escuchar tab (index 0) with the welcome route removed
from the back stack.
- [ ] 17.4 GREEN — build `lib/pantallas/pantalla_bienvenida.dart` as a full-screen route; content only (logo /
headline / body / 3 bullets / 1 CTA) — no PRO pill, no trial duration, no price, no secondary link.
- [ ] 17.5 GREEN — wire the CTA to dismiss-and-navigate to Escuchar with the route removed.
- [ ] 17.6 REFACTOR — grep `lib/l10n/app_*.arb` for "PRO"/price/day-count trial strings introduced by this WU;
confirm none exist.
- [ ] 17.7 Verify — all 3 scenario tests green; grep scan clean.
- [x] 17.3 RED — tapping "Empezar a escuchar" lands on the Escuchar tab (index 0) with the welcome route removed
from the back stack. Test starts `EstadoNavegacionRaiz` on a DIFFERENT tab first so a pass actually proves
`irA()` ran, not just that escuchar was already the default.
- [x] 17.4 GREEN — built `lib/pantallas/pantalla_bienvenida.dart` as a full-screen route (bare `Scaffold`, no
`PluriPushScaffold` — this is an entry screen with no back affordance, not a second-level pushed screen);
content only (logo / headline / body / 3 bullets / 1 CTA) — no PRO pill, no trial duration, no price, no
secondary link. Content is the mockup's screen 14 copy verbatim MINUS its entire monetization block (the "14
días PRO gratis / luego 2,99 €/año" pricing card and the "Seguir con la versión gratuita" link), Spanish
re-cast to the app's established voseo register (the raw mockup HTML uses "tú" form; the other ~750 lines of
`app_es.arb` consistently use voseo — e.g. "Elegí", "Guardá", "querés" — matched that instead of copying the
mockup's grammar verbatim) and "auto" instead of the mockup's "coche" (matching the one existing precedent at
`localMusicSectionDescription`). The 3 bullet icon colours reuse existing named tokens
(`electricMagenta`/`liveGreen`/`warmCoral`) that happen to be the exact hex values the mockup already used for
those 3 icons — no new raw `Color(0x...)` literal introduced. `SingleChildScrollView` used instead of a
`Column` + `Spacer()` (the exact overflow hazard WU14 already hit and fixed) since this screen carries
meaningfully more content than a typical card. 9 new ARB keys, en/es only (`welcomeHeadline`, `welcomeBody`,
`welcomeBullet{1,2,3}{Title,Subtitle}`, `welcomeCtaLabel`).
- [x] 17.5 GREEN — CTA wired to `context.read<EstadoNavegacionRaiz>().irA(RaizPluriWave.escuchar)` then
`Navigator.of(context).pop()`. **Scope note**: this WU's own verify command and task list only cover
`pantalla_bienvenida_test.dart` — wiring the screen into the REAL first-launch flow (i.e. `app.dart`/
`main.dart` deciding *when* to push it, analogous to `ServicioContenidoApp.debeMostrarInicio()`'s pattern for
`PluriOnboardingDialog`) is NOT part of WU17 as scoped and was deliberately not added here to avoid inventing
untested persistence/launch semantics. Flagged as a likely WU17b gap (same shape as WU15/WU15b).
- [x] 17.6 REFACTOR — whole-repo grep confirmed clean: `\bPRO\b` (word-boundary) has exactly one hit in all of
`lib/` — this WU's own doc comment describing the guard (never rendered). The unqualified substring "PRO"
also matches pre-existing, unrelated `"PROGRAMADOS"` (vacation-alarms section title, WU9) and
`pluri_premium_widgets.dart` (a pre-existing decorative-hero-card widget file name, "premium" as in visual
styling, not a paid tier) — both false positives for a naive substring check, neither a monetization string.
No day-count trial phrase, no €/$ amount, no "gratuita"/"gratis"/"free version" string anywhere in
`lib/l10n/app_*.arb` or `lib/`.
- [x] 17.7 Verify — all 3 scenario tests green; grep scan clean.
## WU17b — Wire the welcome screen into the first-launch flow
**Not in the original plan — added to close the WU17 gap noted above, same shape as WU15/WU15b.** WU17 built
`PantallaBienvenida` fully tested and committed, but left it unreachable from the app: `rg "PantallaBienvenida"
lib/app.dart lib/main.dart` found nothing. This work unit exists solely to fix that.
**Commit**: `fix(bienvenida): wire the welcome screen into the first-launch flow`
**Depends on**: WU17
**Spec refs**: `onboarding-welcome` — Full-Screen Welcome Route (reachability; the render/content/no-monetization/CTA
scenarios stay WU17's own, unmodified)
**Verify**: `flutter test test/servicios/servicio_bienvenida_test.dart test/pantallas/pantalla_bienvenida_primer_lanzamiento_test.dart test/pantallas/pantalla_bienvenida_test.dart test/widget_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --cached --name-only --diff-filter=ACM HEAD -- '*.dart')`
**New tests**: `test/servicios/servicio_bienvenida_test.dart`, `test/pantallas/pantalla_bienvenida_primer_lanzamiento_test.dart`
**Modified tests**: none (WU17's own `pantalla_bienvenida_test.dart` re-run unmodified as a regression check)
**Coexistence ruling applied.** `PluriOnboardingDialog` (an unrelated, pre-existing "what's new"/help-content modal
loaded from markdown assets) is NOT deleted, merged, or replaced — first-launch welcome and a what's-new modal are
different things, confirmed by reading `assets/content/onboarding/en.md`: it is a detailed feature-reference
walkthrough plus per-version update notes, structurally different content from the welcome screen's 3-bullet
marketing intro. Both now run from `app.dart`'s `_PaginaPrincipalState`, in this order on every cold start: the
welcome screen's once-ever check resolves FIRST, then the pre-existing what's-new dialog's own independent
per-version due-or-not check runs exactly as it did before this WU. Sequencing (not two independent fire-and-forget
calls) is what prevents the two from ever racing onto the screen at the same time.
- [x] 17b.1 RED — `test/servicios/servicio_bienvenida_test.dart`: a new `ServicioBienvenida` (mirroring
`ServicioContenidoApp`'s injectable-`SharedPreferences`, versioned-key convention, S3-R4) is due before it has
ever been marked seen, is not due after `marcarBienvenidaVista()`, and respects a seen flag already persisted
by a prior launch (`SharedPreferences.setMockInitialValues`).
- [x] 17b.2 RED — `test/pantallas/pantalla_bienvenida_primer_lanzamiento_test.dart`: a new
`PantallaBienvenida.mostrarSiProcede(context)` static method (mirroring `PluriOnboardingDialog.mostrarSiProcede`'s
check-then-show-then-mark-seen shape) pushes the welcome screen on a first launch (no seen flag persisted), does
NOT push it when the seen flag is already persisted, and — in one continuous session — persists the flag after
being shown once so a later check in the same run skips it.
- [x] 17b.3 GREEN — created `lib/servicios/servicio_bienvenida.dart` (`ServicioBienvenida`, key
`pluri_bienvenida_vista_v1`, plain one-time boolean — no version comparison needed, unlike
`ServicioContenidoApp`, since this welcome is a single first-impression surface, not a per-version one).
- [x] 17b.4 GREEN — added the static `PantallaBienvenida.mostrarSiProcede(BuildContext)` method to the existing
`pantalla_bienvenida.dart` file (no new wrapper class needed — unlike `PluriOnboardingDialog`, there is only one
call shape here); updated the class doc comment to describe how it coexists with `PluriOnboardingDialog`.
- [x] 17b.5 GREEN — wired `app.dart`: imported `pantalla_bienvenida.dart`; renamed the existing
`_onboardingInicialSolicitado` guard flag to `_flujoPrimerLanzamientoSolicitado` (it now covers the combined
sequence, not only the dialog); added `_mostrarFlujoPrimerLanzamiento()`, which awaits
`PantallaBienvenida.mostrarSiProcede(context)` then calls the pre-existing, untouched
`_mostrarOnboardingInicial()` — replacing the single `unawaited(_mostrarOnboardingInicial())` call site with
`unawaited(_mostrarFlujoPrimerLanzamiento())`.
- [x] 17b.6 REFACTOR — confirmed `_mostrarOnboardingInicial()`'s own body (900ms delay, `_alarmaSonandoActiva` guard,
`PluriOnboardingDialog.mostrarSiProcede` call) is byte-for-byte unchanged — only its call site moved one level
deeper into the new sequencing method. No scenario in `PantallaBienvenida`'s own WU17 test file needed to
change (all 3 pass unmodified, confirming the CTA/content/no-monetization behavior is untouched).
- [x] 17b.7 Verify — scoped suite green: 13/13 (3 `servicio_bienvenida_test.dart` + 3
`pantalla_bienvenida_primer_lanzamiento_test.dart` + 3 `pantalla_bienvenida_test.dart` [byte-identical,
unmodified] + 4 `widget_test.dart`). `flutter analyze`: 1 issue, identical to baseline. Scoped `dart format`:
reformatted 1 of 5 touched files (whitespace-only string-literal wrapping), stable on re-run. Literal-encoding
scan: one console-rendering false positive on the pre-existing "días" string (verified byte-correct UTF-8 via a
direct file read with the encoding pinned), zero real corruption.
## WU18 — i18n batch (all 13 locales)
@@ -1021,53 +1131,84 @@ UI copy every other capability introduced
**Verify**: `flutter test && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
**Modified tests**: ARB key-parity guard test (extend existing or add one if none exists)
- [ ] 18.1 RED — an ARB-parity test asserting all 13 `lib/l10n/app_*.arb` files contain an identical key set.
- [ ] 18.1b RED — **anti-copy guard (a key-parity test is NOT enough).** `l10n.yaml` sets
`template-arb-file: app_es.arb`, so any key missing from a locale silently falls back to **Spanish** — that is
why untranslated strings currently render as Spanish inside `app_localizations_ja.dart`, `_fr.dart`, etc.
A key-parity test would pass just as happily if all 11 locales simply COPIED the Spanish value.
Write a test that, for every non-`es` locale, collects every key whose value is byte-identical to the `app_es.arb`
value and requires each one to appear in an explicit allowlist (a checked-in file, e.g.
`test/l10n/identical_value_allowlist.dart`). Legitimately-identical values exist — brand names, "OK", "Radio",
codec names, pure symbols — so the allowlist is the escape hatch, but **each entry must be added deliberately,
never in bulk**. Any unlisted identical value fails the test.
Seed the allowlist ONLY with entries that are genuinely untranslatable; do not pre-populate it to make the
suite green.
- [ ] 18.2 RED — `navHome` exists as a key in all 13 locales, and `git diff` for this commit touches exactly 11
`navHome` **value** lines (en/es were WU1's job, task 1.9).
- [ ] 18.3 GREEN — add the ~35-45 new keys (accumulated from WU4-WU17's English/Spanish-only strings) to all 13
ARB files, sourcing translations for the 11 non-en/es locales.
- [ ] 18.4 GREEN — set the `navHome` value in the remaining 11 locales to a real translation of "Listen" (not a
mechanical copy of the English/Spanish pair).
- [ ] 18.5 REFACTOR — run the ARB-parity check across all 13 files; confirm zero missing/extra keys, AND that the
18.1b anti-copy guard passes with an allowlist containing only genuinely-untranslatable entries.
Sanity figures at the time this was written: `app_es.arb` (template) 540 keys, `app_en.arb` 561, and all 11
other locales 518 — i.e. the non-template locales were **22 keys behind**, and those 22 were rendering in
Spanish. WU18 is what closes that gap; the count will have grown as WU8-WU17 land, so re-measure rather than
trusting these numbers.
- [ ] 18.6 Verify — full `flutter test` suite green; `flutter analyze` clean; scoped `dart format` over the files
this change touched produces no diff (NEVER bare `dart format .` — see the warning at the top);
grep confirms no "PRO"/price/trial-duration string exists in any ARB file (final whole-app Success Criteria
check).
- [x] 18.1 RED — `test/l10n/arb_parity_test.dart` (+ `test/l10n/arb_test_helpers.dart` shared reader): an ARB-parity
test asserting all 13 `lib/l10n/app_*.arb` files contain an identical key set, plus a `navHome`-exists check
(task 18.2's first half). Confirmed RED against the pre-merge tree: 11 locales missing 72 keys each.
- [x] 18.1b RED — `test/l10n/arb_anti_copy_test.dart` + `test/l10n/identical_value_allowlist.dart` (the anti-copy
guard). **Scope correction found at apply time**: read literally ("for every non-`es` locale, collects every
key whose value is byte-identical") this must cover the WHOLE file, not only WU18's own new keys — a
narrower scope would leave 183 pre-existing identical values (mostly genuine `pt`/`es` Iberian-Romance
cognates, plus international genre names, "OK", brand names, symbols) completely unaudited. Built the
allowlist to cover both: 207 total entries (after de-duplicating two detection-script bugs found along the
way — see 18.5's note), each with an individual justification comment, grouped by category for readability
but never wildcarded.
**`en` is deliberately excluded** from this guard's locale loop (see the test file's own doc comment): `en`
and `es` have been hand-maintained together by every WU throughout this whole change (`en`/`es`-only new
keys, never left lagging), so `en` never carries the "silently fell back to the template" risk this guard
exists to catch — unlike the other 11, which this WU batch-translates for the first time.
- [x] 18.2 RED — `navHome` exists as a key in all 13 locales (part of 18.1's parity test); `git diff` for this
commit touches exactly **11** `navHome` **value** lines, confirmed via `git diff | grep -c '"navHome"'` = 22
(11 removed + 11 added) after the merge (en/es were WU1's job, task 1.9, untouched here).
- [x] 18.3 GREEN — added exactly **72** new keys (measured, not the ~35-45 estimate) to all 13 ARB files, with real,
idiomatic translations for the 11 non-en/es locales (ar, bn, de, fr, hi, id, it, ja, pt, ru, zh) — vocabulary
cross-checked against ~20 already-translated sibling keys in each locale (e.g. "station"/"favorites"/
"preset"/"OK"/genre-name conventions, register — `du`-informal `de`, `vous`-formal `fr`, `tu`-informal `it`,
polite-imperative `ru`/`ja`/`hi`/`bn`, `você`-imperative `pt` with no explicit pronoun) so new copy reads as
part of the same voice as existing copy, not machine-translated in isolation. 7 of the 72 keys carry ICU
placeholders/plurals; their `@key` metadata blocks were added to all 11 locales matching the template's own
established convention of replicating metadata into every locale file (confirmed via a diff scan: the
53 pre-existing metadata blocks shared between `es` and a sample locale were byte-identical). Arabic and
Russian plurals use full CLDR categories (`one`/`two`/`few`/`other` and `one`/`few`/`other`) matching the
precedent already set by the pre-existing `stationCount` key, rather than the simpler `=1`/`other` the ES/EN
template itself uses for these two new keys.
- [x] 18.4 GREEN — set `navHome` in the remaining 11 locales to a real translation of "Listen"/"Escuchar" (ar
استماع, bn শোনা, de Hören, fr Écouter, hi सुनना, id Dengar, it Ascolta, ja 聴く, pt Ouvir, ru Слушать,
zh 收听) — each cross-checked against that locale's OWN sibling nav-tab labels (`navSearch`/`navFavorites`/
etc.) for register/part-of-speech consistency, not a mechanical copy of "Listen" or "Escuchar".
- [x] 18.5 REFACTOR — re-ran the parity + anti-copy guard; **two detection-script bugs found and fixed along the
way, not left in the final allowlist**: (1) the first draft's collision scanner used PowerShell's default
`-eq`, which is **case-insensitive** — it falsely flagged 5 pairs (e.g. `de/durationMinutesOnly` "min" vs
"Min") as collisions; re-scanned with `-ceq` (case-sensitive, matching Dart's own `==`) and removed the 5
false positives. (2) the first allowlist draft was missing 7 genuine collisions the script had already found
but a manual transcription pass dropped (`settingsGroupAudioTitle` ×4, `endField`/`endLabel` fr, `alarmFadeInLabel`
pt) — added them back. Final state: zero missing/extra keys across all 13 files; anti-copy guard's own
"no stale entries" self-check (added as this test's second assertion, not just the one the task named) also
passes, so the allowlist is verified as exactly the current, real collision set — 207 entries, none stale,
none missing.
- [x] 18.6 Verify — full `flutter test`: **747/747 green, 2 skipped** (up from the 737-baseline this batch started
from — +6 WU17b tests + +4 WU18 tests, reconciles exactly). `flutter analyze`: 1 issue, identical to
baseline. Scoped `dart format` (11 regenerated `lib/l10n/gen/*.dart` + 4 new `test/l10n/*.dart` files, ARB
JSON files are not `dart format` targets): reformatted 3 of 15 files (whitespace/line-wrapping only), stable
on re-run. Grep confirms zero `\bPRO\b` (case-sensitive), €, $, "gratis"/"gratuit"/"free version", or
day-count trial-phrase hits in any `app_*.arb` file. Literal-encoding scan (Arabic, Bengali, Devanagari,
Japanese, Chinese, Cyrillic all touched): zero mojibake hits across all 11 modified ARB files, the 11
regenerated `gen/*.dart` files, and the 4 new test files; spot-checked several locales' rendered text
directly to confirm.
---
## Final Verification Sequence (run once, after WU18's commit)
1. `flutter test` — full suite green.
2. `flutter analyze` — clean.
3. `dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM main -- '*.dart')` — no diff over the
files this change touched. **NEVER bare `dart format .`** — it reformats 27 unrelated pre-existing files and would
break the empty-diff guards in steps 4 and 5 below (Engram `reference/dart-format-scope-hazard`, id 2511).
4. `git diff --stat lib/servicios/navegacion_auto.dart lib/servicios/servicio_ecualizador.dart` — both empty.
5. `git diff lib/servicios/servicio_audio.dart` — band-application block (`749-762`) unchanged.
6. Grep `lib/` for `countrycodes` — zero hits.
7. Grep `lib/l10n/app_*.arb` for "PRO", a currency amount, or a day-count trial phrase — zero hits.
8. Confirm all 13 ARB files have identical key sets and `navHome` present in each.
9. Confirm the equalizer screen renders exactly 5 sliders.
10. Run `test/pantallas/pantalla_alarma_sonando_dismiss_guard_test.dart` in isolation — passes, file diff empty.
All 10 steps run and confirmed after the WU18 commit:
1. [x] `flutter test` — full suite green: **747/747, 2 skipped**.
2. [x] `flutter analyze` — clean: 1 issue, identical to the pre-branch baseline.
3. [x] `dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM main -- '*.dart')` — no diff over
the files this change touched. **NEVER bare `dart format .`** — it reformats 27 unrelated pre-existing files and
would break the empty-diff guards in steps 4 and 5 below (Engram `reference/dart-format-scope-hazard`, id 2511).
4. [x] `git diff --stat lib/servicios/navegacion_auto.dart lib/servicios/servicio_ecualizador.dart` — both empty.
5. [x] `git diff lib/servicios/servicio_audio.dart` — band-application block (`749-762`) unchanged.
6. [x] Grep `lib/` for `countrycodes` — zero hits.
7. [x] Grep `lib/l10n/app_*.arb` for "PRO", a currency amount, or a day-count trial phrase — zero hits.
8. [x] Confirm all 13 ARB files have identical key sets and `navHome` present in each — 466 real keys each, confirmed.
9. [x] Confirm the equalizer screen renders exactly 5 sliders — confirmed.
10. [x] Run `test/pantallas/pantalla_alarma_sonando_dismiss_guard_test.dart` in isolation — 13/13 passes, file diff
empty vs `main`.
**All 18 work units (+ WU15b, WU17b, and the pre-existing controller-dispose bugfix) are now complete.** Branch
`feat/rediseno-funcional` is ready for `sdd-verify`.
## Next Step
`sdd-apply`implement WU1 first (unblocks everything else); honor the `size:exception` commits (WU3a, WU3b, WU10)
as recorded above rather than attempting to split them further.
`sdd-verify`validate the completed implementation against `specs/*/spec.md`; `sdd-apply`'s work on this change is
done.
+96
View File
@@ -0,0 +1,96 @@
import 'package:flutter_test/flutter_test.dart';
import 'arb_test_helpers.dart';
import 'identical_value_allowlist.dart';
/// WU18 task 18.1b -- the anti-copy guard.
///
/// A key-parity test alone is NOT sufficient: it passes just as happily if
/// all 11 non-`es` locales simply copy the Spanish template value verbatim
/// for every key -- which would certify the exact bug it exists to catch
/// (see `reference/l10n-spanish-template-fallback`). This test collects
/// every value in a non-`es` locale that is byte-identical to the Spanish
/// template's value for the same key and fails unless that (locale, key)
/// pair is explicitly, individually allowlisted in
/// `identical_value_allowlist.dart`.
///
/// Scope note: `en` is deliberately excluded from this guard. Unlike the
/// other 11 locales -- which this change batch-translates for the first
/// time -- `en` and `es` have been hand-maintained together by every work
/// unit throughout this whole redesign (`en`/`es`-only new keys, never
/// left lagging), so `en` never carries the "silently fell back to the
/// template" risk this guard exists to catch.
const _auditedLocales = [
'ar',
'bn',
'de',
'fr',
'hi',
'id',
'it',
'ja',
'pt',
'ru',
'zh',
];
void main() {
test('every non-es value identical to the Spanish template is a '
'deliberately allowlisted exception, not an accidental untranslated '
'copy', () {
final es = readArb('es');
final unlisted = <String>[];
for (final locale in _auditedLocales) {
final arb = readArb(locale);
for (final key in realKeys(arb)) {
if (!es.containsKey(key))
continue; // arb_parity_test's job, not this one's
if (arb[key] == es[key] &&
!identicalValueAllowlist.contains((locale, key))) {
unlisted.add('$locale/$key = "${arb[key]}"');
}
}
}
expect(
unlisted,
isEmpty,
reason:
'Found value(s) identical to the Spanish template that are NOT '
'in identical_value_allowlist.dart -- this is very likely an '
'untranslated copy-paste, not a deliberate cognate/brand-name/'
'symbol exception. Translate it, or add a reviewed, individual '
'allowlist entry with a justification comment if it truly is '
'untranslatable:\n${unlisted.join('\n')}',
);
});
test(
'every allowlisted pair is a real, current collision (no stale entries)',
() {
final es = readArb('es');
final stale = <String>[];
for (final entry in identicalValueAllowlist) {
final (locale, key) = entry;
final arb = readArb(locale);
final stillCollides =
arb.containsKey(key) && es.containsKey(key) && arb[key] == es[key];
if (!stillCollides) {
stale.add('$locale/$key');
}
}
expect(
stale,
isEmpty,
reason:
'identical_value_allowlist.dart has entries that no longer '
'correspond to a real identical value -- remove them, the '
'allowlist must only contain genuine, current exceptions:\n'
'${stale.join('\n')}',
);
},
);
}
+54
View File
@@ -0,0 +1,54 @@
import 'package:flutter_test/flutter_test.dart';
import 'arb_test_helpers.dart';
/// WU18 task 18.1 / 18.2 -- ARB key-parity guard.
///
/// `l10n.yaml` sets `template-arb-file: app_es.arb`, so Spanish is the l10n
/// template: any key missing from a locale's ARB silently falls back to the
/// Spanish value at runtime (not English, which is the more common
/// assumption). This test guarantees all 13 locales carry the same key set,
/// so nothing can silently render in Spanish because a locale was never
/// given that key.
void main() {
test('all 13 ARB files declare an identical key set', () {
final arbs = {
for (final locale in supportedArbLocales) locale: readArb(locale),
};
final templateKeys = realKeys(arbs['es']!);
for (final locale in supportedArbLocales) {
if (locale == 'es') continue;
final keys = realKeys(arbs[locale]!);
final missing = templateKeys.difference(keys);
final extra = keys.difference(templateKeys);
expect(
missing,
isEmpty,
reason:
'app_$locale.arb is missing ${missing.length} key(s) present in '
'the es template (these silently fall back to Spanish at '
'runtime): $missing',
);
expect(
extra,
isEmpty,
reason:
'app_$locale.arb has ${extra.length} key(s) not present in the '
'es template: $extra',
);
}
});
test('navHome exists as a key in all 13 locales', () {
for (final locale in supportedArbLocales) {
final arb = readArb(locale);
expect(
realKeys(arb).contains('navHome'),
isTrue,
reason: 'app_$locale.arb is missing the navHome key',
);
}
});
}
+36
View File
@@ -0,0 +1,36 @@
import 'dart:convert';
import 'dart:io';
/// All 13 supported locale codes backing `lib/l10n/app_*.arb`.
///
/// `flutter test` always runs with the working directory set to the
/// package root, so `lib/l10n/...` below resolves correctly without any
/// extra path juggling.
const List<String> supportedArbLocales = [
'ar',
'bn',
'de',
'en',
'es',
'fr',
'hi',
'id',
'it',
'ja',
'pt',
'ru',
'zh',
];
/// Reads and decodes `lib/l10n/app_<locale>.arb` as a JSON map.
Map<String, dynamic> readArb(String locale) {
final file = File('lib/l10n/app_$locale.arb');
return json.decode(file.readAsStringSync()) as Map<String, dynamic>;
}
/// Real translation keys only -- excludes ICU metadata entries (`@key`)
/// and the `@@locale` marker, which are not themselves translatable
/// strings.
Set<String> realKeys(Map<String, dynamic> arb) {
return arb.keys.where((key) => !key.startsWith('@')).toSet();
}
+275
View File
@@ -0,0 +1,275 @@
/// WU18 anti-copy guard allowlist (task 18.1b).
///
/// A key-parity check alone is not enough to prove a locale was actually
/// translated: it passes just as happily if a locale simply copies the
/// Spanish (`app_es.arb`, the l10n template) value verbatim. `arb_anti_copy_test.dart`
/// scans every non-`es` locale for values that are byte-identical to the
/// Spanish template and fails unless each one is listed here.
///
/// Legitimately-identical values DO exist -- brand names, international
/// loanwords, pure symbols, and words that are simply spelled the same in
/// closely related Romance languages (Spanish/Portuguese/Italian/French all
/// share a huge amount of short, common vocabulary by descent, not by
/// accident). This file is the deliberate, reviewed escape hatch for those.
///
/// Every entry below was reviewed individually and is grouped only for
/// readability -- this file must NEVER be bulk-populated to silence the
/// test. If you find an unlisted collision, translate it; do not add it
/// here unless it is genuinely untranslatable.
library;
/// (locale, ARB key) pairs whose value is intentionally identical to the
/// Spanish template's value for that key.
const Set<(String locale, String key)> identicalValueAllowlist = {
// ---------------------------------------------------------------------
// Brand / product names -- never translated in any language.
// ---------------------------------------------------------------------
('ar', 'appTitle'), // "PluriWave"
('bn', 'appTitle'),
('de', 'appTitle'),
('fr', 'appTitle'),
('hi', 'appTitle'),
('id', 'appTitle'),
('it', 'appTitle'),
('ja', 'appTitle'),
('pt', 'appTitle'),
('ru', 'appTitle'),
('zh', 'appTitle'),
('ar', 'welcomeBullet2Title'), // "Android Auto" -- Google product name
('bn', 'welcomeBullet2Title'),
('de', 'welcomeBullet2Title'),
('fr', 'welcomeBullet2Title'),
('hi', 'welcomeBullet2Title'),
('id', 'welcomeBullet2Title'),
('it', 'welcomeBullet2Title'),
('ja', 'welcomeBullet2Title'),
('pt', 'welcomeBullet2Title'),
('ru', 'welcomeBullet2Title'),
('zh', 'welcomeBullet2Title'),
// ---------------------------------------------------------------------
// Pure symbols / placeholders -- no translatable text at all.
// ---------------------------------------------------------------------
('ar', 'dash'), // "—" (em dash)
('bn', 'dash'),
('de', 'dash'),
('fr', 'dash'),
('hi', 'dash'),
('id', 'dash'),
('it', 'dash'),
('ja', 'dash'),
('pt', 'dash'),
('ru', 'dash'),
('zh', 'dash'),
('ar', 'streamUrlHint'), // example URL shown as form hint text, not prose
('bn', 'streamUrlHint'),
('de', 'streamUrlHint'),
('fr', 'streamUrlHint'),
('hi', 'streamUrlHint'),
('id', 'streamUrlHint'),
('it', 'streamUrlHint'),
('ja', 'streamUrlHint'),
('pt', 'streamUrlHint'),
('ru', 'streamUrlHint'),
('zh', 'streamUrlHint'),
(
'ar',
'favoriteGroupsChipLabel',
), // "{groupName} · {count}" -- placeholders + separator only
('bn', 'favoriteGroupsChipLabel'),
('de', 'favoriteGroupsChipLabel'),
('fr', 'favoriteGroupsChipLabel'),
('hi', 'favoriteGroupsChipLabel'),
('id', 'favoriteGroupsChipLabel'),
('it', 'favoriteGroupsChipLabel'),
('ja', 'favoriteGroupsChipLabel'),
('pt', 'favoriteGroupsChipLabel'),
('ru', 'favoriteGroupsChipLabel'),
('zh', 'favoriteGroupsChipLabel'),
// ---------------------------------------------------------------------
// International music-genre labels -- treated as proper nouns and left
// untranslated by convention across virtually every music app/catalog
// (Spotify, Apple Music, YouTube Music, etc. all do the same).
// ---------------------------------------------------------------------
('de', 'genrePop'), ('de', 'genreRock'), ('de', 'genreJazz'),
('de', 'genreHipHop'), ('de', 'genreCountry'), ('de', 'genreMetal'),
('de', 'genreReggae'),
('fr', 'genrePop'), ('fr', 'genreRock'), ('fr', 'genreJazz'),
('fr', 'genreHipHop'), ('fr', 'genreCountry'), ('fr', 'genreMetal'),
('fr', 'genreReggae'),
('id', 'genrePop'), ('id', 'genreRock'), ('id', 'genreJazz'),
('id', 'genreHipHop'), ('id', 'genreCountry'), ('id', 'genreMetal'),
('id', 'genreReggae'),
('it', 'genrePop'), ('it', 'genreRock'), ('it', 'genreJazz'),
('it', 'genreHipHop'), ('it', 'genreCountry'), ('it', 'genreMetal'),
('it', 'genreReggae'),
('it', 'genreLatin'), // "Latina" -- also an es/it cognate
('pt', 'genrePop'), ('pt', 'genreRock'), ('pt', 'genreJazz'),
('pt', 'genreHipHop'), ('pt', 'genreCountry'), ('pt', 'genreMetal'),
('pt', 'genreReggae'),
('pt', 'genreLatin'), // "Latina" -- also an es/pt cognate
// Same genre names re-used verbatim as equalizer preset names.
('de', 'equalizerPresetRock'),
('de', 'equalizerPresetPop'),
('de', 'equalizerPresetJazz'),
('fr', 'equalizerPresetRock'),
('fr', 'equalizerPresetPop'),
('fr', 'equalizerPresetJazz'),
('id', 'equalizerPresetRock'),
('id', 'equalizerPresetPop'),
('id', 'equalizerPresetJazz'),
('it', 'equalizerPresetRock'),
('it', 'equalizerPresetPop'),
('it', 'equalizerPresetJazz'),
('pt', 'equalizerPresetRock'),
('pt', 'equalizerPresetPop'),
('pt', 'equalizerPresetJazz'),
// ---------------------------------------------------------------------
// "Preset"/"OK" -- international tech loanwords already established
// throughout this file (see `advancedEqDevicePresetLabel`, `actionOk`).
// ---------------------------------------------------------------------
('de', 'actionOk'), ('de', 'statusOk'),
('fr', 'actionOk'), ('fr', 'statusOk'),
('it', 'actionOk'), ('it', 'statusOk'),
('pt', 'actionOk'), ('pt', 'statusOk'),
('de', 'advancedEqDevicePresetLabel'), // "Preset: {presetName}"
('id', 'advancedEqDevicePresetLabel'),
('it', 'advancedEqDevicePresetLabel'),
('pt', 'advancedEqDevicePresetLabel'),
// ---------------------------------------------------------------------
// Duration / count unit abbreviations -- "min", "h", "s" are
// internationally understood abbreviations kept literal across many
// languages in this file already (not English left in place; German,
// French, Italian and Portuguese all natively abbreviate their own words
// for minute/hour/second the same way).
// ---------------------------------------------------------------------
('fr', 'durationHoursMinutesSeconds'), ('fr', 'durationMinutesSeconds'),
('fr', 'durationMinutesOnly'), ('fr', 'durationSecondsOnly'),
('fr', 'alarmSnoozeOptionLabel'), // "{minutes} min"
('it', 'durationHoursMinutesSeconds'), ('it', 'durationMinutesSeconds'),
('it', 'durationMinutesOnly'), ('it', 'durationSecondsOnly'),
('it', 'alarmSnoozeOptionLabel'),
('pt', 'durationHoursMinutesSeconds'), ('pt', 'durationMinutesSeconds'),
('pt', 'durationMinutesOnly'), ('pt', 'durationSecondsOnly'),
('pt', 'alarmSnoozeOptionLabel'),
(
'fr',
'stationsCount',
), // "{count} radios" -- "radios" is a real French cognate plural
(
'de',
'alarmFadeInLabel',
), // "Fade-in {seconds}s" -- "fade-in" is a standard audio-production term
('it', 'alarmFadeInLabel'),
('pt', 'alarmFadeInLabel'),
(
'de',
'settingsGroupAudioTitle',
), // "AUDIO" -- shared international/Latin-root term (WU18 new key)
('fr', 'settingsGroupAudioTitle'),
('id', 'settingsGroupAudioTitle'),
('it', 'settingsGroupAudioTitle'),
// ---------------------------------------------------------------------
// Short weekday abbreviations that happen to coincide across Romance
// languages even though the full weekday names differ (only the
// 3-letter abbreviation collides, e.g. lunes/lundi/lunedì -> "Lun").
// ---------------------------------------------------------------------
('fr', 'weekdayShortMonday'), ('fr', 'weekdayShortTuesday'),
('it', 'weekdayShortMonday'),
('it', 'weekdayShortTuesday'),
('it', 'weekdayShortSunday'),
('pt', 'weekdayShortSaturday'), ('pt', 'weekdayShortSunday'),
('fr', 'endField'), // "Fin" -- es/fr cognate ("end")
('fr', 'endLabel'), // "Fin"
// ---------------------------------------------------------------------
// Country / language endonyms and cognate proper nouns -- country and
// language names are frequently identical or near-identical across
// Romance languages (and beyond); several are also the country's own
// official native name.
// ---------------------------------------------------------------------
('id', 'countryArgentina'),
('it', 'countryArgentina'),
('pt', 'countryArgentina'),
('id', 'countryItaly'),
('it', 'countryItaly'), // "Italia" -- Italy's own endonym
('id', 'countryBrazil'),
(
'pt',
'countryBrazil',
), // "Brasil" -- Brazil's own official (Portuguese) name
('it', 'countryFrance'), // "Francia"
('pt', 'countryMexico'), // "México"
('pt', 'countryUk'), // "Reino Unido" -- identical es/pt calque
('it', 'languageSystemDefault'), // "Sistema"
('pt', 'languageSystemDefault'),
('pt', 'languageSectionTitle'), // "Idioma"
('pt', 'searchLanguageFilterLabel'), // "Idioma"
('pt', 'searchCountryFilterLabel'), // "País"
('pt', 'countryOptionalLabel'), // "País (opcional)"
// ---------------------------------------------------------------------
// Portuguese/Spanish close-cognate vocabulary. Portuguese and Spanish
// are both Iberian Romance languages and share an unusually large amount
// of short, common UI vocabulary verbatim -- these are correct,
// independently-chosen Portuguese words that happen to be spelled the
// same as their Spanish counterpart, not an untranslated leftover.
// ---------------------------------------------------------------------
('pt', 'navSearch'), // "Buscar"
('pt', 'navFavorites'), // "Favoritos"
('pt', 'favoritesTitle'), // "Favoritos"
('pt', 'cancelAction'), // "Cancelar"
('pt', 'cancelTimer'), // "Cancelar timer"
('pt', 'startTimer'), // "Iniciar timer"
('pt', 'hoursLabel'), // "Horas"
('pt', 'minutesLabel'), // "Minutos"
('pt', 'secondsLabel'), // "Segundos"
('pt', 'timeField'), // "Hora"
('pt', 'alarmInlineHourLabel'), // "Hora" (WU18 new key, task 18.3)
('it', 'alarmInlineMinuteLabel'), // "Minuto" (WU18 new key, task 18.3)
('pt', 'alarmInlineMinuteLabel'), // "Minuto" (WU18 new key, task 18.3)
('pt', 'countriesScreenTitle'), // "Países" (WU18 new key, task 18.3)
('pt', 'favoritesFilterAllLabel'), // "Todas" (WU18 new key, task 18.3)
('pt', 'seeAllAction'), // "Ver todas" (WU18 new key, task 18.3)
(
'pt',
'vacationUpcomingSectionTitle',
), // "PROGRAMADOS" (WU18 new key, task 18.3)
('pt', 'settingsSafeStatus'), // "Seguro"
('pt', 'preferredStationAutomaticFallback'), // "Fallback automático"
('pt', 'recordingsMaxSizeMbLabel'), // "Megabytes máximos"
('pt', 'favoriteGroupsTitle'), // "Listas de favoritos"
('pt', 'favoriteGroupsEdit'), // "Editar lista"
('pt', 'searchFiltersLabel'), // "Filtros"
('pt', 'detectAction'), // "Detectar"
('pt', 'alarmScreenTitle'), // "Despertar musical"
('pt', 'editAction'), // "Editar"
('pt', 'defaultAlarmName'), // "Despertador musical"
('pt', 'soundDigitalPulse'), // "Pulso digital"
('pt', 'pauseAction'), // "Pausar"
('pt', 'playbackStatusConnecting'), // "Conectando..."
('pt', 'playbackStatusPaused'), // "Pausado"
('pt', 'playbackStatusReconnecting'), // "Reconectando..."
('it', 'equalizerBandLabel'), // "Banda {band}"
('pt', 'equalizerBandLabel'), // "Banda {band}"
('pt', 'equalizerPresetFlat'), // "Plano"
('pt', 'equalizerPresetVoice'), // "Voz"
('pt', 'equalizerPresetCustom'), // "Personalizado"
('pt', 'customOption'), // "Personalizada"
('pt', 'indefiniteOption'), // "Indefinida"
('pt', 'invalidNumber'), // "Número inválido"
('pt', 'stopAction'), // "Parar"
('pt', 'eqDeviceEditTitle'), // "Editar dispositivo"
('pt', 'eqDeviceConnected'), // "Conectado"
('pt', 'localMusicSectionTitle'), // "Música local (Android Auto)"
(
'pt',
'recordingsLibraryStorageCaption',
), // WU18 new key (task 18.3) -- "usados"/"MB"/"de" all coincide
(
'pt',
'searchResultsCount',
), // WU18 new key (task 18.3) -- "resultado(s)" is an es/pt cognate
};
@@ -28,14 +28,6 @@ void _suppressListTileInkAssertion() {
/// WU3b task 3b.1: the GRABACIONES Y MÚSICA detail screen for "Grabaciones"
/// renders inside a [PluriPushScaffold] and its moved controls still respond
/// exactly as they did inside the old `_SeccionGrabaciones`.
///
/// This file deliberately does NOT interact with "Maximum recording size"
/// (`_editarTamanoMaximo`): that control has a pre-existing, out-of-scope
/// controller-dispose race (documented in `pantalla_ajustes_grabaciones.dart`
/// and, for the analogous `_editarGrupo` case, in
/// `pantalla_ajustes_grupos_favoritos_test.dart`) that this move does not
/// fix. "Restore default path" is exercised instead — a real, moved
/// capability with no such hazard.
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
@@ -100,4 +92,37 @@ void main() {
await tester.pump(const Duration(seconds: 5));
await tester.pumpAndSettle();
});
testWidgets(
'moved control still responds: editing the maximum recording size '
'persists it',
(tester) async {
_suppressListTileInkAssertion();
final estado = EstadoGrabacion(
servicio: FakeServicioGrabacionRadioInactiva(),
);
addTearDown(estado.dispose);
expect(estado.maxBytes, 500 * 1024 * 1024, reason: 'default, not 250');
await tester.pumpWidget(buildScreen(estado));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
await tester.tap(find.text('Maximum recording size'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), '250');
await tester.pumpAndSettle();
await tester.tap(find.text('Save quick access'));
await tester.pumpAndSettle();
expect(estado.maxBytes, 250 * 1024 * 1024);
expect(find.text('Recording limit updated to 250 MB'), findsOneWidget);
// SnackBar's own dismiss Timer is not frame-scheduled — let it resolve
// before teardown (WU3a batch discovery) instead of leaving a pending
// Timer behind.
await tester.pump(const Duration(seconds: 5));
await tester.pumpAndSettle();
},
);
}
@@ -33,39 +33,6 @@ void _suppressListTileInkAssertion() {
addTearDown(() => FlutterError.onError = original);
}
/// Pre-existing bug, out of scope for this move-only WU: `_editarGrupo`
/// (copied verbatim from the old `_SeccionGruposFavoritos`) disposes its
/// `TextEditingController` immediately after `showModalBottomSheet` resolves,
/// racing the sheet's own close animation, which still holds a `TextField`
/// bound to that controller for a couple more frames. It does not stop
/// `crearGrupoFavoritos` from running correctly, and reproduces identically
/// against the pre-WU3a combined screen (this widget's body is unmodified).
/// Fixing the dispose timing would be a logic edit, which this WU's
/// verbatim-move contract forbids; flagged for a future fix instead.
///
/// The one race produces a cascade of framework-internal symptoms while the
/// sheet's close animation and the disposed controller fight over the same
/// frame (an overlay `_dependents.isEmpty` assertion, and a transient
/// RenderFlex overflow against this file's bottom-sheet Column). All are
/// suppressed together as one documented, narrowly-scoped exception.
void _suppressDisposedControllerCascade() {
final original = FlutterError.onError;
FlutterError.onError = (details) {
final message = details.exceptionAsString();
final full = details.toString();
final isKnownCascade =
message.contains(
'A TextEditingController was used after being disposed',
) ||
message.contains("'_dependents.isEmpty': is not true") ||
(message.contains('RenderFlex overflowed') &&
full.contains('pantalla_ajustes_grupos_favoritos.dart'));
if (isKnownCascade) return;
original?.call(details);
};
addTearDown(() => FlutterError.onError = original);
}
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
@@ -124,7 +91,6 @@ void main() {
tester,
) async {
_suppressListTileInkAssertion();
_suppressDisposedControllerCascade();
final estado = await crearEstado();
addTearDown(estado.dispose);
@@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/pantallas/pantalla_bienvenida.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// WU17b: `PantallaBienvenida.mostrarSiProcede` wires the welcome screen
/// into the genuine first-launch flow. WU17 built and tested the screen
/// fully in isolation, but nothing in `lib/app.dart` or `lib/main.dart`
/// ever referenced it — it was unreachable. These tests pin the actual
/// gap: the screen must show on a genuine first launch and never again
/// once `ServicioBienvenida` has recorded it as seen.
Widget _appConDisparador(GlobalKey<NavigatorState> navigatorKey) {
return MaterialApp(
navigatorKey: navigatorKey,
locale: const Locale('es'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Builder(
builder:
(context) => Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () => PantallaBienvenida.mostrarSiProcede(context),
child: const Text('disparar'),
),
),
),
),
);
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
testWidgets(
'first launch (no seen flag persisted) shows the welcome screen',
(tester) async {
SharedPreferences.setMockInitialValues({});
final navigatorKey = GlobalKey<NavigatorState>();
await tester.pumpWidget(_appConDisparador(navigatorKey));
await tester.tap(find.text('disparar'));
await tester.pumpAndSettle();
expect(find.byType(PantallaBienvenida), findsOneWidget);
},
);
testWidgets(
'second launch (seen flag already persisted) does not show it again',
(tester) async {
SharedPreferences.setMockInitialValues({
'pluri_bienvenida_vista_v1': true,
});
final navigatorKey = GlobalKey<NavigatorState>();
await tester.pumpWidget(_appConDisparador(navigatorKey));
await tester.tap(find.text('disparar'));
await tester.pumpAndSettle();
expect(find.byType(PantallaBienvenida), findsNothing);
},
);
testWidgets('showing it once persists the flag so a later check in the same '
'session skips it', (tester) async {
SharedPreferences.setMockInitialValues({});
final navigatorKey = GlobalKey<NavigatorState>();
await tester.pumpWidget(_appConDisparador(navigatorKey));
await tester.tap(find.text('disparar'));
await tester.pumpAndSettle();
expect(find.byType(PantallaBienvenida), findsOneWidget);
// Dismiss it the same way the CTA does (a plain pop), simulating the
// welcome screen being resolved before the next check ever happens.
navigatorKey.currentState!.pop();
await tester.pumpAndSettle();
await tester.tap(find.text('disparar'));
await tester.pumpAndSettle();
expect(find.byType(PantallaBienvenida), findsNothing);
});
}
@@ -0,0 +1,144 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/estado/estado_navegacion.dart';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/pantallas/pantalla_bienvenida.dart';
import 'package:provider/provider.dart';
/// WU17: `onboarding-welcome` spec — full-screen route (not a modal),
/// content only (logo/headline/body/3 bullets/1 CTA), binding
/// no-monetization guard, and CTA dismisses to Escuchar with the route
/// popped.
Widget _app({required EstadoNavegacionRaiz navegacion}) {
return ChangeNotifierProvider<EstadoNavegacionRaiz>.value(
value: navegacion,
child: const MaterialApp(
locale: Locale('es'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: PantallaBienvenida(),
),
);
}
void main() {
testWidgets(
'renders as a full-screen route with a logo, headline, body copy, '
'exactly 3 feature bullets, and exactly one primary CTA',
(tester) async {
final navegacion = EstadoNavegacionRaiz();
addTearDown(navegacion.dispose);
await tester.pumpWidget(_app(navegacion: navegacion));
await tester.pumpAndSettle();
// A full-screen route, never a modal dialog.
expect(find.byType(Dialog), findsNothing);
expect(find.byType(AlertDialog), findsNothing);
expect(find.byType(Scaffold), findsOneWidget);
expect(find.byType(Image), findsOneWidget, reason: 'the logo mark');
expect(find.text('Tu mundo, en directo'), findsOneWidget);
expect(
find.textContaining('53.412 emisoras de 238 países'),
findsOneWidget,
);
expect(
find.byType(FilaCaracteristicaBienvenida),
findsNWidgets(3),
reason: 'exactly 3 feature bullets',
);
expect(
find.byType(FilledButton),
findsOneWidget,
reason: 'exactly 1 primary CTA',
);
expect(find.text('Empezar a escuchar'), findsOneWidget);
},
);
testWidgets('rendered content contains no monetization strings', (
tester,
) async {
final navegacion = EstadoNavegacionRaiz();
addTearDown(navegacion.dispose);
await tester.pumpWidget(_app(navegacion: navegacion));
await tester.pumpAndSettle();
final textoCompleto = tester
.widgetList<Text>(find.byType(Text))
.map((widget) => widget.data ?? '')
.join('\n');
expect(textoCompleto.contains('PRO'), isFalse);
expect(textoCompleto.contains(''), isFalse);
expect(textoCompleto.contains(r'$'), isFalse);
expect(
RegExp(r'\d+\s*d[ií]as?\b', caseSensitive: false).hasMatch(
textoCompleto,
),
isFalse,
reason: 'no day-count trial phrase (e.g. "14 días")',
);
expect(
find.textContaining('gratuita', findRichText: true),
findsNothing,
reason: 'no secondary "free version" link',
);
expect(
find.textContaining('gratis', findRichText: true),
findsNothing,
reason: 'no secondary "free version" link',
);
// Only one tappable action on the whole screen: the primary CTA.
expect(find.byType(TextButton), findsNothing);
expect(find.byType(FilledButton), findsOneWidget);
});
testWidgets(
'tapping the primary CTA switches to Escuchar and pops the welcome route',
(tester) async {
// Start on a DIFFERENT tab so a pass actually proves irA() ran,
// rather than coasting on EstadoNavegacionRaiz's own escuchar default.
final navegacion = EstadoNavegacionRaiz()
..irA(RaizPluriWave.ajustes);
addTearDown(navegacion.dispose);
final navigatorKey = GlobalKey<NavigatorState>();
await tester.pumpWidget(
ChangeNotifierProvider<EstadoNavegacionRaiz>.value(
value: navegacion,
child: MaterialApp(
navigatorKey: navigatorKey,
locale: const Locale('es'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const Scaffold(body: Center(child: Text('home stand-in'))),
),
),
);
unawaited(
navigatorKey.currentState!.push(
MaterialPageRoute<void>(builder: (_) => const PantallaBienvenida()),
),
);
await tester.pumpAndSettle();
expect(find.byType(PantallaBienvenida), findsOneWidget);
await tester.tap(find.text('Empezar a escuchar'));
await tester.pumpAndSettle();
expect(navegacion.actual, RaizPluriWave.escuchar);
expect(
find.byType(PantallaBienvenida),
findsNothing,
reason: 'the welcome route was removed from the back stack',
);
expect(find.text('home stand-in'), findsOneWidget);
},
);
}
@@ -0,0 +1,44 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/servicios/servicio_bienvenida.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// WU17b: `ServicioBienvenida` decides whether the first-run welcome
/// screen (`PantallaBienvenida`) is still due — same injectable-prefs,
/// versioned-key convention as `ServicioContenidoApp` (S3-R4), but a plain
/// boolean flag since this welcome is a one-time, not per-version, surface.
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('ServicioBienvenida', () {
test(
'debeMostrarBienvenida is true before it has ever been marked seen',
() async {
SharedPreferences.setMockInitialValues({});
final servicio = ServicioBienvenida();
expect(await servicio.debeMostrarBienvenida(), isTrue);
},
);
test(
'debeMostrarBienvenida is false after marcarBienvenidaVista',
() async {
SharedPreferences.setMockInitialValues({});
final servicio = ServicioBienvenida();
await servicio.marcarBienvenidaVista();
expect(await servicio.debeMostrarBienvenida(), isFalse);
},
);
test('respects a seen flag already persisted by a prior launch', () async {
SharedPreferences.setMockInitialValues({
'pluri_bienvenida_vista_v1': true,
});
final servicio = ServicioBienvenida();
expect(await servicio.debeMostrarBienvenida(), isFalse);
});
});
}
+117
View File
@@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/estado/estado_radio.dart';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/servicios/servicio_audio.dart';
import 'package:pluriwave/tema/pluriwave_tokens.dart';
import 'package:pluriwave/widgets/mini_reproductor.dart';
import 'package:provider/provider.dart';
@@ -93,4 +94,120 @@ void main() {
expect(find.byType(AlertDialog), findsNothing);
expect(find.byIcon(Icons.refresh_rounded), findsOneWidget);
});
// WU16: the offline/reconnect banner restyle. `offlineAccent` (added in
// WU1, previously unused by any screen) tints the connectivity-trouble
// sub-states so they read as visually distinct from ordinary loading.
testWidgets(
'reconnecting state tints the spinner and status label with the offline accent',
(tester) async {
final audio = FakeServicioAudio();
final estado = _estadoRadio(audio);
addTearDown(estado.dispose);
await audio.reproducir(emisoraDemo(uuid: 'r1', nombre: 'Radio Uno'));
await tester.pumpWidget(
ChangeNotifierProvider<EstadoRadio>.value(
value: estado,
child: MaterialApp(
locale: const Locale('es'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const Scaffold(body: MiniReproductor()),
),
),
);
await tester.pumpAndSettle();
audio.emitirEstado(EstadoReproduccion.reconectando);
await tester.pump();
await tester.pump();
expect(
tester
.widget<CircularProgressIndicator>(
find.byType(CircularProgressIndicator),
)
.color,
PluriWaveTokens.dark.offlineAccent,
);
expect(
tester.widget<Text>(find.text('Reconectando...')).style?.color,
PluriWaveTokens.dark.offlineAccent,
);
},
);
testWidgets(
'plain buffering keeps the default spinner colour, not the offline accent',
(tester) async {
final audio = FakeServicioAudio();
final estado = _estadoRadio(audio);
addTearDown(estado.dispose);
await audio.reproducir(emisoraDemo(uuid: 'r1', nombre: 'Radio Uno'));
await tester.pumpWidget(
ChangeNotifierProvider<EstadoRadio>.value(
value: estado,
child: MaterialApp(
locale: const Locale('es'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const Scaffold(body: MiniReproductor()),
),
),
);
await tester.pumpAndSettle();
audio.emitirEstado(EstadoReproduccion.cargando);
await tester.pump();
await tester.pump();
expect(
tester
.widget<CircularProgressIndicator>(
find.byType(CircularProgressIndicator),
)
.color,
isNull,
reason: 'cargando is not a connectivity problem, keep the default',
);
},
);
testWidgets(
'error state tints the retry icon and status label with the offline accent',
(tester) async {
final audio = FakeServicioAudio();
final estado = _estadoRadio(audio);
addTearDown(estado.dispose);
await audio.reproducir(emisoraDemo(uuid: 'r1', nombre: 'Radio Uno'));
await tester.pumpWidget(
ChangeNotifierProvider<EstadoRadio>.value(
value: estado,
child: MaterialApp(
locale: const Locale('es'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const Scaffold(body: MiniReproductor()),
),
),
);
await tester.pumpAndSettle();
audio.emitirEstado(EstadoReproduccion.error);
await tester.pump();
await tester.pump();
expect(
tester.widget<Icon>(find.byIcon(Icons.refresh_rounded)).color,
PluriWaveTokens.dark.offlineAccent,
);
expect(
tester.widget<Text>(find.text('Error de conexión')).style?.color,
PluriWaveTokens.dark.offlineAccent,
);
},
);
}