feat: translate recommendations and menu dishes into user language

- Generate recipes in English (reverted prompt to English-only)
- Add TranslateRecipes to OpenAI client (translate.go) — sends compact
  JSON payload of translatable fields, merges back into original recipes
- recommendation/handler.go: translate recipes in-memory before response
  when lang != "en"; falls back to English on error
- dish/repository.go: Create() now returns (dishID, recipeID, err) so
  callers can upsert dish_translations after saving
- menu/handler.go: saveRecipes returns savedRecipeEntry slice with dishID;
  saveDishTranslations calls TranslateRecipes then UpsertTranslation for
  each dish when the request locale is not English
- savedrecipe/repository.go: updated to ignore dishID from Create()
- init.go: wire openaiClient as RecipeTranslator and dishRepository as
  DishTranslator for menu.NewHandler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dbastrikin
2026-03-23 17:40:19 +02:00
parent cba50489be
commit bffeb05a43
7 changed files with 278 additions and 55 deletions

View File

@@ -16,7 +16,6 @@ var goalNames = map[string]string{
"gain": "muscle gain",
}
// GenerateRecipes generates recipes using the OpenAI API.
// Retries up to maxRetries times only when the response is not valid JSON.
// API-level errors (rate limits, auth, etc.) are returned immediately.
@@ -62,11 +61,6 @@ func buildRecipePrompt(req ai.RecipeRequest) string {
goal = "weight maintenance"
}
targetLang := langNames[req.Lang]
if targetLang == "" {
targetLang = "English"
}
restrictions := "none"
if len(req.Restrictions) > 0 {
restrictions = strings.Join(req.Restrictions, ", ")
@@ -93,7 +87,7 @@ func buildRecipePrompt(req ai.RecipeRequest) string {
strings.Join(req.AvailableProducts, "\n") + "\n"
}
return fmt.Sprintf(`You are a chef and nutritionist. Generate %d recipes in %s.
return fmt.Sprintf(`You are a chef and nutritionist. Generate %d recipes in English.
User profile:
- Goal: %s
@@ -107,7 +101,7 @@ Requirements for each recipe:
- Include approximate macros per serving
IMPORTANT:
- All text fields (title, description, ingredient names, units, step descriptions, tags) MUST be in %s.
- All text fields (title, description, ingredient names, units, step descriptions, tags) MUST be in English.
- The "image_query" field MUST always be in English (it is used for stock-photo search).
Return ONLY a valid JSON array without markdown or extra text:
@@ -124,7 +118,7 @@ Return ONLY a valid JSON array without markdown or extra text:
"steps": [{"number": 1, "description": "...", "timer_seconds": null}],
"tags": ["..."],
"nutrition_per_serving": {"calories": 420, "protein_g": 48, "fat_g": 12, "carbs_g": 18}
}]`, count, targetLang, goal, req.DailyCalories, restrictions, cuisines, productsSection, perMealCalories, targetLang)
}]`, count, goal, req.DailyCalories, restrictions, cuisines, productsSection, perMealCalories)
}
func parseRecipesJSON(text string) ([]ai.Recipe, error) {