i18n: translate meal/drink pairing, nutrition, comments, DMs, people search, URL import

Full sweep of hardcoded English strings across:
- Meal pairing and drink pairing dialogs (titles, descriptions, role/
  type labels, regenerate/generate buttons, progress labels) — also
  fixed drink type labels that had French text hardcoded regardless
  of locale ("Sans alcool"/"Chaud").
- Nutrition panel — had no useTranslations at all.
- Comments: header, empty/loading state, post/reply/cancel/delete
  buttons, relative timestamps (just now/Xm ago/Xh ago/Xd ago), and
  comment-reactions' toasts + aria-labels.
- Rating stars toasts.
- Direct messages: thread, conversation list, message button, both
  /messages pages.
- People search page and component.
- URL import dialog (title, description, buttons, toasts).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Arnaud
2026-07-09 03:20:49 +02:00
parent e67145493e
commit 08ab9ac71f
16 changed files with 299 additions and 101 deletions
@@ -38,14 +38,14 @@ const ROLE_ICON: Record<Pairing["role"], React.ElementType> = {
sauce: ChefHat,
};
const ROLE_LABEL: Record<Pairing["role"], string> = {
starter: "Starter",
side: "Side",
salad: "Salad",
bread: "Bread",
drink: "Drink",
dessert: "Dessert",
sauce: "Sauce",
const ROLE_LABEL_KEY: Record<Pairing["role"], string> = {
starter: "pairingRoleStarter",
side: "pairingRoleSide",
salad: "pairingRoleSalad",
bread: "pairingRoleBread",
drink: "pairingRoleDrink",
dessert: "pairingRoleDessert",
sauce: "pairingRoleSauce",
};
const DIFFICULTY_VARIANT = {
@@ -154,22 +154,22 @@ export function MealPairingButton({ recipeId }: { recipeId: string }) {
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<UtensilsCrossed className="h-5 w-5 text-primary" />
Complete the meal
{t("pairingDialogTitle")}
</DialogTitle>
<DialogDescription>
AI-suggested dishes that pair well with this recipe. Generate any of them as a new recipe.
{t("pairingDialogDescription")}
</DialogDescription>
</DialogHeader>
{loading ? (
<div className="py-6 space-y-3">
<FakeProgressBar active={loading} durationMs={8000} label="Finding perfect pairings…" />
<FakeProgressBar active={loading} durationMs={8000} label={t("pairingFindingLabel")} />
</div>
) : pairings.length === 0 ? (
<div className="flex flex-col items-center gap-4 py-8">
<Button onClick={suggest} size="lg">
<Sparkles className="h-4 w-4" />
Suggest pairings
{t("pairingSuggestButton")}
</Button>
</div>
) : (
@@ -199,7 +199,7 @@ export function MealPairingButton({ recipeId }: { recipeId: string }) {
<div className="flex-1 min-w-0 space-y-1.5">
<div className="flex items-center gap-2 flex-wrap">
<span className="font-semibold">{pairing.name}</span>
<Badge variant="outline" className="text-xs">{ROLE_LABEL[pairing.role]}</Badge>
<Badge variant="outline" className="text-xs">{t(ROLE_LABEL_KEY[pairing.role])}</Badge>
<Badge variant={DIFFICULTY_VARIANT[pairing.difficulty]} className="text-xs">{pairing.difficulty}</Badge>
{pairing.prepTimeMins && (
<span className="text-xs text-muted-foreground">{pairing.prepTimeMins}m</span>
@@ -218,7 +218,7 @@ export function MealPairingButton({ recipeId }: { recipeId: string }) {
<FakeProgressBar
active={!!generatingProgress}
durationMs={generatingProgress.total * 10000}
label={`Generating recipe ${generatingProgress.current} of ${generatingProgress.total}`}
label={t("pairingGeneratingLabel", { current: generatingProgress.current, total: generatingProgress.total })}
/>
)}
<div className="flex gap-2 pt-1">
@@ -229,7 +229,7 @@ export function MealPairingButton({ recipeId }: { recipeId: string }) {
disabled={!!generatingProgress}
>
<Sparkles className="h-4 w-4" />
Regenerate
{t("pairingRegenerate")}
</Button>
<Button
className="flex-1"
@@ -239,12 +239,12 @@ export function MealPairingButton({ recipeId }: { recipeId: string }) {
{generatingProgress ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
Generating {generatingProgress.current}/{generatingProgress.total}
{t("pairingGeneratingButton", { current: generatingProgress.current, total: generatingProgress.total })}
</>
) : (
<>
<Sparkles className="h-4 w-4" />
Generate{selected.size > 0 ? ` (${selected.size})` : ""}
{t("pairingGenerateButton")}{selected.size > 0 ? ` (${selected.size})` : ""}
</>
)}
</Button>