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:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Sparkles, Loader2, Camera, Type, Upload, X, Shuffle } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
@@ -69,6 +70,7 @@ export function AiGenerateDialog({
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}) {
|
||||
const t = useTranslations("ai.generate");
|
||||
const router = useRouter();
|
||||
const [tab, setTab] = useState<Tab>("describe");
|
||||
|
||||
@@ -122,7 +124,7 @@ export function AiGenerateDialog({
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json() as { error?: string };
|
||||
toast.error(err.error ?? "Failed to generate recipe");
|
||||
toast.error(err.error ?? t("error"));
|
||||
return;
|
||||
}
|
||||
const generated = await res.json() as GeneratedRecipe;
|
||||
@@ -131,9 +133,9 @@ export function AiGenerateDialog({
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ...generated, visibility: "private", aiGenerated: true, language }),
|
||||
});
|
||||
if (!saveRes.ok) { toast.error("Failed to save generated recipe"); return; }
|
||||
if (!saveRes.ok) { toast.error(t("saveError")); return; }
|
||||
const saved = await saveRes.json() as { id: string };
|
||||
toast.success("Recipe generated! Review and edit before publishing.");
|
||||
toast.success(t("success"));
|
||||
handleClose();
|
||||
router.push(`/recipes/${saved.id}/edit`);
|
||||
} finally {
|
||||
@@ -151,13 +153,13 @@ export function AiGenerateDialog({
|
||||
body: JSON.stringify({ imageBase64: photoBase64, mimeType: photoMime }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
let msg = "Failed to analyze photo";
|
||||
let msg = t("photoError");
|
||||
try { msg = ((await res.json()) as { error?: string }).error ?? msg; } catch { /* non-JSON body */ }
|
||||
toast.error(msg);
|
||||
return;
|
||||
}
|
||||
const { id } = await res.json() as { id: string };
|
||||
toast.success("Recipe recognized! Review and edit before publishing.");
|
||||
toast.success(t("photoSuccess"));
|
||||
handleClose();
|
||||
router.push(`/recipes/${id}/edit`);
|
||||
} finally {
|
||||
@@ -226,7 +228,7 @@ export function AiGenerateDialog({
|
||||
id="ai-prompt"
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
placeholder="e.g. A creamy mushroom risotto with parmesan, serves 4, easy difficulty"
|
||||
placeholder={t("placeholder")}
|
||||
rows={4}
|
||||
disabled={busy}
|
||||
/>
|
||||
@@ -249,7 +251,7 @@ export function AiGenerateDialog({
|
||||
<Label>Difficulty</Label>
|
||||
<Select value={difficulty} onValueChange={(v) => setDifficulty(v as typeof difficulty)} disabled={busy}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Any" />
|
||||
<SelectValue placeholder={t("anyDifficulty")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">Any</SelectItem>
|
||||
|
||||
Reference in New Issue
Block a user