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:
Arnaud
2026-07-02 07:58:18 +02:00
parent afff6cf9eb
commit 01fdbb880b
32 changed files with 515 additions and 187 deletions
@@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Flame, Clock, ChefHat, Search, Sparkles, Wand2, ArrowRight } from "lucide-react";
import { useTranslations } from "next-intl";
import type { RecipeResult as ExploreRecipeResult } from "@/app/(app)/explore/page";
const DIFFICULTY_COLORS: Record<string, string> = {
@@ -100,6 +101,8 @@ type Props = {
export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
const router = useRouter();
const searchParams = useSearchParams();
const t = useTranslations("explore");
const tCommon = useTranslations("common");
const [inputValue, setInputValue] = useState(initialQuery);
const [query, setQuery] = useState(initialQuery);
@@ -243,7 +246,7 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
ref={inputRef}
value={inputValue}
onChange={(e) => handleInputChange(e.target.value)}
placeholder="Search public recipes…"
placeholder={t("searchPlaceholder")}
className="pl-10 h-12 text-base"
autoFocus={!!initialQuery}
/>
@@ -253,7 +256,7 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
<div className="flex flex-wrap items-center gap-3">
<Select value={difficulty} onValueChange={handleDifficultyChange}>
<SelectTrigger className="w-40">
<SelectValue placeholder="Difficulty" />
<SelectValue placeholder={tCommon("difficulty")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="any">Any difficulty</SelectItem>
@@ -265,7 +268,7 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
<Input
type="number"
min={1}
placeholder="Max minutes"
placeholder={t("maxMinutes")}
value={maxMins}
onChange={(e) => handleMaxMinsChange(e.target.value)}
className="w-36"
@@ -342,7 +345,7 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
<Input
value={ideasPrompt}
onChange={(e) => setIdeasPrompt(e.target.value)}
placeholder="e.g. quick weeknight dinners, Italian comfort food…"
placeholder={t("aiSearchPlaceholder")}
className="bg-background"
/>
<Button type="submit" disabled={ideasLoading} variant="secondary" className="shrink-0">