From 694fbdc1f331905e89c15713cd87e45e6f2fd9e1 Mon Sep 17 00:00:00 2001 From: uvtqt Date: Wed, 11 Nov 2026 09:25:38 +0100 Subject: [PATCH 1/3] Translate 321 missing Spanish strings Device picker, detected activities, step calibration, trends, the month grid, advanced charts and the first-weeks card fell back to English in a Spanish app. The supplement catalogue and the WHOOP firmware warning are not part of this change. --- lib/l10n/app_es.arb | 323 +++++++++++++++++++++++++++++++- test/spanish_coverage_test.dart | 47 +++++ 2 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 test/spanish_coverage_test.dart diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 0fb047874..e0bdb1998 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -2892,5 +2892,326 @@ } }, "reportContactLabel": "Contacto (opcional)", - "reportContactHint": "Correo o Discord, solo si quieres una respuesta" + "reportContactHint": "Correo o Discord, solo si quieres una respuesta", + "alarmNextLabel": "SIGUIENTE", + "alarmScheduleGroup": "Horario semanal", + "alarmWakeTimeRowTitle": "Hora de despertar", + "devicesWhichSourceWins": "Qué fuente tiene prioridad", + "devicesWhichSourceWinsSub": "Cuando dos de tus dispositivos miden lo mismo", + "devicesResetToDefault": "Volver al orden predeterminado", + "devicesCouldNotReachCorosWatch": "No se pudo conectar. Tiene que estar cerca y no estar conectado a otra app.", + "deviceFilterAllDevices": "Todos los dispositivos", + "devicesSyncNowSubListen": "Escuchar lo que envíe ahora mismo", + "devicePickerTitle": "Conecta tus dispositivos", + "devicePickerSubtitle": "Trae lo que uses. Puedes añadir más en cualquier momento.", + "devicePickerBrowseByCategory": "Explorar por categoría", + "devicePickerPrivacyNote": "Todo se queda en este teléfono. No se envía nada a ningún sitio a menos que decidas exportarlo.", + "devicePickerSearchLabel": "Buscar dispositivos o tipos", + "devicePickerSearchHint": "Buscar dispositivos o tipos", + "devicePickerNothingNearbyTitle": "Todavía no se ha encontrado nada", + "devicePickerNothingNearbyBody": "Un dispositivo solo responde mientras está activo, puesto y sin conectar a otro teléfono o app.", + "devicePickerNearby": "Cerca", + "devicePickerScanning": "Buscando dispositivos cercanos…", + "devicePickerBlurbBand": "La banda para la que se creó esta app. WHOOP 4 o 5.", + "devicePickerBlurbRing": "Lee el anillo directamente, sin cuenta ni suscripción de Oura.", + "devicePickerBlurbPolarPmd": "Un Polar Verity Sense u OH1. Mide el tiempo entre latidos de forma óptica y lo transmite durante un entrenamiento, igual que una banda pectoral.", + "devicePickerBlurbSensor": "Una banda pectoral o un brazalete, para el tiempo entre latidos durante un entrenamiento.", + "devicePickerBlurbCoros": "Un reloj deportivo. Lee la batería y la frecuencia cardíaca en vivo; las actividades grabadas se quedan en el reloj.", + "devicePickerBlurbColmi": "Un anillo Colmi. Sincroniza frecuencia cardíaca, fases del sueño, pasos, SpO2, VFC y estrés; los valores que calcula el propio anillo se etiquetan como de Colmi. Experimental.", + "devicePickerCannotPairFromHere": "Ese dispositivo no se puede emparejar desde esta pantalla.", + "welcomeLabResultsWritten": "{n, plural, one{1 resultado de laboratorio escrito} other{{n} resultados de laboratorio escritos}}", + "readinessDetailBreakdownNoScoreNote": "Una vista más aproximada de las mismas cuatro entradas. No suman la puntuación de hoy, que falta por el motivo indicado arriba.", + "readinessDetailNightsOfTarget": "{have} de {need} noches", + "readinessDetailOutsideRange": "no, fuera de tu rango habitual", + "readinessDetailLiftingScore": "sube tu puntuación", + "readinessDetailLoweringScore": "baja tu puntuación", + "readinessDetailAskHrv": "¿Está tu VFC en su rango habitual?", + "readinessDetailAskRhr": "¿Está tu frecuencia cardíaca en reposo en su rango habitual?", + "readinessDetailAskResp": "¿Está tu frecuencia respiratoria en su rango habitual?", + "readinessDetailAskTemp": "¿Está la temperatura de tu piel en su rango habitual?", + "dayTimelineDeviceBounded": "El detalle por dispositivo solo se guarda para los días recientes. Antes del {oldest} sabemos qué dispositivo registró, no lo que dijo.", + "metricDetailUsingForX": "Usando {device} para {metric}.", + "metricDetailPreferX": "Preferir {device}", + "metricDetailHistoryKeepsSource": "Los días ya terminados conservan la fuente con la que se calcularon.", + "metricDetailDimmedCaption": "Estos son los días en los que intervino {device}. La línea es tu lectura combinada.", + "metricDetailSlotNoValueRecording": "{day}, sin valor · {who}", + "metricDetailSlotNothingRecording": "{day}, nada estaba registrando", + "metricDetailRankToday": "Hoy es más alto que el {pct}% de tus propios días.", + "metricDetailRankTodayBand": "Hoy es más alto que el {pct}% de tus propios días — {band}.", + "metricDetailRankFrom": "Tu lectura del {date} es más alta que el {pct}% de tus propios días.", + "metricDetailRankFromBand": "Tu lectura del {date} es más alta que el {pct}% de tus propios días — {band}.", + "homeSyncedThrough": "Sincronizado hasta {when}", + "homeSyncedNever": "Todavía no hay datos de la banda", + "homeBriefingTitle": "Resumen", + "homeBriefingSubtitleEmpty": "Toca para escribir el resumen de hoy", + "sleepDetailEditNaps": "Siestas", + "sleepDetailIWasAsleep": "Estaba dormido", + "sleepDetailAskEnough": "¿Dormiste lo suficiente?", + "sleepDetailAskDeep": "¿Tuviste tu sueño profundo habitual?", + "sleepDetailAskSleptThrough": "¿Dormiste durante todo el tiempo en cama?", + "sleepDetailAskOnTime": "¿Te dormiste a tu hora habitual?", + "metricHeartEffortTitle": "Esfuerzo cardíaco", + "activitySetupWorkLabel": "Trabajo", + "activitySetupRestLabel": "Descanso", + "activitySetupRoundsLabel": "Rondas", + "activityZonesAnchorManual": "Definidas por ti, no calculadas: estos cinco umbrales sustituyen a los que te habrían dado tu edad o un máximo medido.", + "activityZonesSetYourOwnLink": "Definir tus propias zonas", + "activityZonesEditYourOwnLink": "Editar tus propias zonas", + "activityZonesEditYourOwnTitle": "Definir tus propias zonas", + "activityZonesEditYourOwnBody": "Cinco umbrales en lpm, de menor a mayor: dónde empieza cada zona. Borra los cinco para volver a las zonas que calculamos para ti.", + "activityZonesNeedAllFive": "Hacen falta los cinco umbrales, de menor a mayor. No se guardó nada.", + "activityZonesMustAscend": "Cada zona debe empezar más alto que la anterior. No se guardó nada.", + "activityZonesCurrentEdgesNote": "Los rangos son los límites actuales de tus zonas; las sesiones anteriores pueden haberse contado con otros.", + "activitySummaryShareToStrava": "Compartir en Strava", + "activitySummaryLapFastest": "{time}, la más rápida", + "signalRrIntervals": "Intervalos entre latidos", + "signalHr1Hz": "Frecuencia cardíaca continua", + "signalHrSparse": "Frecuencia cardíaca", + "signalAccel1Hz": "Movimiento", + "signalAccelHighRate": "Movimiento de alta frecuencia", + "signalPpgGreen": "PPG verde", + "signalPpgRedIr": "PPG rojo/infrarrojo", + "signalSkinTempRaw": "Temperatura de la piel", + "signalVendorScalars": "Los valores propios del dispositivo", + "missingSignalRrIntervals": "sin intervalos entre latidos", + "missingSignalHr1Hz": "sin frecuencia cardíaca continua", + "missingSignalHrSparse": "sin frecuencia cardíaca", + "missingSignalAccel1Hz": "sin acelerómetro", + "missingSignalAccelHighRate": "sin acelerómetro de alta frecuencia", + "missingSignalPpgGreen": "sin PPG verde", + "missingSignalPpgRedIr": "sin PPG rojo/infrarrojo", + "missingSignalSkinTempRaw": "sin sensor de temperatura", + "missingSignalVendorScalars": "no informa de nada propio", + "missingSignalUnknown": "no puede aportar esto", + "devicesSensorLimit": "{count, plural, =1{Este teléfono empareja como máximo un sensor junto a tu banda. Quítalo para añadir otro.} other{Este teléfono empareja como máximo {count} sensores junto a tu banda. Quita uno para añadir otro.}}", + "devicesOrderNotSaved": "No se pudo guardar ese orden. No cambió nada.", + "devicesRequestNotSaved": "No se pudo guardar esa solicitud. Inténtalo de nuevo.", + "activityReviewTitle": "Actividades detectadas", + "activityPossibleNap": "Posible siesta", + "activityPossibleWorkout": "Posible entrenamiento", + "activityConfirm": "Confirmar", + "activityEdit": "Editar", + "activityDiscard": "Descartar", + "activityChangeSport": "Cambiar deporte", + "activityNotAWorkout": "No es un entrenamiento", + "activitySaveConfirm": "Guardar y confirmar", + "activityNothing": "Nada que revisar", + "activityEmptyBody": "Las posibles siestas y entrenamientos aparecerán aquí.", + "activityLoadFailed": "No se pudieron cargar las actividades. Inténtalo de nuevo.", + "activityReviewProblem": "No se pudo terminar", + "activityReview": "Revisar", + "activityLoading": "Cargando…", + "activityPending": "{count} por revisar", + "activityLengthMinutes": "{minutes} min", + "activityStart": "Inicio", + "activityEnd": "Fin", + "activityCheckTimes": "Revisa las horas", + "activityNapWindow": "Elige un intervalo pasado de entre 5 minutos y 6 horas.", + "activityEditNap": "Editar siesta", + "activityEditWorkout": "Editar entrenamiento", + "activityNapAlerts": "Siestas detectadas", + "activityPushHint": "Solo notificaciones push. Las sugerencias siguen en Actividades detectadas.", + "activityCatalogueName": "{key, select, other{{name}}}", + "exerciseCatalogueName": "{key, select, other{{name}}}", + "presentationBluetoothHeartRateSensor": "Sensor de frecuencia cardíaca Bluetooth", + "presentationGarminWatch": "Reloj Garmin", + "presentationPolarSensor": "Sensor Polar", + "presentationCorosWatch": "Reloj Coros", + "presentationCOROSWatch": "Reloj COROS", + "presentationSmartRingR11MR10M": "Anillo inteligente (R11M/R10M)", + "presentationDaFitMOYOUNGWatch": "Reloj DaFit / MOYOUNG", + "presentationWearFitBand": "Banda WearFit", + "presentationSmartRingBandLefunProtocol": "Anillo/banda inteligente (protocolo Lefun)", + "presentationHPlusHRBand": "Banda HPlus HR", + "presentationFossilSkagenHybridSmartwatch": "Reloj híbrido Fossil/Skagen", + "presentationColmiRing": "Anillo Colmi", + "presentationNoSleepWasScoredForThisDayRestingHR": "no se puntuó ningún sueño para este día — la FC en reposo solo se mide durante un periodo de sueño, nunca en horas de vigilia.", + "presentationBaevskyStressIndex0100RestingAutonomicTensionPRV": "Índice de estrés de Baevsky → 0–100; tensión autonómica en reposo (PRV).", + "presentationNoReadinessInputsPresentNeverImputed": "no hay entradas de recuperación — \"—\" (nunca se imputan).", + "presentationMETValueYourWeightRefinedByHeartRateOr": "Valor MET × tu peso, ajustado por la frecuencia cardíaca; o solo la frecuencia cardíaca, para una actividad sin MET.", + "presentationMood": "Ánimo", + "presentationSleepQuality": "Calidad del sueño", + "presentationS": "s", + "presentationV": "µV", + "presentationReadsTheRingDirectlyNoAccountNoKeyExchange": "Lee el anillo directamente, sin cuenta ni intercambio de claves.", + "presentationPebble2OrPebble2SEOnlyPairsOnly": "Solo Pebble 2 o Pebble 2 SE. Por ahora solo se empareja; todavía no se lee ni se guarda nada.", + "presentationTheOriginalFossilSkagenHybridSmartwatchNotTheNewer": "El reloj híbrido Fossil/Skagen original, no el Hybrid HR más reciente. Se empareja y se conecta; todavía no se deriva nada de él.", + "presentationABudgetActivityBandPairsAndBanksItsRaw": "Una banda de actividad económica. Se empareja y guarda sus datos en bruto, pero todavía no se deriva nada de ellos.", + "presentationPairsAndBanksItsRawDataNothingIsDecoded": "Se empareja y guarda sus datos en bruto; todavía no se decodifica nada.", + "presentationAGenericRingOrBandSoldUnderManyStorefront": "Un anillo o una banda genéricos que se venden con muchos nombres comerciales. Se empareja y se conecta; todavía no informa de nada.", + "presentationAGenericHPlusFamilyHRBandPairsAndBanks": "Una banda HR genérica de la familia HPlus. Se empareja y guarda su historial; todavía no se decodifica nada en un número.", + "presentationPairsAndBanksItsRawDataInTheBackground": "Se empareja y guarda sus datos en bruto en segundo plano, pero todavía no deriva nada de ellos.", + "presentationAnUnbrandedDaFitMOYOUNGStyleWatchPairsAndBanks": "Un reloj sin marca del tipo DaFit/MOYOUNG. Se empareja y guarda sus propios datos; todavía no se deriva nada de él.", + "presentationAnUnbrandedMakibesHR3BoardPairsAndBanksIts": "Una placa Makibes HR3 sin marca. Se empareja y guarda sus datos en bruto; todavía no se deriva nada de ellos.", + "presentationAnUnbrandedID115BoardPairsAndBanksItsRaw": "Una placa ID115 sin marca. Se empareja y guarda sus datos en bruto; todavía no se deriva nada de ellos.", + "presentationAnUnbrandedXWatchBoardPairsAndBanksItsRaw": "Una placa XWatch sin marca. Se empareja y guarda sus datos en bruto; todavía no se deriva nada de ellos.", + "presentationAnUnbrandedWatch9BoardPairsAndBanksItsRaw": "Una placa Watch9 sin marca. Se empareja y guarda sus datos en bruto; todavía no se deriva nada de ellos.", + "presentationAnSMAQ2OSSSmartwatchPairsAndBanksIts": "Un reloj inteligente SMA-Q2-OSS. Se empareja y guarda sus datos en bruto; todavía no se deriva nada de ellos.", + "presentationATLW64OrNO1F1FitnessBandPairsAnd": "Una banda de actividad TLW64 o NO1 F1. Se empareja y guarda sus datos en bruto; todavía no se deriva nada de ellos.", + "presentationCaffeine": "Cafeína", + "presentationAlcohol": "Alcohol", + "presentationMeasurementsOf": "{have} de {total} medidas", + "presentationAveragePulse": "{value} lpm de media", + "presentationSoreness": "Dolor muscular", + "presentationScreensBeforeBed": "Pantallas antes de dormir", + "presentationIn": "in", + "presentationLb": "lb", + "presentationMl": "ml", + "presentationMg": "mg", + "presentationUnits": "unidades", + "presentationWeightRepsLogged": "{weight} {unit} × {reps} registrado", + "presentationVolumeUnit": "{unit} de volumen", + "journalTagCaffeine": "cafeína", + "journalTagAlcohol": "alcohol", + "journalTagLateMeal": "cena tardía", + "journalTagStress": "estrés", + "journalTagPoorSleep": "mal sueño", + "journalTagTravel": "viaje", + "journalTagScreensLate": "pantallas tarde", + "journalTagMeds": "medicación", + "journalTagSick": "enfermo", + "journalTagSauna": "sauna", + "journalTagColdPlunge": "baño frío", + "journalTagSocial": "vida social", + "journalTagWorkout": "entrenamiento", + "journalTagRestDay": "día de descanso", + "liveHrShowingDeviceSemantics": "Mostrando {label}. Toca para cambiar de dispositivo.", + "devicesResetSourceSemantics": "Restablecer {signal} al orden predeterminado", + "metricDetailStepsGoalValue": "{count, plural, one{{value} paso} other{{value} pasos}}", + "scrubberNothingSelected": "Nada seleccionado", + "activityZonesNameWarmUp": "Calentamiento", + "activityZonesNameEasy": "Suave", + "activityZonesNameAerobic": "Aeróbico", + "activityZonesNameThreshold": "Umbral", + "activityZonesNameMaxEffort": "Esfuerzo máximo", + "findingIllnessTitle": "La frecuencia cardíaca en reposo ha estado elevada", + "findingIllnessDetail": "Las últimas noches sitúan tu frecuencia cardíaca en reposo por encima de lo habitual, por una noche muy alta o por varias ligeramente elevadas. Una sola señal: un patrón, no una causa.", + "findingLowReadinessTitle": "Recuperación baja", + "findingLowReadinessDetail": "La recuperación estuvo en su franja más baja. En conjunto, las señales de esa noche quedaron muy por debajo de lo habitual.", + "findingLowReadinessScoredDetail": "La recuperación fue de {score}, su franja más baja. En conjunto, las señales de esa noche quedaron muy por debajo de lo habitual.", + "missingSignalSteps": "sin recuento de pasos", + "missingSignalSkinTempC": "sin temperatura de la piel", + "missingSignalActivityLevel": "sin registro de actividad", + "missingSignalDeviceHrv": "sin VFC propia", + "missingSignalDeviceStages": "sin fases del sueño propias", + "missingSignalDeviceResp": "sin frecuencia respiratoria propia", + "missingSignalDeviceStress": "sin estrés propio", + "missingSignalDeviceSpo2": "sin SpO2 propia", + "signalSteps": "Pasos", + "signalSkinTempC": "Temperatura de la piel (°C)", + "signalActivityLevel": "Actividad", + "signalDeviceHrv": "La VFC propia del dispositivo", + "signalDeviceStages": "Las fases del sueño propias del dispositivo", + "signalDeviceResp": "La frecuencia respiratoria propia del dispositivo", + "signalDeviceStress": "El estrés propio del dispositivo", + "signalDeviceSpo2": "La SpO2 propia del dispositivo", + "devicePickerBlurbThermometer": "Guarda cada lectura como temperatura corporal.", + "devicePickerBlurbMiscaleBc": "Guarda cada peso estabilizado y su bioimpedancia, incluidos los almacenados.", + "devicePickerBlurbMiscale2": "Guarda cada peso estabilizado, incluidos los almacenados.", + "dayTimelineZoomNoHr": "Sin frecuencia cardíaca en este tramo", + "dayTimelineZoomPerMinute": "Promedios de 1 minuto. Arrastra los tiradores para ampliar; con dos horas o menos se muestra cada segundo mientras se conserve.", + "dayTimelineZoomPerMinuteOnly": "Promedios de 1 minuto. Las lecturas segundo a segundo ya no se conservan para este tramo.", + "dayTimelineZoomPerSecond": "Cada segundo que registró la banda. Las lecturas segundo a segundo solo se conservan para los días recientes.", + "dataExportHeartRate": "Exportar la frecuencia cardíaca, minuto a minuto", + "dataExportHeartRateSub": "Un CSV de los días que elijas, con un promedio de 1 minuto por cada minuto registrado. Los datos segundo a segundo están en la exportación de la base de datos", + "dataHeartRateRangeHelp": "La frecuencia cardíaca se conserva del {first} al {last}", + "trendNormalForYou": "Normal para ti", + "trendAboveUsual": "Por encima de lo habitual", + "trendBelowUsual": "Por debajo de lo habitual", + "trendVsUsual": "{diff} respecto a tu valor habitual de {usual}", + "trendUsualRange": "Rango habitual {lo}–{hi}{unit}", + "trendNeedsDays": "Tu rango habitual aparece después de {need} días. Llevas {have}.", + "monthGridLegendBetter": "Mejor de lo habitual", + "monthGridLegendUsual": "Habitual para ti", + "monthGridLegendWorse": "Peor de lo habitual", + "monthGridLegendOutside": "Fuera de tu rango habitual", + "monthGridRangeFootnote": "Una celda por día. Una barra arriba significa por encima de tu rango habitual; abajo, por debajo. Una celda con solo el contorno no tiene valor.", + "monthGridNoRangeTitle": "{title}: todavía sin rango habitual", + "monthGridFlatBody": "Todos tus días han sido más o menos iguales, así que todavía no hay un rango habitual con el que comparar un día.", + "monthGridRowSemantics": "{title}: {have} de {total} días tienen valor. {below} por debajo y {above} por encima de tu rango habitual.", + "zoneRowsRange": "{lo}–{hi} lpm", + "zoneRowsRangeTop": "{lo}+ lpm", + "zoneRowsMinutes": "{minutes} min", + "advancedTitle": "Gráficas avanzadas", + "advancedIntro": "Vistas de investigación de los mismos datos. Nada de esto cambia tus puntuaciones.", + "advancedBeatsTitle": "Latido a latido", + "advancedBeatsSub": "Cada latido frente al anterior, la VFC a lo largo de la noche, la capacidad de desaceleración", + "advancedRhythmTitle": "Horario de sueño y ritmo diario", + "advancedRhythmSub": "El sueño hora a hora, la variabilidad en reposo, la forma prevista de hoy", + "healthAdvancedLinkTitle": "Gráficas avanzadas", + "healthAdvancedLinkSub": "Detalle latido a latido y horario de sueño, para los curiosos", + "profileBandBattery": "Banda {pct}%", + "profileExportRow": "Exportación y copia de seguridad", + "profileExportRowSub": "Hojas de cálculo, una copia completa e importación del historial", + "profileSettingsRow": "Ajustes", + "profileSettingsRowSub": "Banda, app, datos, privacidad y avanzado", + "sleepTabPlanTitle": "Tu plan de sueño", + "sleepTabMoreTitle": "Alarma y reloj biológico", + "healthWellbeingTitle": "Mente, hábitos y medicación", + "healthWellbeingMindSub": "Respiración, ánimo y el diario", + "healthWellbeingHabitsSub": "Hábitos diarios, contados como días cumplidos", + "healthWellbeingMedsSub": "Las dosis de hoy y la última semana", + "healthWellbeingCycleSub": "Registra tu periodo y mira en qué punto del ciclo estás", + "activityStepsFoodTitle": "Pasos y comida", + "activityStepsSub": "Hoy y todos los días anteriores", + "activityNutritionSub": "Registra la comida y mira la proteína de hoy", + "homeBackToToday": "Volver a hoy", + "homeSomeDataMissingTitle": "Faltan algunos datos de hoy", + "homeFirstWeekTitle": "Tus primeras semanas", + "homeFirstWeekWear": "Usa la banda esta noche", + "homeFirstWeekSleep": "Sueño: la mañana después de tu primera noche", + "homeFirstWeekStrain": "Esfuerzo: a partir del día {day}", + "homeFirstWeekRecovery": "Recuperación: a partir de la noche {night}, cuando conozca tu normalidad", + "homeFirstWeekSoFar": "{count, plural, =1{1 día registrado hasta ahora} other{{count} días registrados hasta ahora}}", + "homeFirstWeekDone": "Hecho: {step}", + "homeCoachSetUpChip": "Haz preguntas sobre tus datos: configura el coach", + "homeRecoverySleeping": "Durmiendo…", + "homeRecoveryAfterWake": "La recuperación se puntúa cuando te despiertas.", + "homeRecoveryFinishingUp": "Terminando", + "homeRecoveryUpdated": "Actualizado {time} · {from} → {to}", + "dataRecoveryChanged": "La recuperación cambió: {list}", + "homeRecoveryStartsIn": "{count, plural, =1{La recuperación empieza en 1 noche} other{La recuperación empieza en {count} noches}}", + "healthWellbeingAllSub": "Respiración, ánimo, el diario, hábitos y medicación", + "healthWellbeingAllSubCycle": "Respiración, ánimo, el diario, hábitos, medicación y ciclo", + "settingsStepCalibration": "Calibración de pasos", + "settingsStepCalibrationSub": "Aprende cuánto cuenta tu banda de más o de menos al caminar y lo corrige en el teléfono. No se escribe nada en la banda y nada sale del dispositivo", + "stepCalTitle": "Calibración de pasos", + "stepCalIntro": "Tu banda cuenta los pasos con su propio sensor de movimiento, y cuánto se desvía depende de ti. Estas rutinas lo miden. Nada sale de tu teléfono.", + "stepCalSlowTitle": "Caminata lenta", + "stepCalSlowSub": "camina despacio, con el teléfono en el bolsillo", + "stepCalSlowGuide": "Camina despacio y a ritmo constante, con la banda ajustada. Cuenta tú 100 pasos.", + "stepCalBriskTitle": "Caminata rápida", + "stepCalBriskSub": "camina a tu ritmo habitual", + "stepCalBriskGuide": "Camina a tu ritmo normal. Cuenta tú 100 pasos.", + "stepCalJogTitle": "Trote", + "stepCalJogSub": "trota a ritmo constante", + "stepCalJogGuide": "Trota a ritmo constante. Cuenta tú 100 pasos.", + "stepCalEverydayTitle": "Por casa", + "stepCalEverydaySub": "ordena durante tres minutos, con el teléfono en el bolsillo", + "stepCalEverydayGuide": "Haz cosas normales por casa (llevar, levantar, ordenar) con el teléfono en el bolsillo. Tres minutos. No camines de un lado a otro a propósito.", + "stepCalCalibrated": "Calibrado — contado {n} veces", + "stepCalNotCalibrated": "Todavía sin calibrar", + "stepCalBandSeen": "{count, plural, one{paso visto por la banda} other{pasos vistos por la banda}}", + "stepCalCountYourself": "Cuenta tus propios pasos. Para cuando llegues a {target}.", + "stepCalElapsed": "Sigue: esta va por tiempo.", + "stepCalHowManyCounted": "¿Cuántos pasos contaste?", + "stepCalFinish": "Listo", + "stepCalCancel": "Cancelar", + "stepCalKeepWalking": "Todavía no se ha registrado nada: empieza a caminar con la banda ajustada.", + "stepCalKeepMoving": "Todavía no se ha registrado nada: lleva el teléfono encima y sigue moviéndote.", + "stepCalNoSignal": "La banda no registró nada durante esa rutina.", + "stepCalNoPhoneAccess": "Esta rutina necesita el contador de pasos de tu teléfono y no se concedió el acceso. Ese contador es la referencia para el movimiento en casa.", + "stepCalPhoneNotReady": "El contador de pasos de tu teléfono no respondió. Inténtalo de nuevo en un momento.", + "stepCalNoPhone": "Tu teléfono no contó ningún paso mientras lo hacías. La próxima vez llévalo en el bolsillo: es la referencia para el movimiento en casa.", + "stepCalBandQuiet": "La banda casi no detectó movimiento. Conviene comprobar que la llevas ajustada y que está transmitiendo.", + "stepCalTooShort": "Esa rutina fue demasiado corta para medir. Camina el recuento completo.", + "stepCalMismatch": "La banda contó muchos más pasos que tú. Estaba captando algo que no era caminar.", + "stepCalWrongSource": "Esa rutina usó la referencia equivocada.", + "stepCalBadData": "Esa rutina produjo datos del sensor ilegibles.", + "stepCalRatioOutOfRange": "Tu recuento y el de la banda están demasiado lejos para usarlos. Repite la rutina y camina el recuento completo.", + "stepCalCadenceOutOfRange": "La banda no encontró un ritmo de caminata en esa rutina. Empieza a caminar justo después de pulsar inicio y mantén un ritmo constante.", + "stepCalRoutineTooShort": "Esa rutina terminó antes de que acabara el temporizador. Sigue hasta que se complete.", + "stepCalSaveFailed": "No se pudo guardar esa calibración.", + "stepCalDoneGait": "Guardado. Contaste {count} pasos y la banda vio {band}. A partir de ahora, este tipo de caminata se corrige un {pct}%.", + "stepCalDoneHousehold": "Guardado. Tu teléfono contó {count} de los {band} pasos que vio la banda. El movimiento en casa ahora se escala a {gain}." } diff --git a/test/spanish_coverage_test.dart b/test/spanish_coverage_test.dart new file mode 100644 index 000000000..49af4ef35 --- /dev/null +++ b/test/spanish_coverage_test.dart @@ -0,0 +1,47 @@ +// Spanish was missing 321 strings outside the supplement catalogue, so whole +// screens (device picker, detected activities, step calibration, trends) fell +// back to English on an otherwise Spanish app. +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:openstrap_edge/l10n/app_localizations.dart'; + +void main() { + final es = lookupAppLocalizations(const Locale('es')); + final en = lookupAppLocalizations(const Locale('en')); + + test('spanish has every english string outside the two known gaps', () { + Map arb(String code) => + jsonDecode(File('lib/l10n/app_$code.arb').readAsStringSync()) + as Map; + final spanish = arb('es'); + final missing = [ + for (final k in arb('en').keys) + if (!k.startsWith('@') && !spanish.containsKey(k)) k, + ]; + // Still to do: the supplement catalogue. The WHOOP firmware warning is + // added by its own change. + missing.removeWhere((k) => + k.startsWith('supplement') || k.startsWith('whoopFirmwareWarning')); + expect(missing, isEmpty); + }); + + test('the newly covered screens read in spanish', () { + expect(es.devicePickerTitle, 'Conecta tus dispositivos'); + expect(es.activityReviewTitle, 'Actividades detectadas'); + expect(es.stepCalTitle, 'Calibración de pasos'); + expect(es.stepCalTitle, isNot(en.stepCalTitle)); + }); + + test('counts agree with their noun', () { + expect(es.stepCalBandSeen(1), 'paso visto por la banda'); + expect(es.stepCalBandSeen(5), 'pasos vistos por la banda'); + expect(es.homeFirstWeekSoFar(1), '1 día registrado hasta ahora'); + expect(es.homeFirstWeekSoFar(3), '3 días registrados hasta ahora'); + expect(es.welcomeLabResultsWritten(1), '1 resultado de laboratorio escrito'); + expect(es.devicesSensorLimit(2), contains('2 sensores')); + }); +} From 8773625958fdb391761dad029e6d838077fd6ff7 Mon Sep 17 00:00:00 2001 From: uvtqt Date: Wed, 11 Nov 2026 13:15:48 +0100 Subject: [PATCH 2/3] Spanish: name the built-in activities and groups activityCatalogueName passed every name through unchanged, so the activity picker stayed English. Custom activities still pass through. --- lib/l10n/app_es.arb | 2 +- test/spanish_coverage_test.dart | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index e0bdb1998..fded97d29 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -3011,7 +3011,7 @@ "activityEditWorkout": "Editar entrenamiento", "activityNapAlerts": "Siestas detectadas", "activityPushHint": "Solo notificaciones push. Las sugerencias siguen en Actividades detectadas.", - "activityCatalogueName": "{key, select, other{{name}}}", + "activityCatalogueName": "{key, select, kcardio{Cardio} kstrength{Fuerza} ksports{Deportes} kathletics{Atletismo} koutdoor{Aire libre} kmindbody{Cuerpo y mente} keveryday{Día a día} kother{Otros} krunning{Carrera} ktrailrunning{Trail running} kwalking{Caminata} khiking{Senderismo} kcycling{Ciclismo} kindoorbike{Bicicleta estática} krowing{Remo} kswimming{Natación} kelliptical{Elíptica} kstairclimber{Escaladora} ktreadmill{Cinta de correr} kjumprope{Saltar la cuerda} kweighttraining{Entrenamiento con pesas} kpowerlifting{Powerlifting} kbodyweight{Peso corporal} kcalisthenics{Calistenia} kkettlebell{Kettlebell} kcrossfit{CrossFit} khiit{HIIT} kcircuittraining{Entrenamiento en circuito} kfunctional{Entrenamiento funcional} kfootball{Fútbol} kbasketball{Baloncesto} kcricket{Críquet} ktennis{Tenis} kbadminton{Bádminton} ktabletennis{Tenis de mesa} ksquash{Squash} kpadel{Pádel} kvolleyball{Voleibol} khockey{Hockey} kbaseball{Béisbol} krugby{Rugby} kgolf{Golf} kbowling{Bolos} kboxing{Boxeo} kmartialarts{Artes marciales} kwrestling{Lucha} kclimbing{Escalada} ksprinting{Esprint} ktrackintervals{Series en pista} kcrosscountry{Campo a través} khurdles{Vallas} klongjump{Salto de longitud} kshotput{Lanzamiento de peso} kjavelin{Lanzamiento de jabalina} kpolevault{Salto con pértiga} kmountainbiking{Ciclismo de montaña} kkayaking{Kayak} ksurfing{Surf} kpaddleboard{Paddle surf} kskiing{Esquí} ksnowboarding{Snowboard} kskating{Patinaje} kskateboarding{Skateboarding} khorseriding{Equitación} kyoga{Yoga} kpilates{Pilates} kstretching{Estiramientos} kmobility{Movilidad} ktaichi{Taichí} kbreathwork{Ejercicios de respiración} kmeditation{Meditación} khousework{Tareas del hogar} kgardening{Jardinería} kdogwalking{Pasear al perro} kchildcare{Cuidado de niños} kdiy{Bricolaje} kshopping{Compras} kstairs{Escaleras} kgeneralworkout{Entrenamiento general} kdancing{Baile} kintimacy{Intimidad} kphysiotherapy{Fisioterapia} ksauna{Sauna} kcoldplunge{Baño frío} other{{name}}}", "exerciseCatalogueName": "{key, select, other{{name}}}", "presentationBluetoothHeartRateSensor": "Sensor de frecuencia cardíaca Bluetooth", "presentationGarminWatch": "Reloj Garmin", diff --git a/test/spanish_coverage_test.dart b/test/spanish_coverage_test.dart index 49af4ef35..49af7cf3e 100644 --- a/test/spanish_coverage_test.dart +++ b/test/spanish_coverage_test.dart @@ -36,6 +36,13 @@ void main() { expect(es.stepCalTitle, isNot(en.stepCalTitle)); }); + test('built-in activities are named in spanish, custom ones pass through', + () { + expect(es.activityCatalogueName('krunning', 'Running'), 'Carrera'); + expect(es.activityCatalogueName('kcardio', 'Cardio'), 'Cardio'); + expect(es.activityCatalogueName('custom', 'Mi deporte'), 'Mi deporte'); + }); + test('counts agree with their noun', () { expect(es.stepCalBandSeen(1), 'paso visto por la banda'); expect(es.stepCalBandSeen(5), 'pasos vistos por la banda'); From 180bcd1ae4895152fcf90e37c51b7ba6a902feff Mon Sep 17 00:00:00 2001 From: abdulsaheel Date: Fri, 13 Nov 2026 10:50:58 +0530 Subject: [PATCH 3/3] es: nap window is 3 hours like the app enforces, step calibration says when to stop --- lib/l10n/app_es.arb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index ecf2f58ee..09007a1fa 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -3034,7 +3034,7 @@ "activityStart": "Inicio", "activityEnd": "Fin", "activityCheckTimes": "Revisa las horas", - "activityNapWindow": "Elige un intervalo pasado de entre 5 minutos y 6 horas.", + "activityNapWindow": "Elige un intervalo pasado de entre 5 minutos y 3 horas.", "activityEditNap": "Editar siesta", "activityEditWorkout": "Editar entrenamiento", "activityNapAlerts": "Siestas detectadas", @@ -3220,7 +3220,7 @@ "stepCalCalibrated": "Calibrado — contado {n} veces", "stepCalNotCalibrated": "Todavía sin calibrar", "stepCalBandSeen": "{count, plural, one{paso visto por la banda} other{pasos vistos por la banda}}", - "stepCalCountYourself": "Cuenta tus propios pasos. Para cuando llegues a {target}.", + "stepCalCountYourself": "Cuenta tus propios pasos. Detente al llegar a {target}.", "stepCalElapsed": "Sigue: esta va por tiempo.", "stepCalHowManyCounted": "¿Cuántos pasos contaste?", "stepCalFinish": "Listo",