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:
@@ -20,14 +20,14 @@ type Provider = "openai" | "anthropic" | "openrouter" | "ollama" | "";
|
||||
|
||||
type UseCase = {
|
||||
key: "text" | "vision" | "mealPlan";
|
||||
label: string;
|
||||
description: string;
|
||||
labelKey: "useCaseText" | "useCaseVision" | "useCaseMealPlan";
|
||||
descriptionKey: "useCaseTextDesc" | "useCaseVisionDesc" | "useCaseMealPlanDesc";
|
||||
};
|
||||
|
||||
const USE_CASES: UseCase[] = [
|
||||
{ key: "text", label: "Text generation", description: "Recipe generation, variations, translations, adapt" },
|
||||
{ key: "vision", label: "Vision / photo import", description: "Dish recognition, recipe import from photo" },
|
||||
{ key: "mealPlan", label: "Meal planning", description: "Weekly meal plan generation" },
|
||||
{ key: "text", labelKey: "useCaseText", descriptionKey: "useCaseTextDesc" },
|
||||
{ key: "vision", labelKey: "useCaseVision", descriptionKey: "useCaseVisionDesc" },
|
||||
{ key: "mealPlan", labelKey: "useCaseMealPlan", descriptionKey: "useCaseMealPlanDesc" },
|
||||
];
|
||||
|
||||
const PRESET_MODELS: Record<string, { label: string; models: { value: string; label: string }[] }> = {
|
||||
@@ -87,7 +87,7 @@ export function ModelPrefsForm({ initialPrefs }: { initialPrefs: Prefs | null })
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{USE_CASES.map(({ key, label, description }) => {
|
||||
{USE_CASES.map(({ key, labelKey, descriptionKey }) => {
|
||||
const providerKey = `${key}Provider` as keyof Prefs;
|
||||
const modelKey = `${key}Model` as keyof Prefs;
|
||||
const provider = (prefs[providerKey] ?? "") as Provider;
|
||||
@@ -97,12 +97,12 @@ export function ModelPrefsForm({ initialPrefs }: { initialPrefs: Prefs | null })
|
||||
return (
|
||||
<div key={key} className="rounded-lg border p-4 space-y-3">
|
||||
<div>
|
||||
<p className="font-medium text-sm">{label}</p>
|
||||
<p className="text-xs text-muted-foreground">{description}</p>
|
||||
<p className="font-medium text-sm">{t(labelKey)}</p>
|
||||
<p className="text-xs text-muted-foreground">{t(descriptionKey)}</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Provider</Label>
|
||||
<Label className="text-xs">{t("providerLabel")}</Label>
|
||||
<Select
|
||||
value={provider || "default"}
|
||||
onValueChange={(v) => {
|
||||
@@ -116,7 +116,7 @@ export function ModelPrefsForm({ initialPrefs }: { initialPrefs: Prefs | null })
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="default">
|
||||
<span className="text-muted-foreground">Default (auto)</span>
|
||||
<span className="text-muted-foreground">{t("defaultAuto")}</span>
|
||||
</SelectItem>
|
||||
<SelectItem value="openai">OpenAI</SelectItem>
|
||||
<SelectItem value="anthropic">Anthropic</SelectItem>
|
||||
@@ -127,7 +127,7 @@ export function ModelPrefsForm({ initialPrefs }: { initialPrefs: Prefs | null })
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-xs">Model</Label>
|
||||
<Label className="text-xs">{t("modelLabel")}</Label>
|
||||
{presets ? (
|
||||
<Select
|
||||
value={model || "default"}
|
||||
@@ -138,7 +138,7 @@ export function ModelPrefsForm({ initialPrefs }: { initialPrefs: Prefs | null })
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="default">
|
||||
<span className="text-muted-foreground">Default</span>
|
||||
<span className="text-muted-foreground">{t("modelDefault")}</span>
|
||||
</SelectItem>
|
||||
<SelectGroup>
|
||||
<SelectLabel>{PRESET_MODELS[provider]?.label}</SelectLabel>
|
||||
@@ -164,7 +164,7 @@ export function ModelPrefsForm({ initialPrefs }: { initialPrefs: Prefs | null })
|
||||
})}
|
||||
|
||||
<Button onClick={() => { void handleSave(); }} disabled={saving} size="sm">
|
||||
{saving ? "Saving…" : "Save model preferences"}
|
||||
{saving ? t("modelSaving") : t("saveModelPrefs")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user