Files
food-ai/backend/internal/ingredient/model.go
dbastrikin c0cf1b38ea feat: implement backend localization infrastructure
- Add internal/locale package: Parse(Accept-Language), FromContext/WithLang helpers, 12 supported languages
- Add Language middleware that reads Accept-Language header and stores lang in context
- Register Language middleware globally in server router (after CORS)

Database migrations:
- 009: create recipe_translations, saved_recipe_translations, ingredient_translations tables; migrate existing _ru data
- 010: drop legacy _ru columns (title_ru, description_ru, canonical_name_ru); update FTS index

Models: remove all _ru fields (TitleRu, DescriptionRu, NameRu, UnitRu, CanonicalNameRu)

Repositories:
- recipe: Upsert drops _ru params; GetByID does LEFT JOIN COALESCE on recipe_translations; ListMissingTranslation(lang); UpsertTranslation
- ingredient: same pattern with ingredient_translations; Search now queries translated names/aliases
- savedrecipe: List/GetByID LEFT JOIN COALESCE on saved_recipe_translations; UpsertTranslation

Gemini:
- RecipeRequest/MenuRequest gain Lang field
- buildRecipePrompt rewritten in English with target-language content instruction; image_query always in English
- GenerateMenu propagates Lang to GenerateRecipes

Handlers:
- recommendation/menu: pass locale.FromContext(ctx) as Lang
- recognition: saveClassification stores Russian translation via UpsertTranslation instead of _ru column

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 23:17:34 +02:00

30 lines
1.0 KiB
Go

package ingredient
import (
"encoding/json"
"time"
)
// IngredientMapping is the canonical ingredient record used to link
// user products, recipe ingredients, and Spoonacular data.
// CanonicalName holds the content for the language resolved at query time
// (English by default, or from ingredient_translations when available).
type IngredientMapping struct {
ID string `json:"id"`
CanonicalName string `json:"canonical_name"`
SpoonacularID *int `json:"spoonacular_id"`
Aliases json.RawMessage `json:"aliases"` // []string
Category *string `json:"category"`
DefaultUnit *string `json:"default_unit"`
CaloriesPer100g *float64 `json:"calories_per_100g"`
ProteinPer100g *float64 `json:"protein_per_100g"`
FatPer100g *float64 `json:"fat_per_100g"`
CarbsPer100g *float64 `json:"carbs_per_100g"`
FiberPer100g *float64 `json:"fiber_per_100g"`
StorageDays *int `json:"storage_days"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}