feat: German translations for TheMealDB recipes + recipe detail view
- Add GermanTranslator service with 200+ ingredient and meal name mappings - Translate titles and ingredients when fetching from TheMealDB - Add click-to-view recipe detail modal on week plan meal cards - Import RecipeDetail component in WeekPlanView Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
188
backend/Services/GermanTranslator.cs
Normal file
188
backend/Services/GermanTranslator.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using MealPlanner.Models;
|
||||
|
||||
namespace MealPlanner.Services;
|
||||
|
||||
public static class GermanTranslator
|
||||
{
|
||||
private static readonly Dictionary<string, string> MealNames = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// Common TheMealDB meal titles
|
||||
{"Beef", "Rind"}, {"Chicken", "Hähnchen"}, {"Pork", "Schwein"}, {"Lamb", "Lamm"},
|
||||
{"Fish", "Fisch"}, {"Salmon", "Lachs"}, {"Tuna", "Thunfisch"}, {"Shrimp", "Garnelen"},
|
||||
{"Prawn", "Garnelen"}, {"Turkey", "Truthahn"}, {"Duck", "Ente"}, {"Veal", "Kalb"},
|
||||
{"Steak", "Steak"}, {"Roast", "Braten"}, {"Grilled", "Gegrilltes"}, {"Fried", "Gebratenes"},
|
||||
{"Baked", "Gebackenes"}, {"Braised", "Geschmortes"}, {"Smoked", "Geräuchertes"},
|
||||
{"Soup", "Suppe"}, {"Stew", "Eintopf"}, {"Salad", "Salat"}, {"Pie", "Pastete"},
|
||||
{"Pasta", "Pasta"}, {"Noodles", "Nudeln"}, {"Rice", "Reis"}, {"Bread", "Brot"},
|
||||
{"Cake", "Kuchen"}, {"Pancake", "Pfannkuchen"}, {"Pancakes", "Pfannkuchen"},
|
||||
{"Curry", "Curry"}, {"Casserole", "Auflauf"}, {"Sandwich", "Sandwich"},
|
||||
{"Wrap", "Wrap"}, {"Tacos", "Tacos"}, {"Burrito", "Burrito"},
|
||||
{"Spaghetti", "Spaghetti"}, {"Lasagne", "Lasagne"}, {"Risotto", "Risotto"},
|
||||
{"Pizza", "Pizza"}, {"Burger", "Burger"}, {"Meatballs", "Fleischbällchen"},
|
||||
{"Teriyaki", "Teriyaki"}, {"Sweet", "Süß"}, {"Spicy", "Scharf"},
|
||||
{"Creamy", "Cremig"}, {"Crispy", "Knusprig"}, {"Honey", "Honig"},
|
||||
{"Garlic", "Knoblauch"}, {"Lemon", "Zitronen"}, {"Mushroom", "Pilz"},
|
||||
{"Tomato", "Tomaten"}, {"Potato", "Kartoffel"}, {"Potatoes", "Kartoffeln"},
|
||||
{"Cheese", "Käse"}, {"Cream", "Sahne"}, {"Butter", "Butter"},
|
||||
{"Chocolate", "Schokoladen"}, {"Caramel", "Karamell"}, {"Vanilla", "Vanille"},
|
||||
{"Apple", "Apfel"}, {"Orange", "Orangen"}, {"Banana", "Bananen"},
|
||||
{"Beans", "Bohnen"}, {"Lentil", "Linsen"}, {"Lentils", "Linsen"},
|
||||
{"Vegetable", "Gemüse"}, {"Vegetables", "Gemüse"},
|
||||
{"with", "mit"}, {"and", "und"}, {"in", "in"}, {"on", "auf"},
|
||||
{"Sauce", "Soße"}, {"Gravy", "Bratensaft"}, {"Dressing", "Dressing"},
|
||||
{"Stuffed", "Gefüllte"}, {"Roasted", "Geröstete"}, {"Sautéed", "Sautierte"},
|
||||
{"Chili", "Chili"}, {"Ginger", "Ingwer"}, {"Coconut", "Kokos"},
|
||||
{"Spinach", "Spinat"}, {"Broccoli", "Brokkoli"}, {"Carrot", "Karotten"},
|
||||
{"Onion", "Zwiebel"}, {"Pepper", "Pfeffer"}, {"Peppers", "Paprika"},
|
||||
{"Corn", "Mais"}, {"Peas", "Erbsen"}, {"Cabbage", "Kohl"},
|
||||
{"Eggplant", "Aubergine"}, {"Zucchini", "Zucchini"}, {"Celery", "Sellerie"},
|
||||
{"Parsley", "Petersilie"}, {"Basil", "Basilikum"}, {"Thyme", "Thymian"},
|
||||
{"Oregano", "Oregano"}, {"Rosemary", "Rosmarin"}, {"Cinnamon", "Zimt"},
|
||||
{"Nutmeg", "Muskatnuss"}, {"Cumin", "Kreuzkümmel"}, {"Paprika", "Paprika"},
|
||||
{"Soy", "Soja"}, {"Sesame", "Sesam"}, {"Peanut", "Erdnuss"},
|
||||
{"Almond", "Mandel"}, {"Walnut", "Walnuss"},
|
||||
{"Egg", "Ei"}, {"Eggs", "Eier"}, {"Milk", "Milch"}, {"Yogurt", "Joghurt"},
|
||||
{"Oil", "Öl"}, {"Olive", "Oliven"}, {"Vinegar", "Essig"},
|
||||
{"Sugar", "Zucker"}, {"Salt", "Salz"}, {"Flour", "Mehl"},
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> Ingredients = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// All from above plus more specific ingredient names
|
||||
{"Beef", "Rindfleisch"}, {"Chicken", "Hähnchen"}, {"Chicken Breast", "Hähnchenbrust"},
|
||||
{"Chicken Thighs", "Hähnchenschenkel"}, {"Chicken Stock", "Hühnerbrühe"},
|
||||
{"Minced Beef", "Rinderhackfleisch"}, {"Ground Beef", "Rinderhackfleisch"},
|
||||
{"Pork", "Schweinefleisch"}, {"Bacon", "Speck"}, {"Sausage", "Wurst"},
|
||||
{"Lamb", "Lammfleisch"}, {"Fish", "Fisch"}, {"Salmon", "Lachs"},
|
||||
{"Tuna", "Thunfisch"}, {"Shrimp", "Garnelen"}, {"Prawns", "Garnelen"},
|
||||
{"Butter", "Butter"}, {"Olive Oil", "Olivenöl"}, {"Vegetable Oil", "Pflanzenöl"},
|
||||
{"Sesame Oil", "Sesamöl"}, {"Coconut Oil", "Kokosöl"},
|
||||
{"Milk", "Milch"}, {"Cream", "Sahne"}, {"Sour Cream", "Saure Sahne"},
|
||||
{"Heavy Cream", "Schlagsahne"}, {"Double Cream", "Sahne"},
|
||||
{"Cheese", "Käse"}, {"Parmesan", "Parmesan"}, {"Cheddar", "Cheddar"},
|
||||
{"Mozzarella", "Mozzarella"}, {"Feta", "Feta"}, {"Cream Cheese", "Frischkäse"},
|
||||
{"Yogurt", "Joghurt"}, {"Yoghurt", "Joghurt"}, {"Egg", "Ei"}, {"Eggs", "Eier"},
|
||||
{"Egg Yolks", "Eigelb"}, {"Egg Whites", "Eiweiß"},
|
||||
{"Onion", "Zwiebel"}, {"Onions", "Zwiebeln"}, {"Red Onion", "Rote Zwiebel"},
|
||||
{"Garlic", "Knoblauch"}, {"Garlic Clove", "Knoblauchzehe"},
|
||||
{"Garlic Cloves", "Knoblauchzehen"}, {"Garlic Minced", "Knoblauch gehackt"},
|
||||
{"Ginger", "Ingwer"}, {"Celery", "Sellerie"}, {"Carrot", "Karotte"},
|
||||
{"Carrots", "Karotten"}, {"Potato", "Kartoffel"}, {"Potatoes", "Kartoffeln"},
|
||||
{"Sweet Potato", "Süßkartoffel"}, {"Tomato", "Tomate"}, {"Tomatoes", "Tomaten"},
|
||||
{"Cherry Tomatoes", "Kirschtomaten"}, {"Tomato Paste", "Tomatenmark"},
|
||||
{"Tomato Puree", "Tomatenmark"}, {"Chopped Tomatoes", "Gehackte Tomaten"},
|
||||
{"Tomato Sauce", "Tomatensoße"}, {"Passata", "Passata"},
|
||||
{"Pepper", "Pfeffer"}, {"Bell Pepper", "Paprika"}, {"Red Pepper", "Rote Paprika"},
|
||||
{"Green Pepper", "Grüne Paprika"}, {"Chilli", "Chili"}, {"Chili", "Chili"},
|
||||
{"Chilli Powder", "Chilipulver"}, {"Chili Flakes", "Chiliflocken"},
|
||||
{"Mushroom", "Pilze"}, {"Mushrooms", "Pilze"},
|
||||
{"Spinach", "Spinat"}, {"Broccoli", "Brokkoli"}, {"Cauliflower", "Blumenkohl"},
|
||||
{"Cabbage", "Kohl"}, {"Lettuce", "Salat"}, {"Cucumber", "Gurke"},
|
||||
{"Zucchini", "Zucchini"}, {"Courgette", "Zucchini"}, {"Eggplant", "Aubergine"},
|
||||
{"Aubergine", "Aubergine"}, {"Corn", "Mais"}, {"Peas", "Erbsen"},
|
||||
{"Green Beans", "Grüne Bohnen"}, {"Kidney Beans", "Kidneybohnen"},
|
||||
{"Chickpeas", "Kichererbsen"}, {"Lentils", "Linsen"},
|
||||
{"Rice", "Reis"}, {"Basmati Rice", "Basmatireis"}, {"Long Grain Rice", "Langkornreis"},
|
||||
{"Pasta", "Nudeln"}, {"Spaghetti", "Spaghetti"}, {"Penne", "Penne"},
|
||||
{"Noodles", "Nudeln"}, {"Lasagne Sheets", "Lasagneplatten"},
|
||||
{"Bread", "Brot"}, {"Breadcrumbs", "Semmelbrösel"}, {"Flour", "Mehl"},
|
||||
{"Plain Flour", "Mehl"}, {"Self Raising Flour", "Mehl mit Backpulver"},
|
||||
{"Cornflour", "Speisestärke"}, {"Cornstarch", "Speisestärke"},
|
||||
{"Baking Powder", "Backpulver"}, {"Baking Soda", "Natron"},
|
||||
{"Sugar", "Zucker"}, {"Brown Sugar", "Brauner Zucker"}, {"Caster Sugar", "Feiner Zucker"},
|
||||
{"Icing Sugar", "Puderzucker"}, {"Honey", "Honig"}, {"Maple Syrup", "Ahornsirup"},
|
||||
{"Salt", "Salz"}, {"Black Pepper", "Schwarzer Pfeffer"},
|
||||
{"Cumin", "Kreuzkümmel"}, {"Paprika", "Paprika"}, {"Turmeric", "Kurkuma"},
|
||||
{"Coriander", "Koriander"}, {"Cinnamon", "Zimt"}, {"Nutmeg", "Muskatnuss"},
|
||||
{"Oregano", "Oregano"}, {"Basil", "Basilikum"}, {"Thyme", "Thymian"},
|
||||
{"Rosemary", "Rosmarin"}, {"Parsley", "Petersilie"}, {"Bay Leaf", "Lorbeerblatt"},
|
||||
{"Bay Leaves", "Lorbeerblätter"}, {"Mint", "Minze"}, {"Dill", "Dill"},
|
||||
{"Chives", "Schnittlauch"}, {"Cilantro", "Koriander"},
|
||||
{"Soy Sauce", "Sojasoße"}, {"Worcestershire Sauce", "Worcestersoße"},
|
||||
{"Fish Sauce", "Fischsoße"}, {"Hot Sauce", "Scharfe Soße"},
|
||||
{"Vinegar", "Essig"}, {"Balsamic Vinegar", "Balsamico-Essig"},
|
||||
{"Red Wine Vinegar", "Rotweinessig"}, {"White Wine Vinegar", "Weißweinessig"},
|
||||
{"Lemon", "Zitrone"}, {"Lemon Juice", "Zitronensaft"}, {"Lime", "Limette"},
|
||||
{"Lime Juice", "Limettensaft"}, {"Orange", "Orange"}, {"Orange Juice", "Orangensaft"},
|
||||
{"Wine", "Wein"}, {"Red Wine", "Rotwein"}, {"White Wine", "Weißwein"},
|
||||
{"Beer", "Bier"}, {"Coconut Milk", "Kokosmilch"}, {"Stock", "Brühe"},
|
||||
{"Beef Stock", "Rinderbrühe"}, {"Vegetable Stock", "Gemüsebrühe"},
|
||||
{"Water", "Wasser"}, {"Ice", "Eis"},
|
||||
{"Chocolate", "Schokolade"}, {"Dark Chocolate", "Zartbitterschokolade"},
|
||||
{"Cocoa", "Kakao"}, {"Vanilla", "Vanille"}, {"Vanilla Extract", "Vanilleextrakt"},
|
||||
{"Apple", "Apfel"}, {"Banana", "Banane"}, {"Strawberries", "Erdbeeren"},
|
||||
{"Blueberries", "Blaubeeren"}, {"Raisins", "Rosinen"},
|
||||
{"Almonds", "Mandeln"}, {"Walnuts", "Walnüsse"}, {"Peanuts", "Erdnüsse"},
|
||||
{"Peanut Butter", "Erdnussbutter"}, {"Pine Nuts", "Pinienkerne"},
|
||||
{"Sesame Seeds", "Sesamkörner"}, {"Sunflower Oil", "Sonnenblumenöl"},
|
||||
{"Spring Onions", "Frühlingszwiebeln"}, {"Leek", "Lauch"},
|
||||
{"Fennel", "Fenchel"}, {"Avocado", "Avocado"}, {"Olives", "Oliven"},
|
||||
{"Capers", "Kapern"}, {"Anchovies", "Sardellen"},
|
||||
{"Tortilla", "Tortilla"}, {"Pita", "Fladenbrot"}, {"Naan", "Naan"},
|
||||
{"Tofu", "Tofu"}, {"Coconut", "Kokosnuss"}, {"Coconut Cream", "Kokoscreme"},
|
||||
{"Curry Powder", "Currypulver"}, {"Garam Masala", "Garam Masala"},
|
||||
{"Mustard", "Senf"}, {"Dijon Mustard", "Dijon-Senf"}, {"Ketchup", "Ketchup"},
|
||||
{"Mayonnaise", "Mayonnaise"}, {"Sriracha", "Sriracha"},
|
||||
};
|
||||
|
||||
public static string TranslateTitle(string title)
|
||||
{
|
||||
// Word-by-word translation for meal titles
|
||||
var words = title.Split(' ');
|
||||
var translated = new List<string>();
|
||||
int i = 0;
|
||||
while (i < words.Length)
|
||||
{
|
||||
// Try two-word match first
|
||||
if (i + 1 < words.Length)
|
||||
{
|
||||
var twoWord = $"{words[i]} {words[i + 1]}";
|
||||
if (MealNames.TryGetValue(twoWord, out var tw))
|
||||
{
|
||||
translated.Add(tw);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (MealNames.TryGetValue(words[i], out var single))
|
||||
translated.Add(single);
|
||||
else
|
||||
translated.Add(words[i]); // Keep original if no translation
|
||||
i++;
|
||||
}
|
||||
return string.Join(' ', translated);
|
||||
}
|
||||
|
||||
public static string TranslateIngredient(string name)
|
||||
{
|
||||
// Try exact match first
|
||||
if (Ingredients.TryGetValue(name, out var exact)) return exact;
|
||||
|
||||
// Try two-word, three-word combos from start
|
||||
var words = name.Split(' ');
|
||||
if (words.Length >= 3)
|
||||
{
|
||||
var three = $"{words[0]} {words[1]} {words[2]}";
|
||||
if (Ingredients.TryGetValue(three, out var tw3)) return tw3;
|
||||
}
|
||||
if (words.Length >= 2)
|
||||
{
|
||||
var two = $"{words[0]} {words[1]}";
|
||||
if (Ingredients.TryGetValue(two, out var tw2)) return tw2;
|
||||
}
|
||||
// Single word
|
||||
if (words.Length >= 1 && Ingredients.TryGetValue(words[0], out var sw)) return sw;
|
||||
|
||||
return name; // Keep original
|
||||
}
|
||||
|
||||
public static Recipe TranslateRecipe(Recipe recipe)
|
||||
{
|
||||
recipe.Title = TranslateTitle(recipe.Title);
|
||||
foreach (var ing in recipe.Ingredients)
|
||||
{
|
||||
ing.Name = TranslateIngredient(ing.Name);
|
||||
}
|
||||
return recipe;
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,7 @@ public class TheMealDbClient(HttpClient httpClient)
|
||||
recipe.Ingredients.Add(ingredient);
|
||||
}
|
||||
|
||||
GermanTranslator.TranslateRecipe(recipe);
|
||||
return recipe;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user