feat: push+email notifications, recipe notes, fork/clone, pantry-aware lists, GDPR export
Five S-sized items from HANDOFF.md's new-features backlog, all wiring up previously-orphaned infra: - createNotification now sends web push + email for every notification type (follow/comment/reply/reaction/rating/mention), not just comments - Personal recipe notes: private per-user notes on any viewable recipe (recipeNotes table had zero API/UI before this) - Recipe fork/clone: deep-copies a viewable recipe into your own library as a private draft, linked via recipeVariations, respects tier quota - Pantry-aware shopping lists: meal-plan-generated lists now subtract on-hand pantry quantities (ingredientId match, falling back to normalized name match) and flag partial/ambiguous matches instead of guessing - GDPR data export: downloadable JSON of a user's own content and activity across every relevant table, secrets/internal tables excluded New migrations 0025 (unique index for recipe-notes upsert) and 0026 (shopping_list_items.in_pantry) generated, left unapplied like 0023/0024. Verified with typecheck, lint, and a full local `docker build`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,19 @@
|
||||
"reply": "{name} a répondu à votre commentaire",
|
||||
"reaction": "{name} a réagi à votre commentaire",
|
||||
"rating": "{name} a noté votre recette",
|
||||
"mention": "{name} vous a mentionné dans un commentaire"
|
||||
"mention": "{name} vous a mentionné dans un commentaire",
|
||||
"detail": {
|
||||
"comment": "{name} a commenté votre recette « {title} »",
|
||||
"rating": "{name} a noté votre recette « {title} » {stars} étoiles"
|
||||
},
|
||||
"pushTitle": {
|
||||
"follow": "Nouvel abonné",
|
||||
"comment": "Nouveau commentaire",
|
||||
"reply": "Nouvelle réponse",
|
||||
"reaction": "Nouvelle réaction",
|
||||
"rating": "Nouvelle note",
|
||||
"mention": "Vous avez été mentionné"
|
||||
}
|
||||
},
|
||||
"recipe": {
|
||||
"byAuthor": "par {name}",
|
||||
@@ -82,6 +94,11 @@
|
||||
"pairingBulkSuccess": "{count} recettes générées — retrouvez-les dans votre bibliothèque",
|
||||
"pairingDialogTitle": "Compléter le repas",
|
||||
"pairingDialogDescription": "Plats suggérés par l'IA qui accompagnent bien cette recette. Générez-en n'importe lequel comme nouvelle recette.",
|
||||
"forkTooltip": "Copier cette recette",
|
||||
"forked": "Recette copiée dans votre bibliothèque",
|
||||
"forkFailed": "Échec de la copie de la recette",
|
||||
"forkLimitReached": "Limite de recettes atteinte pour votre offre",
|
||||
"forkedFrom": "Copiée depuis {title}",
|
||||
"pairingFindingLabel": "Recherche des meilleurs accords…",
|
||||
"pairingSuggestButton": "Suggérer des accompagnements",
|
||||
"pairingRegenerate": "Régénérer",
|
||||
@@ -499,6 +516,15 @@
|
||||
"tagLabel": "Tag",
|
||||
"clearFilters": "Effacer les filtres"
|
||||
},
|
||||
"recipeNotes": {
|
||||
"title": "Vos notes personnelles",
|
||||
"visibilityHint": "Visible uniquement par vous — jamais partagé avec l'auteur de la recette ou quiconque d'autre.",
|
||||
"placeholder": "ex. : j'ai mis moins de sel que ce que la recette indique, très bon avec du riz à la place des pâtes…",
|
||||
"save": "Enregistrer",
|
||||
"saving": "Enregistrement…",
|
||||
"saved": "Note enregistrée",
|
||||
"saveFailed": "Échec de l'enregistrement de la note"
|
||||
},
|
||||
"recipeForm": {
|
||||
"titleLabel": "Titre *",
|
||||
"titleRequired": "Le titre est requis",
|
||||
@@ -678,6 +704,7 @@
|
||||
"copiedToClipboard": "Liste copiée dans le presse-papiers",
|
||||
"exportBuildFailed": "Impossible de générer l'export",
|
||||
"instacartNotConfigured": "Instacart n'est pas encore configuré",
|
||||
"alreadyInPantry": "Déjà au garde-manger",
|
||||
"items": "{checked}/{total} articles",
|
||||
"itemsChecked": "{checked}/{total} articles cochés",
|
||||
"fromMealPlan": " · Depuis le planning repas",
|
||||
@@ -860,6 +887,9 @@
|
||||
"changePasswordButton": "Changer le mot de passe",
|
||||
"passwordChanged": "Mot de passe modifié avec succès.",
|
||||
"passwordChangeFailed": "Échec de la modification du mot de passe.",
|
||||
"downloadData": "Télécharger mes données",
|
||||
"downloadDataDescription": "Obtenez un export JSON de toutes les données personnelles qu'Epicure détient sur vous — votre profil, vos recettes, votre activité et vos paramètres.",
|
||||
"downloadDataButton": "Télécharger mes données",
|
||||
"webhookCreateFailed": "Échec de la création du webhook",
|
||||
"webhookDeleteFailed": "Échec de la suppression du webhook",
|
||||
"webhookUpdateFailed": "Échec de la mise à jour du webhook",
|
||||
|
||||
Reference in New Issue
Block a user