diff --git a/backend/Services/GermanTranslator.cs b/backend/Services/GermanTranslator.cs new file mode 100644 index 0000000..8592503 --- /dev/null +++ b/backend/Services/GermanTranslator.cs @@ -0,0 +1,188 @@ +using MealPlanner.Models; + +namespace MealPlanner.Services; + +public static class GermanTranslator +{ + private static readonly Dictionary 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 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(); + 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; + } +} diff --git a/backend/Services/TheMealDbClient.cs b/backend/Services/TheMealDbClient.cs index e4b9a90..2c223ab 100644 --- a/backend/Services/TheMealDbClient.cs +++ b/backend/Services/TheMealDbClient.cs @@ -95,6 +95,7 @@ public class TheMealDbClient(HttpClient httpClient) recipe.Ingredients.Add(ingredient); } + GermanTranslator.TranslateRecipe(recipe); return recipe; } diff --git a/frontend/src/components/MealCard.vue b/frontend/src/components/MealCard.vue index 4772cb1..78342bf 100644 --- a/frontend/src/components/MealCard.vue +++ b/frontend/src/components/MealCard.vue @@ -6,7 +6,7 @@ -
+
-
+

{{ entry.recipe.title }}

{{ entry.recipe.ingredients.length }} Zutaten @@ -76,6 +76,7 @@ defineProps<{ defineEmits<{ swap: [entry: MealPlanEntry] reroll: [entry: MealPlanEntry] + click: [] }>() const imgError = ref(false) diff --git a/frontend/src/views/WeekPlanView.vue b/frontend/src/views/WeekPlanView.vue index 5d98da3..ee27925 100644 --- a/frontend/src/views/WeekPlanView.vue +++ b/frontend/src/views/WeekPlanView.vue @@ -65,6 +65,7 @@ :day-name="getDayName(entry.date)" @swap="openSwapModal(entry)" @reroll="handleReroll(entry)" + @click="selectedRecipe = entry.recipe" />

@@ -98,6 +99,13 @@
+ + + import { ref, computed, onMounted } from 'vue' import MealCard from '../components/MealCard.vue' +import RecipeDetail from '../components/RecipeDetail.vue' import SwapModal from '../components/SwapModal.vue' import { useMealPlanStore, useRecipesStore } from '../stores/mealPlan' -import type { MealPlanEntry } from '../stores/mealPlan' +import type { MealPlanEntry, Recipe } from '../stores/mealPlan' const store = useMealPlanStore() const recipesStore = useRecipesStore() const swapEntry = ref(null) +const selectedRecipe = ref(null) const DAY_NAMES = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']