From 2f89e623b21e66c9b7a472915a7a04806950f721 Mon Sep 17 00:00:00 2001 From: Ulas Date: Tue, 31 Mar 2026 22:50:16 +0200 Subject: [PATCH] feat: i18n shopping, meals, calendar pages Co-Authored-By: Claude Sonnet 4.6 --- public/pages/calendar.js | 130 ++++++++++++++++++++++----------------- public/pages/meals.js | 107 +++++++++++++++++--------------- public/pages/shopping.js | 70 ++++++++++++--------- 3 files changed, 172 insertions(+), 135 deletions(-) diff --git a/public/pages/calendar.js b/public/pages/calendar.js index 4174ee4..4949046 100644 --- a/public/pages/calendar.js +++ b/public/pages/calendar.js @@ -8,17 +8,35 @@ import { api } from '/api.js'; import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js'; import { openModal as openSharedModal, closeModal } from '/components/modal.js'; import { stagger } from '/utils/ux.js'; +import { t } from '/i18n.js'; // -------------------------------------------------------- // Konstanten // -------------------------------------------------------- const VIEWS = ['month', 'week', 'day', 'agenda']; -const VIEW_LABELS = { month: 'Monat', week: 'Woche', day: 'Tag', agenda: 'Agenda' }; -const DAY_NAMES_SHORT = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']; -const DAY_NAMES_LONG = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']; -const MONTH_NAMES = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', - 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']; +const VIEW_LABELS = () => ({ + month: t('calendar.viewMonth'), + week: t('calendar.viewWeek'), + day: t('calendar.viewDay'), + agenda: t('calendar.viewAgenda'), +}); +const DAY_NAMES_SHORT = () => [ + t('calendar.dayShortSunday'), t('calendar.dayShortMonday'), t('calendar.dayShortTuesday'), + t('calendar.dayShortWednesday'), t('calendar.dayShortThursday'), t('calendar.dayShortFriday'), + t('calendar.dayShortSaturday'), +]; +const DAY_NAMES_LONG = () => [ + t('calendar.dayLongSunday'), t('calendar.dayLongMonday'), t('calendar.dayLongTuesday'), + t('calendar.dayLongWednesday'), t('calendar.dayLongThursday'), t('calendar.dayLongFriday'), + t('calendar.dayLongSaturday'), +]; +const MONTH_NAMES = () => [ + t('calendar.monthJanuary'), t('calendar.monthFebruary'), t('calendar.monthMarch'), + t('calendar.monthApril'), t('calendar.monthMay'), t('calendar.monthJune'), + t('calendar.monthJuly'), t('calendar.monthAugust'), t('calendar.monthSeptember'), + t('calendar.monthOctober'), t('calendar.monthNovember'), t('calendar.monthDecember'), +]; const EVENT_COLORS = [ '#007AFF', '#34C759', '#FF9500', '#FF3B30', @@ -73,9 +91,9 @@ function getMondayOf(dateStr) { function formatDate(dateStr, { long = false, weekday = false } = {}) { const d = new Date(dateStr + 'T00:00:00'); const day = d.getDate(); - const mon = MONTH_NAMES[d.getMonth()]; + const mon = MONTH_NAMES()[d.getMonth()]; if (weekday) { - const wd = long ? DAY_NAMES_LONG[d.getDay()] : DAY_NAMES_SHORT[d.getDay()]; + const wd = long ? DAY_NAMES_LONG()[d.getDay()] : DAY_NAMES_SHORT()[d.getDay()]; return `${wd}, ${day}. ${mon}`; } return `${day}. ${mon} ${d.getFullYear()}`; @@ -132,7 +150,7 @@ async function loadRange(from, to) { } catch (err) { console.error('[Calendar] loadRange Fehler:', err); state.events = []; - window.oikos?.showToast('Termine konnten nicht geladen werden.', 'danger'); + window.oikos?.showToast(t('calendar.loadError'), 'danger'); } state.rangeFrom = from; state.rangeTo = to; @@ -161,7 +179,7 @@ export async function render(container, { user }) {
-
@@ -185,26 +203,26 @@ function renderToolbar() { if (!bar) return; bar.innerHTML = ` -

Kalender

+

${t('calendar.title')}

-
- +
${VIEWS.map((v) => ` + data-view="${v}">${VIEW_LABELS()[v]} `).join('')}
-
-
@@ -237,12 +255,12 @@ function updateLabel() { if (!lbl) return; const d = new Date(state.cursor + 'T00:00:00'); const year = d.getFullYear(); - const mon = MONTH_NAMES[d.getMonth()]; + const mon = MONTH_NAMES()[d.getMonth()]; if (state.view === 'month') lbl.textContent = `${mon} ${year}`; - if (state.view === 'week') lbl.textContent = `KW ${getWeekNumber(state.cursor)} · ${mon} ${year}`; + if (state.view === 'week') lbl.textContent = t('calendar.weekNumberLabel', { week: getWeekNumber(state.cursor), month: mon, year }); if (state.view === 'day') lbl.textContent = formatDate(state.cursor, { weekday: true, long: true }); - if (state.view === 'agenda') lbl.textContent = `Ab ${formatDate(state.cursor)}`; + if (state.view === 'agenda') lbl.textContent = t('calendar.agendaFrom', { date: formatDate(state.cursor) }); } function getWeekNumber(dateStr) { @@ -328,7 +346,7 @@ function renderMonthView(container) { container.innerHTML = `
- ${['Mo','Di','Mi','Do','Fr','Sa','So'].map((n) => `
${n}
`).join('')} + ${[t('calendar.dayShortMonday'),t('calendar.dayShortTuesday'),t('calendar.dayShortWednesday'),t('calendar.dayShortThursday'),t('calendar.dayShortFriday'),t('calendar.dayShortSaturday'),t('calendar.dayShortSunday')].map((n) => `
${n}
`).join('')}
${days.map(({ date, inMonth }) => renderMonthDay(date, inMonth)).join('')} @@ -376,7 +394,7 @@ function renderMonthDay(date, inMonth) {
${new Date(date + 'T00:00:00').getDate()}
${evHtml} - ${extra > 0 ? `
+${extra} weitere
` : ''} + ${extra > 0 ? `
${t('calendar.moreEvents', { count: extra })}
` : ''}
`; } @@ -404,14 +422,14 @@ function renderWeekView(container) { ${days.map((d) => { const dt = new Date(d + 'T00:00:00'); return `
-
${DAY_NAMES_SHORT[(dt.getDay())]}
+
${DAY_NAMES_SHORT()[dt.getDay()]}
${dt.getDate()}
`; }).join('')}
-
ganztg.
+
${t('calendar.allDayShort')}
${days.map((d, i) => `
${alldayEvs[i].map((ev) => ` @@ -523,7 +541,7 @@ function renderDayView(container) {
${allday.length ? `
-
ganztg.
+
${t('calendar.allDayShort')}
${allday.map((ev) => `
@@ -584,12 +602,12 @@ function renderAgendaView(container) { container.innerHTML = `
${groups.length === 0 - ? `
Keine Termine im gewählten Zeitraum.
` + ? `
${t('calendar.noEvents')}
` : groups.map(({ date, events }) => `
${formatDate(date)} - ${DAY_NAMES_LONG[new Date(date + 'T00:00:00').getDay()]} + ${DAY_NAMES_LONG()[new Date(date + 'T00:00:00').getDay()]}
${events.map((ev) => renderAgendaEvent(ev)).join('')}
@@ -611,7 +629,7 @@ function renderAgendaView(container) { function renderAgendaEvent(ev) { const timeStr = ev.all_day - ? 'Ganztägig' + ? t('calendar.allDay') : formatTime(ev.start_datetime) + (ev.end_datetime ? ` – ${formatTime(ev.end_datetime)} Uhr` : ' Uhr'); @@ -650,7 +668,7 @@ function showEventPopup(ev, anchor) { popup.className = 'event-popup'; const timeStr = ev.all_day - ? 'Ganztägig' + ? t('calendar.allDay') : formatDateTime(ev.start_datetime) + (ev.end_datetime ? ` – ${formatTime(ev.end_datetime)} Uhr` : ''); @@ -664,7 +682,7 @@ function showEventPopup(ev, anchor) { ${ev.assigned_name ? `
👤 ${escHtml(ev.assigned_name)}
` : ''}
- + @@ -687,7 +705,7 @@ function showEventPopup(ev, anchor) { }); popup.querySelector('#popup-delete').addEventListener('click', async () => { - if (!confirm(`"${ev.title}" wirklich löschen?`)) return; + if (!confirm(t('calendar.deleteConfirm', { title: ev.title }))) return; popup.remove(); await deleteEvent(ev.id); }); @@ -712,7 +730,7 @@ function openEventModal({ mode, event = null, date = null }) { const content = buildEventModalContent({ mode, event, date }); openSharedModal({ - title: isEdit ? 'Termin bearbeiten' : 'Neuer Termin', + title: isEdit ? t('calendar.editEvent') : t('calendar.newEvent'), content, size: 'md', onSave(panel) { @@ -745,7 +763,7 @@ function openEventModal({ mode, event = null, date = null }) { panel.querySelector('#modal-cancel').addEventListener('click', closeModal); panel.querySelector('#modal-delete')?.addEventListener('click', async () => { - if (!confirm(`"${event.title}" wirklich löschen?`)) return; + if (!confirm(t('calendar.deleteConfirm', { title: event.title }))) return; closeModal(); await deleteEvent(event.id); }); @@ -767,7 +785,7 @@ function buildEventModalContent({ mode, event, date }) { ? event.end_datetime.slice(11, 16) : '10:00'; const userOpts = [ - '', + ``, ...state.users.map((u) => `` ), @@ -775,36 +793,36 @@ function buildEventModalContent({ mode, event, date }) { return `
- + + placeholder="${t('calendar.titlePlaceholder')}" value="${escHtml(isEdit ? event.title : '')}">
- +
- +
- +
- +
@@ -813,29 +831,29 @@ function buildEventModalContent({ mode, event, date }) {
- + + placeholder="${t('calendar.locationPlaceholder')}" value="${escHtml(isEdit && event.location ? event.location : '')}">
- +
- +
${EVENT_COLORS.map((c) => `
- + + placeholder="${t('calendar.descriptionPlaceholder')}">${escHtml(isEdit && event.description ? event.description : '')}
${renderRRuleFields('event', isEdit ? event.recurrence_rule : null)} `; } @@ -868,7 +886,7 @@ async function saveEvent(overlay, mode, eventId) { const title = overlay.querySelector('#modal-title').value.trim(); if (!title) { - window.oikos?.showToast('Titel ist erforderlich', 'error'); + window.oikos?.showToast(t('calendar.titleRequired'), 'error'); return; } @@ -918,11 +936,11 @@ async function saveEvent(overlay, mode, eventId) { closeModal(); renderView(); - window.oikos?.showToast(mode === 'create' ? 'Termin erstellt' : 'Termin gespeichert', 'success'); + window.oikos?.showToast(mode === 'create' ? t('calendar.createdToast') : t('calendar.savedToast'), 'success'); } catch (err) { - window.oikos?.showToast(err.data?.error ?? 'Fehler beim Speichern', 'error'); + window.oikos?.showToast(err.data?.error ?? t('calendar.saveError'), 'error'); saveBtn.disabled = false; - saveBtn.textContent = mode === 'edit' ? 'Speichern' : 'Erstellen'; + saveBtn.textContent = mode === 'edit' ? t('common.save') : t('common.create'); } } @@ -931,9 +949,9 @@ async function deleteEvent(id) { await api.delete(`/calendar/${id}`); state.events = state.events.filter((e) => e.id !== id); renderView(); - window.oikos?.showToast('Termin gelöscht', 'success'); + window.oikos?.showToast(t('calendar.deletedToast'), 'success'); } catch (err) { - window.oikos?.showToast(err.data?.error ?? 'Fehler beim Löschen', 'error'); + window.oikos?.showToast(err.data?.error ?? t('calendar.deleteError'), 'error'); } } diff --git a/public/pages/meals.js b/public/pages/meals.js index d66cdf7..a7f9366 100644 --- a/public/pages/meals.js +++ b/public/pages/meals.js @@ -7,19 +7,23 @@ import { api } from '/api.js'; import { openModal as openSharedModal, closeModal as closeSharedModal } from '/components/modal.js'; import { stagger } from '/utils/ux.js'; +import { t } from '/i18n.js'; // -------------------------------------------------------- // Konstanten // -------------------------------------------------------- -const MEAL_TYPES = [ - { key: 'breakfast', label: 'Frühstück', icon: 'sunrise' }, - { key: 'lunch', label: 'Mittagessen', icon: 'sun' }, - { key: 'dinner', label: 'Abendessen', icon: 'moon' }, - { key: 'snack', label: 'Snack', icon: 'cookie' }, +const MEAL_TYPES = () => [ + { key: 'breakfast', label: t('meals.typeBreakfast'), icon: 'sunrise' }, + { key: 'lunch', label: t('meals.typeLunch'), icon: 'sun' }, + { key: 'dinner', label: t('meals.typeDinner'), icon: 'moon' }, + { key: 'snack', label: t('meals.typeSnack'), icon: 'cookie' }, ]; -const DAY_NAMES = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']; +const DAY_NAMES = () => [ + t('meals.dayMo'), t('meals.dayDi'), t('meals.dayMi'), t('meals.dayDo'), + t('meals.dayFr'), t('meals.daySa'), t('meals.daySo'), +]; // -------------------------------------------------------- // State @@ -84,7 +88,7 @@ async function loadWeek(week) { console.error('[Meals] loadWeek Fehler:', err); state.meals = []; state.currentWeek = getMondayOf(week); - window.oikos?.showToast('Essensplan konnte nicht geladen werden.', 'danger'); + window.oikos?.showToast(t('meals.loadError'), 'danger'); } } @@ -105,19 +109,19 @@ export async function render(container, { user }) { _container = container; container.innerHTML = `
-

Essensplan

+

${t('meals.title')}

- - - +
-
Lade…
+
${t('meals.loadingIndicator')}
`; @@ -144,6 +148,7 @@ function renderWeekGrid() { formatWeekLabel(state.currentWeek); const days = Array.from({ length: 7 }, (_, i) => addDays(state.currentWeek, i)); + const dayNames = DAY_NAMES(); grid.innerHTML = days.map((date, idx) => { const mealsForDay = state.meals.filter((m) => m.date === date); @@ -152,11 +157,11 @@ function renderWeekGrid() { return `
- ${DAY_NAMES[idx]} + ${dayNames[idx]} ${formatDayDate(date)}
- ${MEAL_TYPES.map((type) => renderSlot(date, type, mealsForDay)).join('')} + ${MEAL_TYPES().map((type) => renderSlot(date, type, mealsForDay)).join('')}
`; @@ -175,14 +180,14 @@ function renderSlot(date, type, mealsForDay) {
${type.label}
-
Kein Essen geplant
+
${t('meals.noMealPlanned')}
@@ -192,7 +197,7 @@ function renderSlot(date, type, mealsForDay) { const ingCount = meal.ingredients?.length ?? 0; const ingDone = meal.ingredients?.filter((i) => i.on_shopping_list).length ?? 0; - const ingLabel = ingCount > 0 ? `${ingCount} Zutat${ingCount !== 1 ? 'en' : ''}` : ''; + const ingLabel = ingCount > 0 ? (ingCount !== 1 ? t('meals.ingredientCountPlural', { count: ingCount }) : t('meals.ingredientCount', { count: ingCount })) : ''; const ingDoneLabel = ingCount > 0 && ingDone === ingCount ? ' ✓' : ''; const canTransfer = ingCount > 0 && ingDone < ingCount; @@ -211,12 +216,12 @@ function renderSlot(date, type, mealsForDay) { ${canTransfer ? `` : ''}
@@ -429,7 +434,7 @@ function openMealModal(opts) { const content = buildModalContent(opts); openSharedModal({ - title: isEdit ? 'Mahlzeit bearbeiten' : 'Mahlzeit hinzufügen', + title: isEdit ? t('meals.editMeal') : t('meals.addMealTitle'), content, size: 'md', onSave(panel) { @@ -498,12 +503,12 @@ function openMealModal(opts) { try { const res = await api.post(`/meals/${state.modal.meal.id}/to-shopping-list`, { listId }); if (res.data.transferred > 0) { - window.oikos?.showToast(`${res.data.transferred} Zutat${res.data.transferred !== 1 ? 'en' : ''} übertragen`, 'success'); + window.oikos?.showToast(res.data.transferred !== 1 ? t('meals.transferSuccessPlural', { count: res.data.transferred }) : t('meals.transferSuccess', { count: res.data.transferred }), 'success'); await loadWeek(state.currentWeek); closeModal(); renderWeekGrid(); } else { - window.oikos?.showToast('Alle Zutaten bereits übertragen', 'info'); + window.oikos?.showToast(t('meals.transferAlreadyDone'), 'info'); btn.disabled = false; } } catch (err) { @@ -520,13 +525,13 @@ function openMealModal(opts) { function buildModalContent({ mode, date, mealType, meal }) { const isEdit = mode === 'edit'; - const typeOpts = MEAL_TYPES.map((t) => - `` + const typeOpts = MEAL_TYPES().map((mt) => + `` ).join(''); const listOpts = state.lists.length ? state.lists.map((l) => ``).join('') - : ''; + : ``; const ingRows = isEdit && meal.ingredients?.length ? meal.ingredients.map((ing) => ingredientRowHTML(ing.name, ing.quantity ?? '', ing.id)).join('') @@ -537,36 +542,36 @@ function buildModalContent({ mode, date, mealType, meal }) { return `
- +
- +
- +
- + + placeholder="${t('meals.notesPlaceholder')}">${escHtml(isEdit && meal.notes ? meal.notes : '')}
- +
${ingRows}
@@ -574,26 +579,26 @@ function buildModalContent({ mode, date, mealType, meal }) {
- Zutaten auf Einkaufsliste übertragen + ${t('meals.transferLabel')}
` : ''} `; } function ingredientRowHTML(name, qty, id) { return `
- - -
@@ -613,7 +618,7 @@ async function saveModal(overlay) { const notes = overlay.querySelector('#modal-notes').value.trim() || null; if (!title) { - window.oikos?.showToast('Titel ist erforderlich', 'error'); + window.oikos?.showToast(t('meals.titleRequired'), 'error'); return; } @@ -656,11 +661,11 @@ async function saveModal(overlay) { closeModal(); renderWeekGrid(); - window.oikos?.showToast(mode === 'create' ? 'Mahlzeit hinzugefügt' : 'Mahlzeit gespeichert', 'success'); + window.oikos?.showToast(mode === 'create' ? t('meals.addMealTitle') : t('meals.editMeal'), 'success'); } catch (err) { - window.oikos?.showToast(err.data?.error ?? 'Fehler beim Speichern', 'error'); + window.oikos?.showToast(err.data?.error ?? t('common.errorGeneric'), 'error'); saveBtn.disabled = false; - saveBtn.textContent = state.modal?.mode === 'edit' ? 'Speichern' : 'Hinzufügen'; + saveBtn.textContent = state.modal?.mode === 'edit' ? t('common.save') : t('common.add'); } } @@ -669,14 +674,14 @@ async function saveModal(overlay) { // -------------------------------------------------------- async function deleteMeal(mealId) { - if (!confirm('Mahlzeit wirklich löschen?')) return; + if (!confirm(t('meals.deleteMeal') + '?')) return; try { await api.delete(`/meals/${mealId}`); state.meals = state.meals.filter((m) => m.id !== mealId); renderWeekGrid(); - window.oikos?.showToast('Mahlzeit gelöscht', 'success'); + window.oikos?.showToast(t('meals.deleteMeal'), 'success'); } catch (err) { - window.oikos?.showToast(err.data?.error ?? 'Fehler beim Löschen', 'error'); + window.oikos?.showToast(err.data?.error ?? t('common.errorGeneric'), 'error'); } } @@ -686,7 +691,7 @@ async function deleteMeal(mealId) { async function transferMeal(mealId) { if (!state.lists.length) { - window.oikos?.showToast('Keine Einkaufslisten vorhanden', 'error'); + window.oikos?.showToast(t('meals.noShoppingLists'), 'error'); return; } @@ -703,14 +708,14 @@ async function transferMeal(mealId) { try { const res = await api.post(`/meals/${mealId}/to-shopping-list`, { listId }); if (res.data.transferred > 0) { - window.oikos?.showToast(`${res.data.transferred} Zutat${res.data.transferred !== 1 ? 'en' : ''} übertragen`, 'success'); + window.oikos?.showToast(res.data.transferred !== 1 ? t('meals.transferSuccessPlural', { count: res.data.transferred }) : t('meals.transferSuccess', { count: res.data.transferred }), 'success'); await loadWeek(state.currentWeek); renderWeekGrid(); } else { - window.oikos?.showToast('Alle Zutaten bereits übertragen', 'info'); + window.oikos?.showToast(t('meals.transferAlreadyDone'), 'info'); } } catch (err) { - window.oikos?.showToast(err.data?.error ?? 'Fehler beim Übertragen', 'error'); + window.oikos?.showToast(err.data?.error ?? t('common.errorGeneric'), 'error'); } } diff --git a/public/pages/shopping.js b/public/pages/shopping.js index cd15326..b6991dc 100644 --- a/public/pages/shopping.js +++ b/public/pages/shopping.js @@ -6,6 +6,7 @@ import { api } from '/api.js'; import { stagger, vibrate } from '/utils/ux.js'; +import { t } from '/i18n.js'; // -------------------------------------------------------- // Konstanten @@ -21,6 +22,18 @@ const ITEM_CATEGORIES = [ 'Tiefkühl', 'Getränke', 'Haushalt', 'Drogerie', 'Sonstiges', ]; +const CATEGORY_LABELS = () => ({ + 'Obst & Gemüse': t('shopping.catFruitVeg'), + 'Backwaren': t('shopping.catBakery'), + 'Milchprodukte': t('shopping.catDairy'), + 'Fleisch & Fisch': t('shopping.catMeatFish'), + 'Tiefkühl': t('shopping.catFrozen'), + 'Getränke': t('shopping.catDrinks'), + 'Haushalt': t('shopping.catHousehold'), + 'Drogerie': t('shopping.catDrugstore'), + 'Sonstiges': t('shopping.catMisc'), +}); + const CATEGORY_ICONS = { 'Obst & Gemüse': 'apple', 'Backwaren': 'wheat', @@ -95,9 +108,9 @@ function renderListContent(container) { content.innerHTML = `
-
Keine Listen
+
${t('shopping.noLists')}
- Erstelle eine Liste mit dem + Button. + ${t('shopping.noListsDescription')}
`; if (window.lucide) window.lucide.createIcons(); @@ -110,7 +123,7 @@ function renderListContent(container) {
+ role="button" tabindex="0" aria-label="${t('shopping.renameListLabel')}"> ${state.activeList.name} @@ -119,10 +132,10 @@ function renderListContent(container) { ` : ''} @@ -134,17 +147,17 @@ function renderListContent(container) {
+ placeholder="${t('shopping.itemNamePlaceholder')}" aria-label="${t('shopping.itemNameLabel')}" autocomplete="off"> + placeholder="${t('shopping.itemQtyPlaceholder')}" aria-label="${t('shopping.itemQtyLabel')}" autocomplete="off">
- ${ITEM_CATEGORIES.map((c) => `` ).join('')} -
@@ -170,17 +183,18 @@ function renderItems() { -
Die Liste ist leer
-
Artikel über das Eingabefeld oben hinzufügen.
+
${t('shopping.emptyList')}
+
${t('shopping.emptyListDescription')}
`; } + const catLabels = CATEGORY_LABELS(); const groups = groupItemsByCategory(state.items); return groups.map(([cat, items]) => `
- ${cat} + ${catLabels[cat] || cat}
${items.map(renderItem).join('')}
`).join(''); @@ -192,17 +206,17 @@ function renderItem(item) {
@@ -210,7 +224,7 @@ function renderItem(item) { ${item.quantity ? `
${escHtml(item.quantity)}
` : ''}
@@ -474,7 +488,7 @@ function updateItemsList(container) { `); if (window.lucide) window.lucide.createIcons(); } else if (clearBtn) { @@ -483,7 +497,7 @@ function updateItemsList(container) { } else { clearBtn.innerHTML = ` - Abgehakt löschen (${checkedCount})`; + ${t('shopping.clearChecked', { count: checkedCount })}`; if (window.lucide) window.lucide.createIcons(); } } @@ -509,7 +523,7 @@ async function loadLists() { } catch (err) { console.error('[Shopping] loadLists Fehler:', err); state.lists = []; - window.oikos?.showToast('Listen konnten nicht geladen werden.', 'danger'); + window.oikos?.showToast(t('shopping.listsLoadError'), 'danger'); } } @@ -528,7 +542,7 @@ async function switchList(listId, container) { console.error('[Shopping] loadItems Fehler:', err); state.items = []; state.activeList = state.lists.find((l) => l.id === listId) ?? null; - window.oikos?.showToast('Artikel konnten nicht geladen werden.', 'danger'); + window.oikos?.showToast(t('shopping.itemsLoadError'), 'danger'); } renderListContent(container); wireListContentEvents(container); @@ -548,7 +562,7 @@ function wireTabBar(container) { } if (target.dataset.action === 'new-list') { - const name = prompt('Name der neuen Liste:'); + const name = prompt(t('shopping.newListPrompt')); if (!name?.trim()) return; try { const data = await api.post('/shopping', { name: name.trim() }); @@ -621,7 +635,7 @@ function wireListContentEvents(container) { updateItemsList(container); updateListCounter(state.activeListId, -count, -count); renderTabs(container); - window.oikos.showToast(`${count} Artikel entfernt.`); + window.oikos.showToast(t('shopping.itemsRemovedToast', { count })); } catch (err) { window.oikos.showToast(err.message, 'danger'); } @@ -629,7 +643,7 @@ function wireListContentEvents(container) { // ---- Liste umbenennen ---- if (action === 'rename-list') { - const newName = prompt('Neuer Listen-Name:', state.activeList?.name); + const newName = prompt(t('shopping.renameListPrompt'), state.activeList?.name); if (!newName?.trim() || newName.trim() === state.activeList?.name) return; try { const data = await api.put(`/shopping/${state.activeListId}`, { name: newName.trim() }); @@ -646,7 +660,7 @@ function wireListContentEvents(container) { // ---- Liste löschen ---- if (action === 'delete-list') { - if (!confirm(`Liste "${state.activeList?.name}" und alle Artikel löschen?`)) return; + if (!confirm(t('shopping.deleteListConfirm', { name: state.activeList?.name }))) return; try { await api.delete(`/shopping/${state.activeListId}`); state.lists = state.lists.filter((l) => l.id !== state.activeListId); @@ -659,7 +673,7 @@ function wireListContentEvents(container) { renderTabs(container); renderListContent(container); } - window.oikos.showToast('Liste gelöscht.'); + window.oikos.showToast(t('shopping.deletedListToast')); } catch (err) { window.oikos.showToast(err.message, 'danger'); } @@ -701,15 +715,15 @@ export async function render(container, { user }) { } } catch (err) { console.error('[Shopping] Ladefehler:', err.message); - window.oikos.showToast('Einkaufslisten konnten nicht geladen werden.', 'danger'); + window.oikos.showToast(t('shopping.listsLoadError'), 'danger'); } container.innerHTML = `
-

Einkaufslisten

+

${t('shopping.title')}

-