Files
pluriwave/test/l10n/identical_value_allowlist.dart
T
FreeTLab acf2ebb55f
Build & Deploy PluriWave / Análisis de código (push) Successful in 26s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 3m13s
fix: alinear permisos, paywall y grabacion con lo que la app hace de verdad
Revision previa al envio a produccion. Cada punto se verifico en el codigo
antes de tocarlo; lo que ya estaba bien se dejo como estaba.

Ubicacion: se declaraba precision fina sin usarla

El unico consumidor de ubicacion pide `LocationAccuracy.low` y se queda solo
con el codigo ISO del pais, asi que `ACCESS_FINE_LOCATION` no aportaba nada. Y
contradecia la declaracion de Seguridad de los datos ya aprobada en Play, que
dice ubicacion APROXIMADA: declarar una cosa y pedir otra es precisamente lo
que se penaliza en revision.

Verificado que los manifiestos de geolocator_android y geocoding_android no
declaran permisos propios, asi que el merge no lo reinyecta y no hace falta
`tools:node="remove"`. El plugin construye su peticion en tiempo de ejecucion a
partir de lo declarado, de modo que con COARSE pide COARSE. Sin cambio
funcional: la deteccion de pais sigue igual.

El paywall vendia Android Auto como exclusivo, y ya no lo es

La etiqueta era literalmente "Android Auto", a secas. Pero el tier gratuito
recibio una carpeta navegable con emisoras reproducibles cuando hubo que
cumplir las guias del coche, asi que esa frase dejo de ser cierta. Ahora dice
que PRO añade el catalogo completo, favoritos, mis emisoras y musica local, y
aclara que gratis tiene las destacadas. Un paywall que promete lo que el tier
gratuito ya tiene expone a reclamacion y a que se cite en revision.

Microfono: se pide al activar el visualizador, no antes

Con una explicacion previa en los 13 idiomas, en vez de aparecer sin contexto.

Grabacion: uso privado de verdad, no solo en el aviso

La pantalla de grabaciones entregaba el fichero a cualquier aplicacion con
`Share.shareXFiles`. La intencion era abrirlo en un reproductor del propio
telefono, no redistribuirlo, y una cosa es copia privada y la otra no. Ahora
usa el `openFile` que ya existia -- FileProvider + ACTION_VIEW -- y avisa
cuando ningun reproductor del dispositivo puede abrirla, en vez de fallar en
silencio. Se añade ademas el aviso de uso privado en esa pantalla.

`recordingActionShare` la usaban DOS botones con significados distintos: el de
grabaciones, que mandaba el audio, y el del reproductor, que comparte el nombre
y la url de la emisora. Una clave, dos sentidos, y esa ambiguedad basto para
que al leer el codigo pareciera que solo se compartian enlaces. Separadas en
`stationActionShare` y `recordingActionOpenIn`.

La grabacion sigue siendo PRO. Lo que reduce el riesgo es que la copia no salga
del dispositivo, no regalar la funcion: los anuncios tambien son monetizacion.

Suite completa: 1587 pasan, 2 omitidos. flutter analyze mantiene los 5 avisos
preexistentes.
2026-09-18 17:06:59 +02:00

299 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'),
// `premiumBeneficioAndroidAuto` used to live here as a bare "Android
// Auto" product name. It is now a real sentence describing what PRO adds
// in the car, so every locale translates it and no entry belongs here.
// ---------------------------------------------------------------------
// 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.
};