Files
Epicure/apps/web/components/recipe/translate-button.tsx
T
Arnaud 01fdbb880b 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.
2026-07-02 07:58:18 +02:00

134 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { Languages, Loader2 } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
const LANGUAGES = [
{ code: "French", label: "French" },
{ code: "Spanish", label: "Spanish" },
{ code: "German", label: "German" },
{ code: "Italian", label: "Italian" },
{ code: "Portuguese", label: "Portuguese" },
{ code: "English", label: "English" },
{ code: "Japanese", label: "Japanese" },
{ code: "Chinese", label: "Chinese" },
{ code: "Arabic", label: "Arabic" },
{ code: "Dutch", label: "Dutch" },
{ code: "Polish", label: "Polish" },
{ code: "Russian", label: "Russian" },
];
export function TranslateButton({ recipeId }: { recipeId: string }) {
const t = useTranslations("ai.translate");
const router = useRouter();
const [open, setOpen] = useState(false);
const [targetLanguage, setTargetLanguage] = useState("French");
const [translating, setTranslating] = useState(false);
async function handleTranslate() {
setTranslating(true);
try {
const res = await fetch(`/api/v1/ai/translate/${recipeId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ targetLanguage }),
});
if (!res.ok) {
const err = await res.json() as { error?: string };
toast.error(err.error ?? t("error"));
return;
}
const { id } = await res.json() as { id: string };
toast.success(t("success"));
setOpen(false);
router.push(`/recipes/${id}/edit`);
} finally {
setTranslating(false);
}
}
return (
<>
<TooltipProvider>
<Tooltip>
<TooltipTrigger render={
<Button variant="ghost" size="icon" onClick={() => setOpen(true)}>
<Languages className="h-4 w-4" />
</Button>
} />
<TooltipContent>Translate</TooltipContent>
</Tooltip>
</TooltipProvider>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Languages className="h-5 w-5 text-primary" />
Translate Recipe
</DialogTitle>
<DialogDescription>
AI will translate the title, description, ingredients, and steps. Quantities and units are preserved.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label>Target language</Label>
<Select value={targetLanguage} onValueChange={(v) => v && setTargetLanguage(v)} disabled={translating}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{LANGUAGES.map((l) => (
<SelectItem key={l.code} value={l.code}>{l.label}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{translating && (
<p className="text-sm text-muted-foreground">This may take 2030 seconds</p>
)}
<div className="flex gap-2 justify-end">
<Button variant="outline" onClick={() => setOpen(false)} disabled={translating}>
Cancel
</Button>
<Button onClick={handleTranslate} disabled={translating}>
{translating ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
Translating
</>
) : (
<>
<Languages className="h-4 w-4" />
Translate
</>
)}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</>
);
}