i18n(recipes): finish remaining ternaries across components and pages
Migrate FavoritesFilter, IconFilter, TagFilter, FilterPanel, HefeSwapper and the offline-shell, season/[month], icon/[icon], favorites, search, tips-and-tricks, and index pages to use the recipes i18n dictionary. Add corresponding keys for filter toggles, filter placeholders, yeast toggle title, recipes-growing suffix, search "for" preposition, and favorites count labels. Strip unused isEnglish derivations from layout, tag, and category landing pages. Bump site version to 1.56.1.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.56.0",
|
||||
"version": "1.56.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script>
|
||||
import TagChip from '$lib/components/recipes/TagChip.svelte';
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
|
||||
|
||||
let {
|
||||
categories = [],
|
||||
@@ -9,9 +11,9 @@
|
||||
useAndLogic = true
|
||||
} = $props();
|
||||
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const label = $derived(isEnglish ? 'Category' : 'Kategorie');
|
||||
const selectLabel = $derived(isEnglish ? 'Select category...' : 'Kategorie auswählen...');
|
||||
const t = $derived(m[/** @type {RecipesLang} */ (lang)]);
|
||||
const label = $derived(t.category_nav);
|
||||
const selectLabel = $derived(t.select_category_placeholder);
|
||||
|
||||
// Convert selected to array for OR mode, keep as single value for AND mode
|
||||
const selectedArray = $derived(
|
||||
|
||||
@@ -74,7 +74,12 @@ const t: Record<string, Record<string, string>> = {
|
||||
moveReferenceDownAria: 'Referenz nach unten verschieben',
|
||||
removeReferenceAria: 'Referenz entfernen',
|
||||
moveListUpAria: 'Liste nach oben verschieben',
|
||||
moveListDownAria: 'Liste nach unten verschieben'
|
||||
moveListDownAria: 'Liste nach unten verschieben',
|
||||
notSet: 'Nicht gesetzt',
|
||||
duration: 'Dauer',
|
||||
temperature: 'Temperatur',
|
||||
mode: 'Modus',
|
||||
customModePlaceholder: 'oder eigenen Modus eingeben…'
|
||||
},
|
||||
en: {
|
||||
preparation: 'Preparation:',
|
||||
@@ -109,7 +114,12 @@ const t: Record<string, Record<string, string>> = {
|
||||
moveReferenceDownAria: 'Move reference down',
|
||||
removeReferenceAria: 'Remove reference',
|
||||
moveListUpAria: 'Move list up',
|
||||
moveListDownAria: 'Move list down'
|
||||
moveListDownAria: 'Move list down',
|
||||
notSet: 'Not set',
|
||||
duration: 'Duration',
|
||||
temperature: 'Temperature',
|
||||
mode: 'Mode',
|
||||
customModePlaceholder: 'or enter custom mode…'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1017,7 +1027,7 @@ h3{
|
||||
{#if add_info.baking.mode}<span class="chip mode">{add_info.baking.mode}</span>{/if}
|
||||
</span>
|
||||
{:else if !bakingExpanded}
|
||||
<span class="baking-summary muted">{lang === 'de' ? 'Nicht gesetzt' : 'Not set'}</span>
|
||||
<span class="baking-summary muted">{t[lang].notSet}</span>
|
||||
{/if}
|
||||
<svg class="chevron" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
|
||||
<path d="M7 10l5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
@@ -1027,7 +1037,7 @@ h3{
|
||||
{#if bakingExpanded}
|
||||
<div id="baking-fields-{lang}" class="baking-form">
|
||||
<div class="baking-field">
|
||||
<label for="baking-length-{lang}">{lang === 'de' ? 'Dauer' : 'Duration'}</label>
|
||||
<label for="baking-length-{lang}">{t[lang].duration}</label>
|
||||
<div class="input-wrap">
|
||||
<input
|
||||
id="baking-length-{lang}"
|
||||
@@ -1041,7 +1051,7 @@ h3{
|
||||
</div>
|
||||
</div>
|
||||
<div class="baking-field">
|
||||
<label for="baking-temp-{lang}">{lang === 'de' ? 'Temperatur' : 'Temperature'}</label>
|
||||
<label for="baking-temp-{lang}">{t[lang].temperature}</label>
|
||||
<div class="input-wrap">
|
||||
<input
|
||||
id="baking-temp-{lang}"
|
||||
@@ -1055,7 +1065,7 @@ h3{
|
||||
</div>
|
||||
</div>
|
||||
<div class="baking-field mode-field">
|
||||
<span class="mode-label">{lang === 'de' ? 'Modus' : 'Mode'}</span>
|
||||
<span class="mode-label">{t[lang].mode}</span>
|
||||
<div class="mode-chips">
|
||||
{#each BAKING_MODES[lang] as mode}
|
||||
<button
|
||||
@@ -1070,7 +1080,7 @@ h3{
|
||||
type="text"
|
||||
class="mode-custom"
|
||||
bind:value={add_info.baking.mode}
|
||||
placeholder={lang === 'de' ? 'oder eigenen Modus eingeben…' : 'or enter custom mode…'}
|
||||
placeholder={t[lang].customModePlaceholder}
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script>
|
||||
import Toggle from '$lib/components/Toggle.svelte';
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
|
||||
|
||||
let {
|
||||
enabled = false,
|
||||
@@ -8,8 +10,8 @@
|
||||
lang = 'de'
|
||||
} = $props();
|
||||
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const label = $derived(isEnglish ? 'Favorites' : 'Favoriten');
|
||||
const t = $derived(m[/** @type {RecipesLang} */ (lang)]);
|
||||
const label = $derived(t.favorites);
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let checked = $state(enabled);
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import SeasonFilter from './SeasonFilter.svelte';
|
||||
import FavoritesFilter from './FavoritesFilter.svelte';
|
||||
import LogicModeToggle from './LogicModeToggle.svelte';
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
|
||||
|
||||
let {
|
||||
availableCategories = [],
|
||||
@@ -27,6 +29,7 @@
|
||||
onLogicModeToggle = () => {}
|
||||
} = $props();
|
||||
|
||||
const t = $derived(m[/** @type {RecipesLang} */ (lang)]);
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const months = $derived(isEnglish
|
||||
? ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
|
||||
@@ -132,7 +135,7 @@
|
||||
|
||||
<div class="filter-wrapper">
|
||||
<button class="toggle-button" onclick={toggleFilters} type="button">
|
||||
<span>{filtersOpen ? (isEnglish ? 'Hide Filters' : 'Filter ausblenden') : (isEnglish ? 'Show Filters' : 'Filter einblenden')}</span>
|
||||
<span>{filtersOpen ? t.hide_filters : t.show_filters}</span>
|
||||
<span class="arrow" class:open={filtersOpen}>▼</span>
|
||||
</button>
|
||||
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { enhance } from '$app/forms';
|
||||
import { page } from '$app/state';
|
||||
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
|
||||
|
||||
let { item, multiplier = 1, yeastId = 0, lang = 'de' } = $props();
|
||||
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const toggleTitle = $derived(isEnglish
|
||||
? 'Switch between fresh yeast and dry yeast'
|
||||
: 'Zwischen Frischhefe und Trockenhefe wechseln');
|
||||
const t = $derived(m[/** @type {RecipesLang} */ (lang)]);
|
||||
const toggleTitle = $derived(t.yeast_toggle_title);
|
||||
|
||||
// Get all current URL parameters to preserve state
|
||||
const currentParams = $derived(browser ? new URLSearchParams(window.location.search) : page.url.searchParams);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script>
|
||||
import TagChip from '$lib/components/recipes/TagChip.svelte';
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
|
||||
|
||||
let {
|
||||
availableIcons = [],
|
||||
@@ -9,9 +11,9 @@
|
||||
useAndLogic = true
|
||||
} = $props();
|
||||
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const t = $derived(m[/** @type {RecipesLang} */ (lang)]);
|
||||
const label = 'Icon';
|
||||
const selectLabel = $derived(isEnglish ? 'Select icon...' : 'Icon auswählen...');
|
||||
const selectLabel = $derived(t.select_icon_placeholder);
|
||||
|
||||
// Convert selected to array for OR mode, keep as single value for AND mode
|
||||
const selectedArray = $derived(
|
||||
|
||||
@@ -5,6 +5,8 @@ import Croissant from '@lucide/svelte/icons/croissant';
|
||||
import Flame from '@lucide/svelte/icons/flame';
|
||||
import CookingPot from '@lucide/svelte/icons/cooking-pot';
|
||||
import UtensilsCrossed from '@lucide/svelte/icons/utensils-crossed';
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
|
||||
let { data } = $props();
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
@@ -99,16 +101,18 @@ const flattenedInstructions = $derived.by(() => {
|
||||
return flattenInstructionReferences(data.instructions, lang);
|
||||
});
|
||||
|
||||
const isEnglish = $derived(data.lang === 'en');
|
||||
const lang = $derived(/** @type {RecipesLang} */ (data.lang));
|
||||
const t = $derived(m[lang]);
|
||||
const isEnglish = $derived(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'
|
||||
preparation: t.preparation_section,
|
||||
bulkFermentation: t.bulk_fermentation,
|
||||
finalProof: t.final_proof,
|
||||
baking: t.baking_section,
|
||||
cooking: t.cooking_section,
|
||||
onThePlate: t.on_the_plate,
|
||||
instructions: t.instructions_label,
|
||||
at: t.at_temp
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script>
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
|
||||
|
||||
let {
|
||||
useAndLogic = true,
|
||||
@@ -6,10 +8,10 @@
|
||||
lang = 'de'
|
||||
} = $props();
|
||||
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const label = $derived(isEnglish ? 'Filter Mode' : 'Filter-Modus');
|
||||
const andLabel = $derived(isEnglish ? 'AND' : 'UND');
|
||||
const orLabel = $derived(isEnglish ? 'OR' : 'ODER');
|
||||
const t = $derived(m[/** @type {RecipesLang} */ (lang)]);
|
||||
const label = $derived(t.filter_mode);
|
||||
const andLabel = $derived(t.and_label);
|
||||
const orLabel = $derived(t.or_label);
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
let checked = $state(useAndLogic);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import { browser } from '$app/environment';
|
||||
import FilterPanel from './FilterPanel.svelte';
|
||||
import { getCategories } from '$lib/js/categories';
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
|
||||
|
||||
// Filter props for different contexts
|
||||
let {
|
||||
@@ -20,12 +22,13 @@
|
||||
// Generate categories internally based on language
|
||||
const categories = $derived(getCategories(lang));
|
||||
|
||||
const t = $derived(m[/** @type {RecipesLang} */ (lang)]);
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const searchResultsUrl = $derived(isEnglish ? '/recipes/search' : '/rezepte/search');
|
||||
const labels = $derived({
|
||||
placeholder: isEnglish ? 'Search...' : 'Suche...',
|
||||
searchTitle: isEnglish ? 'Search' : 'Suchen',
|
||||
clearTitle: isEnglish ? 'Clear search' : 'Sucheintrag löschen'
|
||||
placeholder: t.search_placeholder_short,
|
||||
searchTitle: t.search_title,
|
||||
clearTitle: t.clear_search_title
|
||||
});
|
||||
|
||||
let searchQuery = $state('');
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script>
|
||||
import TagChip from '$lib/components/recipes/TagChip.svelte';
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
|
||||
|
||||
let {
|
||||
selectedSeasons = [],
|
||||
@@ -8,9 +10,9 @@
|
||||
months = []
|
||||
} = $props();
|
||||
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const label = $derived(isEnglish ? 'Season' : 'Saison');
|
||||
const selectLabel = $derived(isEnglish ? 'Select season...' : 'Saison auswählen...');
|
||||
const t = $derived(m[/** @type {RecipesLang} */ (lang)]);
|
||||
const label = $derived(t.season_nav);
|
||||
const selectLabel = $derived(t.select_season_placeholder);
|
||||
|
||||
let inputValue = $state('');
|
||||
let dropdownOpen = $state(false);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script>
|
||||
import TagChip from '$lib/components/recipes/TagChip.svelte';
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
|
||||
|
||||
let {
|
||||
availableTags = [],
|
||||
@@ -8,9 +10,9 @@
|
||||
lang = 'de'
|
||||
} = $props();
|
||||
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const t = $derived(m[/** @type {RecipesLang} */ (lang)]);
|
||||
const label = 'Tags';
|
||||
const addTagLabel = $derived(isEnglish ? 'Type or select tag...' : 'Tag eingeben oder auswählen...');
|
||||
const addTagLabel = $derived(t.add_tag_placeholder);
|
||||
|
||||
// Filter out already selected tags
|
||||
const unselectedTags = $derived(availableTags.filter(t => !selectedTags.includes(t)));
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script>
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
let { item, ondelete, onedit, isEnglish = false } = $props();
|
||||
const t = $derived(isEnglish ? m.en : m.de);
|
||||
|
||||
/** @param {string} url */
|
||||
function getDomain(url) {
|
||||
@@ -142,8 +144,8 @@
|
||||
|
||||
<div class="card">
|
||||
<div class="accent"></div>
|
||||
<button class="card-btn edit-btn" onclick={() => onedit(item)} aria-label={isEnglish ? 'Edit' : 'Bearbeiten'}>✎</button>
|
||||
<button class="card-btn delete-btn" onclick={() => ondelete(item._id)} aria-label={isEnglish ? 'Delete' : 'Löschen'}>✕</button>
|
||||
<button class="card-btn edit-btn" onclick={() => onedit(item)} aria-label={t.edit}>✎</button>
|
||||
<button class="card-btn delete-btn" onclick={() => ondelete(item._id)} aria-label={t.delete}>✕</button>
|
||||
<div class="body">
|
||||
<p class="name">{item.name}</p>
|
||||
{#if item.links?.length}
|
||||
|
||||
@@ -139,5 +139,80 @@ export const de = {
|
||||
tips_title: 'Tipps & Tricks',
|
||||
favorites_meta_description: 'Meine favorisierten Rezepte aus der Bockenschen Küche.',
|
||||
empty_favorites_1: 'Du hast noch keine Rezepte als Favoriten gespeichert.',
|
||||
empty_favorites_2: 'Besuche ein Rezept und klicke auf das Herz-Symbol, um es zu deinen Favoriten hinzuzufügen.'
|
||||
empty_favorites_2: 'Besuche ein Rezept und klicke auf das Herz-Symbol, um es zu deinen Favoriten hinzuzufügen.',
|
||||
|
||||
// Filters
|
||||
filter_mode: 'Filter-Modus',
|
||||
and_label: 'UND',
|
||||
or_label: 'ODER',
|
||||
select_category_placeholder: 'Kategorie auswählen…',
|
||||
select_season_placeholder: 'Saison auswählen…',
|
||||
|
||||
// Search component
|
||||
search_placeholder_short: 'Suche…',
|
||||
search_title: 'Suchen',
|
||||
clear_search_title: 'Sucheintrag löschen',
|
||||
|
||||
// Tag / Category landing
|
||||
recipes_with_keyword: 'Rezepte mit Stichwort',
|
||||
recipes_in_category: 'Rezepte in Kategorie',
|
||||
|
||||
// Card actions
|
||||
edit: 'Bearbeiten',
|
||||
delete: 'Löschen',
|
||||
|
||||
// Administration page
|
||||
administration_title: 'Administration',
|
||||
untranslated_recipes: 'Unübersetzte Rezepte',
|
||||
alt_text_generator: 'Alt-Text Generator',
|
||||
image_colors: 'Bildfarben',
|
||||
nutrition_mappings: 'Nährwert-Zuordnungen',
|
||||
|
||||
// Recipe detail page (long site title variant)
|
||||
site_title_long: "Bocken'sche Rezepte",
|
||||
|
||||
// InstructionsPage section labels
|
||||
preparation_section: 'Vorbereitung:',
|
||||
bulk_fermentation: 'Stockgare:',
|
||||
final_proof: 'Stückgare:',
|
||||
baking_section: 'Backen:',
|
||||
cooking_section: 'Kochen:',
|
||||
on_the_plate: 'Auf dem Teller:',
|
||||
instructions_label: 'Zubereitung',
|
||||
at_temp: 'bei',
|
||||
|
||||
// CreateStepList baking
|
||||
not_set: 'Nicht gesetzt',
|
||||
duration: 'Dauer',
|
||||
temperature: 'Temperatur',
|
||||
mode_label: 'Modus',
|
||||
custom_mode_placeholder: 'oder eigenen Modus eingeben…',
|
||||
|
||||
// Administration page descriptions
|
||||
administration_description: 'Rezepte und Inhalte verwalten',
|
||||
untranslated_description: 'Rezepte ansehen und verwalten, die übersetzt werden müssen',
|
||||
alt_text_description: 'Alternativtext für Rezeptbilder mit KI generieren',
|
||||
image_colors_description: 'Dominante Farben aus Rezeptbildern für Ladeplatzhalter extrahieren',
|
||||
nutrition_mappings_description: 'Kalorien- und Nährwertdaten für alle Rezepte generieren oder aktualisieren',
|
||||
|
||||
// Smaller filters / pages
|
||||
loading_offline: 'Lade Offline-Inhalte…',
|
||||
hide_filters: 'Filter ausblenden',
|
||||
show_filters: 'Filter einblenden',
|
||||
select_icon_placeholder: 'Icon auswählen…',
|
||||
add_tag_placeholder: 'Tag eingeben oder auswählen…',
|
||||
|
||||
// Index / tips / yeast
|
||||
recipes_growing_suffix: 'Rezepte und stetig wachsend…',
|
||||
recipes_collection_meta: 'Eine stetig wachsende Ansammlung an Rezepten aus der Bockenschen Küche.',
|
||||
tips_description: "Eine stetig wachsende Ansammlung an Rezepten aus der Bockenschen Küche.",
|
||||
yeast_toggle_title: 'Zwischen Frischhefe und Trockenhefe wechseln',
|
||||
|
||||
// Search results pageTitle
|
||||
search_results_for_word: 'für',
|
||||
|
||||
// Favorites count label
|
||||
favorites_count_label: 'favorisierte Rezepte',
|
||||
favorite_recipe_singular: 'favorite recipe',
|
||||
favorite_recipes_plural: 'favorite recipes'
|
||||
} as const;
|
||||
|
||||
@@ -139,5 +139,80 @@ export const en = {
|
||||
tips_title: 'Tips & Tricks',
|
||||
favorites_meta_description: "My favorite recipes from Bocken's kitchen.",
|
||||
empty_favorites_1: "You haven't saved any recipes as favorites yet.",
|
||||
empty_favorites_2: 'Visit a recipe and click the heart icon to add it to your favorites.'
|
||||
empty_favorites_2: 'Visit a recipe and click the heart icon to add it to your favorites.',
|
||||
|
||||
// Filters
|
||||
filter_mode: 'Filter Mode',
|
||||
and_label: 'AND',
|
||||
or_label: 'OR',
|
||||
select_category_placeholder: 'Select category…',
|
||||
select_season_placeholder: 'Select season…',
|
||||
|
||||
// Search component
|
||||
search_placeholder_short: 'Search…',
|
||||
search_title: 'Search',
|
||||
clear_search_title: 'Clear search',
|
||||
|
||||
// Tag / Category landing
|
||||
recipes_with_keyword: 'Recipes with Keyword',
|
||||
recipes_in_category: 'Recipes in Category',
|
||||
|
||||
// Card actions
|
||||
edit: 'Edit',
|
||||
delete: 'Delete',
|
||||
|
||||
// Administration page
|
||||
administration_title: 'Administration',
|
||||
untranslated_recipes: 'Untranslated Recipes',
|
||||
alt_text_generator: 'Alt-Text Generator',
|
||||
image_colors: 'Image Colors',
|
||||
nutrition_mappings: 'Nutrition Mappings',
|
||||
|
||||
// Recipe detail page
|
||||
site_title_long: 'Bocken Recipes',
|
||||
|
||||
// InstructionsPage section labels
|
||||
preparation_section: 'Preparation:',
|
||||
bulk_fermentation: 'Bulk Fermentation:',
|
||||
final_proof: 'Final Proof:',
|
||||
baking_section: 'Baking:',
|
||||
cooking_section: 'Cooking:',
|
||||
on_the_plate: 'On the Plate:',
|
||||
instructions_label: 'Instructions',
|
||||
at_temp: 'at',
|
||||
|
||||
// CreateStepList baking
|
||||
not_set: 'Not set',
|
||||
duration: 'Duration',
|
||||
temperature: 'Temperature',
|
||||
mode_label: 'Mode',
|
||||
custom_mode_placeholder: 'or enter custom mode…',
|
||||
|
||||
// Administration page descriptions
|
||||
administration_description: 'Manage recipes and content',
|
||||
untranslated_description: 'View and manage recipes that need translation',
|
||||
alt_text_description: 'Generate alternative text for recipe images using AI',
|
||||
image_colors_description: 'Extract dominant colors from recipe images for loading placeholders',
|
||||
nutrition_mappings_description: 'Generate or regenerate calorie and nutrition data for all recipes',
|
||||
|
||||
// Smaller filters / pages
|
||||
loading_offline: 'Loading offline content…',
|
||||
hide_filters: 'Hide Filters',
|
||||
show_filters: 'Show Filters',
|
||||
select_icon_placeholder: 'Select icon…',
|
||||
add_tag_placeholder: 'Type or select tag…',
|
||||
|
||||
// Index / tips / yeast
|
||||
recipes_growing_suffix: 'recipes and constantly growing…',
|
||||
recipes_collection_meta: "A constantly growing collection of recipes from Bocken's kitchen.",
|
||||
tips_description: "A constantly growing collection of recipes from Bocken's kitchen.",
|
||||
yeast_toggle_title: 'Switch between fresh yeast and dry yeast',
|
||||
|
||||
// Search results pageTitle
|
||||
search_results_for_word: 'for',
|
||||
|
||||
// Favorites count label
|
||||
favorites_count_label: 'favorite recipes',
|
||||
favorite_recipe_singular: 'favorite recipe',
|
||||
favorite_recipes_plural: 'favorite recipes'
|
||||
} as const satisfies Record<keyof typeof de, string>;
|
||||
|
||||
@@ -60,7 +60,6 @@ import { m } from '$lib/js/recipesI18n';
|
||||
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
|
||||
const lang = $derived(/** @type {RecipesLang} */ (data.lang));
|
||||
const t = $derived(m[lang]);
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const labels = $derived({
|
||||
allRecipes: t.all_recipes,
|
||||
favorites: t.favorites,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
hasActiveSearch = ids.size < data.all_brief.length;
|
||||
}
|
||||
|
||||
const isEnglish = $derived(data.lang === 'en');
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const categories = $derived(getCategories(data.lang));
|
||||
|
||||
// Pick a seasonal hero recipe (changes daily) — only recipes with hashed images
|
||||
@@ -122,15 +122,11 @@
|
||||
|
||||
const labels = $derived({
|
||||
title: t.index_title,
|
||||
subheading: isEnglish
|
||||
? `${data.all_brief.length} recipes and constantly growing...`
|
||||
: `${data.all_brief.length} Rezepte und stetig wachsend...`,
|
||||
subheading: `${data.all_brief.length} ${t.recipes_growing_suffix}`,
|
||||
all: t.all,
|
||||
inSeason: t.in_season_now,
|
||||
metaTitle: t.site_title,
|
||||
metaDescription: isEnglish
|
||||
? "A constantly growing collection of recipes from Bocken's kitchen."
|
||||
: "Eine stetig wachsende Ansammlung an Rezepten aus der Bockenschen Küche.",
|
||||
metaDescription: t.recipes_collection_meta,
|
||||
metaAlt: t.meta_alt_hero
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
import ErrorView from '$lib/components/ErrorView.svelte';
|
||||
import { getErrorTitle, getErrorDescription, errorLabels, pick } from '$lib/js/errorStrings';
|
||||
import { m } from '$lib/js/recipesI18n';
|
||||
|
||||
let status = $derived(page.status);
|
||||
let error = $derived(page.error as any);
|
||||
@@ -37,23 +38,22 @@
|
||||
status === 404 && isEnglishRoute && germanRecipeExists && !checkingGermanRecipe
|
||||
);
|
||||
|
||||
const t = $derived(m[isEnglish ? 'en' : 'de']);
|
||||
|
||||
let title = $derived(
|
||||
status === 404 ? (isEnglish ? 'Recipe Not Found' : 'Rezept nicht gefunden')
|
||||
: getErrorTitle(status, isEnglish)
|
||||
status === 404 ? t.recipe_not_found : getErrorTitle(status, isEnglish)
|
||||
);
|
||||
|
||||
let description = $derived(
|
||||
showGermanFallback
|
||||
? 'This recipe has not been translated to English yet, but the German version is available.'
|
||||
: status === 404
|
||||
? (isEnglish ? 'The requested recipe could not be found.' : 'Das angeforderte Rezept konnte nicht gefunden werden.')
|
||||
? t.recipe_not_found_desc
|
||||
: getErrorDescription(status, isEnglish)
|
||||
);
|
||||
|
||||
let details = $derived(
|
||||
checkingGermanRecipe
|
||||
? (isEnglish ? 'Checking for German version…' : 'Suche nach deutscher Version…')
|
||||
: error?.details
|
||||
checkingGermanRecipe ? t.checking_german_version : error?.details
|
||||
);
|
||||
|
||||
let recipesHref = $derived(resolve('/[recipeLang=recipeLang]', { recipeLang: isEnglishRoute ? 'recipes' : 'rezepte' }));
|
||||
@@ -75,7 +75,7 @@
|
||||
{#snippet actions()}
|
||||
{#if status === 401}
|
||||
<button class="link link-primary" onclick={login}>{pick(errorLabels.login, isEnglish)}</button>
|
||||
<a class="link" href={recipesHref}>{isEnglish ? 'Recipes' : 'Rezepte'}</a>
|
||||
<a class="link" href={recipesHref}>{t.recipes_link}</a>
|
||||
{:else if showGermanFallback}
|
||||
<button class="link link-primary" onclick={viewGermanRecipe}>View German recipe</button>
|
||||
{#if user}
|
||||
@@ -84,9 +84,9 @@
|
||||
<a class="link" href={recipesHref}>Recipes</a>
|
||||
{:else if status === 500}
|
||||
<button class="link link-primary" onclick={goBack}>{pick(errorLabels.tryAgain, isEnglish)}</button>
|
||||
<a class="link" href={recipesHref}>{isEnglish ? 'Recipes' : 'Rezepte'}</a>
|
||||
<a class="link" href={recipesHref}>{t.recipes_link}</a>
|
||||
{:else}
|
||||
<a class="link link-primary" href={recipesHref}>{isEnglish ? 'Recipes' : 'Rezepte'}</a>
|
||||
<a class="link link-primary" href={recipesHref}>{t.recipes_link}</a>
|
||||
<button class="link" onclick={goBack}>{pick(errorLabels.goBack, isEnglish)}</button>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import FavoriteButton from '$lib/components/FavoriteButton.svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { recipeTranslationStore } from '$lib/stores/recipeTranslation.svelte';
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
|
||||
let { data } = $props<{ data: PageData }>();
|
||||
|
||||
@@ -32,7 +33,9 @@
|
||||
recipeTranslationStore.set(null);
|
||||
});
|
||||
|
||||
const isEnglish = $derived(data.lang === 'en');
|
||||
const lang = $derived(data.lang as RecipesLang);
|
||||
const t = $derived(m[lang]);
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
|
||||
// Use mediapath from images array (includes hash for cache busting)
|
||||
// Fallback to short_name.webp for backward compatibility
|
||||
@@ -100,10 +103,10 @@
|
||||
const formatted_display_date = $derived(display_date.toLocaleDateString(isEnglish ? 'en-US' : 'de-DE', options));
|
||||
|
||||
const labels = $derived({
|
||||
season: isEnglish ? 'Season:' : 'Saison:',
|
||||
keywords: isEnglish ? 'Keywords:' : 'Stichwörter:',
|
||||
lastModified: isEnglish ? 'Last modified:' : 'Letzte Änderung:',
|
||||
title: isEnglish ? "Bocken Recipes" : "Bocken'sche Rezepte"
|
||||
season: t.season_label,
|
||||
keywords: t.keywords_colon,
|
||||
lastModified: t.last_modified,
|
||||
title: t.site_title_long
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
|
||||
@@ -1,50 +1,39 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
|
||||
let { data } = $props<{ data: PageData }>();
|
||||
const t = $derived(m[data.lang as RecipesLang]);
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
const isEnglish = data.lang === 'en';
|
||||
const pageTitle = isEnglish ? 'Administration' : 'Administration';
|
||||
const pageDescription = isEnglish
|
||||
? 'Manage recipes and content'
|
||||
: 'Rezepte und Inhalte verwalten';
|
||||
const pageTitle = $derived(t.administration_title);
|
||||
const pageDescription = $derived(t.administration_description);
|
||||
|
||||
// svelte-ignore state_referenced_locally
|
||||
const links = [
|
||||
const links = $derived([
|
||||
{
|
||||
title: isEnglish ? 'Untranslated Recipes' : 'Unübersetzte Rezepte',
|
||||
description: isEnglish
|
||||
? 'View and manage recipes that need translation'
|
||||
: 'Rezepte ansehen und verwalten, die übersetzt werden müssen',
|
||||
title: t.untranslated_recipes,
|
||||
description: t.untranslated_description,
|
||||
href: `/${data.recipeLang}/admin/untranslated`,
|
||||
icon: '🌐'
|
||||
},
|
||||
{
|
||||
title: isEnglish ? 'Alt-Text Generator' : 'Alt-Text Generator',
|
||||
description: isEnglish
|
||||
? 'Generate alternative text for recipe images using AI'
|
||||
: 'Alternativtext für Rezeptbilder mit KI generieren',
|
||||
title: t.alt_text_generator,
|
||||
description: t.alt_text_description,
|
||||
href: `/${data.recipeLang}/admin/alt-text-generator`,
|
||||
icon: '🖼️'
|
||||
},
|
||||
{
|
||||
title: isEnglish ? 'Image Colors' : 'Bildfarben',
|
||||
description: isEnglish
|
||||
? 'Extract dominant colors from recipe images for loading placeholders'
|
||||
: 'Dominante Farben aus Rezeptbildern für Ladeplatzhalter extrahieren',
|
||||
title: t.image_colors,
|
||||
description: t.image_colors_description,
|
||||
href: `/${data.recipeLang}/admin/image-colors`,
|
||||
icon: '🎨'
|
||||
},
|
||||
{
|
||||
title: isEnglish ? 'Nutrition Mappings' : 'Nährwert-Zuordnungen',
|
||||
description: isEnglish
|
||||
? 'Generate or regenerate calorie and nutrition data for all recipes'
|
||||
: 'Kalorien- und Nährwertdaten für alle Rezepte generieren oder aktualisieren',
|
||||
title: t.nutrition_mappings,
|
||||
description: t.nutrition_mappings_description,
|
||||
href: `/${data.recipeLang}/admin/nutrition`,
|
||||
icon: '🥗'
|
||||
}
|
||||
];
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
const lang = $derived(data.lang as RecipesLang);
|
||||
const t = $derived(m[lang]);
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const labels = $derived({
|
||||
title: t.categories_title,
|
||||
siteTitle: t.site_title
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
let { data } = $props<{ data: PageData }>();
|
||||
let current_month = new Date().getMonth() + 1;
|
||||
|
||||
const isEnglish = $derived(data.lang === 'en');
|
||||
const label = $derived(isEnglish ? 'Recipes in Category' : 'Rezepte in Kategorie');
|
||||
const siteTitle = $derived(isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte');
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
const lang = $derived(data.lang as RecipesLang);
|
||||
const t = $derived(m[lang]);
|
||||
const label = $derived(t.recipes_in_category);
|
||||
const siteTitle = $derived(t.site_title);
|
||||
|
||||
let matchedRecipeIds = $state(new Set<string>());
|
||||
let hasActiveSearch = $state(false);
|
||||
|
||||
@@ -10,14 +10,16 @@
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
const lang = $derived(data.lang as RecipesLang);
|
||||
const t = $derived(m[lang]);
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const countLabel = $derived(
|
||||
lang === 'en'
|
||||
? (data.favorites.length === 1 ? t.favorite_recipe_singular : t.favorite_recipes_plural)
|
||||
: t.favorites_count_label
|
||||
);
|
||||
const labels = $derived({
|
||||
title: t.favorites,
|
||||
pageTitle: t.favorites_page_title,
|
||||
metaDescription: t.favorites_meta_description,
|
||||
count: isEnglish
|
||||
? `${data.favorites.length} favorite recipe${data.favorites.length !== 1 ? 's' : ''}`
|
||||
: `${data.favorites.length} favorisierte Rezepte`,
|
||||
count: `${data.favorites.length} ${countLabel}`,
|
||||
noFavorites: t.no_favorites_yet,
|
||||
errorLoading: t.error_loading_favorites,
|
||||
emptyState1: t.empty_favorites_1,
|
||||
@@ -103,7 +105,7 @@
|
||||
</div>
|
||||
{:else if data.favorites.length > 0}
|
||||
<div class="empty-state">
|
||||
<p>{isEnglish ? 'No matching favorites found.' : 'Keine passenden Favoriten gefunden.'}</p>
|
||||
<p>{t.no_matching_favorites}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="empty-state">
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
let { data } = $props<{ data: PageData }>();
|
||||
import { rand_array } from '$lib/js/randomize';
|
||||
|
||||
const isEnglish = $derived(data.lang === 'en');
|
||||
const siteTitle = $derived(isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte');
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
const lang = $derived(data.lang as RecipesLang);
|
||||
const t = $derived(m[lang]);
|
||||
const siteTitle = $derived(t.site_title);
|
||||
|
||||
// Search state
|
||||
let matchedRecipeIds = $state(new Set());
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
|
||||
let { data } = $props();
|
||||
const t = $derived(m[data.lang as RecipesLang]);
|
||||
|
||||
// This page serves as an "app shell" that gets cached by the service worker.
|
||||
// When a user directly navigates to a recipe page while offline and that exact
|
||||
@@ -36,7 +38,7 @@ onMount(() => {
|
||||
|
||||
<div class="offline-shell">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>{data.lang === 'en' ? 'Loading offline content...' : 'Lade Offline-Inhalte...'}</p>
|
||||
<p>{t.loading_offline}</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -9,12 +9,9 @@
|
||||
|
||||
const lang = $derived(data.lang as RecipesLang);
|
||||
const t = $derived(m[lang]);
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const labels = $derived({
|
||||
title: t.search_results_title,
|
||||
pageTitle: isEnglish
|
||||
? `Search Results${data.query ? ` for "${data.query}"` : ''} - Bocken Recipes`
|
||||
: `Suchergebnisse${data.query ? ` für "${data.query}"` : ''} - Bocken Rezepte`,
|
||||
pageTitle: `${t.search_results_title}${data.query ? ` ${t.search_results_for_word} "${data.query}"` : ''} - ${t.site_title}`,
|
||||
metaDescription: t.search_meta_description,
|
||||
filteredBy: t.filtered_by,
|
||||
category: t.category_nav,
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
||||
let { data } = $props<{ data: PageData }>();
|
||||
|
||||
const isEnglish = $derived(data.lang === 'en');
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
const lang = $derived(data.lang as RecipesLang);
|
||||
const t = $derived(m[lang]);
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const months = $derived(isEnglish
|
||||
? ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
|
||||
: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]);
|
||||
const siteTitle = $derived(isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte');
|
||||
const siteTitle = $derived(t.site_title);
|
||||
const currentMonth = $derived(months[data.month - 1]);
|
||||
|
||||
import { rand_array } from '$lib/js/randomize';
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
const lang = $derived(data.lang as RecipesLang);
|
||||
const t = $derived(m[lang]);
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const labels = $derived({
|
||||
title: t.keywords_title,
|
||||
siteTitle: t.site_title,
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
let { data } = $props<{ data: PageData }>();
|
||||
let current_month = new Date().getMonth() + 1;
|
||||
|
||||
const isEnglish = $derived(data.lang === 'en');
|
||||
const label = $derived(isEnglish ? 'Recipes with Keyword' : 'Rezepte mit Stichwort');
|
||||
const siteTitle = $derived(isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte');
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
const lang = $derived(data.lang as RecipesLang);
|
||||
const t = $derived(m[lang]);
|
||||
const label = $derived(t.recipes_with_keyword);
|
||||
const siteTitle = $derived(t.site_title);
|
||||
|
||||
let matchedRecipeIds = $state(new Set<string>());
|
||||
let hasActiveSearch = $state(false);
|
||||
|
||||
@@ -8,13 +8,10 @@
|
||||
import { m, type RecipesLang } from '$lib/js/recipesI18n';
|
||||
const lang = $derived(data.lang as RecipesLang);
|
||||
const t = $derived(m[lang]);
|
||||
const isEnglish = $derived(lang === 'en');
|
||||
const labels = $derived({
|
||||
title: t.tips_title,
|
||||
siteTitle: t.site_title,
|
||||
description: isEnglish
|
||||
? "A constantly growing collection of recipes from Bocken's kitchen."
|
||||
: 'Eine stetig wachsende Ansammlung an Rezepten aus der Bockenschen Küche.'
|
||||
description: t.tips_description
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user