From 26bbd61e1da2ddee17a21b3ce1cc0f58cd3c8864 Mon Sep 17 00:00:00 2001 From: Ulas Date: Tue, 31 Mar 2026 22:57:45 +0200 Subject: [PATCH] feat: i18n notes, contacts, budget, settings pages Co-Authored-By: Claude Sonnet 4.6 --- public/pages/budget.js | 92 ++++++++++++++---------- public/pages/contacts.js | 77 ++++++++++---------- public/pages/notes.js | 71 +++++++++--------- public/pages/settings.js | 151 ++++++++++++++++++++------------------- 4 files changed, 206 insertions(+), 185 deletions(-) diff --git a/public/pages/budget.js b/public/pages/budget.js index 301a209..bdfa7a2 100644 --- a/public/pages/budget.js +++ b/public/pages/budget.js @@ -8,6 +8,7 @@ import { api } from '/api.js'; import { openModal as openSharedModal, closeModal } from '/components/modal.js'; import { stagger, vibrate } from '/utils/ux.js'; +import { t } from '/i18n.js'; // -------------------------------------------------------- // Konstanten @@ -18,6 +19,18 @@ const CATEGORIES = [ 'Freizeit', 'Kleidung', 'Gesundheit', 'Bildung', 'Sonstiges', ]; +const CATEGORY_LABELS = () => ({ + 'Lebensmittel': t('budget.catFood'), + 'Miete': t('budget.catRent'), + 'Versicherung': t('budget.catInsurance'), + 'Mobilität': t('budget.catMobility'), + 'Freizeit': t('budget.catLeisure'), + 'Kleidung': t('budget.catClothing'), + 'Gesundheit': t('budget.catHealth'), + 'Bildung': t('budget.catEducation'), + 'Sonstiges': t('budget.catMisc'), +}); + const MONTH_NAMES = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']; @@ -74,7 +87,7 @@ async function loadMonth(month) { state.entries = []; state.summary = { income: 0, expenses: 0, balance: 0, byCategory: [] }; state.prevSummary = null; - window.oikos?.showToast('Budget konnte nicht geladen werden.', 'danger'); + window.oikos?.showToast(t('budget.loadError'), 'danger'); } } @@ -89,24 +102,24 @@ export async function render(container, { user }) { container.innerHTML = `
-

Budget

+

${t('budget.title')}

- - + - -
-
Lade…
+
${t('budget.loadingIndicator')}
-
@@ -171,17 +184,17 @@ function renderBody() {
-
Einnahmen
+
${t('budget.income')}
${formatAmount(s.income)}
${p ? renderTrend(s.income, p.income, prevLabel) : ''}
-
Ausgaben
+
${t('budget.expenses')}
${formatAmount(Math.abs(s.expenses))}
${p ? renderTrend(s.expenses, p.expenses, prevLabel) : ''}
-
Saldo
+
${t('budget.balance')}
${formatAmount(s.balance)}
${p ? renderTrend(s.balance, p.balance, prevLabel) : ''}
@@ -190,7 +203,7 @@ function renderBody() { ${s.byCategory.length ? ` @@ -298,7 +311,7 @@ function renderEntries() { function renderTrend(current, prev, prevLabel) { const delta = current - prev; if (Math.abs(delta) < 0.005) { - return `
— wie ${prevLabel}
`; + return `
${t('budget.trendNeutral', { month: prevLabel })}
`; } const positive = delta > 0; const arrow = positive ? '▲' : '▼'; @@ -323,38 +336,39 @@ function openBudgetModal({ mode, entry = null }) { const isExpense = isEdit ? entry.amount < 0 : true; const absAmount = isEdit ? Math.abs(entry.amount).toFixed(2) : ''; + const catLabels = CATEGORY_LABELS(); const catOpts = CATEGORIES.map((c) => - `` + `` ).join(''); const content = `
+ id="type-expense" type="button">${t('budget.typeExpense')} + id="type-income" type="button">${t('budget.typeIncome')}
- + + placeholder="${t('budget.titlePlaceholder')}" value="${escHtml(isEdit ? entry.title : '')}">
- +
- +
- +
@@ -362,22 +376,22 @@ function openBudgetModal({ mode, entry = null }) {
`; openSharedModal({ - title: isEdit ? 'Eintrag bearbeiten' : 'Neuer Eintrag', + title: isEdit ? t('budget.editEntry') : t('budget.newEntry'), content, size: 'sm', onSave(panel) { @@ -397,7 +411,7 @@ function openBudgetModal({ mode, entry = null }) { panel.querySelector('#bm-cancel').addEventListener('click', closeModal); panel.querySelector('#bm-delete')?.addEventListener('click', async () => { - if (!confirm(`"${entry.title}" wirklich löschen?`)) return; + if (!confirm(t('budget.deletePersonConfirm', { title: entry.title }))) return; closeModal(); await deleteEntry(entry.id); }); @@ -410,9 +424,9 @@ function openBudgetModal({ mode, entry = null }) { const date = panel.querySelector('#bm-date').value; const recurring = panel.querySelector('#bm-recurring').checked ? 1 : 0; - if (!title) { window.oikos?.showToast('Titel ist erforderlich', 'error'); return; } - if (isNaN(absVal) || absVal <= 0) { window.oikos?.showToast('Gültigen Betrag eingeben', 'error'); return; } - if (!date) { window.oikos?.showToast('Datum ist erforderlich', 'error'); return; } + if (!title) { window.oikos?.showToast(t('common.titleRequired'), 'error'); return; } + if (isNaN(absVal) || absVal <= 0) { window.oikos?.showToast(t('budget.validAmountRequired'), 'error'); return; } + if (!date) { window.oikos?.showToast(t('budget.dateRequired'), 'error'); return; } const amount = currentType === 'expense' ? -absVal : absVal; @@ -434,11 +448,11 @@ function openBudgetModal({ mode, entry = null }) { closeModal(); renderBody(); - window.oikos?.showToast(mode === 'create' ? 'Eintrag hinzugefügt' : 'Eintrag gespeichert', 'success'); + window.oikos?.showToast(mode === 'create' ? t('budget.addedToast') : t('budget.savedToast'), 'success'); } catch (err) { window.oikos?.showToast(err.data?.error ?? 'Fehler', 'error'); saveBtn.disabled = false; - saveBtn.textContent = isEdit ? 'Speichern' : 'Hinzufügen'; + saveBtn.textContent = isEdit ? t('common.save') : t('common.add'); } }); }, @@ -450,7 +464,7 @@ function openBudgetModal({ mode, entry = null }) { // -------------------------------------------------------- async function deleteEntry(id) { - if (!confirm('Eintrag wirklich löschen?')) return; + if (!confirm(t('budget.deleteConfirm'))) return; try { await api.delete(`/budget/${id}`); state.entries = state.entries.filter((e) => e.id !== id); @@ -458,7 +472,7 @@ async function deleteEntry(id) { state.summary = sumRes.data; renderBody(); vibrate([30, 50, 30]); - window.oikos?.showToast('Eintrag gelöscht', 'success'); + window.oikos?.showToast(t('budget.deletedToast'), 'success'); } catch (err) { window.oikos?.showToast(err.data?.error ?? 'Fehler', 'error'); } diff --git a/public/pages/contacts.js b/public/pages/contacts.js index a19b225..57d2b3c 100644 --- a/public/pages/contacts.js +++ b/public/pages/contacts.js @@ -7,6 +7,7 @@ import { api } from '/api.js'; import { openModal as openSharedModal, closeModal } from '/components/modal.js'; import { stagger, vibrate } from '/utils/ux.js'; +import { t } from '/i18n.js'; // -------------------------------------------------------- // Konstanten @@ -44,32 +45,32 @@ export async function render(container, { user }) { _container = container; container.innerHTML = `
-

Kontakte

+

${t('contacts.title')}

-
- + ${CATEGORIES.map((c) => ` `).join('')}
-
@@ -115,13 +116,13 @@ export async function render(container, { user }) { try { const text = await file.text(); const contact = parseVCard(text); - if (!contact.name) { window.oikos?.showToast('vCard enthält keinen Namen.', 'warning'); return; } + if (!contact.name) { window.oikos?.showToast(t('contacts.vcardNoName'), 'warning'); return; } const res = await api.post('/contacts', contact); state.contacts.push(res.data); renderList(); - window.oikos?.showToast(`${res.data.name} importiert.`, 'success'); + window.oikos?.showToast(t('contacts.importedToast', { name: res.data.name }), 'success'); } catch (err) { - window.oikos?.showToast('Import fehlgeschlagen: ' + err.message, 'danger'); + window.oikos?.showToast(t('contacts.importError', { error: err.message }), 'danger'); } }); } @@ -164,8 +165,8 @@ function renderList() { -
Noch keine Kontakte
-
Neue Kontakte über den + Button hinzufügen.
+
${t('contacts.emptyTitle')}
+
${t('contacts.emptyDescription')}
`; if (window.lucide) lucide.createIcons(); @@ -207,9 +208,9 @@ function renderList() { } function renderContactItem(c) { - const phone = c.phone ? `` : ''; - const email = c.email ? `` : ''; - const maps = c.address ? `` : ''; + const phone = c.phone ? `` : ''; + const email = c.email ? `` : ''; + const maps = c.address ? `` : ''; const meta = [c.phone, c.email].filter(Boolean).join(' · '); return ` @@ -222,10 +223,10 @@ function renderContactItem(c) { @@ -247,49 +248,49 @@ function openContactModal({ mode, contact = null }) { const content = `
- - + +
- +
- - + +
- - + +
- - + +
- - + +
`; openSharedModal({ - title: isEdit ? 'Kontakt bearbeiten' : 'Neuer Kontakt', + title: isEdit ? t('contacts.editContact') : t('contacts.newContact'), content, size: 'md', onSave(panel) { panel.querySelector('#cm-cancel').addEventListener('click', closeModal); panel.querySelector('#cm-delete')?.addEventListener('click', async () => { - if (!confirm(`"${contact.name}" wirklich löschen?`)) return; + if (!confirm(t('contacts.deletePersonConfirm', { name: contact.name }))) return; closeModal(); await deleteContact(contact.id); }); @@ -303,7 +304,7 @@ function openContactModal({ mode, contact = null }) { const address = panel.querySelector('#cm-address').value.trim() || null; const notes = panel.querySelector('#cm-notes').value.trim() || null; - if (!name) { window.oikos?.showToast('Name ist erforderlich', 'error'); return; } + if (!name) { window.oikos?.showToast(t('common.nameRequired'), 'error'); return; } saveBtn.disabled = true; saveBtn.textContent = '…'; @@ -324,11 +325,11 @@ function openContactModal({ mode, contact = null }) { } closeModal(); renderList(); - window.oikos?.showToast(mode === 'create' ? 'Kontakt gespeichert' : 'Kontakt aktualisiert', 'success'); + window.oikos?.showToast(mode === 'create' ? t('contacts.savedToast') : t('contacts.updatedToast'), 'success'); } catch (err) { window.oikos?.showToast(err.data?.error ?? 'Fehler', 'error'); saveBtn.disabled = false; - saveBtn.textContent = isEdit ? 'Speichern' : 'Erstellen'; + saveBtn.textContent = isEdit ? t('common.save') : t('common.create'); } }); }, @@ -336,13 +337,13 @@ function openContactModal({ mode, contact = null }) { } async function deleteContact(id) { - if (!confirm('Kontakt wirklich löschen?')) return; + if (!confirm(t('contacts.deleteConfirm'))) return; try { await api.delete(`/contacts/${id}`); state.contacts = state.contacts.filter((c) => c.id !== id); renderList(); vibrate([30, 50, 30]); - window.oikos?.showToast('Kontakt gelöscht', 'success'); + window.oikos?.showToast(t('contacts.deletedToast'), 'success'); } catch (err) { window.oikos?.showToast(err.data?.error ?? 'Fehler', 'error'); } diff --git a/public/pages/notes.js b/public/pages/notes.js index 009c52b..bbbe076 100644 --- a/public/pages/notes.js +++ b/public/pages/notes.js @@ -7,6 +7,7 @@ import { api } from '/api.js'; import { openModal as openSharedModal, closeModal, btnError } from '/components/modal.js'; import { stagger, vibrate } from '/utils/ux.js'; +import { t } from '/i18n.js'; // -------------------------------------------------------- // Konstanten @@ -48,20 +49,20 @@ export async function render(container, { user }) { container.innerHTML = `
-

Pinnwand

+

${t('notes.title')}

-
@@ -75,7 +76,7 @@ export async function render(container, { user }) { } catch (err) { console.error('[Notes] Laden fehlgeschlagen:', err); state.notes = []; - window.oikos?.showToast('Notizen konnten nicht geladen werden.', 'danger'); + window.oikos?.showToast(t('notes.loadError'), 'danger'); } const grid = container.querySelector('#notes-grid'); grid.addEventListener('click', async (e) => { @@ -131,8 +132,8 @@ function renderGrid() { -
${isFiltered ? 'Keine Treffer' : 'Noch keine Notizen'}
-
${isFiltered ? `Keine Notiz enthält „${escHtml(state.filterQuery)}".` : 'Neue Notiz über den + Button erstellen.'}
+
${isFiltered ? t('notes.noResultsTitle') : t('notes.emptyTitle')}
+
${isFiltered ? t('notes.noResultsDescription', { query: state.filterQuery }) : t('notes.emptyDescription')}
`; if (window.lucide) lucide.createIcons(); @@ -156,7 +157,7 @@ function renderNoteCard(note) { data-id="${note.id}" style="background-color:${escHtml(note.color)};color:${textColor};"> ${note.title ? `
${escHtml(note.title)}
` : ''} @@ -167,7 +168,7 @@ function renderNoteCard(note) { style="background-color:${escHtml(note.creator_color || '#8E8E93')}">${initials} ${escHtml(note.creator_name || '')} - @@ -315,58 +316,58 @@ function openNoteModal({ mode, note = null }) { const content = `
- + + placeholder="${t('notes.titlePlaceholder')}" value="${escHtml(isEdit && note.title ? note.title : '')}">
- +
- - - - - - - - - - - -
- +
${NOTE_COLORS.map((c) => `
`; openSharedModal({ - title: isEdit ? 'Notiz bearbeiten' : 'Neue Notiz', + title: isEdit ? t('notes.editNote') : t('notes.newNote'), content, size: 'md', onSave(panel) { @@ -427,7 +428,7 @@ function openNoteModal({ mode, note = null }) { const color = panel.querySelector('.note-color-swatch--active')?.dataset.color || NOTE_COLORS[0]; const pinned = panel.querySelector('#note-pinned').checked ? 1 : 0; - if (!cnt) { window.oikos?.showToast('Inhalt ist erforderlich', 'error'); return; } + if (!cnt) { window.oikos?.showToast(t('common.contentRequired'), 'error'); return; } saveBtn.disabled = true; saveBtn.textContent = '…'; @@ -444,12 +445,12 @@ function openNoteModal({ mode, note = null }) { } closeModal(); renderGrid(); - window.oikos?.showToast(mode === 'create' ? 'Notiz erstellt' : 'Notiz gespeichert', 'success'); + window.oikos?.showToast(mode === 'create' ? t('notes.createdToast') : t('notes.savedToast'), 'success'); } catch (err) { window.oikos?.showToast(err.data?.error ?? 'Fehler', 'error'); btnError(saveBtn); saveBtn.disabled = false; - saveBtn.textContent = isEdit ? 'Speichern' : 'Erstellen'; + saveBtn.textContent = isEdit ? t('common.save') : t('common.create'); } }); }, @@ -473,13 +474,13 @@ async function togglePin(id) { } async function deleteNote(id) { - if (!confirm('Notiz wirklich löschen?')) return; + if (!confirm(t('notes.deleteConfirm'))) return; try { await api.delete(`/notes/${id}`); state.notes = state.notes.filter((n) => n.id !== id); renderGrid(); vibrate([30, 50, 30]); - window.oikos?.showToast('Notiz gelöscht', 'success'); + window.oikos?.showToast(t('notes.deletedToast'), 'success'); } catch (err) { window.oikos?.showToast(err.data?.error ?? 'Fehler', 'error'); } diff --git a/public/pages/settings.js b/public/pages/settings.js index 29c5f4d..66c2423 100644 --- a/public/pages/settings.js +++ b/public/pages/settings.js @@ -5,6 +5,7 @@ */ import { api, auth } from '/api.js'; +import { t } from '/i18n.js'; /** * @param {HTMLElement} container @@ -32,32 +33,42 @@ export async function render(container, { user }) { if (aStatus.status === 'fulfilled') appleStatus = aStatus.value; } catch (_) { /* non-critical */ } + const googleStatusText = googleStatus.connected + ? (googleStatus.lastSync ? t('settings.connectedLastSync', { date: formatDate(googleStatus.lastSync) }) : t('settings.connected')) + : googleStatus.configured ? t('settings.notConnected') : t('settings.notConfigured'); + + const appleStatusText = appleStatus.connected + ? (appleStatus.lastSync ? t('settings.connectedLastSync', { date: formatDate(appleStatus.lastSync) }) : t('settings.connected')) + : appleStatus.configured + ? (appleStatus.lastSync ? t('settings.configuredLastSync', { date: formatDate(appleStatus.lastSync) }) : t('settings.configured')) + : t('settings.notConnected'); + container.innerHTML = `
- ${syncOk ? `
Kalender-Sync mit ${syncOk === 'google' ? 'Google' : 'Apple'} erfolgreich verbunden.
` : ''} - ${syncErr ? `
Verbindung mit ${syncErr === 'google' ? 'Google' : 'Apple'} fehlgeschlagen. Bitte erneut versuchen.
` : ''} + ${syncOk ? `
${syncOk === 'google' ? t('settings.syncSuccessGoogle') : t('settings.syncSuccessApple')}
` : ''} + ${syncErr ? `
${syncErr === 'google' ? t('settings.syncErrorGoogle') : t('settings.syncErrorApple')}
` : ''}
-

Design

+

${t('settings.sectionDesign')}

-

Darstellung

+

${t('settings.cardAppearance')}

- - -
@@ -65,7 +76,7 @@ export async function render(container, { user }) {
-

Mein Konto

+

${t('settings.sectionAccount')}

-

Passwort ändern

+

${t('settings.changePassword')}

- +
- +
- +
- +
-

Kalender-Synchronisation

+

${t('settings.sectionCalendarSync')}

@@ -116,21 +127,19 @@ export async function render(container, { user }) {
-
Google Calendar
+
${t('settings.googleCalendar')}
- ${googleStatus.connected - ? `Verbunden${googleStatus.lastSync ? ` · Zuletzt: ${formatDate(googleStatus.lastSync)}` : ''}` - : googleStatus.configured ? 'Nicht verbunden' : 'Nicht konfiguriert (fehlende .env-Variablen)'} + ${googleStatusText}
${googleStatus.configured ? `
${googleStatus.connected ? ` - - ${user?.role === 'admin' ? `` : ''} + + ${user?.role === 'admin' ? `` : ''} ` : ` - ${user?.role === 'admin' ? `Mit Google verbinden` : 'Nur Admin kann Google Calendar verbinden.'} + ${user?.role === 'admin' ? `${t('settings.connectGoogle')}` : `${t('settings.googleOnlyAdmin')}`} `}
` : ''} @@ -145,84 +154,80 @@ export async function render(container, { user }) {
-
Apple Calendar (iCloud)
+
${t('settings.appleCalendar')}
- ${appleStatus.connected - ? `Verbunden${appleStatus.lastSync ? ` · Zuletzt: ${formatDate(appleStatus.lastSync)}` : ''}` - : appleStatus.configured - ? `Konfiguriert (via .env)${appleStatus.lastSync ? ` · Zuletzt: ${formatDate(appleStatus.lastSync)}` : ''}` - : 'Nicht verbunden'} + ${appleStatusText}
${appleStatus.configured ? `
- - ${appleStatus.connected && user?.role === 'admin' ? `` : ''} + + ${appleStatus.connected && user?.role === 'admin' ? `` : ''}
` : user?.role === 'admin' ? `
- - + +
- +
- + - Passwort unter appleid.apple.com → Sicherheit erstellen. + ${t('settings.applePasswordHint')}
- +
- ` : 'Nur Admin kann Apple Calendar verbinden.'} + ` : `${t('settings.appleOnlyAdmin')}`} ${user?.role === 'admin' ? `
-

Familienmitglieder

+

${t('settings.sectionFamily')}

    ${users.map(memberHtml).join('')}
- +
-

Neues Familienmitglied

+

${t('settings.newMemberTitle')}

- +
- +
- +
- +
- +
- - + +
@@ -231,7 +236,7 @@ export async function render(container, { user }) {
- +
`; @@ -270,7 +275,7 @@ function bindEvents(container, user) { errorEl.hidden = true; if (newPw !== confirmPw) { - showError(errorEl, 'Passwörter stimmen nicht überein.'); + showError(errorEl, t('settings.passwordMismatch')); return; } @@ -279,7 +284,7 @@ function bindEvents(container, user) { try { await api.patch('/auth/me/password', { current_password: currentPw, new_password: newPw }); passwordForm.reset(); - window.oikos?.showToast('Passwort erfolgreich geändert.', 'success'); + window.oikos?.showToast(t('settings.passwordSavedToast'), 'success'); } catch (err) { showError(errorEl, err.message); } finally { @@ -293,15 +298,15 @@ function bindEvents(container, user) { if (googleSyncBtn) { googleSyncBtn.addEventListener('click', async () => { googleSyncBtn.disabled = true; - googleSyncBtn.textContent = 'Synchronisiere…'; + googleSyncBtn.textContent = t('settings.synchronizing'); try { await api.post('/calendar/google/sync', {}); - window.oikos?.showToast('Google Calendar synchronisiert.', 'success'); + window.oikos?.showToast(t('settings.syncSuccess', { provider: 'Google Calendar' }), 'success'); } catch (err) { window.oikos?.showToast(err.message, 'danger'); } finally { googleSyncBtn.disabled = false; - googleSyncBtn.textContent = 'Jetzt synchronisieren'; + googleSyncBtn.textContent = t('settings.syncNow'); } }); } @@ -310,10 +315,10 @@ function bindEvents(container, user) { const googleDisconnectBtn = container.querySelector('#google-disconnect-btn'); if (googleDisconnectBtn) { googleDisconnectBtn.addEventListener('click', async () => { - if (!confirm('Google Calendar-Verbindung trennen?')) return; + if (!confirm(t('settings.googleDisconnectConfirm'))) return; try { await api.delete('/calendar/google/disconnect'); - window.oikos?.showToast('Google Calendar getrennt.', 'default'); + window.oikos?.showToast(t('settings.disconnectedToast', { provider: 'Google Calendar' }), 'default'); window.oikos?.navigate('/settings'); } catch (err) { window.oikos?.showToast(err.message, 'danger'); @@ -326,15 +331,15 @@ function bindEvents(container, user) { if (appleSyncBtn) { appleSyncBtn.addEventListener('click', async () => { appleSyncBtn.disabled = true; - appleSyncBtn.textContent = 'Synchronisiere…'; + appleSyncBtn.textContent = t('settings.synchronizing'); try { await api.post('/calendar/apple/sync', {}); - window.oikos?.showToast('Apple Calendar synchronisiert.', 'success'); + window.oikos?.showToast(t('settings.syncSuccess', { provider: 'Apple Calendar' }), 'success'); } catch (err) { window.oikos?.showToast(err.message, 'danger'); } finally { appleSyncBtn.disabled = false; - appleSyncBtn.textContent = 'Jetzt synchronisieren'; + appleSyncBtn.textContent = t('settings.syncNow'); } }); } @@ -343,10 +348,10 @@ function bindEvents(container, user) { const appleDisconnectBtn = container.querySelector('#apple-disconnect-btn'); if (appleDisconnectBtn) { appleDisconnectBtn.addEventListener('click', async () => { - if (!confirm('Apple Calendar-Verbindung trennen?')) return; + if (!confirm(t('settings.appleDisconnectConfirm'))) return; try { await api.delete('/calendar/apple/disconnect'); - window.oikos?.showToast('Apple Calendar getrennt.', 'default'); + window.oikos?.showToast(t('settings.disconnectedToast', { provider: 'Apple Calendar' }), 'default'); window.oikos?.navigate('/settings'); } catch (err) { window.oikos?.showToast(err.message, 'danger'); @@ -368,16 +373,16 @@ function bindEvents(container, user) { const btn = container.querySelector('#apple-connect-btn'); btn.disabled = true; - btn.textContent = 'Verbinde…'; + btn.textContent = t('settings.appleConnecting'); try { await api.post('/calendar/apple/connect', { url, username, password }); - window.oikos?.showToast('Apple Calendar verbunden.', 'success'); + window.oikos?.showToast(t('settings.appleConnectedToast'), 'success'); window.oikos?.navigate('/settings'); } catch (err) { showError(errorEl, err.message); } finally { btn.disabled = false; - btn.textContent = 'Verbinden & testen'; + btn.textContent = t('settings.appleConnectBtn'); } }); } @@ -425,7 +430,7 @@ function bindEvents(container, user) { addMemberForm.reset(); container.querySelector('#add-member-form-card').classList.add('settings-card--hidden'); container.querySelector('#add-member-btn').hidden = false; - window.oikos?.showToast(`${res.user.display_name} hinzugefügt.`, 'success'); + window.oikos?.showToast(t('settings.memberAddedToast', { name: res.user.display_name }), 'success'); bindDeleteButtons(container, user); } catch (err) { showError(errorEl, err.message); @@ -458,11 +463,11 @@ function bindDeleteButtons(container, user) { btn.addEventListener('click', async () => { const id = parseInt(btn.dataset.deleteUser, 10); const name = btn.dataset.name; - if (!confirm(`${name} wirklich löschen?`)) return; + if (!confirm(t('settings.deleteMemberConfirm', { name }))) return; try { await auth.deleteUser(id); btn.closest('.settings-member').remove(); - window.oikos?.showToast(`${name} gelöscht.`, 'default'); + window.oikos?.showToast(t('settings.memberDeletedToast', { name }), 'default'); } catch (err) { window.oikos?.showToast(err.message, 'danger'); } @@ -480,9 +485,9 @@ function memberHtml(u) {
${initials(u.display_name)}
${u.display_name} - @${u.username} · ${u.role === 'admin' ? 'Admin' : 'Mitglied'} + @${u.username} · ${u.role === 'admin' ? t('settings.roleAdmin') : t('settings.roleMember')}
-