From 370b2f9c145d22374291b927b1e267b7970869b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 16 Jun 2026 23:51:51 +0200 Subject: [PATCH] chore(cleanup): prune dead i18n keys, fix knip glob, scope Rest Mode to window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove 18 unreferenced i18n subtrees (75 leaves per locale, 450 strings across de/en/es/fr/it/pl) left behind by the settings D0-D8 reshuffle and the medication advanced/erweitert naming drift: settings.ai.disableCoach (superseded by settings.coach.activate), doctorReport.dialog/sections, the dead settings.sections export.hero / export.cards.doctorReport / layout.medications / layout.mood / integrations.tabs subtrees and the orphaned integrations connections/channels blurbs, the medications detail.advanced.title/group + detail.edit duplicates, insights.recovery .scoreLink, insights.range, targets.relativeDay (duplicate of targets.consistency.relativeDay), and insights.subPage.primary / lastMeasurement. Each subtree confirmed unused; the i18n coverage, locale-integrity, and sections-parity suites stay green. The per-slug settings.sections..description keys are kept — they back the SectionPlaceholder fallback and the parity test asserts them. Point the stale knip entry at src/proxy.ts (renamed from middleware.ts) and drop the no-match src/lib/health glob. Resolve the analytics Rest Mode annotation as of the scored window: thread a single scoredAt instant into both the health-score computation and resolveRestMode, instead of letting the annotation read a later now(). --- knip.json | 3 +- messages/de.json | 115 +-------------------------------- messages/en.json | 115 +-------------------------------- messages/es.json | 115 +-------------------------------- messages/fr.json | 115 +-------------------------------- messages/it.json | 115 +-------------------------------- messages/pl.json | 115 +-------------------------------- src/app/api/analytics/route.ts | 9 ++- 8 files changed, 14 insertions(+), 688 deletions(-) diff --git a/knip.json b/knip.json index 367c93737..3b6200073 100644 --- a/knip.json +++ b/knip.json @@ -17,10 +17,9 @@ "src/app/**/robots.ts", "src/app/**/sitemap.ts", "src/app/**/default.tsx", - "src/middleware.ts", + "src/proxy.ts", "src/instrumentation.ts", "src/lib/jobs/**/*.ts", - "src/lib/health/**/*.ts", "scripts/**/*.ts", "scripts/**/*.mjs", "e2e/**/*.spec.ts", diff --git a/messages/de.json b/messages/de.json index 9da6a04a9..35477aa3c 100644 --- a/messages/de.json +++ b/messages/de.json @@ -123,37 +123,6 @@ "glucoseBelowTarget": "unter Ziel", "glucoseAboveTarget": "über Ziel", "practiceLabel": "Praxis", - "dialog": { - "title": "Arztbericht (PDF)", - "description": "Lege den Auswertungszeitraum fest und (optional) den Praxisnamen — beide erscheinen auf dem Deckblatt.", - "startLabel": "Von", - "endLabel": "Bis", - "practiceLabel": "Praxis / Klinik (optional)", - "practicePlaceholder": "z. B. Dr. Müller — Hausarztpraxis", - "preset90d": "Letzte 90 Tage", - "preset180d": "Letzte 6 Monate", - "preset365d": "Letzte 12 Monate", - "submit": "PDF erstellen", - "cancel": "Abbrechen", - "errorEndBeforeStart": "Das Ende-Datum darf nicht vor dem Start liegen.", - "errorRangeTooLong": "Der Auswertungszeitraum darf maximal 2 Jahre umfassen.", - "errorInvalidDate": "Bitte wähle ein gültiges Start- und Ende-Datum." - }, - "sections": { - "title": "Inhalte auswählen", - "bp": "Blutdruck", - "weight": "Gewicht", - "pulse": "Ruhepuls", - "bmi": "Body-Mass-Index (BMI)", - "mood": "Stimmung", - "moodSensitive": "Sensibel — standardmäßig aus.", - "compliance": "Therapietreue", - "sleep": "Schlafdauer", - "cycle": "Menstruationszyklus", - "empty": "Für den gewählten Zeitraum liegen noch keine Daten vor. Der Bericht enthält dennoch das Deckblatt.", - "unavailableHint": "Keine Daten in diesem Zeitraum", - "labs": "Laborwerte" - }, "insurer": "Krankenkasse", "insuranceNumber": "Versichertennr.", "summaryTitle": "Klinische Zusammenfassung", @@ -1640,17 +1609,7 @@ "copyToken": "Token kopieren", "mintedHint": "Token jetzt kopieren — es wird nur einmal angezeigt." }, - "edit": { - "planOption": "Plan bearbeiten", - "advancedOption": "Erweiterte Einstellungen" - }, "advanced": { - "title": "Erweiterte Einstellungen", - "group": { - "data": "Daten", - "lifecycle": "Lebenszyklus", - "externalApi": "Externe API" - }, "dataPortability": { "import": { "title": "Einnahmen importieren", @@ -2196,23 +2155,6 @@ "confidenceLow": "Geringe Konfidenz", "pendingLabel": "Trend-Kommentar wird generiert" }, - "range": { - "label": "Zeitraum", - "noPriorData": "keine Daten im Vorzeitraum", - "option": { - "7d": "7T", - "30d": "30T", - "90d": "90T", - "1y": "1J" - }, - "priorLabel": { - "7d": "ggü. vorherigen 7T", - "30d": "ggü. vorherigen 30T", - "90d": "ggü. vorherigen 90T", - "1y": "ggü. Vorjahr" - }, - "noChange": "keine Änderung" - }, "correlationRow": { "title": "Zusammenhänge", "disclaimer": "Zusammenhänge sind beobachtend, nicht kausal — sprich mit deinem Arzt, wenn der Blutdruck hoch ist.", @@ -2554,14 +2496,6 @@ "target": { "adjustLink": "Zielbereich anpassen" }, - "primary": { - "avg30Label": "30-Tage-Durchschnitt", - "inRangeLabel": "Im Zielbereich", - "inRangeAria": "{pct}% der erfassten Tage im Zielbereich" - }, - "lastMeasurement": { - "label": "Letzte Messung" - }, "explainer": { "trigger": "Was ist {title}?", "bloodPressureTitle": "Blutdruck", @@ -3649,11 +3583,6 @@ "title": "Herz & Energie pro Tag", "subtitle": "Herz- und Energiewerte des ganzen Tages" }, - "scoreLink": { - "title": "Erholungs-Score", - "subtitle": "Sieh dir an, wie sich der nächtliche Score zusammensetzt", - "cta": "Ansehen" - }, "block": { "ansChargeExplainer": "Wie aufgeladen dein autonomes Nervensystem in den Tag startet – ein Recharge-Wert aus deinen nächtlichen Herz- und Atemsignalen.", "dayStrainExplainer": "Die kardiovaskuläre Tagesbelastung auf der Belastungsskala deines Geräts. Lies sie im Verhältnis zu deiner Erholung, nicht als Ziel für sich.", @@ -3896,18 +3825,7 @@ }, "integrations": { "title": "Integrationen", - "description": "Verbundene Dienste, Zustellkanäle und die Quellen-Priorität an einem Ort.", - "tabs": { - "connections": "Verbindungen", - "channels": "Kanäle", - "sources": "Quellen" - }, - "connections": { - "description": "Withings, WHOOP, Fitbit, Polar, Oura und Nightscout verbinden." - }, - "channels": { - "description": "Telegram, ntfy, Web-Push, Webhook und E-Mail – die Wege, über die Benachrichtigungen zugestellt werden." - } + "description": "Verbundene Dienste, Zustellkanäle und die Quellen-Priorität an einem Ort." }, "notifications": { "title": "Benachrichtigungen", @@ -3927,14 +3845,6 @@ "insights": { "title": "Insights", "description": "Reihenfolge der Übersicht und der Navigations-Pills." - }, - "medications": { - "title": "Medikamente", - "description": "Listenansicht und Reihenfolge der Medikamente." - }, - "mood": { - "title": "Stimmungs-Tags", - "description": "Gruppen, Tags und Reihenfolge der Tag-Auswahl." } }, "dashboard": { @@ -4021,12 +3931,6 @@ "description": "Lade deine Daten als CSV oder vollständiges JSON-Backup herunter — oder importiere aus Apple Health, JSON oder CSV. Den Arztbericht findest du jetzt unter Gesundheitsakte.", "otherOptionsHeading": "Weitere Export-Optionen", "downloadFailed": "Der Export konnte nicht heruntergeladen werden. Bitte versuche es erneut.", - "hero": { - "eyebrow": "Arzttermin", - "valueStatement": "Ein druckbarer PDF-Bericht für den Arzttermin — Vitalwerte, BMI, Blutdruck-Klassifikation, Therapietreue und (optional) Stimmungsverlauf, alles auf wenigen Seiten.", - "cta": "Bericht konfigurieren & generieren", - "formatHint": "PDF · druckfertig" - }, "actions": { "download": "Herunterladen" }, @@ -4035,10 +3939,6 @@ "until": "Bis" }, "cards": { - "doctorReport": { - "title": "Arztbericht", - "description": "Druckbares PDF für den Arzttermin — Vitalwerte, BMI, BD-Klassifikation, Therapietreue, Stimmung." - }, "measurementsCsv": { "title": "Messwerte", "description": "Gewicht, Blutdruck, Puls, Glukose und alle weiteren Messungen als CSV-Datei." @@ -4416,14 +4316,6 @@ "clearedToast": "{count} gespeicherte Angaben vergessen.", "clearError": "Konnte die gespeicherten Angaben nicht löschen. Bitte erneut versuchen." }, - "disableCoach": { - "title": "Coach ausblenden", - "description": "Versteckt den Coach-Button und das Coach-Panel auf allen Seiten.", - "toggleAria": "Coach-Button und Coach-Panel ausblenden", - "savedHidden": "Coach ist ausgeblendet.", - "savedShown": "Coach ist sichtbar.", - "saveError": "Speichern fehlgeschlagen. Bitte erneut versuchen." - }, "aboutMe": { "title": "Über mich", "description": "Persönlicher Kontext, den Coach und Tagesbriefing zusätzlich zu deinen Daten lesen.", @@ -5907,11 +5799,6 @@ "sixDaysAgo": "vor 6 Tagen" } }, - "relativeDay": { - "today": "heute", - "yesterday": "gestern", - "daysAgo": "vor {count} Tagen" - }, "edit": { "openLabel": "Zielbereich für {metric} bearbeiten", "title": "Zielbereich bearbeiten — {metric}", diff --git a/messages/en.json b/messages/en.json index 7bf65e8ab..b651f7a98 100644 --- a/messages/en.json +++ b/messages/en.json @@ -123,37 +123,6 @@ "glucoseBelowTarget": "below target", "glucoseAboveTarget": "above target", "practiceLabel": "Practice", - "dialog": { - "title": "Doctor report (PDF)", - "description": "Pick the reporting period and (optionally) the practice name; both are printed on the cover.", - "startLabel": "Start date", - "endLabel": "End date", - "practiceLabel": "Practice / clinic name (optional)", - "practicePlaceholder": "e.g. Dr. Müller — Family practice", - "preset90d": "Last 90 days", - "preset180d": "Last 6 months", - "preset365d": "Last 12 months", - "submit": "Generate PDF", - "cancel": "Cancel", - "errorEndBeforeStart": "End date must be on or after the start date.", - "errorRangeTooLong": "The reporting period cannot exceed 2 years.", - "errorInvalidDate": "Please select a valid start and end date." - }, - "sections": { - "title": "Choose sections", - "bp": "Blood pressure", - "weight": "Weight", - "pulse": "Resting pulse", - "bmi": "Body Mass Index (BMI)", - "mood": "Mood", - "moodSensitive": "Sensitive — off by default.", - "compliance": "Medication adherence", - "sleep": "Sleep duration", - "cycle": "Menstrual cycle", - "empty": "No data was recorded in this date range yet. The report will still include the cover page.", - "unavailableHint": "No data in this period", - "labs": "Lab results" - }, "insurer": "Health insurer", "insuranceNumber": "Insurance no.", "summaryTitle": "Clinical summary", @@ -1640,17 +1609,7 @@ "copyToken": "Copy token", "mintedHint": "Copy this token now — it is shown only once." }, - "edit": { - "planOption": "Edit plan", - "advancedOption": "Advanced settings" - }, "advanced": { - "title": "Advanced settings", - "group": { - "data": "Data", - "lifecycle": "Lifecycle", - "externalApi": "External API" - }, "dataPortability": { "import": { "title": "Import intakes", @@ -2196,23 +2155,6 @@ "confidenceLow": "Low confidence", "pendingLabel": "Generating trend commentary" }, - "range": { - "label": "Time range", - "noPriorData": "no prior-period data", - "option": { - "7d": "7d", - "30d": "30d", - "90d": "90d", - "1y": "1y" - }, - "priorLabel": { - "7d": "vs prior 7d", - "30d": "vs prior 30d", - "90d": "vs prior 90d", - "1y": "vs prior year" - }, - "noChange": "no change" - }, "correlationRow": { "title": "Relationships", "disclaimer": "Relationships are observational, not causal — talk to your doctor if BP runs high.", @@ -2554,14 +2496,6 @@ "target": { "adjustLink": "Adjust target range" }, - "primary": { - "avg30Label": "30-day average", - "inRangeLabel": "In range", - "inRangeAria": "{pct}% of logged days within target" - }, - "lastMeasurement": { - "label": "Last measurement" - }, "explainer": { "trigger": "What is {title}?", "bloodPressureTitle": "Blood pressure", @@ -3649,11 +3583,6 @@ "title": "Daily heart & energy", "subtitle": "Whole-day cardiac and energy totals" }, - "scoreLink": { - "title": "Recovery score", - "subtitle": "See how the nightly score breaks down", - "cta": "View" - }, "block": { "ansChargeExplainer": "How charged your autonomic nervous system is for the day ahead — a recharge reading drawn from your overnight heart and breathing signals.", "dayStrainExplainer": "The day's whole-body cardiovascular load on your device's strain scale. Read it against your recovery, not as a goal on its own.", @@ -3896,18 +3825,7 @@ }, "integrations": { "title": "Integrations", - "description": "Connected services, delivery channels, and source priority in one place.", - "tabs": { - "connections": "Connections", - "channels": "Channels", - "sources": "Sources" - }, - "connections": { - "description": "Connect Withings, WHOOP, Fitbit, Polar, Oura, and Nightscout." - }, - "channels": { - "description": "Telegram, ntfy, Web Push, Webhook, and Email — the routes notifications are delivered through." - } + "description": "Connected services, delivery channels, and source priority in one place." }, "notifications": { "title": "Notifications", @@ -3927,14 +3845,6 @@ "insights": { "title": "Insights", "description": "Overview sections and navigation-pill order." - }, - "medications": { - "title": "Medications", - "description": "List view and medication order." - }, - "mood": { - "title": "Mood tags", - "description": "Groups, tags, and order of the mood picker." } }, "dashboard": { @@ -4021,12 +3931,6 @@ "description": "Download your data as CSV or a full JSON backup — or import from Apple Health, JSON, or CSV. The doctor report now lives under Health Record.", "otherOptionsHeading": "Other export options", "downloadFailed": "Couldn't download the export. Please try again.", - "hero": { - "eyebrow": "Doctor visit", - "valueStatement": "A printable PDF report for your doctor's appointment — vitals, BMI, blood-pressure classification, medication adherence and (optionally) mood, all on a few pages.", - "cta": "Configure & generate report", - "formatHint": "PDF · print-ready" - }, "actions": { "download": "Download" }, @@ -4035,10 +3939,6 @@ "until": "Until" }, "cards": { - "doctorReport": { - "title": "Doctor Report", - "description": "Printable PDF for the doctor's appointment — vitals, BMI, BP classification, medication adherence, mood." - }, "measurementsCsv": { "title": "Measurements", "description": "Weight, blood pressure, pulse, glucose, and every other measurement as comma-separated values." @@ -4416,14 +4316,6 @@ "clearedToast": "Forgot {count} stored facts.", "clearError": "Could not clear the stored facts. Please try again." }, - "disableCoach": { - "title": "Hide Coach", - "description": "Hides the Coach button and drawer everywhere.", - "toggleAria": "Hide the Coach button and drawer", - "savedHidden": "Coach hidden.", - "savedShown": "Coach visible.", - "saveError": "Could not save the change. Please try again." - }, "aboutMe": { "title": "About me", "description": "Personal context the Coach and the daily briefing read alongside your data.", @@ -5907,11 +5799,6 @@ "sixDaysAgo": "6 days ago" } }, - "relativeDay": { - "today": "today", - "yesterday": "yesterday", - "daysAgo": "{count} days ago" - }, "edit": { "openLabel": "Edit target range for {metric}", "title": "Edit target range — {metric}", diff --git a/messages/es.json b/messages/es.json index 778c3ce36..53f0e1d22 100644 --- a/messages/es.json +++ b/messages/es.json @@ -123,37 +123,6 @@ "glucoseBelowTarget": "por debajo del objetivo", "glucoseAboveTarget": "por encima del objetivo", "practiceLabel": "Consulta", - "dialog": { - "title": "Informe médico (PDF)", - "description": "Define el periodo de análisis y (opcional) el nombre de la consulta — ambos aparecen en la portada.", - "startLabel": "Desde", - "endLabel": "Hasta", - "practiceLabel": "Consulta / clínica (opcional)", - "practicePlaceholder": "p. ej. Dr. Müller — Medicina familiar", - "preset90d": "Últimos 90 días", - "preset180d": "Últimos 6 meses", - "preset365d": "Últimos 12 meses", - "submit": "Generar PDF", - "cancel": "Cancelar", - "errorEndBeforeStart": "La fecha final no puede ser anterior a la inicial.", - "errorRangeTooLong": "El periodo de análisis no puede superar los 2 años.", - "errorInvalidDate": "Selecciona una fecha de inicio y fin válida." - }, - "sections": { - "title": "Seleccionar contenido", - "bp": "Presión arterial", - "weight": "Peso", - "pulse": "Pulso en reposo", - "bmi": "Índice de masa corporal (IMC)", - "mood": "Estado de ánimo", - "moodSensitive": "Sensible — desactivado por defecto.", - "compliance": "Cumplimiento de medicación", - "sleep": "Duración del sueño", - "cycle": "Ciclo menstrual", - "empty": "No hay datos para el periodo seleccionado. El informe se generará con la portada.", - "unavailableHint": "Sin datos en este periodo", - "labs": "Resultados de análisis" - }, "insurer": "Seguro médico", "insuranceNumber": "N.º de asegurado", "summaryTitle": "Resumen clínico", @@ -1640,17 +1609,7 @@ "copyToken": "Copiar token", "mintedHint": "Copia este token ahora — solo se muestra una vez." }, - "edit": { - "planOption": "Editar plan", - "advancedOption": "Ajustes avanzados" - }, "advanced": { - "title": "Ajustes avanzados", - "group": { - "data": "Datos", - "lifecycle": "Ciclo de vida", - "externalApi": "API externa" - }, "dataPortability": { "import": { "title": "Importar tomas", @@ -2196,23 +2155,6 @@ "confidenceLow": "Confianza baja", "pendingLabel": "Generando comentario de tendencia" }, - "range": { - "label": "Período", - "noPriorData": "sin datos del período anterior", - "option": { - "7d": "7d", - "30d": "30d", - "90d": "90d", - "1y": "1a" - }, - "priorLabel": { - "7d": "vs 7d anteriores", - "30d": "vs 30d anteriores", - "90d": "vs 90d anteriores", - "1y": "vs año anterior" - }, - "noChange": "sin cambios" - }, "correlationRow": { "title": "Correlaciones", "disclaimer": "Las correlaciones son observacionales, no causales — habla con tu médico si tu tensión es alta.", @@ -2554,14 +2496,6 @@ "target": { "adjustLink": "Ajustar el rango objetivo" }, - "primary": { - "avg30Label": "Promedio de 30 días", - "inRangeLabel": "En el objetivo", - "inRangeAria": "{pct}% de los días registrados dentro del objetivo" - }, - "lastMeasurement": { - "label": "Última medición" - }, "explainer": { "trigger": "¿Qué es {title}?", "bloodPressureTitle": "Presión arterial", @@ -3649,11 +3583,6 @@ "title": "Corazón y energía del día", "subtitle": "Totales cardiacos y de energía de todo el día" }, - "scoreLink": { - "title": "Puntuación de recuperación", - "subtitle": "Mira cómo se descompone la puntuación nocturna", - "cta": "Ver" - }, "block": { "ansChargeExplainer": "Cuán cargado está tu sistema nervioso autónomo para el día — una lectura de recarga obtenida de tus señales nocturnas de corazón y respiración.", "dayStrainExplainer": "La carga cardiovascular del día en la escala de esfuerzo de tu dispositivo. Léela en relación con tu recuperación, no como una meta en sí.", @@ -3896,18 +3825,7 @@ }, "integrations": { "title": "Integraciones", - "description": "Servicios conectados, canales de entrega y prioridad de fuentes en un solo lugar.", - "tabs": { - "connections": "Conexiones", - "channels": "Canales", - "sources": "Fuentes" - }, - "connections": { - "description": "Conecta Withings, WHOOP, Fitbit, Polar, Oura y Nightscout." - }, - "channels": { - "description": "Telegram, ntfy, Web Push, Webhook y correo: las vías por las que se entregan las notificaciones." - } + "description": "Servicios conectados, canales de entrega y prioridad de fuentes en un solo lugar." }, "notifications": { "title": "Notificaciones", @@ -3927,14 +3845,6 @@ "insights": { "title": "Análisis", "description": "Orden de la vista general y de las pestañas de navegación." - }, - "medications": { - "title": "Medicamentos", - "description": "Vista de lista y orden de los medicamentos." - }, - "mood": { - "title": "Etiquetas de ánimo", - "description": "Grupos, etiquetas y orden del selector de ánimo." } }, "dashboard": { @@ -4021,12 +3931,6 @@ "description": "Descarga tus datos como CSV o una copia de seguridad JSON completa, o importa desde Apple Health, JSON o CSV. El informe médico ahora está en Historia de salud.", "otherOptionsHeading": "Otras opciones de exportación", "downloadFailed": "No se pudo descargar la exportación. Inténtalo de nuevo.", - "hero": { - "eyebrow": "Cita médica", - "valueStatement": "Un informe PDF imprimible para tu cita médica — constantes, IMC, clasificación de la tensión, cumplimiento de la medicación y (opcionalmente) el estado de ánimo, en pocas páginas.", - "cta": "Configurar y generar informe", - "formatHint": "PDF · listo para imprimir" - }, "actions": { "download": "Descargar" }, @@ -4035,10 +3939,6 @@ "until": "Hasta" }, "cards": { - "doctorReport": { - "title": "Informe médico", - "description": "PDF imprimible para la cita médica — constantes vitales, IMC, clasificación de la presión arterial, cumplimiento de la medicación, estado de ánimo." - }, "measurementsCsv": { "title": "Mediciones", "description": "Peso, presión arterial, pulso, glucosa y todas las demás mediciones como valores separados por comas." @@ -4416,14 +4316,6 @@ "clearedToast": "Se olvidaron {count} datos guardados.", "clearError": "No se pudieron borrar los datos guardados. Inténtalo de nuevo." }, - "disableCoach": { - "title": "Ocultar el Coach", - "description": "Oculta el botón del Coach y el panel en todas las páginas.", - "toggleAria": "Ocultar el botón del Coach y el panel", - "savedHidden": "Coach oculto.", - "savedShown": "Coach visible.", - "saveError": "No se pudo guardar el cambio. Inténtalo de nuevo." - }, "aboutMe": { "title": "Sobre mí", "description": "Contexto personal que el Coach y el informe diario leen junto con tus datos.", @@ -5907,11 +5799,6 @@ "sixDaysAgo": "hace 6 días" } }, - "relativeDay": { - "today": "Hoy", - "yesterday": "Ayer", - "daysAgo": "hace {count} días" - }, "edit": { "openLabel": "Editar el rango objetivo de {metric}", "title": "Editar rango objetivo — {metric}", diff --git a/messages/fr.json b/messages/fr.json index d36a55ab0..a148aa1c1 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -123,37 +123,6 @@ "glucoseBelowTarget": "sous la cible", "glucoseAboveTarget": "au-dessus de la cible", "practiceLabel": "Cabinet", - "dialog": { - "title": "Rapport médical (PDF)", - "description": "Définis la période d’analyse et (optionnel) le nom du cabinet — les deux apparaîtront sur la page de garde.", - "startLabel": "Du", - "endLabel": "Au", - "practiceLabel": "Cabinet / clinique (optionnel)", - "practicePlaceholder": "p. ex. Dr Müller — médecin généraliste", - "preset90d": "90 derniers jours", - "preset180d": "6 derniers mois", - "preset365d": "12 derniers mois", - "submit": "Générer le PDF", - "cancel": "Annuler", - "errorEndBeforeStart": "La date de fin ne peut pas être antérieure à la date de début.", - "errorRangeTooLong": "La période d’analyse ne peut pas dépasser 2 ans.", - "errorInvalidDate": "Choisis une date de début et une date de fin valides." - }, - "sections": { - "title": "Choisir le contenu", - "bp": "Tension artérielle", - "weight": "Poids", - "pulse": "Pouls au repos", - "bmi": "Indice de masse corporelle (IMC)", - "mood": "Humeur", - "moodSensitive": "Sensible — désactivé par défaut.", - "compliance": "Observance des médicaments", - "sleep": "Durée du sommeil", - "cycle": "Cycle menstruel", - "empty": "Pas de données sur la période sélectionnée. Le rapport conservera la page de garde.", - "unavailableHint": "Pas de données sur cette période", - "labs": "Résultats d'analyses" - }, "insurer": "Assurance maladie", "insuranceNumber": "N° d’assuré", "summaryTitle": "Résumé clinique", @@ -1640,17 +1609,7 @@ "copyToken": "Copier le jeton", "mintedHint": "Copiez ce jeton maintenant — il n’est affiché qu’une seule fois." }, - "edit": { - "planOption": "Modifier le plan", - "advancedOption": "Paramètres avancés" - }, "advanced": { - "title": "Paramètres avancés", - "group": { - "data": "Données", - "lifecycle": "Cycle de vie", - "externalApi": "API externe" - }, "dataPortability": { "import": { "title": "Importer les prises", @@ -2196,23 +2155,6 @@ "confidenceLow": "Confiance faible", "pendingLabel": "Commentaire de tendance en cours de génération" }, - "range": { - "label": "Période", - "noPriorData": "aucune donnée sur la période précédente", - "option": { - "7d": "7j", - "30d": "30j", - "90d": "90j", - "1y": "1an" - }, - "priorLabel": { - "7d": "vs 7j précédents", - "30d": "vs 30j précédents", - "90d": "vs 90j précédents", - "1y": "vs année précédente" - }, - "noChange": "aucun changement" - }, "correlationRow": { "title": "Corrélations", "disclaimer": "Les corrélations sont observationnelles, pas causales — parle à ton médecin si ta tension est élevée.", @@ -2554,14 +2496,6 @@ "target": { "adjustLink": "Ajuster la plage cible" }, - "primary": { - "avg30Label": "Moyenne sur 30 jours", - "inRangeLabel": "Dans la cible", - "inRangeAria": "{pct}% des jours enregistrés dans la cible" - }, - "lastMeasurement": { - "label": "Dernière mesure" - }, "explainer": { "trigger": "Qu'est-ce que {title} ?", "bloodPressureTitle": "Tension artérielle", @@ -3649,11 +3583,6 @@ "title": "Cœur et énergie du jour", "subtitle": "Totaux cardiaques et énergétiques de la journée" }, - "scoreLink": { - "title": "Score de récupération", - "subtitle": "Voyez comment se décompose le score nocturne", - "cta": "Voir" - }, "block": { "ansChargeExplainer": "À quel point votre système nerveux autonome est rechargé pour la journée — une mesure de recharge issue de vos signaux nocturnes de cœur et de respiration.", "dayStrainExplainer": "La charge cardiovasculaire de la journée sur l'échelle d'effort de votre appareil. Lisez-la au regard de votre récupération, pas comme un objectif en soi.", @@ -3896,18 +3825,7 @@ }, "integrations": { "title": "Intégrations", - "description": "Services connectés, canaux de diffusion et priorité des sources au même endroit.", - "tabs": { - "connections": "Connexions", - "channels": "Canaux", - "sources": "Sources" - }, - "connections": { - "description": "Connecter Withings, WHOOP, Fitbit, Polar, Oura et Nightscout." - }, - "channels": { - "description": "Telegram, ntfy, Web Push, Webhook et e-mail : les voies par lesquelles les notifications sont envoyées." - } + "description": "Services connectés, canaux de diffusion et priorité des sources au même endroit." }, "notifications": { "title": "Notifications", @@ -3927,14 +3845,6 @@ "insights": { "title": "Analyses", "description": "Ordre de la vue d'ensemble et des onglets de navigation." - }, - "medications": { - "title": "Médicaments", - "description": "Vue de la liste et ordre des médicaments." - }, - "mood": { - "title": "Étiquettes d’humeur", - "description": "Groupes, étiquettes et ordre du sélecteur d’humeur." } }, "dashboard": { @@ -4021,12 +3931,6 @@ "description": "Téléchargez vos données en CSV ou en sauvegarde JSON complète, ou importez depuis Apple Santé, JSON ou CSV. Le compte rendu médical se trouve désormais dans Dossier de santé.", "otherOptionsHeading": "Autres options d'export", "downloadFailed": "Échec du téléchargement de l'export. Veuillez réessayer.", - "hero": { - "eyebrow": "Rendez-vous médical", - "valueStatement": "Un rapport PDF imprimable pour votre rendez-vous médical — constantes, IMC, classification de tension, observance des traitements et (en option) historique de l'humeur, en quelques pages.", - "cta": "Configurer & générer le rapport", - "formatHint": "PDF · prêt à imprimer" - }, "actions": { "download": "Télécharger" }, @@ -4035,10 +3939,6 @@ "until": "Jusqu’au" }, "cards": { - "doctorReport": { - "title": "Rapport médical", - "description": "PDF imprimable pour le rendez-vous médical — constantes, IMC, classification de la tension, observance des traitements, humeur." - }, "measurementsCsv": { "title": "Mesures", "description": "Poids, tension artérielle, pouls, glycémie et toutes les autres mesures en valeurs séparées par des virgules." @@ -4416,14 +4316,6 @@ "clearedToast": "{count} informations enregistrées oubliées.", "clearError": "Impossible d'effacer les informations enregistrées. Veuillez réessayer." }, - "disableCoach": { - "title": "Masquer le Coach", - "description": "Masque le bouton et le panneau du Coach sur toutes les pages.", - "toggleAria": "Masquer le bouton et le panneau du Coach", - "savedHidden": "Coach masqué.", - "savedShown": "Coach visible.", - "saveError": "Impossible d'enregistrer la modification. Veuillez réessayer." - }, "aboutMe": { "title": "À propos de moi", "description": "Contexte personnel que le Coach et le briefing quotidien lisent en plus de vos données.", @@ -5907,11 +5799,6 @@ "sixDaysAgo": "il y a 6 jours" } }, - "relativeDay": { - "today": "Aujourd'hui", - "yesterday": "Hier", - "daysAgo": "il y a {count} jours" - }, "edit": { "openLabel": "Modifier la plage cible de {metric}", "title": "Modifier la plage cible — {metric}", diff --git a/messages/it.json b/messages/it.json index e3657c924..b7d55971b 100644 --- a/messages/it.json +++ b/messages/it.json @@ -123,37 +123,6 @@ "glucoseBelowTarget": "sotto obiettivo", "glucoseAboveTarget": "sopra obiettivo", "practiceLabel": "Studio", - "dialog": { - "title": "Referto medico (PDF)", - "description": "Imposta il periodo di analisi e (opzionale) il nome dello studio — entrambi compaiono in copertina.", - "startLabel": "Dal", - "endLabel": "Al", - "practiceLabel": "Studio / clinica (opzionale)", - "practicePlaceholder": "es. Dr. Müller — Medicina generale", - "preset90d": "Ultimi 90 giorni", - "preset180d": "Ultimi 6 mesi", - "preset365d": "Ultimi 12 mesi", - "submit": "Genera PDF", - "cancel": "Annulla", - "errorEndBeforeStart": "La data di fine non può essere antecedente a quella di inizio.", - "errorRangeTooLong": "Il periodo di analisi non può superare i 2 anni.", - "errorInvalidDate": "Scegli una data di inizio e di fine valide." - }, - "sections": { - "title": "Seleziona contenuti", - "bp": "Pressione arteriosa", - "weight": "Peso", - "pulse": "Pulsazioni a riposo", - "bmi": "Indice di massa corporea (BMI)", - "mood": "Umore", - "moodSensitive": "Sensibile — disattivato per default.", - "compliance": "Aderenza ai farmaci", - "sleep": "Durata del sonno", - "cycle": "Ciclo mestruale", - "empty": "Nessun dato per il periodo selezionato. Il referto manterrà comunque la copertina.", - "unavailableHint": "Nessun dato in questo periodo", - "labs": "Risultati degli esami" - }, "insurer": "Assicurazione sanitaria", "insuranceNumber": "N. assicurato", "summaryTitle": "Riepilogo clinico", @@ -1640,17 +1609,7 @@ "copyToken": "Copia token", "mintedHint": "Copia questo token ora — viene mostrato una sola volta." }, - "edit": { - "planOption": "Modifica piano", - "advancedOption": "Impostazioni avanzate" - }, "advanced": { - "title": "Impostazioni avanzate", - "group": { - "data": "Dati", - "lifecycle": "Ciclo di vita", - "externalApi": "API esterna" - }, "dataPortability": { "import": { "title": "Importa assunzioni", @@ -2196,23 +2155,6 @@ "confidenceLow": "Confidenza bassa", "pendingLabel": "Commento di trend in generazione" }, - "range": { - "label": "Periodo", - "noPriorData": "nessun dato nel periodo precedente", - "option": { - "7d": "7g", - "30d": "30g", - "90d": "90g", - "1y": "1a" - }, - "priorLabel": { - "7d": "vs 7g precedenti", - "30d": "vs 30g precedenti", - "90d": "vs 90g precedenti", - "1y": "vs anno precedente" - }, - "noChange": "nessuna variazione" - }, "correlationRow": { "title": "Correlazioni", "disclaimer": "Le correlazioni sono osservazionali, non causali — parla con il medico se la pressione è alta.", @@ -2554,14 +2496,6 @@ "target": { "adjustLink": "Modifica l'intervallo obiettivo" }, - "primary": { - "avg30Label": "Media a 30 giorni", - "inRangeLabel": "Nell'obiettivo", - "inRangeAria": "{pct}% dei giorni registrati nell'obiettivo" - }, - "lastMeasurement": { - "label": "Ultima misurazione" - }, "explainer": { "trigger": "Che cos'è {title}?", "bloodPressureTitle": "Pressione arteriosa", @@ -3649,11 +3583,6 @@ "title": "Cuore ed energia del giorno", "subtitle": "Totali cardiaci ed energetici dell'intera giornata" }, - "scoreLink": { - "title": "Punteggio di recupero", - "subtitle": "Vedi come si scompone il punteggio notturno", - "cta": "Apri" - }, "block": { "ansChargeExplainer": "Quanto è carico il tuo sistema nervoso autonomo per la giornata — una lettura di ricarica ricavata dai segnali notturni di cuore e respiro.", "dayStrainExplainer": "Il carico cardiovascolare della giornata sulla scala di sforzo del tuo dispositivo. Leggilo in rapporto al tuo recupero, non come obiettivo a sé.", @@ -3896,18 +3825,7 @@ }, "integrations": { "title": "Integrazioni", - "description": "Servizi collegati, canali di consegna e priorità delle fonti in un unico posto.", - "tabs": { - "connections": "Connessioni", - "channels": "Canali", - "sources": "Origini" - }, - "connections": { - "description": "Collega Withings, WHOOP, Fitbit, Polar, Oura e Nightscout." - }, - "channels": { - "description": "Telegram, ntfy, Web Push, Webhook ed e-mail: le vie attraverso cui vengono recapitate le notifiche." - } + "description": "Servizi collegati, canali di consegna e priorità delle fonti in un unico posto." }, "notifications": { "title": "Notifiche", @@ -3927,14 +3845,6 @@ "insights": { "title": "Approfondimenti", "description": "Ordine della panoramica e delle schede di navigazione." - }, - "medications": { - "title": "Farmaci", - "description": "Vista elenco e ordine dei farmaci." - }, - "mood": { - "title": "Tag dell’umore", - "description": "Gruppi, tag e ordine del selettore dell’umore." } }, "dashboard": { @@ -4021,12 +3931,6 @@ "description": "Scarica i tuoi dati come CSV o backup JSON completo, oppure importa da Apple Salute, JSON o CSV. Il referto medico ora si trova in Cartella sanitaria.", "otherOptionsHeading": "Altre opzioni di esportazione", "downloadFailed": "Impossibile scaricare l'esportazione. Riprova.", - "hero": { - "eyebrow": "Visita medica", - "valueStatement": "Un referto PDF stampabile per la visita medica — parametri vitali, BMI, classificazione della pressione, aderenza alla terapia e (facoltativamente) l'andamento dell'umore, in poche pagine.", - "cta": "Configura e genera il referto", - "formatHint": "PDF · pronto per la stampa" - }, "actions": { "download": "Scarica" }, @@ -4035,10 +3939,6 @@ "until": "Fino al" }, "cards": { - "doctorReport": { - "title": "Referto medico", - "description": "PDF stampabile per la visita medica — parametri vitali, IMC, classificazione della pressione, aderenza ai farmaci, umore." - }, "measurementsCsv": { "title": "Misurazioni", "description": "Peso, pressione, polso, glicemia e tutte le altre misurazioni in valori separati da virgola." @@ -4416,14 +4316,6 @@ "clearedToast": "Dimenticate {count} informazioni memorizzate.", "clearError": "Impossibile cancellare le informazioni memorizzate. Riprova." }, - "disableCoach": { - "title": "Nascondi il Coach", - "description": "Nasconde il pulsante e il pannello del Coach su tutte le pagine.", - "toggleAria": "Nascondi il pulsante e il pannello del Coach", - "savedHidden": "Coach nascosto.", - "savedShown": "Coach visibile.", - "saveError": "Impossibile salvare la modifica. Riprova." - }, "aboutMe": { "title": "Su di me", "description": "Contesto personale che il Coach e il briefing giornaliero leggono insieme ai tuoi dati.", @@ -5907,11 +5799,6 @@ "sixDaysAgo": "6 giorni fa" } }, - "relativeDay": { - "today": "Oggi", - "yesterday": "Ieri", - "daysAgo": "{count} giorni fa" - }, "edit": { "openLabel": "Modifica il range obiettivo per {metric}", "title": "Modifica range obiettivo — {metric}", diff --git a/messages/pl.json b/messages/pl.json index ca5797728..20bda86fc 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -123,37 +123,6 @@ "glucoseBelowTarget": "poniżej celu", "glucoseAboveTarget": "powyżej celu", "practiceLabel": "Gabinet", - "dialog": { - "title": "Raport medyczny (PDF)", - "description": "Ustaw okres analizy i (opcjonalnie) nazwę gabinetu — oba pojawią się na stronie tytułowej.", - "startLabel": "Od", - "endLabel": "Do", - "practiceLabel": "Gabinet / klinika (opcjonalnie)", - "practicePlaceholder": "np. dr Müller — lekarz rodzinny", - "preset90d": "Ostatnie 90 dni", - "preset180d": "Ostatnie 6 miesięcy", - "preset365d": "Ostatnie 12 miesięcy", - "submit": "Wygeneruj PDF", - "cancel": "Anuluj", - "errorEndBeforeStart": "Data końcowa nie może być wcześniejsza niż początkowa.", - "errorRangeTooLong": "Okres analizy nie może przekraczać 2 lat.", - "errorInvalidDate": "Wybierz poprawną datę początku i końca." - }, - "sections": { - "title": "Wybierz zawartość", - "bp": "Ciśnienie krwi", - "weight": "Waga", - "pulse": "Tętno spoczynkowe", - "bmi": "Wskaźnik masy ciała (BMI)", - "mood": "Nastrój", - "moodSensitive": "Wrażliwe — domyślnie wyłączone.", - "compliance": "Przestrzeganie leków", - "sleep": "Czas snu", - "cycle": "Cykl menstruacyjny", - "empty": "Brak danych dla wybranego okresu. Raport zachowa stronę tytułową.", - "unavailableHint": "Brak danych w tym okresie", - "labs": "Wyniki badań" - }, "insurer": "Ubezpieczyciel zdrowotny", "insuranceNumber": "Nr ubezpieczonego", "summaryTitle": "Podsumowanie kliniczne", @@ -1640,17 +1609,7 @@ "copyToken": "Kopiuj token", "mintedHint": "Skopiuj ten token teraz — jest pokazywany tylko raz." }, - "edit": { - "planOption": "Edytuj plan", - "advancedOption": "Ustawienia zaawansowane" - }, "advanced": { - "title": "Ustawienia zaawansowane", - "group": { - "data": "Dane", - "lifecycle": "Cykl życia", - "externalApi": "Zewnętrzne API" - }, "dataPortability": { "import": { "title": "Importuj przyjęcia", @@ -2196,23 +2155,6 @@ "confidenceLow": "Niska pewność", "pendingLabel": "Komentarz trendu jest generowany" }, - "range": { - "label": "Okres", - "noPriorData": "brak danych z poprzedniego okresu", - "option": { - "7d": "7d", - "30d": "30d", - "90d": "90d", - "1y": "1r" - }, - "priorLabel": { - "7d": "vs poprzednie 7d", - "30d": "vs poprzednie 30d", - "90d": "vs poprzednie 90d", - "1y": "vs poprzedni rok" - }, - "noChange": "bez zmian" - }, "correlationRow": { "title": "Korelacje", "disclaimer": "Korelacje są obserwacyjne, nie przyczynowe — porozmawiaj z lekarzem, jeśli ciśnienie jest wysokie.", @@ -2554,14 +2496,6 @@ "target": { "adjustLink": "Dostosuj zakres docelowy" }, - "primary": { - "avg30Label": "Średnia z 30 dni", - "inRangeLabel": "W zakresie", - "inRangeAria": "{pct}% zarejestrowanych dni w zakresie celu" - }, - "lastMeasurement": { - "label": "Ostatni pomiar" - }, "explainer": { "trigger": "Czym jest {title}?", "bloodPressureTitle": "Ciśnienie krwi", @@ -3649,11 +3583,6 @@ "title": "Serce i energia w ciągu dnia", "subtitle": "Całodobowe sumy sercowe i energetyczne" }, - "scoreLink": { - "title": "Wynik regeneracji", - "subtitle": "Zobacz, jak rozkłada się nocny wynik", - "cta": "Zobacz" - }, "block": { "ansChargeExplainer": "Jak naładowany jest Twój autonomiczny układ nerwowy na dany dzień — odczyt regeneracji oparty na nocnych sygnałach serca i oddechu.", "dayStrainExplainer": "Dzienne obciążenie sercowo-naczyniowe na skali wysiłku Twojego urządzenia. Odczytuj je w odniesieniu do regeneracji, nie jako cel sam w sobie.", @@ -3896,18 +3825,7 @@ }, "integrations": { "title": "Integracje", - "description": "Połączone usługi, kanały dostarczania i priorytet źródeł w jednym miejscu.", - "tabs": { - "connections": "Połączenia", - "channels": "Kanały", - "sources": "Źródła" - }, - "connections": { - "description": "Połącz Withings, WHOOP, Fitbit, Polar, Oura i Nightscout." - }, - "channels": { - "description": "Telegram, ntfy, Web Push, Webhook i e-mail — drogi dostarczania powiadomień." - } + "description": "Połączone usługi, kanały dostarczania i priorytet źródeł w jednym miejscu." }, "notifications": { "title": "Powiadomienia", @@ -3927,14 +3845,6 @@ "insights": { "title": "Analizy", "description": "Kolejność przeglądu i pigułek nawigacyjnych." - }, - "medications": { - "title": "Leki", - "description": "Widok listy i kolejność leków." - }, - "mood": { - "title": "Tagi nastroju", - "description": "Grupy, tagi i kolejność w wyborze nastroju." } }, "dashboard": { @@ -4021,12 +3931,6 @@ "description": "Pobierz swoje dane jako CSV lub pełną kopię zapasową JSON albo zaimportuj z Apple Health, JSON lub CSV. Raport dla lekarza znajdziesz teraz w Dokumentacji zdrowotnej.", "otherOptionsHeading": "Inne opcje eksportu", "downloadFailed": "Nie udało się pobrać eksportu. Spróbuj ponownie.", - "hero": { - "eyebrow": "Wizyta u lekarza", - "valueStatement": "Raport PDF do druku na wizytę u lekarza — parametry życiowe, BMI, klasyfikacja ciśnienia, przestrzeganie leczenia oraz (opcjonalnie) zapis nastroju, na kilku stronach.", - "cta": "Skonfiguruj i wygeneruj raport", - "formatHint": "PDF · gotowe do druku" - }, "actions": { "download": "Pobierz" }, @@ -4035,10 +3939,6 @@ "until": "Do" }, "cards": { - "doctorReport": { - "title": "Raport dla lekarza", - "description": "PDF do druku na wizytę lekarską — parametry życiowe, BMI, klasyfikacja ciśnienia, przestrzeganie leczenia, nastrój." - }, "measurementsCsv": { "title": "Pomiary", "description": "Waga, ciśnienie, tętno, glukoza i wszystkie inne pomiary jako wartości rozdzielone przecinkami." @@ -4416,14 +4316,6 @@ "clearedToast": "Zapomniano {count} zapisanych informacji.", "clearError": "Nie udało się usunąć zapisanych informacji. Spróbuj ponownie." }, - "disableCoach": { - "title": "Ukryj Coacha", - "description": "Ukrywa przycisk Coacha i panel na wszystkich stronach.", - "toggleAria": "Ukryj przycisk Coacha i panel", - "savedHidden": "Coach ukryty.", - "savedShown": "Coach widoczny.", - "saveError": "Nie udało się zapisać zmiany. Spróbuj ponownie." - }, "aboutMe": { "title": "O mnie", "description": "Osobisty kontekst, który Coach i codzienny briefing czytają obok Twoich danych.", @@ -5907,11 +5799,6 @@ "sixDaysAgo": "6 dni temu" } }, - "relativeDay": { - "today": "Dzisiaj", - "yesterday": "Wczoraj", - "daysAgo": "{count} dni temu" - }, "edit": { "openLabel": "Edytuj zakres docelowy dla {metric}", "title": "Edytuj zakres docelowy — {metric}", diff --git a/src/app/api/analytics/route.ts b/src/app/api/analytics/route.ts index 2f9756c28..9539cc1ec 100644 --- a/src/app/api/analytics/route.ts +++ b/src/app/api/analytics/route.ts @@ -436,11 +436,15 @@ async function buildAnalyticsResponse(user: AuthedUser) { const glucoseEnabled = await isModuleEnabled(user.id, "glucose"); const glucoseByContextOut = glucoseEnabled ? glucoseByContext : null; const glucoseClinicalOut = glucoseEnabled ? glucoseClinical : null; + // The reference instant the score is graded as-of. Reused below so the Rest + // Mode annotation resolves against the SAME window the score covers, not a + // fresh "now" that could fall outside it. + const scoredAt = new Date(); const healthScore = await computeUserHealthScoreFastPath({ userId: user.id, ...bpInputs, heightCm: user.heightCm ?? null, - now: new Date(), + now: scoredAt, coverage, moodEnabled, }); @@ -449,8 +453,9 @@ async function buildAnalyticsResponse(user: AuthedUser) { // active we frame the score (not penalise it): the number above is left // exactly as computed and we attach a value-free "you were unwell" context. // Resolved server-side so iOS mirrors the same fact, never recomputing it. + // Resolved as-of the scored window (`scoredAt`), not a fresh now. if (healthScore) { - const restMode = await resolveRestMode(user.id); + const restMode = await resolveRestMode(user.id, scoredAt); healthScore.restMode = restMode.active ? { active: true,