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:
@@ -4,6 +4,7 @@ import { auth } from "@/lib/auth/server";
|
||||
import { db, recipes, eq, and } from "@epicure/db";
|
||||
import { PrintTrigger } from "@/components/recipe/print-trigger";
|
||||
import { hasQuantity } from "@/lib/fractions";
|
||||
import { getMessages, formatMessage } from "@/lib/i18n/server";
|
||||
|
||||
type Params = { params: Promise<{ id: string }> };
|
||||
|
||||
@@ -12,6 +13,8 @@ export default async function RecipePrintPage({ params }: Params) {
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
if (!session) return null;
|
||||
|
||||
const m = getMessages((session.user as { locale?: string }).locale);
|
||||
|
||||
const recipe = await db.query.recipes.findFirst({
|
||||
where: and(eq(recipes.id, id), eq(recipes.authorId, session.user.id)),
|
||||
with: {
|
||||
@@ -24,19 +27,9 @@ export default async function RecipePrintPage({ params }: Params) {
|
||||
|
||||
const totalMins = (recipe.prepMins ?? 0) + (recipe.cookMins ?? 0);
|
||||
|
||||
const DIETARY_LABELS: Record<string, string> = {
|
||||
vegan: "Vegan",
|
||||
vegetarian: "Vegetarian",
|
||||
glutenFree: "Gluten-free",
|
||||
dairyFree: "Dairy-free",
|
||||
nutFree: "Nut-free",
|
||||
halal: "Halal",
|
||||
kosher: "Kosher",
|
||||
};
|
||||
|
||||
const activeTags = Object.entries(recipe.dietaryTags ?? {})
|
||||
.filter(([, v]) => v)
|
||||
.map(([k]) => DIETARY_LABELS[k])
|
||||
.map(([k]) => m.recipe.dietary[k as keyof typeof m.recipe.dietary])
|
||||
.filter(Boolean);
|
||||
|
||||
return (
|
||||
@@ -88,10 +81,10 @@ export default async function RecipePrintPage({ params }: Params) {
|
||||
)}
|
||||
|
||||
<div className="meta">
|
||||
{recipe.baseServings && <span>Serves {recipe.baseServings}</span>}
|
||||
{recipe.prepMins && <span>Prep {recipe.prepMins} min</span>}
|
||||
{recipe.cookMins && <span>Cook {recipe.cookMins} min</span>}
|
||||
{totalMins > 0 && <span>Total {totalMins} min</span>}
|
||||
{recipe.baseServings && <span>{formatMessage(m.recipe.servings, { count: recipe.baseServings })}</span>}
|
||||
{recipe.prepMins && <span>{formatMessage(m.recipe.prep, { mins: recipe.prepMins })}</span>}
|
||||
{recipe.cookMins && <span>{formatMessage(m.recipe.cook, { mins: recipe.cookMins })}</span>}
|
||||
{totalMins > 0 && <span>{formatMessage(m.recipe.total, { mins: totalMins })}</span>}
|
||||
{recipe.difficulty && <span>{recipe.difficulty.charAt(0).toUpperCase() + recipe.difficulty.slice(1)}</span>}
|
||||
</div>
|
||||
|
||||
@@ -105,7 +98,7 @@ export default async function RecipePrintPage({ params }: Params) {
|
||||
|
||||
{recipe.ingredients.length > 0 && (
|
||||
<>
|
||||
<h2>Ingredients</h2>
|
||||
<h2>{m.recipe.ingredients}</h2>
|
||||
<ul className="ingredients">
|
||||
{recipe.ingredients.map((ing) => (
|
||||
<li key={ing.id}>
|
||||
@@ -122,7 +115,7 @@ export default async function RecipePrintPage({ params }: Params) {
|
||||
|
||||
{recipe.steps.length > 0 && (
|
||||
<>
|
||||
<h2>Instructions</h2>
|
||||
<h2>{m.recipe.instructions}</h2>
|
||||
<ol className="steps">
|
||||
{recipe.steps.map((step) => (
|
||||
<li key={step.id}>
|
||||
@@ -137,7 +130,7 @@ export default async function RecipePrintPage({ params }: Params) {
|
||||
)}
|
||||
</article>
|
||||
|
||||
<footer>Printed from Epicure</footer>
|
||||
<footer>{m.print.footer}</footer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user