Files
ketopath/apps/web/messages/it.json
T
lucianoandClaude Opus 4.7 bb48357179 feat: web push notifications, mobile-ready (PRD §5.6)
ADR 0003: VAPID self-hosted today, DeviceToken model agnostic to
platform so iOS/Android (Expo/APNs/FCM) plug in as new senders later.

Backend (apps/api/src/modules/notifications)
- sender.ts: NotificationSender interface, WebPushSender via VAPID
- notifications.routes.ts: GET /me/notifications/config, POST/DELETE
  /me/device-tokens, PATCH /me/notifications/settings, POST /me/notifications/test
- scheduler.ts: node-cron Mon 09:00 Europe/Rome for weekly weigh-in
  reminder; auto-cleanup of expired tokens on 404/410
- env: VAPID_PUBLIC_KEY/PRIVATE_KEY/SUBJECT (all optional → push gracefully off)

Frontend
- public/sw.js minimal (push + notificationclick)
- lib/notifications/push-client.ts: subscribe / unsubscribe / getCurrentSubscription
- profile/notifications-{actions,panel}.tsx: editorial panel with toggles,
  device list, "send test", per-device removal
- pushReady requires both permission AND active subscription (covers the
  case where the user revoked the SW but kept the browser permission)

Schema
- DeviceToken { userId, platform, endpoint, p256dh, auth, token, userAgent,
  createdAt, lastSeenAt } with unique(userId, endpoint)
- ExtendedPrismaClient type exported from @ketopath/db
- NotificationSettings zod schema in @ketopath/shared

Tooling
- lint-staged: split .js out of eslint glob so service worker is only
  formatted (it lives outside the TS project)

i18n
- Notifications namespace (it) with typed error keys

Smoke tested: POST /me/device-tokens 201, POST /me/notifications/test 200,
real push delivered to a macOS Chrome device.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:58:35 +02:00

319 lines
13 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"Home": {
"title": "Una keto italiana, sostenibile",
"tagline": "Il sentiero che ti accompagna dalla prima settimana di adattamento al mantenimento — settimana dopo settimana, piatto dopo piatto.",
"ctaPrimary": "Iscriviti",
"ctaSecondary": "Accedi",
"disclaimer": "KetoPath è uno strumento di stile di vita: suggerisce, non prescrive. Le indicazioni nutrizionali non sostituiscono il parere del tuo medico.",
"signedInAs": "Accesso effettuato come {name}",
"signOut": "Esci",
"completeProfile": "Profilo & calcoli",
"viewPlan": "Piano della settimana",
"viewShopping": "Lista della spesa",
"viewTracking": "Pesata & misure",
"viewFasting": "Digiuno intermittente",
"lettura": "Continua la lettura",
"inizia": "Inizia",
"greeting": "Buongiorno, <name>{name}</name>.",
"principle1": "Personalizzazione totale: ciò che vedi è solo ciò che ti riguarda.",
"principle2": "Cucina italiana e mediterranea: ingredienti veri, di stagione, reperibili.",
"principle3": "Progressività: dalla fase intensiva al mantenimento, senza strappi."
},
"Welcome": {
"title": "Prima di iniziare",
"subtitle": "KetoPath è uno strumento di stile di vita, non un dispositivo medico. Quattro punti, prima di proseguire.",
"point1": "L'app fornisce solo suggerimenti di stile di vita basati sui parametri che inserisci.",
"point2": "Non diagnostica patologie, non prescrive trattamenti, non sostituisce il parere del tuo medico.",
"point3": "Se hai diabete di tipo 1, sei in gravidanza o allattamento, hai disturbi alimentari, problemi cardiaci o renali — parla prima con il tuo medico.",
"point4": "Prima di iniziare ti consigliamo esami di base: glicemia, colesterolo, profilo lipidico, funzionalità renale ed epatica.",
"checkAdult": "Confermo di essere maggiorenne.",
"checkAware": "Dichiaro di non avere condizioni mediche escludenti, oppure di averle discusse con il mio medico.",
"checkAccepts": "Accetto che KetoPath non sostituisce il parere medico professionale.",
"submit": "Accetto e proseguo",
"submitting": "Salvataggio…",
"legalNote": "Il consenso è registrato con data e ora come previsto dall'art. 7 GDPR."
},
"CookieBanner": {
"aria": "Informativa cookie",
"message": "Usiamo solo cookie essenziali per autenticazione e sessione. Niente analytics o tracker di terze parti.",
"dismiss": "Ho capito"
},
"Metadata": {
"title": "KetoPath — Chetogenica e digiuno intermittente, personalizzati",
"description": "Una keto italiana, sostenibile. Piani adattivi, tracking e lista della spesa automatica — settimana dopo settimana."
},
"Auth": {
"SignIn": {
"title": "Accedi",
"subtitle": "Bentornato. Riprendi da dove avevi lasciato.",
"emailLabel": "Email",
"passwordLabel": "Password",
"submit": "Accedi",
"submitting": "Accesso in corso…",
"google": "Continua con Google",
"or": "oppure",
"noAccount": "Non hai ancora un account? <link>Iscriviti</link>",
"error": "Credenziali non valide. Riprova."
},
"SignUp": {
"title": "Iscriviti",
"subtitle": "Crea il tuo profilo KetoPath in meno di un minuto.",
"nameLabel": "Come ti chiami",
"emailLabel": "Email",
"passwordLabel": "Password",
"passwordHint": "Almeno 8 caratteri",
"submit": "Crea account",
"submitting": "Creazione…",
"google": "Continua con Google",
"or": "oppure",
"haveAccount": "Hai già un account? <link>Accedi</link>",
"error": "Non è stato possibile completare la registrazione. Riprova.",
"disclaimer": "Procedendo accetti che i tuoi dati siano trattati come descritto nell'informativa sulla privacy. KetoPath non sostituisce il parere medico."
}
},
"Fasting": {
"title": "Digiuno intermittente",
"subtitle": "Una finestra alimentare e una di riposo. Il timer scandisce il ritmo, il corpo fa il resto.",
"historyTitle": "Storico sessioni",
"activeBadge": "Sessione in corso",
"inactiveBadge": "Pronto a iniziare",
"startCallToAction": "Scegli un protocollo e parti. Il timer continua a scorrere anche se chiudi l'app.",
"chooseProtocol": "Protocollo",
"start": "Avvia digiuno",
"starting": "Avvio…",
"saving": "Salvataggio…",
"remaining": "Mancano {time}",
"reached": "Obiettivo raggiunto. Puoi rompere il digiuno o proseguire.",
"completeReached": "Concludi",
"completeEarly": "Concludi adesso",
"abort": "Annulla sessione",
"confirmAbort": "Sei sicuro di voler annullare questa sessione?",
"phaseTitle": "Fase metabolica",
"phases": {
"postprandial": {
"label": "Post-prandiale",
"description": "Digestione in corso. Insulina alta, glicemia in salita."
},
"glycogenolysis": {
"label": "Glicogenolisi",
"description": "Il fegato libera glucosio dalle scorte di glicogeno. L'insulina cala."
},
"lipolysis": {
"label": "Lipolisi",
"description": "Le scorte di grasso iniziano a essere mobilitate per produrre energia."
},
"ketogenesis": {
"label": "Chetogenesi",
"description": "Il fegato produce corpi chetonici. Le sensazioni mentali si fanno più nitide."
},
"autophagy": {
"label": "Autofagia",
"description": "Le cellule riciclano componenti danneggiati. Pulizia interna profonda."
}
},
"startedAt": "Iniziato",
"target": "Obiettivo",
"protocolGuideTitle": "Guida ai protocolli",
"protocolHints": {
"FOURTEEN_TEN": {
"title": "Inizio dolce",
"description": "Adatto in fase di mantenimento o per chi inizia. 10 ore di alimentazione."
},
"SIXTEEN_EIGHT": {
"title": "Lo standard",
"description": "Il protocollo più diffuso. Equilibrio tra sostenibilità e benefici metabolici."
},
"EIGHTEEN_SIX": {
"title": "Più aggressivo",
"description": "Inizia a sollecitare la chetogenesi. Per chi è già adattato."
},
"TWENTY_FOUR": {
"title": "Avanzato",
"description": "Solo 4 ore di alimentazione. Da praticare con regolarità ma non quotidianamente."
}
},
"emptyHistory": "Nessuna sessione registrata. Avvia il primo digiuno per iniziare a costruire la serie.",
"statCompleted": "Sessioni concluse",
"statTotal": "Ore totali",
"statSessions": "Sessioni totali",
"status": {
"IN_PROGRESS": "In corso",
"COMPLETED": "Completata",
"ABORTED": "Annullata"
}
},
"Tracking": {
"title": "Pesata e misure",
"subtitle": "Pochi numeri, una volta a settimana. La tendenza emerge dalla regolarità — non dalla pesata di oggi.",
"newEntry": "Nuova rilevazione",
"history": "Storico",
"date": "Data",
"weightKg": "Peso (kg)",
"measurements": "Misure (cm) — opzionali",
"waist": "Girovita",
"hips": "Fianchi",
"thigh": "Coscia",
"arm": "Braccio",
"subjective": "Indicatori soggettivi (1–10)",
"energy": "Energia",
"sleep": "Sonno",
"hunger": "Fame",
"save": "Registra",
"saving": "Salvataggio…",
"saved": "Registrato.",
"current": "Peso attuale",
"delta": "Variazione",
"emptyHistory": "Nessuna rilevazione registrata. Inserisci la prima a destra: la tendenza apparirà dopo due o tre pesate."
},
"Notifications": {
"eyebrow": "Notifiche",
"title": "Promemoria al momento giusto",
"subtitle": "Decidi tu cosa vuoi ricevere e su quale dispositivo. Niente marketing, niente notifiche di terze parti.",
"thisDevice": "Questo dispositivo",
"thisDeviceActive": "Le notifiche sono attive su questo dispositivo.",
"thisDeviceInactive": "Questo dispositivo non riceve ancora notifiche. Attivalo per il promemoria settimanale.",
"enableHere": "Attiva sul dispositivo",
"sendTest": "Invia notifica di prova",
"weeklyWeighInLabel": "Promemoria pesata settimanale",
"weeklyWeighInHint": "Lunedì alle 09:00 ti ricordiamo la pesata della settimana.",
"fastingMilestonesLabel": "Tappe del digiuno",
"fastingMilestonesHint": "Ti avvisiamo all'ingresso in chetogenesi e all'autofagia. Disponibile a breve.",
"registeredDevices": "Dispositivi registrati",
"subscribed": "Notifiche attivate.",
"unsubscribed": "Notifiche disattivate su questo dispositivo.",
"testSent": "Notifica di prova inviata.",
"blocked": "Hai bloccato le notifiche per questo sito. Cambia l'impostazione dal browser per riattivarle.",
"unsupported": "Il tuo browser non supporta le notifiche push. Su iOS apri prima KetoPath dalla Home.",
"serverDisabled": "Le notifiche non sono ancora attive sul server.",
"configUnavailable": "Impossibile caricare le impostazioni notifiche. Riprova più tardi.",
"working": "Attendere…",
"errors": {
"unsupported": "Il tuo browser non supporta le notifiche push.",
"permission_denied": "Hai negato il permesso. Modificalo dal browser per riprovare.",
"permission_default": "Permesso non concesso. Riprova e seleziona 'Consenti'.",
"no_vapid_key": "Configurazione mancante. Contatta l'amministratore.",
"api_error": "Errore di rete. Riprova fra poco.",
"sw_error": "Impossibile registrare il service worker.",
"no_devices": "Nessun dispositivo registrato.",
"push_not_configured": "Le notifiche non sono ancora abilitate sul server.",
"unknown": "Si è verificato un errore imprevisto."
}
},
"Shopping": {
"title": "La spesa della settimana",
"subtitle": "Tutto ciò che ti serve per il piano in corso, raggruppato per reparto.",
"noPlan": "Genera prima un piano della settimana: la lista comparirà qui.",
"viewPlan": "Vai al piano",
"groupSize": "{n} articoli",
"totalLabel": "Totale articoli",
"estimatedCost": "Spesa stimata",
"checked": "{n} di {total}",
"fromRecipes": "Per: {names}",
"categoryLabel": {
"verdura": "Verdura",
"frutta": "Frutta",
"carne": "Carne",
"pesce": "Pesce",
"latticini": "Latticini",
"uova": "Uova",
"condimenti": "Condimenti",
"panetteria": "Panetteria",
"frutta-secca": "Frutta secca",
"erbe": "Erbe e spezie",
"salumi": "Salumi"
},
"viewCart": "Lista spesa"
},
"Recipe": {
"title": "Ricetta",
"back": "← Torna al piano",
"ingredientsTitle": "Ingredienti",
"ingredientsSubtitle": "Per una porzione singola",
"preparationTitle": "Preparazione",
"preparationFallback": "Mescola gli ingredienti seguendo l'ordine indicato e cuoci come preferisci.",
"macrosTitle": "Valori per porzione",
"kcal": "Calorie",
"protein": "Proteine",
"fat": "Grassi",
"netCarb": "Carb. netti",
"metaTitle": "Scheda ricetta",
"prepTime": "Preparazione",
"minutes": "{n} min",
"difficulty": "Difficoltà",
"phase": "Fase",
"phasesAll": "Tutte le fasi",
"phasesSome": "Fase {phases}",
"notesChef": "Nota dello chef",
"difficultyLabel": {
"FACILE": "Facile",
"MEDIA": "Media",
"ELABORATA": "Elaborata"
},
"categoryLabel": {
"COLAZIONE": "Colazione",
"PRANZO": "Pranzo",
"SPUNTINO": "Spuntino",
"CENA": "Cena"
},
"notFound": "Ricetta non trovata."
},
"Plan": {
"title": "Il tuo piano",
"weekOf": "Settimana del {date}",
"noPlan": "Non hai ancora un piano. Generalo: l'algoritmo sceglie le ricette adatte alla tua fase e ai tuoi macros.",
"generate": "Genera piano",
"regenerate": "Rigenera",
"noRecipe": "Nessuna ricetta selezionata",
"showAlternatives": "Vedi {n} alternative",
"hideAlternatives": "Nascondi",
"swap": "Scegli",
"dayTotal": "{kcal} kcal totali",
"meals": {
"COLAZIONE": "Colazione",
"PRANZO": "Pranzo",
"SPUNTINO": "Spuntino",
"CENA": "Cena"
},
"days": {
"monday": "Lunedì",
"tuesday": "Martedì",
"wednesday": "Mercoledì",
"thursday": "Giovedì",
"friday": "Venerdì",
"saturday": "Sabato",
"sunday": "Domenica"
}
},
"Profile": {
"title": "Il tuo profilo",
"subtitle": "I dati che ci servono per calcolare BMR, TDEE e i target di macros della settimana.",
"age": "Età",
"gender": "Sesso",
"heightCm": "Altezza (cm)",
"weightStartKg": "Iniziale",
"weightCurrentKg": "Attuale",
"weightGoalKg": "Obiettivo",
"activityLevel": "Livello di attività",
"selectPlaceholder": "Seleziona…",
"save": "Salva profilo",
"saving": "Salvataggio…",
"summary": "Calcolo personalizzato",
"emptySummary": "Salva il profilo per ricevere il tuo BMR, il TDEE e il target calorico giornaliero.",
"bmr": "Metabolismo basale",
"tdee": "Fabbisogno totale",
"activityMultiplier": "Moltiplicatore attività",
"hint": "Valori stimati con la formula Mifflin-St Jeor. Non sostituiscono il parere di un nutrizionista.",
"genderOptions": {
"MALE": "Maschio",
"FEMALE": "Femmina",
"OTHER": "Altro / preferisco non specificare"
},
"activityOptions": {
"SEDENTARY": "Sedentario · lavoro d'ufficio",
"LIGHT": "Leggero · sport 1–3 volte / settimana",
"MODERATE": "Moderato · sport 3–5 volte / settimana",
"INTENSE": "Intenso · sport 6+ volte / settimana"
}
}
}