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:
@@ -4,13 +4,20 @@ import { onNavigate } from "$app/navigation";
|
|||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import HefeSwapper from './HefeSwapper.svelte';
|
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
|
// Calculate yeast IDs for each yeast ingredient
|
||||||
let yeastIds = {};
|
const yeastIds = $derived.by(() => {
|
||||||
$: {
|
const ids = {};
|
||||||
yeastIds = {};
|
|
||||||
let yeastCounter = 0;
|
let yeastCounter = 0;
|
||||||
if (data.ingredients) {
|
if (data.ingredients) {
|
||||||
for (let listIndex = 0; listIndex < data.ingredients.length; listIndex++) {
|
for (let listIndex = 0; listIndex < data.ingredients.length; listIndex++) {
|
||||||
@@ -18,17 +25,19 @@ $: {
|
|||||||
if (list.list) {
|
if (list.list) {
|
||||||
for (let ingredientIndex = 0; ingredientIndex < list.list.length; ingredientIndex++) {
|
for (let ingredientIndex = 0; ingredientIndex < list.list.length; ingredientIndex++) {
|
||||||
const ingredient = list.list[ingredientIndex];
|
const ingredient = list.list[ingredientIndex];
|
||||||
if (ingredient.name === "Frischhefe" || ingredient.name === "Trockenhefe") {
|
if (ingredient.name === "Frischhefe" || ingredient.name === "Trockenhefe" ||
|
||||||
yeastIds[`${listIndex}-${ingredientIndex}`] = yeastCounter++;
|
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
|
// 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
|
// Progressive enhancement - use JS if available
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -318,11 +327,11 @@ span
|
|||||||
{#if data.ingredients}
|
{#if data.ingredients}
|
||||||
<div class=ingredients>
|
<div class=ingredients>
|
||||||
{#if data.portions}
|
{#if data.portions}
|
||||||
<h3>Portionen:</h3>
|
<h3>{labels.portions}</h3>
|
||||||
{@html convertFloatsToFractions(multiplyFirstAndSecondNumbers(data.portions, multiplier))}
|
{@html convertFloatsToFractions(multiplyFirstAndSecondNumbers(data.portions, multiplier))}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<h3>Menge anpassen:</h3>
|
<h3>{labels.adjustAmount}</h3>
|
||||||
<div class=multipliers>
|
<div class=multipliers>
|
||||||
<form method="get" style="display: inline;">
|
<form method="get" style="display: inline;">
|
||||||
<input type="hidden" name="multiplier" value="0.5" />
|
<input type="hidden" name="multiplier" value="0.5" />
|
||||||
@@ -389,7 +398,7 @@ span
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Zutaten</h2>
|
<h2>{labels.ingredients}</h2>
|
||||||
{#each data.ingredients as list, listIndex}
|
{#each data.ingredients as list, listIndex}
|
||||||
{#if list.name}
|
{#if list.name}
|
||||||
<h3>{list.name}</h3>
|
<h3>{list.name}</h3>
|
||||||
@@ -399,7 +408,7 @@ span
|
|||||||
<div class=amount>{@html adjust_amount(item.amount, multiplier)} {item.unit}</div>
|
<div class=amount>{@html adjust_amount(item.amount, multiplier)} {item.unit}</div>
|
||||||
<div class=name>
|
<div class=name>
|
||||||
{@html item.name.replace("{{multiplier}}", isNaN(parseFloat(item.amount)) ? multiplier : multiplier * parseFloat(item.amount))}
|
{@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}
|
{@const yeastId = yeastIds[`${listIndex}-${ingredientIndex}`] ?? 0}
|
||||||
<HefeSwapper {item} {multiplier} {yeastId} />
|
<HefeSwapper {item} {multiplier} {yeastId} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
<script>
|
<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>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
*{
|
*{
|
||||||
@@ -59,35 +71,35 @@ h4{
|
|||||||
<div class=instructions>
|
<div class=instructions>
|
||||||
<div class=additional_info>
|
<div class=additional_info>
|
||||||
{#if data.preparation}
|
{#if data.preparation}
|
||||||
<div><h4>Vorbereitung:</h4>{data.preparation}</div>
|
<div><h4>{labels.preparation}</h4>{data.preparation}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
{#if data.fermentation}
|
{#if data.fermentation}
|
||||||
{#if data.fermentation.bulk}
|
{#if data.fermentation.bulk}
|
||||||
<div><h4>Stockgare:</h4>{data.fermentation.bulk}</div>
|
<div><h4>{labels.bulkFermentation}</h4>{data.fermentation.bulk}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if data.fermentation.final}
|
{#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}
|
{/if}
|
||||||
|
|
||||||
{#if data.baking.temperature}
|
{#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}
|
||||||
|
|
||||||
{#if data.cooking}
|
{#if data.cooking}
|
||||||
<div><h4>Kochen:</h4>{data.cooking}</div>
|
<div><h4>{labels.cooking}</h4>{data.cooking}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if data.total_time}
|
{#if data.total_time}
|
||||||
<div><h4>Auf dem Teller:</h4>{data.total_time}</div>
|
<div><h4>{labels.onThePlate}</h4>{data.total_time}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if data.instructions}
|
{#if data.instructions}
|
||||||
<h2>Zubereitung</h2>
|
<h2>{labels.instructions}</h2>
|
||||||
{#each data.instructions as list}
|
{#each data.instructions as list}
|
||||||
{#if list.name}
|
{#if list.name}
|
||||||
<h3>{list.name}</h3>
|
<h3>{list.name}</h3>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
|
||||||
|
|
||||||
let { user, showLanguageSelector = false } = $props();
|
let { user, showLanguageSelector = false } = $props();
|
||||||
|
|
||||||
let currentLang = $state('de');
|
let currentLang = $state('de');
|
||||||
@@ -42,7 +44,19 @@
|
|||||||
// Get the current path directly from window
|
// Get the current path directly from window
|
||||||
const path = typeof window !== 'undefined' ? window.location.pathname : currentPath;
|
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;
|
let newPath = path;
|
||||||
if (lang === 'en' && path.startsWith('/rezepte')) {
|
if (lang === 'en' && path.startsWith('/rezepte')) {
|
||||||
newPath = path.replace('/rezepte', '/recipes');
|
newPath = path.replace('/rezepte', '/recipes');
|
||||||
|
|||||||
9
src/lib/stores/recipeTranslation.ts
Normal file
9
src/lib/stores/recipeTranslation.ts
Normal 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);
|
||||||
@@ -14,9 +14,25 @@
|
|||||||
import {stripHtmlTags} from '$lib/js/stripHtmlTags';
|
import {stripHtmlTags} from '$lib/js/stripHtmlTags';
|
||||||
import FavoriteButton from '$lib/components/FavoriteButton.svelte';
|
import FavoriteButton from '$lib/components/FavoriteButton.svelte';
|
||||||
import RecipeLanguageSwitcher from '$lib/components/RecipeLanguageSwitcher.svelte';
|
import RecipeLanguageSwitcher from '$lib/components/RecipeLanguageSwitcher.svelte';
|
||||||
|
import { onMount, onDestroy } from 'svelte';
|
||||||
|
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
|
||||||
|
|
||||||
let { data }: { data: PageData } = $props();
|
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');
|
const isEnglish = $derived(data.lang === 'en');
|
||||||
|
|
||||||
// Use German short_name for images (they're the same for both languages)
|
// Use German short_name for images (they're the same for both languages)
|
||||||
|
|||||||
Reference in New Issue
Block a user