fix: mobile layout fixes, i18n coverage, and recipe share link

Mobile:
- Recipes search bar full-width on mobile instead of capped narrow
- Cook mode ingredients panel stacks above the step instead of
  squeezing it into a narrow column
- Version history Compare/Restore buttons wrap onto their own row
- Recipe edit ingredient fields wrap instead of forcing horizontal
  scroll on narrow viewports

i18n: translates remaining hardcoded strings across recipes
filter/sort, adapt-recipe and AI variations dialogs, the full
settings section (sidebar + 6 sub-pages + BYOK/model-prefs/
API-keys/webhooks managers), explore tab, collections (new/fork/
share dialogs), meal planning (planner, AI generation phases, new
shopping list, shared-plan view), photo import, recipe bulk-select
toolbar, and recipe action-button tooltips. Also fixes the recipes
page subtitle, which wasn't just unworded but missing its {count}
interpolation entirely — it always rendered as the bare word
"results" regardless of how many recipes existed.

Feature: adds a ShareRecipeButton that copies the public /r/{id}
link to the clipboard, with a notice when the recipe isn't Public
yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Arnaud
2026-07-02 15:13:51 +02:00
parent b07bada291
commit eb424d8c04
44 changed files with 932 additions and 376 deletions
@@ -28,6 +28,7 @@ export function AdaptRecipeButton({
ingredients: Ingredient[];
}) {
const t = useTranslations("recipe");
const tCommon = useTranslations("common");
const router = useRouter();
const [open, setOpen] = useState(false);
const [excluded, setExcluded] = useState<Set<string>>(new Set());
@@ -76,7 +77,7 @@ export function AdaptRecipeButton({
const { id, adaptationNotes: notes } = await res.json() as { id: string; adaptationNotes: string };
setAdaptationNotes(notes);
toast.success("Adapted recipe saved as draft");
toast.success(t("adapted"));
setOpen(false);
reset();
router.push(`/recipes/${id}/edit`);
@@ -96,7 +97,7 @@ export function AdaptRecipeButton({
<Wand2 className="h-4 w-4" />
</Button>
} />
<TooltipContent>Adapt</TooltipContent>
<TooltipContent>{t("adaptTooltip")}</TooltipContent>
</Tooltip>
</TooltipProvider>
@@ -105,10 +106,10 @@ export function AdaptRecipeButton({
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Wand2 className="h-5 w-5 text-primary" />
Adapt this recipe
{t("adaptTitle")}
</DialogTitle>
<DialogDescription>
Tap ingredients to exclude them. AI will find the best substitutes while preserving the dish.
{t("adaptDescription")}
</DialogDescription>
</DialogHeader>
@@ -116,10 +117,10 @@ export function AdaptRecipeButton({
{/* Ingredient chips */}
<div className="space-y-2">
<Label>
Exclude ingredients
{t("excludeIngredients")}
{excluded.size > 0 && (
<span className="ml-2 text-xs text-destructive font-normal">
{excluded.size} excluded
{t("excludedCount", { count: excluded.size })}
</span>
)}
</Label>
@@ -149,8 +150,8 @@ export function AdaptRecipeButton({
{/* Free-text constraints */}
<div className="space-y-2">
<Label htmlFor="extra-constraints">
Additional constraints
<span className="ml-2 text-xs text-muted-foreground font-normal">optional</span>
{t("additionalConstraints")}
<span className="ml-2 text-xs text-muted-foreground font-normal">{t("optional")}</span>
</Label>
<Textarea
id="extra-constraints"
@@ -164,7 +165,7 @@ export function AdaptRecipeButton({
{adapting && (
<p className="text-sm text-muted-foreground">
Adapting recipe this may take 2030 seconds
{t("adapting")}
</p>
)}
@@ -174,17 +175,17 @@ export function AdaptRecipeButton({
onClick={() => { setOpen(false); reset(); }}
disabled={adapting}
>
Cancel
{tCommon("cancel")}
</Button>
{excluded.size > 0 && (
<Button variant="ghost" size="sm" onClick={() => setExcluded(new Set())} disabled={adapting}>
Clear
{t("clearExclusions")}
</Button>
)}
<Button onClick={handleAdapt} disabled={adapting || !hasConstraints}>
{adapting
? <><Loader2 className="h-4 w-4 animate-spin" />Adapting</>
: <><Wand2 className="h-4 w-4" />Adapt recipe</>
? <><Loader2 className="h-4 w-4 animate-spin" />{t("adaptingButton")}</>
: <><Wand2 className="h-4 w-4" />{t("adaptButton")}</>
}
</Button>
</div>