feat(i18n): translate hardcoded strings across print, public recipe, and recipe management UI
Wires dozens of components and server pages to next-intl instead of hardcoded English text, and adds a lightweight getMessages/formatMessage helper for server components (print pages, public recipe page, cook metadata) since next-intl/server isn't wired into this app's routing. Backfills missing en/fr message keys that existing code already referenced but fr.json lacked.
This commit is contained in:
@@ -44,6 +44,10 @@
|
||||
"pairingGenerateFailed": "Failed to generate \"{name}\"",
|
||||
"pairingSaveFailed": "Failed to save \"{name}\"",
|
||||
"pairingSuccess": "Recipe generated — review before publishing",
|
||||
"pairingBulkSuccess": "{count} recipes generated — find them in your library",
|
||||
"cookAction": "Cook",
|
||||
"adaptConstraintPlaceholder": "e.g. Make it vegan, lower the calories, use pantry staples only, gluten-free…",
|
||||
"photoImportFailed": "Failed to import recipe from photo.",
|
||||
"shoppingListCreateFailed": "Failed to create list",
|
||||
"shoppingListAddFailed": "Failed to add ingredients",
|
||||
"bulkDeleted": "{count, plural, one {1 recipe deleted} other {{count} recipes deleted}}",
|
||||
@@ -136,6 +140,60 @@
|
||||
"metric": "Metric",
|
||||
"imperial": "Imperial"
|
||||
},
|
||||
"print": {
|
||||
"footer": "Printed from Epicure",
|
||||
"day": "Day",
|
||||
"srv": " · {count} srv",
|
||||
"days": {
|
||||
"mon": "Monday",
|
||||
"tue": "Tuesday",
|
||||
"wed": "Wednesday",
|
||||
"thu": "Thursday",
|
||||
"fri": "Friday",
|
||||
"sat": "Saturday",
|
||||
"sun": "Sunday"
|
||||
}
|
||||
},
|
||||
"publicRecipe": {
|
||||
"publicRecipeBy": "Public recipe by",
|
||||
"viewInLibrary": "View in your library",
|
||||
"signUpFree": "Sign up free",
|
||||
"logIn": "Log in",
|
||||
"wantToSave": "Want to save this recipe?",
|
||||
"wantToSaveDescription": "Create a free account to save recipes, scale servings, and build your own library.",
|
||||
"getStartedFree": "Get started free"
|
||||
},
|
||||
"substitute": {
|
||||
"fetchError": "Couldn't fetch substitutes.",
|
||||
"titleFor": "Substitute for {ingredient}",
|
||||
"substitutesFor": "Substitutes for",
|
||||
"finding": "Finding substitutes…",
|
||||
"noneFound": "No substitutions found."
|
||||
},
|
||||
"recipeChat": {
|
||||
"sorry": "Sorry, I couldn't answer that.",
|
||||
"error": "Something went wrong. Please try again.",
|
||||
"suggestion1": "Can I substitute any ingredients?",
|
||||
"suggestion2": "How do I know when it's done?",
|
||||
"suggestion3": "Can I make this ahead of time?",
|
||||
"suggestion4": "What can I serve with this?",
|
||||
"ariaLabel": "Ask AI about this recipe",
|
||||
"title": "Ask about this recipe",
|
||||
"intro": "Ask anything about this recipe — ingredients, techniques, substitutions, timing…",
|
||||
"inputPlaceholder": "Ask a question…"
|
||||
},
|
||||
"servingScaler": {
|
||||
"servings": "Servings",
|
||||
"reset": "Reset",
|
||||
"aiScale": "AI Scale",
|
||||
"scaling": "Scaling…",
|
||||
"aiScaledNote": "AI-scaled quantities shown below"
|
||||
},
|
||||
"explore": {
|
||||
"searchPlaceholder": "Search public recipes…",
|
||||
"maxMinutes": "Max minutes",
|
||||
"aiSearchPlaceholder": "e.g. quick weeknight dinners, Italian comfort food…"
|
||||
},
|
||||
"common": {
|
||||
"loading": "Loading…",
|
||||
"error": "Something went wrong",
|
||||
@@ -220,7 +278,8 @@
|
||||
"createFirst": "Create your first recipe",
|
||||
"resultSingular": "result",
|
||||
"resultPlural": "results",
|
||||
"for": "for"
|
||||
"for": "for",
|
||||
"filterByTag": "Filter by tag…"
|
||||
},
|
||||
"recipeForm": {
|
||||
"titleLabel": "Title *",
|
||||
@@ -244,6 +303,9 @@
|
||||
"ingredient": "Ingredient",
|
||||
"note": "Note",
|
||||
"addIngredient": "Add ingredient",
|
||||
"setCover": "Set as cover",
|
||||
"removePhoto": "Remove",
|
||||
"expand": "Expand",
|
||||
"steps": "Steps",
|
||||
"stepPlaceholder": "Step {n}…",
|
||||
"timerSeconds": "Timer (s)",
|
||||
@@ -277,6 +339,11 @@
|
||||
"noRecipes": "No recipes found",
|
||||
"addFailed": "Failed to add",
|
||||
"addedToPantry": "{count, plural, one {1 item added to pantry} other {{count} items added to pantry}}",
|
||||
"moveToPantryFailed": "Failed to move items to pantry",
|
||||
"aisleOther": "Other",
|
||||
"listEmptyState": "This list is empty.",
|
||||
"checkedCount": "{checked}/{total} checked",
|
||||
"moveToPantry": "Move {count} to pantry",
|
||||
"listCreated": "List created",
|
||||
"days": {
|
||||
"mon": "Mon",
|
||||
@@ -326,7 +393,21 @@
|
||||
"empty": "No shopping lists yet",
|
||||
"listEmpty": "Empty",
|
||||
"generated": "Generated",
|
||||
"items": "{checked}/{total} items"
|
||||
"items": "{checked}/{total} items",
|
||||
"itemsChecked": "{checked}/{total} items checked",
|
||||
"fromMealPlan": " · From meal plan",
|
||||
"addToList": "Add to list",
|
||||
"dialogTitle": "Add to shopping list",
|
||||
"dialogDescription": "Add the ingredients of <strong>{title}</strong> to a shopping list.",
|
||||
"scaledSuffix": "scaled)",
|
||||
"loadingLists": "Loading your lists…",
|
||||
"modeExisting": "Add to existing list",
|
||||
"modeNew": "Create new list",
|
||||
"listNamePlaceholder": "List name",
|
||||
"ingredientsWillBeAdded": "{count, plural, one {1 ingredient will be added.} other {{count} ingredients will be added.}}",
|
||||
"adding": "Adding…",
|
||||
"addIngredients": "Add ingredients",
|
||||
"addedCount": "{count} ingredients added to list"
|
||||
},
|
||||
"collections": {
|
||||
"title": "Collections",
|
||||
@@ -345,7 +426,9 @@
|
||||
"collectionCreated": "Collection created",
|
||||
"collectionForked": "Collection forked to your library",
|
||||
"inviteSent": "Invitation sent",
|
||||
"memberRemoved": "Member removed"
|
||||
"memberRemoved": "Member removed",
|
||||
"replyPlaceholder": "Reply…",
|
||||
"commentPlaceholder": "Share your thoughts…"
|
||||
},
|
||||
"cookingMode": {
|
||||
"cooking": "Cooking",
|
||||
@@ -363,6 +446,8 @@
|
||||
"enableVoice": "Enable voice control",
|
||||
"stopVoice": "Stop voice",
|
||||
"toggleIngredients": "Toggle ingredients",
|
||||
"pageTitle": "Cooking: {title}",
|
||||
"pageTitleFallback": "Cooking Mode",
|
||||
"shortcutsTitle": "Shortcuts & commands",
|
||||
"keyboardSection": "Keyboard",
|
||||
"voiceSection": "Voice commands",
|
||||
@@ -423,8 +508,16 @@
|
||||
"redeliveryFailed": "Failed to redeliver",
|
||||
"apiKeyCreateFailed": "Failed to create API key",
|
||||
"apiKeyRevokeFailed": "Failed to revoke API key",
|
||||
"apiKeyNamePlaceholder": "e.g. My app",
|
||||
"webhookUrlPlaceholder": "https://example.com/webhook",
|
||||
"webhookAdding": "Adding…",
|
||||
"webhookAddButton": "Add webhook",
|
||||
"modelDefaultPlaceholder": "Default",
|
||||
"modelNamePlaceholder": "e.g. llama3.2",
|
||||
"copyFailed": "Failed to copy to clipboard",
|
||||
"byokSaved": "{provider} key saved",
|
||||
"byokSaveFailed": "Failed to save key",
|
||||
"byokRemoved": "{provider} key removed",
|
||||
"byokRemoveFailed": "Failed to remove key",
|
||||
"modelSaved": "Model preferences saved",
|
||||
"modelSaveFailed": "Failed to save",
|
||||
|
||||
+179
-5
@@ -28,6 +28,32 @@
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler",
|
||||
"delete": "Supprimer",
|
||||
"noIngredientsSteps": "Pas encore d'ingrédients ni d'étapes.",
|
||||
"generateContent": "Générer avec l'IA",
|
||||
"editManually": "Modifier manuellement",
|
||||
"deleted": "Recette supprimée",
|
||||
"deleteFailed": "Échec de la suppression",
|
||||
"imported": "Recette importée ! Vérifiez avant de publier.",
|
||||
"adapted": "Recette adaptée enregistrée comme brouillon",
|
||||
"adaptFailed": "Échec de l'adaptation de la recette",
|
||||
"adaptConstraintRequired": "Sélectionnez au moins un ingrédient à exclure ou ajoutez une contrainte",
|
||||
"adaptConstraintPlaceholder": "ex. Rendre végétalien, réduire les calories, utiliser seulement les produits du garde-manger, sans gluten…",
|
||||
"variationsDirectionsPlaceholder": "ex. rendre végétalien, utiliser uniquement les produits du garde-manger, réduire le sucre…",
|
||||
"historyRestoreFailed": "Échec de la restauration de la version",
|
||||
"pairingMealFailed": "Échec de la suggestion d'accompagnements",
|
||||
"pairingDrinkFailed": "Échec de la suggestion de boissons",
|
||||
"pairingGenerateFailed": "Échec de la génération de « {name} »",
|
||||
"pairingSaveFailed": "Échec de l'enregistrement de « {name} »",
|
||||
"pairingSuccess": "Recette générée — vérifiez avant de publier",
|
||||
"pairingBulkSuccess": "{count} recettes générées — retrouvez-les dans votre bibliothèque",
|
||||
"cookAction": "Cuisiner",
|
||||
"photoImportFailed": "Échec de l'importation de la recette depuis la photo.",
|
||||
"shoppingListCreateFailed": "Échec de la création de la liste",
|
||||
"shoppingListAddFailed": "Échec de l'ajout des ingrédients",
|
||||
"bulkDeleted": "{count, plural, one {1 recette supprimée} other {{count} recettes supprimées}}",
|
||||
"bulkVisibility": "{count, plural, one {1 recette définie sur {visibility}} other {{count} recettes définies sur {visibility}}}",
|
||||
"bulkDeleteFailed": "Échec de la suppression",
|
||||
"bulkUpdateFailed": "Échec de la mise à jour",
|
||||
"visibility": {
|
||||
"private": "Privée",
|
||||
"unlisted": "Non répertoriée",
|
||||
@@ -52,14 +78,32 @@
|
||||
"generate": {
|
||||
"title": "Générer une recette avec l'IA",
|
||||
"description": "Décrivez ce que vous voulez cuisiner et l'IA créera une recette complète.",
|
||||
"descriptionFull": "Décrivez un plat ou prenez une photo — l'IA construit la recette complète.",
|
||||
"tabDescribe": "Décrire",
|
||||
"tabPhoto": "Depuis une photo",
|
||||
"surpriseMe": "Surprends-moi",
|
||||
"prompt": "Que voulez-vous cuisiner ?",
|
||||
"placeholder": "ex. Un risotto crémeux aux champignons et parmesan, 4 portions, niveau facile",
|
||||
"language": "Langue",
|
||||
"language": "Langue de la recette",
|
||||
"difficulty": "Difficulté",
|
||||
"anyDifficulty": "Toutes",
|
||||
"button": "Générer",
|
||||
"generating": "Génération en cours…",
|
||||
"success": "Recette générée ! Vérifiez et modifiez avant de publier.",
|
||||
"error": "Échec de la génération de la recette",
|
||||
"saveError": "Échec de l'enregistrement de la recette générée"
|
||||
"saveError": "Échec de l'enregistrement de la recette générée",
|
||||
"uploadClick": "Cliquez pour importer",
|
||||
"uploadDrag": "ou glissez une photo du plat",
|
||||
"uploadFormats": "JPEG, PNG ou WebP · l'IA reconstituera la recette",
|
||||
"analyzing": "Analyse en cours…",
|
||||
"analyzePhoto": "Analyse de la photo du plat…",
|
||||
"recognizeDish": "Reconnaître le plat",
|
||||
"photoSuccess": "Recette reconnue ! Vérifiez et modifiez avant de publier.",
|
||||
"photoError": "Échec de l'analyse de la photo",
|
||||
"contentGenerated": "Ingrédients et étapes générés !",
|
||||
"contentGenerateFailed": "Échec de la génération",
|
||||
"contentSaveFailed": "Échec de l'enregistrement du contenu généré",
|
||||
"generatingContent": "Génération du contenu de la recette…"
|
||||
},
|
||||
"variations": {
|
||||
"title": "Variations de recette par IA",
|
||||
@@ -96,16 +140,79 @@
|
||||
"metric": "Métrique",
|
||||
"imperial": "Impérial"
|
||||
},
|
||||
"print": {
|
||||
"footer": "Imprimé depuis Epicure",
|
||||
"day": "Jour",
|
||||
"srv": " · {count} pers.",
|
||||
"days": {
|
||||
"mon": "Lundi",
|
||||
"tue": "Mardi",
|
||||
"wed": "Mercredi",
|
||||
"thu": "Jeudi",
|
||||
"fri": "Vendredi",
|
||||
"sat": "Samedi",
|
||||
"sun": "Dimanche"
|
||||
}
|
||||
},
|
||||
"publicRecipe": {
|
||||
"publicRecipeBy": "Recette publique par",
|
||||
"viewInLibrary": "Voir dans votre bibliothèque",
|
||||
"signUpFree": "S'inscrire gratuitement",
|
||||
"logIn": "Se connecter",
|
||||
"wantToSave": "Envie de sauvegarder cette recette ?",
|
||||
"wantToSaveDescription": "Créez un compte gratuit pour sauvegarder des recettes, ajuster les portions et construire votre propre bibliothèque.",
|
||||
"getStartedFree": "Commencer gratuitement"
|
||||
},
|
||||
"substitute": {
|
||||
"fetchError": "Impossible de récupérer les substituts.",
|
||||
"titleFor": "Substitut pour {ingredient}",
|
||||
"substitutesFor": "Substituts pour",
|
||||
"finding": "Recherche de substituts…",
|
||||
"noneFound": "Aucune substitution trouvée."
|
||||
},
|
||||
"recipeChat": {
|
||||
"sorry": "Désolé, je n'ai pas pu répondre à cela.",
|
||||
"error": "Une erreur s'est produite. Veuillez réessayer.",
|
||||
"suggestion1": "Puis-je substituer des ingrédients ?",
|
||||
"suggestion2": "Comment savoir quand c'est prêt ?",
|
||||
"suggestion3": "Puis-je préparer cela à l'avance ?",
|
||||
"suggestion4": "Avec quoi puis-je servir ce plat ?",
|
||||
"ariaLabel": "Demander à l'IA à propos de cette recette",
|
||||
"title": "Poser une question sur cette recette",
|
||||
"intro": "Demandez n'importe quoi sur cette recette — ingrédients, techniques, substitutions, minutage…",
|
||||
"inputPlaceholder": "Posez une question…"
|
||||
},
|
||||
"servingScaler": {
|
||||
"servings": "Portions",
|
||||
"reset": "Réinitialiser",
|
||||
"aiScale": "Ajuster avec l'IA",
|
||||
"scaling": "Ajustement…",
|
||||
"aiScaledNote": "Quantités ajustées par l'IA affichées ci-dessous"
|
||||
},
|
||||
"explore": {
|
||||
"searchPlaceholder": "Rechercher des recettes publiques…",
|
||||
"maxMinutes": "Minutes max",
|
||||
"aiSearchPlaceholder": "ex. dîners rapides en semaine, cuisine réconfortante italienne…"
|
||||
},
|
||||
"common": {
|
||||
"loading": "Chargement…",
|
||||
"error": "Une erreur s'est produite",
|
||||
"save": "Enregistrer",
|
||||
"saved": "Enregistré",
|
||||
"saveFailed": "Échec de l'enregistrement",
|
||||
"deleteFailed": "Échec de la suppression",
|
||||
"updateFailed": "Échec de la mise à jour",
|
||||
"copyFailed": "Échec de la copie dans le presse-papiers",
|
||||
"deleted": "Supprimé",
|
||||
"cancel": "Annuler",
|
||||
"delete": "Supprimer",
|
||||
"confirm": "Confirmer",
|
||||
"back": "Retour"
|
||||
"back": "Retour",
|
||||
"difficulty": "Difficulté",
|
||||
"anyDifficulty": "Toutes",
|
||||
"editManually": "Modifier manuellement",
|
||||
"surpriseMe": "Surprends-moi",
|
||||
"generatingContent": "Génération du contenu de la recette…"
|
||||
},
|
||||
"auth": {
|
||||
"signIn": "Se connecter",
|
||||
@@ -162,7 +269,8 @@
|
||||
"createFirst": "Créer votre première recette",
|
||||
"resultSingular": "résultat",
|
||||
"resultPlural": "résultats",
|
||||
"for": "pour"
|
||||
"for": "pour",
|
||||
"filterByTag": "Filtrer par tag…"
|
||||
},
|
||||
"recipeForm": {
|
||||
"titleLabel": "Titre *",
|
||||
@@ -183,6 +291,9 @@
|
||||
"ingredient": "Ingrédient",
|
||||
"note": "Note",
|
||||
"addIngredient": "Ajouter un ingrédient",
|
||||
"setCover": "Définir comme couverture",
|
||||
"removePhoto": "Supprimer",
|
||||
"expand": "Développer",
|
||||
"steps": "Étapes",
|
||||
"stepPlaceholder": "Étape {n}…",
|
||||
"timerSeconds": "Minuteur (s)",
|
||||
@@ -212,7 +323,16 @@
|
||||
"addTo": "Ajouter à {day} – {meal}",
|
||||
"searchRecipes": "Rechercher des recettes…",
|
||||
"servings": "Portions",
|
||||
"difficulty": "Difficulté",
|
||||
"noRecipes": "Aucune recette trouvée",
|
||||
"addFailed": "Échec de l'ajout",
|
||||
"addedToPantry": "{count, plural, one {1 article ajouté au garde-manger} other {{count} articles ajoutés au garde-manger}}",
|
||||
"moveToPantryFailed": "Échec du déplacement des articles vers le garde-manger",
|
||||
"aisleOther": "Autre",
|
||||
"listEmptyState": "Cette liste est vide.",
|
||||
"checkedCount": "{checked}/{total} cochés",
|
||||
"moveToPantry": "Déplacer {count} vers le garde-manger",
|
||||
"listCreated": "Liste créée",
|
||||
"days": {
|
||||
"mon": "Lun",
|
||||
"tue": "Mar",
|
||||
@@ -261,7 +381,21 @@
|
||||
"empty": "Aucune liste de courses",
|
||||
"listEmpty": "Vide",
|
||||
"generated": "Générée",
|
||||
"items": "{checked}/{total} articles"
|
||||
"items": "{checked}/{total} articles",
|
||||
"itemsChecked": "{checked}/{total} articles cochés",
|
||||
"fromMealPlan": " · Depuis le planning repas",
|
||||
"addToList": "Ajouter à la liste",
|
||||
"dialogTitle": "Ajouter à la liste de courses",
|
||||
"dialogDescription": "Ajouter les ingrédients de <strong>{title}</strong> à une liste de courses.",
|
||||
"scaledSuffix": "ajustée)",
|
||||
"loadingLists": "Chargement de vos listes…",
|
||||
"modeExisting": "Ajouter à une liste existante",
|
||||
"modeNew": "Créer une nouvelle liste",
|
||||
"listNamePlaceholder": "Nom de la liste",
|
||||
"ingredientsWillBeAdded": "{count, plural, one {1 ingrédient sera ajouté.} other {{count} ingrédients seront ajoutés.}}",
|
||||
"adding": "Ajout…",
|
||||
"addIngredients": "Ajouter les ingrédients",
|
||||
"addedCount": "{count} ingrédients ajoutés à la liste"
|
||||
},
|
||||
"collections": {
|
||||
"title": "Collections",
|
||||
@@ -271,6 +405,19 @@
|
||||
"recipeCount": "{count} recette",
|
||||
"recipeCountPlural": "{count} recettes"
|
||||
},
|
||||
"social": {
|
||||
"followed": "Abonné",
|
||||
"unfollowed": "Désabonné",
|
||||
"commentFailed": "Échec de la publication du commentaire",
|
||||
"deleted": "Supprimé",
|
||||
"ratingSaved": "Note enregistrée",
|
||||
"collectionCreated": "Collection créée",
|
||||
"collectionForked": "Collection dupliquée dans votre bibliothèque",
|
||||
"inviteSent": "Invitation envoyée",
|
||||
"memberRemoved": "Membre supprimé",
|
||||
"replyPlaceholder": "Répondre…",
|
||||
"commentPlaceholder": "Partagez vos impressions…"
|
||||
},
|
||||
"cookingMode": {
|
||||
"cooking": "En cuisine",
|
||||
"stepOf": "Étape {current} sur {total}",
|
||||
@@ -287,6 +434,8 @@
|
||||
"enableVoice": "Activer les commandes vocales",
|
||||
"stopVoice": "Désactiver les commandes vocales",
|
||||
"toggleIngredients": "Afficher les ingrédients",
|
||||
"pageTitle": "Cuisiner : {title}",
|
||||
"pageTitleFallback": "Mode cuisine",
|
||||
"shortcutsTitle": "Raccourcis et commandes",
|
||||
"keyboardSection": "Clavier",
|
||||
"voiceSection": "Commandes vocales",
|
||||
@@ -340,6 +489,31 @@
|
||||
"changePasswordButton": "Changer le mot de passe",
|
||||
"passwordChanged": "Mot de passe modifié avec succès.",
|
||||
"passwordChangeFailed": "Échec de la modification du mot de passe.",
|
||||
"webhookCreateFailed": "Échec de la création du webhook",
|
||||
"webhookDeleteFailed": "Échec de la suppression du webhook",
|
||||
"webhookUpdateFailed": "Échec de la mise à jour du webhook",
|
||||
"webhookUrlPlaceholder": "https://exemple.fr/webhook",
|
||||
"webhookAdding": "Ajout…",
|
||||
"webhookAddButton": "Ajouter un webhook",
|
||||
"redeliverySuccess": "Renvoi mis en file d'attente",
|
||||
"redeliveryFailed": "Échec du renvoi",
|
||||
"apiKeyCreateFailed": "Échec de la création de la clé API",
|
||||
"apiKeyRevokeFailed": "Échec de la révocation de la clé API",
|
||||
"apiKeyNamePlaceholder": "ex. Mon application",
|
||||
"copyFailed": "Échec de la copie dans le presse-papiers",
|
||||
"byokSaved": "Clé {provider} enregistrée",
|
||||
"byokSaveFailed": "Échec de l'enregistrement de la clé",
|
||||
"byokRemoved": "Clé {provider} supprimée",
|
||||
"byokRemoveFailed": "Échec de la suppression de la clé",
|
||||
"modelSaved": "Préférences de modèle enregistrées",
|
||||
"modelSaveFailed": "Échec de l'enregistrement",
|
||||
"modelDefaultPlaceholder": "Par défaut",
|
||||
"modelNamePlaceholder": "ex. llama3.2",
|
||||
"userUpdated": "Utilisateur mis à jour avec succès",
|
||||
"userUpdateFailed": "Échec de l'enregistrement",
|
||||
"adminSettingsSaved": "Paramètres enregistrés",
|
||||
"adminSettingsFailed": "Échec de l'enregistrement des paramètres",
|
||||
"nutritionGoalsSaved": "Objectifs nutritionnels enregistrés",
|
||||
"bio": "Bio",
|
||||
"publicBio": "Bio publique",
|
||||
"publicBioDescription": "Affichée sur votre profil public.",
|
||||
|
||||
Reference in New Issue
Block a user