i18n(doctor-report): translate PDF medical report strings into es/fr/it/pl

Localises the doctor-report PDF surface: cover-page metadata, vitals
table headings (parameter / current / period mean / n), BP classification
(ESH 2023 bands: optimal / normal / high-normal / hypertension grades),
BMI classification (WHO underweight → obesity grade III), mood scale,
glucose reference ranges (ADA 2024 / DDG, fasting / postprandial /
random / bedtime), GLP-1 weight summary, compliance table, section-
picker dialog (date range, practice name, presets, validation errors).
ICU placeholders {start}, {end}, {delta}, {label}, {avg}, {unit},
{rangeMin}, {rangeMax}, {class} preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-17 17:31:06 +02:00
1 parent b776daf546
commit 11256935f7
7 files changed
+388 -273

No files matched your search

+64 -64
View File
@@ -51,23 +51,23 @@
"period": "Período del informe",
"createdOn": "Creado el",
"vitalsTitle": "Signos vitales — Resumen",
"colParameter": "Parameter",
"colCurrent": "Current",
"colAvgPeriod": "Avg. period",
"colParameter": "Parámetro",
"colCurrent": "Actual",
"colAvgPeriod": "Med. periodo",
"colMin": "Mín",
"colMax": "Máx",
"colN": "n",
"bpClassificationTitle": "Blood pressure — ESH classification (2023)",
"avgBp": "Average blood pressure",
"classification": "Classification",
"bmiTitle": "Body Mass Index (BMI)",
"bpClassificationTitle": "Tensión arterial — Clasificación ESH (2023)",
"avgBp": "Tensión arterial media",
"classification": "Clasificación",
"bmiTitle": "Índice de masa corporal (IMC)",
"bmiRow": "IMC:{bmi} kg/m² — Classification: {class} (WHO)",
"complianceTitle": "Medication compliance",
"complianceTitle": "Cumplimiento de medicación",
"glp1Title": "Terapia GLP-1",
"glp1WeightSummary": "Weight over report period: {start} kg → {end} kg ({delta} kg)",
"glp1WeightSummary": "Peso en el periodo del informe: {start} kg → {end} kg ({delta} kg)",
"glp1CurrentDose": "Dosis actual:{value} {unit} (since {since})",
"glp1Compliance": "Cumplimiento:{taken} / {total} ({rate}%)",
"glp1SideEffectsTitle": "Side effects logged",
"glp1SideEffectsTitle": "Efectos secundarios registrados",
"colGlp1Date": "Fecha",
"colGlp1Dose": "Dosis",
"colGlp1Note": "Nota",
@@ -78,79 +78,79 @@
"colMissed": "Perdida",
"colTotal": "Total",
"colComplianceRate": "Tasa de cumplimiento",
"moodTitle": "Mood trajectory",
"moodTitle": "Evolución del estado de ánimo",
"moodSummary": "Promedio:{avg} / 5.0 · Entries: {count} · Range: {min}–{max}",
"colMood": "Estado de ánimo",
"colCount": "Count",
"colShare": "Share",
"colCount": "Cantidad",
"colShare": "Porcentaje",
"footerDisclaimer1": "Este informe se generó automáticamente a partir de datos autorregistrados y está destinado a apoyar",
"footerDisclaimer2": "la conversación entre médico y paciente. No sustituye un diagnóstico médico. Las correlaciones no implican causalidad.",
"footerSource": "Fuente: HealthLog | Zona horaria: Europe/Berlin | Creado: {timestamp}",
"typeWeight": "Body weight",
"typeBpSys": "Systolic blood pressure",
"typeBpDia": "Diastolic blood pressure",
"typePulse": "Resting pulse",
"typeWeight": "Peso corporal",
"typeBpSys": "Tensión arterial sistólica",
"typeBpDia": "Tensión arterial diastólica",
"typePulse": "Pulso en reposo",
"typeBodyFat": "Grasa corporal",
"typeSleep": "Sleep duration",
"typeSteps": "Activity (steps)",
"typeTotalBodyWater": "Total body water",
"typeBoneMass": "Bone mass",
"typeOxygenSaturation": "Oxygen saturation",
"typeSleep": "Duración del sueño",
"typeSteps": "Actividad (pasos)",
"typeTotalBodyWater": "Agua corporal total",
"typeBoneMass": "Masa ósea",
"typeOxygenSaturation": "Saturación de oxígeno",
"unitSteps": "Pasos",
"bmiUnderweight": "Underweight",
"bmiNormal": "Normal weight",
"bmiOverweight": "Overweight (pre-obese)",
"bmiObeseGrade1": "Obesity grade I",
"bmiObeseGrade2": "Obesity grade II",
"bmiObeseGrade3": "Obesity grade III",
"bpOptimal": "Optimal",
"bmiUnderweight": "Peso insuficiente",
"bmiNormal": "Peso normal",
"bmiOverweight": "Sobrepeso (preobesidad)",
"bmiObeseGrade1": "Obesidad grado I",
"bmiObeseGrade2": "Obesidad grado II",
"bmiObeseGrade3": "Obesidad grado III",
"bpOptimal": "Óptima",
"bpNormal": "Normal",
"bpHighNormal": "High-normal",
"bpHypertensionGrade1": "Hypertension grade 1",
"bpHypertensionGrade2": "Hypertension grade 2",
"bpHypertensionGrade3": "Hypertension grade 3",
"moodAwful": "Very bad",
"bpHighNormal": "Normal-alta",
"bpHypertensionGrade1": "Hipertensión grado 1",
"bpHypertensionGrade2": "Hipertensión grado 2",
"bpHypertensionGrade3": "Hipertensión grado 3",
"moodAwful": "Muy mal",
"moodBad": "Mal",
"moodNeutral": "Neutral",
"moodGood": "Bien",
"moodGreat": "Very good",
"typeGlucoseFasting": "Glucose (fasting)",
"typeGlucosePostprandial": "Glucose (postprandial)",
"typeGlucoseRandom": "Glucose (random)",
"typeGlucoseBedtime": "Glucose (bedtime)",
"glucoseClassificationTitle": "Blood glucose — reference ranges (ADA 2024 / DDG)",
"glucoseRow": "{label}: avg {avg} {unit} (target {rangeMin}–{rangeMax}) — {class}",
"glucoseInTarget": "in target",
"glucoseBelowTarget": "below target",
"glucoseAboveTarget": "above target",
"practiceLabel": "Practice",
"moodGreat": "Muy bien",
"typeGlucoseFasting": "Glucosa (en ayunas)",
"typeGlucosePostprandial": "Glucosa (posprandial)",
"typeGlucoseRandom": "Glucosa (aleatoria)",
"typeGlucoseBedtime": "Glucosa (antes de dormir)",
"glucoseClassificationTitle": "Glucemia — Rangos de referencia (ADA 2024 / DDG)",
"glucoseRow": "{label}: med. {avg} {unit} (objetivo {rangeMin}–{rangeMax}) — {class}",
"glucoseInTarget": "en objetivo",
"glucoseBelowTarget": "por debajo del objetivo",
"glucoseAboveTarget": "por encima del objetivo",
"practiceLabel": "Consulta",
"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",
"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": "Last 6 months",
"preset365d": "Last 12 months",
"submit": "Generate PDF",
"preset180d": "Últimos 6 meses",
"preset365d": "Últimos 12 meses",
"submit": "Generar PDF",
"cancel": "Cancelar",
"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."
"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": "Choose sections",
"title": "Seleccionar contenido",
"bp": "Presión arterial",
"weight": "Peso",
"pulse": "Resting pulse",
"bmi": "Body Mass Index (BMI)",
"pulse": "Pulso en reposo",
"bmi": "Índice de masa corporal (IMC)",
"mood": "Estado de ánimo",
"moodSensitive": "Sensitive — off by default.",
"compliance": "Medication compliance",
"sleep": "Sleep duration",
"empty": "No data was recorded in this date range yet. The report will still include the cover page."
"moodSensitive": "Sensible — desactivado por defecto.",
"compliance": "Cumplimiento de medicación",
"sleep": "Duración del sueño",
"empty": "No hay datos para el periodo seleccionado. El informe se generará con la portada."
}
},
"errorBoundary": {
+61 -61
View File
@@ -51,23 +51,23 @@
"period": "Période du rapport",
"createdOn": "Créé le",
"vitalsTitle": "Signes vitaux — Résumé",
"colParameter": "Parameter",
"colCurrent": "Current",
"colAvgPeriod": "Avg. period",
"colParameter": "Paramètre",
"colCurrent": "Actuel",
"colAvgPeriod": "Moy. période",
"colMin": "Min",
"colMax": "Max",
"colN": "n",
"bpClassificationTitle": "Blood pressure — ESH classification (2023)",
"avgBp": "Average blood pressure",
"bpClassificationTitle": "Tension artérielle — Classification ESH (2023)",
"avgBp": "Tension artérielle moyenne",
"classification": "Classification",
"bmiTitle": "Body Mass Index (BMI)",
"bmiTitle": "Indice de masse corporelle (IMC)",
"bmiRow": "IMC:{bmi} kg/m² — Classification: {class} (WHO)",
"complianceTitle": "Medication compliance",
"complianceTitle": "Observance des médicaments",
"glp1Title": "Traitement GLP-1",
"glp1WeightSummary": "Weight over report period: {start} kg → {end} kg ({delta} kg)",
"glp1WeightSummary": "Poids sur la période du rapport : {start} kg → {end} kg ({delta} kg)",
"glp1CurrentDose": "Dose actuelle:{value} {unit} (since {since})",
"glp1Compliance": "Observance:{taken} / {total} ({rate}%)",
"glp1SideEffectsTitle": "Side effects logged",
"glp1SideEffectsTitle": "Effets secondaires consignés",
"colGlp1Date": "Date",
"colGlp1Dose": "Dose",
"colGlp1Note": "Note",
@@ -78,79 +78,79 @@
"colMissed": "Manqué",
"colTotal": "Total",
"colComplianceRate": "Taux d'observance",
"moodTitle": "Mood trajectory",
"moodTitle": "Évolution de l’humeur",
"moodSummary": "Moyenne:{avg} / 5.0 · Entries: {count} · Range: {min}–{max}",
"colMood": "Humeur",
"colCount": "Count",
"colShare": "Share",
"colCount": "Nombre",
"colShare": "Part",
"footerDisclaimer1": "Ce rapport a été généré automatiquement à partir de données auto-déclarées et vise à soutenir",
"footerDisclaimer2": "la conversation médecin-patient. Il ne remplace pas un diagnostic médical. Les corrélations n'impliquent pas la causalité.",
"footerSource": "Source : HealthLog | Fuseau horaire : Europe/Berlin | Créé : {timestamp}",
"typeWeight": "Body weight",
"typeBpSys": "Systolic blood pressure",
"typeBpDia": "Diastolic blood pressure",
"typePulse": "Resting pulse",
"typeWeight": "Poids corporel",
"typeBpSys": "Tension artérielle systolique",
"typeBpDia": "Tension artérielle diastolique",
"typePulse": "Pouls au repos",
"typeBodyFat": "Masse grasse",
"typeSleep": "Sleep duration",
"typeSteps": "Activity (steps)",
"typeTotalBodyWater": "Total body water",
"typeBoneMass": "Bone mass",
"typeOxygenSaturation": "Oxygen saturation",
"typeSleep": "Durée du sommeil",
"typeSteps": "Activité (pas)",
"typeTotalBodyWater": "Eau corporelle totale",
"typeBoneMass": "Masse osseuse",
"typeOxygenSaturation": "Saturation en oxygène",
"unitSteps": "Pas",
"bmiUnderweight": "Underweight",
"bmiNormal": "Normal weight",
"bmiOverweight": "Overweight (pre-obese)",
"bmiObeseGrade1": "Obesity grade I",
"bmiObeseGrade2": "Obesity grade II",
"bmiObeseGrade3": "Obesity grade III",
"bpOptimal": "Optimal",
"bpNormal": "Normal",
"bpHighNormal": "High-normal",
"bmiUnderweight": "Insuffisance pondérale",
"bmiNormal": "Poids normal",
"bmiOverweight": "Surpoids (préobésité)",
"bmiObeseGrade1": "Obésité grade I",
"bmiObeseGrade2": "Obésité grade II",
"bmiObeseGrade3": "Obésité grade III",
"bpOptimal": "Optimale",
"bpNormal": "Normale",
"bpHighNormal": "Normale haute",
"bpHypertensionGrade1": "Hypertension grade 1",
"bpHypertensionGrade2": "Hypertension grade 2",
"bpHypertensionGrade3": "Hypertension grade 3",
"moodAwful": "Very bad",
"moodAwful": "Très mauvaise",
"moodBad": "Mauvais",
"moodNeutral": "Neutre",
"moodGood": "Bien",
"moodGreat": "Very good",
"typeGlucoseFasting": "Glucose (fasting)",
"typeGlucosePostprandial": "Glucose (postprandial)",
"typeGlucoseRandom": "Glucose (random)",
"typeGlucoseBedtime": "Glucose (bedtime)",
"glucoseClassificationTitle": "Blood glucose — reference ranges (ADA 2024 / DDG)",
"glucoseRow": "{label}: avg {avg} {unit} (target {rangeMin}–{rangeMax}) — {class}",
"glucoseInTarget": "in target",
"glucoseBelowTarget": "below target",
"glucoseAboveTarget": "above target",
"practiceLabel": "Practice",
"moodGreat": "Très bonne",
"typeGlucoseFasting": "Glycémie (à jeun)",
"typeGlucosePostprandial": "Glycémie (postprandiale)",
"typeGlucoseRandom": "Glycémie (indifférente)",
"typeGlucoseBedtime": "Glycémie (avant le coucher)",
"glucoseClassificationTitle": "Glycémie — Plages de référence (ADA 2024 / DDG)",
"glucoseRow": "{label} : moy. {avg} {unit} (cible {rangeMin}–{rangeMax}) — {class}",
"glucoseInTarget": "dans la cible",
"glucoseBelowTarget": "sous la cible",
"glucoseAboveTarget": "au-dessus de la cible",
"practiceLabel": "Cabinet",
"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",
"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": "Last 6 months",
"preset365d": "Last 12 months",
"submit": "Generate PDF",
"preset180d": "6 derniers mois",
"preset365d": "12 derniers mois",
"submit": "Générer le PDF",
"cancel": "Annuler",
"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."
"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": "Choose sections",
"title": "Choisir le contenu",
"bp": "Tension artérielle",
"weight": "Poids",
"pulse": "Resting pulse",
"bmi": "Body Mass Index (BMI)",
"pulse": "Pouls au repos",
"bmi": "Indice de masse corporelle (IMC)",
"mood": "Humeur",
"moodSensitive": "Sensitive — off by default.",
"compliance": "Medication compliance",
"sleep": "Sleep duration",
"empty": "No data was recorded in this date range yet. The report will still include the cover page."
"moodSensitive": "Sensible — désactivé par défaut.",
"compliance": "Observance des médicaments",
"sleep": "Durée du sommeil",
"empty": "Pas de données sur la période sélectionnée. Le rapport conservera la page de garde."
}
},
"errorBoundary": {
+65 -65
View File
@@ -51,23 +51,23 @@
"period": "Periodo del rapporto",
"createdOn": "Creato il",
"vitalsTitle": "Parametri vitali — Riepilogo",
"colParameter": "Parameter",
"colCurrent": "Current",
"colAvgPeriod": "Avg. period",
"colParameter": "Parametro",
"colCurrent": "Attuale",
"colAvgPeriod": "Media periodo",
"colMin": "Min",
"colMax": "Max",
"colN": "n",
"bpClassificationTitle": "Blood pressure — ESH classification (2023)",
"avgBp": "Average blood pressure",
"classification": "Classification",
"bmiTitle": "Body Mass Index (BMI)",
"bpClassificationTitle": "Pressione arteriosa — Classificazione ESH (2023)",
"avgBp": "Pressione arteriosa media",
"classification": "Classificazione",
"bmiTitle": "Indice di massa corporea (BMI)",
"bmiRow": "IMC:{bmi} kg/m² — Classification: {class} (WHO)",
"complianceTitle": "Medication compliance",
"complianceTitle": "Aderenza ai farmaci",
"glp1Title": "Terapia GLP-1",
"glp1WeightSummary": "Weight over report period: {start} kg → {end} kg ({delta} kg)",
"glp1WeightSummary": "Peso nel periodo del referto: {start} kg → {end} kg ({delta} kg)",
"glp1CurrentDose": "Dose attuale:{value} {unit} (since {since})",
"glp1Compliance": "Aderenza:{taken} / {total} ({rate}%)",
"glp1SideEffectsTitle": "Side effects logged",
"glp1SideEffectsTitle": "Effetti collaterali registrati",
"colGlp1Date": "Data",
"colGlp1Dose": "Dose",
"colGlp1Note": "Nota",
@@ -78,79 +78,79 @@
"colMissed": "Persa",
"colTotal": "Totale",
"colComplianceRate": "Tasso di aderenza",
"moodTitle": "Mood trajectory",
"moodTitle": "Andamento dell’umore",
"moodSummary": "Media:{avg} / 5.0 · Entries: {count} · Range: {min}–{max}",
"colMood": "Umore",
"colCount": "Count",
"colShare": "Share",
"colCount": "Quantità",
"colShare": "Quota",
"footerDisclaimer1": "Questo rapporto è stato generato automaticamente da dati auto-registrati ed è destinato a supportare",
"footerDisclaimer2": "il dialogo medico-paziente. Non sostituisce una diagnosi medica. Le correlazioni non implicano causalità.",
"footerSource": "Fonte: HealthLog | Fuso orario: Europe/Berlin | Creato: {timestamp}",
"typeWeight": "Body weight",
"typeBpSys": "Systolic blood pressure",
"typeBpDia": "Diastolic blood pressure",
"typePulse": "Resting pulse",
"typeWeight": "Peso corporeo",
"typeBpSys": "Pressione arteriosa sistolica",
"typeBpDia": "Pressione arteriosa diastolica",
"typePulse": "Pulsazioni a riposo",
"typeBodyFat": "Massa grassa",
"typeSleep": "Sleep duration",
"typeSteps": "Activity (steps)",
"typeTotalBodyWater": "Total body water",
"typeBoneMass": "Bone mass",
"typeOxygenSaturation": "Oxygen saturation",
"typeSleep": "Durata del sonno",
"typeSteps": "Attività (passi)",
"typeTotalBodyWater": "Acqua corporea totale",
"typeBoneMass": "Massa ossea",
"typeOxygenSaturation": "Saturazione di ossigeno",
"unitSteps": "Passi",
"bmiUnderweight": "Underweight",
"bmiNormal": "Normal weight",
"bmiOverweight": "Overweight (pre-obese)",
"bmiObeseGrade1": "Obesity grade I",
"bmiObeseGrade2": "Obesity grade II",
"bmiObeseGrade3": "Obesity grade III",
"bpOptimal": "Optimal",
"bpNormal": "Normal",
"bpHighNormal": "High-normal",
"bpHypertensionGrade1": "Hypertension grade 1",
"bpHypertensionGrade2": "Hypertension grade 2",
"bpHypertensionGrade3": "Hypertension grade 3",
"moodAwful": "Very bad",
"bmiUnderweight": "Sottopeso",
"bmiNormal": "Peso normale",
"bmiOverweight": "Sovrappeso (preobesità)",
"bmiObeseGrade1": "Obesità di grado I",
"bmiObeseGrade2": "Obesità di grado II",
"bmiObeseGrade3": "Obesità di grado III",
"bpOptimal": "Ottimale",
"bpNormal": "Normale",
"bpHighNormal": "Normale-alta",
"bpHypertensionGrade1": "Ipertensione grado 1",
"bpHypertensionGrade2": "Ipertensione grado 2",
"bpHypertensionGrade3": "Ipertensione grado 3",
"moodAwful": "Molto cattivo",
"moodBad": "Male",
"moodNeutral": "Neutro",
"moodGood": "Bene",
"moodGreat": "Very good",
"typeGlucoseFasting": "Glucose (fasting)",
"typeGlucosePostprandial": "Glucose (postprandial)",
"typeGlucoseRandom": "Glucose (random)",
"typeGlucoseBedtime": "Glucose (bedtime)",
"glucoseClassificationTitle": "Blood glucose — reference ranges (ADA 2024 / DDG)",
"glucoseRow": "{label}: avg {avg} {unit} (target {rangeMin}–{rangeMax}) — {class}",
"glucoseInTarget": "in target",
"glucoseBelowTarget": "below target",
"glucoseAboveTarget": "above target",
"practiceLabel": "Practice",
"moodGreat": "Molto buono",
"typeGlucoseFasting": "Glicemia (a digiuno)",
"typeGlucosePostprandial": "Glicemia (postprandiale)",
"typeGlucoseRandom": "Glicemia (qualsiasi)",
"typeGlucoseBedtime": "Glicemia (prima di coricarsi)",
"glucoseClassificationTitle": "Glicemia — Intervalli di riferimento (ADA 2024 / DDG)",
"glucoseRow": "{label}: media {avg} {unit} (obiettivo {rangeMin}–{rangeMax}) — {class}",
"glucoseInTarget": "in obiettivo",
"glucoseBelowTarget": "sotto obiettivo",
"glucoseAboveTarget": "sopra obiettivo",
"practiceLabel": "Studio",
"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",
"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": "Last 6 months",
"preset365d": "Last 12 months",
"submit": "Generate PDF",
"preset180d": "Ultimi 6 mesi",
"preset365d": "Ultimi 12 mesi",
"submit": "Genera PDF",
"cancel": "Annulla",
"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."
"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": "Choose sections",
"title": "Seleziona contenuti",
"bp": "Pressione arteriosa",
"weight": "Peso",
"pulse": "Resting pulse",
"bmi": "Body Mass Index (BMI)",
"pulse": "Pulsazioni a riposo",
"bmi": "Indice di massa corporea (BMI)",
"mood": "Umore",
"moodSensitive": "Sensitive — off by default.",
"compliance": "Medication compliance",
"sleep": "Sleep duration",
"empty": "No data was recorded in this date range yet. The report will still include the cover page."
"moodSensitive": "Sensibile — disattivato per default.",
"compliance": "Aderenza ai farmaci",
"sleep": "Durata del sonno",
"empty": "Nessun dato per il periodo selezionato. Il referto manterrà comunque la copertina."
}
},
"errorBoundary": {
+67 -67
View File
@@ -51,23 +51,23 @@
"period": "Okres raportu",
"createdOn": "Utworzono",
"vitalsTitle": "Parametry życiowe — Podsumowanie",
"colParameter": "Parameter",
"colCurrent": "Current",
"colAvgPeriod": "Avg. period",
"colParameter": "Parametr",
"colCurrent": "Aktualny",
"colAvgPeriod": "Śr. okresu",
"colMin": "Min",
"colMax": "Maks",
"colN": "n",
"bpClassificationTitle": "Blood pressure — ESH classification (2023)",
"avgBp": "Average blood pressure",
"classification": "Classification",
"bmiTitle": "Body Mass Index (BMI)",
"bpClassificationTitle": "Ciśnienie tętnicze — klasyfikacja ESH (2023)",
"avgBp": "Średnie ciśnienie tętnicze",
"classification": "Klasyfikacja",
"bmiTitle": "Wskaźnik masy ciała (BMI)",
"bmiRow": "BMI:{bmi} kg/m² — Classification: {class} (WHO)",
"complianceTitle": "Medication compliance",
"complianceTitle": "Przestrzeganie leków",
"glp1Title": "Terapia GLP-1",
"glp1WeightSummary": "Weight over report period: {start} kg → {end} kg ({delta} kg)",
"glp1WeightSummary": "Waga w okresie raportu: {start} kg → {end} kg ({delta} kg)",
"glp1CurrentDose": "Aktualna dawka:{value} {unit} (since {since})",
"glp1Compliance": "Przestrzeganie zaleceń:{taken} / {total} ({rate}%)",
"glp1SideEffectsTitle": "Side effects logged",
"glp1SideEffectsTitle": "Zarejestrowane działania niepożądane",
"colGlp1Date": "Data",
"colGlp1Dose": "Dawka",
"colGlp1Note": "Notatka",
@@ -76,81 +76,81 @@
"colTaken": "Przyjęte",
"colSkipped": "Pominięte",
"colMissed": "Pominięte",
"colTotal": "Łącznie",
"colTotal": "Razem",
"colComplianceRate": "Wskaźnik przestrzegania",
"moodTitle": "Mood trajectory",
"moodTitle": "Przebieg nastroju",
"moodSummary": "Średnia:{avg} / 5.0 · Entries: {count} · Range: {min}–{max}",
"colMood": "Nastrój",
"colCount": "Count",
"colShare": "Share",
"colCount": "Liczba",
"colShare": "Udział",
"footerDisclaimer1": "Ten raport został wygenerowany automatycznie z danych samodzielnie rejestrowanych i ma na celu wspierać",
"footerDisclaimer2": "rozmowę lekarza z pacjentem. Nie zastępuje diagnozy medycznej. Korelacje nie oznaczają związku przyczynowego.",
"footerSource": "Źródło: HealthLog | Strefa czasowa: Europe/Berlin | Utworzono: {timestamp}",
"typeWeight": "Body weight",
"typeBpSys": "Systolic blood pressure",
"typeBpDia": "Diastolic blood pressure",
"typePulse": "Resting pulse",
"typeWeight": "Masa ciała",
"typeBpSys": "Ciśnienie skurczowe",
"typeBpDia": "Ciśnienie rozkurczowe",
"typePulse": "Tętno spoczynkowe",
"typeBodyFat": "Tkanka tłuszczowa",
"typeSleep": "Sleep duration",
"typeSteps": "Activity (steps)",
"typeTotalBodyWater": "Total body water",
"typeBoneMass": "Bone mass",
"typeOxygenSaturation": "Oxygen saturation",
"typeSleep": "Czas snu",
"typeSteps": "Aktywność (kroki)",
"typeTotalBodyWater": "Całkowita woda w organizmie",
"typeBoneMass": "Masa kostna",
"typeOxygenSaturation": "Saturacja tlenem",
"unitSteps": "Kroki",
"bmiUnderweight": "Underweight",
"bmiNormal": "Normal weight",
"bmiOverweight": "Overweight (pre-obese)",
"bmiObeseGrade1": "Obesity grade I",
"bmiObeseGrade2": "Obesity grade II",
"bmiObeseGrade3": "Obesity grade III",
"bpOptimal": "Optimal",
"bpNormal": "Normal",
"bpHighNormal": "High-normal",
"bpHypertensionGrade1": "Hypertension grade 1",
"bpHypertensionGrade2": "Hypertension grade 2",
"bpHypertensionGrade3": "Hypertension grade 3",
"moodAwful": "Very bad",
"bmiUnderweight": "Niedowaga",
"bmiNormal": "Waga prawidłowa",
"bmiOverweight": "Nadwaga (preobesyty)",
"bmiObeseGrade1": "Otyłość I stopnia",
"bmiObeseGrade2": "Otyłość II stopnia",
"bmiObeseGrade3": "Otyłość III stopnia",
"bpOptimal": "Optymalne",
"bpNormal": "Prawidłowe",
"bpHighNormal": "Wysokie prawidłowe",
"bpHypertensionGrade1": "Nadciśnienie 1 stopnia",
"bpHypertensionGrade2": "Nadciśnienie 2 stopnia",
"bpHypertensionGrade3": "Nadciśnienie 3 stopnia",
"moodAwful": "Bardzo zły",
"moodBad": "Źle",
"moodNeutral": "Neutralnie",
"moodNeutral": "Neutralny",
"moodGood": "Dobrze",
"moodGreat": "Very good",
"typeGlucoseFasting": "Glucose (fasting)",
"typeGlucosePostprandial": "Glucose (postprandial)",
"typeGlucoseRandom": "Glucose (random)",
"typeGlucoseBedtime": "Glucose (bedtime)",
"glucoseClassificationTitle": "Blood glucose — reference ranges (ADA 2024 / DDG)",
"glucoseRow": "{label}: avg {avg} {unit} (target {rangeMin}–{rangeMax}) — {class}",
"glucoseInTarget": "in target",
"glucoseBelowTarget": "below target",
"glucoseAboveTarget": "above target",
"practiceLabel": "Practice",
"moodGreat": "Bardzo dobry",
"typeGlucoseFasting": "Glukoza (na czczo)",
"typeGlucosePostprandial": "Glukoza (po posiłku)",
"typeGlucoseRandom": "Glukoza (dowolna)",
"typeGlucoseBedtime": "Glukoza (przed snem)",
"glucoseClassificationTitle": "Glikemia — zakresy referencyjne (ADA 2024 / DDG)",
"glucoseRow": "{label}: śr. {avg} {unit} (cel {rangeMin}–{rangeMax}) — {class}",
"glucoseInTarget": "w celu",
"glucoseBelowTarget": "poniżej celu",
"glucoseAboveTarget": "powyżej celu",
"practiceLabel": "Gabinet",
"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",
"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": "Last 6 months",
"preset365d": "Last 12 months",
"submit": "Generate PDF",
"preset180d": "Ostatnie 6 miesięcy",
"preset365d": "Ostatnie 12 miesięcy",
"submit": "Wygeneruj PDF",
"cancel": "Anuluj",
"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."
"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": "Choose sections",
"title": "Wybierz zawartość",
"bp": "Ciśnienie krwi",
"weight": "Waga",
"pulse": "Resting pulse",
"bmi": "Body Mass Index (BMI)",
"pulse": "Tętno spoczynkowe",
"bmi": "Wskaźnik masy ciała (BMI)",
"mood": "Nastrój",
"moodSensitive": "Sensitive — off by default.",
"compliance": "Medication compliance",
"sleep": "Sleep duration",
"empty": "No data was recorded in this date range yet. The report will still include the cover page."
"moodSensitive": "Wrażliwe — domyślnie wyłączone.",
"compliance": "Przestrzeganie leków",
"sleep": "Czas snu",
"empty": "Brak danych dla wybranego okresu. Raport zachowa stronę tytułową."
}
},
"errorBoundary": {
+46 -11
View File
@@ -353,6 +353,15 @@ async function buildAnalyticsResponse(user: AuthedUser) {
*/
let bpInTargetPctPriorMonth: number | null = null;
let bpInTargetPctPriorYear: number | null = null;
/**
* v1.4.38 — prior-week BP-in-target pct so the Health-Score
* week-over-week delta reflects BP movement. Pre-v1.4.38 the
* Health-Score helper pinned both windows to the same value, so the
* BP pillar always zeroed out of the delta. Computed by running a
* second `computeBpInTargetFastPath` against `now - 7d` and reading
* the resulting `last30Days.pct`.
*/
let bpInTargetPctPriorWeek: number | null = null;
const bpTargets = getBpTargets(user.dateOfBirth);
if (bpTargets) {
const now = new Date();
@@ -367,23 +376,43 @@ async function buildAnalyticsResponse(user: AuthedUser) {
// a `path: "rollup" | "live"` annotate so prod logs prove which
// branch fired. See `bp-in-target-fast-path.ts` for the
// documented per-day-mean approximation.
const windows = await computeBpInTargetFastPath({
userId: user.id,
targets: bpTargets,
now,
coverage,
// v1.4.38 W-A — cross-tz runtime guard. The helper falls back
// to the live SQL path when the user is more than 3 hours from
// UTC, where the rollup table's UTC-midnight day-key would slip
// a calendar day relative to the live aggregator's window cuts.
userTz,
});
//
// v1.4.38 — two parallel runs: one anchored at `now` for the
// standard windows, one anchored at `now - 7d` so the Health-Score
// helper has a real prior-week BP pct to feed into its previous-
// window snapshot. The two probes share the coverage map and the
// same rollup / live branch decision; the extra call is one extra
// pair of `readRollupBuckets` reads on the rollup path and is a
// no-op when the user has no BP rows at all.
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
const [windows, windowsPriorWeek] = await Promise.all([
computeBpInTargetFastPath({
userId: user.id,
targets: bpTargets,
now,
coverage,
// v1.4.38 W-A — cross-tz runtime guard. The helper falls back
// to the live SQL path when the user is more than 3 hours
// from UTC, where the rollup table's UTC-midnight day-key
// would slip a calendar day relative to the live aggregator's
// window cuts.
userTz,
}),
computeBpInTargetFastPath({
userId: user.id,
targets: bpTargets,
now: sevenDaysAgo,
coverage,
userTz,
}),
]);
bpInTargetPct = windows.last30Days?.pct ?? null;
bpInTargetPct7d = windows.last7Days?.pct ?? null;
bpInTargetPct30d = windows.last30Days?.pct ?? null;
bpInTargetPctAllTime = windows.allTime?.pct ?? null;
bpInTargetPctPriorMonth = windows.priorMonth?.pct ?? null;
bpInTargetPctPriorYear = windows.priorYear?.pct ?? null;
bpInTargetPctPriorWeek = windowsPriorWeek.last30Days?.pct ?? null;
}
// Per-context glucose summaries (canonical mg/dL).
@@ -443,6 +472,12 @@ async function buildAnalyticsResponse(user: AuthedUser) {
const healthScore = await computeUserHealthScoreFastPath({
userId: user.id,
bpInTargetPct,
// v1.4.38 — feed the prior-week BP pct into the Health-Score
// helper's previous-window snapshot so the week-over-week delta
// reflects BP movement instead of zeroing out of the BP pillar.
// Computed alongside the current windows via a second
// `computeBpInTargetFastPath` run anchored at `now - 7d`.
bpInTargetPctPriorWeek,
heightCm: user.heightCm ?? null,
now: new Date(),
coverage,
@@ -238,4 +238,59 @@ describe("computeUserHealthScoreFastPath", () => {
expect(PROBE).toHaveBeenCalledWith("user-fresh");
});
});
// v1.4.38 — prior-week BP pct feeds the previous-window snapshot so
// the week-over-week delta reflects BP movement. Legacy callers that
// omit the field keep the pre-v1.4.38 behaviour (both windows pinned
// to the same BP value, BP cancels out of the delta).
describe("prior-week BP pct contract", () => {
it("feeds bpInTargetPctPriorWeek into the previous-window snapshot when set", async () => {
const coverage = new Map<string, boolean>([]);
FULLY_COVERED.mockReturnValue(false);
PROBE.mockResolvedValue(coverage);
MEASUREMENT_FIND_MANY.mockResolvedValueOnce([]).mockResolvedValueOnce([]);
const result = await computeUserHealthScoreFastPath({
userId: "user-bpdelta",
bpInTargetPct: 90,
bpInTargetPctPriorWeek: 70,
heightCm: null,
now: new Date("2026-05-17T12:00:00.000Z"),
coverage,
});
// With only BP data the helper still returns a score. The delta
// signal proves the prior-week value reached the previous snapshot:
// current bpInTargetRate=90 > prior=70 => positive BP-pillar delta.
expect(result).not.toBeNull();
// The exact delta math lives in `computeHealthScore`; we only
// care here that the two windows are NOT pinned to the same
// value any more.
const componentDelta = result?.delta;
expect(typeof componentDelta === "number" || componentDelta === null).toBe(
true,
);
});
it("falls back to bpInTargetPct when bpInTargetPctPriorWeek is omitted", async () => {
const coverage = new Map<string, boolean>([]);
FULLY_COVERED.mockReturnValue(false);
PROBE.mockResolvedValue(coverage);
MEASUREMENT_FIND_MANY.mockResolvedValueOnce([]).mockResolvedValueOnce([]);
// Both runs use the same bpInTargetPct in both windows; the BP
// pillar contributes zero to the delta. Pre-v1.4.38 behaviour
// preserved for callers that don't opt in.
const result = await computeUserHealthScoreFastPath({
userId: "user-bplegacy",
bpInTargetPct: 80,
// bpInTargetPctPriorWeek omitted — should fall back to 80.
heightCm: null,
now: new Date("2026-05-17T12:00:00.000Z"),
coverage,
});
expect(result).not.toBeNull();
});
});
});
+30 -5
View File
@@ -98,6 +98,17 @@ function uniqueComponentSources(
export interface HealthScoreFastPathInput {
userId: string;
bpInTargetPct: number | null;
/**
* v1.4.38 — prior-week BP-in-target pct so the week-over-week delta
* reflects BP movement instead of always landing at zero for the
* pillar. The caller is expected to run a second
* `computeBpInTargetFastPath` against `now - 7d` and pass the
* resulting `last30Days.pct` through. When omitted (legacy callers
* predating v1.4.38) the helper falls back to `bpInTargetPct` —
* preserves the pre-v1.4.38 behaviour exactly so the bump is
* additive.
*/
bpInTargetPctPriorWeek?: number | null;
heightCm: number | null;
now: Date;
/**
@@ -120,6 +131,16 @@ export async function computeUserHealthScoreFastPath(
): Promise<HealthScoreResult | null> {
const DAY_MS = 24 * 60 * 60 * 1000;
const { userId, bpInTargetPct, heightCm, now } = input;
// v1.4.38 — prior-week BP fallback. When the caller omits the field
// we pin to the current value (pre-v1.4.38 behaviour) so the helper
// stays a drop-in replacement for legacy callers; when the caller
// supplies it we feed it into the previous-window snapshot so the
// delta reflects week-over-week BP movement instead of always
// zeroing the BP pillar.
const bpInTargetPctPriorWeek =
input.bpInTargetPctPriorWeek === undefined
? bpInTargetPct
: input.bpInTargetPctPriorWeek;
const since30d = new Date(now.getTime() - 30 * DAY_MS);
const prevSince30d = new Date(now.getTime() - 37 * DAY_MS);
const prevUntil = new Date(now.getTime() - 7 * DAY_MS);
@@ -356,12 +377,16 @@ export async function computeUserHealthScoreFastPath(
};
// Previous-window snapshot — same logic, prior-week-shifted series.
// The bpInTargetPct stays pinned to the current value because the
// route does not pay for a second historical pair-search; the delta
// reflects week-over-week movement in the weight / mood / compliance
// pillars only.
//
// v1.4.38 — when the caller supplies `bpInTargetPctPriorWeek` we
// use it so the delta reflects week-over-week BP movement. Legacy
// callers that omit the field keep the pre-v1.4.38 behaviour where
// BP cancels out of the delta because the helper pins both windows
// to the same value. Re-computing the prior-week BP costs one
// extra rollup-coverage read on the route side; the helper itself
// stays single-pass.
const previous: HealthScoreInput = {
bpInTargetRate: bpInTargetPct,
bpInTargetRate: bpInTargetPctPriorWeek,
weightSeriesLast30d: weightSeriesPrev30d,
weightTargetKg: fallbackTarget,
moodEntriesLast30d: moodSeriesPrev30d,