feat(web): Next.js 15 app shell, auth, i18n, base UI

App Router setup with next-intl (en/fr), Better Auth wiring, shadcn/ui components,
Tailwind, AES-256-GCM encrypt util, Redis rate limiter, tier limit checker,
site_settings helper for runtime .env overrides.
This commit is contained in:
Arnaud
2026-07-01 08:09:31 +02:00
parent add9365250
commit 5b40968c9d
50 changed files with 4274 additions and 0 deletions
+341
View File
@@ -0,0 +1,341 @@
{
"nav": {
"recipes": "Recipes",
"explore": "Explore",
"search": "Search",
"feed": "Feed",
"collections": "Collections",
"mealPlan": "Meal Plan",
"pantry": "Pantry",
"shopping": "Shopping",
"settings": "Settings",
"admin": "Admin",
"signOut": "Sign out",
"apiKeys": "API keys",
"webhooks": "Webhooks",
"language": "Language"
},
"recipe": {
"ingredients": "Ingredients",
"instructions": "Instructions",
"photos": "Photos",
"servings": "{count} servings",
"prep": "{mins}m prep",
"cook": "{mins}m cook",
"total": "{mins}m total",
"edit": "Edit",
"variations": "Variations",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"visibility": {
"private": "Private",
"unlisted": "Unlisted",
"public": "Public"
},
"difficulty": {
"easy": "Easy",
"medium": "Medium",
"hard": "Hard"
},
"dietary": {
"vegan": "Vegan",
"vegetarian": "Vegetarian",
"glutenFree": "Gluten-free",
"dairyFree": "Dairy-free",
"nutFree": "Nut-free",
"halal": "Halal",
"kosher": "Kosher"
}
},
"ai": {
"generate": {
"title": "Generate recipe with AI",
"description": "Describe what you want to cook and AI will create a complete recipe.",
"prompt": "What do you want to cook?",
"placeholder": "e.g. A creamy mushroom risotto with parmesan, serves 4, easy difficulty",
"language": "Language",
"button": "Generate",
"generating": "Generating…",
"success": "Recipe generated! Review and edit before publishing.",
"error": "Failed to generate recipe",
"saveError": "Failed to save generated recipe"
},
"variations": {
"title": "AI Recipe Variations",
"description": "Generate creative variations of this recipe — dietary adaptations, flavor twists, technique changes.",
"generate": "Generate 3 variations",
"generating": "Generating variations…",
"regenerate": "Regenerate",
"apply": "Apply",
"applying": "Applying…",
"ingredientChanges": "Ingredient changes",
"stepChanges": "Step changes",
"success": "Variation created — review and edit before publishing.",
"error": "Failed to generate variations",
"saveError": "Failed to save variation",
"wait": "This may take 2030 seconds"
},
"translate": {
"title": "Translate Recipe",
"description": "Translate this recipe's title, description, ingredients, and steps into another language.",
"targetLanguage": "Target language",
"button": "Translate",
"translating": "Translating…",
"success": "Translation saved as new draft recipe.",
"error": "Failed to translate recipe",
"saveError": "Failed to save translated recipe",
"wait": "This may take 2030 seconds"
}
},
"settings": {
"title": "Settings",
"profile": "Profile",
"language": "Language",
"units": "Units",
"metric": "Metric",
"imperial": "Imperial"
},
"common": {
"loading": "Loading…",
"error": "Something went wrong",
"save": "Save",
"saved": "Saved",
"saveFailed": "Failed to save",
"cancel": "Cancel",
"delete": "Delete",
"confirm": "Confirm",
"back": "Back"
},
"auth": {
"signIn": "Sign in",
"signInTitle": "Sign in to your recipe library",
"signInLoading": "Signing in…",
"signUp": "Sign up",
"signUpTitle": "Create account",
"signUpSubtitle": "Start building your recipe library",
"signUpLoading": "Creating account…",
"continueWithGoogle": "Continue with Google",
"email": "Email",
"emailPlaceholder": "you@example.com",
"password": "Password",
"name": "Name",
"namePlaceholder": "Your name",
"forgotPassword": "Forgot password?",
"forgotPasswordTitle": "Forgot password",
"forgotPasswordSent": "Check your inbox",
"forgotPasswordDescription": "Enter your email and we'll send you a reset link.",
"forgotPasswordSentDescription": "If an account exists for that email, you'll receive a reset link shortly.",
"sendResetLink": "Send reset link",
"sendingLink": "Sending…",
"resetPasswordTitle": "Reset password",
"resetPasswordDescription": "Enter a new password for your account.",
"newPassword": "New password",
"confirmPassword": "Confirm password",
"updatePassword": "Update password",
"updatingPassword": "Updating…",
"invalidToken": "Invalid or missing reset token.",
"noAccount": "No account? Sign up",
"alreadyHaveAccount": "Already have an account? Sign in",
"backToSignIn": "Back to sign in",
"emailNotVerified": "Email not verified",
"checkInboxVerification": "Check your inbox for a verification email.",
"resendVerification": "Resend verification email",
"resendingSending": "Sending…",
"or": "or"
},
"recipes": {
"title": "Recipes",
"subtitle": "Your personal recipe library",
"new": "New recipe",
"newTitle": "New recipe",
"newSubtitle": "Build your recipe from scratch",
"importUrl": "Import URL",
"generate": "Generate",
"search": "Search recipes…",
"noRecipes": "No recipes yet",
"noMatch": "No recipes match",
"clearSearch": "Clear search",
"createFirst": "Create your first recipe",
"resultSingular": "result",
"resultPlural": "results",
"for": "for"
},
"recipeForm": {
"titleLabel": "Title *",
"titleRequired": "Title is required",
"titlePlaceholder": "My amazing recipe",
"descriptionLabel": "Description",
"descriptionPlaceholder": "What makes this recipe special…",
"servings": "Servings",
"prepMins": "Prep (min)",
"cookMins": "Cook (min)",
"difficulty": "Difficulty",
"visibility": "Visibility",
"dietaryTags": "Dietary tags",
"photos": "Photos",
"ingredients": "Ingredients",
"amount": "Amount",
"unit": "Unit",
"ingredient": "Ingredient",
"note": "Note",
"addIngredient": "Add ingredient",
"steps": "Steps",
"stepPlaceholder": "Step {n}…",
"timerSeconds": "Timer (s)",
"addStep": "Add step",
"saving": "Saving…",
"saveChanges": "Save changes",
"createRecipe": "Create recipe",
"updateSuccess": "Recipe updated",
"createSuccess": "Recipe created",
"saveError": "Failed to save recipe"
},
"canCook": {
"title": "What can I cook?",
"subtitle": "Recipes you can make with your pantry",
"emptyPantry": "Your pantry is empty",
"addPantryItems": "Add pantry items",
"readyToCook": "Ready to cook",
"almostThere": "Almost there (7099%)",
"partialMatches": "Partial matches",
"noMatches": "No strong matches found",
"missing": "Missing",
"ingredientProgress": "{matched}/{total} ingredients"
},
"mealPlan": {
"title": "Meal Plan",
"shoppingLists": "Shopping lists",
"addTo": "Add to {day} {meal}",
"searchRecipes": "Search recipes…",
"servings": "Servings",
"noRecipes": "No recipes found",
"days": {
"mon": "Mon",
"tue": "Tue",
"wed": "Wed",
"thu": "Thu",
"fri": "Fri",
"sat": "Sat",
"sun": "Sun"
},
"meals": {
"breakfast": "Breakfast",
"lunch": "Lunch",
"dinner": "Dinner",
"snack": "Snack"
},
"generateWithAi": "Generate with AI",
"aiModalDescription": "AI will generate a full week of meals and create draft recipes for you.",
"dietaryPrefs": "Dietary preferences",
"dietaryPrefsPlaceholder": "e.g. vegetarian, gluten-free, low-carb…",
"aiPantryNote": "Your pantry items will be considered when available.",
"generating": "Generating…",
"generate": "Generate",
"cancel": "Cancel",
"aiGenerated": "Meal plan generated!",
"includePantryItems": "Include pantry items",
"pantryMode": "Pantry mode — maximize use of what you have",
"pantryModeDescription": "Prefer meals that use multiple pantry ingredients and minimize extra shopping."
},
"pantry": {
"title": "Pantry",
"subtitle": "Track what you have on hand",
"canCook": "What can I cook?"
},
"feed": {
"title": "Feed",
"followEmpty": "Follow chefs to see their recipes here.",
"noNew": "No new recipes from people you follow.",
"following": "Following",
"trending": "Trending",
"trendingEmpty": "No trending recipes this week.",
"loading": "Loading…"
},
"shoppingLists": {
"title": "Shopping Lists",
"subtitle": "Plan your grocery runs",
"empty": "No shopping lists yet",
"listEmpty": "Empty",
"generated": "Generated",
"items": "{checked}/{total} items"
},
"collections": {
"title": "Collections",
"subtitle": "Curate your favorite recipes into collections",
"empty": "No collections yet",
"public": "Public",
"recipeCount": "{count} recipe",
"recipeCountPlural": "{count} recipes"
},
"cookingMode": {
"cooking": "Cooking",
"stepOf": "Step {current} of {total}",
"ingredients": "Ingredients",
"startTimer": "Start timer",
"timerDone": "Done",
"pause": "Pause",
"reset": "Reset",
"previous": "Previous",
"next": "Next",
"finish": "Done!",
"voiceNotSupported": "Voice not supported in this browser",
"voiceError": "Voice error — try again",
"enableVoice": "Enable voice control",
"stopVoice": "Stop voice",
"toggleIngredients": "Toggle ingredients",
"shortcutsTitle": "Shortcuts & commands",
"keyboardSection": "Keyboard",
"voiceSection": "Voice commands",
"enableMicFirst": "Enable voice via the mic button first.",
"stepRepeatPrefix": "Step {n}: ",
"shortcuts": {
"nextStep": "Next step",
"prevStep": "Previous step",
"startTimer": "Start timer",
"toggleHelp": "Toggle this panel",
"exitMode": "Exit cook mode"
},
"voiceLabels": {
"nextCmd": "next / continue",
"prevCmd": "previous / back",
"timerCmd": "timer / start",
"stopCmd": "stop / pause",
"resetCmd": "reset",
"repeatCmd": "repeat / again",
"nextLabel": "Next step",
"prevLabel": "Previous step",
"timerLabel": "Start timer",
"stopLabel": "Pause timer",
"resetLabel": "Reset timer",
"repeatLabel": "Re-read step"
}
},
"settingsForm": {
"profile": "Profile",
"displayName": "Display name",
"email": "Email",
"emailReadOnly": "Email cannot be changed here.",
"saving": "Saving…",
"title": "Settings",
"subtitle": "Manage your account and preferences",
"language": "Language",
"languageDescription": "Choose your preferred language for the app and AI-generated content.",
"changeEmail": "Change email",
"newEmail": "New email address",
"changeEmailDescription": "A verification link will be sent to your new address before it takes effect.",
"sendVerification": "Send verification",
"sendingVerification": "Sending…",
"emailChangeSent": "Verification email sent. Check your inbox.",
"emailChangeFailed": "Failed to send verification email.",
"changePassword": "Change password",
"currentPassword": "Current password",
"newPassword": "New password",
"confirmPassword": "Confirm new password",
"passwordMismatch": "Passwords don't match.",
"changingPassword": "Changing…",
"changePasswordButton": "Change password",
"passwordChanged": "Password changed successfully.",
"passwordChangeFailed": "Failed to change password."
}
}