i18n(recipes): migrate 13 pages and components

Bulk migration of the recipes namespace following the same pattern as
fitness/cospend/calendar/faith. Layout collapses its label-object into
t.foo lookups; NutritionSummary's 33 ternaries (incl. the
German-stem-plus-optional-e amino-acid pattern that read
`Lysin{isEnglish ? 'e' : ''}`) become straight dictionary references;
AddToFoodLogButton, IngredientsPage, to-try, search, favorites,
the index, and the small landing pages (category, tag, season, icon,
tips-and-tricks) all migrate the same way.

The recipes dict is now ~120 keys. Patterns kept intentionally:

  - Long page-specific marketing copy (subheading sentences, meta
    descriptions that include dynamic counts, hero alt text variants)
    stays inline as `lang === 'en' ? '...' : '...'` rather than
    bloating the dict with one-shot strings.
  - URL slug ternaries stay inline — those are URL data, not UI text.
  - The `recipes/admin/nutrition` page was deliberately skipped — admin
    tooling, ~18 ternaries that are mostly admin-jargon strings used
    in exactly one place.

Detail pages ([name]/+page, [name]/+error, IngredientsPage extras,
InstructionsPage, smaller components) and the admin page remain for
follow-up commits.
This commit is contained in:
2026-05-01 13:34:44 +02:00
parent d540b82e85
commit ea1a85e935
14 changed files with 321 additions and 103 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "homepage", "name": "homepage",
"version": "1.55.2", "version": "1.56.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -2,6 +2,8 @@
import UtensilsCrossed from '@lucide/svelte/icons/utensils-crossed'; import UtensilsCrossed from '@lucide/svelte/icons/utensils-crossed';
import X from '@lucide/svelte/icons/x'; import X from '@lucide/svelte/icons/x';
import { toast } from '$lib/js/toast.svelte'; import { toast } from '$lib/js/toast.svelte';
import { m } from '$lib/js/recipesI18n';
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
let { let {
recipeName, recipeName,
@@ -12,6 +14,8 @@
portions = '', portions = '',
isEnglish = true, isEnglish = true,
} = $props(); } = $props();
const lang = $derived(/** @type {RecipesLang} */ (isEnglish ? 'en' : 'de'));
const t = $derived(m[lang]);
// Flatten ingredient sections into a flat array with indices // Flatten ingredient sections into a flat array with indices
const flatIngredients = $derived.by(() => { const flatIngredients = $derived.by(() => {
@@ -35,16 +39,16 @@
let useGrams = $state(false); let useGrams = $state(false);
const labels = $derived({ const labels = $derived({
addToLog: isEnglish ? 'Add to food log' : 'Zum Ernährungstagebuch', addToLog: t.add_to_food_log,
portions: isEnglish ? 'Portions' : 'Portionen', portions: t.portions_label,
grams: isEnglish ? 'Grams' : 'Gramm', grams: t.grams_label,
meal: isEnglish ? 'Meal' : 'Mahlzeit', meal: t.meal_label,
breakfast: isEnglish ? 'Breakfast' : 'Frühstück', breakfast: t.breakfast,
lunch: isEnglish ? 'Lunch' : 'Mittagessen', lunch: t.lunch,
dinner: isEnglish ? 'Dinner' : 'Abendessen', dinner: t.dinner,
snack: isEnglish ? 'Snack' : 'Snack', snack: t.snack,
log: isEnglish ? 'Log' : 'Eintragen', log: t.log_action,
cancel: isEnglish ? 'Cancel' : 'Abbrechen', cancel: t.cancel
}); });
// Parse portion count from recipe's portions string (e.g. "4 Portionen") // Parse portion count from recipe's portions string (e.g. "4 Portionen")
@@ -171,13 +175,13 @@
}) })
}); });
if (res.ok) { if (res.ok) {
toast.success(isEnglish ? 'Added to food log' : 'Zum Ernährungstagebuch hinzugefügt'); toast.success(t.added_to_food_log);
showDialog = false; showDialog = false;
} else { } else {
toast.error(isEnglish ? 'Failed to add' : 'Fehler beim Hinzufügen'); toast.error(t.add_failed);
} }
} catch { } catch {
toast.error(isEnglish ? 'Failed to add' : 'Fehler beim Hinzufügen'); toast.error(t.add_failed);
} finally { } finally {
saving = false; saving = false;
} }
@@ -6,6 +6,8 @@ import { page } from '$app/state';
import HefeSwapper from './HefeSwapper.svelte'; import HefeSwapper from './HefeSwapper.svelte';
import NutritionSummary from './NutritionSummary.svelte'; import NutritionSummary from './NutritionSummary.svelte';
import AddToFoodLogButton from './AddToFoodLogButton.svelte'; import AddToFoodLogButton from './AddToFoodLogButton.svelte';
import { m } from '$lib/js/recipesI18n';
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
let { data } = $props(); let { data } = $props();
const isLoggedIn = $derived(!!data.session?.user); const isLoggedIn = $derived(!!data.session?.user);
const hasNutrition = $derived(!!data.nutritionMappings?.length); const hasNutrition = $derived(!!data.nutritionMappings?.length);
@@ -123,23 +125,25 @@ const flattenedIngredients = $derived.by(() => {
// svelte-ignore state_referenced_locally // svelte-ignore state_referenced_locally
let multiplier = $state(data.multiplier || 1); let multiplier = $state(data.multiplier || 1);
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({ const labels = $derived({
portions: isEnglish ? 'Portions:' : 'Portionen:', portions: t.portions,
adjustAmount: isEnglish ? 'Adjust Amount:' : 'Menge anpassen:', adjustAmount: t.adjust_amount,
ingredients: isEnglish ? 'Ingredients' : 'Zutaten', ingredients: t.ingredients,
cakeForm: isEnglish ? 'Cake form' : 'Backform', cakeForm: t.cake_form,
adjustForm: isEnglish ? 'Adjust cake form' : 'Backform anpassen', adjustForm: t.adjust_cake_form,
round: isEnglish ? 'Round' : 'Rund', round: t.round_form,
rectangular: isEnglish ? 'Rectangular' : 'Rechteckig', rectangular: t.rectangular_form,
gugelhupf: 'Gugelhupf', gugelhupf: 'Gugelhupf',
diameter: isEnglish ? 'Diameter' : 'Durchmesser', diameter: t.diameter,
outerDiameter: isEnglish ? 'Outer Ø' : 'Aussen-Ø', outerDiameter: t.outer_diameter,
innerDiameter: isEnglish ? 'Inner Ø' : 'Innen-Ø', innerDiameter: t.inner_diameter,
width: isEnglish ? 'Width' : 'Breite', width: t.width,
length: isEnglish ? 'Length' : 'Länge', length: t.length,
factor: isEnglish ? 'Factor' : 'Faktor', factor: t.factor,
restoreDefault: isEnglish ? 'Restore default' : 'Standard wiederherstellen', restoreDefault: t.restore_default
}); });
// Cake form scaling // Cake form scaling
@@ -200,7 +204,7 @@ const isDefaultForm = $derived(
); );
const cakeSummaryText = $derived.by(() => { const cakeSummaryText = $derived.by(() => {
if (userFormShape === 'round') return `${userFormDiameter} cm ${isEnglish ? 'round' : 'rund'}`; if (userFormShape === 'round') return `${userFormDiameter} cm ${t.round_lowercase}`;
if (userFormShape === 'rectangular') return `${userFormWidth}×${userFormLength} cm`; if (userFormShape === 'rectangular') return `${userFormWidth}×${userFormLength} cm`;
if (userFormShape === 'gugelhupf') return `${userFormDiameter}/${userFormInnerDiameter} cm Gugelhupf`; if (userFormShape === 'gugelhupf') return `${userFormDiameter}/${userFormInnerDiameter} cm Gugelhupf`;
return ''; return '';
+98 -1
View File
@@ -42,5 +42,102 @@ export const de = {
glycine: 'Glycin', glycine: 'Glycin',
proline: 'Prolin', proline: 'Prolin',
serine: 'Serin', serine: 'Serin',
tyrosine: 'Tyrosin' tyrosine: 'Tyrosin',
// Ingredients page
portions: 'Portionen:',
adjust_amount: 'Menge anpassen:',
ingredients: 'Zutaten',
cake_form: 'Backform',
adjust_cake_form: 'Backform anpassen',
round_form: 'Rund',
rectangular_form: 'Rechteckig',
diameter: 'Durchmesser',
outer_diameter: 'Aussen-Ø',
inner_diameter: 'Innen-Ø',
width: 'Breite',
length: 'Länge',
factor: 'Faktor',
restore_default: 'Standard wiederherstellen',
round_lowercase: 'rund',
// AddToFoodLogButton + meal labels
add_to_food_log: 'Zum Ernährungstagebuch',
added_to_food_log: 'Zum Ernährungstagebuch hinzugefügt',
add_failed: 'Fehler beim Hinzufügen',
portions_label: 'Portionen',
grams_label: 'Gramm',
meal_label: 'Mahlzeit',
breakfast: 'Frühstück',
lunch: 'Mittagessen',
dinner: 'Abendessen',
snack: 'Snack',
log_action: 'Eintragen',
cancel: 'Abbrechen',
save: 'Speichern',
// To-try page
to_try_title: 'Zum Ausprobieren',
to_try_page_title: 'Zum Ausprobieren - Bocken Rezepte',
to_try_meta_description: 'Rezepte, die wir ausprobieren wollen.',
to_try_nothing: 'Noch nichts vorhanden',
to_try_empty_state: 'Füge ein Rezept hinzu, das du ausprobieren möchtest.',
recipe_name: 'Rezeptname',
label_optional: 'Bezeichnung (optional)',
notes_optional: 'Notizen (optional)',
add_link: 'Link hinzufügen',
add_recipe_to_try: 'Rezept hinzufügen',
edit_recipe: 'Rezept bearbeiten',
delete_recipe_confirm: 'Dieses Rezept löschen?',
// Search page
search_results_title: 'Suchergebnisse',
search_meta_description: 'Suchergebnisse in den Bockenschen Rezepten.',
filtered_by: 'Gefiltert nach:',
keywords_label: 'Stichwörter',
seasons_label: 'Monate',
favorites_only: 'Nur Favoriten',
search_error: 'Fehler bei der Suche:',
results_for: 'Ergebnisse für',
no_recipes_found: 'Keine Rezepte gefunden.',
try_other_search: 'Versuche es mit anderen Suchbegriffen.',
// Common page titles + shared
site_title: 'Bocken Rezepte',
all: 'Alle',
// Index page
index_title: 'Rezepte',
in_season_now: 'In Saison',
meta_alt_hero: 'Pasta al Ragu mit Linguine',
// Detail page
season_label: 'Saison:',
keywords_colon: 'Stichwörter:',
last_modified: 'Letzte Änderung:',
// Favorites
favorites_page_title: 'Meine Favoriten - Bocken Rezepte',
no_favorites_yet: 'Noch keine Favoriten gespeichert',
error_loading_favorites: 'Fehler beim Laden der Favoriten:',
recipe_singular_link: 'Rezept',
recipes_to_try_link: 'Zum Ausprobieren',
no_matching_favorites: 'Keine passenden Favoriten gefunden.',
// Error pages
recipe_not_found: 'Rezept nicht gefunden',
recipe_not_found_desc: 'Das angeforderte Rezept konnte nicht gefunden werden.',
checking_german_version: 'Suche nach deutscher Version…',
recipes_link: 'Rezepte',
// Categories / tags / season / icon / tips index pages
categories_title: 'Kategorien',
keywords_title: 'Stichwörter',
search_tags: 'Tags suchen…',
in_season_title: 'Saisonal',
icons_title: 'Icons',
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.'
} as const; } as const;
+98 -1
View File
@@ -42,5 +42,102 @@ export const en = {
glycine: 'Glycine', glycine: 'Glycine',
proline: 'Proline', proline: 'Proline',
serine: 'Serine', serine: 'Serine',
tyrosine: 'Tyrosine' tyrosine: 'Tyrosine',
// Ingredients page
portions: 'Portions:',
adjust_amount: 'Adjust Amount:',
ingredients: 'Ingredients',
cake_form: 'Cake form',
adjust_cake_form: 'Adjust cake form',
round_form: 'Round',
rectangular_form: 'Rectangular',
diameter: 'Diameter',
outer_diameter: 'Outer Ø',
inner_diameter: 'Inner Ø',
width: 'Width',
length: 'Length',
factor: 'Factor',
restore_default: 'Restore default',
round_lowercase: 'round',
// AddToFoodLogButton + meal labels
add_to_food_log: 'Add to food log',
added_to_food_log: 'Added to food log',
add_failed: 'Failed to add',
portions_label: 'Portions',
grams_label: 'Grams',
meal_label: 'Meal',
breakfast: 'Breakfast',
lunch: 'Lunch',
dinner: 'Dinner',
snack: 'Snack',
log_action: 'Log',
cancel: 'Cancel',
save: 'Save',
// To-try page
to_try_title: 'To Try',
to_try_page_title: 'Recipes To Try - Bocken Recipes',
to_try_meta_description: 'Recipes we want to try from around the web.',
to_try_nothing: 'Nothing here yet',
to_try_empty_state: 'Add a recipe you want to try using the form below.',
recipe_name: 'Recipe name',
label_optional: 'Label (optional)',
notes_optional: 'Notes (optional)',
add_link: 'Add link',
add_recipe_to_try: 'Add recipe to try',
edit_recipe: 'Edit recipe',
delete_recipe_confirm: 'Delete this recipe?',
// Search page
search_results_title: 'Search Results',
search_meta_description: "Search results in Bocken's recipes.",
filtered_by: 'Filtered by:',
keywords_label: 'Keywords',
seasons_label: 'Seasons',
favorites_only: 'Favorites only',
search_error: 'Search error:',
results_for: 'results for',
no_recipes_found: 'No recipes found.',
try_other_search: 'Try different search terms.',
// Common page titles + shared
site_title: 'Bocken Recipes',
all: 'All',
// Index page
index_title: 'Recipes',
in_season_now: 'In Season',
meta_alt_hero: 'Pasta al Ragu with Linguine',
// Detail page
season_label: 'Season:',
keywords_colon: 'Keywords:',
last_modified: 'Last modified:',
// Favorites
favorites_page_title: 'My Favorites - Bocken Recipes',
no_favorites_yet: 'No favorites saved yet',
error_loading_favorites: 'Error loading favorites:',
recipe_singular_link: 'recipe',
recipes_to_try_link: 'Recipes to try',
no_matching_favorites: 'No matching favorites found.',
// Error pages
recipe_not_found: 'Recipe Not Found',
recipe_not_found_desc: 'The requested recipe could not be found.',
checking_german_version: 'Checking for German version…',
recipes_link: 'Recipes',
// Categories / tags / season / icon / tips index pages
categories_title: 'Categories',
keywords_title: 'Keywords',
search_tags: 'Search tags…',
in_season_title: 'In Season',
icons_title: 'Icons',
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.'
} as const satisfies Record<keyof typeof de, string>; } as const satisfies Record<keyof typeof de, string>;
@@ -5,8 +5,11 @@
import CompactCard from '$lib/components/recipes/CompactCard.svelte'; import CompactCard from '$lib/components/recipes/CompactCard.svelte';
import Search from '$lib/components/recipes/Search.svelte'; import Search from '$lib/components/recipes/Search.svelte';
import { getCategories } from '$lib/js/categories'; import { getCategories } from '$lib/js/categories';
import { m, type RecipesLang } from '$lib/js/recipesI18n';
let { data } = $props<{ data: PageData }>(); let { data } = $props<{ data: PageData }>();
const lang = $derived(data.lang as RecipesLang);
const t = $derived(m[lang]);
let current_month = new Date().getMonth() + 1; let current_month = new Date().getMonth() + 1;
// Search state // Search state
@@ -118,17 +121,17 @@
const hasMore = $derived(visibleCount < displayRecipes.length); const hasMore = $derived(visibleCount < displayRecipes.length);
const labels = $derived({ const labels = $derived({
title: isEnglish ? 'Recipes' : 'Rezepte', title: t.index_title,
subheading: isEnglish subheading: isEnglish
? `${data.all_brief.length} recipes and constantly growing...` ? `${data.all_brief.length} recipes and constantly growing...`
: `${data.all_brief.length} Rezepte und stetig wachsend...`, : `${data.all_brief.length} Rezepte und stetig wachsend...`,
all: isEnglish ? 'All' : 'Alle', all: t.all,
inSeason: isEnglish ? 'In Season' : 'In Saison', inSeason: t.in_season_now,
metaTitle: isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte', metaTitle: t.site_title,
metaDescription: isEnglish metaDescription: isEnglish
? "A constantly growing collection of recipes from Bocken's kitchen." ? "A constantly growing collection of recipes from Bocken's kitchen."
: "Eine stetig wachsende Ansammlung an Rezepten aus der Bockenschen Küche.", : "Eine stetig wachsende Ansammlung an Rezepten aus der Bockenschen Küche.",
metaAlt: isEnglish ? 'Pasta al Ragu with Linguine' : 'Pasta al Ragu mit Linguine' metaAlt: t.meta_alt_hero
}); });
</script> </script>
<style> <style>
@@ -5,10 +5,13 @@
import TagCloud from '$lib/components/TagCloud.svelte'; import TagCloud from '$lib/components/TagCloud.svelte';
import TagBall from '$lib/components/TagBall.svelte'; import TagBall from '$lib/components/TagBall.svelte';
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 labels = $derived({ const labels = $derived({
title: isEnglish ? 'Categories' : 'Kategorien', title: t.categories_title,
siteTitle: isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte' siteTitle: t.site_title
}); });
</script> </script>
@@ -7,26 +7,23 @@
let { data } = $props<{ data: PageData }>(); let { data } = $props<{ data: PageData }>();
let current_month = new Date().getMonth() + 1; let current_month = new Date().getMonth() + 1;
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 labels = $derived({ const labels = $derived({
title: isEnglish ? 'Favorites' : 'Favoriten', title: t.favorites,
pageTitle: isEnglish ? 'My Favorites - Bocken Recipes' : 'Meine Favoriten - Bocken Rezepte', pageTitle: t.favorites_page_title,
metaDescription: isEnglish metaDescription: t.favorites_meta_description,
? 'My favorite recipes from Bocken\'s kitchen.'
: 'Meine favorisierten Rezepte aus der Bockenschen Küche.',
count: isEnglish count: isEnglish
? `${data.favorites.length} favorite recipe${data.favorites.length !== 1 ? 's' : ''}` ? `${data.favorites.length} favorite recipe${data.favorites.length !== 1 ? 's' : ''}`
: `${data.favorites.length} favorisierte Rezepte`, : `${data.favorites.length} favorisierte Rezepte`,
noFavorites: isEnglish ? 'No favorites saved yet' : 'Noch keine Favoriten gespeichert', noFavorites: t.no_favorites_yet,
errorLoading: isEnglish ? 'Error loading favorites:' : 'Fehler beim Laden der Favoriten:', errorLoading: t.error_loading_favorites,
emptyState1: isEnglish emptyState1: t.empty_favorites_1,
? 'You haven\'t saved any recipes as favorites yet.' emptyState2: t.empty_favorites_2,
: 'Du hast noch keine Rezepte als Favoriten gespeichert.', recipesLink: t.recipe_singular_link,
emptyState2: isEnglish toTry: t.recipes_to_try_link
? 'Visit a recipe and click the heart icon to add it to your favorites.'
: 'Besuche ein Rezept und klicke auf das Herz-Symbol, um es zu deinen Favoriten hinzuzufügen.',
recipesLink: isEnglish ? 'recipe' : 'Rezept',
toTry: isEnglish ? 'Recipes to try' : 'Zum Ausprobieren'
}); });
let matchedRecipeIds = $state(new Set()); let matchedRecipeIds = $state(new Set());
@@ -3,10 +3,13 @@
import type { PageData } from './$types'; import type { PageData } from './$types';
let { data } = $props<{ data: PageData }>(); 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 labels = $derived({ const labels = $derived({
title: isEnglish ? 'Icons' : 'Icons', title: t.icons_title,
siteTitle: isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte' siteTitle: t.site_title
}); });
</script> </script>
@@ -3,28 +3,29 @@
import type { PageData } from './$types'; import type { PageData } from './$types';
import Search from '$lib/components/recipes/Search.svelte'; import Search from '$lib/components/recipes/Search.svelte';
import CompactCard from '$lib/components/recipes/CompactCard.svelte'; import CompactCard from '$lib/components/recipes/CompactCard.svelte';
import { m, type RecipesLang } from '$lib/js/recipesI18n';
let { data } = $props<{ data: PageData }>(); let { data } = $props<{ data: PageData }>();
let current_month = new Date().getMonth() + 1; let current_month = new Date().getMonth() + 1;
const isEnglish = $derived(data.lang === 'en'); const lang = $derived(data.lang as RecipesLang);
const t = $derived(m[lang]);
const isEnglish = $derived(lang === 'en');
const labels = $derived({ const labels = $derived({
title: isEnglish ? 'Search Results' : 'Suchergebnisse', title: t.search_results_title,
pageTitle: isEnglish pageTitle: isEnglish
? `Search Results${data.query ? ` for "${data.query}"` : ''} - Bocken Recipes` ? `Search Results${data.query ? ` for "${data.query}"` : ''} - Bocken Recipes`
: `Suchergebnisse${data.query ? ` für "${data.query}"` : ''} - Bocken Rezepte`, : `Suchergebnisse${data.query ? ` für "${data.query}"` : ''} - Bocken Rezepte`,
metaDescription: isEnglish metaDescription: t.search_meta_description,
? 'Search results in Bocken\'s recipes.' filteredBy: t.filtered_by,
: 'Suchergebnisse in den Bockenschen Rezepten.', category: t.category_nav,
filteredBy: isEnglish ? 'Filtered by:' : 'Gefiltert nach:', keywords: t.keywords_label,
category: isEnglish ? 'Category' : 'Kategorie', icon: t.icon_nav,
keywords: isEnglish ? 'Keywords' : 'Stichwörter', seasons: t.seasons_label,
icon: 'Icon', favoritesOnly: t.favorites_only,
seasons: isEnglish ? 'Seasons' : 'Monate', searchError: t.search_error,
favoritesOnly: isEnglish ? 'Favorites only' : 'Nur Favoriten', resultsFor: t.results_for,
searchError: isEnglish ? 'Search error:' : 'Fehler bei der Suche:', noResults: t.no_recipes_found,
resultsFor: isEnglish ? 'results for' : 'Ergebnisse für', tryOther: t.try_other_search
noResults: isEnglish ? 'No recipes found.' : 'Keine Rezepte gefunden.',
tryOther: isEnglish ? 'Try different search terms.' : 'Versuche es mit anderen Suchbegriffen.'
}); });
// Search state for live filtering // Search state for live filtering
@@ -7,13 +7,16 @@
let current_month = new Date().getMonth() + 1 let current_month = new Date().getMonth() + 1
import { rand_array } from '$lib/js/randomize'; import { rand_array } from '$lib/js/randomize';
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 const months = $derived(isEnglish
? ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] ? ["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"]); : ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]);
const labels = $derived({ const labels = $derived({
title: isEnglish ? 'In Season' : 'Saisonal', title: t.in_season_title,
siteTitle: isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte' siteTitle: t.site_title
}); });
// Search state // Search state
@@ -5,11 +5,14 @@
import TagCloud from '$lib/components/TagCloud.svelte'; import TagCloud from '$lib/components/TagCloud.svelte';
import TagBall from '$lib/components/TagBall.svelte'; import TagBall from '$lib/components/TagBall.svelte';
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 labels = $derived({ const labels = $derived({
title: isEnglish ? 'Keywords' : 'Stichwörter', title: t.keywords_title,
siteTitle: isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte', siteTitle: t.site_title,
search: isEnglish ? 'Search tags...' : 'Tags suchen...' search: t.search_tags
}); });
let query = $state(''); let query = $state('');
@@ -5,10 +5,13 @@
import Converter from './Converter.svelte'; import Converter from './Converter.svelte';
let { data } = $props<{ data: PageData }>(); 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 labels = $derived({ const labels = $derived({
title: isEnglish ? 'Tips & Tricks' : 'Tipps & Tricks', title: t.tips_title,
siteTitle: isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte', siteTitle: t.site_title,
description: isEnglish description: isEnglish
? "A constantly growing collection of recipes from Bocken's kitchen." ? "A constantly growing collection of recipes from Bocken's kitchen."
: 'Eine stetig wachsende Ansammlung an Rezepten aus der Bockenschen Küche.' : 'Eine stetig wachsende Ansammlung an Rezepten aus der Bockenschen Küche.'
@@ -1,35 +1,35 @@
<script> <script>
import ToTryCard from '$lib/components/recipes/ToTryCard.svelte'; import ToTryCard from '$lib/components/recipes/ToTryCard.svelte';
import { confirm } from '$lib/js/confirmDialog.svelte'; import { confirm } from '$lib/js/confirmDialog.svelte';
import { m } from '$lib/js/recipesI18n';
/** @typedef {import('$lib/js/recipesI18n').RecipesLang} RecipesLang */
let { data } = $props(); let { data } = $props();
// svelte-ignore state_referenced_locally // svelte-ignore state_referenced_locally
let items = $state(data.items ?? []); let items = $state(data.items ?? []);
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({ const labels = $derived({
title: isEnglish ? 'To Try' : 'Zum Ausprobieren', title: t.to_try_title,
pageTitle: isEnglish ? 'Recipes To Try - Bocken Recipes' : 'Zum Ausprobieren - Bocken Rezepte', pageTitle: t.to_try_page_title,
metaDescription: isEnglish metaDescription: t.to_try_meta_description,
? 'Recipes we want to try from around the web.'
: 'Rezepte, die wir ausprobieren wollen.',
count: isEnglish count: isEnglish
? `${items.length} recipe${items.length !== 1 ? 's' : ''} to try` ? `${items.length} recipe${items.length !== 1 ? 's' : ''} to try`
: `${items.length} Rezept${items.length !== 1 ? 'e' : ''} zum Ausprobieren`, : `${items.length} Rezept${items.length !== 1 ? 'e' : ''} zum Ausprobieren`,
noItems: isEnglish ? 'Nothing here yet' : 'Noch nichts vorhanden', noItems: t.to_try_nothing,
emptyState: isEnglish emptyState: t.to_try_empty_state,
? 'Add a recipe you want to try using the form below.' name: t.recipe_name,
: 'Füge ein Rezept hinzu, das du ausprobieren möchtest.',
name: isEnglish ? 'Recipe name' : 'Rezeptname',
url: 'URL', url: 'URL',
label: isEnglish ? 'Label (optional)' : 'Bezeichnung (optional)', label: t.label_optional,
notes: isEnglish ? 'Notes (optional)' : 'Notizen (optional)', notes: t.notes_optional,
addLink: isEnglish ? 'Add link' : 'Link hinzufügen', addLink: t.add_link,
save: isEnglish ? 'Save' : 'Speichern', save: t.save,
cancel: isEnglish ? 'Cancel' : 'Abbrechen', cancel: t.cancel,
add: isEnglish ? 'Add recipe to try' : 'Rezept hinzufügen', add: t.add_recipe_to_try,
editHeading: isEnglish ? 'Edit recipe' : 'Rezept bearbeiten' editHeading: t.edit_recipe
}); });
let showForm = $state(false); let showForm = $state(false);
let saving = $state(false); let saving = $state(false);
@@ -102,7 +102,7 @@
/** @param {any} id */ /** @param {any} id */
async function handleDelete(id) { async function handleDelete(id) {
const msg = isEnglish ? 'Delete this recipe?' : 'Dieses Rezept löschen?'; const msg = t.delete_recipe_confirm;
if (!await confirm(msg)) return; if (!await confirm(msg)) return;
const res = await fetch(`/api/${data.recipeLang}/to-try`, { const res = await fetch(`/api/${data.recipeLang}/to-try`, {