add English translation support for recipes with DeepL integration

- Add embedded translations schema to Recipe model with English support
- Create DeepL translation service with batch translation and change detection
- Build translation approval UI with side-by-side editing for all recipe fields
- Integrate translation workflow into add/edit pages with field comparison
- Create complete English recipe routes at /recipes/* mirroring German structure
- Add language switcher component with hreflang SEO tags
- Support image loading from German short_name for English recipes
- Add English API endpoints for all recipe filters (category, tag, icon, season)
- Include layout with English navigation header for all recipe subroutes
This commit is contained in:
2025-12-26 20:28:43 +01:00
parent 731adda897
commit 36a7fac39a
34 changed files with 3061 additions and 44 deletions

View File

@@ -11,6 +11,8 @@ export let isFavorite = false;
export let showFavoriteIndicator = false;
// to manually override lazy loading for top cards
export let loading_strat : "lazy" | "eager" | undefined;
// route prefix for language support (/rezepte or /recipes)
export let routePrefix = '/rezepte';
if(loading_strat === undefined){
loading_strat = "lazy"
}
@@ -27,7 +29,9 @@ onMount(() => {
isloaded = document.querySelector("img")?.complete ? true : false
})
const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
// Use germanShortName for images if available (English recipes), otherwise use short_name (German recipes)
const imageShortName = recipe.germanShortName || recipe.short_name;
const img_name = imageShortName + ".webp?v=" + recipe.dateModified
</script>
<style>
.card_anchor{
@@ -253,7 +257,7 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
<div class=card_anchor class:search_me={search} data-tags=[{recipe.tags}]>
<div class="card" class:margin_right={do_margin_right}>
<a href="/rezepte/{recipe.short_name}" class="card-main-link" aria-label="View recipe: {recipe.name}">
<a href="{routePrefix}/{recipe.short_name}" class="card-main-link" aria-label="View recipe: {recipe.name}">
<span class="visually-hidden">View recipe: {recipe.name}</span>
</a>
<div class=div_div_image >
@@ -261,24 +265,24 @@ const img_name=recipe.short_name + ".webp?v=" + recipe.dateModified
<noscript>
<img id=image class="backdrop_blur" src="https://bocken.org/static/rezepte/thumb/{img_name}" loading={loading_strat} alt="{recipe.alt}"/>
</noscript>
<img class:blur={!isloaded} id=image class="backdrop_blur" src={'https://bocken.org/static/rezepte/thumb/' + recipe.short_name + '.webp'} loading={loading_strat} alt="{recipe.alt}" on:load={() => isloaded=true}/>
<img class:blur={!isloaded} id=image class="backdrop_blur" src={'https://bocken.org/static/rezepte/thumb/' + imageShortName + '.webp'} loading={loading_strat} alt="{recipe.alt}" on:load={() => isloaded=true}/>
</div>
</div>
{#if showFavoriteIndicator && isFavorite}
<div class="favorite-indicator">❤️</div>
{/if}
{#if icon_override || recipe.season.includes(current_month)}
<a href="/rezepte/icon/{recipe.icon}" class=icon>{recipe.icon}</a>
<a href="{routePrefix}/icon/{recipe.icon}" class=icon>{recipe.icon}</a>
{/if}
<div class="card_title">
<a href="/rezepte/category/{recipe.category}" class=category>{recipe.category}</a>
<a href="{routePrefix}/category/{recipe.category}" class=category>{recipe.category}</a>
<div>
<div class=name>{@html recipe.name}</div>
<div class=description>{@html recipe.description}</div>
</div>
<div class=tags>
{#each recipe.tags as tag}
<a href="/rezepte/tag/{tag}" class=tag>{tag}</a>
<a href="{routePrefix}/tag/{tag}" class=tag>{tag}</a>
{/each}
</div>
</div>