Google Play devolvio "Approved with Issues" en el codigo 157: "clicking on stop button makes the entire app useless", citado contra las Android for Cars App Quality Guidelines. La causa no era el boton de parar. Maquina de estados del transporte _cambiarFuente publicaba mediaItem y loading ANTES de su primer await y solo comprobaba su revision despues de que _recrearPlayer retornase. Los cambios de fuente se encolan incrementando la revision al encolar, no al ejecutar, asi que tocar una emisora, tocar otra antes de que cargue y pulsar Stop dejaba que las entradas obsoletas reescribieran loading sobre el idle que stop() acababa de publicar. Estado final: loading para siempre sobre una sesion que audio_service ya habia desactivado. Ahora la guarda de revision es la primera sentencia del metodo. pause() no invalidaba una carga en vuelo, asi que la emisora arrancaba igual despues de pulsar pausa; se revalida la intencion antes de llamar a play(). Se anade un suelo de estado que cierra cualquier loading o buffering sin carga viva, exento cuando el reproductor ya entrego audio y solo esta rebufferando, para no convertir un tunel en un error. El presupuesto hasta el primer mensaje baja a menos de diez segundos y los reintentos ya no borran el mensaje visible. Tier gratuito en el coche El arbol devolvia una unica fila no reproducible para cualquier carpeta cuando no habia premium, y un revisor con instalacion limpia siempre es tier gratuito. Ademas skipToNext, skipToPrevious, playFromSearch y playFromMediaId retornaban en silencio. La raiz gratuita pasa a ofrecer una sola carpeta con emisoras reales y reproducibles, compiladas en el binario para que existan en frio, y la puerta de entitlement acota contenido en vez de bloquear acciones. Se elimina la fila "Funcion Premium". Una consulta de voz vacia arranca la ultima emisora, que fallaba tambien a los clientes de pago. Localizacion El locale del handler solo lo fijaba un widget que el motor headless nunca construye, asi que todo error del coche salia en castellano. Se resuelve desde el locale de plataforma. Se traducen las once etiquetas del arbol que estaban a fuego y se retira la convencion que lo justificaba. Un test nuevo falla si vuelve a aparecer texto visible fuera del sistema de traduccion. Suite completa: 1455 pasan, 2 omitidos. Los mecanismos se verificaron por mutacion: borrar cada uno pone la suite en rojo. flutter analyze mantiene los 5 avisos preexistentes.
310 lines
13 KiB
Dart
310 lines
13 KiB
Dart
/// 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'),
|
|
(
|
|
'ar',
|
|
'premiumBeneficioAndroidAuto',
|
|
), // "Android Auto" -- Google product name (fix/import-alarmas-y-paywall)
|
|
('bn', 'premiumBeneficioAndroidAuto'),
|
|
('de', 'premiumBeneficioAndroidAuto'),
|
|
('fr', 'premiumBeneficioAndroidAuto'),
|
|
('hi', 'premiumBeneficioAndroidAuto'),
|
|
('id', 'premiumBeneficioAndroidAuto'),
|
|
('it', 'premiumBeneficioAndroidAuto'),
|
|
('ja', 'premiumBeneficioAndroidAuto'),
|
|
('pt', 'premiumBeneficioAndroidAuto'),
|
|
('ru', 'premiumBeneficioAndroidAuto'),
|
|
('zh', 'premiumBeneficioAndroidAuto'),
|
|
|
|
// ---------------------------------------------------------------------
|
|
// 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
|
|
(
|
|
'pt',
|
|
'alarmDiagnosticsManufacturerLabel',
|
|
), // fix/alarmas-fiabilidad new key -- "Fabricante" is identical in pt/es
|
|
(
|
|
'pt',
|
|
'desbloquearPremium',
|
|
), // iap-freemium-unlock new key -- "Desbloquear" is identical in pt/es and
|
|
// "Premium" is an untranslated product tier name in both.
|
|
(
|
|
'pt',
|
|
'restaurarCompras',
|
|
), // iap-freemium-unlock new key -- "Restaurar compras" is the standard
|
|
// Portuguese store wording and coincides with es word for word.
|
|
(
|
|
'pt',
|
|
'autoCarpetaFavoritos',
|
|
), // fix/auto-quality-guidelines car-tree label -- "Favoritos" is the same
|
|
// word in pt and es, exactly like the already-listed ('pt',
|
|
// 'favoritesTitle') above, which carries this very value.
|
|
};
|