add bilingual labels to recipe components and fix language switcher

- Translate hardcoded German terms in IngredientsPage and InstructionsPage
- Migrate both components to Svelte 5 runes (, , )
- Fix language switcher to use correct short names via shared store
- Add recipeTranslationStore for recipe-specific language switching
This commit is contained in:
2025-12-26 21:35:59 +01:00
parent 6de3d76504
commit 442b2a3145
5 changed files with 82 additions and 22 deletions

View File

@@ -4,13 +4,20 @@ import { onNavigate } from "$app/navigation";
import { browser } from '$app/environment';
import { page } from '$app/stores';
import HefeSwapper from './HefeSwapper.svelte';
export let data
let multiplier = data.multiplier || 1;
let { data } = $props();
let multiplier = $state(data.multiplier || 1);
const isEnglish = $derived(data.lang === 'en');
const labels = $derived({
portions: isEnglish ? 'Portions:' : 'Portionen:',
adjustAmount: isEnglish ? 'Adjust Amount:' : 'Menge anpassen:',
ingredients: isEnglish ? 'Ingredients' : 'Zutaten'
});
// Calculate yeast IDs for each yeast ingredient
let yeastIds = {};
$: {
yeastIds = {};
const yeastIds = $derived.by(() => {
const ids = {};
let yeastCounter = 0;
if (data.ingredients) {
for (let listIndex = 0; listIndex < data.ingredients.length; listIndex++) {
@@ -18,17 +25,19 @@ $: {
if (list.list) {
for (let ingredientIndex = 0; ingredientIndex < list.list.length; ingredientIndex++) {
const ingredient = list.list[ingredientIndex];
if (ingredient.name === "Frischhefe" || ingredient.name === "Trockenhefe") {
yeastIds[`${listIndex}-${ingredientIndex}`] = yeastCounter++;
}
if (ingredient.name === "Frischhefe" || ingredient.name === "Trockenhefe" ||
ingredient.name === "Fresh Yeast" || ingredient.name === "Dry Yeast") {
ids[`${listIndex}-${ingredientIndex}`] = yeastCounter++;
}
}
}
}
}
return ids;
});
// Get all current URL parameters to preserve state in multiplier forms
$: currentParams = browser ? new URLSearchParams(window.location.search) : $page.url.searchParams;
const currentParams = $derived(browser ? new URLSearchParams(window.location.search) : $page.url.searchParams);
// Progressive enhancement - use JS if available
onMount(() => {
@@ -318,11 +327,11 @@ span
{#if data.ingredients}
<div class=ingredients>
{#if data.portions}
<h3>Portionen:</h3>
<h3>{labels.portions}</h3>
{@html convertFloatsToFractions(multiplyFirstAndSecondNumbers(data.portions, multiplier))}
{/if}
<h3>Menge anpassen:</h3>
<h3>{labels.adjustAmount}</h3>
<div class=multipliers>
<form method="get" style="display: inline;">
<input type="hidden" name="multiplier" value="0.5" />
@@ -389,7 +398,7 @@ span
</form>
</div>
<h2>Zutaten</h2>
<h2>{labels.ingredients}</h2>
{#each data.ingredients as list, listIndex}
{#if list.name}
<h3>{list.name}</h3>
@@ -399,7 +408,7 @@ span
<div class=amount>{@html adjust_amount(item.amount, multiplier)} {item.unit}</div>
<div class=name>
{@html item.name.replace("{{multiplier}}", isNaN(parseFloat(item.amount)) ? multiplier : multiplier * parseFloat(item.amount))}
{#if item.name === "Frischhefe" || item.name === "Trockenhefe"}
{#if item.name === "Frischhefe" || item.name === "Trockenhefe" || item.name === "Fresh Yeast" || item.name === "Dry Yeast"}
{@const yeastId = yeastIds[`${listIndex}-${ingredientIndex}`] ?? 0}
<HefeSwapper {item} {multiplier} {yeastId} />
{/if}

View File

@@ -1,5 +1,17 @@
<script>
export let data
let { data } = $props();
const isEnglish = $derived(data.lang === 'en');
const labels = $derived({
preparation: isEnglish ? 'Preparation:' : 'Vorbereitung:',
bulkFermentation: isEnglish ? 'Bulk Fermentation:' : 'Stockgare:',
finalProof: isEnglish ? 'Final Proof:' : 'Stückgare:',
baking: isEnglish ? 'Baking:' : 'Backen:',
cooking: isEnglish ? 'Cooking:' : 'Kochen:',
onThePlate: isEnglish ? 'On the Plate:' : 'Auf dem Teller:',
instructions: isEnglish ? 'Instructions' : 'Zubereitung',
at: isEnglish ? 'at' : 'bei'
});
</script>
<style>
*{
@@ -59,35 +71,35 @@ h4{
<div class=instructions>
<div class=additional_info>
{#if data.preparation}
<div><h4>Vorbereitung:</h4>{data.preparation}</div>
<div><h4>{labels.preparation}</h4>{data.preparation}</div>
{/if}
{#if data.fermentation}
{#if data.fermentation.bulk}
<div><h4>Stockgare:</h4>{data.fermentation.bulk}</div>
<div><h4>{labels.bulkFermentation}</h4>{data.fermentation.bulk}</div>
{/if}
{#if data.fermentation.final}
<div><h4>Stückgare:</h4> {data.fermentation.final}</div>
<div><h4>{labels.finalProof}</h4> {data.fermentation.final}</div>
{/if}
{/if}
{#if data.baking.temperature}
<div><h4>Backen:</h4> {data.baking.length} bei {data.baking.temperature} °C {data.baking.mode}</div>
<div><h4>{labels.baking}</h4> {data.baking.length} {labels.at} {data.baking.temperature} °C {data.baking.mode}</div>
{/if}
{#if data.cooking}
<div><h4>Kochen:</h4>{data.cooking}</div>
<div><h4>{labels.cooking}</h4>{data.cooking}</div>
{/if}
{#if data.total_time}
<div><h4>Auf dem Teller:</h4>{data.total_time}</div>
<div><h4>{labels.onThePlate}</h4>{data.total_time}</div>
{/if}
</div>
{#if data.instructions}
<h2>Zubereitung</h2>
<h2>{labels.instructions}</h2>
{#each data.instructions as list}
{#if list.name}
<h3>{list.name}</h3>

View File

@@ -2,6 +2,8 @@
import { onMount } from "svelte";
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
let { user, showLanguageSelector = false } = $props();
let currentLang = $state('de');
@@ -42,7 +44,19 @@
// Get the current path directly from window
const path = typeof window !== 'undefined' ? window.location.pathname : currentPath;
// Convert current path to target language
// If we have recipe translation data from store, use the correct short names
const recipeData = $recipeTranslationStore;
if (recipeData) {
if (lang === 'en' && recipeData.englishShortName) {
goto(`/recipes/${recipeData.englishShortName}`);
return;
} else if (lang === 'de' && recipeData.germanShortName) {
goto(`/rezepte/${recipeData.germanShortName}`);
return;
}
}
// Convert current path to target language (for non-recipe pages)
let newPath = path;
if (lang === 'en' && path.startsWith('/rezepte')) {
newPath = path.replace('/rezepte', '/recipes');

View File

@@ -0,0 +1,9 @@
import { writable } from 'svelte/store';
interface RecipeTranslationData {
germanShortName: string;
englishShortName?: string;
hasEnglishTranslation: boolean;
}
export const recipeTranslationStore = writable<RecipeTranslationData | null>(null);

View File

@@ -14,9 +14,25 @@
import {stripHtmlTags} from '$lib/js/stripHtmlTags';
import FavoriteButton from '$lib/components/FavoriteButton.svelte';
import RecipeLanguageSwitcher from '$lib/components/RecipeLanguageSwitcher.svelte';
import { onMount, onDestroy } from 'svelte';
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
let { data }: { data: PageData } = $props();
// Set store for recipe translation data so UserHeader can access it
onMount(() => {
recipeTranslationStore.set({
germanShortName: data.germanShortName || data.short_name,
englishShortName: data.englishShortName,
hasEnglishTranslation: data.hasEnglishTranslation || false
});
});
// Clear store when leaving recipe page
onDestroy(() => {
recipeTranslationStore.set(null);
});
const isEnglish = $derived(data.lang === 'en');
// Use German short_name for images (they're the same for both languages)