diff --git a/CHANGELOG.md b/CHANGELOG.md index 7174405eb..201caa4d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## [1.11.3] — 2026-06-04 — WHOOP body data, quality-of-life polish, fuller translations + +### Added + +- **WHOOP body data.** WHOOP weight and maximum-heart-rate context now flow in alongside recovery, sleep, cycle, and workout data. Weight is ranked below a real scale by your source priority, so a connected scale or manual entry still wins; your profile height is filled in from WHOOP only if you haven't set one yet. +- **Stop a Coach reply mid-stream.** A Stop control appears while the assistant is answering, and closing the Coach drawer now ends an in-flight response instead of leaving it running. +- **Undo a logged dose.** The confirmation after taking or skipping a dose now offers an Undo, so a mis-tap is one tap to reverse instead of a trip into the history. + +### Changed + +- **More of the app speaks your language.** Spanish, French, Italian, and Polish now cover the Settings, Medications, Insights rhythm-event, and Achievements copy that previously fell back to English. +- **A WHOOP plan that doesn't expose every data type no longer breaks the connection.** A data class WHOOP declines to share is skipped on its own while the rest keeps syncing. +- The first Coach reply on a cold cache arrives faster — its inputs are now gathered in parallel. + +### Fixed + +- **A failed dose log is no longer silent.** If recording a dose doesn't go through, you now see an error instead of a confirmation for something that didn't happen. +- The quick-entry sheets only ask before discarding when you have actually typed something. +- A failed export now shows a clear message instead of a raw error code. +- Saving feedback on an Insights recommendation surfaces an error if it can't be stored. +- Several loading placeholders now reserve their final height, removing the small layout shifts on the Insights and dashboard surfaces as content lands. + +### Accessibility + +- Reduced-motion now suppresses the Insights card entrance animation. +- The API-token revoke button carries an accessible name and a larger target, and the medication scheduler's step controls have a larger touch target. + +### Notes + +- WHOOP weight is the value stored in your WHOOP profile, not a scale reading; it is ranked last among weight sources and only surfaces when nothing higher-priority is present. + ## [1.11.2] — 2026-06-04 — Coach-memory controls, privacy hardening, more pinnable metrics ### Added diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 19c3d242a..91dc031e2 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: HealthLog API - version: 1.11.2 + version: 1.11.3 description: >- Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest. diff --git a/messages/de.json b/messages/de.json index fbd360987..4d10544f1 100644 --- a/messages/de.json +++ b/messages/de.json @@ -278,6 +278,12 @@ "emptyDescription": "Lege zuerst ein Medikament mit Zeitfenster an, damit du die Einnahme protokollieren kannst.", "emptyCta": "Medikament anlegen" }, + "quickEntryDiscard": { + "title": "Eintrag verwerfen?", + "description": "Du hast nicht gespeicherte Eingaben. Formular schließen und das Getippte verwerfen?", + "confirm": "Verwerfen", + "cancel": "Weiter bearbeiten" + }, "compliance7d": "Einnahmetreue (7 Tage)", "mood": "Stimmung", "customizeTitle": "Dashboard-Layout", @@ -622,6 +628,10 @@ "skipped": "Übersprungen", "intakeToastTaken": "{name} als genommen erfasst", "intakeToastSkipped": "{name} übersprungen", + "intakeToastFailed": "{name} konnte nicht erfasst werden – bitte erneut versuchen", + "intakeUndo": "Rückgängig", + "intakeUndone": "Eintrag zurückgenommen", + "intakeUndoFailed": "Rückgängig fehlgeschlagen – im Verlauf korrigieren", "weekdaySunday": "Sonntag", "weekdayMonday": "Montag", "weekdayTuesday": "Dienstag", @@ -1518,6 +1528,7 @@ "feedbackThanks": "Danke für dein Feedback", "feedbackConfirmed": "Feedback gespeichert", "feedbackAlreadyRated": "Bereits bewertet", + "feedbackError": "Dein Feedback konnte nicht gespeichert werden. Bitte versuche es erneut.", "confidence": "Vertrauen", "confidenceAria": "Vertrauen: {value} von 100", "confidenceHigh": "Hohes Vertrauen", @@ -1539,6 +1550,7 @@ "regenerateAnalysis": "Analyse neu starten", "regenerateSuccess": "Analyse wurde neu erstellt", "warmAssessments": "Auswertungen vorbereiten", + "warmAssessmentsHint": "Auswertungen werden über Nacht automatisch aktualisiert. Bereite sie jetzt vor, um den neuesten Stand zu sehen.", "warmStarted": "Auswertungen werden im Hintergrund erstellt", "narrativeTitle": "Dein Zeitraum im Rückblick", "narrativeWeek": "Diese Woche", @@ -1696,6 +1708,7 @@ "tagline": "Persönlicher Gesundheitscoach, auf deine Daten gestützt.", "newChat": "Neuer Chat", "send": "Senden", + "stop": "Stopp", "thinking": "Denke nach…", "composerPlaceholder": "Frag mich etwas zu deinen Daten…", "composerHint": "Enter zum Senden, Umschalt+Enter für eine neue Zeile.", @@ -3093,6 +3106,7 @@ "title": "Export", "description": "Lade deine Gesundheitsdaten als PDF, CSV oder vollständiges JSON-Backup herunter.", "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, Medikamenten-Compliance und (optional) Stimmungsverlauf, alles auf wenigen Seiten.", @@ -3457,6 +3471,7 @@ "tokenNamePlaceholder": "Token-Name (z.B. iPhone Shortcut)", "tokenCreated": "Token erstellt — kopiere ihn jetzt! Er wird nicht erneut angezeigt.", "tokenRevoke": "Token widerrufen?", + "tokenRevokeAction": "Token widerrufen", "tokenRevokeDescription": "Der Token wird unwiderruflich deaktiviert. Anwendungen, die ihn nutzen, verlieren den Zugriff.", "tokenRevoked": "Widerrufen", "tokenExpired": "Abgelaufen", diff --git a/messages/en.json b/messages/en.json index f82aea721..6f7eee95c 100644 --- a/messages/en.json +++ b/messages/en.json @@ -278,6 +278,12 @@ "emptyDescription": "Create a medication with a schedule first, then come back here to log the intake.", "emptyCta": "Add medication" }, + "quickEntryDiscard": { + "title": "Discard this entry?", + "description": "You have unsaved input. Close the form and lose what you typed?", + "confirm": "Discard", + "cancel": "Keep editing" + }, "compliance7d": "Compliance (7 days)", "mood": "Mood", "customizeTitle": "Dashboard layout", @@ -622,6 +628,10 @@ "skipped": "Skipped", "intakeToastTaken": "{name} recorded as taken", "intakeToastSkipped": "{name} skipped", + "intakeToastFailed": "Could not record {name} — try again", + "intakeUndo": "Undo", + "intakeUndone": "Entry reverted", + "intakeUndoFailed": "Could not undo — open the history to fix it", "weekdaySunday": "Sunday", "weekdayMonday": "Monday", "weekdayTuesday": "Tuesday", @@ -1518,6 +1528,7 @@ "feedbackThanks": "Thanks for your feedback", "feedbackConfirmed": "Feedback recorded", "feedbackAlreadyRated": "Already rated", + "feedbackError": "Could not save your feedback. Please try again.", "confidence": "Confidence", "confidenceAria": "Confidence: {value} of 100", "confidenceHigh": "High confidence", @@ -1539,6 +1550,7 @@ "regenerateAnalysis": "Re-run analysis", "regenerateSuccess": "Analysis refreshed", "warmAssessments": "Prepare assessments", + "warmAssessmentsHint": "Assessments refresh overnight automatically. Prepare them now to read the latest.", "warmStarted": "Assessments are being prepared in the background", "narrativeTitle": "Your period in review", "narrativeWeek": "This week", @@ -1696,6 +1708,7 @@ "tagline": "Personal health coach, grounded in your data.", "newChat": "New chat", "send": "Send", + "stop": "Stop", "thinking": "Thinking…", "composerPlaceholder": "Ask anything about your data…", "composerHint": "Press Enter to send, Shift+Enter for a new line.", @@ -3093,6 +3106,7 @@ "title": "Export", "description": "Download your health data as PDF, CSV, or a full JSON backup.", "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 compliance and (optionally) mood, all on a few pages.", @@ -3457,6 +3471,7 @@ "tokenNamePlaceholder": "Token name (e.g., iPhone Shortcut)", "tokenCreated": "Token created — copy it now! It won't be shown again.", "tokenRevoke": "Revoke token?", + "tokenRevokeAction": "Revoke token", "tokenRevokeDescription": "The token will be permanently deactivated. Applications using it will lose access.", "tokenRevoked": "Revoked", "tokenExpired": "Expired", diff --git a/messages/es.json b/messages/es.json index a45cc6212..31fbcc8a1 100644 --- a/messages/es.json +++ b/messages/es.json @@ -278,6 +278,12 @@ "emptyDescription": "Crea primero un medicamento con horario, después vuelve aquí para registrar la toma.", "emptyCta": "Añadir medicamento" }, + "quickEntryDiscard": { + "title": "¿Descartar esta entrada?", + "description": "Tienes datos sin guardar. ¿Cerrar el formulario y perder lo que escribiste?", + "confirm": "Descartar", + "cancel": "Seguir editando" + }, "compliance7d": "Cumplimiento (7 días)", "mood": "Estado de ánimo", "customizeTitle": "Diseño del panel", @@ -554,35 +560,35 @@ "scheduleDaily": "A diario", "inactive": "En pausa", "intakeHistory": "Historial de tomas", - "openDetailPage": "Open medication detail page", + "openDetailPage": "Abrir la página de detalle del medicamento", "deleteConfirm": "¿Eliminar medicamento?", "compliance": "Cumplimiento", - "importIntakes": "Import intakes", - "importDescription": "Paste a JSON array with intake data. Expected format:", - "importUploadFile": "Upload JSON file", - "importPaste": "Paste JSON here...", - "importSelected": "Selected: {name}", - "importFileLoaded": "File \"{name}\" loaded", - "importInvalidJson": "File does not contain valid JSON", - "importNoArray": "No array found", - "importResult": "{imported} intakes imported", - "importDuplicatesSkipped": "{count} duplicates skipped", - "importInvalidSkipped": "{count} invalid entries skipped", - "importFailed": "Import failed", - "importInvalidFormat": "Invalid JSON format", - "apiEndpointTitle": "API Endpoint: {name}", - "apiEndpointDescription": "Use this endpoint to record intakes via API (e.g., iPhone Shortcut or cron job).", - "apiEndpointActive": "API endpoint active", - "apiEndpointActivated": "API endpoint activated", - "apiEndpointDeactivated": "API endpoint deactivated", - "apiTokenCount": "Active ({count} tokens)", - "apiToken": "API Token", - "apiTokenActiveHint": "Endpoint is active. Existing tokens remain valid but cannot be shown in plain text again.", - "apiTokenActivateHint": "Activate the endpoint to create a new token.", - "apiTokenOnceHint": "The token is only shown in plain text once at creation.", - "requestExample": "Request example", - "statusLoadFailed": "Could not load status", - "changeFailed": "Change failed", + "importIntakes": "Importar tomas", + "importDescription": "Pega un array JSON con los datos de tomas. Formato esperado:", + "importUploadFile": "Subir archivo JSON", + "importPaste": "Pega el JSON aquí...", + "importSelected": "Seleccionado: {name}", + "importFileLoaded": "Archivo \"{name}\" cargado", + "importInvalidJson": "El archivo no contiene un JSON válido", + "importNoArray": "No se encontró ningún array", + "importResult": "{imported} tomas importadas", + "importDuplicatesSkipped": "{count} duplicados omitidos", + "importInvalidSkipped": "{count} entradas no válidas omitidas", + "importFailed": "Falló la importación", + "importInvalidFormat": "Formato JSON no válido", + "apiEndpointTitle": "Endpoint de API: {name}", + "apiEndpointDescription": "Usa este endpoint para registrar tomas a través de la API (p. ej., con un Atajo de iPhone o un cron job).", + "apiEndpointActive": "Endpoint de API activo", + "apiEndpointActivated": "Endpoint de API activado", + "apiEndpointDeactivated": "Endpoint de API desactivado", + "apiTokenCount": "Activo ({count} tokens)", + "apiToken": "Token de API", + "apiTokenActiveHint": "El endpoint está activo. Los tokens existentes siguen siendo válidos, pero no se pueden mostrar de nuevo en texto plano.", + "apiTokenActivateHint": "Activa el endpoint para crear un nuevo token.", + "apiTokenOnceHint": "El token solo se muestra en texto plano una vez, al crearlo.", + "requestExample": "Ejemplo de solicitud", + "statusLoadFailed": "No se pudo cargar el estado", + "changeFailed": "Falló el cambio", "categoryBloodPressure": "Presión arterial", "categoryVitamin": "Vitaminas", "categoryThyroid": "Tiroides", @@ -594,7 +600,7 @@ "categorySkin": "Cuidado de la piel", "categorySleepAid": "Para dormir", "categoryDiabetes": "Diabetes", - "categoryAntibiotic": "Antibiotic", + "categoryAntibiotic": "Antibiótico", "categoryOther": "Otros", "daysSun": "Do", "daysMon": "Lu", @@ -622,6 +628,10 @@ "skipped": "Omitida", "intakeToastTaken": "{name} registrada como tomada", "intakeToastSkipped": "{name} omitida", + "intakeToastFailed": "No se pudo registrar {name}; inténtalo de nuevo", + "intakeUndo": "Deshacer", + "intakeUndone": "Entrada revertida", + "intakeUndoFailed": "No se pudo deshacer; corrígelo en el historial", "weekdaySunday": "Domingo", "weekdayMonday": "Lunes", "weekdayTuesday": "Martes", @@ -691,7 +701,7 @@ "glp1Specific": "Específico de GLP-1" }, "entries": { - "nausea": "Náusea", + "nausea": "Náuseas", "vomiting": "Vómito", "diarrhea": "Diarrea", "constipation": "Estreñimiento", @@ -738,141 +748,141 @@ } }, "cadence": { - "section": "Cadence", - "label": "How often", + "section": "Frecuencia", + "label": "Con qué frecuencia", "kind": { - "daily": "Every day", - "weekdays": "Certain days of the week", - "everyNWeeks": "Every N weeks on certain days", - "monthly": "Monthly on a specific day", - "everyNMonths": "Every N months on a specific day", - "yearly": "Once a year", - "rolling": "Every N days from when I last took it (flexible)", - "rollingExplainer": "Counts from your last logged intake — pauses if you skip a dose.", - "oneShot": "One-time dose" + "daily": "Cada día", + "weekdays": "Determinados días de la semana", + "everyNWeeks": "Cada N semanas en determinados días", + "monthly": "Mensualmente un día concreto", + "everyNMonths": "Cada N meses un día concreto", + "yearly": "Una vez al año", + "rolling": "Cada N días desde la última toma (flexible)", + "rollingExplainer": "Cuenta desde tu última toma registrada — se pausa si te saltas una dosis.", + "oneShot": "Dosis única" }, "weekdays": { - "label": "Days of the week", + "label": "Días de la semana", "short": { "mo": "Mo", - "tu": "Tu", - "we": "We", - "th": "Th", + "tu": "Ma", + "we": "Mi", + "th": "Ju", "fr": "Fr", "sa": "Sa", - "su": "Su" + "su": "Do" }, "long": { - "mo": "Monday", - "tu": "Tuesday", - "we": "Wednesday", - "th": "Thursday", - "fr": "Friday", - "sa": "Saturday", - "su": "Sunday" + "mo": "Lunes", + "tu": "Martes", + "we": "Miércoles", + "th": "Jueves", + "fr": "Viernes", + "sa": "Sábado", + "su": "Domingo" } }, "intervalWeeks": { - "suffix": "weeks" + "suffix": "semanas" }, "dayOfMonth": { - "label": "Day" + "label": "Día" }, "intervalMonths": { - "suffix": "months", - "dayOnLabel": "on day" + "suffix": "meses", + "dayOnLabel": "el día" }, "yearly": { "date": { - "label": "Date" + "label": "Fecha" } }, "rollingDays": { - "suffix": "days" + "suffix": "días" } }, "timesOfDay": { - "section": "Times of day", - "label": "Times of day", + "section": "Horas del día", + "label": "Horas del día", "empty": { - "cta": "Add the first time." + "cta": "Añade la primera hora." }, - "add": "Add time", - "remove": "Remove", + "add": "Añadir hora", + "remove": "Quitar", "presets": { - "morning": "Morning", - "noon": "Noon", - "evening": "Evening", - "night": "Night" + "morning": "Mañana", + "noon": "Mediodía", + "evening": "Tarde", + "night": "Noche" }, "max": { - "reached": "Maximum reached — remove a time before adding another." + "reached": "Máximo alcanzado — quita una hora antes de añadir otra." } }, "courseWindow": { - "section": "Course window", + "section": "Periodo de tratamiento", "startsOn": { - "label": "Starts on" + "label": "Empieza el" }, "endsOn": { - "label": "Ends on" + "label": "Termina el" }, - "noEndDate": "No end date", - "oneShotCaption": "(one-time dose)", - "invalidRange": "End date must be on or after the start date." + "noEndDate": "Sin fecha de fin", + "oneShotCaption": "(dosis única)", + "invalidRange": "La fecha de fin debe ser igual o posterior a la de inicio." } }, "wizard": { "header": { - "stepOf": "Step {current} of {total}", - "createTitle": "New medication", - "editTitle": "Edit {name}" + "stepOf": "Paso {current} de {total}", + "createTitle": "Nuevo medicamento", + "editTitle": "Editar {name}" }, "nav": { - "back": "Back", - "next": "Next", - "save": "Save", - "saveEdit": "Save changes", + "back": "Atrás", + "next": "Siguiente", + "save": "Guardar", + "saveEdit": "Guardar cambios", "nextHint": "Siguiente: {step}", "reviewHint": "Siguiente: Guardar", "jumpFirst": "Ir al primer paso", "jumpLast": "Ir a revisar y guardar" }, "nl": { - "button": "Describe" + "button": "Describir" }, "steps": { "step1": { - "title": "What's the medication called?", - "subline": "Exactly as it appears on the box.", + "title": "¿Cómo se llama el medicamento?", + "subline": "Exactamente como aparece en la caja.", "label": "Name", - "placeholder": "e.g. Ramipril", + "placeholder": "p. ej. Ramipril", "short": "Nombre" }, "step2": { - "title": "What kind of medication is it?", - "subline": "We use this for the right templates and analytics.", + "title": "¿Qué tipo de medicamento es?", + "subline": "Lo usamos para las plantillas y los análisis adecuados.", "short": "Tipo" }, "step3": { - "title": "What's the dose?", - "subline": "A tablet, a puff, a drop — depending on the form.", - "amountLabel": "Amount", - "amountPlaceholder": "e.g. 5", - "unitLabel": "Unit", + "title": "¿Cuál es la dosis?", + "subline": "Una pastilla, una inhalación, una gota — según la forma.", + "amountLabel": "Cantidad", + "amountPlaceholder": "p. ej. 5", + "unitLabel": "Unidad", "unit": { "mg": "mg", "ml": "ml", - "iu": "IU", + "iu": "UI", "mcg": "µg", "g": "g", - "tablets": "tablet(s)", - "capsules": "capsule(s)", - "drops": "drops", - "puffs": "puff", - "sprays": "spray", - "pieces": "pieces", - "other": "other" + "tablets": "comprimido(s)", + "capsules": "cápsula(s)", + "drops": "gotas", + "puffs": "inhalación", + "sprays": "pulverización", + "pieces": "unidades", + "other": "otro" }, "deliveryFormLabel": "Vía", "deliveryForm": { @@ -888,124 +898,124 @@ "short": "Dosis" }, "step4": { - "title": "Over what period are you taking it?", - "subline": "A start date is enough for today. You can add an end date later.", + "title": "¿Durante qué periodo lo tomas?", + "subline": "Por hoy basta con una fecha de inicio. Puedes añadir una fecha de fin más tarde.", "short": "Cuándo" }, "step5": { - "title": "How often do you take it?", - "subline": "Pick the cadence — the details follow on the next step.", + "title": "¿Con qué frecuencia lo tomas?", + "subline": "Elige la frecuencia — los detalles vienen en el siguiente paso.", "short": "Frecuencia" }, "step6": { - "title": "What does that look like in detail?", - "subline": "Set weekdays, intervals, or the day of the month.", + "title": "¿Cómo es exactamente?", + "subline": "Indica los días de la semana, los intervalos o el día del mes.", "intervalWeeks": { - "label": "Every", - "suffix": "weeks" + "label": "Cada", + "suffix": "semanas" }, "dayOfMonth": { - "label": "On", - "suffix": "day of the month" + "label": "El", + "suffix": "día del mes" }, "rollingDays": { - "label": "Every", - "suffix": "days from the last intake" + "label": "Cada", + "suffix": "días desde la última toma" }, "short": "Detalle" }, "step7": { - "title": "When do you take it?", - "subline": "One or more times of day — reminders follow this list.", - "presetsLabel": "Time-of-day presets", + "title": "¿Cuándo lo tomas?", + "subline": "Una o varias horas del día — los recordatorios siguen esta lista.", + "presetsLabel": "Preajustes por hora del día", "presets": { - "morning": "Morning", - "noon": "Noon", - "evening": "Evening", - "night": "Night" + "morning": "Mañana", + "noon": "Mediodía", + "evening": "Tarde", + "night": "Noche" }, "short": "Horas" }, "step8": { - "title": "All done — should HealthLog remind you?", - "subline": "Review everything below before you save.", - "remindersLabel": "Reminders on", - "remindersDescription": "Push, Telegram or ntfy — based on your settings.", - "multiScheduleNote": "This medication has multiple schedules. The detailed editor returns in a later release.", + "title": "Listo — ¿quieres que HealthLog te lo recuerde?", + "subline": "Revisa todo abajo antes de guardar.", + "remindersLabel": "Recordatorios activados", + "remindersDescription": "Push, Telegram o ntfy — según tus ajustes.", + "multiScheduleNote": "Este medicamento tiene varios horarios. El editor detallado volverá en una versión posterior.", "short": "Listo" } }, "classRow": { - "bloodPressure": "Blood pressure", + "bloodPressure": "Presión arterial", "diabetes": "Diabetes", - "hormone": "Hormones", - "glp1": "GLP-1 injection", - "painRelief": "Pain", - "allergy": "Allergy", - "vitamin": "Vitamins", - "supplement": "Supplement", - "antibiotic": "Antibiotic", - "other": "Other" + "hormone": "Hormonas", + "glp1": "Inyección de GLP-1", + "painRelief": "Dolor", + "allergy": "Alergia", + "vitamin": "Vitaminas", + "supplement": "Suplemento", + "antibiotic": "Antibiótico", + "other": "Otro" }, "cadence": { "daily": { - "label": "Daily", - "description": "Every day at the same time." + "label": "Diario", + "description": "Cada día a la misma hora." }, "weekdays": { - "label": "Specific weekdays", - "description": "E.g. Monday, Wednesday, Friday." + "label": "Días concretos de la semana", + "description": "P. ej. lunes, miércoles, viernes." }, "everyNWeeks": { - "label": "Every few weeks", - "description": "E.g. every 2 weeks on Thursdays (GLP-1, biologics)." + "label": "Cada pocas semanas", + "description": "P. ej. cada 2 semanas los jueves (GLP-1, biológicos)." }, "monthly": { - "label": "Monthly", - "description": "On a fixed day each month (e.g. always the 1st)." + "label": "Mensual", + "description": "Un día fijo cada mes (p. ej. siempre el 1)." }, "rolling": { - "label": "Flexible from last dose", - "description": "Leave the next dose open. When you tap 'taken', the counter starts again from that moment." + "label": "Flexible desde la última dosis", + "description": "Deja abierta la próxima dosis. Cuando pulsas «tomada», el contador empieza de nuevo desde ese momento." }, "oneShot": { - "label": "Single dose", - "description": "One administration, e.g. a vaccine or an antibiotic course." + "label": "Dosis única", + "description": "Una sola administración, p. ej. una vacuna o un ciclo de antibiótico." } }, "summary": { - "title": "Summary", + "title": "Resumen", "cadence": { - "daily": "Every day", - "weekdays": "On selected days of the week", - "biweekly": "Every two weeks", - "monthly": "Monthly", - "quarterly": "Every three months", - "yearly": "Once a year", - "rolling": "Every {n} days from your last intake", - "oneShot": "A single dose", - "everyNWeeks": "Every {n} weeks", - "everyNMonths": "Every {n} months" + "daily": "Cada día", + "weekdays": "En los días de la semana seleccionados", + "biweekly": "Cada dos semanas", + "monthly": "Mensual", + "quarterly": "Cada tres meses", + "yearly": "Una vez al año", + "rolling": "Cada {n} días desde tu última toma", + "oneShot": "Una dosis única", + "everyNWeeks": "Cada {n} semanas", + "everyNMonths": "Cada {n} meses" }, - "weekdaysDetail": ", on {days}", - "dayOfMonthDetail": ", on day {day}.", - "times": "at {times}", - "startsOn": "Starts: {date}", - "endsOn": "Ends: {date}", - "noEndDate": "No end date" + "weekdaysDetail": ", los {days}", + "dayOfMonthDetail": ", el día {day}.", + "times": "a las {times}", + "startsOn": "Empieza: {date}", + "endsOn": "Termina: {date}", + "noEndDate": "Sin fecha de fin" }, "compose": { - "scheduleIndex": "Schedule {n} of {total}", + "scheduleIndex": "Horario {n} de {total}", "list": { - "add": "Add another schedule", - "edit": "Edit", - "remove": "Remove", - "removeDisabled": "At least one schedule is required.", - "empty": "No schedule yet." + "add": "Añadir otro horario", + "edit": "Editar", + "remove": "Quitar", + "removeDisabled": "Se requiere al menos un horario.", + "empty": "Aún no hay horario." } }, "errors": { - "submitFailed": "Could not save the medication — please retry." + "submitFailed": "No se pudo guardar el medicamento — inténtalo de nuevo." } }, "doseStrength": { @@ -1160,88 +1170,88 @@ }, "detail": { "status": { - "active": "Active", - "paused": "Paused", - "ended": "Ended" + "active": "Activo", + "paused": "Pausado", + "ended": "Finalizado" }, "today": { - "groupLabel": "Today's dose", - "taken": "Taken", - "skipped": "Skipped", - "toastTaken": "Today's dose logged", - "toastSkipped": "Today's dose skipped", - "recordedTaken": "Taken today at {time}", - "recordedSkipped": "Skipped today at {time}", - "recordedOneShot": "One-shot dose taken at {time}", - "noneScheduled": "No dose scheduled for today.", - "pausedHint": "Paused — no reminder today.", - "error": "Could not log the dose." + "groupLabel": "Dosis de hoy", + "taken": "Tomada", + "skipped": "Omitida", + "toastTaken": "Dosis de hoy registrada", + "toastSkipped": "Dosis de hoy omitida", + "recordedTaken": "Tomada hoy a las {time}", + "recordedSkipped": "Omitida hoy a las {time}", + "recordedOneShot": "Dosis única tomada a las {time}", + "noneScheduled": "No hay ninguna dosis programada para hoy.", + "pausedHint": "En pausa — hoy no hay recordatorio.", + "error": "No se pudo registrar la dosis." }, "cadence": { - "oneShotOn": "One-time dose on {date}.", - "oneShotPending": "One-time dose pending." + "oneShotOn": "Dosis única el {date}.", + "oneShotPending": "Dosis única pendiente." }, "intake": { - "title": "Intake history", - "importButton": "Import", + "title": "Historial de tomas", + "importButton": "Importar", "rowActions": { - "openMenu": "Open row actions", - "edit": "Edit", - "delete": "Delete" + "openMenu": "Abrir acciones de la fila", + "edit": "Editar", + "delete": "Eliminar" }, "selection": { - "rowToggleLabel": "Select row" + "rowToggleLabel": "Seleccionar fila" }, "bulkDelete": { - "selectionCount": "{count} entries selected", - "deleteButton": "Delete selection", - "cancelButton": "Cancel", - "confirmTitle": "Delete the selected entries?", - "confirmBody": "{count} entries will be removed permanently. The medication itself stays.", - "confirmAction": "Delete", - "toast": "Selection deleted", - "failed": "Could not delete the selection." + "selectionCount": "{count} entradas seleccionadas", + "deleteButton": "Eliminar selección", + "cancelButton": "Cancelar", + "confirmTitle": "¿Eliminar las entradas seleccionadas?", + "confirmBody": "Se eliminarán {count} entradas de forma permanente. El medicamento se conserva.", + "confirmAction": "Eliminar", + "toast": "Selección eliminada", + "failed": "No se pudo eliminar la selección." }, "edit": { - "dialogTitle": "Edit intake", - "takenAtLabel": "Taken at", - "skippedLabel": "Skipped", - "noteLabel": "Note", - "save": "Save", - "cancel": "Cancel", - "savedToast": "Intake updated", - "failed": "Could not update the intake." + "dialogTitle": "Editar toma", + "takenAtLabel": "Tomada a las", + "skippedLabel": "Omitida", + "noteLabel": "Nota", + "save": "Guardar", + "cancel": "Cancelar", + "savedToast": "Toma actualizada", + "failed": "No se pudo actualizar la toma." }, "deleteRow": { - "confirmTitle": "Delete this intake?", - "confirmBody": "The entry is removed permanently.", - "confirmAction": "Delete", - "toast": "Intake deleted", - "failed": "Could not delete the intake." + "confirmTitle": "¿Eliminar esta toma?", + "confirmBody": "La entrada se elimina de forma permanente.", + "confirmAction": "Eliminar", + "toast": "Toma eliminada", + "failed": "No se pudo eliminar la toma." } }, "notifications": { - "title": "Notifications", - "switchLabel": "Send a reminder when this dose is due", - "helperOn": "Reminders are on.", - "helperOff": "Reminders are off.", - "enabledToast": "Reminders on", - "disabledToast": "Reminders off", - "toggleFailed": "Could not change the reminder setting.", - "clientManagedChip": "Your iPhone manages reminders for this medication." + "title": "Recordatorios", + "switchLabel": "Enviar un recordatorio cuando toque esta dosis", + "helperOn": "Los recordatorios están activados.", + "helperOff": "Los recordatorios están desactivados.", + "enabledToast": "Recordatorios activados", + "disabledToast": "Recordatorios desactivados", + "toggleFailed": "No se pudo cambiar el ajuste de recordatorios.", + "clientManagedChip": "Tu iPhone gestiona los recordatorios de este medicamento." }, "settings": { - "title": "Settings", + "title": "Ajustes", "phases": { - "openButton": "Configure phases", - "requiresCourseWindow": "Phases are available once a course window is set." + "openButton": "Configurar fases", + "requiresCourseWindow": "Las fases están disponibles una vez fijado un periodo de tratamiento." }, "grace": { - "label": "Reminder window — applies to your primary schedule", - "primaryScheduleNote": "Multi-schedule medications keep their other schedules' default window.", - "unit": "minutes", - "saved": "Reminder window saved", - "failed": "Could not save the reminder window." + "label": "Ventana de recordatorio — se aplica a tu horario principal", + "primaryScheduleNote": "Los medicamentos con varios horarios mantienen la ventana predeterminada de sus demás horarios.", + "unit": "minutos", + "saved": "Ventana de recordatorio guardada", + "failed": "No se pudo guardar la ventana de recordatorio." }, "codes": { "label": "Códigos clínicos (ATC / RxNorm)", @@ -1253,57 +1263,57 @@ } }, "zone": { - "title": "Manage & danger zone", + "title": "Gestión y zona de peligro", "pause": { - "title": "Pause", - "helper": "Pause reminders until you turn them back on. History stays intact.", - "pausedToast": "Reminders paused", - "resumedToast": "Reminders resumed", - "failed": "Could not change the paused state." + "title": "Pausar", + "helper": "Pausa los recordatorios hasta que los reactives. El historial se conserva.", + "pausedToast": "Recordatorios pausados", + "resumedToast": "Recordatorios reanudados", + "failed": "No se pudo cambiar el estado de pausa." }, "end": { - "title": "End medication", - "helper": "Stop reminders and mark the medication as ended. History stays visible.", - "button": "End", - "dialogTitle": "End {name} — stop reminders, history stays visible", - "dialogBody": "No more reminders. Old entries remain in the timeline.", - "toast": "Medication ended", - "failed": "Could not end the medication." + "title": "Finalizar medicamento", + "helper": "Detiene los recordatorios y marca el medicamento como finalizado. El historial sigue siendo visible.", + "button": "Finalizar", + "dialogTitle": "Finalizar {name} — detiene los recordatorios, el historial sigue visible", + "dialogBody": "No habrá más recordatorios. Las entradas antiguas permanecen en la línea de tiempo.", + "toast": "Medicamento finalizado", + "failed": "No se pudo finalizar el medicamento." }, "purge": { - "title": "Delete history", - "helper": "{count} intake events are stored for this medication.", - "button": "Delete history", - "dialogTitle": "Really delete the history?", - "dialogBody": "The {count} intake events will be removed permanently. The medication itself stays.", - "toast": "History deleted", - "failed": "Could not delete the history." + "title": "Eliminar historial", + "helper": "Hay {count} tomas registradas para este medicamento.", + "button": "Eliminar historial", + "dialogTitle": "¿Eliminar realmente el historial?", + "dialogBody": "Las {count} tomas se eliminarán de forma permanente. El medicamento se conserva.", + "toast": "Historial eliminado", + "failed": "No se pudo eliminar el historial." }, "delete": { - "title": "Delete medication", - "helper": "Removes the medication and every related record.", - "button": "Delete", - "dialogTitle": "Delete {name}?", - "dialogBody": "The medication, its schedules, intake history, API tokens, phase config and reminders will be removed permanently.", - "toast": "Medication deleted", - "failed": "Could not delete the medication." + "title": "Eliminar medicamento", + "helper": "Elimina el medicamento y todos los registros relacionados.", + "button": "Eliminar", + "dialogTitle": "¿Eliminar {name}?", + "dialogBody": "El medicamento, sus horarios, el historial de tomas, los tokens de API, la configuración de fases y los recordatorios se eliminarán de forma permanente.", + "toast": "Medicamento eliminado", + "failed": "No se pudo eliminar el medicamento." } }, "phases": { - "modeLabel": "{phase} mode", - "saveFailed": "Could not save the reminder phases." + "modeLabel": "Modo de {phase}", + "saveFailed": "No se pudieron guardar las fases de recordatorio." }, "api": { - "caption": "Endpoint for \"{name}\"", - "copyUrl": "Copy URL", - "urlCopied": "URL copied", - "mintToken": "Mint token", - "mintAnotherToken": "Mint another token", - "mintFailed": "Could not mint a token.", - "copyFailed": "Could not copy.", - "tokenCopied": "Token copied", - "copyToken": "Copy token", - "mintedHint": "Copy this token now — it is shown only once." + "caption": "Endpoint para «{name}»", + "copyUrl": "Copiar URL", + "urlCopied": "URL copiada", + "mintToken": "Generar token", + "mintAnotherToken": "Generar otro token", + "mintFailed": "No se pudo generar un token.", + "copyFailed": "No se pudo copiar.", + "tokenCopied": "Token copiado", + "copyToken": "Copiar token", + "mintedHint": "Copia este token ahora — solo se muestra una vez." }, "edit": { "planOption": "Editar plan", @@ -1518,6 +1528,7 @@ "feedbackThanks": "Gracias por tu opinión", "feedbackConfirmed": "Opinión guardada", "feedbackAlreadyRated": "Ya valorado", + "feedbackError": "No se pudo guardar tu opinión. Inténtalo de nuevo.", "confidence": "Confianza", "confidenceAria": "Confianza: {value} de 100", "confidenceHigh": "Confianza alta", @@ -1539,6 +1550,7 @@ "regenerateAnalysis": "Volver a iniciar el análisis", "regenerateSuccess": "Análisis regenerado", "warmAssessments": "Preparar evaluaciones", + "warmAssessmentsHint": "Las evaluaciones se actualizan automáticamente durante la noche. Prepáralas ahora para ver lo más reciente.", "warmStarted": "Las evaluaciones se están preparando en segundo plano", "narrativeTitle": "Tu período en resumen", "narrativeWeek": "Esta semana", @@ -1696,6 +1708,7 @@ "tagline": "Coach personal de salud, apoyado en tus datos.", "newChat": "Nueva conversación", "send": "Enviar", + "stop": "Detener", "thinking": "Pensando…", "composerPlaceholder": "Pregúntame sobre tus datos…", "composerHint": "Intro para enviar, Mayús+Intro para una nueva línea.", @@ -2784,19 +2797,19 @@ "loadError": "No se pudieron cargar las notificaciones de tu dispositivo.", "disclaimer": "Estas son alertas que tu dispositivo generó con su propia detección integrada. Se muestran aquí solo para tu información. No es una evaluación médica de HealthLog, y HealthLog no diagnostica ninguna afección. Si una notificación te preocupa, habla con tu médico.", "event": { - "irregularRhythm": "Irregular rhythm notification", - "highHeartRate": "High heart rate notification", - "lowHeartRate": "Low heart rate notification", - "walkingSteadiness": "Walking steadiness alert", - "breathingDisturbance": "Breathing disturbance during sleep" + "irregularRhythm": "Notificación de ritmo irregular", + "highHeartRate": "Notificación de frecuencia cardíaca alta", + "lowHeartRate": "Notificación de frecuencia cardíaca baja", + "walkingSteadiness": "Alerta de estabilidad al caminar", + "breathingDisturbance": "Alteración respiratoria durante el sueño" }, "verdict": { - "irregular": "Your device flagged a possible irregular rhythm.", - "notDetected": "Your device did not flag an irregular rhythm.", - "inconclusive": "Your device could not make a reading.", - "low": "Your device flagged low walking steadiness.", - "veryLow": "Your device flagged very low walking steadiness.", - "fired": "Your device flagged this event." + "irregular": "Tu dispositivo detectó un posible ritmo irregular.", + "notDetected": "Tu dispositivo no detectó un ritmo irregular.", + "inconclusive": "Tu dispositivo no pudo realizar una medición.", + "low": "Tu dispositivo detectó una estabilidad al caminar baja.", + "veryLow": "Tu dispositivo detectó una estabilidad al caminar muy baja.", + "fired": "Tu dispositivo señaló este evento." } } }, @@ -2998,24 +3011,24 @@ "disableError": "No se pudo desactivar el modo Investigación. Inténtalo de nuevo." }, "shell": { - "sectionsNav": "Settings sections" + "sectionsNav": "Secciones de ajustes" }, "sections": { "account": { "title": "Cuenta", - "description": "Profile, password, passkeys, and your onboarding tour." + "description": "Perfil, contraseña, claves de acceso y tu recorrido de incorporación." }, "integrations": { "title": "Integraciones", - "description": "Withings, moodLog, and other connected services." + "description": "Withings, moodLog y otros servicios conectados." }, "notifications": { "title": "Notificaciones", - "description": "Live overview of every configured channel." + "description": "Vista en vivo de cada canal configurado." }, "dashboard": { "title": "Panel", - "description": "Tile layout and order." + "description": "Diseño y orden de los mosaicos." }, "thresholds": { "title": "Objetivos", @@ -3078,12 +3091,12 @@ } }, "ai": { - "title": "AI Insights", - "description": "Provider, model, key." + "title": "Análisis con IA", + "description": "Proveedor, modelo, clave." }, "api": { "title": "API & Tokens", - "description": "Bearer tokens for your own scripts and apps — log measurements or medication intake from anywhere." + "description": "Tokens Bearer para tus propios scripts y apps — registra mediciones o tomas de medicación desde cualquier lugar." }, "advanced": { "title": "Avanzado", @@ -3091,8 +3104,9 @@ }, "export": { "title": "Exportar", - "description": "Download your health data as PDF, CSV, or a full JSON backup.", + "description": "Descarga tus datos de salud como PDF, CSV o una copia de seguridad JSON completa.", "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.", @@ -3100,39 +3114,39 @@ "formatHint": "PDF · listo para imprimir" }, "actions": { - "download": "Download" + "download": "Descargar" }, "filters": { "since": "Desde", - "until": "Until" + "until": "Hasta" }, "cards": { "doctorReport": { - "title": "Doctor Report", - "description": "Printable PDF for the doctor's appointment — vitals, BMI, BP classification, medication compliance, mood." + "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": "Measurements", - "description": "Weight, blood pressure, pulse, glucose, and every other measurement as comma-separated values." + "title": "Mediciones", + "description": "Peso, presión arterial, pulso, glucosa y todas las demás mediciones como valores separados por comas." }, "medicationsCsv": { "title": "Medicamentos", - "description": "Your medication list with dosage, schedules, and (optionally) the full intake history.", - "includeIntake": "Include intake history" + "description": "Tu lista de medicación con dosis, horarios y (opcionalmente) el historial completo de tomas.", + "includeIntake": "Incluir historial de tomas" }, "moodCsv": { "title": "Estado de ánimo", - "description": "Daily mood entries with score, tags, and timestamps." + "description": "Entradas de estado de ánimo diarias con puntuación, etiquetas y marcas de tiempo." }, "fullBackup": { - "title": "Full backup", - "description": "Single JSON file with everything — same shape as the weekly auto-backup. Useful for self-restore via admin upload." + "title": "Copia completa", + "description": "Un único archivo JSON con todo — el mismo formato que la copia automática semanal. Útil para restaurar tú mismo mediante la carga de administrador." } } }, "about": { - "title": "About", - "description": "Version, license, links." + "title": "Acerca de", + "description": "Versión, licencia, enlaces." }, "sharing": { "title": "Compartir", @@ -3141,20 +3155,20 @@ }, "profile": "Perfil", "language": "Idioma", - "languageDescription": "Choose the display language of the application.", + "languageDescription": "Elige el idioma de visualización de la aplicación.", "username": "Nombre de usuario", - "height": "Height (cm)", + "height": "Altura (cm)", "dateOfBirth": "Fecha de nacimiento", - "dateOfBirthHint": "Used for automatic blood pressure target calculations.", + "dateOfBirthHint": "Se usa para el cálculo automático de los objetivos de presión arterial.", "gender": "Sexo", - "genderNone": "Not specified", + "genderNone": "Sin especificar", "genderMale": "Hombre", "genderFemale": "Mujer", - "genderHint": "Used for gender-specific target values.", + "genderHint": "Se usa para valores objetivo específicos por sexo.", "timezone": "Zona horaria", - "timezoneHint": "Used for chart axis labels, reminder times, export timestamps, and Coach context. Stored data is unchanged.", - "timezoneInvalid": "Not a valid IANA timezone.", - "profileSaved": "Profile saved", + "timezoneHint": "Se usa para las etiquetas de los ejes de los gráficos, las horas de recordatorio, las marcas de tiempo de exportación y el contexto del Coach. Los datos almacenados no cambian.", + "timezoneInvalid": "No es una zona horaria IANA válida.", + "profileSaved": "Perfil guardado", "avatar": { "title": "Foto de perfil", "description": "Sube una foto que se almacena en tu propio servidor. Aparece en toda la aplicación en lugar de un servicio de avatares externo.", @@ -3168,174 +3182,174 @@ "tooLarge": "La imagen supera el límite de 2 MB.", "error": "No se pudo actualizar la foto de perfil. Inténtalo de nuevo." }, - "changePassword": "Change password", - "changePasswordDescription": "Replace your current password with a new one.", - "passwordReset": "Password reset", - "currentPassword": "Current password", - "newPassword": "New password", - "confirmNewPassword": "Confirm new password", - "passwordMismatch": "New passwords do not match", - "passwordUpdated": "Password updated successfully", + "changePassword": "Cambiar contraseña", + "changePasswordDescription": "Sustituye tu contraseña actual por una nueva.", + "passwordReset": "Restablecer contraseña", + "currentPassword": "Contraseña actual", + "newPassword": "Nueva contraseña", + "confirmNewPassword": "Confirmar nueva contraseña", + "passwordMismatch": "Las nuevas contraseñas no coinciden", + "passwordUpdated": "Contraseña actualizada correctamente", "passkeys": "Passkeys", - "registeredPasskeys": "Registered Passkeys", - "passkeysDescription": "Passkeys allow secure login without a password.", + "registeredPasskeys": "Claves de acceso registradas", + "passkeysDescription": "Las claves de acceso permiten iniciar sesión de forma segura sin contraseña.", "passkeyName": "Nombre", - "passkeyDevice": "Device", + "passkeyDevice": "Dispositivo", "passkeyBackup": "Backup", - "passkeyCreated": "Created", + "passkeyCreated": "Creada", "passkeyActions": "Acciones", - "noPasskeys": "No passkeys registered.", - "addPasskey": "Add passkey", - "passkeyAdded": "Passkey added successfully!", - "passkeyOptionsError": "Could not load passkey options", - "passkeyRegistrationFailed": "Passkey registration failed", - "passkeyRegistrationCancelled": "Passkey registration cancelled", - "passkeyNotSupported": "Your browser or device doesn't support passkeys yet. Try a different browser or device.", - "passkeyAlreadyRegistered": "This device is already registered as a passkey for your account.", - "passkeySecurityBlocked": "Your browser blocked the passkey request for security reasons. Make sure you're on a trusted HTTPS origin and try again.", - "passkeyTimeout": "The passkey prompt timed out. Please try again.", - "passkeyUnknownError": "Passkey registration failed: {message}", - "deletePasskey": "Delete passkey?", - "deletePasskeyDescription": "The passkey will be permanently deleted.", - "singleDevice": "Single device", - "multiDevice": "Multi-device", - "backedUp": "backed up", + "noPasskeys": "No hay claves de acceso registradas.", + "addPasskey": "Añadir clave de acceso", + "passkeyAdded": "¡Clave de acceso añadida correctamente!", + "passkeyOptionsError": "No se pudieron cargar las opciones de la clave de acceso", + "passkeyRegistrationFailed": "Falló el registro de la clave de acceso", + "passkeyRegistrationCancelled": "Registro de la clave de acceso cancelado", + "passkeyNotSupported": "Tu navegador o dispositivo aún no admite claves de acceso. Prueba con otro navegador o dispositivo.", + "passkeyAlreadyRegistered": "Este dispositivo ya está registrado como clave de acceso de tu cuenta.", + "passkeySecurityBlocked": "Tu navegador bloqueó la solicitud de clave de acceso por motivos de seguridad. Asegúrate de estar en un origen HTTPS de confianza e inténtalo de nuevo.", + "passkeyTimeout": "La solicitud de la clave de acceso expiró. Inténtalo de nuevo.", + "passkeyUnknownError": "Falló el registro de la clave de acceso: {message}", + "deletePasskey": "¿Eliminar clave de acceso?", + "deletePasskeyDescription": "La clave de acceso se eliminará de forma permanente.", + "singleDevice": "Un solo dispositivo", + "multiDevice": "Varios dispositivos", + "backedUp": "con copia de seguridad", "telegram": "Telegram Notifications", "telegramDescription": "Get reminders for missed medication intake via Telegram.", - "telegramSaved": "Telegram settings saved", - "botToken": "Bot Token", - "chatId": "Chat ID", - "enableNotifications": "Enable notifications", - "testMessage": "Test message", - "testSent": "Test message sent!", - "telegramStep1": "1. Create a bot via @BotFather in Telegram and copy the token.", - "telegramStep2": "2. Send /start to your bot to activate the chat.", - "telegramStep3": "3. Find your Chat ID via @userinfobot or the Bot API.", + "telegramSaved": "Ajustes de Telegram guardados", + "botToken": "Token del bot", + "chatId": "ID del chat", + "enableNotifications": "Activar notificaciones", + "testMessage": "Mensaje de prueba", + "testSent": "¡Mensaje de prueba enviado!", + "telegramStep1": "1. Crea un bot con @BotFather en Telegram y copia el token.", + "telegramStep2": "2. Envía /start a tu bot para activar el chat.", + "telegramStep3": "3. Obtén tu ID del chat con @userinfobot o la API del bot.", "ntfy": "ntfy", - "ntfyDescription": "Notifications via ntfy (self-hosted or ntfy.sh).", - "ntfyEnable": "Enable ntfy", - "ntfyServer": "Server URL", + "ntfyDescription": "Notificaciones a través de ntfy (autoalojado o ntfy.sh).", + "ntfyEnable": "Activar ntfy", + "ntfyServer": "URL del servidor", "ntfyTopic": "Topic", - "ntfyAuthToken": "Auth Token (optional)", - "ntfyAuthTokenHint": "Only needed for private topics with access control.", - "webPush": "Browser Push", - "webPushDescription": "Receive notifications directly in your browser, even when HealthLog is not open.", - "webPushNotSupported": "Your browser does not support push notifications.", - "webPushDenied": "Push notifications are blocked. Allow them in your browser settings.", - "webPushNotConfigured": "Web Push is not configured on the server.", - "webPushSubscribe": "Enable push", - "webPushUnsubscribe": "Disable push", - "webPushSubscribed": "Push notifications enabled!", - "webPushUnsubscribed": "Push notifications disabled.", - "webPushSubscribeFailed": "Activation failed", + "ntfyAuthToken": "Token de autenticación (opcional)", + "ntfyAuthTokenHint": "Solo necesario para temas privados con control de acceso.", + "webPush": "Push del navegador", + "webPushDescription": "Recibe notificaciones directamente en tu navegador, incluso cuando HealthLog no está abierto.", + "webPushNotSupported": "Tu navegador no admite notificaciones push.", + "webPushDenied": "Las notificaciones push están bloqueadas. Permítelas en los ajustes de tu navegador.", + "webPushNotConfigured": "Web Push no está configurado en el servidor.", + "webPushSubscribe": "Activar push", + "webPushUnsubscribe": "Desactivar push", + "webPushSubscribed": "¡Notificaciones push activadas!", + "webPushUnsubscribed": "Notificaciones push desactivadas.", + "webPushSubscribeFailed": "Falló la activación", "webPushActive": "Activo", - "kiInsightsDescription": "Optional: Save your OpenAI key for daily, automatic evaluations in Insights.", - "codexConnected": "ChatGPT connected successfully! Insights are now active.", - "codexDisconnected": "ChatGPT connection removed.", - "codexConnectionFailed": "ChatGPT connection failed. Please try again.", - "rawData": "Send raw data", - "rawDataOnDescription": "Aggregated metrics plus anonymized raw points from the last 30 days are sent. This includes per-metric measurements (for example weight, blood pressure, pulse) with time context so the provider can detect patterns, outliers, and correlations more reliably. Name, email, and direct account identifiers are not transmitted.", - "rawDataOffDescription": "Only summarized metrics are sent (for example averages, trends, minimum/maximum). No individual points and no exact time context. This is more privacy-preserving, but the provider can be less precise for short-term patterns and fluctuations.", - "rawDataWarning": "Raw mode enabled: the provider receives additional anonymized points from the last 30 days for higher analysis accuracy.", - "regenerateInsights": "Regenerate reports", - "regenerateSuccess": "Reports regenerated successfully", - "regenerateRateLimit": "Please wait — you've hit the hourly limit for analyses.", - "lastGeneratedAt": "Last generated", + "kiInsightsDescription": "Opcional: guarda tu clave de OpenAI para análisis diarios y automáticos en Insights.", + "codexConnected": "¡ChatGPT conectado correctamente! Insights ya está activo.", + "codexDisconnected": "Conexión con ChatGPT eliminada.", + "codexConnectionFailed": "Falló la conexión con ChatGPT. Inténtalo de nuevo.", + "rawData": "Enviar datos en bruto", + "rawDataOnDescription": "Se envían métricas agregadas más puntos en bruto anonimizados de los últimos 30 días. Esto incluye mediciones por métrica (por ejemplo, peso, presión arterial, pulso) con contexto temporal para que el proveedor pueda detectar patrones, valores atípicos y correlaciones de forma más fiable. No se transmiten el nombre, el correo electrónico ni los identificadores directos de la cuenta.", + "rawDataOffDescription": "Solo se envían métricas resumidas (por ejemplo, promedios, tendencias, mínimo/máximo). Sin puntos individuales ni contexto temporal exacto. Esto preserva mejor la privacidad, pero el proveedor puede ser menos preciso con los patrones y las fluctuaciones a corto plazo.", + "rawDataWarning": "Modo en bruto activado: el proveedor recibe puntos anonimizados adicionales de los últimos 30 días para un análisis más preciso.", + "regenerateInsights": "Regenerar informes", + "regenerateSuccess": "Informes regenerados correctamente", + "regenerateRateLimit": "Espera un momento — has alcanzado el límite por hora de análisis.", + "lastGeneratedAt": "Generado por última vez", "ai": { - "chatgptConnectedBadge": "ChatGPT connected", - "adminAiActiveBadge": "Admin provider active", - "connectionExpiredBadge": "Connection expired", - "connectedSince": "Connected since {when}.", - "deviceCodeHeading": "Finish connecting on chatgpt.com", - "deviceCodeStep1": "Open this link on any device:", - "deviceCodeStep2": "Enter this one-time code:", - "deviceCodeStep3": "Approve the connection — this page updates automatically.", + "chatgptConnectedBadge": "ChatGPT conectado", + "adminAiActiveBadge": "Proveedor de administrador activo", + "connectionExpiredBadge": "Conexión caducada", + "connectedSince": "Conectado desde {when}.", + "deviceCodeHeading": "Termina de conectar en chatgpt.com", + "deviceCodeStep1": "Abre este enlace en cualquier dispositivo:", + "deviceCodeStep2": "Introduce este código de un solo uso:", + "deviceCodeStep3": "Aprueba la conexión — esta página se actualiza automáticamente.", "deviceCodeCopy": "Copiar", - "deviceCodeWaiting": "Waiting for approval…", + "deviceCodeWaiting": "Esperando aprobación…", "deviceCodeCancel": "Cancelar", - "oauthNotConfigured": "ChatGPT OAuth is not configured on this instance — use your own API key below instead.", - "modelLabel": "Model", - "modelOptionDefault": "— Default —", - "modelOptionCustom": "Custom…", - "customModelLabel": "Custom model name", - "anthropicKeyLabel": "Anthropic API key", + "oauthNotConfigured": "El OAuth de ChatGPT no está configurado en esta instancia — usa tu propia clave de API a continuación.", + "modelLabel": "Modelo", + "modelOptionDefault": "— Predeterminado —", + "modelOptionCustom": "Personalizado…", + "customModelLabel": "Nombre del modelo personalizado", + "anthropicKeyLabel": "Clave de API de Anthropic", "baseUrlLabel": "Base URL", - "localKeyLabel": "API key (optional)", - "savedPreview": "(saved {preview})", - "savedShort": "(saved)", + "localKeyLabel": "Clave de API (opcional)", + "savedPreview": "(guardada {preview})", + "savedShort": "(guardada)", "saveCta": "Guardar", "saved": "Guardado", - "saveFailed": "Save failed", + "saveFailed": "Falló el guardado", "errorGeneric": "Error", "providerChain": { "types": { "codex": "ChatGPT (Codex)", - "openai": "OpenAI (your key)", + "openai": "OpenAI (tu clave)", "anthropic": "Anthropic (Claude)", - "local": "Local model", - "admin-openai": "Admin OpenAI" + "local": "Modelo local", + "admin-openai": "OpenAI de administrador" }, - "title": "Fallback chain", - "description": "If the primary provider fails, HealthLog walks the chain in order. Drag the rows to reorder, toggle the switch to disable a provider without removing it.", - "moveUp": "Move up", - "moveDown": "Move down", - "removeFromChain": "Remove from chain", - "addProvider": "Add provider", - "addNoneAvailable": "All providers already in chain", - "saveOrder": "Save chain order", - "saved": "Chain saved", - "saveFailed": "Saving the chain failed", - "resetDefaults": "Reset to defaults", - "resetConfirmTitle": "Reset chain to defaults?", - "resetConfirmBody": "The chain reverts to Codex → OpenAI → Anthropic → Local → Admin OpenAI. Your saved credentials are not touched." - }, - "activeProviderHeading": "Active provider", - "activeProviderBody": "Pick the provider you want to use first. The form below configures only the selected provider; the fallback chain at the bottom decides what happens if it fails.", - "activeProviderLabel": "Primary provider", - "providerConfigTitle": "Provider configuration", + "title": "Cadena de respaldo", + "description": "Si el proveedor principal falla, HealthLog recorre la cadena en orden. Arrastra las filas para reordenarlas; usa el interruptor para desactivar un proveedor sin eliminarlo.", + "moveUp": "Subir", + "moveDown": "Bajar", + "removeFromChain": "Quitar de la cadena", + "addProvider": "Añadir proveedor", + "addNoneAvailable": "Todos los proveedores ya están en la cadena", + "saveOrder": "Guardar el orden de la cadena", + "saved": "Cadena guardada", + "saveFailed": "Falló el guardado de la cadena", + "resetDefaults": "Restablecer", + "resetConfirmTitle": "¿Restablecer la cadena a los valores predeterminados?", + "resetConfirmBody": "La cadena vuelve a Codex → OpenAI → Anthropic → Local → OpenAI de administrador. Tus credenciales guardadas no se modifican." + }, + "activeProviderHeading": "Proveedor activo", + "activeProviderBody": "Elige el proveedor que quieres usar primero. El formulario de abajo configura únicamente el proveedor seleccionado; la cadena de respaldo del final decide qué ocurre si falla.", + "activeProviderLabel": "Proveedor principal", + "providerConfigTitle": "Configuración del proveedor", "providerSelect": { - "codex": "ChatGPT account (Codex)", - "openai": "OpenAI (your API key)", + "codex": "Cuenta de ChatGPT (Codex)", + "openai": "OpenAI (tu clave de API)", "anthropic": "Anthropic (Claude)", - "local": "Local model (OpenAI-compatible)", - "admin-openai": "Admin-provided OpenAI" + "local": "Modelo local (compatible con OpenAI)", + "admin-openai": "OpenAI proporcionado por el administrador" }, "openai": { - "modelSelect": "Model", - "modelOptionCustom": "Custom slug…", - "modelCustomLabel": "Custom model slug", + "modelSelect": "Modelo", + "modelOptionCustom": "Slug personalizado…", + "modelCustomLabel": "Slug del modelo personalizado", "modelCustomPlaceholder": "gpt-5", - "baseUrlLabel": "Base URL (advanced)", + "baseUrlLabel": "URL base (avanzado)", "baseUrlPlaceholder": "https://api.openai.com/v1", - "baseUrlHelp": "Override only when using an OpenAI-compatible gateway. Leave blank for OpenAI itself.", - "showAdvanced": "Show advanced", - "hideAdvanced": "Hide advanced", - "apiKey": "API key", + "baseUrlHelp": "Sobrescribe solo cuando uses una pasarela compatible con OpenAI. Déjalo en blanco para OpenAI directamente.", + "showAdvanced": "Mostrar opciones avanzadas", + "hideAdvanced": "Ocultar opciones avanzadas", + "apiKey": "Clave de API", "apiKeyPlaceholder": "sk-…" }, "codex": { - "statusConnected": "Connected", - "statusDisconnected": "Not connected", - "statusExpired": "Connection expired", - "connectButton": "Connect with ChatGPT", - "disconnectButton": "Disconnect", - "modelSlugLabel": "Model slug", - "modelSlugBody": "Codex uses the model your ChatGPT subscription routes to. The CODEX_MODEL environment variable lets ops override this on the instance.", - "lastInsight": "Last insight: {when}" + "statusConnected": "Conectado", + "statusDisconnected": "No conectado", + "statusExpired": "Conexión caducada", + "connectButton": "Conectar con ChatGPT", + "disconnectButton": "Desconectar", + "modelSlugLabel": "Slug del modelo", + "modelSlugBody": "Codex usa el modelo al que enruta tu suscripción de ChatGPT. La variable de entorno CODEX_MODEL permite al operador sobrescribirlo en la instancia.", + "lastInsight": "Último análisis: {when}" }, "adminOpenai": { - "title": "Admin OpenAI", - "body": "Operator-provided OpenAI key. Used as a last-ditch fallback when no personal provider is configured. There is nothing to configure on this row — visibility means the operator has set it up.", - "notConfigured": "The operator has not configured a shared OpenAI key on this instance." + "title": "OpenAI de administrador", + "body": "Clave de OpenAI proporcionada por el operador. Se usa como último recurso cuando no hay ningún proveedor personal configurado. No hay nada que configurar en esta fila — su visibilidad significa que el operador la ha habilitado.", + "notConfigured": "El operador no ha configurado una clave de OpenAI compartida en esta instancia." }, - "testProvider": "Test active provider", + "testProvider": "Probar el proveedor activo", "testSuccess": "OK — {provider} ({model})", - "testFailedShort": "Test failed: {message}", - "testUnexpectedResponse": "AI provider connection failed — unexpected response from the server.", - "testReasonCredentials": "Provider rejected the credentials — re-authenticate in AI settings.", - "testReasonRateLimited": "Provider rate-limited the request — try again shortly.", - "testReasonServerError": "The AI provider returned a server error.", - "testReasonUnreachable": "Could not reach the AI provider.", + "testFailedShort": "Prueba fallida: {message}", + "testUnexpectedResponse": "Falló la conexión con el proveedor de IA — respuesta inesperada del servidor.", + "testReasonCredentials": "El proveedor rechazó las credenciales — vuelve a autenticarte en los ajustes de IA.", + "testReasonRateLimited": "El proveedor limitó la solicitud — inténtalo de nuevo en breve.", + "testReasonServerError": "El proveedor de IA devolvió un error del servidor.", + "testReasonUnreachable": "No se pudo contactar con el proveedor de IA.", "coachMemory": { "title": "Lo que el Coach recuerda", "description": "Datos duraderos que le has contado al Coach. Los usa para que sus respuestas sigan siendo relevantes. Elimina lo que prefieras que olvide.", @@ -3380,28 +3394,28 @@ } }, "withings": "Withings", - "withingsDescription": "Connect your Withings scale and blood pressure monitors.", - "withingsCredentials": "API Credentials", - "withingsClientId": "Client ID", - "withingsClientSecret": "Client Secret", - "withingsCredentialsSaved": "Credentials saved", - "withingsCredentialsSavedPlaceholder": "Saved — enter new to replace", - "withingsCredentialsSavedPlaceholderSecret": "Saved — enter new to replace", - "withingsSaveCredentials": "Save credentials", - "configured": "Configured", - "withingsSync": "Sync now", - "withingsFullSync": "Sync all data", - "withingsFullSyncTitle": "Full synchronization?", - "withingsFullSyncDescription": "All available Withings data will be fully synchronized. This may take some time depending on your history.", - "withingsSyncResult": "{count} measurements synchronized", - "withingsFullSyncResult": "{count} measurements fully synchronized", - "withingsSyncFailed": "Sync failed", - "withingsSynchronize": "Synchronize", - "withingsDisconnect": "Disconnect", - "withingsDisconnectTitle": "Disconnect Withings?", - "withingsDisconnectDescription": "The connection to Withings will be disconnected. Previously synced data will be preserved.", - "withingsConnect": "Connect with Withings", - "withingsNoCredentials": "Please enter your API credentials above to connect Withings.", + "withingsDescription": "Conecta tu báscula y tensiómetros Withings.", + "withingsCredentials": "Credenciales de API", + "withingsClientId": "ID de cliente", + "withingsClientSecret": "Secreto de cliente", + "withingsCredentialsSaved": "Credenciales guardadas", + "withingsCredentialsSavedPlaceholder": "Guardado — introduce uno nuevo para reemplazarlo", + "withingsCredentialsSavedPlaceholderSecret": "Guardado — introduce uno nuevo para reemplazarlo", + "withingsSaveCredentials": "Guardar credenciales", + "configured": "Configurado", + "withingsSync": "Sincronizar ahora", + "withingsFullSync": "Sincronizar todos los datos", + "withingsFullSyncTitle": "¿Sincronización completa?", + "withingsFullSyncDescription": "Se sincronizarán por completo todos los datos disponibles de Withings. Esto puede tardar un poco según tu historial.", + "withingsSyncResult": "{count} mediciones sincronizadas", + "withingsFullSyncResult": "{count} mediciones sincronizadas por completo", + "withingsSyncFailed": "Falló la sincronización", + "withingsSynchronize": "Sincronizar", + "withingsDisconnect": "Desconectar", + "withingsDisconnectTitle": "¿Desconectar Withings?", + "withingsDisconnectDescription": "Se desconectará la conexión con Withings. Los datos ya sincronizados se conservarán.", + "withingsConnect": "Conectar con Withings", + "withingsNoCredentials": "Introduce primero tus credenciales de API arriba para conectar Withings.", "whoop": "WHOOP", "whoopDescription": "Conecta tu banda WHOOP para sincronizar recuperación, sueño, esfuerzo y entrenamientos.", "whoopOverlapNote": "Si WHOOP y otra fuente proporcionan el mismo valor vital —frecuencia cardíaca en reposo, oxígeno en sangre, temperatura corporal, frecuencia respiratoria o fases del sueño—, es posible que veas ambos valores hasta que una futura actualización elija una única fuente preferida.", @@ -3430,9 +3444,9 @@ "withings": { "reconnect": { "banner": { - "title": "Activity sync available", - "body": "Your Withings connection was authorised before activity sync was added. Reconnect once to enable steps, active energy, walking + running distance, and floors-climbed ingest from your Withings devices.", - "action": "Reconnect Withings" + "title": "Sincronización de actividad disponible", + "body": "Tu conexión con Withings se autorizó antes de añadir la sincronización de actividad. Vuelve a conectar una vez para habilitar la importación de pasos, energía activa, distancia caminando y corriendo, y pisos subidos desde tus dispositivos Withings.", + "action": "Reconectar Withings" } } } @@ -3443,132 +3457,133 @@ "warningServerError": "Conectado, error del servidor", "parkedReconnect": "Pausado — reconectar manualmente", "notConnected": "No conectado", - "justNow": "just now", - "minutesAgo": "{count} min ago", - "hoursAgo": "{count} h ago", - "daysAgo": "{count} d ago", - "ariaLabel": "Integration status", + "justNow": "ahora mismo", + "minutesAgo": "hace {count} min", + "hoursAgo": "hace {count} h", + "daysAgo": "hace {count} d", + "ariaLabel": "Estado de la integración", "resumeCta": "Reconectar", "resumeSuccess": "Integración reanudada", "resumeError": "Reconexión fallida" }, - "apiTokens": "API Tokens", - "apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).", - "tokenNamePlaceholder": "Token name (e.g., iPhone Shortcut)", - "tokenCreated": "Token created — copy it now! It won't be shown again.", - "tokenRevoke": "Revoke token?", - "tokenRevokeDescription": "The token will be permanently deactivated. Applications using it will lose access.", - "tokenRevoked": "Revoked", - "tokenExpired": "Expired", + "apiTokens": "Tokens de API", + "apiTokensDescription": "Crea tokens de API para registrar tomas de medicación externas (p. ej., Atajos, automatizaciones).", + "tokenNamePlaceholder": "Nombre del token (p. ej., Atajo de iPhone)", + "tokenCreated": "Token creado — ¡cópialo ahora! No se mostrará de nuevo.", + "tokenRevoke": "¿Revocar token?", + "tokenRevokeAction": "Revocar token", + "tokenRevokeDescription": "El token se desactivará de forma permanente. Las aplicaciones que lo usen perderán el acceso.", + "tokenRevoked": "Revocado", + "tokenExpired": "Caducado", "tokenActive": "Activo", - "activeTokensTitle": "Active tokens", - "revokedTokensTitle": "Revoked tokens ({count})", + "activeTokensTitle": "Tokens activos", + "revokedTokensTitle": "Tokens revocados ({count})", "tokenTableName": "Nombre", - "tokenTablePermissions": "Permissions", + "tokenTablePermissions": "Permisos", "tokenTableStatus": "Estado", - "tokenTableCreated": "Created", - "tokenTableLastUsed": "Last used", + "tokenTableCreated": "Creado", + "tokenTableLastUsed": "Último uso", "tokenTableActions": "Acciones", - "noActiveTokens": "No active tokens available.", - "tokenNeverUsed": "Never", - "apiEndpointsTitle": "API endpoints", - "apiEndpointsDescription": "Overview of currently available external endpoint(s) for token-based ingestion.", - "apiEndpointMethod": "Method", - "apiEndpointPath": "Path", - "apiEndpointAuth": "Authentication", - "apiEndpointExample": "Body example", - "collapse": "Collapse", - "expand": "Expand", - "dangerZone": "Delete All Data", - "dangerZoneTitle": "Danger zone", - "dangerZoneDescription": "Deletes all your health data and integrations. Your user account will be preserved.", - "dangerZoneConfirm": "Delete all data?", - "dangerZoneConfirmDescription": "This will permanently delete all your health data and integrations. Your user account will be preserved.", - "dangerZoneSuccess": "All personal data has been deleted", - "dangerZoneDeleteFailed": "Deletion failed", - "finalDelete": "Delete permanently", - "deleteAccountCardTitle": "Delete account entirely", - "deleteAccountCardDescription": "Deletes your account along with passkeys, audit log, and sessions. This cannot be undone.", - "deleteAccountCta": "Delete account", - "deleteAccountConfirmTitle": "Delete account permanently?", - "deleteAccountConfirmDescription": "Your account, all health data, passkeys, sessions, and audit entries will be permanently deleted. You will be signed out immediately after.", - "deleteAccountSuccess": "Account deleted — signing you out.", - "deleteAccountFailed": "Account could not be deleted", - "deleteAccountFinal": "Delete permanently", + "noActiveTokens": "No hay tokens activos.", + "tokenNeverUsed": "Nunca", + "apiEndpointsTitle": "Endpoints de API", + "apiEndpointsDescription": "Resumen de los endpoints externos disponibles actualmente para la ingesta basada en tokens.", + "apiEndpointMethod": "Método", + "apiEndpointPath": "Ruta", + "apiEndpointAuth": "Autenticación", + "apiEndpointExample": "Ejemplo de cuerpo", + "collapse": "Contraer", + "expand": "Expandir", + "dangerZone": "Eliminar todos los datos", + "dangerZoneTitle": "Zona de peligro", + "dangerZoneDescription": "Elimina todos tus datos de salud e integraciones. Tu cuenta de usuario se conservará.", + "dangerZoneConfirm": "¿Eliminar todos los datos?", + "dangerZoneConfirmDescription": "Esto eliminará de forma permanente todos tus datos de salud e integraciones. Tu cuenta de usuario se conservará.", + "dangerZoneSuccess": "Se han eliminado todos los datos personales", + "dangerZoneDeleteFailed": "Falló la eliminación", + "finalDelete": "Eliminar permanentemente", + "deleteAccountCardTitle": "Eliminar la cuenta por completo", + "deleteAccountCardDescription": "Elimina tu cuenta junto con las claves de acceso, el registro de auditoría y las sesiones. Esto no se puede deshacer.", + "deleteAccountCta": "Eliminar cuenta", + "deleteAccountConfirmTitle": "¿Eliminar la cuenta de forma permanente?", + "deleteAccountConfirmDescription": "Tu cuenta, todos los datos de salud, las claves de acceso, las sesiones y las entradas de auditoría se eliminarán de forma permanente. Se cerrará tu sesión inmediatamente después.", + "deleteAccountSuccess": "Cuenta eliminada — cerrando tu sesión.", + "deleteAccountFailed": "No se pudo eliminar la cuenta", + "deleteAccountFinal": "Eliminar permanentemente", "saved": "Guardado", - "savingError": "Error saving", + "savingError": "Error al guardar", "moodLogTitle": "moodLog", "moodLogDescription": "Import mood data from moodLog", "moodLogUrl": "moodLog URL", "moodLogUrlPlaceholder": "https://mood.example.com", - "moodLogApiKey": "API Key", + "moodLogApiKey": "Clave de API", "moodLogApiKeyPlaceholder": "ml_...", - "moodLogWebhookSecret": "Webhook Secret", - "moodLogWebhookSecretHelp": "Enter this secret in moodLog as webhook secret", - "moodLogSync": "Start sync", - "moodLogFullSync": "Full sync", - "moodLogFullSyncConfirm": "Synchronize fully", - "moodLogFullSyncTitle": "Full sync?", - "moodLogFullSyncDescription": "All available mood data will be fully synchronized.", - "moodLogDisconnect": "Disconnect", - "moodLogDisconnectTitle": "Disconnect moodLog?", - "moodLogDisconnectDescription": "The connection will be removed and all imported mood data will be deleted.", - "moodLogEntries": "Entries", - "moodLogSyncResult": "{count} entries synchronized", - "moodLogSyncFailed": "Sync failed", - "moodLogSaved": "moodLog connection saved", - "moodLogDisconnected": "moodLog disconnected", + "moodLogWebhookSecret": "Secreto del webhook", + "moodLogWebhookSecretHelp": "Introduce este secreto en moodLog como secreto del webhook", + "moodLogSync": "Iniciar sincronización", + "moodLogFullSync": "Sincronización completa", + "moodLogFullSyncConfirm": "Sincronizar por completo", + "moodLogFullSyncTitle": "¿Sincronización completa?", + "moodLogFullSyncDescription": "Se sincronizarán por completo todos los datos de estado de ánimo disponibles.", + "moodLogDisconnect": "Desconectar", + "moodLogDisconnectTitle": "¿Desconectar moodLog?", + "moodLogDisconnectDescription": "Se eliminará la conexión y se borrarán todos los datos de estado de ánimo importados.", + "moodLogEntries": "Entradas", + "moodLogSyncResult": "{count} entradas sincronizadas", + "moodLogSyncFailed": "Falló la sincronización", + "moodLogSaved": "Conexión con moodLog guardada", + "moodLogDisconnected": "moodLog desconectado", "about": { - "version": "App version", + "version": "Versión de la app", "gitSha": "Build", - "builtAt": "Built {time}", - "builtAtLabel": "Built", - "license": "License", - "repository": "Source code", + "builtAt": "Compilado {time}", + "builtAtLabel": "Compilado", + "license": "Licencia", + "repository": "Código fuente", "changelog": "Changelog", - "docs": "Documentation", + "docs": "Documentación", "linksHeading": "Fuentes y documentación", "newerAvailable": "Nueva versión: {tag}", "tourReplay": "Volver a ver el recorrido", "tourReplayHint": "Mira el recorrido del panel — útil si lo saltaste en la primera visita." }, "testConnection": { - "test": "Test connection", - "testing": "Testing…", - "ok": "Connected (latency {latency} ms)", + "test": "Probar conexión", + "testing": "Probando…", + "ok": "Conectado (latencia {latency} ms)", "errors": { - "credentials_rejected": "Credentials rejected — check your token", - "rate_limited": "Rate-limited by the upstream", - "timeout": "Request timed out", - "upstream_error": "Upstream returned an error", - "connection_failed": "Connection failed", - "not_configured": "Not configured", - "url_not_public": "URL is not a public endpoint", - "url_invalid": "URL is invalid", - "redirected": "Endpoint redirects — check the URL", - "endpoint_not_found": "Endpoint not found at the URL", - "credentials_unreadable": "Credentials cannot be decrypted", - "upstream_invalid_json": "Upstream sent invalid JSON", - "vapid_not_configured": "Web Push not configured (VAPID keys missing)", - "rate_limited_self": "Too many test requests", - "generic": "Test failed" + "credentials_rejected": "Credenciales rechazadas — comprueba tu token", + "rate_limited": "Limitado por el servidor remoto", + "timeout": "La solicitud agotó el tiempo de espera", + "upstream_error": "El servidor remoto devolvió un error", + "connection_failed": "Falló la conexión", + "not_configured": "No configurado", + "url_not_public": "La URL no es un endpoint público", + "url_invalid": "La URL no es válida", + "redirected": "El endpoint redirige — comprueba la URL", + "endpoint_not_found": "No se encontró el endpoint en la URL", + "credentials_unreadable": "Las credenciales no se pueden descifrar", + "upstream_invalid_json": "El servidor remoto envió un JSON no válido", + "vapid_not_configured": "Web Push no configurado (faltan las claves VAPID)", + "rate_limited_self": "Demasiadas solicitudes de prueba", + "generic": "Prueba fallida" } }, "notificationStatus": { "title": "Channel reliability", "description": "Live status of every notification channel — Auto-disabled means HealthLog stopped retrying after a permanent error.", - "emptyDescription": "No channels configured yet. Add a channel below to start tracking its delivery health.", + "emptyDescription": "Aún no hay canales configurados. Añade un canal abajo para empezar a controlar su estado de entrega.", "stateActive": "Activo", - "stateAutoDisabled": "Auto-disabled", - "stateSendingPaused": "Sending paused", + "stateAutoDisabled": "Desactivado automáticamente", + "stateSendingPaused": "Envío en pausa", "stateManuallyDisabled": "Desactivado", - "lastSuccess": "Last successful send", - "lastFailure": "Last failure", - "consecutiveFailures": "Consecutive failures", - "disabledReason": "Reason", - "nextRetry": "Next retry", - "reEnable": "Re-enable", - "sendTest": "Send test" + "lastSuccess": "Último envío correcto", + "lastFailure": "Último fallo", + "consecutiveFailures": "Fallos consecutivos", + "disabledReason": "Motivo", + "nextRetry": "Próximo intento", + "reEnable": "Reactivar", + "sendTest": "Enviar prueba" }, "identity": { "description": "Datos opcionales para la exportación del historial de salud (portada PDF + exportación FHIR). Todos los campos son opcionales.", @@ -4108,284 +4123,284 @@ "carrierUnavailableGeoFallback": "{location} — carrier unavailable" }, "achievements": { - "title": "Achievements", - "subtitle": "Earn achievements by tracking your health and taking your meds on time.", - "loginRequired": "Please sign in to view achievements.", - "points": "Points", - "unlocked": "Unlocked", - "nextGoal": "Next goal", - "allCompleted": "All achievements unlocked!", - "completed": "Completed", - "goalReached": "Goal reached", - "remainingUnlocks": "{count} still unlockable", - "noneUnlockedYet": "No achievements unlocked yet.", - "pointsValue": "{points} points", + "title": "Logros", + "subtitle": "Consigue logros registrando tu salud y tomando tu medicación a tiempo.", + "loginRequired": "Inicia sesión para ver los logros.", + "points": "Puntos", + "unlocked": "Desbloqueado", + "nextGoal": "Próximo objetivo", + "allCompleted": "¡Todos los logros desbloqueados!", + "completed": "Completado", + "goalReached": "Objetivo alcanzado", + "remainingUnlocks": "Quedan {count} por desbloquear", + "noneUnlockedYet": "Aún no has desbloqueado ningún logro.", + "pointsValue": "{points} puntos", "metricCount": "{count}", - "metricDays": "{count} days", + "metricDays": "{count} días", "metricPercent": "{count}%", "badges": { "intakeTotal1": { - "title": "First intake", - "description": "Record your first taken medication event." + "title": "Primera toma", + "description": "Registra tu primera toma de medicación." }, "intakeTotal10": { - "title": "Intake starter", - "description": "Record 10 taken medication events." + "title": "Iniciado en las tomas", + "description": "Registra 10 tomas de medicación." }, "intakeTotal50": { - "title": "Intake routine", - "description": "Record 50 taken medication events." + "title": "Rutina de tomas", + "description": "Registra 50 tomas de medicación." }, "intakeTotal150": { - "title": "Intake expert", - "description": "Record 150 taken medication events." + "title": "Experto en tomas", + "description": "Registra 150 tomas de medicación." }, "intakeTotal300": { - "title": "Intake legend", - "description": "Record 300 taken medication events." + "title": "Leyenda de las tomas", + "description": "Registra 300 tomas de medicación." }, "overIntake1": { - "title": "Double take", - "description": "Take medication at least once more than planned." + "title": "Dosis doble", + "description": "Toma la medicación al menos una vez más de lo previsto." }, "skippedIntake1": { - "title": "Stepped back", - "description": "Skip at least one planned intake." + "title": "Un paso atrás", + "description": "Omite al menos una toma planificada." }, "passkeyCreated1": { - "title": "Passkey set up", - "description": "Create your first passkey." + "title": "Clave de acceso creada", + "description": "Crea tu primera clave de acceso." }, "passkeyLogin1": { - "title": "Passkey login", - "description": "Sign in at least once with a passkey." + "title": "Inicio con clave de acceso", + "description": "Inicia sesión al menos una vez con una clave de acceso." }, "passwordLogin1": { - "title": "Old school", - "description": "Sign in at least once with username and password." + "title": "A la antigua", + "description": "Inicia sesión al menos una vez con usuario y contraseña." }, "bugReport1": { - "title": "Bug hunter", - "description": "Submit a bug report." + "title": "Cazador de errores", + "description": "Envía un informe de error." }, "loginStreak7": { - "title": "Login streak 7d", - "description": "Sign in on 7 consecutive days." + "title": "Racha de inicios 7d", + "description": "Inicia sesión 7 días seguidos." }, "loginStreak30": { - "title": "Login streak 30d", - "description": "Sign in on 30 consecutive days." + "title": "Racha de inicios 30d", + "description": "Inicia sesión 30 días seguidos." }, "onTimePerfect1": { - "title": "On-time 1d", - "description": "Take all medications on time for 1 day." + "title": "Puntual 1d", + "description": "Toma todos los medicamentos a tiempo durante 1 día." }, "compliance801": { - "title": "80% adherence · 1d", - "description": "Reach at least 80% 30-day adherence for 1 day." + "title": "Adherencia 80% · 1d", + "description": "Alcanza al menos un 80% de adherencia de 30 días durante 1 día." }, "bmiGreen1": { - "title": "BMI green · 1d", - "description": "Keep your BMI in the normal range for 1 day." + "title": "IMC en verde · 1d", + "description": "Mantén tu IMC en el rango normal durante 1 día." }, "bpGreen1": { - "title": "BP green · 1d", - "description": "Keep your blood pressure in the normal range for 1 day." + "title": "Presión en verde · 1d", + "description": "Mantén tu presión arterial en el rango normal durante 1 día." }, "pulseGreen1": { - "title": "Pulse green · 1d", - "description": "Keep your resting pulse in the normal range for 1 day." + "title": "Pulso en verde · 1d", + "description": "Mantén tu pulso en reposo en el rango normal durante 1 día." }, "onTimePerfect7": { - "title": "On-time 7d", - "description": "Take all medications on time for 7 consecutive days." + "title": "Puntual 7d", + "description": "Toma todos los medicamentos a tiempo durante 7 días seguidos." }, "compliance807": { - "title": "80% adherence · 7d", - "description": "Reach at least 80% 30-day adherence for 7 consecutive days." + "title": "Adherencia 80% · 7d", + "description": "Alcanza al menos un 80% de adherencia de 30 días durante 7 días seguidos." }, "bmiGreen7": { - "title": "BMI green · 7d", - "description": "Keep your BMI in the normal range for 7 consecutive days." + "title": "IMC en verde · 7d", + "description": "Mantén tu IMC en el rango normal durante 7 días seguidos." }, "bpGreen7": { - "title": "BP green · 7d", - "description": "Keep your blood pressure in the normal range for 7 consecutive days." + "title": "Presión en verde · 7d", + "description": "Mantén tu presión arterial en el rango normal durante 7 días seguidos." }, "pulseGreen7": { - "title": "Pulse green · 7d", - "description": "Keep your resting pulse in the normal range for 7 consecutive days." + "title": "Pulso en verde · 7d", + "description": "Mantén tu pulso en reposo en el rango normal durante 7 días seguidos." }, "onTimePerfect30": { - "title": "On-time 30d", - "description": "Take all medications on time for 30 consecutive days." + "title": "Puntual 30d", + "description": "Toma todos los medicamentos a tiempo durante 30 días seguidos." }, "compliance8030": { - "title": "80% adherence · 30d", - "description": "Reach at least 80% 30-day adherence for 30 consecutive days." + "title": "Adherencia 80% · 30d", + "description": "Alcanza al menos un 80% de adherencia de 30 días durante 30 días seguidos." }, "bmiGreen30": { - "title": "BMI green · 30d", - "description": "Keep your BMI in the normal range for 30 consecutive days." + "title": "IMC en verde · 30d", + "description": "Mantén tu IMC en el rango normal durante 30 días seguidos." }, "bpGreen30": { - "title": "BP green · 30d", - "description": "Keep your blood pressure in the normal range for 30 consecutive days." + "title": "Presión en verde · 30d", + "description": "Mantén tu presión arterial en el rango normal durante 30 días seguidos." }, "pulseGreen30": { - "title": "Pulse green · 30d", - "description": "Keep your resting pulse in the normal range for 30 consecutive days." + "title": "Pulso en verde · 30d", + "description": "Mantén tu pulso en reposo en el rango normal durante 30 días seguidos." }, "onTimePerfect180": { - "title": "On-time 180d", - "description": "Take all medications on time for 180 consecutive days." + "title": "Puntual 180d", + "description": "Toma todos los medicamentos a tiempo durante 180 días seguidos." }, "compliance80180": { - "title": "80% adherence · 180d", - "description": "Reach at least 80% 30-day adherence for 180 consecutive days." + "title": "Adherencia 80% · 180d", + "description": "Alcanza al menos un 80% de adherencia de 30 días durante 180 días seguidos." }, "bmiGreen180": { - "title": "BMI green · 180d", - "description": "Keep your BMI in the normal range for 180 consecutive days." + "title": "IMC en verde · 180d", + "description": "Mantén tu IMC en el rango normal durante 180 días seguidos." }, "bpGreen180": { - "title": "BP green · 180d", - "description": "Keep your blood pressure in the normal range for 180 consecutive days." + "title": "Presión en verde · 180d", + "description": "Mantén tu presión arterial en el rango normal durante 180 días seguidos." }, "pulseGreen180": { - "title": "Pulse green · 180d", - "description": "Keep your resting pulse in the normal range for 180 consecutive days." + "title": "Pulso en verde · 180d", + "description": "Mantén tu pulso en reposo en el rango normal durante 180 días seguidos." }, "onTimePerfect360": { - "title": "On-time 360d", - "description": "Take all medications on time for 360 consecutive days." + "title": "Puntual 360d", + "description": "Toma todos los medicamentos a tiempo durante 360 días seguidos." }, "compliance80360": { - "title": "80% adherence · 360d", - "description": "Reach at least 80% 30-day adherence for 360 consecutive days." + "title": "Adherencia 80% · 360d", + "description": "Alcanza al menos un 80% de adherencia de 30 días durante 360 días seguidos." }, "bmiGreen360": { - "title": "BMI green · 360d", - "description": "Keep your BMI in the normal range for 360 consecutive days." + "title": "IMC en verde · 360d", + "description": "Mantén tu IMC en el rango normal durante 360 días seguidos." }, "bpGreen360": { - "title": "BP green · 360d", - "description": "Keep your blood pressure in the normal range for 360 consecutive days." + "title": "Presión en verde · 360d", + "description": "Mantén tu presión arterial en el rango normal durante 360 días seguidos." }, "pulseGreen360": { - "title": "Pulse green · 360d", - "description": "Keep your resting pulse in the normal range for 360 consecutive days." + "title": "Pulso en verde · 360d", + "description": "Mantén tu pulso en reposo en el rango normal durante 360 días seguidos." }, "moodFirst": { - "title": "First mood", - "description": "Log your first mood entry." + "title": "Primer estado de ánimo", + "description": "Registra tu primera entrada de estado de ánimo." }, "moodStreak7": { - "title": "Mood diarist 7d", - "description": "Log a mood entry on 7 consecutive days." + "title": "Diario de ánimo 7d", + "description": "Registra una entrada de estado de ánimo 7 días seguidos." }, "moodStreak30": { - "title": "Mood diarist 30d", - "description": "Log a mood entry on 30 consecutive days." + "title": "Diario de ánimo 30d", + "description": "Registra una entrada de estado de ánimo 30 días seguidos." }, "moodUp7": { - "title": "Brighter week", - "description": "Your 7-day mood average improved by at least 1.0 point compared to the previous week." + "title": "Semana más luminosa", + "description": "Tu media de ánimo de 7 días mejoró al menos 1,0 punto respecto a la semana anterior." }, "weightFirst": { - "title": "First weigh-in", - "description": "Record your first weight measurement." + "title": "Primer pesaje", + "description": "Registra tu primera medición de peso." }, "weight50": { - "title": "Fifty weigh-ins", - "description": "Record 50 weight measurements." + "title": "Cincuenta pesajes", + "description": "Registra 50 mediciones de peso." }, "weight200": { - "title": "200 weigh-ins", - "description": "Record 200 weight measurements." + "title": "200 pesajes", + "description": "Registra 200 mediciones de peso." }, "bpFirst": { - "title": "First reading", - "description": "Record your first blood-pressure measurement." + "title": "Primera medición", + "description": "Registra tu primera medición de presión arterial." }, "bp50": { - "title": "Fifty BP readings", - "description": "Record 50 blood-pressure measurements." + "title": "Cincuenta mediciones de presión", + "description": "Registra 50 mediciones de presión arterial." }, "bp200": { - "title": "200 BP readings", - "description": "Record 200 blood-pressure measurements." + "title": "200 mediciones de presión", + "description": "Registra 200 mediciones de presión arterial." }, "pulseFirst": { - "title": "First pulse", - "description": "Record your first pulse measurement." + "title": "Primer pulso", + "description": "Registra tu primera medición de pulso." }, "consistentMonth": { - "title": "Consistent month", - "description": "Log entries on at least 25 distinct days within a single calendar month." + "title": "Mes constante", + "description": "Registra entradas en al menos 25 días distintos dentro de un mismo mes natural." }, "entryStreak7": { - "title": "Tracker streak 7d", - "description": "Log at least one entry on 7 consecutive days." + "title": "Racha de registros 7d", + "description": "Registra al menos una entrada 7 días seguidos." }, "entryStreak30": { - "title": "Tracker streak 30d", - "description": "Log at least one entry on 30 consecutive days." + "title": "Racha de registros 30d", + "description": "Registra al menos una entrada 30 días seguidos." }, "weekendWarrior": { - "title": "Weekend tracker", - "description": "Log entries on 4 consecutive Saturday + Sunday weekend pairs." + "title": "Registrador de fin de semana", + "description": "Registra entradas en 4 fines de semana consecutivos (sábado y domingo)." }, "hiddenNightOwl": { - "title": "Night owl", - "description": "Logged an entry between 02:00 and 04:00 in the morning." + "title": "Búho nocturno", + "description": "Registraste una entrada entre las 02:00 y las 04:00 de la madrugada." }, "hiddenEarlyBird": { - "title": "Early bird", - "description": "Logged an entry between 04:00 and 06:00 in the morning." + "title": "Madrugador", + "description": "Registraste una entrada entre las 04:00 y las 06:00 de la mañana." }, "hiddenLeapDay": { - "title": "Leap-day legend", - "description": "Logged an entry on February 29." + "title": "Leyenda del año bisiesto", + "description": "Registraste una entrada el 29 de febrero." }, "hiddenDoctorPdf": { - "title": "House call", - "description": "Exported your first doctor-report PDF." + "title": "Visita a domicilio", + "description": "Exportaste tu primer informe médico en PDF." }, "hiddenLocaleFlip": { - "title": "Polyglot", - "description": "Switched the app language at least once." + "title": "Políglota", + "description": "Cambiaste el idioma de la app al menos una vez." }, "hiddenBugBuddy": { - "title": "Bug buddy", - "description": "Submitted at least 5 bug reports — the project loves you." + "title": "Colega de errores", + "description": "Enviaste al menos 5 informes de error — el proyecto te quiere." } }, - "nextProgressLabel": "Progress to unlock", - "completedOn": "Completed on {date}", - "locked": "Locked", + "nextProgressLabel": "Progreso para desbloquear", + "completedOn": "Completado el {date}", + "locked": "Bloqueado", "criterionHint": "{current} / {target}", "progressPercent": "{percent}%", "categories": { "medication": "Medicamento", - "vitals": "Vitals", + "vitals": "Constantes vitales", "mood": "Estado de ánimo", - "security": "Account & security", + "security": "Cuenta y seguridad", "engagement": "Engagement", - "hidden": "Hidden" + "hidden": "Oculto" }, "hiddenCard": { - "title": "Hidden achievement", - "description": "Keep tracking — you might just stumble across it.", - "ariaLabel": "Hidden locked achievement" + "title": "Logro oculto", + "description": "Sigue registrando — quizá te topes con él.", + "ariaLabel": "Logro oculto bloqueado" }, "hiddenUnlockToast": { - "title": "You unlocked a hidden achievement!" + "title": "¡Has desbloqueado un logro oculto!" }, "dashboardCard": { - "title": "Recent unlocks", - "viewAll": "View all", - "empty": "No achievements yet — keep logging to unlock your first." + "title": "Desbloqueos recientes", + "viewAll": "Ver todo", + "empty": "Aún no hay logros — sigue registrando para desbloquear el primero." } }, "bugreport": { diff --git a/messages/fr.json b/messages/fr.json index d79ae8d4d..969ed2511 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -278,6 +278,12 @@ "emptyDescription": "Crée d’abord un médicament avec un horaire, puis reviens ici pour saisir la prise.", "emptyCta": "Ajouter un médicament" }, + "quickEntryDiscard": { + "title": "Abandonner cette saisie ?", + "description": "Vous avez des données non enregistrées. Fermer le formulaire et perdre ce que vous avez saisi ?", + "confirm": "Abandonner", + "cancel": "Continuer la saisie" + }, "compliance7d": "Observance (7 jours)", "mood": "Humeur", "customizeTitle": "Mise en page du tableau", @@ -554,35 +560,35 @@ "scheduleDaily": "Quotidien", "inactive": "En pause", "intakeHistory": "Historique des prises", - "openDetailPage": "Open medication detail page", + "openDetailPage": "Ouvrir la page de détail du médicament", "deleteConfirm": "Supprimer le médicament ?", "compliance": "Observance", - "importIntakes": "Import intakes", - "importDescription": "Paste a JSON array with intake data. Expected format:", - "importUploadFile": "Upload JSON file", - "importPaste": "Paste JSON here...", - "importSelected": "Selected: {name}", - "importFileLoaded": "File \"{name}\" loaded", - "importInvalidJson": "File does not contain valid JSON", - "importNoArray": "No array found", - "importResult": "{imported} intakes imported", - "importDuplicatesSkipped": "{count} duplicates skipped", - "importInvalidSkipped": "{count} invalid entries skipped", - "importFailed": "Import failed", - "importInvalidFormat": "Invalid JSON format", - "apiEndpointTitle": "API Endpoint: {name}", - "apiEndpointDescription": "Use this endpoint to record intakes via API (e.g., iPhone Shortcut or cron job).", - "apiEndpointActive": "API endpoint active", - "apiEndpointActivated": "API endpoint activated", - "apiEndpointDeactivated": "API endpoint deactivated", - "apiTokenCount": "Active ({count} tokens)", - "apiToken": "API Token", - "apiTokenActiveHint": "Endpoint is active. Existing tokens remain valid but cannot be shown in plain text again.", - "apiTokenActivateHint": "Activate the endpoint to create a new token.", - "apiTokenOnceHint": "The token is only shown in plain text once at creation.", - "requestExample": "Request example", - "statusLoadFailed": "Could not load status", - "changeFailed": "Change failed", + "importIntakes": "Importer les prises", + "importDescription": "Collez un tableau JSON contenant les données de prises. Format attendu :", + "importUploadFile": "Téléverser un fichier JSON", + "importPaste": "Collez le JSON ici...", + "importSelected": "Sélectionné : {name}", + "importFileLoaded": "Fichier « {name} » chargé", + "importInvalidJson": "Le fichier ne contient pas de JSON valide", + "importNoArray": "Aucun tableau trouvé", + "importResult": "{imported} prises importées", + "importDuplicatesSkipped": "{count} doublons ignorés", + "importInvalidSkipped": "{count} entrées non valides ignorées", + "importFailed": "Échec de l’importation", + "importInvalidFormat": "Format JSON non valide", + "apiEndpointTitle": "Point de terminaison API : {name}", + "apiEndpointDescription": "Utilisez ce point de terminaison pour enregistrer des prises via l’API (par exemple un Raccourci iPhone ou une tâche cron).", + "apiEndpointActive": "Point de terminaison API actif", + "apiEndpointActivated": "Point de terminaison API activé", + "apiEndpointDeactivated": "Point de terminaison API désactivé", + "apiTokenCount": "Actif ({count} jetons)", + "apiToken": "Jeton API", + "apiTokenActiveHint": "Le point de terminaison est actif. Les jetons existants restent valides mais ne peuvent plus être affichés en texte clair.", + "apiTokenActivateHint": "Activez le point de terminaison pour créer un nouveau jeton.", + "apiTokenOnceHint": "Le jeton n’est affiché en texte clair qu’une seule fois, lors de sa création.", + "requestExample": "Exemple de requête", + "statusLoadFailed": "Impossible de charger le statut", + "changeFailed": "Échec de la modification", "categoryBloodPressure": "Tension artérielle", "categoryVitamin": "Vitamines", "categoryThyroid": "Thyroïde", @@ -594,7 +600,7 @@ "categorySkin": "Soin de la peau", "categorySleepAid": "Aide au sommeil", "categoryDiabetes": "Diabetes", - "categoryAntibiotic": "Antibiotic", + "categoryAntibiotic": "Antibiotique", "categoryOther": "Autres", "daysSun": "Dim", "daysMon": "Lun", @@ -622,6 +628,10 @@ "skipped": "Sauté", "intakeToastTaken": "{name} enregistré comme pris", "intakeToastSkipped": "{name} sauté", + "intakeToastFailed": "Impossible d'enregistrer {name} — réessayez", + "intakeUndo": "Annuler", + "intakeUndone": "Entrée annulée", + "intakeUndoFailed": "Annulation impossible — corrigez dans l'historique", "weekdaySunday": "Dimanche", "weekdayMonday": "Lundi", "weekdayTuesday": "Mardi", @@ -691,7 +701,7 @@ "glp1Specific": "Spécifique GLP-1" }, "entries": { - "nausea": "Nausée", + "nausea": "Nausées", "vomiting": "Vomissement", "diarrhea": "Diarrhée", "constipation": "Constipation", @@ -739,48 +749,48 @@ }, "cadence": { "section": "Cadence", - "label": "How often", + "label": "À quelle fréquence", "kind": { - "daily": "Every day", - "weekdays": "Certain days of the week", - "everyNWeeks": "Every N weeks on certain days", - "monthly": "Monthly on a specific day", - "everyNMonths": "Every N months on a specific day", - "yearly": "Once a year", - "rolling": "Every N days from when I last took it (flexible)", - "rollingExplainer": "Counts from your last logged intake — pauses if you skip a dose.", - "oneShot": "One-time dose" + "daily": "Chaque jour", + "weekdays": "Certains jours de la semaine", + "everyNWeeks": "Toutes les N semaines certains jours", + "monthly": "Chaque mois un jour précis", + "everyNMonths": "Tous les N mois un jour précis", + "yearly": "Une fois par an", + "rolling": "Tous les N jours depuis la dernière prise (flexible)", + "rollingExplainer": "Compte à partir de votre dernière prise enregistrée — se met en pause si vous sautez une dose.", + "oneShot": "Dose unique" }, "weekdays": { - "label": "Days of the week", + "label": "Jours de la semaine", "short": { "mo": "Mo", - "tu": "Tu", - "we": "We", - "th": "Th", + "tu": "Ma", + "we": "Me", + "th": "Je", "fr": "Fr", "sa": "Sa", - "su": "Su" + "su": "Di" }, "long": { - "mo": "Monday", - "tu": "Tuesday", - "we": "Wednesday", - "th": "Thursday", - "fr": "Friday", - "sa": "Saturday", - "su": "Sunday" + "mo": "Lundi", + "tu": "Mardi", + "we": "Mercredi", + "th": "Jeudi", + "fr": "Vendredi", + "sa": "Samedi", + "su": "Dimanche" } }, "intervalWeeks": { - "suffix": "weeks" + "suffix": "semaines" }, "dayOfMonth": { - "label": "Day" + "label": "Jour" }, "intervalMonths": { - "suffix": "months", - "dayOnLabel": "on day" + "suffix": "mois", + "dayOnLabel": "le jour" }, "yearly": { "date": { @@ -788,91 +798,91 @@ } }, "rollingDays": { - "suffix": "days" + "suffix": "jours" } }, "timesOfDay": { - "section": "Times of day", - "label": "Times of day", + "section": "Heures de la journée", + "label": "Heures de la journée", "empty": { - "cta": "Add the first time." + "cta": "Ajoutez la première heure." }, - "add": "Add time", - "remove": "Remove", + "add": "Ajouter une heure", + "remove": "Retirer", "presets": { - "morning": "Morning", - "noon": "Noon", - "evening": "Evening", - "night": "Night" + "morning": "Matin", + "noon": "Midi", + "evening": "Soir", + "night": "Nuit" }, "max": { - "reached": "Maximum reached — remove a time before adding another." + "reached": "Maximum atteint — retirez une heure avant d’en ajouter une autre." } }, "courseWindow": { - "section": "Course window", + "section": "Période de traitement", "startsOn": { - "label": "Starts on" + "label": "Commence le" }, "endsOn": { - "label": "Ends on" + "label": "Se termine le" }, - "noEndDate": "No end date", - "oneShotCaption": "(one-time dose)", - "invalidRange": "End date must be on or after the start date." + "noEndDate": "Pas de date de fin", + "oneShotCaption": "(dose unique)", + "invalidRange": "La date de fin doit être égale ou postérieure à la date de début." } }, "wizard": { "header": { - "stepOf": "Step {current} of {total}", - "createTitle": "New medication", - "editTitle": "Edit {name}" + "stepOf": "Étape {current} sur {total}", + "createTitle": "Nouveau médicament", + "editTitle": "Modifier {name}" }, "nav": { - "back": "Back", - "next": "Next", - "save": "Save", - "saveEdit": "Save changes", + "back": "Retour", + "next": "Suivant", + "save": "Enregistrer", + "saveEdit": "Enregistrer les modifications", "nextHint": "Suivant : {step}", "reviewHint": "Suivant : Enregistrer", "jumpFirst": "Aller à la première étape", "jumpLast": "Aller à la vérification et à l’enregistrement" }, "nl": { - "button": "Describe" + "button": "Décrire" }, "steps": { "step1": { - "title": "What's the medication called?", - "subline": "Exactly as it appears on the box.", + "title": "Comment s’appelle le médicament ?", + "subline": "Exactement comme indiqué sur la boîte.", "label": "Name", - "placeholder": "e.g. Ramipril", + "placeholder": "par exemple Ramipril", "short": "Nom" }, "step2": { - "title": "What kind of medication is it?", - "subline": "We use this for the right templates and analytics.", + "title": "De quel type de médicament s’agit-il ?", + "subline": "Nous l’utilisons pour les bons modèles et analyses.", "short": "Type" }, "step3": { - "title": "What's the dose?", - "subline": "A tablet, a puff, a drop — depending on the form.", - "amountLabel": "Amount", - "amountPlaceholder": "e.g. 5", - "unitLabel": "Unit", + "title": "Quelle est la dose ?", + "subline": "Un comprimé, une bouffée, une goutte — selon la forme.", + "amountLabel": "Quantité", + "amountPlaceholder": "par exemple 5", + "unitLabel": "Unité", "unit": { "mg": "mg", "ml": "ml", - "iu": "IU", + "iu": "UI", "mcg": "µg", "g": "g", - "tablets": "tablet(s)", - "capsules": "capsule(s)", - "drops": "drops", - "puffs": "puff", - "sprays": "spray", - "pieces": "pieces", - "other": "other" + "tablets": "comprimé(s)", + "capsules": "gélule(s)", + "drops": "gouttes", + "puffs": "bouffée", + "sprays": "pulvérisation", + "pieces": "pièces", + "other": "autre" }, "deliveryFormLabel": "Voie", "deliveryForm": { @@ -888,124 +898,124 @@ "short": "Dose" }, "step4": { - "title": "Over what period are you taking it?", - "subline": "A start date is enough for today. You can add an end date later.", + "title": "Sur quelle période le prenez-vous ?", + "subline": "Une date de début suffit pour aujourd’hui. Vous pourrez ajouter une date de fin plus tard.", "short": "Quand" }, "step5": { - "title": "How often do you take it?", - "subline": "Pick the cadence — the details follow on the next step.", + "title": "À quelle fréquence le prenez-vous ?", + "subline": "Choisissez la cadence — les détails suivent à l’étape suivante.", "short": "Fréquence" }, "step6": { - "title": "What does that look like in detail?", - "subline": "Set weekdays, intervals, or the day of the month.", + "title": "À quoi cela ressemble-t-il en détail ?", + "subline": "Indiquez les jours de la semaine, les intervalles ou le jour du mois.", "intervalWeeks": { - "label": "Every", - "suffix": "weeks" + "label": "Toutes les", + "suffix": "semaines" }, "dayOfMonth": { - "label": "On", - "suffix": "day of the month" + "label": "Le", + "suffix": "jour du mois" }, "rollingDays": { - "label": "Every", - "suffix": "days from the last intake" + "label": "Tous les", + "suffix": "jours depuis la dernière prise" }, "short": "Détail" }, "step7": { - "title": "When do you take it?", - "subline": "One or more times of day — reminders follow this list.", - "presetsLabel": "Time-of-day presets", + "title": "Quand le prenez-vous ?", + "subline": "Une ou plusieurs heures de la journée — les rappels suivent cette liste.", + "presetsLabel": "Préréglages par moment de la journée", "presets": { - "morning": "Morning", - "noon": "Noon", - "evening": "Evening", - "night": "Night" + "morning": "Matin", + "noon": "Midi", + "evening": "Soir", + "night": "Nuit" }, "short": "Heures" }, "step8": { - "title": "All done — should HealthLog remind you?", - "subline": "Review everything below before you save.", - "remindersLabel": "Reminders on", - "remindersDescription": "Push, Telegram or ntfy — based on your settings.", - "multiScheduleNote": "This medication has multiple schedules. The detailed editor returns in a later release.", + "title": "Terminé — HealthLog doit-il vous le rappeler ?", + "subline": "Vérifiez tout ci-dessous avant d’enregistrer.", + "remindersLabel": "Rappels activés", + "remindersDescription": "Push, Telegram ou ntfy — selon vos réglages.", + "multiScheduleNote": "Ce médicament a plusieurs horaires. L’éditeur détaillé reviendra dans une version ultérieure.", "short": "Fini" } }, "classRow": { - "bloodPressure": "Blood pressure", + "bloodPressure": "Tension artérielle", "diabetes": "Diabetes", "hormone": "Hormones", - "glp1": "GLP-1 injection", - "painRelief": "Pain", - "allergy": "Allergy", - "vitamin": "Vitamins", - "supplement": "Supplement", - "antibiotic": "Antibiotic", - "other": "Other" + "glp1": "Injection de GLP-1", + "painRelief": "Douleur", + "allergy": "Allergie", + "vitamin": "Vitamines", + "supplement": "Complément", + "antibiotic": "Antibiotique", + "other": "Autre" }, "cadence": { "daily": { - "label": "Daily", - "description": "Every day at the same time." + "label": "Quotidien", + "description": "Chaque jour à la même heure." }, "weekdays": { - "label": "Specific weekdays", - "description": "E.g. Monday, Wednesday, Friday." + "label": "Jours précis de la semaine", + "description": "Par exemple lundi, mercredi, vendredi." }, "everyNWeeks": { - "label": "Every few weeks", - "description": "E.g. every 2 weeks on Thursdays (GLP-1, biologics)." + "label": "Toutes les quelques semaines", + "description": "Par exemple toutes les 2 semaines le jeudi (GLP-1, biologiques)." }, "monthly": { - "label": "Monthly", - "description": "On a fixed day each month (e.g. always the 1st)." + "label": "Mensuel", + "description": "Un jour fixe chaque mois (par exemple toujours le 1er)." }, "rolling": { - "label": "Flexible from last dose", - "description": "Leave the next dose open. When you tap 'taken', the counter starts again from that moment." + "label": "Flexible depuis la dernière dose", + "description": "Laissez la prochaine dose ouverte. Quand vous appuyez sur « pris », le compteur repart à partir de ce moment." }, "oneShot": { - "label": "Single dose", - "description": "One administration, e.g. a vaccine or an antibiotic course." + "label": "Dose unique", + "description": "Une seule administration, par exemple un vaccin ou une cure d’antibiotique." } }, "summary": { - "title": "Summary", + "title": "Résumé", "cadence": { - "daily": "Every day", - "weekdays": "On selected days of the week", - "biweekly": "Every two weeks", - "monthly": "Monthly", - "quarterly": "Every three months", - "yearly": "Once a year", - "rolling": "Every {n} days from your last intake", - "oneShot": "A single dose", - "everyNWeeks": "Every {n} weeks", - "everyNMonths": "Every {n} months" + "daily": "Chaque jour", + "weekdays": "Les jours de la semaine sélectionnés", + "biweekly": "Toutes les deux semaines", + "monthly": "Mensuel", + "quarterly": "Tous les trois mois", + "yearly": "Une fois par an", + "rolling": "Tous les {n} jours depuis votre dernière prise", + "oneShot": "Une dose unique", + "everyNWeeks": "Toutes les {n} semaines", + "everyNMonths": "Tous les {n} mois" }, - "weekdaysDetail": ", on {days}", - "dayOfMonthDetail": ", on day {day}.", - "times": "at {times}", - "startsOn": "Starts: {date}", - "endsOn": "Ends: {date}", - "noEndDate": "No end date" + "weekdaysDetail": ", le {days}", + "dayOfMonthDetail": ", le {day}.", + "times": "à {times}", + "startsOn": "Début : {date}", + "endsOn": "Fin : {date}", + "noEndDate": "Pas de date de fin" }, "compose": { - "scheduleIndex": "Schedule {n} of {total}", + "scheduleIndex": "Horaire {n} sur {total}", "list": { - "add": "Add another schedule", - "edit": "Edit", - "remove": "Remove", - "removeDisabled": "At least one schedule is required.", - "empty": "No schedule yet." + "add": "Ajouter un autre horaire", + "edit": "Modifier", + "remove": "Retirer", + "removeDisabled": "Au moins un horaire est requis.", + "empty": "Pas encore d’horaire." } }, "errors": { - "submitFailed": "Could not save the medication — please retry." + "submitFailed": "Impossible d’enregistrer le médicament — veuillez réessayer." } }, "doseStrength": { @@ -1160,88 +1170,88 @@ }, "detail": { "status": { - "active": "Active", - "paused": "Paused", - "ended": "Ended" + "active": "Actif", + "paused": "En pause", + "ended": "Terminé" }, "today": { - "groupLabel": "Today's dose", - "taken": "Taken", - "skipped": "Skipped", - "toastTaken": "Today's dose logged", - "toastSkipped": "Today's dose skipped", - "recordedTaken": "Taken today at {time}", - "recordedSkipped": "Skipped today at {time}", - "recordedOneShot": "One-shot dose taken at {time}", - "noneScheduled": "No dose scheduled for today.", - "pausedHint": "Paused — no reminder today.", - "error": "Could not log the dose." + "groupLabel": "Dose du jour", + "taken": "Pris", + "skipped": "Sauté", + "toastTaken": "Dose du jour enregistrée", + "toastSkipped": "Dose du jour sautée", + "recordedTaken": "Prise aujourd’hui à {time}", + "recordedSkipped": "Sautée aujourd’hui à {time}", + "recordedOneShot": "Dose unique prise à {time}", + "noneScheduled": "Aucune dose prévue aujourd’hui.", + "pausedHint": "En pause — pas de rappel aujourd’hui.", + "error": "Impossible d’enregistrer la dose." }, "cadence": { - "oneShotOn": "One-time dose on {date}.", - "oneShotPending": "One-time dose pending." + "oneShotOn": "Dose unique le {date}.", + "oneShotPending": "Dose unique en attente." }, "intake": { - "title": "Intake history", - "importButton": "Import", + "title": "Historique des prises", + "importButton": "Importer", "rowActions": { - "openMenu": "Open row actions", - "edit": "Edit", - "delete": "Delete" + "openMenu": "Ouvrir les actions de la ligne", + "edit": "Modifier", + "delete": "Supprimer" }, "selection": { - "rowToggleLabel": "Select row" + "rowToggleLabel": "Sélectionner la ligne" }, "bulkDelete": { - "selectionCount": "{count} entries selected", - "deleteButton": "Delete selection", - "cancelButton": "Cancel", - "confirmTitle": "Delete the selected entries?", - "confirmBody": "{count} entries will be removed permanently. The medication itself stays.", - "confirmAction": "Delete", - "toast": "Selection deleted", - "failed": "Could not delete the selection." + "selectionCount": "{count} entrées sélectionnées", + "deleteButton": "Supprimer la sélection", + "cancelButton": "Annuler", + "confirmTitle": "Supprimer les entrées sélectionnées ?", + "confirmBody": "{count} entrées seront supprimées définitivement. Le médicament est conservé.", + "confirmAction": "Supprimer", + "toast": "Sélection supprimée", + "failed": "Impossible de supprimer la sélection." }, "edit": { - "dialogTitle": "Edit intake", - "takenAtLabel": "Taken at", - "skippedLabel": "Skipped", + "dialogTitle": "Modifier la prise", + "takenAtLabel": "Prise à", + "skippedLabel": "Sautée", "noteLabel": "Note", - "save": "Save", - "cancel": "Cancel", - "savedToast": "Intake updated", - "failed": "Could not update the intake." + "save": "Enregistrer", + "cancel": "Annuler", + "savedToast": "Prise mise à jour", + "failed": "Impossible de mettre à jour la prise." }, "deleteRow": { - "confirmTitle": "Delete this intake?", - "confirmBody": "The entry is removed permanently.", - "confirmAction": "Delete", - "toast": "Intake deleted", - "failed": "Could not delete the intake." + "confirmTitle": "Supprimer cette prise ?", + "confirmBody": "L’entrée est supprimée définitivement.", + "confirmAction": "Supprimer", + "toast": "Prise supprimée", + "failed": "Impossible de supprimer la prise." } }, "notifications": { - "title": "Notifications", - "switchLabel": "Send a reminder when this dose is due", - "helperOn": "Reminders are on.", - "helperOff": "Reminders are off.", - "enabledToast": "Reminders on", - "disabledToast": "Reminders off", - "toggleFailed": "Could not change the reminder setting.", - "clientManagedChip": "Your iPhone manages reminders for this medication." + "title": "Rappels", + "switchLabel": "Envoyer un rappel lorsque cette dose est due", + "helperOn": "Les rappels sont activés.", + "helperOff": "Les rappels sont désactivés.", + "enabledToast": "Rappels activés", + "disabledToast": "Rappels désactivés", + "toggleFailed": "Impossible de modifier le réglage des rappels.", + "clientManagedChip": "Votre iPhone gère les rappels de ce médicament." }, "settings": { - "title": "Settings", + "title": "Réglages", "phases": { - "openButton": "Configure phases", - "requiresCourseWindow": "Phases are available once a course window is set." + "openButton": "Configurer les phases", + "requiresCourseWindow": "Les phases sont disponibles une fois une période de traitement définie." }, "grace": { - "label": "Reminder window — applies to your primary schedule", - "primaryScheduleNote": "Multi-schedule medications keep their other schedules' default window.", + "label": "Fenêtre de rappel — s’applique à votre horaire principal", + "primaryScheduleNote": "Les médicaments à horaires multiples conservent la fenêtre par défaut de leurs autres horaires.", "unit": "minutes", - "saved": "Reminder window saved", - "failed": "Could not save the reminder window." + "saved": "Fenêtre de rappel enregistrée", + "failed": "Impossible d’enregistrer la fenêtre de rappel." }, "codes": { "label": "Codes cliniques (ATC / RxNorm)", @@ -1253,57 +1263,57 @@ } }, "zone": { - "title": "Manage & danger zone", + "title": "Gestion et zone à risque", "pause": { - "title": "Pause", - "helper": "Pause reminders until you turn them back on. History stays intact.", - "pausedToast": "Reminders paused", - "resumedToast": "Reminders resumed", - "failed": "Could not change the paused state." + "title": "Mettre en pause", + "helper": "Mettez les rappels en pause jusqu’à ce que vous les réactiviez. L’historique est conservé.", + "pausedToast": "Rappels mis en pause", + "resumedToast": "Rappels réactivés", + "failed": "Impossible de modifier l’état de pause." }, "end": { - "title": "End medication", - "helper": "Stop reminders and mark the medication as ended. History stays visible.", - "button": "End", - "dialogTitle": "End {name} — stop reminders, history stays visible", - "dialogBody": "No more reminders. Old entries remain in the timeline.", - "toast": "Medication ended", - "failed": "Could not end the medication." + "title": "Terminer le médicament", + "helper": "Arrête les rappels et marque le médicament comme terminé. L’historique reste visible.", + "button": "Terminer", + "dialogTitle": "Terminer {name} — arrête les rappels, l’historique reste visible", + "dialogBody": "Plus de rappels. Les anciennes entrées restent dans la chronologie.", + "toast": "Médicament terminé", + "failed": "Impossible de terminer le médicament." }, "purge": { - "title": "Delete history", - "helper": "{count} intake events are stored for this medication.", - "button": "Delete history", - "dialogTitle": "Really delete the history?", - "dialogBody": "The {count} intake events will be removed permanently. The medication itself stays.", - "toast": "History deleted", - "failed": "Could not delete the history." + "title": "Supprimer l’historique", + "helper": "{count} prises sont enregistrées pour ce médicament.", + "button": "Supprimer l’historique", + "dialogTitle": "Supprimer vraiment l’historique ?", + "dialogBody": "Les {count} prises seront supprimées définitivement. Le médicament est conservé.", + "toast": "Historique supprimé", + "failed": "Impossible de supprimer l’historique." }, "delete": { - "title": "Delete medication", - "helper": "Removes the medication and every related record.", - "button": "Delete", - "dialogTitle": "Delete {name}?", - "dialogBody": "The medication, its schedules, intake history, API tokens, phase config and reminders will be removed permanently.", - "toast": "Medication deleted", - "failed": "Could not delete the medication." + "title": "Supprimer le médicament", + "helper": "Supprime le médicament et tous les enregistrements associés.", + "button": "Supprimer", + "dialogTitle": "Supprimer {name} ?", + "dialogBody": "Le médicament, ses horaires, l’historique des prises, les jetons API, la configuration des phases et les rappels seront supprimés définitivement.", + "toast": "Médicament supprimé", + "failed": "Impossible de supprimer le médicament." } }, "phases": { - "modeLabel": "{phase} mode", - "saveFailed": "Could not save the reminder phases." + "modeLabel": "Mode {phase}", + "saveFailed": "Impossible d’enregistrer les phases de rappel." }, "api": { - "caption": "Endpoint for \"{name}\"", - "copyUrl": "Copy URL", - "urlCopied": "URL copied", - "mintToken": "Mint token", - "mintAnotherToken": "Mint another token", - "mintFailed": "Could not mint a token.", - "copyFailed": "Could not copy.", - "tokenCopied": "Token copied", - "copyToken": "Copy token", - "mintedHint": "Copy this token now — it is shown only once." + "caption": "Point de terminaison pour « {name} »", + "copyUrl": "Copier l’URL", + "urlCopied": "URL copiée", + "mintToken": "Générer un jeton", + "mintAnotherToken": "Générer un autre jeton", + "mintFailed": "Impossible de générer un jeton.", + "copyFailed": "Impossible de copier.", + "tokenCopied": "Jeton copié", + "copyToken": "Copier le jeton", + "mintedHint": "Copiez ce jeton maintenant — il n’est affiché qu’une seule fois." }, "edit": { "planOption": "Modifier le plan", @@ -1518,6 +1528,7 @@ "feedbackThanks": "Merci pour ton retour", "feedbackConfirmed": "Retour enregistré", "feedbackAlreadyRated": "Déjà noté", + "feedbackError": "Impossible d’enregistrer votre retour. Réessayez.", "confidence": "Confiance", "confidenceAria": "Confiance : {value} sur 100", "confidenceHigh": "Confiance élevée", @@ -1539,6 +1550,7 @@ "regenerateAnalysis": "Relancer l’analyse", "regenerateSuccess": "Analyse regénérée", "warmAssessments": "Préparer les évaluations", + "warmAssessmentsHint": "Les évaluations sont actualisées automatiquement pendant la nuit. Préparez-les maintenant pour voir les dernières données.", "warmStarted": "Les évaluations sont en cours de préparation en arrière-plan", "narrativeTitle": "Votre période en revue", "narrativeWeek": "Cette semaine", @@ -1696,6 +1708,7 @@ "tagline": "Coach personnel de santé, ancré sur tes données.", "newChat": "Nouvelle conversation", "send": "Envoyer", + "stop": "Arrêter", "thinking": "Réflexion en cours…", "composerPlaceholder": "Pose-moi une question sur tes données…", "composerHint": "Entrée pour envoyer, Maj+Entrée pour une nouvelle ligne.", @@ -2784,19 +2797,19 @@ "loadError": "Impossible de charger les notifications de votre appareil.", "disclaimer": "Ce sont des alertes que votre appareil a produites avec sa propre détection intégrée. Elles sont affichées ici uniquement à titre informatif. Il ne s’agit pas d’une évaluation médicale de HealthLog, et HealthLog ne pose aucun diagnostic. Si une notification vous inquiète, parlez-en à votre médecin.", "event": { - "irregularRhythm": "Irregular rhythm notification", - "highHeartRate": "High heart rate notification", - "lowHeartRate": "Low heart rate notification", - "walkingSteadiness": "Walking steadiness alert", - "breathingDisturbance": "Breathing disturbance during sleep" + "irregularRhythm": "Notification de rythme irrégulier", + "highHeartRate": "Notification de fréquence cardiaque élevée", + "lowHeartRate": "Notification de fréquence cardiaque basse", + "walkingSteadiness": "Alerte de stabilité à la marche", + "breathingDisturbance": "Trouble respiratoire pendant le sommeil" }, "verdict": { - "irregular": "Your device flagged a possible irregular rhythm.", - "notDetected": "Your device did not flag an irregular rhythm.", - "inconclusive": "Your device could not make a reading.", - "low": "Your device flagged low walking steadiness.", - "veryLow": "Your device flagged very low walking steadiness.", - "fired": "Your device flagged this event." + "irregular": "Votre appareil a détecté un possible rythme irrégulier.", + "notDetected": "Votre appareil n’a pas détecté de rythme irrégulier.", + "inconclusive": "Votre appareil n’a pas pu effectuer de mesure.", + "low": "Votre appareil a détecté une faible stabilité à la marche.", + "veryLow": "Votre appareil a détecté une très faible stabilité à la marche.", + "fired": "Votre appareil a signalé cet événement." } } }, @@ -2998,24 +3011,24 @@ "disableError": "Impossible de désactiver le mode Recherche. Veuillez réessayer." }, "shell": { - "sectionsNav": "Settings sections" + "sectionsNav": "Sections des réglages" }, "sections": { "account": { "title": "Compte", - "description": "Profile, password, passkeys, and your onboarding tour." + "description": "Profil, mot de passe, clés d’accès et votre visite guidée." }, "integrations": { "title": "Intégrations", - "description": "Withings, moodLog, and other connected services." + "description": "Withings, moodLog et autres services connectés." }, "notifications": { "title": "Notifications", - "description": "Live overview of every configured channel." + "description": "Aperçu en direct de chaque canal configuré." }, "dashboard": { "title": "Tableau de bord", - "description": "Tile layout and order." + "description": "Disposition et ordre des tuiles." }, "thresholds": { "title": "Objectifs", @@ -3078,12 +3091,12 @@ } }, "ai": { - "title": "AI Insights", - "description": "Provider, model, key." + "title": "Analyses IA", + "description": "Fournisseur, modèle, clé." }, "api": { "title": "API & Tokens", - "description": "Bearer tokens for your own scripts and apps — log measurements or medication intake from anywhere." + "description": "Jetons Bearer pour vos propres scripts et applis — enregistrez des mesures ou des prises de médicaments depuis n’importe où." }, "advanced": { "title": "Avancé", @@ -3091,8 +3104,9 @@ }, "export": { "title": "Exporter", - "description": "Download your health data as PDF, CSV, or a full JSON backup.", + "description": "Téléchargez vos données de santé en PDF, CSV ou sauvegarde JSON complète.", "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.", @@ -3100,39 +3114,39 @@ "formatHint": "PDF · prêt à imprimer" }, "actions": { - "download": "Download" + "download": "Télécharger" }, "filters": { "since": "De", - "until": "Until" + "until": "Jusqu’au" }, "cards": { "doctorReport": { - "title": "Doctor Report", - "description": "Printable PDF for the doctor's appointment — vitals, BMI, BP classification, medication compliance, mood." + "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": "Measurements", - "description": "Weight, blood pressure, pulse, glucose, and every other measurement as comma-separated values." + "title": "Mesures", + "description": "Poids, tension artérielle, pouls, glycémie et toutes les autres mesures en valeurs séparées par des virgules." }, "medicationsCsv": { "title": "Médicaments", - "description": "Your medication list with dosage, schedules, and (optionally) the full intake history.", - "includeIntake": "Include intake history" + "description": "Votre liste de médicaments avec posologie, horaires et (en option) l’historique complet des prises.", + "includeIntake": "Inclure l’historique des prises" }, "moodCsv": { "title": "Humeur", - "description": "Daily mood entries with score, tags, and timestamps." + "description": "Entrées d’humeur quotidiennes avec score, étiquettes et horodatages." }, "fullBackup": { - "title": "Full backup", - "description": "Single JSON file with everything — same shape as the weekly auto-backup. Useful for self-restore via admin upload." + "title": "Sauvegarde complète", + "description": "Un seul fichier JSON contenant tout — même format que la sauvegarde automatique hebdomadaire. Utile pour une restauration personnelle via le téléversement administrateur." } } }, "about": { - "title": "About", - "description": "Version, license, links." + "title": "À propos", + "description": "Version, licence, liens." }, "sharing": { "title": "Partage", @@ -3141,20 +3155,20 @@ }, "profile": "Profil", "language": "Langue", - "languageDescription": "Choose the display language of the application.", + "languageDescription": "Choisissez la langue d’affichage de l’application.", "username": "Nom d'utilisateur", - "height": "Height (cm)", + "height": "Taille (cm)", "dateOfBirth": "Date de naissance", - "dateOfBirthHint": "Used for automatic blood pressure target calculations.", + "dateOfBirthHint": "Utilisé pour le calcul automatique des objectifs de tension artérielle.", "gender": "Sexe", - "genderNone": "Not specified", + "genderNone": "Non précisé", "genderMale": "Homme", "genderFemale": "Femme", - "genderHint": "Used for gender-specific target values.", + "genderHint": "Utilisé pour des valeurs cibles spécifiques au sexe.", "timezone": "Fuseau horaire", - "timezoneHint": "Used for chart axis labels, reminder times, export timestamps, and Coach context. Stored data is unchanged.", - "timezoneInvalid": "Not a valid IANA timezone.", - "profileSaved": "Profile saved", + "timezoneHint": "Utilisé pour les libellés des axes des graphiques, les heures de rappel, les horodatages d’export et le contexte du Coach. Les données stockées restent inchangées.", + "timezoneInvalid": "Fuseau horaire IANA non valide.", + "profileSaved": "Profil enregistré", "avatar": { "title": "Photo de profil", "description": "Téléverse une photo stockée sur ton propre serveur. Elle apparaît dans toute l'application à la place d'un service d'avatar externe.", @@ -3168,174 +3182,174 @@ "tooLarge": "L'image dépasse la limite de 2 Mo.", "error": "Impossible de mettre à jour la photo de profil. Réessaie." }, - "changePassword": "Change password", - "changePasswordDescription": "Replace your current password with a new one.", - "passwordReset": "Password reset", - "currentPassword": "Current password", - "newPassword": "New password", - "confirmNewPassword": "Confirm new password", - "passwordMismatch": "New passwords do not match", - "passwordUpdated": "Password updated successfully", + "changePassword": "Changer le mot de passe", + "changePasswordDescription": "Remplacez votre mot de passe actuel par un nouveau.", + "passwordReset": "Réinitialisation du mot de passe", + "currentPassword": "Mot de passe actuel", + "newPassword": "Nouveau mot de passe", + "confirmNewPassword": "Confirmer le nouveau mot de passe", + "passwordMismatch": "Les nouveaux mots de passe ne correspondent pas", + "passwordUpdated": "Mot de passe mis à jour", "passkeys": "Passkeys", - "registeredPasskeys": "Registered Passkeys", - "passkeysDescription": "Passkeys allow secure login without a password.", + "registeredPasskeys": "Clés d’accès enregistrées", + "passkeysDescription": "Les clés d’accès permettent une connexion sécurisée sans mot de passe.", "passkeyName": "Nom", - "passkeyDevice": "Device", + "passkeyDevice": "Appareil", "passkeyBackup": "Backup", - "passkeyCreated": "Created", + "passkeyCreated": "Créée", "passkeyActions": "Actions", - "noPasskeys": "No passkeys registered.", - "addPasskey": "Add passkey", - "passkeyAdded": "Passkey added successfully!", - "passkeyOptionsError": "Could not load passkey options", - "passkeyRegistrationFailed": "Passkey registration failed", - "passkeyRegistrationCancelled": "Passkey registration cancelled", - "passkeyNotSupported": "Your browser or device doesn't support passkeys yet. Try a different browser or device.", - "passkeyAlreadyRegistered": "This device is already registered as a passkey for your account.", - "passkeySecurityBlocked": "Your browser blocked the passkey request for security reasons. Make sure you're on a trusted HTTPS origin and try again.", - "passkeyTimeout": "The passkey prompt timed out. Please try again.", - "passkeyUnknownError": "Passkey registration failed: {message}", - "deletePasskey": "Delete passkey?", - "deletePasskeyDescription": "The passkey will be permanently deleted.", - "singleDevice": "Single device", - "multiDevice": "Multi-device", - "backedUp": "backed up", + "noPasskeys": "Aucune clé d’accès enregistrée.", + "addPasskey": "Ajouter une clé d’accès", + "passkeyAdded": "Clé d’accès ajoutée !", + "passkeyOptionsError": "Impossible de charger les options de clé d’accès", + "passkeyRegistrationFailed": "Échec de l’enregistrement de la clé d’accès", + "passkeyRegistrationCancelled": "Enregistrement de la clé d’accès annulé", + "passkeyNotSupported": "Votre navigateur ou appareil ne prend pas encore en charge les clés d’accès. Essayez un autre navigateur ou appareil.", + "passkeyAlreadyRegistered": "Cet appareil est déjà enregistré comme clé d’accès de votre compte.", + "passkeySecurityBlocked": "Votre navigateur a bloqué la demande de clé d’accès pour des raisons de sécurité. Assurez-vous d’être sur une origine HTTPS de confiance et réessayez.", + "passkeyTimeout": "La demande de clé d’accès a expiré. Veuillez réessayer.", + "passkeyUnknownError": "Échec de l’enregistrement de la clé d’accès : {message}", + "deletePasskey": "Supprimer la clé d’accès ?", + "deletePasskeyDescription": "La clé d’accès sera définitivement supprimée.", + "singleDevice": "Appareil unique", + "multiDevice": "Multi-appareils", + "backedUp": "sauvegardée", "telegram": "Telegram Notifications", "telegramDescription": "Get reminders for missed medication intake via Telegram.", - "telegramSaved": "Telegram settings saved", - "botToken": "Bot Token", - "chatId": "Chat ID", - "enableNotifications": "Enable notifications", - "testMessage": "Test message", - "testSent": "Test message sent!", - "telegramStep1": "1. Create a bot via @BotFather in Telegram and copy the token.", - "telegramStep2": "2. Send /start to your bot to activate the chat.", - "telegramStep3": "3. Find your Chat ID via @userinfobot or the Bot API.", + "telegramSaved": "Réglages Telegram enregistrés", + "botToken": "Jeton du bot", + "chatId": "ID du chat", + "enableNotifications": "Activer les notifications", + "testMessage": "Message de test", + "testSent": "Message de test envoyé !", + "telegramStep1": "1. Créez un bot via @BotFather dans Telegram et copiez le jeton.", + "telegramStep2": "2. Envoyez /start à votre bot pour activer le chat.", + "telegramStep3": "3. Trouvez votre ID de chat via @userinfobot ou l’API du bot.", "ntfy": "ntfy", - "ntfyDescription": "Notifications via ntfy (self-hosted or ntfy.sh).", - "ntfyEnable": "Enable ntfy", - "ntfyServer": "Server URL", + "ntfyDescription": "Notifications via ntfy (auto-hébergé ou ntfy.sh).", + "ntfyEnable": "Activer ntfy", + "ntfyServer": "URL du serveur", "ntfyTopic": "Topic", - "ntfyAuthToken": "Auth Token (optional)", - "ntfyAuthTokenHint": "Only needed for private topics with access control.", - "webPush": "Browser Push", - "webPushDescription": "Receive notifications directly in your browser, even when HealthLog is not open.", - "webPushNotSupported": "Your browser does not support push notifications.", - "webPushDenied": "Push notifications are blocked. Allow them in your browser settings.", - "webPushNotConfigured": "Web Push is not configured on the server.", - "webPushSubscribe": "Enable push", - "webPushUnsubscribe": "Disable push", - "webPushSubscribed": "Push notifications enabled!", - "webPushUnsubscribed": "Push notifications disabled.", - "webPushSubscribeFailed": "Activation failed", + "ntfyAuthToken": "Jeton d’authentification (facultatif)", + "ntfyAuthTokenHint": "Nécessaire uniquement pour les sujets privés avec contrôle d’accès.", + "webPush": "Push navigateur", + "webPushDescription": "Recevez des notifications directement dans votre navigateur, même lorsque HealthLog n’est pas ouvert.", + "webPushNotSupported": "Votre navigateur ne prend pas en charge les notifications push.", + "webPushDenied": "Les notifications push sont bloquées. Autorisez-les dans les réglages de votre navigateur.", + "webPushNotConfigured": "Web Push n’est pas configuré sur le serveur.", + "webPushSubscribe": "Activer le push", + "webPushUnsubscribe": "Désactiver le push", + "webPushSubscribed": "Notifications push activées !", + "webPushUnsubscribed": "Notifications push désactivées.", + "webPushSubscribeFailed": "Échec de l’activation", "webPushActive": "Actif", - "kiInsightsDescription": "Optional: Save your OpenAI key for daily, automatic evaluations in Insights.", - "codexConnected": "ChatGPT connected successfully! Insights are now active.", - "codexDisconnected": "ChatGPT connection removed.", - "codexConnectionFailed": "ChatGPT connection failed. Please try again.", - "rawData": "Send raw data", - "rawDataOnDescription": "Aggregated metrics plus anonymized raw points from the last 30 days are sent. This includes per-metric measurements (for example weight, blood pressure, pulse) with time context so the provider can detect patterns, outliers, and correlations more reliably. Name, email, and direct account identifiers are not transmitted.", - "rawDataOffDescription": "Only summarized metrics are sent (for example averages, trends, minimum/maximum). No individual points and no exact time context. This is more privacy-preserving, but the provider can be less precise for short-term patterns and fluctuations.", - "rawDataWarning": "Raw mode enabled: the provider receives additional anonymized points from the last 30 days for higher analysis accuracy.", - "regenerateInsights": "Regenerate reports", - "regenerateSuccess": "Reports regenerated successfully", - "regenerateRateLimit": "Please wait — you've hit the hourly limit for analyses.", - "lastGeneratedAt": "Last generated", + "kiInsightsDescription": "Facultatif : enregistrez votre clé OpenAI pour des analyses quotidiennes et automatiques dans Insights.", + "codexConnected": "ChatGPT connecté ! Insights est maintenant actif.", + "codexDisconnected": "Connexion à ChatGPT supprimée.", + "codexConnectionFailed": "Échec de la connexion à ChatGPT. Veuillez réessayer.", + "rawData": "Envoyer les données brutes", + "rawDataOnDescription": "Des métriques agrégées sont envoyées, ainsi que des points bruts anonymisés des 30 derniers jours. Cela inclut des mesures par métrique (par exemple poids, tension, pouls) avec un contexte temporel, afin que le fournisseur puisse détecter plus fiablement les tendances, les valeurs aberrantes et les corrélations. Le nom, l’e-mail et les identifiants de compte directs ne sont pas transmis.", + "rawDataOffDescription": "Seules des métriques résumées sont envoyées (par exemple moyennes, tendances, minimum/maximum). Aucun point individuel ni contexte temporel exact. Cela préserve mieux la vie privée, mais le fournisseur peut être moins précis pour les tendances et fluctuations à court terme.", + "rawDataWarning": "Mode brut activé : le fournisseur reçoit des points anonymisés supplémentaires des 30 derniers jours pour une analyse plus précise.", + "regenerateInsights": "Régénérer les rapports", + "regenerateSuccess": "Rapports régénérés", + "regenerateRateLimit": "Veuillez patienter — vous avez atteint la limite horaire d’analyses.", + "lastGeneratedAt": "Dernière génération", "ai": { - "chatgptConnectedBadge": "ChatGPT connected", - "adminAiActiveBadge": "Admin provider active", - "connectionExpiredBadge": "Connection expired", - "connectedSince": "Connected since {when}.", - "deviceCodeHeading": "Finish connecting on chatgpt.com", - "deviceCodeStep1": "Open this link on any device:", - "deviceCodeStep2": "Enter this one-time code:", - "deviceCodeStep3": "Approve the connection — this page updates automatically.", + "chatgptConnectedBadge": "ChatGPT connecté", + "adminAiActiveBadge": "Fournisseur administrateur actif", + "connectionExpiredBadge": "Connexion expirée", + "connectedSince": "Connecté depuis {when}.", + "deviceCodeHeading": "Terminez la connexion sur chatgpt.com", + "deviceCodeStep1": "Ouvrez ce lien sur n’importe quel appareil :", + "deviceCodeStep2": "Saisissez ce code à usage unique :", + "deviceCodeStep3": "Approuvez la connexion — cette page se met à jour automatiquement.", "deviceCodeCopy": "Copier", - "deviceCodeWaiting": "Waiting for approval…", + "deviceCodeWaiting": "En attente d’approbation…", "deviceCodeCancel": "Annuler", - "oauthNotConfigured": "ChatGPT OAuth is not configured on this instance — use your own API key below instead.", - "modelLabel": "Model", - "modelOptionDefault": "— Default —", - "modelOptionCustom": "Custom…", - "customModelLabel": "Custom model name", - "anthropicKeyLabel": "Anthropic API key", + "oauthNotConfigured": "L’OAuth ChatGPT n’est pas configuré sur cette instance — utilisez plutôt votre propre clé API ci-dessous.", + "modelLabel": "Modèle", + "modelOptionDefault": "— Par défaut —", + "modelOptionCustom": "Personnalisé…", + "customModelLabel": "Nom du modèle personnalisé", + "anthropicKeyLabel": "Clé API Anthropic", "baseUrlLabel": "Base URL", - "localKeyLabel": "API key (optional)", - "savedPreview": "(saved {preview})", - "savedShort": "(saved)", + "localKeyLabel": "Clé API (facultative)", + "savedPreview": "(enregistrée {preview})", + "savedShort": "(enregistrée)", "saveCta": "Enregistrer", "saved": "Enregistré", - "saveFailed": "Save failed", + "saveFailed": "Échec de l’enregistrement", "errorGeneric": "Erreur", "providerChain": { "types": { "codex": "ChatGPT (Codex)", - "openai": "OpenAI (your key)", + "openai": "OpenAI (votre clé)", "anthropic": "Anthropic (Claude)", - "local": "Local model", - "admin-openai": "Admin OpenAI" + "local": "Modèle local", + "admin-openai": "OpenAI administrateur" }, - "title": "Fallback chain", - "description": "If the primary provider fails, HealthLog walks the chain in order. Drag the rows to reorder, toggle the switch to disable a provider without removing it.", - "moveUp": "Move up", - "moveDown": "Move down", - "removeFromChain": "Remove from chain", - "addProvider": "Add provider", - "addNoneAvailable": "All providers already in chain", - "saveOrder": "Save chain order", - "saved": "Chain saved", - "saveFailed": "Saving the chain failed", - "resetDefaults": "Reset to defaults", - "resetConfirmTitle": "Reset chain to defaults?", - "resetConfirmBody": "The chain reverts to Codex → OpenAI → Anthropic → Local → Admin OpenAI. Your saved credentials are not touched." - }, - "activeProviderHeading": "Active provider", - "activeProviderBody": "Pick the provider you want to use first. The form below configures only the selected provider; the fallback chain at the bottom decides what happens if it fails.", - "activeProviderLabel": "Primary provider", - "providerConfigTitle": "Provider configuration", + "title": "Chaîne de secours", + "description": "Si le fournisseur principal échoue, HealthLog parcourt la chaîne dans l’ordre. Faites glisser les lignes pour les réorganiser ; utilisez l’interrupteur pour désactiver un fournisseur sans le supprimer.", + "moveUp": "Monter", + "moveDown": "Descendre", + "removeFromChain": "Retirer de la chaîne", + "addProvider": "Ajouter un fournisseur", + "addNoneAvailable": "Tous les fournisseurs sont déjà dans la chaîne", + "saveOrder": "Enregistrer l’ordre de la chaîne", + "saved": "Chaîne enregistrée", + "saveFailed": "Échec de l’enregistrement de la chaîne", + "resetDefaults": "Réinitialiser", + "resetConfirmTitle": "Réinitialiser la chaîne aux valeurs par défaut ?", + "resetConfirmBody": "La chaîne revient à Codex → OpenAI → Anthropic → Local → OpenAI administrateur. Vos identifiants enregistrés ne sont pas affectés." + }, + "activeProviderHeading": "Fournisseur actif", + "activeProviderBody": "Choisissez le fournisseur à utiliser en premier. Le formulaire ci-dessous configure uniquement le fournisseur sélectionné ; la chaîne de secours en bas détermine ce qui se passe en cas d’échec.", + "activeProviderLabel": "Fournisseur principal", + "providerConfigTitle": "Configuration du fournisseur", "providerSelect": { - "codex": "ChatGPT account (Codex)", - "openai": "OpenAI (your API key)", + "codex": "Compte ChatGPT (Codex)", + "openai": "OpenAI (votre clé API)", "anthropic": "Anthropic (Claude)", - "local": "Local model (OpenAI-compatible)", - "admin-openai": "Admin-provided OpenAI" + "local": "Modèle local (compatible OpenAI)", + "admin-openai": "OpenAI fourni par l’administrateur" }, "openai": { - "modelSelect": "Model", - "modelOptionCustom": "Custom slug…", - "modelCustomLabel": "Custom model slug", + "modelSelect": "Modèle", + "modelOptionCustom": "Slug personnalisé…", + "modelCustomLabel": "Slug du modèle personnalisé", "modelCustomPlaceholder": "gpt-5", - "baseUrlLabel": "Base URL (advanced)", + "baseUrlLabel": "URL de base (avancé)", "baseUrlPlaceholder": "https://api.openai.com/v1", - "baseUrlHelp": "Override only when using an OpenAI-compatible gateway. Leave blank for OpenAI itself.", - "showAdvanced": "Show advanced", - "hideAdvanced": "Hide advanced", - "apiKey": "API key", + "baseUrlHelp": "À remplacer uniquement si vous utilisez une passerelle compatible OpenAI. Laissez vide pour OpenAI lui-même.", + "showAdvanced": "Afficher les options avancées", + "hideAdvanced": "Masquer les options avancées", + "apiKey": "Clé API", "apiKeyPlaceholder": "sk-…" }, "codex": { - "statusConnected": "Connected", - "statusDisconnected": "Not connected", - "statusExpired": "Connection expired", - "connectButton": "Connect with ChatGPT", - "disconnectButton": "Disconnect", - "modelSlugLabel": "Model slug", - "modelSlugBody": "Codex uses the model your ChatGPT subscription routes to. The CODEX_MODEL environment variable lets ops override this on the instance.", - "lastInsight": "Last insight: {when}" + "statusConnected": "Connecté", + "statusDisconnected": "Non connecté", + "statusExpired": "Connexion expirée", + "connectButton": "Se connecter avec ChatGPT", + "disconnectButton": "Déconnecter", + "modelSlugLabel": "Slug du modèle", + "modelSlugBody": "Codex utilise le modèle vers lequel votre abonnement ChatGPT route. La variable d’environnement CODEX_MODEL permet à l’opérateur de le remplacer sur l’instance.", + "lastInsight": "Dernière analyse : {when}" }, "adminOpenai": { - "title": "Admin OpenAI", - "body": "Operator-provided OpenAI key. Used as a last-ditch fallback when no personal provider is configured. There is nothing to configure on this row — visibility means the operator has set it up.", - "notConfigured": "The operator has not configured a shared OpenAI key on this instance." + "title": "OpenAI administrateur", + "body": "Clé OpenAI fournie par l’opérateur. Utilisée en dernier recours lorsqu’aucun fournisseur personnel n’est configuré. Rien à configurer sur cette ligne — sa visibilité signifie que l’opérateur l’a mise en place.", + "notConfigured": "L’opérateur n’a pas configuré de clé OpenAI partagée sur cette instance." }, - "testProvider": "Test active provider", + "testProvider": "Tester le fournisseur actif", "testSuccess": "OK — {provider} ({model})", - "testFailedShort": "Test failed: {message}", - "testUnexpectedResponse": "AI provider connection failed — unexpected response from the server.", - "testReasonCredentials": "Provider rejected the credentials — re-authenticate in AI settings.", - "testReasonRateLimited": "Provider rate-limited the request — try again shortly.", - "testReasonServerError": "The AI provider returned a server error.", - "testReasonUnreachable": "Could not reach the AI provider.", + "testFailedShort": "Échec du test : {message}", + "testUnexpectedResponse": "Échec de la connexion au fournisseur d’IA — réponse inattendue du serveur.", + "testReasonCredentials": "Le fournisseur a rejeté les identifiants — réauthentifiez-vous dans les réglages IA.", + "testReasonRateLimited": "Le fournisseur a limité la requête — réessayez sous peu.", + "testReasonServerError": "Le fournisseur d’IA a renvoyé une erreur serveur.", + "testReasonUnreachable": "Impossible de joindre le fournisseur d’IA.", "coachMemory": { "title": "Ce dont le Coach se souvient", "description": "Les informations durables que vous avez confiées au Coach. Il s'en sert pour garder ses réponses pertinentes. Supprimez tout ce qu'il vaut mieux qu'il oublie.", @@ -3380,28 +3394,28 @@ } }, "withings": "Withings", - "withingsDescription": "Connect your Withings scale and blood pressure monitors.", - "withingsCredentials": "API Credentials", - "withingsClientId": "Client ID", - "withingsClientSecret": "Client Secret", - "withingsCredentialsSaved": "Credentials saved", - "withingsCredentialsSavedPlaceholder": "Saved — enter new to replace", - "withingsCredentialsSavedPlaceholderSecret": "Saved — enter new to replace", - "withingsSaveCredentials": "Save credentials", - "configured": "Configured", - "withingsSync": "Sync now", - "withingsFullSync": "Sync all data", - "withingsFullSyncTitle": "Full synchronization?", - "withingsFullSyncDescription": "All available Withings data will be fully synchronized. This may take some time depending on your history.", - "withingsSyncResult": "{count} measurements synchronized", - "withingsFullSyncResult": "{count} measurements fully synchronized", - "withingsSyncFailed": "Sync failed", - "withingsSynchronize": "Synchronize", - "withingsDisconnect": "Disconnect", - "withingsDisconnectTitle": "Disconnect Withings?", - "withingsDisconnectDescription": "The connection to Withings will be disconnected. Previously synced data will be preserved.", - "withingsConnect": "Connect with Withings", - "withingsNoCredentials": "Please enter your API credentials above to connect Withings.", + "withingsDescription": "Connectez votre balance et vos tensiomètres Withings.", + "withingsCredentials": "Identifiants API", + "withingsClientId": "ID client", + "withingsClientSecret": "Secret client", + "withingsCredentialsSaved": "Identifiants enregistrés", + "withingsCredentialsSavedPlaceholder": "Enregistré — saisissez-en un nouveau pour remplacer", + "withingsCredentialsSavedPlaceholderSecret": "Enregistré — saisissez-en un nouveau pour remplacer", + "withingsSaveCredentials": "Enregistrer les identifiants", + "configured": "Configuré", + "withingsSync": "Synchroniser maintenant", + "withingsFullSync": "Synchroniser toutes les données", + "withingsFullSyncTitle": "Synchronisation complète ?", + "withingsFullSyncDescription": "Toutes les données Withings disponibles seront entièrement synchronisées. Cela peut prendre un certain temps selon votre historique.", + "withingsSyncResult": "{count} mesures synchronisées", + "withingsFullSyncResult": "{count} mesures entièrement synchronisées", + "withingsSyncFailed": "Échec de la synchronisation", + "withingsSynchronize": "Synchroniser", + "withingsDisconnect": "Déconnecter", + "withingsDisconnectTitle": "Déconnecter Withings ?", + "withingsDisconnectDescription": "La connexion à Withings sera déconnectée. Les données déjà synchronisées seront conservées.", + "withingsConnect": "Se connecter avec Withings", + "withingsNoCredentials": "Saisissez d’abord vos identifiants API ci-dessus pour connecter Withings.", "whoop": "WHOOP", "whoopDescription": "Connectez votre bracelet WHOOP pour synchroniser récupération, sommeil, effort et entraînements.", "whoopOverlapNote": "Si WHOOP et une autre source fournissent la même donnée vitale — fréquence cardiaque au repos, oxygène sanguin, température corporelle, fréquence respiratoire ou phases de sommeil —, les deux valeurs peuvent apparaître jusqu'à ce qu'une future mise à jour retienne une seule source préférée.", @@ -3430,9 +3444,9 @@ "withings": { "reconnect": { "banner": { - "title": "Activity sync available", - "body": "Your Withings connection was authorised before activity sync was added. Reconnect once to enable steps, active energy, walking + running distance, and floors-climbed ingest from your Withings devices.", - "action": "Reconnect Withings" + "title": "Synchronisation d’activité disponible", + "body": "Votre connexion Withings a été autorisée avant l’ajout de la synchronisation d’activité. Reconnectez-vous une fois pour activer l’import des pas, de l’énergie active, de la distance à pied et en course, et des étages gravis depuis vos appareils Withings.", + "action": "Reconnecter Withings" } } } @@ -3443,88 +3457,89 @@ "warningServerError": "Connecté, erreur serveur", "parkedReconnect": "En pause — reconnecter manuellement", "notConnected": "Non connecté", - "justNow": "just now", - "minutesAgo": "{count} min ago", - "hoursAgo": "{count} h ago", - "daysAgo": "{count} d ago", - "ariaLabel": "Integration status", + "justNow": "à l’instant", + "minutesAgo": "il y a {count} min", + "hoursAgo": "il y a {count} h", + "daysAgo": "il y a {count} j", + "ariaLabel": "Statut de l’intégration", "resumeCta": "Reconnecter", "resumeSuccess": "Intégration reprise", "resumeError": "Échec de la reconnexion" }, - "apiTokens": "API Tokens", - "apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).", - "tokenNamePlaceholder": "Token name (e.g., iPhone Shortcut)", - "tokenCreated": "Token created — copy it now! It won't be shown again.", - "tokenRevoke": "Revoke token?", - "tokenRevokeDescription": "The token will be permanently deactivated. Applications using it will lose access.", - "tokenRevoked": "Revoked", - "tokenExpired": "Expired", + "apiTokens": "Jetons API", + "apiTokensDescription": "Créez des jetons API pour enregistrer des prises de médicaments externes (par exemple Raccourcis, automatisations).", + "tokenNamePlaceholder": "Nom du jeton (par exemple Raccourci iPhone)", + "tokenCreated": "Jeton créé — copiez-le maintenant ! Il ne sera plus affiché.", + "tokenRevoke": "Révoquer le jeton ?", + "tokenRevokeAction": "Révoquer le jeton", + "tokenRevokeDescription": "Le jeton sera désactivé définitivement. Les applications qui l’utilisent perdront l’accès.", + "tokenRevoked": "Révoqué", + "tokenExpired": "Expiré", "tokenActive": "Actif", - "activeTokensTitle": "Active tokens", - "revokedTokensTitle": "Revoked tokens ({count})", + "activeTokensTitle": "Jetons actifs", + "revokedTokensTitle": "Jetons révoqués ({count})", "tokenTableName": "Nom", - "tokenTablePermissions": "Permissions", + "tokenTablePermissions": "Autorisations", "tokenTableStatus": "Statut", - "tokenTableCreated": "Created", - "tokenTableLastUsed": "Last used", + "tokenTableCreated": "Créé", + "tokenTableLastUsed": "Dernière utilisation", "tokenTableActions": "Actions", - "noActiveTokens": "No active tokens available.", - "tokenNeverUsed": "Never", - "apiEndpointsTitle": "API endpoints", - "apiEndpointsDescription": "Overview of currently available external endpoint(s) for token-based ingestion.", - "apiEndpointMethod": "Method", - "apiEndpointPath": "Path", - "apiEndpointAuth": "Authentication", - "apiEndpointExample": "Body example", - "collapse": "Collapse", - "expand": "Expand", - "dangerZone": "Delete All Data", - "dangerZoneTitle": "Danger zone", - "dangerZoneDescription": "Deletes all your health data and integrations. Your user account will be preserved.", - "dangerZoneConfirm": "Delete all data?", - "dangerZoneConfirmDescription": "This will permanently delete all your health data and integrations. Your user account will be preserved.", - "dangerZoneSuccess": "All personal data has been deleted", - "dangerZoneDeleteFailed": "Deletion failed", - "finalDelete": "Delete permanently", - "deleteAccountCardTitle": "Delete account entirely", - "deleteAccountCardDescription": "Deletes your account along with passkeys, audit log, and sessions. This cannot be undone.", - "deleteAccountCta": "Delete account", - "deleteAccountConfirmTitle": "Delete account permanently?", - "deleteAccountConfirmDescription": "Your account, all health data, passkeys, sessions, and audit entries will be permanently deleted. You will be signed out immediately after.", - "deleteAccountSuccess": "Account deleted — signing you out.", - "deleteAccountFailed": "Account could not be deleted", - "deleteAccountFinal": "Delete permanently", + "noActiveTokens": "Aucun jeton actif disponible.", + "tokenNeverUsed": "Jamais", + "apiEndpointsTitle": "Points de terminaison API", + "apiEndpointsDescription": "Aperçu des points de terminaison externes actuellement disponibles pour l’ingestion par jeton.", + "apiEndpointMethod": "Méthode", + "apiEndpointPath": "Chemin", + "apiEndpointAuth": "Authentification", + "apiEndpointExample": "Exemple de corps", + "collapse": "Réduire", + "expand": "Développer", + "dangerZone": "Supprimer toutes les données", + "dangerZoneTitle": "Zone à risque", + "dangerZoneDescription": "Supprime toutes vos données de santé et intégrations. Votre compte utilisateur sera conservé.", + "dangerZoneConfirm": "Supprimer toutes les données ?", + "dangerZoneConfirmDescription": "Cette action supprimera définitivement toutes vos données de santé et intégrations. Votre compte utilisateur sera conservé.", + "dangerZoneSuccess": "Toutes les données personnelles ont été supprimées", + "dangerZoneDeleteFailed": "Échec de la suppression", + "finalDelete": "Supprimer définitivement", + "deleteAccountCardTitle": "Supprimer entièrement le compte", + "deleteAccountCardDescription": "Supprime votre compte ainsi que les clés d’accès, le journal d’audit et les sessions. Cette action est irréversible.", + "deleteAccountCta": "Supprimer le compte", + "deleteAccountConfirmTitle": "Supprimer définitivement le compte ?", + "deleteAccountConfirmDescription": "Votre compte, toutes vos données de santé, vos clés d’accès, vos sessions et vos entrées d’audit seront définitivement supprimés. Vous serez déconnecté immédiatement après.", + "deleteAccountSuccess": "Compte supprimé — déconnexion en cours.", + "deleteAccountFailed": "Le compte n’a pas pu être supprimé", + "deleteAccountFinal": "Supprimer définitivement", "saved": "Enregistré", - "savingError": "Error saving", + "savingError": "Erreur lors de l’enregistrement", "moodLogTitle": "moodLog", "moodLogDescription": "Import mood data from moodLog", "moodLogUrl": "moodLog URL", "moodLogUrlPlaceholder": "https://mood.example.com", - "moodLogApiKey": "API Key", + "moodLogApiKey": "Clé API", "moodLogApiKeyPlaceholder": "ml_...", - "moodLogWebhookSecret": "Webhook Secret", - "moodLogWebhookSecretHelp": "Enter this secret in moodLog as webhook secret", - "moodLogSync": "Start sync", - "moodLogFullSync": "Full sync", - "moodLogFullSyncConfirm": "Synchronize fully", - "moodLogFullSyncTitle": "Full sync?", - "moodLogFullSyncDescription": "All available mood data will be fully synchronized.", - "moodLogDisconnect": "Disconnect", - "moodLogDisconnectTitle": "Disconnect moodLog?", - "moodLogDisconnectDescription": "The connection will be removed and all imported mood data will be deleted.", - "moodLogEntries": "Entries", - "moodLogSyncResult": "{count} entries synchronized", - "moodLogSyncFailed": "Sync failed", - "moodLogSaved": "moodLog connection saved", - "moodLogDisconnected": "moodLog disconnected", + "moodLogWebhookSecret": "Secret du webhook", + "moodLogWebhookSecretHelp": "Saisissez ce secret dans moodLog comme secret de webhook", + "moodLogSync": "Démarrer la synchronisation", + "moodLogFullSync": "Synchronisation complète", + "moodLogFullSyncConfirm": "Synchroniser entièrement", + "moodLogFullSyncTitle": "Synchronisation complète ?", + "moodLogFullSyncDescription": "Toutes les données d’humeur disponibles seront entièrement synchronisées.", + "moodLogDisconnect": "Déconnecter", + "moodLogDisconnectTitle": "Déconnecter moodLog ?", + "moodLogDisconnectDescription": "La connexion sera supprimée et toutes les données d’humeur importées seront supprimées.", + "moodLogEntries": "Entrées", + "moodLogSyncResult": "{count} entrées synchronisées", + "moodLogSyncFailed": "Échec de la synchronisation", + "moodLogSaved": "Connexion moodLog enregistrée", + "moodLogDisconnected": "moodLog déconnecté", "about": { - "version": "App version", + "version": "Version de l’app", "gitSha": "Build", - "builtAt": "Built {time}", - "builtAtLabel": "Built", - "license": "License", - "repository": "Source code", + "builtAt": "Compilé {time}", + "builtAtLabel": "Compilé", + "license": "Licence", + "repository": "Code source", "changelog": "Changelog", "docs": "Documentation", "linksHeading": "Sources et documentation", @@ -3533,42 +3548,42 @@ "tourReplayHint": "Voir la visite guidée du tableau de bord — pratique si tu l'as sautée à la première visite." }, "testConnection": { - "test": "Test connection", - "testing": "Testing…", - "ok": "Connected (latency {latency} ms)", + "test": "Tester la connexion", + "testing": "Test en cours…", + "ok": "Connecté (latence {latency} ms)", "errors": { - "credentials_rejected": "Credentials rejected — check your token", - "rate_limited": "Rate-limited by the upstream", - "timeout": "Request timed out", - "upstream_error": "Upstream returned an error", - "connection_failed": "Connection failed", - "not_configured": "Not configured", - "url_not_public": "URL is not a public endpoint", - "url_invalid": "URL is invalid", - "redirected": "Endpoint redirects — check the URL", - "endpoint_not_found": "Endpoint not found at the URL", - "credentials_unreadable": "Credentials cannot be decrypted", - "upstream_invalid_json": "Upstream sent invalid JSON", - "vapid_not_configured": "Web Push not configured (VAPID keys missing)", - "rate_limited_self": "Too many test requests", - "generic": "Test failed" + "credentials_rejected": "Identifiants rejetés — vérifiez votre jeton", + "rate_limited": "Limité par le service en amont", + "timeout": "Délai de la requête dépassé", + "upstream_error": "Le service en amont a renvoyé une erreur", + "connection_failed": "Échec de la connexion", + "not_configured": "Non configuré", + "url_not_public": "L’URL n’est pas un point de terminaison public", + "url_invalid": "L’URL n’est pas valide", + "redirected": "Le point de terminaison redirige — vérifiez l’URL", + "endpoint_not_found": "Point de terminaison introuvable à l’URL", + "credentials_unreadable": "Les identifiants ne peuvent pas être déchiffrés", + "upstream_invalid_json": "Le service en amont a envoyé un JSON non valide", + "vapid_not_configured": "Web Push non configuré (clés VAPID manquantes)", + "rate_limited_self": "Trop de requêtes de test", + "generic": "Échec du test" } }, "notificationStatus": { "title": "Channel reliability", "description": "Live status of every notification channel — Auto-disabled means HealthLog stopped retrying after a permanent error.", - "emptyDescription": "No channels configured yet. Add a channel below to start tracking its delivery health.", + "emptyDescription": "Aucun canal configuré pour l’instant. Ajoutez un canal ci-dessous pour commencer à suivre l’état de sa livraison.", "stateActive": "Actif", - "stateAutoDisabled": "Auto-disabled", - "stateSendingPaused": "Sending paused", + "stateAutoDisabled": "Désactivé automatiquement", + "stateSendingPaused": "Envoi en pause", "stateManuallyDisabled": "Désactivé", - "lastSuccess": "Last successful send", - "lastFailure": "Last failure", - "consecutiveFailures": "Consecutive failures", - "disabledReason": "Reason", - "nextRetry": "Next retry", - "reEnable": "Re-enable", - "sendTest": "Send test" + "lastSuccess": "Dernier envoi réussi", + "lastFailure": "Dernier échec", + "consecutiveFailures": "Échecs consécutifs", + "disabledReason": "Raison", + "nextRetry": "Prochaine tentative", + "reEnable": "Réactiver", + "sendTest": "Envoyer un test" }, "identity": { "description": "Informations facultatives pour l’export du dossier de santé (couverture PDF + export FHIR). Tous les champs sont facultatifs.", @@ -4108,284 +4123,284 @@ "carrierUnavailableGeoFallback": "{location} — carrier unavailable" }, "achievements": { - "title": "Achievements", - "subtitle": "Earn achievements by tracking your health and taking your meds on time.", - "loginRequired": "Please sign in to view achievements.", + "title": "Réussites", + "subtitle": "Débloquez des réussites en suivant votre santé et en prenant vos médicaments à temps.", + "loginRequired": "Connectez-vous pour voir les réussites.", "points": "Points", - "unlocked": "Unlocked", - "nextGoal": "Next goal", - "allCompleted": "All achievements unlocked!", - "completed": "Completed", - "goalReached": "Goal reached", - "remainingUnlocks": "{count} still unlockable", - "noneUnlockedYet": "No achievements unlocked yet.", + "unlocked": "Débloqué", + "nextGoal": "Prochain objectif", + "allCompleted": "Toutes les réussites débloquées !", + "completed": "Terminé", + "goalReached": "Objectif atteint", + "remainingUnlocks": "{count} encore à débloquer", + "noneUnlockedYet": "Aucune réussite débloquée pour l’instant.", "pointsValue": "{points} points", "metricCount": "{count}", - "metricDays": "{count} days", + "metricDays": "{count} jours", "metricPercent": "{count}%", "badges": { "intakeTotal1": { - "title": "First intake", - "description": "Record your first taken medication event." + "title": "Première prise", + "description": "Enregistrez votre première prise de médicament." }, "intakeTotal10": { - "title": "Intake starter", - "description": "Record 10 taken medication events." + "title": "Débutant des prises", + "description": "Enregistrez 10 prises de médicaments." }, "intakeTotal50": { - "title": "Intake routine", - "description": "Record 50 taken medication events." + "title": "Routine des prises", + "description": "Enregistrez 50 prises de médicaments." }, "intakeTotal150": { - "title": "Intake expert", - "description": "Record 150 taken medication events." + "title": "Expert des prises", + "description": "Enregistrez 150 prises de médicaments." }, "intakeTotal300": { - "title": "Intake legend", - "description": "Record 300 taken medication events." + "title": "Légende des prises", + "description": "Enregistrez 300 prises de médicaments." }, "overIntake1": { - "title": "Double take", - "description": "Take medication at least once more than planned." + "title": "Double dose", + "description": "Prenez votre médicament au moins une fois de plus que prévu." }, "skippedIntake1": { - "title": "Stepped back", - "description": "Skip at least one planned intake." + "title": "Pause prise", + "description": "Sautez au moins une prise prévue." }, "passkeyCreated1": { - "title": "Passkey set up", - "description": "Create your first passkey." + "title": "Clé d’accès configurée", + "description": "Créez votre première clé d’accès." }, "passkeyLogin1": { - "title": "Passkey login", - "description": "Sign in at least once with a passkey." + "title": "Connexion par clé d’accès", + "description": "Connectez-vous au moins une fois avec une clé d’accès." }, "passwordLogin1": { - "title": "Old school", - "description": "Sign in at least once with username and password." + "title": "À l’ancienne", + "description": "Connectez-vous au moins une fois avec un identifiant et un mot de passe." }, "bugReport1": { - "title": "Bug hunter", - "description": "Submit a bug report." + "title": "Chasseur de bugs", + "description": "Soumettez un rapport de bug." }, "loginStreak7": { - "title": "Login streak 7d", - "description": "Sign in on 7 consecutive days." + "title": "Série de connexions 7j", + "description": "Connectez-vous 7 jours d’affilée." }, "loginStreak30": { - "title": "Login streak 30d", - "description": "Sign in on 30 consecutive days." + "title": "Série de connexions 30j", + "description": "Connectez-vous 30 jours d’affilée." }, "onTimePerfect1": { - "title": "On-time 1d", - "description": "Take all medications on time for 1 day." + "title": "À l’heure 1j", + "description": "Prenez tous vos médicaments à l’heure pendant 1 jour." }, "compliance801": { - "title": "80% adherence · 1d", - "description": "Reach at least 80% 30-day adherence for 1 day." + "title": "Observance 80% · 1j", + "description": "Atteignez au moins 80% d’observance sur 30 jours pendant 1 jour." }, "bmiGreen1": { - "title": "BMI green · 1d", - "description": "Keep your BMI in the normal range for 1 day." + "title": "IMC au vert · 1j", + "description": "Maintenez votre IMC dans la plage normale pendant 1 jour." }, "bpGreen1": { - "title": "BP green · 1d", - "description": "Keep your blood pressure in the normal range for 1 day." + "title": "Tension au vert · 1j", + "description": "Maintenez votre tension dans la plage normale pendant 1 jour." }, "pulseGreen1": { - "title": "Pulse green · 1d", - "description": "Keep your resting pulse in the normal range for 1 day." + "title": "Pouls au vert · 1j", + "description": "Maintenez votre pouls au repos dans la plage normale pendant 1 jour." }, "onTimePerfect7": { - "title": "On-time 7d", - "description": "Take all medications on time for 7 consecutive days." + "title": "À l’heure 7j", + "description": "Prenez tous vos médicaments à l’heure pendant 7 jours d’affilée." }, "compliance807": { - "title": "80% adherence · 7d", - "description": "Reach at least 80% 30-day adherence for 7 consecutive days." + "title": "Observance 80% · 7j", + "description": "Atteignez au moins 80% d’observance sur 30 jours pendant 7 jours d’affilée." }, "bmiGreen7": { - "title": "BMI green · 7d", - "description": "Keep your BMI in the normal range for 7 consecutive days." + "title": "IMC au vert · 7j", + "description": "Maintenez votre IMC dans la plage normale pendant 7 jours d’affilée." }, "bpGreen7": { - "title": "BP green · 7d", - "description": "Keep your blood pressure in the normal range for 7 consecutive days." + "title": "Tension au vert · 7j", + "description": "Maintenez votre tension dans la plage normale pendant 7 jours d’affilée." }, "pulseGreen7": { - "title": "Pulse green · 7d", - "description": "Keep your resting pulse in the normal range for 7 consecutive days." + "title": "Pouls au vert · 7j", + "description": "Maintenez votre pouls au repos dans la plage normale pendant 7 jours d’affilée." }, "onTimePerfect30": { - "title": "On-time 30d", - "description": "Take all medications on time for 30 consecutive days." + "title": "À l’heure 30j", + "description": "Prenez tous vos médicaments à l’heure pendant 30 jours d’affilée." }, "compliance8030": { - "title": "80% adherence · 30d", - "description": "Reach at least 80% 30-day adherence for 30 consecutive days." + "title": "Observance 80% · 30j", + "description": "Atteignez au moins 80% d’observance sur 30 jours pendant 30 jours d’affilée." }, "bmiGreen30": { - "title": "BMI green · 30d", - "description": "Keep your BMI in the normal range for 30 consecutive days." + "title": "IMC au vert · 30j", + "description": "Maintenez votre IMC dans la plage normale pendant 30 jours d’affilée." }, "bpGreen30": { - "title": "BP green · 30d", - "description": "Keep your blood pressure in the normal range for 30 consecutive days." + "title": "Tension au vert · 30j", + "description": "Maintenez votre tension dans la plage normale pendant 30 jours d’affilée." }, "pulseGreen30": { - "title": "Pulse green · 30d", - "description": "Keep your resting pulse in the normal range for 30 consecutive days." + "title": "Pouls au vert · 30j", + "description": "Maintenez votre pouls au repos dans la plage normale pendant 30 jours d’affilée." }, "onTimePerfect180": { - "title": "On-time 180d", - "description": "Take all medications on time for 180 consecutive days." + "title": "À l’heure 180j", + "description": "Prenez tous vos médicaments à l’heure pendant 180 jours d’affilée." }, "compliance80180": { - "title": "80% adherence · 180d", - "description": "Reach at least 80% 30-day adherence for 180 consecutive days." + "title": "Observance 80% · 180j", + "description": "Atteignez au moins 80% d’observance sur 30 jours pendant 180 jours d’affilée." }, "bmiGreen180": { - "title": "BMI green · 180d", - "description": "Keep your BMI in the normal range for 180 consecutive days." + "title": "IMC au vert · 180j", + "description": "Maintenez votre IMC dans la plage normale pendant 180 jours d’affilée." }, "bpGreen180": { - "title": "BP green · 180d", - "description": "Keep your blood pressure in the normal range for 180 consecutive days." + "title": "Tension au vert · 180j", + "description": "Maintenez votre tension dans la plage normale pendant 180 jours d’affilée." }, "pulseGreen180": { - "title": "Pulse green · 180d", - "description": "Keep your resting pulse in the normal range for 180 consecutive days." + "title": "Pouls au vert · 180j", + "description": "Maintenez votre pouls au repos dans la plage normale pendant 180 jours d’affilée." }, "onTimePerfect360": { - "title": "On-time 360d", - "description": "Take all medications on time for 360 consecutive days." + "title": "À l’heure 360j", + "description": "Prenez tous vos médicaments à l’heure pendant 360 jours d’affilée." }, "compliance80360": { - "title": "80% adherence · 360d", - "description": "Reach at least 80% 30-day adherence for 360 consecutive days." + "title": "Observance 80% · 360j", + "description": "Atteignez au moins 80% d’observance sur 30 jours pendant 360 jours d’affilée." }, "bmiGreen360": { - "title": "BMI green · 360d", - "description": "Keep your BMI in the normal range for 360 consecutive days." + "title": "IMC au vert · 360j", + "description": "Maintenez votre IMC dans la plage normale pendant 360 jours d’affilée." }, "bpGreen360": { - "title": "BP green · 360d", - "description": "Keep your blood pressure in the normal range for 360 consecutive days." + "title": "Tension au vert · 360j", + "description": "Maintenez votre tension dans la plage normale pendant 360 jours d’affilée." }, "pulseGreen360": { - "title": "Pulse green · 360d", - "description": "Keep your resting pulse in the normal range for 360 consecutive days." + "title": "Pouls au vert · 360j", + "description": "Maintenez votre pouls au repos dans la plage normale pendant 360 jours d’affilée." }, "moodFirst": { - "title": "First mood", - "description": "Log your first mood entry." + "title": "Première humeur", + "description": "Enregistrez votre première entrée d’humeur." }, "moodStreak7": { - "title": "Mood diarist 7d", - "description": "Log a mood entry on 7 consecutive days." + "title": "Journal d’humeur 7j", + "description": "Enregistrez une humeur 7 jours d’affilée." }, "moodStreak30": { - "title": "Mood diarist 30d", - "description": "Log a mood entry on 30 consecutive days." + "title": "Journal d’humeur 30j", + "description": "Enregistrez une humeur 30 jours d’affilée." }, "moodUp7": { - "title": "Brighter week", - "description": "Your 7-day mood average improved by at least 1.0 point compared to the previous week." + "title": "Semaine plus lumineuse", + "description": "Votre moyenne d’humeur sur 7 jours s’est améliorée d’au moins 1,0 point par rapport à la semaine précédente." }, "weightFirst": { - "title": "First weigh-in", - "description": "Record your first weight measurement." + "title": "Première pesée", + "description": "Enregistrez votre première mesure de poids." }, "weight50": { - "title": "Fifty weigh-ins", - "description": "Record 50 weight measurements." + "title": "Cinquante pesées", + "description": "Enregistrez 50 mesures de poids." }, "weight200": { - "title": "200 weigh-ins", - "description": "Record 200 weight measurements." + "title": "200 pesées", + "description": "Enregistrez 200 mesures de poids." }, "bpFirst": { - "title": "First reading", - "description": "Record your first blood-pressure measurement." + "title": "Première mesure", + "description": "Enregistrez votre première mesure de tension." }, "bp50": { - "title": "Fifty BP readings", - "description": "Record 50 blood-pressure measurements." + "title": "Cinquante mesures de tension", + "description": "Enregistrez 50 mesures de tension." }, "bp200": { - "title": "200 BP readings", - "description": "Record 200 blood-pressure measurements." + "title": "200 mesures de tension", + "description": "Enregistrez 200 mesures de tension." }, "pulseFirst": { - "title": "First pulse", - "description": "Record your first pulse measurement." + "title": "Premier pouls", + "description": "Enregistrez votre première mesure de pouls." }, "consistentMonth": { - "title": "Consistent month", - "description": "Log entries on at least 25 distinct days within a single calendar month." + "title": "Mois régulier", + "description": "Enregistrez des entrées sur au moins 25 jours différents au cours d’un même mois calendaire." }, "entryStreak7": { - "title": "Tracker streak 7d", - "description": "Log at least one entry on 7 consecutive days." + "title": "Série de suivis 7j", + "description": "Enregistrez au moins une entrée 7 jours d’affilée." }, "entryStreak30": { - "title": "Tracker streak 30d", - "description": "Log at least one entry on 30 consecutive days." + "title": "Série de suivis 30j", + "description": "Enregistrez au moins une entrée 30 jours d’affilée." }, "weekendWarrior": { - "title": "Weekend tracker", - "description": "Log entries on 4 consecutive Saturday + Sunday weekend pairs." + "title": "Suivi du week-end", + "description": "Enregistrez des entrées sur 4 week-ends consécutifs (samedi + dimanche)." }, "hiddenNightOwl": { - "title": "Night owl", - "description": "Logged an entry between 02:00 and 04:00 in the morning." + "title": "Oiseau de nuit", + "description": "Vous avez enregistré une entrée entre 02h00 et 04h00 du matin." }, "hiddenEarlyBird": { - "title": "Early bird", - "description": "Logged an entry between 04:00 and 06:00 in the morning." + "title": "Lève-tôt", + "description": "Vous avez enregistré une entrée entre 04h00 et 06h00 du matin." }, "hiddenLeapDay": { - "title": "Leap-day legend", - "description": "Logged an entry on February 29." + "title": "Légende du jour bissextile", + "description": "Vous avez enregistré une entrée le 29 février." }, "hiddenDoctorPdf": { - "title": "House call", - "description": "Exported your first doctor-report PDF." + "title": "Visite à domicile", + "description": "Vous avez exporté votre premier rapport médical en PDF." }, "hiddenLocaleFlip": { - "title": "Polyglot", - "description": "Switched the app language at least once." + "title": "Polyglotte", + "description": "Vous avez changé la langue de l’application au moins une fois." }, "hiddenBugBuddy": { - "title": "Bug buddy", - "description": "Submitted at least 5 bug reports — the project loves you." + "title": "Compagnon des bugs", + "description": "Vous avez soumis au moins 5 rapports de bug — le projet vous adore." } }, - "nextProgressLabel": "Progress to unlock", - "completedOn": "Completed on {date}", - "locked": "Locked", + "nextProgressLabel": "Progression avant déblocage", + "completedOn": "Terminé le {date}", + "locked": "Verrouillé", "criterionHint": "{current} / {target}", "progressPercent": "{percent}%", "categories": { "medication": "Médicament", - "vitals": "Vitals", + "vitals": "Constantes vitales", "mood": "Humeur", - "security": "Account & security", + "security": "Compte et sécurité", "engagement": "Engagement", - "hidden": "Hidden" + "hidden": "Caché" }, "hiddenCard": { - "title": "Hidden achievement", - "description": "Keep tracking — you might just stumble across it.", - "ariaLabel": "Hidden locked achievement" + "title": "Réussite cachée", + "description": "Continuez à suivre — vous pourriez bien tomber dessus.", + "ariaLabel": "Réussite cachée verrouillée" }, "hiddenUnlockToast": { - "title": "You unlocked a hidden achievement!" + "title": "Vous avez débloqué une réussite cachée !" }, "dashboardCard": { - "title": "Recent unlocks", - "viewAll": "View all", - "empty": "No achievements yet — keep logging to unlock your first." + "title": "Débloqués récemment", + "viewAll": "Tout voir", + "empty": "Pas encore de réussites — continuez à suivre pour débloquer la première." } }, "bugreport": { diff --git a/messages/it.json b/messages/it.json index e25ee1368..55e954b18 100644 --- a/messages/it.json +++ b/messages/it.json @@ -278,6 +278,12 @@ "emptyDescription": "Crea prima un farmaco con un orario, poi torna qui per registrarne l’assunzione.", "emptyCta": "Aggiungi farmaco" }, + "quickEntryDiscard": { + "title": "Scartare questa voce?", + "description": "Hai dati non salvati. Chiudere il modulo e perdere ciò che hai digitato?", + "confirm": "Scarta", + "cancel": "Continua a modificare" + }, "compliance7d": "Aderenza (7 giorni)", "mood": "Umore", "customizeTitle": "Layout della dashboard", @@ -554,35 +560,35 @@ "scheduleDaily": "Quotidiano", "inactive": "In pausa", "intakeHistory": "Cronologia assunzioni", - "openDetailPage": "Open medication detail page", + "openDetailPage": "Apri la pagina di dettaglio del farmaco", "deleteConfirm": "Eliminare il farmaco?", "compliance": "Aderenza", - "importIntakes": "Import intakes", - "importDescription": "Paste a JSON array with intake data. Expected format:", - "importUploadFile": "Upload JSON file", - "importPaste": "Paste JSON here...", - "importSelected": "Selected: {name}", - "importFileLoaded": "File \"{name}\" loaded", - "importInvalidJson": "File does not contain valid JSON", - "importNoArray": "No array found", - "importResult": "{imported} intakes imported", - "importDuplicatesSkipped": "{count} duplicates skipped", - "importInvalidSkipped": "{count} invalid entries skipped", - "importFailed": "Import failed", - "importInvalidFormat": "Invalid JSON format", - "apiEndpointTitle": "API Endpoint: {name}", - "apiEndpointDescription": "Use this endpoint to record intakes via API (e.g., iPhone Shortcut or cron job).", - "apiEndpointActive": "API endpoint active", - "apiEndpointActivated": "API endpoint activated", - "apiEndpointDeactivated": "API endpoint deactivated", - "apiTokenCount": "Active ({count} tokens)", - "apiToken": "API Token", - "apiTokenActiveHint": "Endpoint is active. Existing tokens remain valid but cannot be shown in plain text again.", - "apiTokenActivateHint": "Activate the endpoint to create a new token.", - "apiTokenOnceHint": "The token is only shown in plain text once at creation.", - "requestExample": "Request example", - "statusLoadFailed": "Could not load status", - "changeFailed": "Change failed", + "importIntakes": "Importa assunzioni", + "importDescription": "Incolla un array JSON con i dati delle assunzioni. Formato previsto:", + "importUploadFile": "Carica file JSON", + "importPaste": "Incolla il JSON qui...", + "importSelected": "Selezionato: {name}", + "importFileLoaded": "File \"{name}\" caricato", + "importInvalidJson": "Il file non contiene un JSON valido", + "importNoArray": "Nessun array trovato", + "importResult": "{imported} assunzioni importate", + "importDuplicatesSkipped": "{count} duplicati ignorati", + "importInvalidSkipped": "{count} voci non valide ignorate", + "importFailed": "Importazione non riuscita", + "importInvalidFormat": "Formato JSON non valido", + "apiEndpointTitle": "Endpoint API: {name}", + "apiEndpointDescription": "Usa questo endpoint per registrare assunzioni tramite API (ad es. con un Comando rapido iPhone o un cron job).", + "apiEndpointActive": "Endpoint API attivo", + "apiEndpointActivated": "Endpoint API attivato", + "apiEndpointDeactivated": "Endpoint API disattivato", + "apiTokenCount": "Attivo ({count} token)", + "apiToken": "Token API", + "apiTokenActiveHint": "L’endpoint è attivo. I token esistenti restano validi ma non possono essere mostrati di nuovo in chiaro.", + "apiTokenActivateHint": "Attiva l’endpoint per creare un nuovo token.", + "apiTokenOnceHint": "Il token viene mostrato in chiaro solo una volta, alla creazione.", + "requestExample": "Esempio di richiesta", + "statusLoadFailed": "Impossibile caricare lo stato", + "changeFailed": "Modifica non riuscita", "categoryBloodPressure": "Pressione arteriosa", "categoryVitamin": "Vitamine", "categoryThyroid": "Tiroide", @@ -594,7 +600,7 @@ "categorySkin": "Cura della pelle", "categorySleepAid": "Per dormire", "categoryDiabetes": "Diabetes", - "categoryAntibiotic": "Antibiotic", + "categoryAntibiotic": "Antibiotico", "categoryOther": "Altro", "daysSun": "Dom", "daysMon": "Lun", @@ -622,6 +628,10 @@ "skipped": "Saltata", "intakeToastTaken": "{name} registrata come assunta", "intakeToastSkipped": "{name} saltata", + "intakeToastFailed": "Impossibile registrare {name}; riprova", + "intakeUndo": "Annulla", + "intakeUndone": "Voce annullata", + "intakeUndoFailed": "Impossibile annullare; correggi nella cronologia", "weekdaySunday": "Domenica", "weekdayMonday": "Lunedì", "weekdayTuesday": "Martedì", @@ -738,141 +748,141 @@ } }, "cadence": { - "section": "Cadence", - "label": "How often", + "section": "Cadenza", + "label": "Con quale frequenza", "kind": { - "daily": "Every day", - "weekdays": "Certain days of the week", - "everyNWeeks": "Every N weeks on certain days", - "monthly": "Monthly on a specific day", - "everyNMonths": "Every N months on a specific day", - "yearly": "Once a year", - "rolling": "Every N days from when I last took it (flexible)", - "rollingExplainer": "Counts from your last logged intake — pauses if you skip a dose.", - "oneShot": "One-time dose" + "daily": "Ogni giorno", + "weekdays": "In determinati giorni della settimana", + "everyNWeeks": "Ogni N settimane in determinati giorni", + "monthly": "Mensilmente in un giorno specifico", + "everyNMonths": "Ogni N mesi in un giorno specifico", + "yearly": "Una volta all’anno", + "rolling": "Ogni N giorni dall’ultima assunzione (flessibile)", + "rollingExplainer": "Conta dall’ultima assunzione registrata — si mette in pausa se salti una dose.", + "oneShot": "Dose singola" }, "weekdays": { - "label": "Days of the week", + "label": "Giorni della settimana", "short": { "mo": "Mo", - "tu": "Tu", - "we": "We", - "th": "Th", + "tu": "Ma", + "we": "Me", + "th": "Gi", "fr": "Fr", "sa": "Sa", - "su": "Su" + "su": "Do" }, "long": { - "mo": "Monday", - "tu": "Tuesday", - "we": "Wednesday", - "th": "Thursday", - "fr": "Friday", - "sa": "Saturday", - "su": "Sunday" + "mo": "Lunedì", + "tu": "Martedì", + "we": "Mercoledì", + "th": "Giovedì", + "fr": "Venerdì", + "sa": "Sabato", + "su": "Domenica" } }, "intervalWeeks": { - "suffix": "weeks" + "suffix": "settimane" }, "dayOfMonth": { - "label": "Day" + "label": "Giorno" }, "intervalMonths": { - "suffix": "months", - "dayOnLabel": "on day" + "suffix": "mesi", + "dayOnLabel": "il giorno" }, "yearly": { "date": { - "label": "Date" + "label": "Data" } }, "rollingDays": { - "suffix": "days" + "suffix": "giorni" } }, "timesOfDay": { - "section": "Times of day", - "label": "Times of day", + "section": "Orari del giorno", + "label": "Orari del giorno", "empty": { - "cta": "Add the first time." + "cta": "Aggiungi il primo orario." }, - "add": "Add time", - "remove": "Remove", + "add": "Aggiungi orario", + "remove": "Rimuovi", "presets": { - "morning": "Morning", - "noon": "Noon", - "evening": "Evening", - "night": "Night" + "morning": "Mattina", + "noon": "Mezzogiorno", + "evening": "Sera", + "night": "Notte" }, "max": { - "reached": "Maximum reached — remove a time before adding another." + "reached": "Massimo raggiunto — rimuovi un orario prima di aggiungerne un altro." } }, "courseWindow": { - "section": "Course window", + "section": "Periodo di trattamento", "startsOn": { - "label": "Starts on" + "label": "Inizia il" }, "endsOn": { - "label": "Ends on" + "label": "Termina il" }, - "noEndDate": "No end date", - "oneShotCaption": "(one-time dose)", - "invalidRange": "End date must be on or after the start date." + "noEndDate": "Nessuna data di fine", + "oneShotCaption": "(dose singola)", + "invalidRange": "La data di fine deve essere uguale o successiva alla data di inizio." } }, "wizard": { "header": { - "stepOf": "Step {current} of {total}", - "createTitle": "New medication", - "editTitle": "Edit {name}" + "stepOf": "Passo {current} di {total}", + "createTitle": "Nuovo farmaco", + "editTitle": "Modifica {name}" }, "nav": { - "back": "Back", - "next": "Next", - "save": "Save", - "saveEdit": "Save changes", + "back": "Indietro", + "next": "Avanti", + "save": "Salva", + "saveEdit": "Salva modifiche", "nextHint": "Avanti: {step}", "reviewHint": "Avanti: Salva", "jumpFirst": "Vai al primo passaggio", "jumpLast": "Vai a revisione e salvataggio" }, "nl": { - "button": "Describe" + "button": "Descrivi" }, "steps": { "step1": { - "title": "What's the medication called?", - "subline": "Exactly as it appears on the box.", + "title": "Come si chiama il farmaco?", + "subline": "Esattamente come riportato sulla confezione.", "label": "Name", - "placeholder": "e.g. Ramipril", + "placeholder": "ad es. Ramipril", "short": "Nome" }, "step2": { - "title": "What kind of medication is it?", - "subline": "We use this for the right templates and analytics.", + "title": "Che tipo di farmaco è?", + "subline": "Lo usiamo per i modelli e le analisi corretti.", "short": "Tipo" }, "step3": { - "title": "What's the dose?", - "subline": "A tablet, a puff, a drop — depending on the form.", - "amountLabel": "Amount", - "amountPlaceholder": "e.g. 5", - "unitLabel": "Unit", + "title": "Qual è la dose?", + "subline": "Una compressa, uno spruzzo, una goccia — a seconda della forma.", + "amountLabel": "Quantità", + "amountPlaceholder": "ad es. 5", + "unitLabel": "Unità", "unit": { "mg": "mg", "ml": "ml", - "iu": "IU", + "iu": "UI", "mcg": "µg", "g": "g", - "tablets": "tablet(s)", - "capsules": "capsule(s)", - "drops": "drops", - "puffs": "puff", - "sprays": "spray", - "pieces": "pieces", - "other": "other" + "tablets": "compressa/e", + "capsules": "capsula/e", + "drops": "gocce", + "puffs": "spruzzo", + "sprays": "spruzzo", + "pieces": "pezzi", + "other": "altro" }, "deliveryFormLabel": "Via", "deliveryForm": { @@ -888,124 +898,124 @@ "short": "Dose" }, "step4": { - "title": "Over what period are you taking it?", - "subline": "A start date is enough for today. You can add an end date later.", + "title": "Per quanto tempo lo assumi?", + "subline": "Per oggi basta una data di inizio. Potrai aggiungere una data di fine in seguito.", "short": "Quando" }, "step5": { - "title": "How often do you take it?", - "subline": "Pick the cadence — the details follow on the next step.", + "title": "Con quale frequenza lo assumi?", + "subline": "Scegli la cadenza — i dettagli arrivano al passo successivo.", "short": "Frequenza" }, "step6": { - "title": "What does that look like in detail?", - "subline": "Set weekdays, intervals, or the day of the month.", + "title": "Come si presenta nel dettaglio?", + "subline": "Imposta i giorni della settimana, gli intervalli o il giorno del mese.", "intervalWeeks": { - "label": "Every", - "suffix": "weeks" + "label": "Ogni", + "suffix": "settimane" }, "dayOfMonth": { - "label": "On", - "suffix": "day of the month" + "label": "Il", + "suffix": "giorno del mese" }, "rollingDays": { - "label": "Every", - "suffix": "days from the last intake" + "label": "Ogni", + "suffix": "giorni dall’ultima assunzione" }, "short": "Dettaglio" }, "step7": { - "title": "When do you take it?", - "subline": "One or more times of day — reminders follow this list.", - "presetsLabel": "Time-of-day presets", + "title": "Quando lo assumi?", + "subline": "Uno o più orari del giorno — i promemoria seguono questo elenco.", + "presetsLabel": "Preimpostazioni per orario del giorno", "presets": { - "morning": "Morning", - "noon": "Noon", - "evening": "Evening", - "night": "Night" + "morning": "Mattina", + "noon": "Mezzogiorno", + "evening": "Sera", + "night": "Notte" }, "short": "Orari" }, "step8": { - "title": "All done — should HealthLog remind you?", - "subline": "Review everything below before you save.", - "remindersLabel": "Reminders on", - "remindersDescription": "Push, Telegram or ntfy — based on your settings.", - "multiScheduleNote": "This medication has multiple schedules. The detailed editor returns in a later release.", + "title": "Fatto — vuoi che HealthLog te lo ricordi?", + "subline": "Controlla tutto qui sotto prima di salvare.", + "remindersLabel": "Promemoria attivi", + "remindersDescription": "Push, Telegram o ntfy — in base alle tue impostazioni.", + "multiScheduleNote": "Questo farmaco ha più orari. L’editor dettagliato tornerà in una versione successiva.", "short": "Fatto" } }, "classRow": { - "bloodPressure": "Blood pressure", + "bloodPressure": "Pressione arteriosa", "diabetes": "Diabetes", - "hormone": "Hormones", - "glp1": "GLP-1 injection", - "painRelief": "Pain", - "allergy": "Allergy", - "vitamin": "Vitamins", - "supplement": "Supplement", - "antibiotic": "Antibiotic", - "other": "Other" + "hormone": "Ormoni", + "glp1": "Iniezione di GLP-1", + "painRelief": "Dolore", + "allergy": "Allergia", + "vitamin": "Vitamine", + "supplement": "Integratore", + "antibiotic": "Antibiotico", + "other": "Altro" }, "cadence": { "daily": { - "label": "Daily", - "description": "Every day at the same time." + "label": "Giornaliero", + "description": "Ogni giorno alla stessa ora." }, "weekdays": { - "label": "Specific weekdays", - "description": "E.g. Monday, Wednesday, Friday." + "label": "Giorni specifici della settimana", + "description": "Ad es. lunedì, mercoledì, venerdì." }, "everyNWeeks": { - "label": "Every few weeks", - "description": "E.g. every 2 weeks on Thursdays (GLP-1, biologics)." + "label": "Ogni poche settimane", + "description": "Ad es. ogni 2 settimane di giovedì (GLP-1, biologici)." }, "monthly": { - "label": "Monthly", - "description": "On a fixed day each month (e.g. always the 1st)." + "label": "Mensile", + "description": "Un giorno fisso ogni mese (ad es. sempre il 1°)." }, "rolling": { - "label": "Flexible from last dose", - "description": "Leave the next dose open. When you tap 'taken', the counter starts again from that moment." + "label": "Flessibile dall’ultima dose", + "description": "Lascia aperta la prossima dose. Quando tocchi «assunta», il conteggio riparte da quel momento." }, "oneShot": { - "label": "Single dose", - "description": "One administration, e.g. a vaccine or an antibiotic course." + "label": "Dose singola", + "description": "Una singola somministrazione, ad es. un vaccino o un ciclo di antibiotico." } }, "summary": { - "title": "Summary", + "title": "Riepilogo", "cadence": { - "daily": "Every day", - "weekdays": "On selected days of the week", - "biweekly": "Every two weeks", - "monthly": "Monthly", - "quarterly": "Every three months", - "yearly": "Once a year", - "rolling": "Every {n} days from your last intake", - "oneShot": "A single dose", - "everyNWeeks": "Every {n} weeks", - "everyNMonths": "Every {n} months" + "daily": "Ogni giorno", + "weekdays": "Nei giorni della settimana selezionati", + "biweekly": "Ogni due settimane", + "monthly": "Mensile", + "quarterly": "Ogni tre mesi", + "yearly": "Una volta all’anno", + "rolling": "Ogni {n} giorni dall’ultima assunzione", + "oneShot": "Una dose singola", + "everyNWeeks": "Ogni {n} settimane", + "everyNMonths": "Ogni {n} mesi" }, - "weekdaysDetail": ", on {days}", - "dayOfMonthDetail": ", on day {day}.", - "times": "at {times}", - "startsOn": "Starts: {date}", - "endsOn": "Ends: {date}", - "noEndDate": "No end date" + "weekdaysDetail": ", il {days}", + "dayOfMonthDetail": ", il giorno {day}.", + "times": "alle {times}", + "startsOn": "Inizio: {date}", + "endsOn": "Fine: {date}", + "noEndDate": "Nessuna data di fine" }, "compose": { - "scheduleIndex": "Schedule {n} of {total}", + "scheduleIndex": "Programma {n} di {total}", "list": { - "add": "Add another schedule", - "edit": "Edit", - "remove": "Remove", - "removeDisabled": "At least one schedule is required.", - "empty": "No schedule yet." + "add": "Aggiungi un altro programma", + "edit": "Modifica", + "remove": "Rimuovi", + "removeDisabled": "È richiesto almeno un programma.", + "empty": "Ancora nessun programma." } }, "errors": { - "submitFailed": "Could not save the medication — please retry." + "submitFailed": "Impossibile salvare il farmaco — riprova." } }, "doseStrength": { @@ -1160,88 +1170,88 @@ }, "detail": { "status": { - "active": "Active", - "paused": "Paused", - "ended": "Ended" + "active": "Attivo", + "paused": "In pausa", + "ended": "Terminato" }, "today": { - "groupLabel": "Today's dose", - "taken": "Taken", - "skipped": "Skipped", - "toastTaken": "Today's dose logged", - "toastSkipped": "Today's dose skipped", - "recordedTaken": "Taken today at {time}", - "recordedSkipped": "Skipped today at {time}", - "recordedOneShot": "One-shot dose taken at {time}", - "noneScheduled": "No dose scheduled for today.", - "pausedHint": "Paused — no reminder today.", - "error": "Could not log the dose." + "groupLabel": "Dose di oggi", + "taken": "Assunta", + "skipped": "Saltata", + "toastTaken": "Dose di oggi registrata", + "toastSkipped": "Dose di oggi saltata", + "recordedTaken": "Assunta oggi alle {time}", + "recordedSkipped": "Saltata oggi alle {time}", + "recordedOneShot": "Dose singola assunta alle {time}", + "noneScheduled": "Nessuna dose prevista per oggi.", + "pausedHint": "In pausa — nessun promemoria oggi.", + "error": "Impossibile registrare la dose." }, "cadence": { - "oneShotOn": "One-time dose on {date}.", - "oneShotPending": "One-time dose pending." + "oneShotOn": "Dose singola il {date}.", + "oneShotPending": "Dose singola in sospeso." }, "intake": { - "title": "Intake history", - "importButton": "Import", + "title": "Storico delle assunzioni", + "importButton": "Importa", "rowActions": { - "openMenu": "Open row actions", - "edit": "Edit", - "delete": "Delete" + "openMenu": "Apri azioni della riga", + "edit": "Modifica", + "delete": "Elimina" }, "selection": { - "rowToggleLabel": "Select row" + "rowToggleLabel": "Seleziona riga" }, "bulkDelete": { - "selectionCount": "{count} entries selected", - "deleteButton": "Delete selection", - "cancelButton": "Cancel", - "confirmTitle": "Delete the selected entries?", - "confirmBody": "{count} entries will be removed permanently. The medication itself stays.", - "confirmAction": "Delete", - "toast": "Selection deleted", - "failed": "Could not delete the selection." + "selectionCount": "{count} voci selezionate", + "deleteButton": "Elimina selezione", + "cancelButton": "Annulla", + "confirmTitle": "Eliminare le voci selezionate?", + "confirmBody": "{count} voci verranno rimosse definitivamente. Il farmaco resta.", + "confirmAction": "Elimina", + "toast": "Selezione eliminata", + "failed": "Impossibile eliminare la selezione." }, "edit": { - "dialogTitle": "Edit intake", - "takenAtLabel": "Taken at", - "skippedLabel": "Skipped", - "noteLabel": "Note", - "save": "Save", - "cancel": "Cancel", - "savedToast": "Intake updated", - "failed": "Could not update the intake." + "dialogTitle": "Modifica assunzione", + "takenAtLabel": "Assunta alle", + "skippedLabel": "Saltata", + "noteLabel": "Nota", + "save": "Salva", + "cancel": "Annulla", + "savedToast": "Assunzione aggiornata", + "failed": "Impossibile aggiornare l’assunzione." }, "deleteRow": { - "confirmTitle": "Delete this intake?", - "confirmBody": "The entry is removed permanently.", - "confirmAction": "Delete", - "toast": "Intake deleted", - "failed": "Could not delete the intake." + "confirmTitle": "Eliminare questa assunzione?", + "confirmBody": "La voce viene rimossa definitivamente.", + "confirmAction": "Elimina", + "toast": "Assunzione eliminata", + "failed": "Impossibile eliminare l’assunzione." } }, "notifications": { - "title": "Notifications", - "switchLabel": "Send a reminder when this dose is due", - "helperOn": "Reminders are on.", - "helperOff": "Reminders are off.", - "enabledToast": "Reminders on", - "disabledToast": "Reminders off", - "toggleFailed": "Could not change the reminder setting.", - "clientManagedChip": "Your iPhone manages reminders for this medication." + "title": "Promemoria", + "switchLabel": "Invia un promemoria quando questa dose è dovuta", + "helperOn": "I promemoria sono attivi.", + "helperOff": "I promemoria sono disattivati.", + "enabledToast": "Promemoria attivi", + "disabledToast": "Promemoria disattivati", + "toggleFailed": "Impossibile modificare l’impostazione dei promemoria.", + "clientManagedChip": "Il tuo iPhone gestisce i promemoria di questo farmaco." }, "settings": { - "title": "Settings", + "title": "Impostazioni", "phases": { - "openButton": "Configure phases", - "requiresCourseWindow": "Phases are available once a course window is set." + "openButton": "Configura le fasi", + "requiresCourseWindow": "Le fasi sono disponibili dopo aver impostato un periodo di trattamento." }, "grace": { - "label": "Reminder window — applies to your primary schedule", - "primaryScheduleNote": "Multi-schedule medications keep their other schedules' default window.", - "unit": "minutes", - "saved": "Reminder window saved", - "failed": "Could not save the reminder window." + "label": "Finestra di promemoria — si applica al tuo programma principale", + "primaryScheduleNote": "I farmaci con più programmi mantengono la finestra predefinita degli altri programmi.", + "unit": "minuti", + "saved": "Finestra di promemoria salvata", + "failed": "Impossibile salvare la finestra di promemoria." }, "codes": { "label": "Codici clinici (ATC / RxNorm)", @@ -1253,57 +1263,57 @@ } }, "zone": { - "title": "Manage & danger zone", + "title": "Gestione e zona pericolosa", "pause": { - "title": "Pause", - "helper": "Pause reminders until you turn them back on. History stays intact.", - "pausedToast": "Reminders paused", - "resumedToast": "Reminders resumed", - "failed": "Could not change the paused state." + "title": "Metti in pausa", + "helper": "Metti in pausa i promemoria finché non li riattivi. Lo storico resta intatto.", + "pausedToast": "Promemoria in pausa", + "resumedToast": "Promemoria ripresi", + "failed": "Impossibile modificare lo stato di pausa." }, "end": { - "title": "End medication", - "helper": "Stop reminders and mark the medication as ended. History stays visible.", - "button": "End", - "dialogTitle": "End {name} — stop reminders, history stays visible", - "dialogBody": "No more reminders. Old entries remain in the timeline.", - "toast": "Medication ended", - "failed": "Could not end the medication." + "title": "Termina il farmaco", + "helper": "Interrompe i promemoria e segna il farmaco come terminato. Lo storico resta visibile.", + "button": "Termina", + "dialogTitle": "Termina {name} — interrompe i promemoria, lo storico resta visibile", + "dialogBody": "Nessun altro promemoria. Le voci precedenti restano nella cronologia.", + "toast": "Farmaco terminato", + "failed": "Impossibile terminare il farmaco." }, "purge": { - "title": "Delete history", - "helper": "{count} intake events are stored for this medication.", - "button": "Delete history", - "dialogTitle": "Really delete the history?", - "dialogBody": "The {count} intake events will be removed permanently. The medication itself stays.", - "toast": "History deleted", - "failed": "Could not delete the history." + "title": "Elimina lo storico", + "helper": "Per questo farmaco sono memorizzate {count} assunzioni.", + "button": "Elimina lo storico", + "dialogTitle": "Eliminare davvero lo storico?", + "dialogBody": "Le {count} assunzioni verranno rimosse definitivamente. Il farmaco resta.", + "toast": "Storico eliminato", + "failed": "Impossibile eliminare lo storico." }, "delete": { - "title": "Delete medication", - "helper": "Removes the medication and every related record.", - "button": "Delete", - "dialogTitle": "Delete {name}?", - "dialogBody": "The medication, its schedules, intake history, API tokens, phase config and reminders will be removed permanently.", - "toast": "Medication deleted", - "failed": "Could not delete the medication." + "title": "Elimina il farmaco", + "helper": "Rimuove il farmaco e tutti i record correlati.", + "button": "Elimina", + "dialogTitle": "Eliminare {name}?", + "dialogBody": "Il farmaco, i suoi programmi, lo storico delle assunzioni, i token API, la configurazione delle fasi e i promemoria verranno rimossi definitivamente.", + "toast": "Farmaco eliminato", + "failed": "Impossibile eliminare il farmaco." } }, "phases": { - "modeLabel": "{phase} mode", - "saveFailed": "Could not save the reminder phases." + "modeLabel": "Modalità {phase}", + "saveFailed": "Impossibile salvare le fasi dei promemoria." }, "api": { - "caption": "Endpoint for \"{name}\"", - "copyUrl": "Copy URL", - "urlCopied": "URL copied", - "mintToken": "Mint token", - "mintAnotherToken": "Mint another token", - "mintFailed": "Could not mint a token.", - "copyFailed": "Could not copy.", - "tokenCopied": "Token copied", - "copyToken": "Copy token", - "mintedHint": "Copy this token now — it is shown only once." + "caption": "Endpoint per \"{name}\"", + "copyUrl": "Copia URL", + "urlCopied": "URL copiato", + "mintToken": "Genera token", + "mintAnotherToken": "Genera un altro token", + "mintFailed": "Impossibile generare un token.", + "copyFailed": "Impossibile copiare.", + "tokenCopied": "Token copiato", + "copyToken": "Copia token", + "mintedHint": "Copia questo token ora — viene mostrato una sola volta." }, "edit": { "planOption": "Modifica piano", @@ -1518,6 +1528,7 @@ "feedbackThanks": "Grazie per il riscontro", "feedbackConfirmed": "Feedback salvato", "feedbackAlreadyRated": "Già valutato", + "feedbackError": "Impossibile salvare il tuo riscontro. Riprova.", "confidence": "Confidenza", "confidenceAria": "Confidenza: {value} su 100", "confidenceHigh": "Confidenza alta", @@ -1539,6 +1550,7 @@ "regenerateAnalysis": "Riavvia l’analisi", "regenerateSuccess": "Analisi rigenerata", "warmAssessments": "Prepara le valutazioni", + "warmAssessmentsHint": "Le valutazioni si aggiornano automaticamente durante la notte. Preparale ora per vedere i dati più recenti.", "warmStarted": "Le valutazioni vengono preparate in background", "narrativeTitle": "Il tuo periodo in sintesi", "narrativeWeek": "Questa settimana", @@ -1696,6 +1708,7 @@ "tagline": "Coach personale di salute, basato sui tuoi dati.", "newChat": "Nuova chat", "send": "Invia", + "stop": "Interrompi", "thinking": "Sto pensando…", "composerPlaceholder": "Chiedimi qualcosa sui tuoi dati…", "composerHint": "Invio per inviare, Shift+Invio per una nuova riga.", @@ -2784,19 +2797,19 @@ "loadError": "Impossibile caricare le notifiche del dispositivo.", "disclaimer": "Questi sono avvisi generati dal tuo dispositivo con il proprio rilevamento integrato. Sono mostrati qui solo a scopo informativo. Non è una valutazione medica da parte di HealthLog, e HealthLog non diagnostica alcuna condizione. Se una notifica ti preoccupa, parla con il tuo medico.", "event": { - "irregularRhythm": "Irregular rhythm notification", - "highHeartRate": "High heart rate notification", - "lowHeartRate": "Low heart rate notification", - "walkingSteadiness": "Walking steadiness alert", - "breathingDisturbance": "Breathing disturbance during sleep" + "irregularRhythm": "Notifica di ritmo irregolare", + "highHeartRate": "Notifica di frequenza cardiaca alta", + "lowHeartRate": "Notifica di frequenza cardiaca bassa", + "walkingSteadiness": "Avviso di stabilità nella camminata", + "breathingDisturbance": "Disturbo respiratorio durante il sonno" }, "verdict": { - "irregular": "Your device flagged a possible irregular rhythm.", - "notDetected": "Your device did not flag an irregular rhythm.", - "inconclusive": "Your device could not make a reading.", - "low": "Your device flagged low walking steadiness.", - "veryLow": "Your device flagged very low walking steadiness.", - "fired": "Your device flagged this event." + "irregular": "Il tuo dispositivo ha rilevato un possibile ritmo irregolare.", + "notDetected": "Il tuo dispositivo non ha rilevato un ritmo irregolare.", + "inconclusive": "Il tuo dispositivo non è riuscito a effettuare una misurazione.", + "low": "Il tuo dispositivo ha rilevato una bassa stabilità nella camminata.", + "veryLow": "Il tuo dispositivo ha rilevato una stabilità nella camminata molto bassa.", + "fired": "Il tuo dispositivo ha segnalato questo evento." } } }, @@ -2998,31 +3011,31 @@ "disableError": "Impossibile disattivare la modalità Ricerca. Riprova." }, "shell": { - "sectionsNav": "Settings sections" + "sectionsNav": "Sezioni delle impostazioni" }, "sections": { "account": { "title": "Account", - "description": "Profile, password, passkeys, and your onboarding tour." + "description": "Profilo, password, passkey e il tour di benvenuto." }, "integrations": { "title": "Integrazioni", - "description": "Withings, moodLog, and other connected services." + "description": "Withings, moodLog e altri servizi collegati." }, "notifications": { "title": "Notifiche", - "description": "Live overview of every configured channel." + "description": "Panoramica in tempo reale di ogni canale configurato." }, "dashboard": { "title": "Cruscotto", - "description": "Tile layout and order." + "description": "Layout e ordine dei riquadri." }, "thresholds": { "title": "Obiettivi", "description": "Per metrica: il tuo intervallo target per valori sani." }, "sources": { - "title": "Fonti", + "title": "Sorgenti", "description": "Scegli quale fonte prevale quando più di una registra la stessa metrica lo stesso giorno.", "cardTitle": "Priorità delle fonti per metrica", "help": "Quando più fonti forniscono lo stesso valore nello stesso giorno (ad es. Withings e Apple Health riportano entrambe 1.200 passi), la fonte in cima conta come lettura canonica. Le altre fonti restano memorizzate per la traccia di controllo.", @@ -3078,12 +3091,12 @@ } }, "ai": { - "title": "AI Insights", - "description": "Provider, model, key." + "title": "Analisi IA", + "description": "Provider, modello, chiave." }, "api": { "title": "API & Tokens", - "description": "Bearer tokens for your own scripts and apps — log measurements or medication intake from anywhere." + "description": "Token Bearer per i tuoi script e le tue app — registra misurazioni o assunzioni di farmaci da ovunque." }, "advanced": { "title": "Avanzate", @@ -3091,8 +3104,9 @@ }, "export": { "title": "Esporta", - "description": "Download your health data as PDF, CSV, or a full JSON backup.", + "description": "Scarica i tuoi dati sanitari come PDF, CSV o backup JSON completo.", "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.", @@ -3100,39 +3114,39 @@ "formatHint": "PDF · pronto per la stampa" }, "actions": { - "download": "Download" + "download": "Scarica" }, "filters": { "since": "Da", - "until": "Until" + "until": "Fino al" }, "cards": { "doctorReport": { - "title": "Doctor Report", - "description": "Printable PDF for the doctor's appointment — vitals, BMI, BP classification, medication compliance, mood." + "title": "Referto medico", + "description": "PDF stampabile per la visita medica — parametri vitali, IMC, classificazione della pressione, aderenza ai farmaci, umore." }, "measurementsCsv": { - "title": "Measurements", - "description": "Weight, blood pressure, pulse, glucose, and every other measurement as comma-separated values." + "title": "Misurazioni", + "description": "Peso, pressione, polso, glicemia e tutte le altre misurazioni in valori separati da virgola." }, "medicationsCsv": { "title": "Farmaci", - "description": "Your medication list with dosage, schedules, and (optionally) the full intake history.", - "includeIntake": "Include intake history" + "description": "Il tuo elenco di farmaci con dosaggio, orari e (facoltativamente) lo storico completo delle assunzioni.", + "includeIntake": "Includi lo storico delle assunzioni" }, "moodCsv": { "title": "Umore", - "description": "Daily mood entries with score, tags, and timestamps." + "description": "Voci di umore giornaliere con punteggio, tag e orari." }, "fullBackup": { - "title": "Full backup", - "description": "Single JSON file with everything — same shape as the weekly auto-backup. Useful for self-restore via admin upload." + "title": "Backup completo", + "description": "Un unico file JSON con tutto — stesso formato del backup automatico settimanale. Utile per il ripristino autonomo tramite il caricamento da amministratore." } } }, "about": { - "title": "About", - "description": "Version, license, links." + "title": "Informazioni", + "description": "Versione, licenza, link." }, "sharing": { "title": "Condivisione", @@ -3141,20 +3155,20 @@ }, "profile": "Profilo", "language": "Lingua", - "languageDescription": "Choose the display language of the application.", + "languageDescription": "Scegli la lingua di visualizzazione dell’applicazione.", "username": "Nome utente", - "height": "Height (cm)", + "height": "Altezza (cm)", "dateOfBirth": "Data di nascita", - "dateOfBirthHint": "Used for automatic blood pressure target calculations.", + "dateOfBirthHint": "Usato per il calcolo automatico degli obiettivi di pressione arteriosa.", "gender": "Sesso", - "genderNone": "Not specified", + "genderNone": "Non specificato", "genderMale": "Uomo", "genderFemale": "Donna", - "genderHint": "Used for gender-specific target values.", + "genderHint": "Usato per valori obiettivo specifici per sesso.", "timezone": "Fuso orario", - "timezoneHint": "Used for chart axis labels, reminder times, export timestamps, and Coach context. Stored data is unchanged.", - "timezoneInvalid": "Not a valid IANA timezone.", - "profileSaved": "Profile saved", + "timezoneHint": "Usato per le etichette degli assi dei grafici, gli orari dei promemoria, gli orari di esportazione e il contesto del Coach. I dati memorizzati restano invariati.", + "timezoneInvalid": "Non è un fuso orario IANA valido.", + "profileSaved": "Profilo salvato", "avatar": { "title": "Foto del profilo", "description": "Carica una foto archiviata sul tuo server. Appare in tutta l'app al posto di un servizio di avatar esterno.", @@ -3168,174 +3182,174 @@ "tooLarge": "L'immagine supera il limite di 2 MB.", "error": "Impossibile aggiornare la foto del profilo. Riprova." }, - "changePassword": "Change password", - "changePasswordDescription": "Replace your current password with a new one.", - "passwordReset": "Password reset", - "currentPassword": "Current password", - "newPassword": "New password", - "confirmNewPassword": "Confirm new password", - "passwordMismatch": "New passwords do not match", - "passwordUpdated": "Password updated successfully", + "changePassword": "Cambia password", + "changePasswordDescription": "Sostituisci la password attuale con una nuova.", + "passwordReset": "Reimposta password", + "currentPassword": "Password attuale", + "newPassword": "Nuova password", + "confirmNewPassword": "Conferma nuova password", + "passwordMismatch": "Le nuove password non coincidono", + "passwordUpdated": "Password aggiornata correttamente", "passkeys": "Passkeys", - "registeredPasskeys": "Registered Passkeys", - "passkeysDescription": "Passkeys allow secure login without a password.", + "registeredPasskeys": "Passkey registrate", + "passkeysDescription": "Le passkey consentono un accesso sicuro senza password.", "passkeyName": "Nome", - "passkeyDevice": "Device", + "passkeyDevice": "Dispositivo", "passkeyBackup": "Backup", - "passkeyCreated": "Created", + "passkeyCreated": "Creata", "passkeyActions": "Azioni", - "noPasskeys": "No passkeys registered.", - "addPasskey": "Add passkey", - "passkeyAdded": "Passkey added successfully!", - "passkeyOptionsError": "Could not load passkey options", - "passkeyRegistrationFailed": "Passkey registration failed", - "passkeyRegistrationCancelled": "Passkey registration cancelled", - "passkeyNotSupported": "Your browser or device doesn't support passkeys yet. Try a different browser or device.", - "passkeyAlreadyRegistered": "This device is already registered as a passkey for your account.", - "passkeySecurityBlocked": "Your browser blocked the passkey request for security reasons. Make sure you're on a trusted HTTPS origin and try again.", - "passkeyTimeout": "The passkey prompt timed out. Please try again.", - "passkeyUnknownError": "Passkey registration failed: {message}", - "deletePasskey": "Delete passkey?", - "deletePasskeyDescription": "The passkey will be permanently deleted.", - "singleDevice": "Single device", - "multiDevice": "Multi-device", - "backedUp": "backed up", + "noPasskeys": "Nessuna passkey registrata.", + "addPasskey": "Aggiungi passkey", + "passkeyAdded": "Passkey aggiunta correttamente!", + "passkeyOptionsError": "Impossibile caricare le opzioni della passkey", + "passkeyRegistrationFailed": "Registrazione della passkey non riuscita", + "passkeyRegistrationCancelled": "Registrazione della passkey annullata", + "passkeyNotSupported": "Il tuo browser o dispositivo non supporta ancora le passkey. Prova un altro browser o dispositivo.", + "passkeyAlreadyRegistered": "Questo dispositivo è già registrato come passkey del tuo account.", + "passkeySecurityBlocked": "Il tuo browser ha bloccato la richiesta di passkey per motivi di sicurezza. Assicurati di essere su un’origine HTTPS attendibile e riprova.", + "passkeyTimeout": "La richiesta della passkey è scaduta. Riprova.", + "passkeyUnknownError": "Registrazione della passkey non riuscita: {message}", + "deletePasskey": "Eliminare la passkey?", + "deletePasskeyDescription": "La passkey verrà eliminata definitivamente.", + "singleDevice": "Dispositivo singolo", + "multiDevice": "Multi-dispositivo", + "backedUp": "con backup", "telegram": "Telegram Notifications", "telegramDescription": "Get reminders for missed medication intake via Telegram.", - "telegramSaved": "Telegram settings saved", - "botToken": "Bot Token", - "chatId": "Chat ID", - "enableNotifications": "Enable notifications", - "testMessage": "Test message", - "testSent": "Test message sent!", - "telegramStep1": "1. Create a bot via @BotFather in Telegram and copy the token.", - "telegramStep2": "2. Send /start to your bot to activate the chat.", - "telegramStep3": "3. Find your Chat ID via @userinfobot or the Bot API.", + "telegramSaved": "Impostazioni Telegram salvate", + "botToken": "Token del bot", + "chatId": "ID chat", + "enableNotifications": "Attiva le notifiche", + "testMessage": "Messaggio di prova", + "testSent": "Messaggio di prova inviato!", + "telegramStep1": "1. Crea un bot tramite @BotFather su Telegram e copia il token.", + "telegramStep2": "2. Invia /start al tuo bot per attivare la chat.", + "telegramStep3": "3. Trova il tuo ID chat tramite @userinfobot o l’API del bot.", "ntfy": "ntfy", - "ntfyDescription": "Notifications via ntfy (self-hosted or ntfy.sh).", - "ntfyEnable": "Enable ntfy", - "ntfyServer": "Server URL", + "ntfyDescription": "Notifiche tramite ntfy (self-hosted o ntfy.sh).", + "ntfyEnable": "Attiva ntfy", + "ntfyServer": "URL del server", "ntfyTopic": "Topic", - "ntfyAuthToken": "Auth Token (optional)", - "ntfyAuthTokenHint": "Only needed for private topics with access control.", - "webPush": "Browser Push", - "webPushDescription": "Receive notifications directly in your browser, even when HealthLog is not open.", - "webPushNotSupported": "Your browser does not support push notifications.", - "webPushDenied": "Push notifications are blocked. Allow them in your browser settings.", - "webPushNotConfigured": "Web Push is not configured on the server.", - "webPushSubscribe": "Enable push", - "webPushUnsubscribe": "Disable push", - "webPushSubscribed": "Push notifications enabled!", - "webPushUnsubscribed": "Push notifications disabled.", - "webPushSubscribeFailed": "Activation failed", + "ntfyAuthToken": "Token di autenticazione (facoltativo)", + "ntfyAuthTokenHint": "Necessario solo per topic privati con controllo degli accessi.", + "webPush": "Push del browser", + "webPushDescription": "Ricevi notifiche direttamente nel browser, anche quando HealthLog non è aperto.", + "webPushNotSupported": "Il tuo browser non supporta le notifiche push.", + "webPushDenied": "Le notifiche push sono bloccate. Consentile nelle impostazioni del browser.", + "webPushNotConfigured": "Web Push non è configurato sul server.", + "webPushSubscribe": "Attiva push", + "webPushUnsubscribe": "Disattiva push", + "webPushSubscribed": "Notifiche push attivate!", + "webPushUnsubscribed": "Notifiche push disattivate.", + "webPushSubscribeFailed": "Attivazione non riuscita", "webPushActive": "Attivo", - "kiInsightsDescription": "Optional: Save your OpenAI key for daily, automatic evaluations in Insights.", - "codexConnected": "ChatGPT connected successfully! Insights are now active.", - "codexDisconnected": "ChatGPT connection removed.", - "codexConnectionFailed": "ChatGPT connection failed. Please try again.", - "rawData": "Send raw data", - "rawDataOnDescription": "Aggregated metrics plus anonymized raw points from the last 30 days are sent. This includes per-metric measurements (for example weight, blood pressure, pulse) with time context so the provider can detect patterns, outliers, and correlations more reliably. Name, email, and direct account identifiers are not transmitted.", - "rawDataOffDescription": "Only summarized metrics are sent (for example averages, trends, minimum/maximum). No individual points and no exact time context. This is more privacy-preserving, but the provider can be less precise for short-term patterns and fluctuations.", - "rawDataWarning": "Raw mode enabled: the provider receives additional anonymized points from the last 30 days for higher analysis accuracy.", - "regenerateInsights": "Regenerate reports", - "regenerateSuccess": "Reports regenerated successfully", - "regenerateRateLimit": "Please wait — you've hit the hourly limit for analyses.", - "lastGeneratedAt": "Last generated", + "kiInsightsDescription": "Facoltativo: salva la tua chiave OpenAI per analisi giornaliere e automatiche in Insights.", + "codexConnected": "ChatGPT connesso correttamente! Insights è ora attivo.", + "codexDisconnected": "Connessione a ChatGPT rimossa.", + "codexConnectionFailed": "Connessione a ChatGPT non riuscita. Riprova.", + "rawData": "Invia dati grezzi", + "rawDataOnDescription": "Vengono inviate metriche aggregate più punti grezzi anonimizzati degli ultimi 30 giorni. Ciò include misurazioni per singola metrica (ad esempio peso, pressione, polso) con contesto temporale, in modo che il provider possa rilevare in modo più affidabile schemi, valori anomali e correlazioni. Nome, e-mail e identificatori diretti dell’account non vengono trasmessi.", + "rawDataOffDescription": "Vengono inviate solo metriche riassuntive (ad esempio medie, tendenze, minimo/massimo). Nessun punto individuale e nessun contesto temporale preciso. Questo tutela meglio la privacy, ma il provider può essere meno preciso su schemi e oscillazioni a breve termine.", + "rawDataWarning": "Modalità grezza attiva: il provider riceve ulteriori punti anonimizzati degli ultimi 30 giorni per un’analisi più precisa.", + "regenerateInsights": "Rigenera i report", + "regenerateSuccess": "Report rigenerati correttamente", + "regenerateRateLimit": "Attendi — hai raggiunto il limite orario di analisi.", + "lastGeneratedAt": "Ultima generazione", "ai": { - "chatgptConnectedBadge": "ChatGPT connected", - "adminAiActiveBadge": "Admin provider active", - "connectionExpiredBadge": "Connection expired", - "connectedSince": "Connected since {when}.", - "deviceCodeHeading": "Finish connecting on chatgpt.com", - "deviceCodeStep1": "Open this link on any device:", - "deviceCodeStep2": "Enter this one-time code:", - "deviceCodeStep3": "Approve the connection — this page updates automatically.", + "chatgptConnectedBadge": "ChatGPT connesso", + "adminAiActiveBadge": "Provider amministratore attivo", + "connectionExpiredBadge": "Connessione scaduta", + "connectedSince": "Connesso da {when}.", + "deviceCodeHeading": "Completa la connessione su chatgpt.com", + "deviceCodeStep1": "Apri questo link su qualsiasi dispositivo:", + "deviceCodeStep2": "Inserisci questo codice monouso:", + "deviceCodeStep3": "Approva la connessione — questa pagina si aggiorna automaticamente.", "deviceCodeCopy": "Copia", - "deviceCodeWaiting": "Waiting for approval…", + "deviceCodeWaiting": "In attesa di approvazione…", "deviceCodeCancel": "Annulla", - "oauthNotConfigured": "ChatGPT OAuth is not configured on this instance — use your own API key below instead.", - "modelLabel": "Model", - "modelOptionDefault": "— Default —", - "modelOptionCustom": "Custom…", - "customModelLabel": "Custom model name", - "anthropicKeyLabel": "Anthropic API key", + "oauthNotConfigured": "L’OAuth di ChatGPT non è configurato su questa istanza — usa invece la tua chiave API qui sotto.", + "modelLabel": "Modello", + "modelOptionDefault": "— Predefinito —", + "modelOptionCustom": "Personalizzato…", + "customModelLabel": "Nome del modello personalizzato", + "anthropicKeyLabel": "Chiave API Anthropic", "baseUrlLabel": "Base URL", - "localKeyLabel": "API key (optional)", - "savedPreview": "(saved {preview})", - "savedShort": "(saved)", + "localKeyLabel": "Chiave API (facoltativa)", + "savedPreview": "(salvata {preview})", + "savedShort": "(salvata)", "saveCta": "Salva", "saved": "Salvato", - "saveFailed": "Save failed", + "saveFailed": "Salvataggio non riuscito", "errorGeneric": "Errore", "providerChain": { "types": { "codex": "ChatGPT (Codex)", - "openai": "OpenAI (your key)", + "openai": "OpenAI (la tua chiave)", "anthropic": "Anthropic (Claude)", - "local": "Local model", - "admin-openai": "Admin OpenAI" + "local": "Modello locale", + "admin-openai": "OpenAI amministratore" }, - "title": "Fallback chain", - "description": "If the primary provider fails, HealthLog walks the chain in order. Drag the rows to reorder, toggle the switch to disable a provider without removing it.", - "moveUp": "Move up", - "moveDown": "Move down", - "removeFromChain": "Remove from chain", - "addProvider": "Add provider", - "addNoneAvailable": "All providers already in chain", - "saveOrder": "Save chain order", - "saved": "Chain saved", - "saveFailed": "Saving the chain failed", - "resetDefaults": "Reset to defaults", - "resetConfirmTitle": "Reset chain to defaults?", - "resetConfirmBody": "The chain reverts to Codex → OpenAI → Anthropic → Local → Admin OpenAI. Your saved credentials are not touched." - }, - "activeProviderHeading": "Active provider", - "activeProviderBody": "Pick the provider you want to use first. The form below configures only the selected provider; the fallback chain at the bottom decides what happens if it fails.", - "activeProviderLabel": "Primary provider", - "providerConfigTitle": "Provider configuration", + "title": "Catena di fallback", + "description": "Se il provider principale fallisce, HealthLog percorre la catena in ordine. Trascina le righe per riordinarle; usa l’interruttore per disattivare un provider senza rimuoverlo.", + "moveUp": "Sposta su", + "moveDown": "Sposta giù", + "removeFromChain": "Rimuovi dalla catena", + "addProvider": "Aggiungi provider", + "addNoneAvailable": "Tutti i provider sono già nella catena", + "saveOrder": "Salva l’ordine della catena", + "saved": "Catena salvata", + "saveFailed": "Salvataggio della catena non riuscito", + "resetDefaults": "Ripristina", + "resetConfirmTitle": "Ripristinare la catena ai valori predefiniti?", + "resetConfirmBody": "La catena torna a Codex → OpenAI → Anthropic → Locale → OpenAI amministratore. Le credenziali salvate non vengono toccate." + }, + "activeProviderHeading": "Provider attivo", + "activeProviderBody": "Scegli il provider da usare per primo. Il modulo qui sotto configura solo il provider selezionato; la catena di fallback in fondo decide cosa accade se fallisce.", + "activeProviderLabel": "Provider principale", + "providerConfigTitle": "Configurazione del provider", "providerSelect": { - "codex": "ChatGPT account (Codex)", - "openai": "OpenAI (your API key)", + "codex": "Account ChatGPT (Codex)", + "openai": "OpenAI (la tua chiave API)", "anthropic": "Anthropic (Claude)", - "local": "Local model (OpenAI-compatible)", - "admin-openai": "Admin-provided OpenAI" + "local": "Modello locale (compatibile con OpenAI)", + "admin-openai": "OpenAI fornito dall’amministratore" }, "openai": { - "modelSelect": "Model", - "modelOptionCustom": "Custom slug…", - "modelCustomLabel": "Custom model slug", + "modelSelect": "Modello", + "modelOptionCustom": "Slug personalizzato…", + "modelCustomLabel": "Slug del modello personalizzato", "modelCustomPlaceholder": "gpt-5", - "baseUrlLabel": "Base URL (advanced)", + "baseUrlLabel": "URL base (avanzato)", "baseUrlPlaceholder": "https://api.openai.com/v1", - "baseUrlHelp": "Override only when using an OpenAI-compatible gateway. Leave blank for OpenAI itself.", - "showAdvanced": "Show advanced", - "hideAdvanced": "Hide advanced", - "apiKey": "API key", + "baseUrlHelp": "Sovrascrivi solo se usi un gateway compatibile con OpenAI. Lascia vuoto per OpenAI stesso.", + "showAdvanced": "Mostra avanzate", + "hideAdvanced": "Nascondi avanzate", + "apiKey": "Chiave API", "apiKeyPlaceholder": "sk-…" }, "codex": { - "statusConnected": "Connected", - "statusDisconnected": "Not connected", - "statusExpired": "Connection expired", - "connectButton": "Connect with ChatGPT", - "disconnectButton": "Disconnect", - "modelSlugLabel": "Model slug", - "modelSlugBody": "Codex uses the model your ChatGPT subscription routes to. The CODEX_MODEL environment variable lets ops override this on the instance.", - "lastInsight": "Last insight: {when}" + "statusConnected": "Connesso", + "statusDisconnected": "Non connesso", + "statusExpired": "Connessione scaduta", + "connectButton": "Connetti con ChatGPT", + "disconnectButton": "Disconnetti", + "modelSlugLabel": "Slug del modello", + "modelSlugBody": "Codex usa il modello a cui instrada il tuo abbonamento ChatGPT. La variabile d’ambiente CODEX_MODEL consente all’operatore di sovrascriverlo sull’istanza.", + "lastInsight": "Ultima analisi: {when}" }, "adminOpenai": { - "title": "Admin OpenAI", - "body": "Operator-provided OpenAI key. Used as a last-ditch fallback when no personal provider is configured. There is nothing to configure on this row — visibility means the operator has set it up.", - "notConfigured": "The operator has not configured a shared OpenAI key on this instance." + "title": "OpenAI amministratore", + "body": "Chiave OpenAI fornita dall’operatore. Usata come ultima risorsa quando nessun provider personale è configurato. Non c’è nulla da configurare in questa riga — la sua visibilità indica che l’operatore l’ha attivata.", + "notConfigured": "L’operatore non ha configurato una chiave OpenAI condivisa su questa istanza." }, - "testProvider": "Test active provider", + "testProvider": "Prova il provider attivo", "testSuccess": "OK — {provider} ({model})", - "testFailedShort": "Test failed: {message}", - "testUnexpectedResponse": "AI provider connection failed — unexpected response from the server.", - "testReasonCredentials": "Provider rejected the credentials — re-authenticate in AI settings.", - "testReasonRateLimited": "Provider rate-limited the request — try again shortly.", - "testReasonServerError": "The AI provider returned a server error.", - "testReasonUnreachable": "Could not reach the AI provider.", + "testFailedShort": "Prova non riuscita: {message}", + "testUnexpectedResponse": "Connessione al provider IA non riuscita — risposta inattesa dal server.", + "testReasonCredentials": "Il provider ha rifiutato le credenziali — autenticati di nuovo nelle impostazioni IA.", + "testReasonRateLimited": "Il provider ha limitato la richiesta — riprova a breve.", + "testReasonServerError": "Il provider IA ha restituito un errore del server.", + "testReasonUnreachable": "Impossibile raggiungere il provider IA.", "coachMemory": { "title": "Ciò che il Coach ricorda", "description": "Informazioni durature che hai comunicato al Coach. Le usa per mantenere pertinenti le sue risposte. Rimuovi tutto ciò che preferisci dimentichi.", @@ -3380,28 +3394,28 @@ } }, "withings": "Withings", - "withingsDescription": "Connect your Withings scale and blood pressure monitors.", - "withingsCredentials": "API Credentials", - "withingsClientId": "Client ID", - "withingsClientSecret": "Client Secret", - "withingsCredentialsSaved": "Credentials saved", - "withingsCredentialsSavedPlaceholder": "Saved — enter new to replace", - "withingsCredentialsSavedPlaceholderSecret": "Saved — enter new to replace", - "withingsSaveCredentials": "Save credentials", - "configured": "Configured", - "withingsSync": "Sync now", - "withingsFullSync": "Sync all data", - "withingsFullSyncTitle": "Full synchronization?", - "withingsFullSyncDescription": "All available Withings data will be fully synchronized. This may take some time depending on your history.", - "withingsSyncResult": "{count} measurements synchronized", - "withingsFullSyncResult": "{count} measurements fully synchronized", - "withingsSyncFailed": "Sync failed", - "withingsSynchronize": "Synchronize", - "withingsDisconnect": "Disconnect", - "withingsDisconnectTitle": "Disconnect Withings?", - "withingsDisconnectDescription": "The connection to Withings will be disconnected. Previously synced data will be preserved.", - "withingsConnect": "Connect with Withings", - "withingsNoCredentials": "Please enter your API credentials above to connect Withings.", + "withingsDescription": "Collega la tua bilancia e i misuratori di pressione Withings.", + "withingsCredentials": "Credenziali API", + "withingsClientId": "ID client", + "withingsClientSecret": "Secret client", + "withingsCredentialsSaved": "Credenziali salvate", + "withingsCredentialsSavedPlaceholder": "Salvato — inseriscine uno nuovo per sostituirlo", + "withingsCredentialsSavedPlaceholderSecret": "Salvato — inseriscine uno nuovo per sostituirlo", + "withingsSaveCredentials": "Salva credenziali", + "configured": "Configurato", + "withingsSync": "Sincronizza ora", + "withingsFullSync": "Sincronizza tutti i dati", + "withingsFullSyncTitle": "Sincronizzazione completa?", + "withingsFullSyncDescription": "Tutti i dati Withings disponibili verranno sincronizzati completamente. Potrebbe richiedere del tempo a seconda del tuo storico.", + "withingsSyncResult": "{count} misurazioni sincronizzate", + "withingsFullSyncResult": "{count} misurazioni sincronizzate completamente", + "withingsSyncFailed": "Sincronizzazione non riuscita", + "withingsSynchronize": "Sincronizza", + "withingsDisconnect": "Disconnetti", + "withingsDisconnectTitle": "Disconnettere Withings?", + "withingsDisconnectDescription": "La connessione a Withings verrà interrotta. I dati già sincronizzati saranno conservati.", + "withingsConnect": "Connetti con Withings", + "withingsNoCredentials": "Inserisci prima le tue credenziali API qui sopra per collegare Withings.", "whoop": "WHOOP", "whoopDescription": "Collega la tua fascia WHOOP per sincronizzare recupero, sonno, sforzo e allenamenti.", "whoopOverlapNote": "Se WHOOP e un'altra fonte forniscono lo stesso valore vitale — frequenza cardiaca a riposo, ossigeno nel sangue, temperatura corporea, frequenza respiratoria o fasi del sonno —, potresti vedere entrambi i valori finché un futuro aggiornamento non sceglierà un'unica fonte preferita.", @@ -3430,9 +3444,9 @@ "withings": { "reconnect": { "banner": { - "title": "Activity sync available", - "body": "Your Withings connection was authorised before activity sync was added. Reconnect once to enable steps, active energy, walking + running distance, and floors-climbed ingest from your Withings devices.", - "action": "Reconnect Withings" + "title": "Sincronizzazione attività disponibile", + "body": "La tua connessione Withings è stata autorizzata prima dell’aggiunta della sincronizzazione attività. Riconnettiti una volta per abilitare l’importazione di passi, energia attiva, distanza a piedi e di corsa, e piani saliti dai tuoi dispositivi Withings.", + "action": "Riconnetti Withings" } } } @@ -3443,132 +3457,133 @@ "warningServerError": "Connesso, errore del server", "parkedReconnect": "In pausa — riconnettere manualmente", "notConnected": "Non connesso", - "justNow": "just now", - "minutesAgo": "{count} min ago", - "hoursAgo": "{count} h ago", - "daysAgo": "{count} d ago", - "ariaLabel": "Integration status", + "justNow": "proprio ora", + "minutesAgo": "{count} min fa", + "hoursAgo": "{count} h fa", + "daysAgo": "{count} g fa", + "ariaLabel": "Stato dell’integrazione", "resumeCta": "Riconnetti", "resumeSuccess": "Integrazione ripresa", "resumeError": "Riconnessione fallita" }, - "apiTokens": "API Tokens", - "apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).", - "tokenNamePlaceholder": "Token name (e.g., iPhone Shortcut)", - "tokenCreated": "Token created — copy it now! It won't be shown again.", - "tokenRevoke": "Revoke token?", - "tokenRevokeDescription": "The token will be permanently deactivated. Applications using it will lose access.", - "tokenRevoked": "Revoked", - "tokenExpired": "Expired", + "apiTokens": "Token API", + "apiTokensDescription": "Crea token API per registrare assunzioni di farmaci esterne (ad es. Comandi rapidi, automazioni).", + "tokenNamePlaceholder": "Nome del token (ad es. Comando rapido iPhone)", + "tokenCreated": "Token creato — copialo ora! Non verrà mostrato di nuovo.", + "tokenRevoke": "Revocare il token?", + "tokenRevokeAction": "Revoca token", + "tokenRevokeDescription": "Il token verrà disattivato definitivamente. Le applicazioni che lo usano perderanno l’accesso.", + "tokenRevoked": "Revocato", + "tokenExpired": "Scaduto", "tokenActive": "Attivo", - "activeTokensTitle": "Active tokens", - "revokedTokensTitle": "Revoked tokens ({count})", + "activeTokensTitle": "Token attivi", + "revokedTokensTitle": "Token revocati ({count})", "tokenTableName": "Nome", - "tokenTablePermissions": "Permissions", + "tokenTablePermissions": "Autorizzazioni", "tokenTableStatus": "Stato", - "tokenTableCreated": "Created", - "tokenTableLastUsed": "Last used", + "tokenTableCreated": "Creato", + "tokenTableLastUsed": "Ultimo utilizzo", "tokenTableActions": "Azioni", - "noActiveTokens": "No active tokens available.", - "tokenNeverUsed": "Never", - "apiEndpointsTitle": "API endpoints", - "apiEndpointsDescription": "Overview of currently available external endpoint(s) for token-based ingestion.", - "apiEndpointMethod": "Method", - "apiEndpointPath": "Path", - "apiEndpointAuth": "Authentication", - "apiEndpointExample": "Body example", - "collapse": "Collapse", - "expand": "Expand", - "dangerZone": "Delete All Data", - "dangerZoneTitle": "Danger zone", - "dangerZoneDescription": "Deletes all your health data and integrations. Your user account will be preserved.", - "dangerZoneConfirm": "Delete all data?", - "dangerZoneConfirmDescription": "This will permanently delete all your health data and integrations. Your user account will be preserved.", - "dangerZoneSuccess": "All personal data has been deleted", - "dangerZoneDeleteFailed": "Deletion failed", - "finalDelete": "Delete permanently", - "deleteAccountCardTitle": "Delete account entirely", - "deleteAccountCardDescription": "Deletes your account along with passkeys, audit log, and sessions. This cannot be undone.", - "deleteAccountCta": "Delete account", - "deleteAccountConfirmTitle": "Delete account permanently?", - "deleteAccountConfirmDescription": "Your account, all health data, passkeys, sessions, and audit entries will be permanently deleted. You will be signed out immediately after.", - "deleteAccountSuccess": "Account deleted — signing you out.", - "deleteAccountFailed": "Account could not be deleted", - "deleteAccountFinal": "Delete permanently", + "noActiveTokens": "Nessun token attivo disponibile.", + "tokenNeverUsed": "Mai", + "apiEndpointsTitle": "Endpoint API", + "apiEndpointsDescription": "Panoramica degli endpoint esterni attualmente disponibili per l’inserimento basato su token.", + "apiEndpointMethod": "Metodo", + "apiEndpointPath": "Percorso", + "apiEndpointAuth": "Autenticazione", + "apiEndpointExample": "Esempio di body", + "collapse": "Comprimi", + "expand": "Espandi", + "dangerZone": "Elimina tutti i dati", + "dangerZoneTitle": "Zona pericolosa", + "dangerZoneDescription": "Elimina tutti i tuoi dati sanitari e le integrazioni. Il tuo account utente verrà conservato.", + "dangerZoneConfirm": "Eliminare tutti i dati?", + "dangerZoneConfirmDescription": "Questa azione eliminerà definitivamente tutti i tuoi dati sanitari e le integrazioni. Il tuo account utente verrà conservato.", + "dangerZoneSuccess": "Tutti i dati personali sono stati eliminati", + "dangerZoneDeleteFailed": "Eliminazione non riuscita", + "finalDelete": "Elimina definitivamente", + "deleteAccountCardTitle": "Elimina completamente l’account", + "deleteAccountCardDescription": "Elimina il tuo account insieme a passkey, registro di controllo e sessioni. Questa azione non può essere annullata.", + "deleteAccountCta": "Elimina account", + "deleteAccountConfirmTitle": "Eliminare definitivamente l’account?", + "deleteAccountConfirmDescription": "Il tuo account, tutti i dati sanitari, le passkey, le sessioni e le voci di audit verranno eliminati definitivamente. Verrai disconnesso subito dopo.", + "deleteAccountSuccess": "Account eliminato — disconnessione in corso.", + "deleteAccountFailed": "Impossibile eliminare l’account", + "deleteAccountFinal": "Elimina definitivamente", "saved": "Salvato", - "savingError": "Error saving", + "savingError": "Errore durante il salvataggio", "moodLogTitle": "moodLog", "moodLogDescription": "Import mood data from moodLog", "moodLogUrl": "moodLog URL", "moodLogUrlPlaceholder": "https://mood.example.com", - "moodLogApiKey": "API Key", + "moodLogApiKey": "Chiave API", "moodLogApiKeyPlaceholder": "ml_...", - "moodLogWebhookSecret": "Webhook Secret", - "moodLogWebhookSecretHelp": "Enter this secret in moodLog as webhook secret", - "moodLogSync": "Start sync", - "moodLogFullSync": "Full sync", - "moodLogFullSyncConfirm": "Synchronize fully", - "moodLogFullSyncTitle": "Full sync?", - "moodLogFullSyncDescription": "All available mood data will be fully synchronized.", - "moodLogDisconnect": "Disconnect", - "moodLogDisconnectTitle": "Disconnect moodLog?", - "moodLogDisconnectDescription": "The connection will be removed and all imported mood data will be deleted.", - "moodLogEntries": "Entries", - "moodLogSyncResult": "{count} entries synchronized", - "moodLogSyncFailed": "Sync failed", - "moodLogSaved": "moodLog connection saved", - "moodLogDisconnected": "moodLog disconnected", + "moodLogWebhookSecret": "Secret del webhook", + "moodLogWebhookSecretHelp": "Inserisci questo secret in moodLog come secret del webhook", + "moodLogSync": "Avvia sincronizzazione", + "moodLogFullSync": "Sincronizzazione completa", + "moodLogFullSyncConfirm": "Sincronizza completamente", + "moodLogFullSyncTitle": "Sincronizzazione completa?", + "moodLogFullSyncDescription": "Tutti i dati sull’umore disponibili verranno sincronizzati completamente.", + "moodLogDisconnect": "Disconnetti", + "moodLogDisconnectTitle": "Disconnettere moodLog?", + "moodLogDisconnectDescription": "La connessione verrà rimossa e tutti i dati sull’umore importati verranno eliminati.", + "moodLogEntries": "Voci", + "moodLogSyncResult": "{count} voci sincronizzate", + "moodLogSyncFailed": "Sincronizzazione non riuscita", + "moodLogSaved": "Connessione moodLog salvata", + "moodLogDisconnected": "moodLog disconnesso", "about": { - "version": "App version", + "version": "Versione dell’app", "gitSha": "Build", - "builtAt": "Built {time}", - "builtAtLabel": "Built", - "license": "License", - "repository": "Source code", + "builtAt": "Compilato {time}", + "builtAtLabel": "Compilato", + "license": "Licenza", + "repository": "Codice sorgente", "changelog": "Changelog", - "docs": "Documentation", + "docs": "Documentazione", "linksHeading": "Fonti e documentazione", "newerAvailable": "Nuova versione: {tag}", "tourReplay": "Rivedi il tour", "tourReplayHint": "Guarda il tour della dashboard — utile se l'hai saltato alla prima visita." }, "testConnection": { - "test": "Test connection", - "testing": "Testing…", - "ok": "Connected (latency {latency} ms)", + "test": "Prova connessione", + "testing": "Test in corso…", + "ok": "Connesso (latenza {latency} ms)", "errors": { - "credentials_rejected": "Credentials rejected — check your token", - "rate_limited": "Rate-limited by the upstream", - "timeout": "Request timed out", - "upstream_error": "Upstream returned an error", - "connection_failed": "Connection failed", - "not_configured": "Not configured", - "url_not_public": "URL is not a public endpoint", - "url_invalid": "URL is invalid", - "redirected": "Endpoint redirects — check the URL", - "endpoint_not_found": "Endpoint not found at the URL", - "credentials_unreadable": "Credentials cannot be decrypted", - "upstream_invalid_json": "Upstream sent invalid JSON", - "vapid_not_configured": "Web Push not configured (VAPID keys missing)", - "rate_limited_self": "Too many test requests", - "generic": "Test failed" + "credentials_rejected": "Credenziali rifiutate — controlla il token", + "rate_limited": "Limitato dal servizio upstream", + "timeout": "Richiesta scaduta", + "upstream_error": "Il servizio upstream ha restituito un errore", + "connection_failed": "Connessione non riuscita", + "not_configured": "Non configurato", + "url_not_public": "L’URL non è un endpoint pubblico", + "url_invalid": "L’URL non è valido", + "redirected": "L’endpoint reindirizza — controlla l’URL", + "endpoint_not_found": "Endpoint non trovato all’URL", + "credentials_unreadable": "Impossibile decifrare le credenziali", + "upstream_invalid_json": "Il servizio upstream ha inviato un JSON non valido", + "vapid_not_configured": "Web Push non configurato (chiavi VAPID mancanti)", + "rate_limited_self": "Troppe richieste di prova", + "generic": "Prova non riuscita" } }, "notificationStatus": { "title": "Channel reliability", "description": "Live status of every notification channel — Auto-disabled means HealthLog stopped retrying after a permanent error.", - "emptyDescription": "No channels configured yet. Add a channel below to start tracking its delivery health.", + "emptyDescription": "Nessun canale ancora configurato. Aggiungi un canale qui sotto per iniziare a monitorarne la consegna.", "stateActive": "Attivo", - "stateAutoDisabled": "Auto-disabled", - "stateSendingPaused": "Sending paused", + "stateAutoDisabled": "Disattivato automaticamente", + "stateSendingPaused": "Invio in pausa", "stateManuallyDisabled": "Disabilitato", - "lastSuccess": "Last successful send", - "lastFailure": "Last failure", - "consecutiveFailures": "Consecutive failures", - "disabledReason": "Reason", - "nextRetry": "Next retry", - "reEnable": "Re-enable", - "sendTest": "Send test" + "lastSuccess": "Ultimo invio riuscito", + "lastFailure": "Ultimo errore", + "consecutiveFailures": "Errori consecutivi", + "disabledReason": "Motivo", + "nextRetry": "Prossimo tentativo", + "reEnable": "Riattiva", + "sendTest": "Invia prova" }, "identity": { "description": "Dati facoltativi per l’esportazione della cartella clinica (copertina PDF + esportazione FHIR). Tutti i campi sono facoltativi.", @@ -4108,284 +4123,284 @@ "carrierUnavailableGeoFallback": "{location} — carrier unavailable" }, "achievements": { - "title": "Achievements", - "subtitle": "Earn achievements by tracking your health and taking your meds on time.", - "loginRequired": "Please sign in to view achievements.", - "points": "Points", - "unlocked": "Unlocked", - "nextGoal": "Next goal", - "allCompleted": "All achievements unlocked!", - "completed": "Completed", - "goalReached": "Goal reached", - "remainingUnlocks": "{count} still unlockable", - "noneUnlockedYet": "No achievements unlocked yet.", - "pointsValue": "{points} points", + "title": "Obiettivi", + "subtitle": "Ottieni obiettivi monitorando la tua salute e assumendo i farmaci in orario.", + "loginRequired": "Accedi per vedere gli obiettivi.", + "points": "Punti", + "unlocked": "Sbloccato", + "nextGoal": "Prossimo obiettivo", + "allCompleted": "Tutti gli obiettivi sbloccati!", + "completed": "Completato", + "goalReached": "Obiettivo raggiunto", + "remainingUnlocks": "Ancora {count} da sbloccare", + "noneUnlockedYet": "Nessun obiettivo ancora sbloccato.", + "pointsValue": "{points} punti", "metricCount": "{count}", - "metricDays": "{count} days", + "metricDays": "{count} giorni", "metricPercent": "{count}%", "badges": { "intakeTotal1": { - "title": "First intake", - "description": "Record your first taken medication event." + "title": "Prima assunzione", + "description": "Registra la tua prima assunzione di farmaco." }, "intakeTotal10": { - "title": "Intake starter", - "description": "Record 10 taken medication events." + "title": "Principiante delle assunzioni", + "description": "Registra 10 assunzioni di farmaci." }, "intakeTotal50": { - "title": "Intake routine", - "description": "Record 50 taken medication events." + "title": "Routine delle assunzioni", + "description": "Registra 50 assunzioni di farmaci." }, "intakeTotal150": { - "title": "Intake expert", - "description": "Record 150 taken medication events." + "title": "Esperto delle assunzioni", + "description": "Registra 150 assunzioni di farmaci." }, "intakeTotal300": { - "title": "Intake legend", - "description": "Record 300 taken medication events." + "title": "Leggenda delle assunzioni", + "description": "Registra 300 assunzioni di farmaci." }, "overIntake1": { - "title": "Double take", - "description": "Take medication at least once more than planned." + "title": "Doppia dose", + "description": "Assumi il farmaco almeno una volta in più del previsto." }, "skippedIntake1": { - "title": "Stepped back", - "description": "Skip at least one planned intake." + "title": "Pausa presa", + "description": "Salta almeno un’assunzione pianificata." }, "passkeyCreated1": { - "title": "Passkey set up", - "description": "Create your first passkey." + "title": "Passkey configurata", + "description": "Crea la tua prima passkey." }, "passkeyLogin1": { - "title": "Passkey login", - "description": "Sign in at least once with a passkey." + "title": "Accesso con passkey", + "description": "Accedi almeno una volta con una passkey." }, "passwordLogin1": { - "title": "Old school", - "description": "Sign in at least once with username and password." + "title": "Vecchia scuola", + "description": "Accedi almeno una volta con nome utente e password." }, "bugReport1": { - "title": "Bug hunter", - "description": "Submit a bug report." + "title": "Cacciatore di bug", + "description": "Invia una segnalazione di bug." }, "loginStreak7": { - "title": "Login streak 7d", - "description": "Sign in on 7 consecutive days." + "title": "Serie di accessi 7g", + "description": "Accedi per 7 giorni consecutivi." }, "loginStreak30": { - "title": "Login streak 30d", - "description": "Sign in on 30 consecutive days." + "title": "Serie di accessi 30g", + "description": "Accedi per 30 giorni consecutivi." }, "onTimePerfect1": { - "title": "On-time 1d", - "description": "Take all medications on time for 1 day." + "title": "Puntuale 1g", + "description": "Assumi tutti i farmaci in orario per 1 giorno." }, "compliance801": { - "title": "80% adherence · 1d", - "description": "Reach at least 80% 30-day adherence for 1 day." + "title": "Aderenza 80% · 1g", + "description": "Raggiungi almeno l’80% di aderenza su 30 giorni per 1 giorno." }, "bmiGreen1": { - "title": "BMI green · 1d", - "description": "Keep your BMI in the normal range for 1 day." + "title": "IMC nel verde · 1g", + "description": "Mantieni l’IMC nell’intervallo normale per 1 giorno." }, "bpGreen1": { - "title": "BP green · 1d", - "description": "Keep your blood pressure in the normal range for 1 day." + "title": "Pressione nel verde · 1g", + "description": "Mantieni la pressione nell’intervallo normale per 1 giorno." }, "pulseGreen1": { - "title": "Pulse green · 1d", - "description": "Keep your resting pulse in the normal range for 1 day." + "title": "Polso nel verde · 1g", + "description": "Mantieni il polso a riposo nell’intervallo normale per 1 giorno." }, "onTimePerfect7": { - "title": "On-time 7d", - "description": "Take all medications on time for 7 consecutive days." + "title": "Puntuale 7g", + "description": "Assumi tutti i farmaci in orario per 7 giorni consecutivi." }, "compliance807": { - "title": "80% adherence · 7d", - "description": "Reach at least 80% 30-day adherence for 7 consecutive days." + "title": "Aderenza 80% · 7g", + "description": "Raggiungi almeno l’80% di aderenza su 30 giorni per 7 giorni consecutivi." }, "bmiGreen7": { - "title": "BMI green · 7d", - "description": "Keep your BMI in the normal range for 7 consecutive days." + "title": "IMC nel verde · 7g", + "description": "Mantieni l’IMC nell’intervallo normale per 7 giorni consecutivi." }, "bpGreen7": { - "title": "BP green · 7d", - "description": "Keep your blood pressure in the normal range for 7 consecutive days." + "title": "Pressione nel verde · 7g", + "description": "Mantieni la pressione nell’intervallo normale per 7 giorni consecutivi." }, "pulseGreen7": { - "title": "Pulse green · 7d", - "description": "Keep your resting pulse in the normal range for 7 consecutive days." + "title": "Polso nel verde · 7g", + "description": "Mantieni il polso a riposo nell’intervallo normale per 7 giorni consecutivi." }, "onTimePerfect30": { - "title": "On-time 30d", - "description": "Take all medications on time for 30 consecutive days." + "title": "Puntuale 30g", + "description": "Assumi tutti i farmaci in orario per 30 giorni consecutivi." }, "compliance8030": { - "title": "80% adherence · 30d", - "description": "Reach at least 80% 30-day adherence for 30 consecutive days." + "title": "Aderenza 80% · 30g", + "description": "Raggiungi almeno l’80% di aderenza su 30 giorni per 30 giorni consecutivi." }, "bmiGreen30": { - "title": "BMI green · 30d", - "description": "Keep your BMI in the normal range for 30 consecutive days." + "title": "IMC nel verde · 30g", + "description": "Mantieni l’IMC nell’intervallo normale per 30 giorni consecutivi." }, "bpGreen30": { - "title": "BP green · 30d", - "description": "Keep your blood pressure in the normal range for 30 consecutive days." + "title": "Pressione nel verde · 30g", + "description": "Mantieni la pressione nell’intervallo normale per 30 giorni consecutivi." }, "pulseGreen30": { - "title": "Pulse green · 30d", - "description": "Keep your resting pulse in the normal range for 30 consecutive days." + "title": "Polso nel verde · 30g", + "description": "Mantieni il polso a riposo nell’intervallo normale per 30 giorni consecutivi." }, "onTimePerfect180": { - "title": "On-time 180d", - "description": "Take all medications on time for 180 consecutive days." + "title": "Puntuale 180g", + "description": "Assumi tutti i farmaci in orario per 180 giorni consecutivi." }, "compliance80180": { - "title": "80% adherence · 180d", - "description": "Reach at least 80% 30-day adherence for 180 consecutive days." + "title": "Aderenza 80% · 180g", + "description": "Raggiungi almeno l’80% di aderenza su 30 giorni per 180 giorni consecutivi." }, "bmiGreen180": { - "title": "BMI green · 180d", - "description": "Keep your BMI in the normal range for 180 consecutive days." + "title": "IMC nel verde · 180g", + "description": "Mantieni l’IMC nell’intervallo normale per 180 giorni consecutivi." }, "bpGreen180": { - "title": "BP green · 180d", - "description": "Keep your blood pressure in the normal range for 180 consecutive days." + "title": "Pressione nel verde · 180g", + "description": "Mantieni la pressione nell’intervallo normale per 180 giorni consecutivi." }, "pulseGreen180": { - "title": "Pulse green · 180d", - "description": "Keep your resting pulse in the normal range for 180 consecutive days." + "title": "Polso nel verde · 180g", + "description": "Mantieni il polso a riposo nell’intervallo normale per 180 giorni consecutivi." }, "onTimePerfect360": { - "title": "On-time 360d", - "description": "Take all medications on time for 360 consecutive days." + "title": "Puntuale 360g", + "description": "Assumi tutti i farmaci in orario per 360 giorni consecutivi." }, "compliance80360": { - "title": "80% adherence · 360d", - "description": "Reach at least 80% 30-day adherence for 360 consecutive days." + "title": "Aderenza 80% · 360g", + "description": "Raggiungi almeno l’80% di aderenza su 30 giorni per 360 giorni consecutivi." }, "bmiGreen360": { - "title": "BMI green · 360d", - "description": "Keep your BMI in the normal range for 360 consecutive days." + "title": "IMC nel verde · 360g", + "description": "Mantieni l’IMC nell’intervallo normale per 360 giorni consecutivi." }, "bpGreen360": { - "title": "BP green · 360d", - "description": "Keep your blood pressure in the normal range for 360 consecutive days." + "title": "Pressione nel verde · 360g", + "description": "Mantieni la pressione nell’intervallo normale per 360 giorni consecutivi." }, "pulseGreen360": { - "title": "Pulse green · 360d", - "description": "Keep your resting pulse in the normal range for 360 consecutive days." + "title": "Polso nel verde · 360g", + "description": "Mantieni il polso a riposo nell’intervallo normale per 360 giorni consecutivi." }, "moodFirst": { - "title": "First mood", - "description": "Log your first mood entry." + "title": "Primo umore", + "description": "Registra la tua prima voce di umore." }, "moodStreak7": { - "title": "Mood diarist 7d", - "description": "Log a mood entry on 7 consecutive days." + "title": "Diario dell’umore 7g", + "description": "Registra una voce di umore per 7 giorni consecutivi." }, "moodStreak30": { - "title": "Mood diarist 30d", - "description": "Log a mood entry on 30 consecutive days." + "title": "Diario dell’umore 30g", + "description": "Registra una voce di umore per 30 giorni consecutivi." }, "moodUp7": { - "title": "Brighter week", - "description": "Your 7-day mood average improved by at least 1.0 point compared to the previous week." + "title": "Settimana più serena", + "description": "La tua media dell’umore di 7 giorni è migliorata di almeno 1,0 punto rispetto alla settimana precedente." }, "weightFirst": { - "title": "First weigh-in", - "description": "Record your first weight measurement." + "title": "Prima pesata", + "description": "Registra la tua prima misurazione del peso." }, "weight50": { - "title": "Fifty weigh-ins", - "description": "Record 50 weight measurements." + "title": "Cinquanta pesate", + "description": "Registra 50 misurazioni del peso." }, "weight200": { - "title": "200 weigh-ins", - "description": "Record 200 weight measurements." + "title": "200 pesate", + "description": "Registra 200 misurazioni del peso." }, "bpFirst": { - "title": "First reading", - "description": "Record your first blood-pressure measurement." + "title": "Prima misurazione", + "description": "Registra la tua prima misurazione della pressione." }, "bp50": { - "title": "Fifty BP readings", - "description": "Record 50 blood-pressure measurements." + "title": "Cinquanta misurazioni della pressione", + "description": "Registra 50 misurazioni della pressione." }, "bp200": { - "title": "200 BP readings", - "description": "Record 200 blood-pressure measurements." + "title": "200 misurazioni della pressione", + "description": "Registra 200 misurazioni della pressione." }, "pulseFirst": { - "title": "First pulse", - "description": "Record your first pulse measurement." + "title": "Primo polso", + "description": "Registra la tua prima misurazione del polso." }, "consistentMonth": { - "title": "Consistent month", - "description": "Log entries on at least 25 distinct days within a single calendar month." + "title": "Mese costante", + "description": "Registra voci in almeno 25 giorni diversi nello stesso mese di calendario." }, "entryStreak7": { - "title": "Tracker streak 7d", - "description": "Log at least one entry on 7 consecutive days." + "title": "Serie di registrazioni 7g", + "description": "Registra almeno una voce per 7 giorni consecutivi." }, "entryStreak30": { - "title": "Tracker streak 30d", - "description": "Log at least one entry on 30 consecutive days." + "title": "Serie di registrazioni 30g", + "description": "Registra almeno una voce per 30 giorni consecutivi." }, "weekendWarrior": { - "title": "Weekend tracker", - "description": "Log entries on 4 consecutive Saturday + Sunday weekend pairs." + "title": "Tracker del weekend", + "description": "Registra voci in 4 fine settimana consecutivi (sabato + domenica)." }, "hiddenNightOwl": { - "title": "Night owl", - "description": "Logged an entry between 02:00 and 04:00 in the morning." + "title": "Nottambulo", + "description": "Hai registrato una voce tra le 02:00 e le 04:00 del mattino." }, "hiddenEarlyBird": { - "title": "Early bird", - "description": "Logged an entry between 04:00 and 06:00 in the morning." + "title": "Mattiniero", + "description": "Hai registrato una voce tra le 04:00 e le 06:00 del mattino." }, "hiddenLeapDay": { - "title": "Leap-day legend", - "description": "Logged an entry on February 29." + "title": "Leggenda dell’anno bisestile", + "description": "Hai registrato una voce il 29 febbraio." }, "hiddenDoctorPdf": { - "title": "House call", - "description": "Exported your first doctor-report PDF." + "title": "Visita a domicilio", + "description": "Hai esportato il tuo primo referto medico in PDF." }, "hiddenLocaleFlip": { - "title": "Polyglot", - "description": "Switched the app language at least once." + "title": "Poliglotta", + "description": "Hai cambiato la lingua dell’app almeno una volta." }, "hiddenBugBuddy": { - "title": "Bug buddy", - "description": "Submitted at least 5 bug reports — the project loves you." + "title": "Amico dei bug", + "description": "Hai inviato almeno 5 segnalazioni di bug — il progetto ti ama." } }, - "nextProgressLabel": "Progress to unlock", - "completedOn": "Completed on {date}", - "locked": "Locked", + "nextProgressLabel": "Progressi verso lo sblocco", + "completedOn": "Completato il {date}", + "locked": "Bloccato", "criterionHint": "{current} / {target}", "progressPercent": "{percent}%", "categories": { "medication": "Farmaco", - "vitals": "Vitals", + "vitals": "Parametri vitali", "mood": "Umore", - "security": "Account & security", + "security": "Account e sicurezza", "engagement": "Engagement", - "hidden": "Hidden" + "hidden": "Nascosto" }, "hiddenCard": { - "title": "Hidden achievement", - "description": "Keep tracking — you might just stumble across it.", - "ariaLabel": "Hidden locked achievement" + "title": "Obiettivo nascosto", + "description": "Continua a monitorare — potresti imbatterti in esso.", + "ariaLabel": "Obiettivo nascosto bloccato" }, "hiddenUnlockToast": { - "title": "You unlocked a hidden achievement!" + "title": "Hai sbloccato un obiettivo nascosto!" }, "dashboardCard": { - "title": "Recent unlocks", - "viewAll": "View all", - "empty": "No achievements yet — keep logging to unlock your first." + "title": "Sbloccati di recente", + "viewAll": "Vedi tutti", + "empty": "Ancora nessun obiettivo — continua a registrare per sbloccare il primo." } }, "bugreport": { diff --git a/messages/pl.json b/messages/pl.json index 39097727f..4cf5a126a 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -278,6 +278,12 @@ "emptyDescription": "Najpierw dodaj lek z harmonogramem, a potem wróć tutaj, aby zarejestrować jego przyjęcie.", "emptyCta": "Dodaj lek" }, + "quickEntryDiscard": { + "title": "Odrzucić ten wpis?", + "description": "Masz niezapisane dane. Zamknąć formularz i utracić to, co wpisano?", + "confirm": "Odrzuć", + "cancel": "Kontynuuj edycję" + }, "compliance7d": "Przestrzeganie (7 dni)", "mood": "Nastrój", "customizeTitle": "Układ panelu", @@ -554,35 +560,35 @@ "scheduleDaily": "Codziennie", "inactive": "Wstrzymany", "intakeHistory": "Historia przyjęć", - "openDetailPage": "Open medication detail page", + "openDetailPage": "Otwórz stronę szczegółów leku", "deleteConfirm": "Usunąć lek?", "compliance": "Przestrzeganie zaleceń", - "importIntakes": "Import intakes", - "importDescription": "Paste a JSON array with intake data. Expected format:", - "importUploadFile": "Upload JSON file", - "importPaste": "Paste JSON here...", - "importSelected": "Selected: {name}", - "importFileLoaded": "File \"{name}\" loaded", - "importInvalidJson": "File does not contain valid JSON", - "importNoArray": "No array found", - "importResult": "{imported} intakes imported", - "importDuplicatesSkipped": "{count} duplicates skipped", - "importInvalidSkipped": "{count} invalid entries skipped", - "importFailed": "Import failed", - "importInvalidFormat": "Invalid JSON format", - "apiEndpointTitle": "API Endpoint: {name}", - "apiEndpointDescription": "Use this endpoint to record intakes via API (e.g., iPhone Shortcut or cron job).", - "apiEndpointActive": "API endpoint active", - "apiEndpointActivated": "API endpoint activated", - "apiEndpointDeactivated": "API endpoint deactivated", - "apiTokenCount": "Active ({count} tokens)", - "apiToken": "API Token", - "apiTokenActiveHint": "Endpoint is active. Existing tokens remain valid but cannot be shown in plain text again.", - "apiTokenActivateHint": "Activate the endpoint to create a new token.", - "apiTokenOnceHint": "The token is only shown in plain text once at creation.", - "requestExample": "Request example", - "statusLoadFailed": "Could not load status", - "changeFailed": "Change failed", + "importIntakes": "Importuj przyjęcia", + "importDescription": "Wklej tablicę JSON z danymi przyjęć. Oczekiwany format:", + "importUploadFile": "Prześlij plik JSON", + "importPaste": "Wklej tutaj JSON...", + "importSelected": "Wybrano: {name}", + "importFileLoaded": "Plik „{name}” wczytany", + "importInvalidJson": "Plik nie zawiera prawidłowego JSON", + "importNoArray": "Nie znaleziono tablicy", + "importResult": "{imported} przyjęć zaimportowano", + "importDuplicatesSkipped": "{count} duplikatów pominięto", + "importInvalidSkipped": "{count} nieprawidłowych wpisów pominięto", + "importFailed": "Import nie powiódł się", + "importInvalidFormat": "Nieprawidłowy format JSON", + "apiEndpointTitle": "Punkt końcowy API: {name}", + "apiEndpointDescription": "Użyj tego punktu końcowego do rejestrowania przyjęć przez API (np. Skrót iPhone lub zadanie cron).", + "apiEndpointActive": "Punkt końcowy API aktywny", + "apiEndpointActivated": "Punkt końcowy API aktywowany", + "apiEndpointDeactivated": "Punkt końcowy API dezaktywowany", + "apiTokenCount": "Aktywny ({count} tokenów)", + "apiToken": "Token API", + "apiTokenActiveHint": "Punkt końcowy jest aktywny. Istniejące tokeny pozostają ważne, ale nie można ich ponownie pokazać jako zwykły tekst.", + "apiTokenActivateHint": "Aktywuj punkt końcowy, aby utworzyć nowy token.", + "apiTokenOnceHint": "Token jest pokazywany jako zwykły tekst tylko raz, przy tworzeniu.", + "requestExample": "Przykład żądania", + "statusLoadFailed": "Nie udało się wczytać statusu", + "changeFailed": "Zmiana nie powiodła się", "categoryBloodPressure": "Ciśnienie krwi", "categoryVitamin": "Witaminy", "categoryThyroid": "Tarczyca", @@ -594,7 +600,7 @@ "categorySkin": "Pielęgnacja skóry", "categorySleepAid": "Na sen", "categoryDiabetes": "Diabetes", - "categoryAntibiotic": "Antibiotic", + "categoryAntibiotic": "Antybiotyk", "categoryOther": "Inne", "daysSun": "Nd", "daysMon": "Pn", @@ -622,6 +628,10 @@ "skipped": "Pominięte", "intakeToastTaken": "{name} oznaczone jako przyjęte", "intakeToastSkipped": "{name} pominięte", + "intakeToastFailed": "Nie udało się zapisać {name} — spróbuj ponownie", + "intakeUndo": "Cofnij", + "intakeUndone": "Wpis cofnięty", + "intakeUndoFailed": "Nie udało się cofnąć — popraw w historii", "weekdaySunday": "Niedziela", "weekdayMonday": "Poniedziałek", "weekdayTuesday": "Wtorek", @@ -738,141 +748,141 @@ } }, "cadence": { - "section": "Cadence", - "label": "How often", + "section": "Częstotliwość", + "label": "Jak często", "kind": { - "daily": "Every day", - "weekdays": "Certain days of the week", - "everyNWeeks": "Every N weeks on certain days", - "monthly": "Monthly on a specific day", - "everyNMonths": "Every N months on a specific day", - "yearly": "Once a year", - "rolling": "Every N days from when I last took it (flexible)", - "rollingExplainer": "Counts from your last logged intake — pauses if you skip a dose.", - "oneShot": "One-time dose" + "daily": "Codziennie", + "weekdays": "W określone dni tygodnia", + "everyNWeeks": "Co N tygodni w określone dni", + "monthly": "Co miesiąc w określonym dniu", + "everyNMonths": "Co N miesięcy w określonym dniu", + "yearly": "Raz w roku", + "rolling": "Co N dni od ostatniego przyjęcia (elastycznie)", + "rollingExplainer": "Liczy od ostatniego zarejestrowanego przyjęcia — wstrzymuje się, jeśli pominiesz dawkę.", + "oneShot": "Dawka jednorazowa" }, "weekdays": { - "label": "Days of the week", + "label": "Dni tygodnia", "short": { "mo": "Mo", - "tu": "Tu", - "we": "We", - "th": "Th", + "tu": "Wt", + "we": "Śr", + "th": "Cz", "fr": "Fr", "sa": "Sa", - "su": "Su" + "su": "Nd" }, "long": { - "mo": "Monday", - "tu": "Tuesday", - "we": "Wednesday", - "th": "Thursday", - "fr": "Friday", - "sa": "Saturday", - "su": "Sunday" + "mo": "Poniedziałek", + "tu": "Wtorek", + "we": "Środa", + "th": "Czwartek", + "fr": "Piątek", + "sa": "Sobota", + "su": "Niedziela" } }, "intervalWeeks": { - "suffix": "weeks" + "suffix": "tygodni" }, "dayOfMonth": { - "label": "Day" + "label": "Dzień" }, "intervalMonths": { - "suffix": "months", - "dayOnLabel": "on day" + "suffix": "miesięcy", + "dayOnLabel": "w dniu" }, "yearly": { "date": { - "label": "Date" + "label": "Data" } }, "rollingDays": { - "suffix": "days" + "suffix": "dni" } }, "timesOfDay": { - "section": "Times of day", - "label": "Times of day", + "section": "Pory dnia", + "label": "Pory dnia", "empty": { - "cta": "Add the first time." + "cta": "Dodaj pierwszą godzinę." }, - "add": "Add time", - "remove": "Remove", + "add": "Dodaj godzinę", + "remove": "Usuń", "presets": { - "morning": "Morning", - "noon": "Noon", - "evening": "Evening", - "night": "Night" + "morning": "Rano", + "noon": "Południe", + "evening": "Wieczór", + "night": "Noc" }, "max": { - "reached": "Maximum reached — remove a time before adding another." + "reached": "Osiągnięto maksimum — usuń jedną godzinę przed dodaniem kolejnej." } }, "courseWindow": { - "section": "Course window", + "section": "Okres leczenia", "startsOn": { - "label": "Starts on" + "label": "Rozpoczyna się" }, "endsOn": { - "label": "Ends on" + "label": "Kończy się" }, - "noEndDate": "No end date", - "oneShotCaption": "(one-time dose)", - "invalidRange": "End date must be on or after the start date." + "noEndDate": "Brak daty zakończenia", + "oneShotCaption": "(dawka jednorazowa)", + "invalidRange": "Data zakończenia musi być taka sama jak data rozpoczęcia lub późniejsza." } }, "wizard": { "header": { - "stepOf": "Step {current} of {total}", - "createTitle": "New medication", - "editTitle": "Edit {name}" + "stepOf": "Krok {current} z {total}", + "createTitle": "Nowy lek", + "editTitle": "Edytuj {name}" }, "nav": { - "back": "Back", - "next": "Next", - "save": "Save", - "saveEdit": "Save changes", + "back": "Wstecz", + "next": "Dalej", + "save": "Zapisz", + "saveEdit": "Zapisz zmiany", "nextHint": "Dalej: {step}", "reviewHint": "Dalej: Zapisz", "jumpFirst": "Przejdź do pierwszego kroku", "jumpLast": "Przejdź do podsumowania i zapisu" }, "nl": { - "button": "Describe" + "button": "Opisz" }, "steps": { "step1": { - "title": "What's the medication called?", - "subline": "Exactly as it appears on the box.", + "title": "Jak nazywa się lek?", + "subline": "Dokładnie tak, jak na opakowaniu.", "label": "Name", - "placeholder": "e.g. Ramipril", + "placeholder": "np. Ramipryl", "short": "Nazwa" }, "step2": { - "title": "What kind of medication is it?", - "subline": "We use this for the right templates and analytics.", - "short": "Typ" + "title": "Jaki to rodzaj leku?", + "subline": "Wykorzystujemy to do odpowiednich szablonów i analiz.", + "short": "Rodzaj" }, "step3": { - "title": "What's the dose?", - "subline": "A tablet, a puff, a drop — depending on the form.", - "amountLabel": "Amount", - "amountPlaceholder": "e.g. 5", - "unitLabel": "Unit", + "title": "Jaka jest dawka?", + "subline": "Tabletka, wziew, kropla — w zależności od formy.", + "amountLabel": "Ilość", + "amountPlaceholder": "np. 5", + "unitLabel": "Jednostka", "unit": { "mg": "mg", "ml": "ml", - "iu": "IU", + "iu": "j.m.", "mcg": "µg", "g": "g", - "tablets": "tablet(s)", - "capsules": "capsule(s)", - "drops": "drops", - "puffs": "puff", - "sprays": "spray", - "pieces": "pieces", - "other": "other" + "tablets": "tabletka/i", + "capsules": "kapsułka/i", + "drops": "krople", + "puffs": "wziew", + "sprays": "rozpylenie", + "pieces": "sztuki", + "other": "inne" }, "deliveryFormLabel": "Droga podania", "deliveryForm": { @@ -888,124 +898,124 @@ "short": "Dawka" }, "step4": { - "title": "Over what period are you taking it?", - "subline": "A start date is enough for today. You can add an end date later.", + "title": "Przez jaki okres go przyjmujesz?", + "subline": "Na dziś wystarczy data rozpoczęcia. Datę zakończenia możesz dodać później.", "short": "Kiedy" }, "step5": { - "title": "How often do you take it?", - "subline": "Pick the cadence — the details follow on the next step.", + "title": "Jak często go przyjmujesz?", + "subline": "Wybierz częstotliwość — szczegóły w następnym kroku.", "short": "Częstotliwość" }, "step6": { - "title": "What does that look like in detail?", - "subline": "Set weekdays, intervals, or the day of the month.", + "title": "Jak to wygląda szczegółowo?", + "subline": "Ustaw dni tygodnia, interwały lub dzień miesiąca.", "intervalWeeks": { - "label": "Every", - "suffix": "weeks" + "label": "Co", + "suffix": "tygodni" }, "dayOfMonth": { - "label": "On", - "suffix": "day of the month" + "label": "W dniu", + "suffix": "dzień miesiąca" }, "rollingDays": { - "label": "Every", - "suffix": "days from the last intake" + "label": "Co", + "suffix": "dni od ostatniego przyjęcia" }, "short": "Szczegóły" }, "step7": { - "title": "When do you take it?", - "subline": "One or more times of day — reminders follow this list.", - "presetsLabel": "Time-of-day presets", + "title": "Kiedy go przyjmujesz?", + "subline": "Jedna lub więcej pór dnia — przypomnienia opierają się na tej liście.", + "presetsLabel": "Ustawienia wstępne pory dnia", "presets": { - "morning": "Morning", - "noon": "Noon", - "evening": "Evening", - "night": "Night" + "morning": "Rano", + "noon": "Południe", + "evening": "Wieczór", + "night": "Noc" }, "short": "Godziny" }, "step8": { - "title": "All done — should HealthLog remind you?", - "subline": "Review everything below before you save.", - "remindersLabel": "Reminders on", - "remindersDescription": "Push, Telegram or ntfy — based on your settings.", - "multiScheduleNote": "This medication has multiple schedules. The detailed editor returns in a later release.", + "title": "Gotowe — czy HealthLog ma Ci przypominać?", + "subline": "Sprawdź wszystko poniżej przed zapisaniem.", + "remindersLabel": "Przypomnienia włączone", + "remindersDescription": "Push, Telegram lub ntfy — w zależności od ustawień.", + "multiScheduleNote": "Ten lek ma wiele harmonogramów. Szczegółowy edytor wróci w kolejnej wersji.", "short": "Gotowe" } }, "classRow": { - "bloodPressure": "Blood pressure", + "bloodPressure": "Ciśnienie krwi", "diabetes": "Diabetes", - "hormone": "Hormones", - "glp1": "GLP-1 injection", - "painRelief": "Pain", - "allergy": "Allergy", - "vitamin": "Vitamins", - "supplement": "Supplement", - "antibiotic": "Antibiotic", - "other": "Other" + "hormone": "Hormony", + "glp1": "Iniekcja GLP-1", + "painRelief": "Ból", + "allergy": "Alergia", + "vitamin": "Witaminy", + "supplement": "Suplement", + "antibiotic": "Antybiotyk", + "other": "Inne" }, "cadence": { "daily": { - "label": "Daily", - "description": "Every day at the same time." + "label": "Codziennie", + "description": "Codziennie o tej samej porze." }, "weekdays": { - "label": "Specific weekdays", - "description": "E.g. Monday, Wednesday, Friday." + "label": "Określone dni tygodnia", + "description": "Np. poniedziałek, środa, piątek." }, "everyNWeeks": { - "label": "Every few weeks", - "description": "E.g. every 2 weeks on Thursdays (GLP-1, biologics)." + "label": "Co kilka tygodni", + "description": "Np. co 2 tygodnie w czwartki (GLP-1, leki biologiczne)." }, "monthly": { - "label": "Monthly", - "description": "On a fixed day each month (e.g. always the 1st)." + "label": "Co miesiąc", + "description": "W stały dzień każdego miesiąca (np. zawsze 1.)." }, "rolling": { - "label": "Flexible from last dose", - "description": "Leave the next dose open. When you tap 'taken', the counter starts again from that moment." + "label": "Elastycznie od ostatniej dawki", + "description": "Pozostaw kolejną dawkę otwartą. Po naciśnięciu „przyjęto” licznik startuje od nowa od tego momentu." }, "oneShot": { - "label": "Single dose", - "description": "One administration, e.g. a vaccine or an antibiotic course." + "label": "Jednorazowo", + "description": "Jednorazowe podanie, np. szczepionka lub kuracja antybiotykowa." } }, "summary": { - "title": "Summary", + "title": "Podsumowanie", "cadence": { - "daily": "Every day", - "weekdays": "On selected days of the week", - "biweekly": "Every two weeks", - "monthly": "Monthly", - "quarterly": "Every three months", - "yearly": "Once a year", - "rolling": "Every {n} days from your last intake", - "oneShot": "A single dose", - "everyNWeeks": "Every {n} weeks", - "everyNMonths": "Every {n} months" + "daily": "Codziennie", + "weekdays": "W wybrane dni tygodnia", + "biweekly": "Co dwa tygodnie", + "monthly": "Co miesiąc", + "quarterly": "Co trzy miesiące", + "yearly": "Raz w roku", + "rolling": "Co {n} dni od ostatniego przyjęcia", + "oneShot": "Pojedyncza dawka", + "everyNWeeks": "Co {n} tygodni", + "everyNMonths": "Co {n} miesięcy" }, - "weekdaysDetail": ", on {days}", - "dayOfMonthDetail": ", on day {day}.", - "times": "at {times}", - "startsOn": "Starts: {date}", - "endsOn": "Ends: {date}", - "noEndDate": "No end date" + "weekdaysDetail": ", w {days}", + "dayOfMonthDetail": ", dnia {day}.", + "times": "o {times}", + "startsOn": "Początek: {date}", + "endsOn": "Koniec: {date}", + "noEndDate": "Brak daty zakończenia" }, "compose": { - "scheduleIndex": "Schedule {n} of {total}", + "scheduleIndex": "Harmonogram {n} z {total}", "list": { - "add": "Add another schedule", - "edit": "Edit", - "remove": "Remove", - "removeDisabled": "At least one schedule is required.", - "empty": "No schedule yet." + "add": "Dodaj kolejny harmonogram", + "edit": "Edytuj", + "remove": "Usuń", + "removeDisabled": "Wymagany jest co najmniej jeden harmonogram.", + "empty": "Brak harmonogramu." } }, "errors": { - "submitFailed": "Could not save the medication — please retry." + "submitFailed": "Nie udało się zapisać leku — spróbuj ponownie." } }, "doseStrength": { @@ -1160,88 +1170,88 @@ }, "detail": { "status": { - "active": "Active", - "paused": "Paused", - "ended": "Ended" + "active": "Aktywny", + "paused": "Wstrzymany", + "ended": "Zakończony" }, "today": { - "groupLabel": "Today's dose", - "taken": "Taken", - "skipped": "Skipped", - "toastTaken": "Today's dose logged", - "toastSkipped": "Today's dose skipped", - "recordedTaken": "Taken today at {time}", - "recordedSkipped": "Skipped today at {time}", - "recordedOneShot": "One-shot dose taken at {time}", - "noneScheduled": "No dose scheduled for today.", - "pausedHint": "Paused — no reminder today.", - "error": "Could not log the dose." + "groupLabel": "Dzisiejsza dawka", + "taken": "Przyjęto", + "skipped": "Pominięto", + "toastTaken": "Dzisiejsza dawka zapisana", + "toastSkipped": "Dzisiejsza dawka pominięta", + "recordedTaken": "Przyjęto dziś o {time}", + "recordedSkipped": "Pominięto dziś o {time}", + "recordedOneShot": "Jednorazową dawkę przyjęto o {time}", + "noneScheduled": "Na dziś nie zaplanowano żadnej dawki.", + "pausedHint": "Wstrzymano — dziś brak przypomnienia.", + "error": "Nie udało się zapisać dawki." }, "cadence": { - "oneShotOn": "One-time dose on {date}.", - "oneShotPending": "One-time dose pending." + "oneShotOn": "Dawka jednorazowa w dniu {date}.", + "oneShotPending": "Dawka jednorazowa oczekuje." }, "intake": { - "title": "Intake history", - "importButton": "Import", + "title": "Historia przyjęć", + "importButton": "Importuj", "rowActions": { - "openMenu": "Open row actions", - "edit": "Edit", - "delete": "Delete" + "openMenu": "Otwórz akcje wiersza", + "edit": "Edytuj", + "delete": "Usuń" }, "selection": { - "rowToggleLabel": "Select row" + "rowToggleLabel": "Zaznacz wiersz" }, "bulkDelete": { - "selectionCount": "{count} entries selected", - "deleteButton": "Delete selection", - "cancelButton": "Cancel", - "confirmTitle": "Delete the selected entries?", - "confirmBody": "{count} entries will be removed permanently. The medication itself stays.", - "confirmAction": "Delete", - "toast": "Selection deleted", - "failed": "Could not delete the selection." + "selectionCount": "{count} wpisów zaznaczono", + "deleteButton": "Usuń zaznaczone", + "cancelButton": "Anuluj", + "confirmTitle": "Usunąć zaznaczone wpisy?", + "confirmBody": "{count} wpisów zostanie trwale usuniętych. Sam lek pozostaje.", + "confirmAction": "Usuń", + "toast": "Zaznaczone usunięto", + "failed": "Nie udało się usunąć zaznaczonych." }, "edit": { - "dialogTitle": "Edit intake", - "takenAtLabel": "Taken at", - "skippedLabel": "Skipped", - "noteLabel": "Note", - "save": "Save", - "cancel": "Cancel", - "savedToast": "Intake updated", - "failed": "Could not update the intake." + "dialogTitle": "Edytuj przyjęcie", + "takenAtLabel": "Przyjęto o", + "skippedLabel": "Pominięto", + "noteLabel": "Notatka", + "save": "Zapisz", + "cancel": "Anuluj", + "savedToast": "Przyjęcie zaktualizowane", + "failed": "Nie udało się zaktualizować przyjęcia." }, "deleteRow": { - "confirmTitle": "Delete this intake?", - "confirmBody": "The entry is removed permanently.", - "confirmAction": "Delete", - "toast": "Intake deleted", - "failed": "Could not delete the intake." + "confirmTitle": "Usunąć to przyjęcie?", + "confirmBody": "Wpis zostanie trwale usunięty.", + "confirmAction": "Usuń", + "toast": "Przyjęcie usunięte", + "failed": "Nie udało się usunąć przyjęcia." } }, "notifications": { - "title": "Notifications", - "switchLabel": "Send a reminder when this dose is due", - "helperOn": "Reminders are on.", - "helperOff": "Reminders are off.", - "enabledToast": "Reminders on", - "disabledToast": "Reminders off", - "toggleFailed": "Could not change the reminder setting.", - "clientManagedChip": "Your iPhone manages reminders for this medication." + "title": "Przypomnienia", + "switchLabel": "Wyślij przypomnienie, gdy nadejdzie czas tej dawki", + "helperOn": "Przypomnienia są włączone.", + "helperOff": "Przypomnienia są wyłączone.", + "enabledToast": "Przypomnienia włączone", + "disabledToast": "Przypomnienia wyłączone", + "toggleFailed": "Nie udało się zmienić ustawienia przypomnień.", + "clientManagedChip": "Twój iPhone zarządza przypomnieniami dla tego leku." }, "settings": { - "title": "Settings", + "title": "Ustawienia", "phases": { - "openButton": "Configure phases", - "requiresCourseWindow": "Phases are available once a course window is set." + "openButton": "Skonfiguruj fazy", + "requiresCourseWindow": "Fazy są dostępne po ustawieniu okresu leczenia." }, "grace": { - "label": "Reminder window — applies to your primary schedule", - "primaryScheduleNote": "Multi-schedule medications keep their other schedules' default window.", - "unit": "minutes", - "saved": "Reminder window saved", - "failed": "Could not save the reminder window." + "label": "Okno przypomnienia — dotyczy głównego harmonogramu", + "primaryScheduleNote": "Leki z wieloma harmonogramami zachowują domyślne okno pozostałych harmonogramów.", + "unit": "minut", + "saved": "Okno przypomnienia zapisane", + "failed": "Nie udało się zapisać okna przypomnienia." }, "codes": { "label": "Kody kliniczne (ATC / RxNorm)", @@ -1253,57 +1263,57 @@ } }, "zone": { - "title": "Manage & danger zone", + "title": "Zarządzanie i strefa zagrożenia", "pause": { - "title": "Pause", - "helper": "Pause reminders until you turn them back on. History stays intact.", - "pausedToast": "Reminders paused", - "resumedToast": "Reminders resumed", - "failed": "Could not change the paused state." + "title": "Wstrzymaj", + "helper": "Wstrzymaj przypomnienia, aż włączysz je ponownie. Historia pozostaje nienaruszona.", + "pausedToast": "Przypomnienia wstrzymane", + "resumedToast": "Przypomnienia wznowione", + "failed": "Nie udało się zmienić stanu wstrzymania." }, "end": { - "title": "End medication", - "helper": "Stop reminders and mark the medication as ended. History stays visible.", - "button": "End", - "dialogTitle": "End {name} — stop reminders, history stays visible", - "dialogBody": "No more reminders. Old entries remain in the timeline.", - "toast": "Medication ended", - "failed": "Could not end the medication." + "title": "Zakończ lek", + "helper": "Zatrzymuje przypomnienia i oznacza lek jako zakończony. Historia pozostaje widoczna.", + "button": "Zakończ", + "dialogTitle": "Zakończ {name} — zatrzymuje przypomnienia, historia pozostaje widoczna", + "dialogBody": "Brak kolejnych przypomnień. Stare wpisy pozostają na osi czasu.", + "toast": "Lek zakończony", + "failed": "Nie udało się zakończyć leku." }, "purge": { - "title": "Delete history", - "helper": "{count} intake events are stored for this medication.", - "button": "Delete history", - "dialogTitle": "Really delete the history?", - "dialogBody": "The {count} intake events will be removed permanently. The medication itself stays.", - "toast": "History deleted", - "failed": "Could not delete the history." + "title": "Usuń historię", + "helper": "Dla tego leku zapisano {count} przyjęć.", + "button": "Usuń historię", + "dialogTitle": "Na pewno usunąć historię?", + "dialogBody": "{count} przyjęć zostanie trwale usuniętych. Sam lek pozostaje.", + "toast": "Historia usunięta", + "failed": "Nie udało się usunąć historii." }, "delete": { - "title": "Delete medication", - "helper": "Removes the medication and every related record.", - "button": "Delete", - "dialogTitle": "Delete {name}?", - "dialogBody": "The medication, its schedules, intake history, API tokens, phase config and reminders will be removed permanently.", - "toast": "Medication deleted", - "failed": "Could not delete the medication." + "title": "Usuń lek", + "helper": "Usuwa lek i wszystkie powiązane rekordy.", + "button": "Usuń", + "dialogTitle": "Usunąć {name}?", + "dialogBody": "Lek, jego harmonogramy, historia przyjęć, tokeny API, konfiguracja faz i przypomnienia zostaną trwale usunięte.", + "toast": "Lek usunięty", + "failed": "Nie udało się usunąć leku." } }, "phases": { - "modeLabel": "{phase} mode", - "saveFailed": "Could not save the reminder phases." + "modeLabel": "Tryb {phase}", + "saveFailed": "Nie udało się zapisać faz przypomnień." }, "api": { - "caption": "Endpoint for \"{name}\"", - "copyUrl": "Copy URL", - "urlCopied": "URL copied", - "mintToken": "Mint token", - "mintAnotherToken": "Mint another token", - "mintFailed": "Could not mint a token.", - "copyFailed": "Could not copy.", - "tokenCopied": "Token copied", - "copyToken": "Copy token", - "mintedHint": "Copy this token now — it is shown only once." + "caption": "Punkt końcowy dla „{name}”", + "copyUrl": "Kopiuj URL", + "urlCopied": "Skopiowano URL", + "mintToken": "Wygeneruj token", + "mintAnotherToken": "Wygeneruj kolejny token", + "mintFailed": "Nie udało się wygenerować tokena.", + "copyFailed": "Nie udało się skopiować.", + "tokenCopied": "Token skopiowany", + "copyToken": "Kopiuj token", + "mintedHint": "Skopiuj ten token teraz — jest pokazywany tylko raz." }, "edit": { "planOption": "Edytuj plan", @@ -1518,6 +1528,7 @@ "feedbackThanks": "Dzięki za opinię", "feedbackConfirmed": "Opinia zapisana", "feedbackAlreadyRated": "Już ocenione", + "feedbackError": "Nie udało się zapisać opinii. Spróbuj ponownie.", "confidence": "Pewność", "confidenceAria": "Pewność: {value} na 100", "confidenceHigh": "Wysoka pewność", @@ -1539,6 +1550,7 @@ "regenerateAnalysis": "Uruchom analizę ponownie", "regenerateSuccess": "Analiza wygenerowana ponownie", "warmAssessments": "Przygotuj analizy", + "warmAssessmentsHint": "Analizy odświeżają się automatycznie w nocy. Przygotuj je teraz, aby zobaczyć najnowsze dane.", "warmStarted": "Analizy są przygotowywane w tle", "narrativeTitle": "Twój okres w skrócie", "narrativeWeek": "Ten tydzień", @@ -1696,6 +1708,7 @@ "tagline": "Osobisty coach zdrowia, oparty na twoich danych.", "newChat": "Nowa rozmowa", "send": "Wyślij", + "stop": "Zatrzymaj", "thinking": "Myślę…", "composerPlaceholder": "Zapytaj o swoje dane…", "composerHint": "Enter wysyła, Shift+Enter przechodzi do nowej linii.", @@ -2784,19 +2797,19 @@ "loadError": "Nie udało się wczytać powiadomień urządzenia.", "disclaimer": "To alerty wygenerowane przez Twoje urządzenie za pomocą jego wbudowanego wykrywania. Są tu pokazywane wyłącznie dla Twojej informacji. To nie jest ocena medyczna wykonana przez HealthLog, a HealthLog nie diagnozuje żadnych schorzeń. Jeśli powiadomienie Cię niepokoi, skontaktuj się z lekarzem.", "event": { - "irregularRhythm": "Irregular rhythm notification", - "highHeartRate": "High heart rate notification", - "lowHeartRate": "Low heart rate notification", - "walkingSteadiness": "Walking steadiness alert", - "breathingDisturbance": "Breathing disturbance during sleep" + "irregularRhythm": "Powiadomienie o nieregularnym rytmie", + "highHeartRate": "Powiadomienie o wysokim tętnie", + "lowHeartRate": "Powiadomienie o niskim tętnie", + "walkingSteadiness": "Alert o stabilności chodu", + "breathingDisturbance": "Zaburzenie oddychania podczas snu" }, "verdict": { - "irregular": "Your device flagged a possible irregular rhythm.", - "notDetected": "Your device did not flag an irregular rhythm.", - "inconclusive": "Your device could not make a reading.", - "low": "Your device flagged low walking steadiness.", - "veryLow": "Your device flagged very low walking steadiness.", - "fired": "Your device flagged this event." + "irregular": "Twoje urządzenie wykryło możliwy nieregularny rytm.", + "notDetected": "Twoje urządzenie nie wykryło nieregularnego rytmu.", + "inconclusive": "Twoje urządzenie nie mogło wykonać pomiaru.", + "low": "Twoje urządzenie wykryło niską stabilność chodu.", + "veryLow": "Twoje urządzenie wykryło bardzo niską stabilność chodu.", + "fired": "Twoje urządzenie zgłosiło to zdarzenie." } } }, @@ -2998,24 +3011,24 @@ "disableError": "Nie udało się wyłączyć trybu Badawczego. Spróbuj ponownie." }, "shell": { - "sectionsNav": "Settings sections" + "sectionsNav": "Sekcje ustawień" }, "sections": { "account": { "title": "Konto", - "description": "Profile, password, passkeys, and your onboarding tour." + "description": "Profil, hasło, klucze dostępu i przewodnik wprowadzający." }, "integrations": { "title": "Integracje", - "description": "Withings, moodLog, and other connected services." + "description": "Withings, moodLog i inne połączone usługi." }, "notifications": { "title": "Powiadomienia", - "description": "Live overview of every configured channel." + "description": "Podgląd na żywo każdego skonfigurowanego kanału." }, "dashboard": { "title": "Pulpit", - "description": "Tile layout and order." + "description": "Układ i kolejność kafelków." }, "thresholds": { "title": "Cele", @@ -3078,12 +3091,12 @@ } }, "ai": { - "title": "AI Insights", - "description": "Provider, model, key." + "title": "Analizy AI", + "description": "Dostawca, model, klucz." }, "api": { "title": "API & Tokens", - "description": "Bearer tokens for your own scripts and apps — log measurements or medication intake from anywhere." + "description": "Tokeny Bearer dla własnych skryptów i aplikacji — zapisuj pomiary lub przyjęcia leków z dowolnego miejsca." }, "advanced": { "title": "Zaawansowane", @@ -3091,8 +3104,9 @@ }, "export": { "title": "Eksportuj", - "description": "Download your health data as PDF, CSV, or a full JSON backup.", + "description": "Pobierz swoje dane zdrowotne jako PDF, CSV lub pełną kopię zapasową JSON.", "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.", @@ -3100,39 +3114,39 @@ "formatHint": "PDF · gotowe do druku" }, "actions": { - "download": "Download" + "download": "Pobierz" }, "filters": { "since": "Od", - "until": "Until" + "until": "Do" }, "cards": { "doctorReport": { - "title": "Doctor Report", - "description": "Printable PDF for the doctor's appointment — vitals, BMI, BP classification, medication compliance, mood." + "title": "Raport dla lekarza", + "description": "PDF do druku na wizytę lekarską — parametry życiowe, BMI, klasyfikacja ciśnienia, przestrzeganie leczenia, nastrój." }, "measurementsCsv": { - "title": "Measurements", - "description": "Weight, blood pressure, pulse, glucose, and every other measurement as comma-separated values." + "title": "Pomiary", + "description": "Waga, ciśnienie, tętno, glukoza i wszystkie inne pomiary jako wartości rozdzielone przecinkami." }, "medicationsCsv": { "title": "Leki", - "description": "Your medication list with dosage, schedules, and (optionally) the full intake history.", - "includeIntake": "Include intake history" + "description": "Lista leków z dawkowaniem, harmonogramem i (opcjonalnie) pełną historią przyjęć.", + "includeIntake": "Dołącz historię przyjęć" }, "moodCsv": { "title": "Nastrój", - "description": "Daily mood entries with score, tags, and timestamps." + "description": "Codzienne wpisy nastroju z oceną, tagami i znacznikami czasu." }, "fullBackup": { - "title": "Full backup", - "description": "Single JSON file with everything — same shape as the weekly auto-backup. Useful for self-restore via admin upload." + "title": "Pełna kopia zapasowa", + "description": "Pojedynczy plik JSON ze wszystkim — taki sam format jak cotygodniowa automatyczna kopia. Przydatny do samodzielnego przywracania przez przesłanie w panelu administratora." } } }, "about": { - "title": "About", - "description": "Version, license, links." + "title": "O aplikacji", + "description": "Wersja, licencja, linki." }, "sharing": { "title": "Udostępnianie", @@ -3141,20 +3155,20 @@ }, "profile": "Profil", "language": "Język", - "languageDescription": "Choose the display language of the application.", + "languageDescription": "Wybierz język wyświetlania aplikacji.", "username": "Nazwa użytkownika", - "height": "Height (cm)", + "height": "Wzrost (cm)", "dateOfBirth": "Data urodzenia", - "dateOfBirthHint": "Used for automatic blood pressure target calculations.", + "dateOfBirthHint": "Używane do automatycznego obliczania docelowych wartości ciśnienia.", "gender": "Płeć", - "genderNone": "Not specified", + "genderNone": "Nie podano", "genderMale": "Mężczyzna", "genderFemale": "Kobieta", - "genderHint": "Used for gender-specific target values.", + "genderHint": "Używane do wartości docelowych zależnych od płci.", "timezone": "Strefa czasowa", - "timezoneHint": "Used for chart axis labels, reminder times, export timestamps, and Coach context. Stored data is unchanged.", - "timezoneInvalid": "Not a valid IANA timezone.", - "profileSaved": "Profile saved", + "timezoneHint": "Używane do etykiet osi wykresów, godzin przypomnień, znaczników czasu eksportu i kontekstu Coacha. Zapisane dane pozostają niezmienione.", + "timezoneInvalid": "To nie jest prawidłowa strefa czasowa IANA.", + "profileSaved": "Profil zapisany", "avatar": { "title": "Zdjęcie profilowe", "description": "Prześlij zdjęcie przechowywane na własnym serwerze. Pojawia się w całej aplikacji zamiast zewnętrznej usługi awatarów.", @@ -3168,174 +3182,174 @@ "tooLarge": "Obraz przekracza limit 2 MB.", "error": "Nie udało się zaktualizować zdjęcia profilowego. Spróbuj ponownie." }, - "changePassword": "Change password", - "changePasswordDescription": "Replace your current password with a new one.", - "passwordReset": "Password reset", - "currentPassword": "Current password", - "newPassword": "New password", - "confirmNewPassword": "Confirm new password", - "passwordMismatch": "New passwords do not match", - "passwordUpdated": "Password updated successfully", + "changePassword": "Zmień hasło", + "changePasswordDescription": "Zastąp obecne hasło nowym.", + "passwordReset": "Resetowanie hasła", + "currentPassword": "Obecne hasło", + "newPassword": "Nowe hasło", + "confirmNewPassword": "Potwierdź nowe hasło", + "passwordMismatch": "Nowe hasła nie są zgodne", + "passwordUpdated": "Hasło zaktualizowane pomyślnie", "passkeys": "Passkeys", - "registeredPasskeys": "Registered Passkeys", - "passkeysDescription": "Passkeys allow secure login without a password.", + "registeredPasskeys": "Zarejestrowane klucze dostępu", + "passkeysDescription": "Klucze dostępu umożliwiają bezpieczne logowanie bez hasła.", "passkeyName": "Nazwa", - "passkeyDevice": "Device", + "passkeyDevice": "Urządzenie", "passkeyBackup": "Backup", - "passkeyCreated": "Created", + "passkeyCreated": "Utworzono", "passkeyActions": "Akcje", - "noPasskeys": "No passkeys registered.", - "addPasskey": "Add passkey", - "passkeyAdded": "Passkey added successfully!", - "passkeyOptionsError": "Could not load passkey options", - "passkeyRegistrationFailed": "Passkey registration failed", - "passkeyRegistrationCancelled": "Passkey registration cancelled", - "passkeyNotSupported": "Your browser or device doesn't support passkeys yet. Try a different browser or device.", - "passkeyAlreadyRegistered": "This device is already registered as a passkey for your account.", - "passkeySecurityBlocked": "Your browser blocked the passkey request for security reasons. Make sure you're on a trusted HTTPS origin and try again.", - "passkeyTimeout": "The passkey prompt timed out. Please try again.", - "passkeyUnknownError": "Passkey registration failed: {message}", - "deletePasskey": "Delete passkey?", - "deletePasskeyDescription": "The passkey will be permanently deleted.", - "singleDevice": "Single device", - "multiDevice": "Multi-device", - "backedUp": "backed up", + "noPasskeys": "Brak zarejestrowanych kluczy dostępu.", + "addPasskey": "Dodaj klucz dostępu", + "passkeyAdded": "Klucz dostępu dodany pomyślnie!", + "passkeyOptionsError": "Nie udało się wczytać opcji klucza dostępu", + "passkeyRegistrationFailed": "Rejestracja klucza dostępu nie powiodła się", + "passkeyRegistrationCancelled": "Rejestracja klucza dostępu anulowana", + "passkeyNotSupported": "Twoja przeglądarka lub urządzenie nie obsługuje jeszcze kluczy dostępu. Wypróbuj inną przeglądarkę lub urządzenie.", + "passkeyAlreadyRegistered": "To urządzenie jest już zarejestrowane jako klucz dostępu Twojego konta.", + "passkeySecurityBlocked": "Twoja przeglądarka zablokowała żądanie klucza dostępu ze względów bezpieczeństwa. Upewnij się, że jesteś w zaufanym źródle HTTPS i spróbuj ponownie.", + "passkeyTimeout": "Żądanie klucza dostępu wygasło. Spróbuj ponownie.", + "passkeyUnknownError": "Rejestracja klucza dostępu nie powiodła się: {message}", + "deletePasskey": "Usunąć klucz dostępu?", + "deletePasskeyDescription": "Klucz dostępu zostanie trwale usunięty.", + "singleDevice": "Pojedyncze urządzenie", + "multiDevice": "Wiele urządzeń", + "backedUp": "z kopią zapasową", "telegram": "Telegram Notifications", "telegramDescription": "Get reminders for missed medication intake via Telegram.", - "telegramSaved": "Telegram settings saved", - "botToken": "Bot Token", - "chatId": "Chat ID", - "enableNotifications": "Enable notifications", - "testMessage": "Test message", - "testSent": "Test message sent!", - "telegramStep1": "1. Create a bot via @BotFather in Telegram and copy the token.", - "telegramStep2": "2. Send /start to your bot to activate the chat.", - "telegramStep3": "3. Find your Chat ID via @userinfobot or the Bot API.", + "telegramSaved": "Ustawienia Telegrama zapisane", + "botToken": "Token bota", + "chatId": "ID czatu", + "enableNotifications": "Włącz powiadomienia", + "testMessage": "Wiadomość testowa", + "testSent": "Wiadomość testowa wysłana!", + "telegramStep1": "1. Utwórz bota przez @BotFather w Telegramie i skopiuj token.", + "telegramStep2": "2. Wyślij /start do swojego bota, aby aktywować czat.", + "telegramStep3": "3. Znajdź swoje ID czatu przez @userinfobot lub API bota.", "ntfy": "ntfy", - "ntfyDescription": "Notifications via ntfy (self-hosted or ntfy.sh).", - "ntfyEnable": "Enable ntfy", - "ntfyServer": "Server URL", + "ntfyDescription": "Powiadomienia przez ntfy (własny serwer lub ntfy.sh).", + "ntfyEnable": "Włącz ntfy", + "ntfyServer": "Adres URL serwera", "ntfyTopic": "Topic", - "ntfyAuthToken": "Auth Token (optional)", - "ntfyAuthTokenHint": "Only needed for private topics with access control.", - "webPush": "Browser Push", - "webPushDescription": "Receive notifications directly in your browser, even when HealthLog is not open.", - "webPushNotSupported": "Your browser does not support push notifications.", - "webPushDenied": "Push notifications are blocked. Allow them in your browser settings.", - "webPushNotConfigured": "Web Push is not configured on the server.", - "webPushSubscribe": "Enable push", - "webPushUnsubscribe": "Disable push", - "webPushSubscribed": "Push notifications enabled!", - "webPushUnsubscribed": "Push notifications disabled.", - "webPushSubscribeFailed": "Activation failed", + "ntfyAuthToken": "Token uwierzytelniania (opcjonalny)", + "ntfyAuthTokenHint": "Wymagany tylko dla prywatnych tematów z kontrolą dostępu.", + "webPush": "Push przeglądarki", + "webPushDescription": "Otrzymuj powiadomienia bezpośrednio w przeglądarce, nawet gdy HealthLog nie jest otwarty.", + "webPushNotSupported": "Twoja przeglądarka nie obsługuje powiadomień push.", + "webPushDenied": "Powiadomienia push są zablokowane. Zezwól na nie w ustawieniach przeglądarki.", + "webPushNotConfigured": "Web Push nie jest skonfigurowany na serwerze.", + "webPushSubscribe": "Włącz push", + "webPushUnsubscribe": "Wyłącz push", + "webPushSubscribed": "Powiadomienia push włączone!", + "webPushUnsubscribed": "Powiadomienia push wyłączone.", + "webPushSubscribeFailed": "Aktywacja nie powiodła się", "webPushActive": "Aktywne", - "kiInsightsDescription": "Optional: Save your OpenAI key for daily, automatic evaluations in Insights.", - "codexConnected": "ChatGPT connected successfully! Insights are now active.", - "codexDisconnected": "ChatGPT connection removed.", - "codexConnectionFailed": "ChatGPT connection failed. Please try again.", - "rawData": "Send raw data", - "rawDataOnDescription": "Aggregated metrics plus anonymized raw points from the last 30 days are sent. This includes per-metric measurements (for example weight, blood pressure, pulse) with time context so the provider can detect patterns, outliers, and correlations more reliably. Name, email, and direct account identifiers are not transmitted.", - "rawDataOffDescription": "Only summarized metrics are sent (for example averages, trends, minimum/maximum). No individual points and no exact time context. This is more privacy-preserving, but the provider can be less precise for short-term patterns and fluctuations.", - "rawDataWarning": "Raw mode enabled: the provider receives additional anonymized points from the last 30 days for higher analysis accuracy.", - "regenerateInsights": "Regenerate reports", - "regenerateSuccess": "Reports regenerated successfully", - "regenerateRateLimit": "Please wait — you've hit the hourly limit for analyses.", - "lastGeneratedAt": "Last generated", + "kiInsightsDescription": "Opcjonalnie: zapisz swój klucz OpenAI, aby uzyskać codzienne, automatyczne analizy w Insights.", + "codexConnected": "ChatGPT połączony pomyślnie! Insights jest teraz aktywny.", + "codexDisconnected": "Połączenie z ChatGPT usunięte.", + "codexConnectionFailed": "Połączenie z ChatGPT nie powiodło się. Spróbuj ponownie.", + "rawData": "Wysyłaj surowe dane", + "rawDataOnDescription": "Wysyłane są zagregowane metryki oraz zanonimizowane surowe punkty z ostatnich 30 dni. Obejmuje to pomiary dla poszczególnych metryk (np. waga, ciśnienie, tętno) z kontekstem czasowym, aby dostawca mógł rzetelniej wykrywać wzorce, wartości odstające i korelacje. Imię, adres e-mail i bezpośrednie identyfikatory konta nie są przesyłane.", + "rawDataOffDescription": "Wysyłane są tylko podsumowane metryki (np. średnie, trendy, minimum/maksimum). Bez pojedynczych punktów i bez dokładnego kontekstu czasowego. Lepiej chroni to prywatność, ale dostawca może być mniej precyzyjny w przypadku wzorców i krótkoterminowych wahań.", + "rawDataWarning": "Tryb surowych danych włączony: dostawca otrzymuje dodatkowe zanonimizowane punkty z ostatnich 30 dni dla dokładniejszej analizy.", + "regenerateInsights": "Wygeneruj raporty ponownie", + "regenerateSuccess": "Raporty wygenerowane ponownie", + "regenerateRateLimit": "Poczekaj — osiągnięto godzinny limit analiz.", + "lastGeneratedAt": "Ostatnio wygenerowano", "ai": { - "chatgptConnectedBadge": "ChatGPT connected", - "adminAiActiveBadge": "Admin provider active", - "connectionExpiredBadge": "Connection expired", - "connectedSince": "Connected since {when}.", - "deviceCodeHeading": "Finish connecting on chatgpt.com", - "deviceCodeStep1": "Open this link on any device:", - "deviceCodeStep2": "Enter this one-time code:", - "deviceCodeStep3": "Approve the connection — this page updates automatically.", + "chatgptConnectedBadge": "ChatGPT połączony", + "adminAiActiveBadge": "Aktywny dostawca administratora", + "connectionExpiredBadge": "Połączenie wygasło", + "connectedSince": "Połączono od {when}.", + "deviceCodeHeading": "Dokończ łączenie na chatgpt.com", + "deviceCodeStep1": "Otwórz ten link na dowolnym urządzeniu:", + "deviceCodeStep2": "Wprowadź ten jednorazowy kod:", + "deviceCodeStep3": "Zatwierdź połączenie — ta strona zaktualizuje się automatycznie.", "deviceCodeCopy": "Kopiuj", - "deviceCodeWaiting": "Waiting for approval…", + "deviceCodeWaiting": "Oczekiwanie na zatwierdzenie…", "deviceCodeCancel": "Anuluj", - "oauthNotConfigured": "ChatGPT OAuth is not configured on this instance — use your own API key below instead.", + "oauthNotConfigured": "OAuth ChatGPT nie jest skonfigurowany w tej instancji — użyj zamiast tego własnego klucza API poniżej.", "modelLabel": "Model", - "modelOptionDefault": "— Default —", - "modelOptionCustom": "Custom…", - "customModelLabel": "Custom model name", - "anthropicKeyLabel": "Anthropic API key", + "modelOptionDefault": "— Domyślny —", + "modelOptionCustom": "Niestandardowy…", + "customModelLabel": "Nazwa niestandardowego modelu", + "anthropicKeyLabel": "Klucz API Anthropic", "baseUrlLabel": "Base URL", - "localKeyLabel": "API key (optional)", - "savedPreview": "(saved {preview})", - "savedShort": "(saved)", + "localKeyLabel": "Klucz API (opcjonalny)", + "savedPreview": "(zapisano {preview})", + "savedShort": "(zapisano)", "saveCta": "Zapisz", "saved": "Zapisano", - "saveFailed": "Save failed", + "saveFailed": "Zapis nie powiódł się", "errorGeneric": "Błąd", "providerChain": { "types": { "codex": "ChatGPT (Codex)", - "openai": "OpenAI (your key)", + "openai": "OpenAI (Twój klucz)", "anthropic": "Anthropic (Claude)", - "local": "Local model", - "admin-openai": "Admin OpenAI" + "local": "Model lokalny", + "admin-openai": "OpenAI administratora" }, - "title": "Fallback chain", - "description": "If the primary provider fails, HealthLog walks the chain in order. Drag the rows to reorder, toggle the switch to disable a provider without removing it.", - "moveUp": "Move up", - "moveDown": "Move down", - "removeFromChain": "Remove from chain", - "addProvider": "Add provider", - "addNoneAvailable": "All providers already in chain", - "saveOrder": "Save chain order", - "saved": "Chain saved", - "saveFailed": "Saving the chain failed", - "resetDefaults": "Reset to defaults", - "resetConfirmTitle": "Reset chain to defaults?", - "resetConfirmBody": "The chain reverts to Codex → OpenAI → Anthropic → Local → Admin OpenAI. Your saved credentials are not touched." - }, - "activeProviderHeading": "Active provider", - "activeProviderBody": "Pick the provider you want to use first. The form below configures only the selected provider; the fallback chain at the bottom decides what happens if it fails.", - "activeProviderLabel": "Primary provider", - "providerConfigTitle": "Provider configuration", + "title": "Łańcuch zapasowy", + "description": "Jeśli główny dostawca zawiedzie, HealthLog przechodzi przez łańcuch po kolei. Przeciągaj wiersze, aby zmienić kolejność; użyj przełącznika, aby wyłączyć dostawcę bez usuwania go.", + "moveUp": "Przesuń w górę", + "moveDown": "Przesuń w dół", + "removeFromChain": "Usuń z łańcucha", + "addProvider": "Dodaj dostawcę", + "addNoneAvailable": "Wszyscy dostawcy są już w łańcuchu", + "saveOrder": "Zapisz kolejność łańcucha", + "saved": "Łańcuch zapisany", + "saveFailed": "Zapis łańcucha nie powiódł się", + "resetDefaults": "Przywróć domyślne", + "resetConfirmTitle": "Przywrócić domyślny łańcuch?", + "resetConfirmBody": "Łańcuch wraca do Codex → OpenAI → Anthropic → Lokalny → OpenAI administratora. Zapisane dane logowania pozostają nienaruszone." + }, + "activeProviderHeading": "Aktywny dostawca", + "activeProviderBody": "Wybierz dostawcę, którego chcesz użyć jako pierwszego. Formularz poniżej konfiguruje tylko wybranego dostawcę; łańcuch zapasowy na dole decyduje, co się stanie w razie awarii.", + "activeProviderLabel": "Główny dostawca", + "providerConfigTitle": "Konfiguracja dostawcy", "providerSelect": { - "codex": "ChatGPT account (Codex)", - "openai": "OpenAI (your API key)", + "codex": "Konto ChatGPT (Codex)", + "openai": "OpenAI (Twój klucz API)", "anthropic": "Anthropic (Claude)", - "local": "Local model (OpenAI-compatible)", - "admin-openai": "Admin-provided OpenAI" + "local": "Model lokalny (zgodny z OpenAI)", + "admin-openai": "OpenAI udostępniony przez administratora" }, "openai": { "modelSelect": "Model", - "modelOptionCustom": "Custom slug…", - "modelCustomLabel": "Custom model slug", + "modelOptionCustom": "Niestandardowy slug…", + "modelCustomLabel": "Slug niestandardowego modelu", "modelCustomPlaceholder": "gpt-5", - "baseUrlLabel": "Base URL (advanced)", + "baseUrlLabel": "Adres URL bazowy (zaawansowane)", "baseUrlPlaceholder": "https://api.openai.com/v1", - "baseUrlHelp": "Override only when using an OpenAI-compatible gateway. Leave blank for OpenAI itself.", - "showAdvanced": "Show advanced", - "hideAdvanced": "Hide advanced", - "apiKey": "API key", + "baseUrlHelp": "Nadpisuj tylko przy korzystaniu z bramy zgodnej z OpenAI. Pozostaw puste dla samego OpenAI.", + "showAdvanced": "Pokaż zaawansowane", + "hideAdvanced": "Ukryj zaawansowane", + "apiKey": "Klucz API", "apiKeyPlaceholder": "sk-…" }, "codex": { - "statusConnected": "Connected", - "statusDisconnected": "Not connected", - "statusExpired": "Connection expired", - "connectButton": "Connect with ChatGPT", - "disconnectButton": "Disconnect", - "modelSlugLabel": "Model slug", - "modelSlugBody": "Codex uses the model your ChatGPT subscription routes to. The CODEX_MODEL environment variable lets ops override this on the instance.", - "lastInsight": "Last insight: {when}" + "statusConnected": "Połączono", + "statusDisconnected": "Niepołączono", + "statusExpired": "Połączenie wygasło", + "connectButton": "Połącz z ChatGPT", + "disconnectButton": "Rozłącz", + "modelSlugLabel": "Slug modelu", + "modelSlugBody": "Codex używa modelu, do którego kieruje Twoja subskrypcja ChatGPT. Zmienna środowiskowa CODEX_MODEL pozwala operatorowi nadpisać to w instancji.", + "lastInsight": "Ostatnia analiza: {when}" }, "adminOpenai": { - "title": "Admin OpenAI", - "body": "Operator-provided OpenAI key. Used as a last-ditch fallback when no personal provider is configured. There is nothing to configure on this row — visibility means the operator has set it up.", - "notConfigured": "The operator has not configured a shared OpenAI key on this instance." + "title": "OpenAI administratora", + "body": "Klucz OpenAI udostępniony przez operatora. Używany jako ostateczność, gdy nie skonfigurowano żadnego osobistego dostawcy. W tym wierszu nie ma nic do skonfigurowania — jego widoczność oznacza, że operator go skonfigurował.", + "notConfigured": "Operator nie skonfigurował współdzielonego klucza OpenAI w tej instancji." }, - "testProvider": "Test active provider", + "testProvider": "Przetestuj aktywnego dostawcę", "testSuccess": "OK — {provider} ({model})", - "testFailedShort": "Test failed: {message}", - "testUnexpectedResponse": "AI provider connection failed — unexpected response from the server.", - "testReasonCredentials": "Provider rejected the credentials — re-authenticate in AI settings.", - "testReasonRateLimited": "Provider rate-limited the request — try again shortly.", - "testReasonServerError": "The AI provider returned a server error.", - "testReasonUnreachable": "Could not reach the AI provider.", + "testFailedShort": "Test nie powiódł się: {message}", + "testUnexpectedResponse": "Połączenie z dostawcą AI nie powiodło się — nieoczekiwana odpowiedź serwera.", + "testReasonCredentials": "Dostawca odrzucił dane logowania — uwierzytelnij się ponownie w ustawieniach AI.", + "testReasonRateLimited": "Dostawca ograniczył żądanie — spróbuj ponownie za chwilę.", + "testReasonServerError": "Dostawca AI zwrócił błąd serwera.", + "testReasonUnreachable": "Nie udało się połączyć z dostawcą AI.", "coachMemory": { "title": "Co Coach pamięta", "description": "Trwałe informacje, które przekazałeś Coachowi. Wykorzystuje je, aby jego odpowiedzi pozostawały trafne. Usuń wszystko, o czym wolisz, by zapomniał.", @@ -3380,28 +3394,28 @@ } }, "withings": "Withings", - "withingsDescription": "Connect your Withings scale and blood pressure monitors.", - "withingsCredentials": "API Credentials", - "withingsClientId": "Client ID", - "withingsClientSecret": "Client Secret", - "withingsCredentialsSaved": "Credentials saved", - "withingsCredentialsSavedPlaceholder": "Saved — enter new to replace", - "withingsCredentialsSavedPlaceholderSecret": "Saved — enter new to replace", - "withingsSaveCredentials": "Save credentials", - "configured": "Configured", - "withingsSync": "Sync now", - "withingsFullSync": "Sync all data", - "withingsFullSyncTitle": "Full synchronization?", - "withingsFullSyncDescription": "All available Withings data will be fully synchronized. This may take some time depending on your history.", - "withingsSyncResult": "{count} measurements synchronized", - "withingsFullSyncResult": "{count} measurements fully synchronized", - "withingsSyncFailed": "Sync failed", - "withingsSynchronize": "Synchronize", - "withingsDisconnect": "Disconnect", - "withingsDisconnectTitle": "Disconnect Withings?", - "withingsDisconnectDescription": "The connection to Withings will be disconnected. Previously synced data will be preserved.", - "withingsConnect": "Connect with Withings", - "withingsNoCredentials": "Please enter your API credentials above to connect Withings.", + "withingsDescription": "Połącz swoją wagę i ciśnieniomierze Withings.", + "withingsCredentials": "Dane logowania API", + "withingsClientId": "ID klienta", + "withingsClientSecret": "Sekret klienta", + "withingsCredentialsSaved": "Dane logowania zapisane", + "withingsCredentialsSavedPlaceholder": "Zapisano — wprowadź nowy, aby zastąpić", + "withingsCredentialsSavedPlaceholderSecret": "Zapisano — wprowadź nowy, aby zastąpić", + "withingsSaveCredentials": "Zapisz dane logowania", + "configured": "Skonfigurowano", + "withingsSync": "Synchronizuj teraz", + "withingsFullSync": "Synchronizuj wszystkie dane", + "withingsFullSyncTitle": "Pełna synchronizacja?", + "withingsFullSyncDescription": "Wszystkie dostępne dane Withings zostaną w pełni zsynchronizowane. Może to chwilę potrwać w zależności od historii.", + "withingsSyncResult": "{count} pomiarów zsynchronizowano", + "withingsFullSyncResult": "{count} pomiarów w pełni zsynchronizowano", + "withingsSyncFailed": "Synchronizacja nie powiodła się", + "withingsSynchronize": "Synchronizuj", + "withingsDisconnect": "Rozłącz", + "withingsDisconnectTitle": "Rozłączyć Withings?", + "withingsDisconnectDescription": "Połączenie z Withings zostanie rozłączone. Wcześniej zsynchronizowane dane zostaną zachowane.", + "withingsConnect": "Połącz z Withings", + "withingsNoCredentials": "Najpierw wprowadź powyżej dane logowania API, aby połączyć Withings.", "whoop": "WHOOP", "whoopDescription": "Połącz opaskę WHOOP, aby synchronizować regenerację, sen, obciążenie i treningi.", "whoopOverlapNote": "Jeśli WHOOP i inne źródło dostarczają tę samą wartość życiową — tętno spoczynkowe, saturację, temperaturę ciała, częstość oddechów lub fazy snu — możesz widzieć obie wartości, dopóki przyszła aktualizacja nie wybierze jednego preferowanego źródła.", @@ -3430,9 +3444,9 @@ "withings": { "reconnect": { "banner": { - "title": "Activity sync available", - "body": "Your Withings connection was authorised before activity sync was added. Reconnect once to enable steps, active energy, walking + running distance, and floors-climbed ingest from your Withings devices.", - "action": "Reconnect Withings" + "title": "Dostępna synchronizacja aktywności", + "body": "Twoje połączenie z Withings zostało autoryzowane przed dodaniem synchronizacji aktywności. Połącz ponownie jeden raz, aby włączyć import kroków, energii aktywnej, dystansu marszu i biegu oraz pokonanych pięter z urządzeń Withings.", + "action": "Połącz ponownie Withings" } } } @@ -3443,132 +3457,133 @@ "warningServerError": "Połączono, błąd serwera", "parkedReconnect": "Wstrzymane — połącz ręcznie", "notConnected": "Nie połączono", - "justNow": "just now", - "minutesAgo": "{count} min ago", - "hoursAgo": "{count} h ago", - "daysAgo": "{count} d ago", - "ariaLabel": "Integration status", + "justNow": "przed chwilą", + "minutesAgo": "{count} min temu", + "hoursAgo": "{count} godz. temu", + "daysAgo": "{count} dni temu", + "ariaLabel": "Status integracji", "resumeCta": "Połącz ponownie", "resumeSuccess": "Integracja wznowiona", "resumeError": "Ponowne połączenie nieudane" }, - "apiTokens": "API Tokens", - "apiTokensDescription": "Create API tokens for external medication intake (e.g., Shortcuts, automations).", - "tokenNamePlaceholder": "Token name (e.g., iPhone Shortcut)", - "tokenCreated": "Token created — copy it now! It won't be shown again.", - "tokenRevoke": "Revoke token?", - "tokenRevokeDescription": "The token will be permanently deactivated. Applications using it will lose access.", - "tokenRevoked": "Revoked", - "tokenExpired": "Expired", + "apiTokens": "Tokeny API", + "apiTokensDescription": "Twórz tokeny API do zewnętrznego rejestrowania przyjęć leków (np. Skróty, automatyzacje).", + "tokenNamePlaceholder": "Nazwa tokena (np. Skrót iPhone)", + "tokenCreated": "Token utworzony — skopiuj go teraz! Nie zostanie pokazany ponownie.", + "tokenRevoke": "Cofnąć token?", + "tokenRevokeAction": "Unieważnij token", + "tokenRevokeDescription": "Token zostanie trwale dezaktywowany. Aplikacje, które go używają, stracą dostęp.", + "tokenRevoked": "Cofnięto", + "tokenExpired": "Wygasł", "tokenActive": "Aktywne", - "activeTokensTitle": "Active tokens", - "revokedTokensTitle": "Revoked tokens ({count})", + "activeTokensTitle": "Aktywne tokeny", + "revokedTokensTitle": "Cofnięte tokeny ({count})", "tokenTableName": "Nazwa", - "tokenTablePermissions": "Permissions", + "tokenTablePermissions": "Uprawnienia", "tokenTableStatus": "Status", - "tokenTableCreated": "Created", - "tokenTableLastUsed": "Last used", + "tokenTableCreated": "Utworzono", + "tokenTableLastUsed": "Ostatnio użyto", "tokenTableActions": "Akcje", - "noActiveTokens": "No active tokens available.", - "tokenNeverUsed": "Never", - "apiEndpointsTitle": "API endpoints", - "apiEndpointsDescription": "Overview of currently available external endpoint(s) for token-based ingestion.", - "apiEndpointMethod": "Method", - "apiEndpointPath": "Path", - "apiEndpointAuth": "Authentication", - "apiEndpointExample": "Body example", - "collapse": "Collapse", - "expand": "Expand", - "dangerZone": "Delete All Data", - "dangerZoneTitle": "Danger zone", - "dangerZoneDescription": "Deletes all your health data and integrations. Your user account will be preserved.", - "dangerZoneConfirm": "Delete all data?", - "dangerZoneConfirmDescription": "This will permanently delete all your health data and integrations. Your user account will be preserved.", - "dangerZoneSuccess": "All personal data has been deleted", - "dangerZoneDeleteFailed": "Deletion failed", - "finalDelete": "Delete permanently", - "deleteAccountCardTitle": "Delete account entirely", - "deleteAccountCardDescription": "Deletes your account along with passkeys, audit log, and sessions. This cannot be undone.", - "deleteAccountCta": "Delete account", - "deleteAccountConfirmTitle": "Delete account permanently?", - "deleteAccountConfirmDescription": "Your account, all health data, passkeys, sessions, and audit entries will be permanently deleted. You will be signed out immediately after.", - "deleteAccountSuccess": "Account deleted — signing you out.", - "deleteAccountFailed": "Account could not be deleted", - "deleteAccountFinal": "Delete permanently", + "noActiveTokens": "Brak aktywnych tokenów.", + "tokenNeverUsed": "Nigdy", + "apiEndpointsTitle": "Punkty końcowe API", + "apiEndpointsDescription": "Przegląd dostępnych obecnie zewnętrznych punktów końcowych do wprowadzania danych za pomocą tokenów.", + "apiEndpointMethod": "Metoda", + "apiEndpointPath": "Ścieżka", + "apiEndpointAuth": "Uwierzytelnianie", + "apiEndpointExample": "Przykład treści", + "collapse": "Zwiń", + "expand": "Rozwiń", + "dangerZone": "Usuń wszystkie dane", + "dangerZoneTitle": "Strefa zagrożenia", + "dangerZoneDescription": "Usuwa wszystkie Twoje dane zdrowotne i integracje. Twoje konto użytkownika zostanie zachowane.", + "dangerZoneConfirm": "Usunąć wszystkie dane?", + "dangerZoneConfirmDescription": "Spowoduje to trwałe usunięcie wszystkich Twoich danych zdrowotnych i integracji. Twoje konto użytkownika zostanie zachowane.", + "dangerZoneSuccess": "Wszystkie dane osobowe zostały usunięte", + "dangerZoneDeleteFailed": "Usuwanie nie powiodło się", + "finalDelete": "Usuń trwale", + "deleteAccountCardTitle": "Usuń całe konto", + "deleteAccountCardDescription": "Usuwa Twoje konto wraz z kluczami dostępu, dziennikiem audytu i sesjami. Tej operacji nie można cofnąć.", + "deleteAccountCta": "Usuń konto", + "deleteAccountConfirmTitle": "Trwale usunąć konto?", + "deleteAccountConfirmDescription": "Twoje konto, wszystkie dane zdrowotne, klucze dostępu, sesje i wpisy audytu zostaną trwale usunięte. Zostaniesz wylogowany natychmiast po tym.", + "deleteAccountSuccess": "Konto usunięte — trwa wylogowywanie.", + "deleteAccountFailed": "Nie udało się usunąć konta", + "deleteAccountFinal": "Usuń trwale", "saved": "Zapisano", - "savingError": "Error saving", + "savingError": "Błąd podczas zapisywania", "moodLogTitle": "moodLog", "moodLogDescription": "Import mood data from moodLog", "moodLogUrl": "moodLog URL", "moodLogUrlPlaceholder": "https://mood.example.com", - "moodLogApiKey": "API Key", + "moodLogApiKey": "Klucz API", "moodLogApiKeyPlaceholder": "ml_...", - "moodLogWebhookSecret": "Webhook Secret", - "moodLogWebhookSecretHelp": "Enter this secret in moodLog as webhook secret", - "moodLogSync": "Start sync", - "moodLogFullSync": "Full sync", - "moodLogFullSyncConfirm": "Synchronize fully", - "moodLogFullSyncTitle": "Full sync?", - "moodLogFullSyncDescription": "All available mood data will be fully synchronized.", - "moodLogDisconnect": "Disconnect", - "moodLogDisconnectTitle": "Disconnect moodLog?", - "moodLogDisconnectDescription": "The connection will be removed and all imported mood data will be deleted.", - "moodLogEntries": "Entries", - "moodLogSyncResult": "{count} entries synchronized", - "moodLogSyncFailed": "Sync failed", - "moodLogSaved": "moodLog connection saved", - "moodLogDisconnected": "moodLog disconnected", + "moodLogWebhookSecret": "Sekret webhooka", + "moodLogWebhookSecretHelp": "Wprowadź ten sekret w moodLog jako sekret webhooka", + "moodLogSync": "Rozpocznij synchronizację", + "moodLogFullSync": "Pełna synchronizacja", + "moodLogFullSyncConfirm": "Synchronizuj w pełni", + "moodLogFullSyncTitle": "Pełna synchronizacja?", + "moodLogFullSyncDescription": "Wszystkie dostępne dane o nastroju zostaną w pełni zsynchronizowane.", + "moodLogDisconnect": "Rozłącz", + "moodLogDisconnectTitle": "Rozłączyć moodLog?", + "moodLogDisconnectDescription": "Połączenie zostanie usunięte, a wszystkie zaimportowane dane o nastroju zostaną skasowane.", + "moodLogEntries": "Wpisy", + "moodLogSyncResult": "{count} wpisów zsynchronizowano", + "moodLogSyncFailed": "Synchronizacja nie powiodła się", + "moodLogSaved": "Połączenie moodLog zapisane", + "moodLogDisconnected": "moodLog rozłączony", "about": { - "version": "App version", + "version": "Wersja aplikacji", "gitSha": "Build", - "builtAt": "Built {time}", - "builtAtLabel": "Built", - "license": "License", - "repository": "Source code", + "builtAt": "Skompilowano {time}", + "builtAtLabel": "Skompilowano", + "license": "Licencja", + "repository": "Kod źródłowy", "changelog": "Changelog", - "docs": "Documentation", + "docs": "Dokumentacja", "linksHeading": "Źródła i dokumentacja", "newerAvailable": "Nowa wersja: {tag}", "tourReplay": "Obejrzyj samouczek", "tourReplayHint": "Zobacz przewodnik po panelu — przydatne, jeśli pominąłeś go przy pierwszej wizycie." }, "testConnection": { - "test": "Test connection", - "testing": "Testing…", - "ok": "Connected (latency {latency} ms)", + "test": "Testuj połączenie", + "testing": "Testowanie…", + "ok": "Połączono (opóźnienie {latency} ms)", "errors": { - "credentials_rejected": "Credentials rejected — check your token", - "rate_limited": "Rate-limited by the upstream", - "timeout": "Request timed out", - "upstream_error": "Upstream returned an error", - "connection_failed": "Connection failed", - "not_configured": "Not configured", - "url_not_public": "URL is not a public endpoint", - "url_invalid": "URL is invalid", - "redirected": "Endpoint redirects — check the URL", - "endpoint_not_found": "Endpoint not found at the URL", - "credentials_unreadable": "Credentials cannot be decrypted", - "upstream_invalid_json": "Upstream sent invalid JSON", - "vapid_not_configured": "Web Push not configured (VAPID keys missing)", - "rate_limited_self": "Too many test requests", - "generic": "Test failed" + "credentials_rejected": "Dane logowania odrzucone — sprawdź token", + "rate_limited": "Ograniczone przez usługę nadrzędną", + "timeout": "Przekroczono limit czasu żądania", + "upstream_error": "Usługa nadrzędna zwróciła błąd", + "connection_failed": "Połączenie nie powiodło się", + "not_configured": "Nieskonfigurowano", + "url_not_public": "Adres URL nie jest publicznym punktem końcowym", + "url_invalid": "Adres URL jest nieprawidłowy", + "redirected": "Punkt końcowy przekierowuje — sprawdź adres URL", + "endpoint_not_found": "Nie znaleziono punktu końcowego pod adresem URL", + "credentials_unreadable": "Nie można odszyfrować danych logowania", + "upstream_invalid_json": "Usługa nadrzędna wysłała nieprawidłowy JSON", + "vapid_not_configured": "Web Push nieskonfigurowany (brak kluczy VAPID)", + "rate_limited_self": "Zbyt wiele żądań testowych", + "generic": "Test nie powiódł się" } }, "notificationStatus": { "title": "Channel reliability", "description": "Live status of every notification channel — Auto-disabled means HealthLog stopped retrying after a permanent error.", - "emptyDescription": "No channels configured yet. Add a channel below to start tracking its delivery health.", + "emptyDescription": "Nie skonfigurowano jeszcze żadnych kanałów. Dodaj kanał poniżej, aby zacząć śledzić jego dostarczalność.", "stateActive": "Aktywne", - "stateAutoDisabled": "Auto-disabled", - "stateSendingPaused": "Sending paused", + "stateAutoDisabled": "Automatycznie wyłączone", + "stateSendingPaused": "Wysyłanie wstrzymane", "stateManuallyDisabled": "Wyłączone", - "lastSuccess": "Last successful send", - "lastFailure": "Last failure", - "consecutiveFailures": "Consecutive failures", - "disabledReason": "Reason", - "nextRetry": "Next retry", - "reEnable": "Re-enable", - "sendTest": "Send test" + "lastSuccess": "Ostatnia udana wysyłka", + "lastFailure": "Ostatni błąd", + "consecutiveFailures": "Kolejne błędy", + "disabledReason": "Powód", + "nextRetry": "Następna próba", + "reEnable": "Włącz ponownie", + "sendTest": "Wyślij test" }, "identity": { "description": "Opcjonalne dane do eksportu dokumentacji zdrowotnej (okładka PDF + eksport FHIR). Wszystkie pola są opcjonalne.", @@ -4108,284 +4123,284 @@ "carrierUnavailableGeoFallback": "{location} — carrier unavailable" }, "achievements": { - "title": "Achievements", - "subtitle": "Earn achievements by tracking your health and taking your meds on time.", - "loginRequired": "Please sign in to view achievements.", - "points": "Points", - "unlocked": "Unlocked", - "nextGoal": "Next goal", - "allCompleted": "All achievements unlocked!", - "completed": "Completed", - "goalReached": "Goal reached", - "remainingUnlocks": "{count} still unlockable", - "noneUnlockedYet": "No achievements unlocked yet.", - "pointsValue": "{points} points", + "title": "Osiągnięcia", + "subtitle": "Zdobywaj osiągnięcia, śledząc swoje zdrowie i przyjmując leki na czas.", + "loginRequired": "Zaloguj się, aby zobaczyć osiągnięcia.", + "points": "Punkty", + "unlocked": "Odblokowano", + "nextGoal": "Następny cel", + "allCompleted": "Wszystkie osiągnięcia odblokowane!", + "completed": "Ukończono", + "goalReached": "Cel osiągnięty", + "remainingUnlocks": "Jeszcze {count} do odblokowania", + "noneUnlockedYet": "Nie odblokowano jeszcze żadnego osiągnięcia.", + "pointsValue": "{points} punktów", "metricCount": "{count}", - "metricDays": "{count} days", + "metricDays": "{count} dni", "metricPercent": "{count}%", "badges": { "intakeTotal1": { - "title": "First intake", - "description": "Record your first taken medication event." + "title": "Pierwsze przyjęcie", + "description": "Zarejestruj swoje pierwsze przyjęcie leku." }, "intakeTotal10": { - "title": "Intake starter", - "description": "Record 10 taken medication events." + "title": "Początkujący w przyjęciach", + "description": "Zarejestruj 10 przyjęć leków." }, "intakeTotal50": { - "title": "Intake routine", - "description": "Record 50 taken medication events." + "title": "Rutyna przyjęć", + "description": "Zarejestruj 50 przyjęć leków." }, "intakeTotal150": { - "title": "Intake expert", - "description": "Record 150 taken medication events." + "title": "Ekspert przyjęć", + "description": "Zarejestruj 150 przyjęć leków." }, "intakeTotal300": { - "title": "Intake legend", - "description": "Record 300 taken medication events." + "title": "Legenda przyjęć", + "description": "Zarejestruj 300 przyjęć leków." }, "overIntake1": { - "title": "Double take", - "description": "Take medication at least once more than planned." + "title": "Podwójna dawka", + "description": "Przyjmij lek co najmniej raz więcej niż zaplanowano." }, "skippedIntake1": { - "title": "Stepped back", - "description": "Skip at least one planned intake." + "title": "Krok wstecz", + "description": "Pomiń co najmniej jedno zaplanowane przyjęcie." }, "passkeyCreated1": { - "title": "Passkey set up", - "description": "Create your first passkey." + "title": "Klucz dostępu ustawiony", + "description": "Utwórz swój pierwszy klucz dostępu." }, "passkeyLogin1": { - "title": "Passkey login", - "description": "Sign in at least once with a passkey." + "title": "Logowanie kluczem dostępu", + "description": "Zaloguj się co najmniej raz kluczem dostępu." }, "passwordLogin1": { - "title": "Old school", - "description": "Sign in at least once with username and password." + "title": "Stara szkoła", + "description": "Zaloguj się co najmniej raz nazwą użytkownika i hasłem." }, "bugReport1": { - "title": "Bug hunter", - "description": "Submit a bug report." + "title": "Łowca błędów", + "description": "Wyślij zgłoszenie błędu." }, "loginStreak7": { - "title": "Login streak 7d", - "description": "Sign in on 7 consecutive days." + "title": "Seria logowań 7d", + "description": "Zaloguj się przez 7 kolejnych dni." }, "loginStreak30": { - "title": "Login streak 30d", - "description": "Sign in on 30 consecutive days." + "title": "Seria logowań 30d", + "description": "Zaloguj się przez 30 kolejnych dni." }, "onTimePerfect1": { - "title": "On-time 1d", - "description": "Take all medications on time for 1 day." + "title": "Na czas 1d", + "description": "Przyjmij wszystkie leki na czas przez 1 dzień." }, "compliance801": { - "title": "80% adherence · 1d", - "description": "Reach at least 80% 30-day adherence for 1 day." + "title": "Przestrzeganie 80% · 1d", + "description": "Osiągnij co najmniej 80% przestrzegania w 30 dni przez 1 dzień." }, "bmiGreen1": { - "title": "BMI green · 1d", - "description": "Keep your BMI in the normal range for 1 day." + "title": "BMI na zielono · 1d", + "description": "Utrzymaj BMI w normie przez 1 dzień." }, "bpGreen1": { - "title": "BP green · 1d", - "description": "Keep your blood pressure in the normal range for 1 day." + "title": "Ciśnienie na zielono · 1d", + "description": "Utrzymaj ciśnienie w normie przez 1 dzień." }, "pulseGreen1": { - "title": "Pulse green · 1d", - "description": "Keep your resting pulse in the normal range for 1 day." + "title": "Tętno na zielono · 1d", + "description": "Utrzymaj tętno spoczynkowe w normie przez 1 dzień." }, "onTimePerfect7": { - "title": "On-time 7d", - "description": "Take all medications on time for 7 consecutive days." + "title": "Na czas 7d", + "description": "Przyjmij wszystkie leki na czas przez 7 kolejnych dni." }, "compliance807": { - "title": "80% adherence · 7d", - "description": "Reach at least 80% 30-day adherence for 7 consecutive days." + "title": "Przestrzeganie 80% · 7d", + "description": "Osiągnij co najmniej 80% przestrzegania w 30 dni przez 7 kolejnych dni." }, "bmiGreen7": { - "title": "BMI green · 7d", - "description": "Keep your BMI in the normal range for 7 consecutive days." + "title": "BMI na zielono · 7d", + "description": "Utrzymaj BMI w normie przez 7 kolejnych dni." }, "bpGreen7": { - "title": "BP green · 7d", - "description": "Keep your blood pressure in the normal range for 7 consecutive days." + "title": "Ciśnienie na zielono · 7d", + "description": "Utrzymaj ciśnienie w normie przez 7 kolejnych dni." }, "pulseGreen7": { - "title": "Pulse green · 7d", - "description": "Keep your resting pulse in the normal range for 7 consecutive days." + "title": "Tętno na zielono · 7d", + "description": "Utrzymaj tętno spoczynkowe w normie przez 7 kolejnych dni." }, "onTimePerfect30": { - "title": "On-time 30d", - "description": "Take all medications on time for 30 consecutive days." + "title": "Na czas 30d", + "description": "Przyjmij wszystkie leki na czas przez 30 kolejnych dni." }, "compliance8030": { - "title": "80% adherence · 30d", - "description": "Reach at least 80% 30-day adherence for 30 consecutive days." + "title": "Przestrzeganie 80% · 30d", + "description": "Osiągnij co najmniej 80% przestrzegania w 30 dni przez 30 kolejnych dni." }, "bmiGreen30": { - "title": "BMI green · 30d", - "description": "Keep your BMI in the normal range for 30 consecutive days." + "title": "BMI na zielono · 30d", + "description": "Utrzymaj BMI w normie przez 30 kolejnych dni." }, "bpGreen30": { - "title": "BP green · 30d", - "description": "Keep your blood pressure in the normal range for 30 consecutive days." + "title": "Ciśnienie na zielono · 30d", + "description": "Utrzymaj ciśnienie w normie przez 30 kolejnych dni." }, "pulseGreen30": { - "title": "Pulse green · 30d", - "description": "Keep your resting pulse in the normal range for 30 consecutive days." + "title": "Tętno na zielono · 30d", + "description": "Utrzymaj tętno spoczynkowe w normie przez 30 kolejnych dni." }, "onTimePerfect180": { - "title": "On-time 180d", - "description": "Take all medications on time for 180 consecutive days." + "title": "Na czas 180d", + "description": "Przyjmij wszystkie leki na czas przez 180 kolejnych dni." }, "compliance80180": { - "title": "80% adherence · 180d", - "description": "Reach at least 80% 30-day adherence for 180 consecutive days." + "title": "Przestrzeganie 80% · 180d", + "description": "Osiągnij co najmniej 80% przestrzegania w 30 dni przez 180 kolejnych dni." }, "bmiGreen180": { - "title": "BMI green · 180d", - "description": "Keep your BMI in the normal range for 180 consecutive days." + "title": "BMI na zielono · 180d", + "description": "Utrzymaj BMI w normie przez 180 kolejnych dni." }, "bpGreen180": { - "title": "BP green · 180d", - "description": "Keep your blood pressure in the normal range for 180 consecutive days." + "title": "Ciśnienie na zielono · 180d", + "description": "Utrzymaj ciśnienie w normie przez 180 kolejnych dni." }, "pulseGreen180": { - "title": "Pulse green · 180d", - "description": "Keep your resting pulse in the normal range for 180 consecutive days." + "title": "Tętno na zielono · 180d", + "description": "Utrzymaj tętno spoczynkowe w normie przez 180 kolejnych dni." }, "onTimePerfect360": { - "title": "On-time 360d", - "description": "Take all medications on time for 360 consecutive days." + "title": "Na czas 360d", + "description": "Przyjmij wszystkie leki na czas przez 360 kolejnych dni." }, "compliance80360": { - "title": "80% adherence · 360d", - "description": "Reach at least 80% 30-day adherence for 360 consecutive days." + "title": "Przestrzeganie 80% · 360d", + "description": "Osiągnij co najmniej 80% przestrzegania w 30 dni przez 360 kolejnych dni." }, "bmiGreen360": { - "title": "BMI green · 360d", - "description": "Keep your BMI in the normal range for 360 consecutive days." + "title": "BMI na zielono · 360d", + "description": "Utrzymaj BMI w normie przez 360 kolejnych dni." }, "bpGreen360": { - "title": "BP green · 360d", - "description": "Keep your blood pressure in the normal range for 360 consecutive days." + "title": "Ciśnienie na zielono · 360d", + "description": "Utrzymaj ciśnienie w normie przez 360 kolejnych dni." }, "pulseGreen360": { - "title": "Pulse green · 360d", - "description": "Keep your resting pulse in the normal range for 360 consecutive days." + "title": "Tętno na zielono · 360d", + "description": "Utrzymaj tętno spoczynkowe w normie przez 360 kolejnych dni." }, "moodFirst": { - "title": "First mood", - "description": "Log your first mood entry." + "title": "Pierwszy nastrój", + "description": "Zapisz swój pierwszy wpis nastroju." }, "moodStreak7": { - "title": "Mood diarist 7d", - "description": "Log a mood entry on 7 consecutive days." + "title": "Dziennik nastroju 7d", + "description": "Zapisuj nastrój przez 7 kolejnych dni." }, "moodStreak30": { - "title": "Mood diarist 30d", - "description": "Log a mood entry on 30 consecutive days." + "title": "Dziennik nastroju 30d", + "description": "Zapisuj nastrój przez 30 kolejnych dni." }, "moodUp7": { - "title": "Brighter week", - "description": "Your 7-day mood average improved by at least 1.0 point compared to the previous week." + "title": "Pogodniejszy tydzień", + "description": "Twoja 7-dniowa średnia nastroju poprawiła się o co najmniej 1,0 punkt względem poprzedniego tygodnia." }, "weightFirst": { - "title": "First weigh-in", - "description": "Record your first weight measurement." + "title": "Pierwsze ważenie", + "description": "Zapisz swój pierwszy pomiar wagi." }, "weight50": { - "title": "Fifty weigh-ins", - "description": "Record 50 weight measurements." + "title": "Pięćdziesiąt ważeń", + "description": "Zapisz 50 pomiarów wagi." }, "weight200": { - "title": "200 weigh-ins", - "description": "Record 200 weight measurements." + "title": "200 ważeń", + "description": "Zapisz 200 pomiarów wagi." }, "bpFirst": { - "title": "First reading", - "description": "Record your first blood-pressure measurement." + "title": "Pierwszy pomiar", + "description": "Zapisz swój pierwszy pomiar ciśnienia." }, "bp50": { - "title": "Fifty BP readings", - "description": "Record 50 blood-pressure measurements." + "title": "Pięćdziesiąt pomiarów ciśnienia", + "description": "Zapisz 50 pomiarów ciśnienia." }, "bp200": { - "title": "200 BP readings", - "description": "Record 200 blood-pressure measurements." + "title": "200 pomiarów ciśnienia", + "description": "Zapisz 200 pomiarów ciśnienia." }, "pulseFirst": { - "title": "First pulse", - "description": "Record your first pulse measurement." + "title": "Pierwsze tętno", + "description": "Zapisz swój pierwszy pomiar tętna." }, "consistentMonth": { - "title": "Consistent month", - "description": "Log entries on at least 25 distinct days within a single calendar month." + "title": "Konsekwentny miesiąc", + "description": "Zapisz wpisy w co najmniej 25 różnych dniach w jednym miesiącu kalendarzowym." }, "entryStreak7": { - "title": "Tracker streak 7d", - "description": "Log at least one entry on 7 consecutive days." + "title": "Seria wpisów 7d", + "description": "Zapisuj co najmniej jeden wpis przez 7 kolejnych dni." }, "entryStreak30": { - "title": "Tracker streak 30d", - "description": "Log at least one entry on 30 consecutive days." + "title": "Seria wpisów 30d", + "description": "Zapisuj co najmniej jeden wpis przez 30 kolejnych dni." }, "weekendWarrior": { - "title": "Weekend tracker", - "description": "Log entries on 4 consecutive Saturday + Sunday weekend pairs." + "title": "Weekendowy rejestrator", + "description": "Zapisuj wpisy w 4 kolejnych weekendach (sobota + niedziela)." }, "hiddenNightOwl": { - "title": "Night owl", - "description": "Logged an entry between 02:00 and 04:00 in the morning." + "title": "Nocny marek", + "description": "Zapisałeś wpis między 02:00 a 04:00 nad ranem." }, "hiddenEarlyBird": { - "title": "Early bird", - "description": "Logged an entry between 04:00 and 06:00 in the morning." + "title": "Ranny ptaszek", + "description": "Zapisałeś wpis między 04:00 a 06:00 rano." }, "hiddenLeapDay": { - "title": "Leap-day legend", - "description": "Logged an entry on February 29." + "title": "Legenda dnia przestępnego", + "description": "Zapisałeś wpis 29 lutego." }, "hiddenDoctorPdf": { - "title": "House call", - "description": "Exported your first doctor-report PDF." + "title": "Wizyta domowa", + "description": "Wyeksportowałeś swój pierwszy raport dla lekarza w PDF." }, "hiddenLocaleFlip": { - "title": "Polyglot", - "description": "Switched the app language at least once." + "title": "Poliglota", + "description": "Zmieniłeś język aplikacji co najmniej raz." }, "hiddenBugBuddy": { - "title": "Bug buddy", - "description": "Submitted at least 5 bug reports — the project loves you." + "title": "Towarzysz błędów", + "description": "Wysłałeś co najmniej 5 zgłoszeń błędów — projekt Cię kocha." } }, - "nextProgressLabel": "Progress to unlock", - "completedOn": "Completed on {date}", - "locked": "Locked", + "nextProgressLabel": "Postęp do odblokowania", + "completedOn": "Ukończono {date}", + "locked": "Zablokowane", "criterionHint": "{current} / {target}", "progressPercent": "{percent}%", "categories": { "medication": "Lek", - "vitals": "Vitals", + "vitals": "Parametry życiowe", "mood": "Nastrój", - "security": "Account & security", + "security": "Konto i bezpieczeństwo", "engagement": "Engagement", - "hidden": "Hidden" + "hidden": "Ukryte" }, "hiddenCard": { - "title": "Hidden achievement", - "description": "Keep tracking — you might just stumble across it.", - "ariaLabel": "Hidden locked achievement" + "title": "Ukryte osiągnięcie", + "description": "Śledź dalej — może na nie natrafisz.", + "ariaLabel": "Ukryte zablokowane osiągnięcie" }, "hiddenUnlockToast": { - "title": "You unlocked a hidden achievement!" + "title": "Odblokowałeś ukryte osiągnięcie!" }, "dashboardCard": { - "title": "Recent unlocks", - "viewAll": "View all", - "empty": "No achievements yet — keep logging to unlock your first." + "title": "Ostatnio odblokowane", + "viewAll": "Zobacz wszystkie", + "empty": "Brak osiągnięć — zapisuj dalej, aby odblokować pierwsze." } }, "bugreport": { diff --git a/package.json b/package.json index 3dff3e73c..e094b3198 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "healthlog", - "version": "1.11.2", + "version": "1.11.3", "description": "Self-hosted personal-health-tracking PWA with Withings integration, AI insights, and doctor-report PDF export.", "license": "AGPL-3.0-only", "homepage": "https://healthlog.dev", diff --git a/src/app/insights/__tests__/page-skeletons.test.ts b/src/app/insights/__tests__/page-skeletons.test.ts index 017a434eb..1b225f1a2 100644 --- a/src/app/insights/__tests__/page-skeletons.test.ts +++ b/src/app/insights/__tests__/page-skeletons.test.ts @@ -4,51 +4,79 @@ import { join } from "node:path"; /** * v1.4.43 W11 — insights mother-page dynamic-skeleton guards. + * v1.11.3 — rewritten for the shared `BlockSkeleton` loader. * - * The three `next/dynamic` loading placeholders for - * `DailyBriefing` / `CorrelationRow` / `TrendsRow` used to reserve - * heights (`h-48` / `h-32` / `h-64`) noticeably shorter than the - * loaded content (~24 rem / ~20 rem each), which CLS-shifted the - * page on slow networks. They also lacked `motion-reduce:animate-none`, - * so motion-sensitive users saw a continuous pulse. + * The `next/dynamic` loading placeholders for the below-the-hero blocks + * used to be bespoke `
` snippets with hard-coded guessed + * heights. Those fixed heights pinned each placeholder taller or shorter + * than the resolved block, so the page CLS-shifted as each chunk landed. * - * This textual guard pins both the larger reserved heights and the - * motion-reduce class. The check is intentionally simple — render- - * mounting the page would haul in TanStack-Query / Auth / I18n - * scaffolding for a property a substring search already proves. + * E1 collapses all six loaders onto a single shared `BlockSkeleton` that + * routes through the `Skeleton` primitive (which carries + * `motion-reduce:animate-none`) and holds the row open with a `min-h` + * floor rather than a fixed `h-[Xrem]`. This guard pins the new contract: + * every loader uses `BlockSkeleton`, none re-introduces a hard-coded + * fixed height, and the decorative (un-mountable) cards stay `aria-hidden`. + * + * The check is intentionally textual — render-mounting the page would haul + * in TanStack-Query / Auth / I18n scaffolding for a property a substring + * search already proves. */ -describe("insights mother-page dynamic-skeleton heights + motion-reduce", () => { +describe("insights mother-page dynamic-skeleton loaders use the shared BlockSkeleton", () => { const src = readFileSync( join(process.cwd(), "src/app/insights/page.tsx"), "utf8", ); - it("DailyBriefing skeleton reserves h-[24rem]", () => { - expect(src).toMatch( - /DailyBriefing[\s\S]*?h-\[24rem\][^"]*motion-reduce:animate-none/, - ); + it("defines the shared BlockSkeleton helper", () => { + expect(src).toMatch(/function BlockSkeleton\(/); + // The helper renders the shared Skeleton primitive, which carries + // motion-reduce:animate-none, so every consumer inherits the guard. + expect(src).toMatch(/+ {t("insights.warmAssessmentsHint")} +