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
@@ -244,7 +244,7 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
<div className="max-w-5xl mx-auto space-y-10">
{/* Search bar */}
<div className="space-y-3">
<h1 className="text-3xl font-bold">Explore</h1>
<h1 className="text-3xl font-bold">{t("title")}</h1>
<form onSubmit={handleSubmit} className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground pointer-events-none" />
<Input
@@ -264,10 +264,10 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
<SelectValue placeholder={tCommon("difficulty")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="any">Any difficulty</SelectItem>
<SelectItem value="easy">Easy</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="hard">Hard</SelectItem>
<SelectItem value="any">{t("anyDifficulty")}</SelectItem>
<SelectItem value="easy">{tRecipe("difficulty.easy")}</SelectItem>
<SelectItem value="medium">{tRecipe("difficulty.medium")}</SelectItem>
<SelectItem value="hard">{tRecipe("difficulty.hard")}</SelectItem>
</SelectContent>
</Select>
<Input
@@ -280,7 +280,7 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
/>
{!loading && (
<span className="text-sm text-muted-foreground ml-auto">
{total} {total === 1 ? "recipe" : "recipes"} found
{t(total === 1 ? "resultsFoundSingular" : "resultsFoundPlural", { count: total })}
</span>
)}
</div>
@@ -303,8 +303,8 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
{hasQuery && !loading && results.length === 0 && (
<div className="flex flex-col items-center justify-center py-24 text-muted-foreground">
<ChefHat className="h-12 w-12 mb-4 opacity-30" />
<p className="text-lg font-medium">No recipes found for &ldquo;{query}&rdquo;</p>
<p className="text-sm mt-1">Try different keywords or remove filters</p>
<p className="text-lg font-medium">{t("noResultsTitle", { query })}</p>
<p className="text-sm mt-1">{t("noResultsHint")}</p>
</div>
)}
@@ -323,7 +323,7 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
disabled={loadingMore}
className="min-w-32"
>
{loadingMore ? "Loading" : "Load more"}
{loadingMore ? tCommon("loading") : t("loadMore")}
</Button>
</div>
)}
@@ -337,9 +337,9 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
<section className="rounded-xl border bg-gradient-to-br from-violet-50 to-indigo-50 dark:from-violet-950/30 dark:to-indigo-950/30 p-6 space-y-4">
<div className="flex items-center gap-2">
<Sparkles className="h-5 w-5 text-violet-500" />
<h2 className="text-xl font-semibold">Recipe ideas</h2>
<h2 className="text-xl font-semibold">{t("recipeIdeas")}</h2>
</div>
<p className="text-sm text-muted-foreground">Tell the AI what you&apos;re in the mood for, or let it surprise you.</p>
<p className="text-sm text-muted-foreground">{t("recipeIdeasHint")}</p>
<form
onSubmit={(e) => {
e.preventDefault();
@@ -355,9 +355,9 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
/>
<Button type="submit" disabled={ideasLoading} variant="secondary" className="shrink-0">
{ideasLoading ? (
<span className="flex items-center gap-2"><Wand2 className="h-4 w-4 animate-pulse" /> Generating</span>
<span className="flex items-center gap-2"><Wand2 className="h-4 w-4 animate-pulse" /> {t("generating")}</span>
) : (
<span className="flex items-center gap-2"><Wand2 className="h-4 w-4" /> Get ideas</span>
<span className="flex items-center gap-2"><Wand2 className="h-4 w-4" /> {t("getIdeas")}</span>
)}
</Button>
<Button
@@ -367,7 +367,7 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
onClick={() => { setIdeasPrompt(""); fetchIdeas(""); }}
className="shrink-0"
>
Surprise me
{t("surpriseMe")}
</Button>
</form>
@@ -414,9 +414,9 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
onClick={() => generateFromIdea(idea)}
>
{generatingId === idea.title ? (
<span className="flex items-center gap-2"><Wand2 className="h-3.5 w-3.5 animate-pulse" /> Generating</span>
<span className="flex items-center gap-2"><Wand2 className="h-3.5 w-3.5 animate-pulse" /> {t("generating")}</span>
) : (
<span className="flex items-center gap-2"><ArrowRight className="h-3.5 w-3.5" /> Generate full recipe</span>
<span className="flex items-center gap-2"><ArrowRight className="h-3.5 w-3.5" /> {t("generateFullRecipe")}</span>
)}
</Button>
</div>
@@ -428,11 +428,11 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
<section>
<div className="flex items-center gap-2 mb-4">
<Flame className="h-5 w-5 text-orange-500" />
<h2 className="text-xl font-semibold">Trending this week</h2>
<h2 className="text-xl font-semibold">{t("trendingThisWeek")}</h2>
</div>
{trending.length === 0 ? (
<p className="text-muted-foreground text-sm py-8 text-center">
No trending recipes yet. Be the first to share one!
{t("noTrending")}
</p>
) : (
<HorizontalScroll>
@@ -448,11 +448,11 @@ export function ExplorePageContent({ trending, recent, initialQuery }: Props) {
<section>
<div className="flex items-center gap-2 mb-4">
<Clock className="h-5 w-5 text-blue-500" />
<h2 className="text-xl font-semibold">Recently added</h2>
<h2 className="text-xl font-semibold">{t("recentlyAdded")}</h2>
</div>
{recent.length === 0 ? (
<p className="text-muted-foreground text-sm py-8 text-center">
No public recipes yet.
{t("noRecent")}
</p>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">