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:
+225
-9
@@ -16,6 +16,11 @@
|
||||
"language": "Language"
|
||||
},
|
||||
"recipe": {
|
||||
"share": "Share",
|
||||
"shareLinkCopied": "Link copied to clipboard",
|
||||
"shareCopyFailed": "Failed to copy link",
|
||||
"shareNotPublicNotice": "This link will only work once the recipe is set to Public — link copied anyway.",
|
||||
"print": "Print",
|
||||
"ingredients": "Ingredients",
|
||||
"instructions": "Instructions",
|
||||
"photos": "Photos",
|
||||
@@ -37,6 +42,17 @@
|
||||
"adapted": "Adapted recipe saved as draft",
|
||||
"adaptFailed": "Failed to adapt recipe",
|
||||
"adaptConstraintRequired": "Select at least one ingredient to exclude or add a constraint",
|
||||
"adaptTooltip": "Adapt",
|
||||
"adaptTitle": "Adapt this recipe",
|
||||
"adaptDescription": "Tap ingredients to exclude them. AI will find the best substitutes while preserving the dish.",
|
||||
"excludeIngredients": "Exclude ingredients",
|
||||
"excludedCount": "{count} excluded",
|
||||
"additionalConstraints": "Additional constraints",
|
||||
"optional": "optional",
|
||||
"adapting": "Adapting recipe — this may take 20–30 seconds…",
|
||||
"adaptingButton": "Adapting…",
|
||||
"adaptButton": "Adapt recipe",
|
||||
"clearExclusions": "Clear",
|
||||
"variationsDirectionsPlaceholder": "e.g. make it vegan, use only pantry staples, reduce sugar…",
|
||||
"historyRestoreFailed": "Failed to restore version",
|
||||
"pairingMealFailed": "Failed to suggest pairings",
|
||||
@@ -48,12 +64,30 @@
|
||||
"cookAction": "Cook",
|
||||
"adaptConstraintPlaceholder": "e.g. Make it vegan, lower the calories, use pantry staples only, gluten-free…",
|
||||
"photoImportFailed": "Failed to import recipe from photo.",
|
||||
"importFromPhoto": "Import from Photo",
|
||||
"analyzingPhoto": "Analyzing photo…",
|
||||
"pairMealTooltip": "Pair meal",
|
||||
"historyTooltip": "History",
|
||||
"translateTooltip": "Translate",
|
||||
"drinksTooltip": "Drinks",
|
||||
"variationsTooltip": "Variations",
|
||||
"viewPublicly": "View publicly",
|
||||
"shoppingListCreateFailed": "Failed to create list",
|
||||
"shoppingListAddFailed": "Failed to add ingredients",
|
||||
"bulkDeleted": "{count, plural, one {1 recipe deleted} other {{count} recipes deleted}}",
|
||||
"bulkVisibility": "{count, plural, one {1 recipe set to {visibility}} other {{count} recipes set to {visibility}}}",
|
||||
"bulkDeleteFailed": "Delete failed",
|
||||
"bulkUpdateFailed": "Update failed",
|
||||
"bulkDeleteConfirm": "{count, plural, one {Delete 1 recipe?} other {Delete {count} recipes?}} This cannot be undone.",
|
||||
"deselectAll": "Deselect all",
|
||||
"selectAll": "Select all",
|
||||
"clickToSelect": "Click cards to select",
|
||||
"selectedCount": "{count} selected",
|
||||
"select": "Select",
|
||||
"visibility": "Visibility",
|
||||
"makePublic": "Make public",
|
||||
"makeUnlisted": "Make unlisted",
|
||||
"makePrivate": "Make private",
|
||||
"visibility": {
|
||||
"private": "Private",
|
||||
"unlisted": "Unlisted",
|
||||
@@ -103,7 +137,8 @@
|
||||
"contentGenerated": "Ingredients and steps generated!",
|
||||
"contentGenerateFailed": "Generation failed",
|
||||
"contentSaveFailed": "Failed to save generated content",
|
||||
"generatingContent": "Generating recipe content…"
|
||||
"generatingContent": "Generating recipe content…",
|
||||
"photoAnalysisNote": "AI will analyze this dish and infer ingredients, quantities, and cooking steps."
|
||||
},
|
||||
"variations": {
|
||||
"title": "AI Recipe Variations",
|
||||
@@ -118,7 +153,10 @@
|
||||
"success": "Variation created — review and edit before publishing.",
|
||||
"error": "Failed to generate variations",
|
||||
"saveError": "Failed to save variation",
|
||||
"wait": "This may take 20–30 seconds"
|
||||
"wait": "This may take 20–30 seconds",
|
||||
"directions": "Directions",
|
||||
"optional": "optional",
|
||||
"stepLabel": "Step {n}"
|
||||
},
|
||||
"translate": {
|
||||
"title": "Translate Recipe",
|
||||
@@ -138,7 +176,37 @@
|
||||
"language": "Language",
|
||||
"units": "Units",
|
||||
"metric": "Metric",
|
||||
"imperial": "Imperial"
|
||||
"imperial": "Imperial",
|
||||
"security": "Security",
|
||||
"aiModels": "AI & Models",
|
||||
"notifications": "Notifications",
|
||||
"nutrition": "Nutrition",
|
||||
"apiKeys": "API Keys",
|
||||
"webhooks": "Webhooks",
|
||||
"byok": {
|
||||
"title": "Your API Keys (BYOK)",
|
||||
"description": "Use your own API keys instead of the app's shared quota. Keys are encrypted at rest."
|
||||
},
|
||||
"modelPrefs": {
|
||||
"title": "Model Preferences",
|
||||
"description": "Choose which model to use for each task. Defaults to your first configured provider."
|
||||
},
|
||||
"apiKeysPage": {
|
||||
"title": "API Keys",
|
||||
"description": "Manage API keys for programmatic access to the Epicure API."
|
||||
},
|
||||
"pushNotifications": {
|
||||
"title": "Push Notifications",
|
||||
"description": "Get notified when someone comments on your recipes or likes your content."
|
||||
},
|
||||
"nutritionGoals": {
|
||||
"title": "Daily Nutrition Goals",
|
||||
"description": "Set your daily targets. These are shown as progress bars on your meal plan."
|
||||
},
|
||||
"webhooksPage": {
|
||||
"title": "Webhooks",
|
||||
"description": "Receive HTTP callbacks when events happen in your Epicure account."
|
||||
}
|
||||
},
|
||||
"print": {
|
||||
"footer": "Printed from Epicure",
|
||||
@@ -190,9 +258,26 @@
|
||||
"aiScaledNote": "AI-scaled quantities shown below"
|
||||
},
|
||||
"explore": {
|
||||
"title": "Explore",
|
||||
"searchPlaceholder": "Search public recipes…",
|
||||
"maxMinutes": "Max minutes",
|
||||
"aiSearchPlaceholder": "e.g. quick weeknight dinners, Italian comfort food…"
|
||||
"aiSearchPlaceholder": "e.g. quick weeknight dinners, Italian comfort food…",
|
||||
"anyDifficulty": "Any difficulty",
|
||||
"resultsFoundSingular": "{count} recipe found",
|
||||
"resultsFoundPlural": "{count} recipes found",
|
||||
"noResultsTitle": "No recipes found for “{query}”",
|
||||
"noResultsHint": "Try different keywords or remove filters",
|
||||
"loadMore": "Load more",
|
||||
"recipeIdeas": "Recipe ideas",
|
||||
"recipeIdeasHint": "Tell the AI what you're in the mood for, or let it surprise you.",
|
||||
"generating": "Generating…",
|
||||
"getIdeas": "Get ideas",
|
||||
"surpriseMe": "Surprise me",
|
||||
"generateFullRecipe": "Generate full recipe",
|
||||
"trendingThisWeek": "Trending this week",
|
||||
"noTrending": "No trending recipes yet. Be the first to share one!",
|
||||
"recentlyAdded": "Recently added",
|
||||
"noRecent": "No public recipes yet."
|
||||
},
|
||||
"common": {
|
||||
"loading": "Loading…",
|
||||
@@ -200,6 +285,8 @@
|
||||
"save": "Save",
|
||||
"saved": "Saved",
|
||||
"saveFailed": "Failed to save",
|
||||
"print": "Print",
|
||||
"share": "Share",
|
||||
"deleteFailed": "Delete failed",
|
||||
"updateFailed": "Update failed",
|
||||
"copyFailed": "Failed to copy to clipboard",
|
||||
@@ -276,10 +363,25 @@
|
||||
"noMatch": "No recipes match",
|
||||
"clearSearch": "Clear search",
|
||||
"createFirst": "Create your first recipe",
|
||||
"resultSingular": "result",
|
||||
"resultPlural": "results",
|
||||
"resultSingular": "{count} recipe",
|
||||
"resultPlural": "{count} recipes",
|
||||
"for": "for",
|
||||
"filterByTag": "Filter by tag…"
|
||||
"filterByTag": "Filter by tag…",
|
||||
"sortRecentlyUpdated": "Recently updated",
|
||||
"sortOldestUpdated": "Oldest updated",
|
||||
"sortNewestFirst": "Newest first",
|
||||
"sortOldestFirst": "Oldest first",
|
||||
"sortTitleAZ": "Title A–Z",
|
||||
"sortTitleZA": "Title Z–A",
|
||||
"sort": "Sort",
|
||||
"sortBy": "Sort by",
|
||||
"filter": "Filter",
|
||||
"all": "All",
|
||||
"anyDifficulty": "Any difficulty",
|
||||
"visibilityLabel": "Visibility",
|
||||
"difficultyLabel": "Difficulty",
|
||||
"tagLabel": "Tag",
|
||||
"clearFilters": "Clear filters"
|
||||
},
|
||||
"recipeForm": {
|
||||
"titleLabel": "Title *",
|
||||
@@ -333,6 +435,13 @@
|
||||
"mealPlan": {
|
||||
"title": "Meal Plan",
|
||||
"shoppingLists": "Shopping lists",
|
||||
"print": "Print",
|
||||
"shareTitle": "Share this week's plan",
|
||||
"shareDescription": "Invite household members to view or edit this week's meal plan.",
|
||||
"sharedWithYou": "Shared with you",
|
||||
"sharedPlanOf": "{name}'s plan",
|
||||
"weekOf": "Week of {date}",
|
||||
"sharedMealPlanTitle": "{name}'s Meal Plan",
|
||||
"addTo": "Add to {day} – {meal}",
|
||||
"searchRecipes": "Search recipes…",
|
||||
"servings": "Servings",
|
||||
@@ -373,7 +482,26 @@
|
||||
"aiGenerated": "Meal plan generated!",
|
||||
"includePantryItems": "Include pantry items",
|
||||
"pantryMode": "Pantry mode — maximize use of what you have",
|
||||
"pantryModeDescription": "Prefer meals that use multiple pantry ingredients and minimize extra shopping."
|
||||
"pantryModeDescription": "Prefer meals that use multiple pantry ingredients and minimize extra shopping.",
|
||||
"removeFailed": "Failed to remove",
|
||||
"generationFailed": "Generation failed",
|
||||
"anyDifficulty": "Any",
|
||||
"aiPhaseAnalyzing": "Analyzing your preferences…",
|
||||
"aiPhasePlanning": "Planning breakfast, lunch & dinner…",
|
||||
"aiPhaseSelecting": "Selecting recipes for each day…",
|
||||
"aiPhaseBalancing": "Balancing nutrition across the week…",
|
||||
"aiPhaseFinalizing": "Finalizing your meal plan…",
|
||||
"newList": "New list",
|
||||
"newListTitle": "New shopping list",
|
||||
"listNameLabel": "Name",
|
||||
"listNamePlaceholder": "Weekly groceries",
|
||||
"generateFromWeek": "Generate from meal plan week (optional)",
|
||||
"generateFromWeekHint": "Picks Monday of the selected week",
|
||||
"listCreateFailed": "Failed to create",
|
||||
"listCreating": "Creating…",
|
||||
"listCreate": "Create",
|
||||
"pickRecipe": "Pick recipe",
|
||||
"addEntry": "+ Add"
|
||||
},
|
||||
"pantry": {
|
||||
"title": "Pantry",
|
||||
@@ -397,9 +525,20 @@
|
||||
"empty": "No shopping lists yet",
|
||||
"listEmpty": "Empty",
|
||||
"generated": "Generated",
|
||||
"shareTitle": "Share shopping list",
|
||||
"shareDescription": "Invite household members to view or edit this list.",
|
||||
"sharedWithYou": "Shared with you",
|
||||
"sendToGroceryDelivery": "Send to grocery delivery",
|
||||
"copyAsText": "Copy list as text",
|
||||
"sendToInstacart": "Send to Instacart",
|
||||
"copiedToClipboard": "List copied to clipboard",
|
||||
"exportBuildFailed": "Could not build export",
|
||||
"instacartNotConfigured": "Instacart isn't configured yet",
|
||||
"items": "{checked}/{total} items",
|
||||
"itemsChecked": "{checked}/{total} items checked",
|
||||
"fromMealPlan": " · From meal plan",
|
||||
"itemCount": "{count} item",
|
||||
"itemCountPlural": "{count} items",
|
||||
"addToList": "Add to list",
|
||||
"dialogTitle": "Add to shopping list",
|
||||
"dialogDescription": "Add the ingredients of <strong>{title}</strong> to a shopping list.",
|
||||
@@ -419,7 +558,40 @@
|
||||
"empty": "No collections yet",
|
||||
"public": "Public",
|
||||
"recipeCount": "{count} recipe",
|
||||
"recipeCountPlural": "{count} recipes"
|
||||
"recipeCountPlural": "{count} recipes",
|
||||
"exportPdf": "Export as PDF",
|
||||
"shareTitle": "Share collection",
|
||||
"shareDescription": "Invite people to view or edit this collection.",
|
||||
"new": "New collection",
|
||||
"newTitle": "New collection",
|
||||
"nameLabel": "Name",
|
||||
"namePlaceholder": "Weekend dinners",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Optional…",
|
||||
"makePublic": "Make public",
|
||||
"creating": "Creating…",
|
||||
"create": "Create",
|
||||
"createFailed": "Failed to create",
|
||||
"forking": "Forking…",
|
||||
"forkCollection": "Fork collection",
|
||||
"forkFailed": "Fork failed"
|
||||
},
|
||||
"shareDialog": {
|
||||
"invite": "Invite",
|
||||
"emailPlaceholder": "Email address",
|
||||
"owner": "Owner",
|
||||
"viewer": "Viewer",
|
||||
"editor": "Editor",
|
||||
"loadingMembers": "Loading members…",
|
||||
"noMembers": "No members yet.",
|
||||
"invitationSent": "Invitation sent",
|
||||
"alreadyMember": "Already a member",
|
||||
"userNotFound": "User not found",
|
||||
"inviteFailed": "Could not invite user",
|
||||
"loadMembersFailed": "Could not load members",
|
||||
"memberRemoved": "Member removed",
|
||||
"removeMemberFailed": "Could not remove member",
|
||||
"enterEmail": "Enter an email address"
|
||||
},
|
||||
"social": {
|
||||
"followed": "Following",
|
||||
@@ -513,9 +685,36 @@
|
||||
"apiKeyCreateFailed": "Failed to create API key",
|
||||
"apiKeyRevokeFailed": "Failed to revoke API key",
|
||||
"apiKeyNamePlaceholder": "e.g. My app",
|
||||
"apiKeyNameLabel": "Key name",
|
||||
"apiKeyCreating": "Creating…",
|
||||
"apiKeyCreate": "Create",
|
||||
"apiKeyRevealNotice": "Save this key — it will not be shown again.",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied!",
|
||||
"dismiss": "Dismiss",
|
||||
"noApiKeys": "No API keys yet.",
|
||||
"createdOn": "Created {date}",
|
||||
"lastUsedOn": "Last used {date}",
|
||||
"neverUsed": "Never used",
|
||||
"revoke": "Revoke",
|
||||
"webhookUrlPlaceholder": "https://example.com/webhook",
|
||||
"webhookAdding": "Adding…",
|
||||
"webhookAddButton": "Add webhook",
|
||||
"webhookDocsLink": "View webhook docs & Zapier integration →",
|
||||
"endpointUrlLabel": "Endpoint URL",
|
||||
"eventsLabel": "Events",
|
||||
"eventsHint": "Select which events trigger this webhook. Leave all unchecked to receive all events.",
|
||||
"secretRevealNotice": "Save this signing secret — it will not be shown again.",
|
||||
"secretUsageHint": "Use it to verify the X-Epicure-Signature header on incoming requests.",
|
||||
"noWebhooks": "No webhooks yet.",
|
||||
"addedOn": "Added {date}",
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"allEvents": "All events",
|
||||
"deliveries": "Deliveries",
|
||||
"disable": "Disable",
|
||||
"enable": "Enable",
|
||||
"noDeliveriesYet": "No deliveries yet.",
|
||||
"modelDefaultPlaceholder": "Default",
|
||||
"modelNamePlaceholder": "e.g. llama3.2",
|
||||
"copyFailed": "Failed to copy to clipboard",
|
||||
@@ -523,8 +722,25 @@
|
||||
"byokSaveFailed": "Failed to save key",
|
||||
"byokRemoved": "{provider} key removed",
|
||||
"byokRemoveFailed": "Failed to remove key",
|
||||
"byokConfigured": "Configured",
|
||||
"byokOllamaHint": "Set via OLLAMA_BASE_URL env var",
|
||||
"byokSaving": "Saving…",
|
||||
"byokSave": "Save",
|
||||
"byokFooter": "Your keys are encrypted at rest. When set, they override the app's default keys for your account.",
|
||||
"modelSaved": "Model preferences saved",
|
||||
"modelSaveFailed": "Failed to save",
|
||||
"useCaseText": "Text generation",
|
||||
"useCaseTextDesc": "Recipe generation, variations, translations, adapt",
|
||||
"useCaseVision": "Vision / photo import",
|
||||
"useCaseVisionDesc": "Dish recognition, recipe import from photo",
|
||||
"useCaseMealPlan": "Meal planning",
|
||||
"useCaseMealPlanDesc": "Weekly meal plan generation",
|
||||
"providerLabel": "Provider",
|
||||
"modelLabel": "Model",
|
||||
"defaultAuto": "Default (auto)",
|
||||
"modelDefault": "Default",
|
||||
"modelSaving": "Saving…",
|
||||
"saveModelPrefs": "Save model preferences",
|
||||
"userUpdated": "User updated successfully",
|
||||
"userUpdateFailed": "Failed to save",
|
||||
"adminSettingsSaved": "Settings saved",
|
||||
|
||||
Reference in New Issue
Block a user