@@ -300,7 +300,7 @@ export const WebhooksTab: React.FC = () => {
{wh.name}
{wh.url}
- {Array.isArray(wh.events) ? wh.events.length : 0} subscribed
+ {t('settings.webhooks.eventsSubscribed', { count: Array.isArray(wh.events) ? wh.events.length : 0 })}
{lastEither === 'success' && lastSuccess && (
@@ -325,9 +325,9 @@ export const WebhooksTab: React.FC = () => {
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300'
: 'bg-neutral-200 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-400'
}`}
- title={wh.active ? 'Click to disable' : 'Click to enable'}
+ title={wh.active ? t('settings.webhooks.toggleToDisable', 'Click to disable') : t('settings.webhooks.toggleToEnable', 'Click to enable')}
>
- {wh.active ? 'Active' : 'Disabled'}
+ {wh.active ? t('settings.webhooks.statusActive', 'Active') : t('settings.webhooks.statusDisabled', 'Disabled')}
@@ -337,19 +337,19 @@ export const WebhooksTab: React.FC = () => {
className="inline-flex items-center gap-1 px-2 py-1 text-xs text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-neutral-100"
>
- Deliveries
+ {t('settings.webhooks.deliveriesLink', 'Deliveries')}
}
onClick={() => {
- if (confirm(`Delete "${wh.name}"? Pending deliveries are also removed.`)) {
+ if (confirm(t('settings.webhooks.confirmDelete', { name: wh.name, defaultValue: `Delete "${wh.name}"? Pending deliveries are also removed.` }))) {
deleteMutation.mutate(wh.id);
}
}}
>
- Delete
+ {t('settings.webhooks.delete', 'Delete')}
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json
index 4e87802a..4bcf8b7f 100644
--- a/frontend/src/i18n/locales/de.json
+++ b/frontend/src/i18n/locales/de.json
@@ -1312,7 +1312,9 @@
"showGalleryFilterBar": "Filterleiste in Galerien anzeigen",
"showGalleryFilterBarHelp": "Dateiname-Suche und Sortiersteuerungen über Raster-Galerien anzeigen. Deaktivieren für ein übersichtlicheres Layout.",
"enablePhoneField": "Telefonnummer-Feld aktivieren",
- "enablePhoneFieldHelp": "Fügt ein optionales Telefonnummer-Eingabefeld zum Veranstaltungsformular hinzu. Nützlich für Folgeautomatisierungen wie WhatsApp-Zustellung via n8n. Immer optional, auch wenn aktiviert."
+ "enablePhoneFieldHelp": "Fügt ein optionales Telefonnummer-Eingabefeld zum Veranstaltungsformular hinzu. Nützlich für Folgeautomatisierungen wie WhatsApp-Zustellung via n8n. Immer optional, auch wenn aktiviert.",
+ "defaultFeedbackEnabled": "Gäste-Feedback standardmäßig aktivieren",
+ "defaultFeedbackEnabledHelp": "Vorbelegt \"Gäste-Feedback\" beim Erstellen neuer Events. Einzelne Feedback-Optionen (Likes, Bewertungen, Kommentare) bleiben pro Event anpassbar."
},
"imageSecurity": {
"title": "Bildschutz",
@@ -1560,9 +1562,10 @@
"statusRevoked": "Widerrufen",
"statusExpired": "Abgelaufen",
"statusActive": "Aktiv",
- "confirmRevoke": "Diesen Token wirklich widerrufen?",
+ "confirmRevoke": "\"{{name}}\" widerrufen? Bestehende Integrationen, die dieses Token nutzen, erhalten ab sofort 401.",
"revoke": "Widerrufen",
- "empty": "Noch keine Tokens. Generieren Sie oben einen, um zu beginnen."
+ "empty": "Noch keine Tokens. Generieren Sie oben einen, um zu beginnen.",
+ "preview": "Vorschau"
},
"webhooks": {
"title": "Webhooks",
@@ -1576,7 +1579,38 @@
"template": "Vorlage (optional)",
"create": "Webhook erstellen",
"existing": "Vorhandene Webhooks",
- "empty": "Noch keine Webhooks. Erstellen Sie oben einen, um Ereignisbenachrichtigungen zu erhalten."
+ "empty": "Noch keine Webhooks. Erstellen Sie oben einen, um Ereignisbenachrichtigungen zu erhalten.",
+ "namePlaceholder": "z. B. n8n WhatsApp",
+ "urlPlaceholder": "https://n8n.example.com/webhook/picpeak",
+ "showAdvanced": "+ Erweitert (Filter, Vorlage)",
+ "hideAdvanced": "− Erweitert ausblenden (Filter, Vorlage)",
+ "filterHelp": "Dot-Pfad → erwarteter Wert. Alle Schlüssel müssen übereinstimmen (AND). Array für \"eines aus\".",
+ "filterError": "Filter muss gültiges JSON sein",
+ "templatePlaceholder": "Neue Galerie: ${data.event.event_name} → ${data.event.share_url}",
+ "templateHelp": "Ersetzt den Standard-JSON-Umschlag als Request-Body. Nur ${dot.path}-Substitution aus der Payload — keine Logik, keine Ausdrücke.",
+ "copyButton": "Kopieren",
+ "dismiss": "Schließen",
+ "copied": "Kopiert",
+ "copyFailed": "Kopieren fehlgeschlagen",
+ "colName": "Name",
+ "colUrl": "URL",
+ "colEvents": "Events",
+ "colLastDelivery": "Letzte Zustellung",
+ "colStatus": "Status",
+ "colActions": "Aktionen",
+ "eventsSubscribed_one": "{{count}} abonniert",
+ "eventsSubscribed_other": "{{count}} abonniert",
+ "statusActive": "Aktiv",
+ "statusDisabled": "Deaktiviert",
+ "toggleToDisable": "Klicken zum Deaktivieren",
+ "toggleToEnable": "Klicken zum Aktivieren",
+ "deliveriesLink": "Zustellungen",
+ "delete": "Löschen",
+ "confirmDelete": "\"{{name}}\" löschen? Ausstehende Zustellungen werden ebenfalls entfernt.",
+ "createError": "Webhook konnte nicht erstellt werden",
+ "updateError": "Webhook konnte nicht aktualisiert werden",
+ "deletedToast": "Webhook gelöscht",
+ "deleteError": "Webhook konnte nicht gelöscht werden"
},
"sectionLabel": "Einstellungsbereich",
"navAriaLabel": "Einstellungsnavigation",
@@ -2180,7 +2214,72 @@
"categoryHeroUpdated": "Kategorie-Titelbild aktualisiert",
"publicSiteResetToDefault": "Öffentliche Seite auf Standard zurückgesetzt",
"cmsPageLogoUploaded": "CMS-Seiten-Logo hochgeladen: {{slug}}",
- "adminPasswordResetByAdmin": "Admin-Passwort durch anderen Admin zurückgesetzt: {{username}}"
+ "adminPasswordResetByAdmin": "Admin-Passwort durch anderen Admin zurückgesetzt: {{username}}",
+ "contractCreated": "Vertrag erstellt: {{contractNumber}}",
+ "contractCreatedFromQuote": "Vertrag aus Angebot erstellt: {{contractNumber}}",
+ "contractUpdated": "Vertrag aktualisiert: {{contractNumber}}",
+ "contractSent": "Vertrag gesendet: {{contractNumber}}",
+ "contractResentSigned": "Unterschriebener Vertrag erneut gesendet: {{contractNumber}}",
+ "contractSignedByCustomer": "Vertrag vom Kunden unterschrieben: {{contractNumber}}",
+ "contractSignedPdfUploaded": "Unterschriebenes Vertrag-PDF hochgeladen: {{contractNumber}}",
+ "contractSignaturesRestamped": "Vertragsunterschriften neu gestempelt: {{contractNumber}}",
+ "contractCancelled": "Vertrag storniert: {{contractNumber}}",
+ "contractConvertedToEvent": "Vertrag in Event umgewandelt: {{contractNumber}}",
+ "contractConvertedToEmptyEvent": "Vertrag in leeres Event umgewandelt: {{contractNumber}}",
+ "contractConvertedToInvoices": "Vertrag in Rechnungen umgewandelt: {{contractNumber}}",
+ "contractConvertedToEmptyInvoice": "Vertrag in leere Rechnung umgewandelt: {{contractNumber}}",
+ "quoteCreated": "Angebot erstellt: {{quoteNumber}}",
+ "quoteSent": "Angebot gesendet: {{quoteNumber}}",
+ "quoteUpdated": "Angebot aktualisiert: {{quoteNumber}}",
+ "quoteAcceptedByAdmin": "Angebot angenommen: {{quoteNumber}}",
+ "quoteDeclinedByAdmin": "Angebot abgelehnt: {{quoteNumber}}",
+ "quoteConverted": "Angebot umgewandelt: {{quoteNumber}}",
+ "quoteConvertedInvoicesOnly": "Angebot in Rechnungen umgewandelt: {{quoteNumber}}",
+ "invoiceCreated": "Rechnung erstellt: {{invoiceNumber}}",
+ "invoiceSent": "Rechnung gesendet: {{invoiceNumber}}",
+ "invoiceScheduled": "Rechnung geplant: {{invoiceNumber}}",
+ "invoiceCancelled": "Rechnung storniert: {{invoiceNumber}}",
+ "invoiceCancelledViaStorno": "Rechnung per Storno storniert: {{invoiceNumber}}",
+ "invoiceReissued": "Rechnung neu ausgestellt: {{invoiceNumber}}",
+ "invoicePaidAdminNotified": "Rechnung als bezahlt markiert: {{invoiceNumber}}",
+ "invoicePaymentCheckRecorded": "Zahlungsprüfung erfasst für Rechnung: {{invoiceNumber}}",
+ "invoicePaymentCheckSent": "Zahlungsprüfung gesendet für Rechnung: {{invoiceNumber}}",
+ "invoiceReleasedForDelivery": "Rechnung zur Lieferung freigegeben: {{invoiceNumber}}",
+ "invoiceReminderSent": "Rechnungserinnerung gesendet: {{invoiceNumber}}",
+ "stornoSent": "Storno gesendet: {{invoiceNumber}}",
+ "monthlyBillIssued": "Monatsrechnung für Kunden ausgestellt",
+ "monthlyBillSkippedEmpty": "Monatsrechnung übersprungen (keine Einträge)",
+ "monthlyBillTriggeredManually": "Monatsrechnung manuell ausgelöst",
+ "monthlyBillingItemsQueued": "Monatsabrechnungsposten in Warteschlange",
+ "installmentPlanUpdated": "Ratenplan aktualisiert",
+ "expenseCreated": "Ausgabe erstellt",
+ "expenseUpdated": "Ausgabe aktualisiert",
+ "expensePaid": "Ausgabe als bezahlt markiert",
+ "expenseInvoiced": "Ausgabe in Rechnung gestellt",
+ "hourEntryLogged": "Stundeneintrag erfasst",
+ "hourEntryUpdated": "Stundeneintrag aktualisiert",
+ "hourEntryDeleted": "Stundeneintrag gelöscht",
+ "hourEntryLoggedToMonthlyDraft": "Stundeneintrag in Monatsentwurf erfasst",
+ "hourEntriesBilled": "Stundeneinträge an Kunden abgerechnet",
+ "incomingInvoiceCaptured": "Eingangsrechnung erfasst",
+ "incomingInvoiceCategorized": "Eingangsrechnung kategorisiert",
+ "incomingInvoiceUpdated": "Eingangsrechnung aktualisiert",
+ "incomingInvoiceRebilled": "Eingangsrechnung an Kunden weiterverrechnet",
+ "incomingInvoiceSupplierPayment": "Lieferantenzahlung erfasst",
+ "incomingMailConfigUpdated": "Eingangs-Mail-Konfiguration aktualisiert",
+ "customerCreatedPassive": "Passiver Kunde erstellt: {{email}}",
+ "adminUserActivated": "Admin-Konto aktiviert: {{username}}",
+ "adminUserDeleted": "Admin-Konto gelöscht: {{username}}",
+ "adminPasswordReset": "Admin-Passwort zurückgesetzt: {{username}}",
+ "bulkArchiveCompleted": "Massen-Archivierung abgeschlossen: {{count}} Events archiviert",
+ "emailQueueFlushed": "E-Mail-Warteschlange geleert",
+ "emailResent": "Erstellungs-E-Mail erneut gesendet für {{eventName}}",
+ "emailTemplateCreated": "E-Mail-Vorlage erstellt: {{template_key}}",
+ "eventDuplicated": "Event dupliziert aus {{source_event_name}}",
+ "feedbackDeleted": "Feedback gelöscht",
+ "feedbackModerated": "Feedback moderiert",
+ "feedbackSettingsUpdated": "Feedback-Einstellungen aktualisiert für {{eventName}}",
+ "wordFilterAdded": "Wortfilter hinzugefügt: {{word}}"
},
"notificationToasts": {
"markedAllRead": "Alle Benachrichtigungen als gelesen markiert",
@@ -2375,7 +2474,65 @@
"photo_uploaded": "Foto hochgeladen in {{eventName}}",
"category_hero_updated": "Kategorie-Titelbild aktualisiert",
"public_site_reset_to_default": "Öffentliche Seite auf Standard zurückgesetzt",
- "cms_page_logo_uploaded": "CMS-Seiten-Logo hochgeladen: {{slug}}"
+ "cms_page_logo_uploaded": "CMS-Seiten-Logo hochgeladen: {{slug}}",
+ "contract_created": "Vertrag erstellt: {{contractNumber}}",
+ "contract_created_from_quote": "Vertrag aus Angebot erstellt: {{contractNumber}}",
+ "contract_updated": "Vertrag aktualisiert: {{contractNumber}}",
+ "contract_sent": "Vertrag gesendet: {{contractNumber}}",
+ "contract_resent_signed": "Unterschriebener Vertrag erneut gesendet: {{contractNumber}}",
+ "contract_signed_by_customer": "Vertrag vom Kunden unterschrieben: {{contractNumber}}",
+ "contract_signed_pdf_uploaded": "Unterschriebenes Vertrag-PDF hochgeladen: {{contractNumber}}",
+ "contract_signatures_restamped": "Vertragsunterschriften neu gestempelt: {{contractNumber}}",
+ "contract_cancelled": "Vertrag storniert: {{contractNumber}}",
+ "contract_converted_to_event": "Vertrag in Event umgewandelt: {{contractNumber}}",
+ "contract_converted_to_empty_event": "Vertrag in leeres Event umgewandelt: {{contractNumber}}",
+ "contract_converted_to_invoices": "Vertrag in Rechnungen umgewandelt: {{contractNumber}}",
+ "contract_converted_to_empty_invoice": "Vertrag in leere Rechnung umgewandelt: {{contractNumber}}",
+ "quote_created": "Angebot erstellt: {{quoteNumber}}",
+ "quote_sent": "Angebot gesendet: {{quoteNumber}}",
+ "quote_updated": "Angebot aktualisiert: {{quoteNumber}}",
+ "quote_accepted_by_admin": "Angebot angenommen: {{quoteNumber}}",
+ "quote_declined_by_admin": "Angebot abgelehnt: {{quoteNumber}}",
+ "quote_converted": "Angebot umgewandelt: {{quoteNumber}}",
+ "quote_converted_invoices_only": "Angebot in Rechnungen umgewandelt: {{quoteNumber}}",
+ "invoice_created": "Rechnung erstellt: {{invoiceNumber}}",
+ "invoice_sent": "Rechnung gesendet: {{invoiceNumber}}",
+ "invoice_scheduled": "Rechnung geplant: {{invoiceNumber}}",
+ "invoice_cancelled": "Rechnung storniert: {{invoiceNumber}}",
+ "invoice_cancelled_via_storno": "Rechnung per Storno storniert: {{invoiceNumber}}",
+ "invoice_reissued": "Rechnung neu ausgestellt: {{invoiceNumber}}",
+ "invoice_paid_admin_notified": "Rechnung als bezahlt markiert: {{invoiceNumber}}",
+ "invoice_payment_check_recorded": "Zahlungsprüfung erfasst für Rechnung: {{invoiceNumber}}",
+ "invoice_payment_check_sent": "Zahlungsprüfung gesendet für Rechnung: {{invoiceNumber}}",
+ "invoice_released_for_delivery": "Rechnung zur Lieferung freigegeben: {{invoiceNumber}}",
+ "invoice_reminder_sent": "Rechnungserinnerung gesendet: {{invoiceNumber}}",
+ "storno_sent": "Storno gesendet: {{invoiceNumber}}",
+ "monthly_bill_issued": "Monatsrechnung für Kunden ausgestellt",
+ "monthly_bill_skipped_empty": "Monatsrechnung übersprungen (keine Einträge)",
+ "monthly_bill_triggered_manually": "Monatsrechnung manuell ausgelöst",
+ "monthly_billing_items_queued": "Monatsabrechnungsposten in Warteschlange",
+ "installment_plan_updated": "Ratenplan aktualisiert",
+ "expense_created": "Ausgabe erstellt",
+ "expense_updated": "Ausgabe aktualisiert",
+ "expense_paid": "Ausgabe als bezahlt markiert",
+ "expense_invoiced": "Ausgabe in Rechnung gestellt",
+ "hour_entry_logged": "Stundeneintrag erfasst",
+ "hour_entry_updated": "Stundeneintrag aktualisiert",
+ "hour_entry_deleted": "Stundeneintrag gelöscht",
+ "hour_entry_logged_to_monthly_draft": "Stundeneintrag in Monatsentwurf erfasst",
+ "hour_entries_billed": "Stundeneinträge an Kunden abgerechnet",
+ "incoming_invoice_captured": "Eingangsrechnung erfasst",
+ "incoming_invoice_categorized": "Eingangsrechnung kategorisiert",
+ "incoming_invoice_updated": "Eingangsrechnung aktualisiert",
+ "incoming_invoice_rebilled": "Eingangsrechnung an Kunden weiterverrechnet",
+ "incoming_invoice_supplier_payment": "Lieferantenzahlung erfasst",
+ "incoming_mail_config_updated": "Eingangs-Mail-Konfiguration aktualisiert",
+ "customer_created_passive": "Passiver Kunde erstellt: {{email}}",
+ "admin_user_activated": "Admin-Konto aktiviert: {{username}}",
+ "admin_user_deleted": "Admin-Konto gelöscht: {{username}}",
+ "email_queue_flushed": "E-Mail-Warteschlange geleert",
+ "email_template_created": "E-Mail-Vorlage erstellt: {{template_key}}",
+ "event_duplicated": "Event dupliziert aus {{source_event_name}}"
}
},
"acceptInvitation": {
@@ -3956,10 +4113,15 @@
"settingsSaved": "Zuordnungen gespeichert.",
"saveDefaults": "Zuordnungen speichern",
"accountType": {
- "asset": "Aktiven", "liability": "Passiven", "equity": "Eigenkapital", "revenue": "Ertrag", "expense": "Aufwand"
+ "asset": "Aktiven",
+ "liability": "Passiven",
+ "equity": "Eigenkapital",
+ "revenue": "Ertrag",
+ "expense": "Aufwand"
},
"vatDirection": {
- "input": "Vorsteuer", "output": "Umsatzsteuer"
+ "input": "Vorsteuer",
+ "output": "Umsatzsteuer"
},
"defaults": {
"title": "Standard- & Systemkonten",
@@ -3974,20 +4136,40 @@
"ledger_account_per_diem": "Spesenpauschalen-Konto",
"ledger_account_rebilled_revenue": "Weiterverrechnete Spesen (Ertrag)"
},
- "vatMap": { "title": "MWST-Code nach steuerlicher Behandlung (Kosten)" },
- "outputVatMap": { "title": "MWST-Code nach Umsatzsatz", "empty": "Fügen Sie oben Ausgangs-MWST-Codes hinzu, um pro Umsatzsatz einen Code festzulegen." },
- "categoryMap": { "title": "Aufwandkategorie → Konto" },
- "accounts": { "title": "Kontenplan" },
- "vatCodes": { "title": "MWST-Codes" },
+ "vatMap": {
+ "title": "MWST-Code nach steuerlicher Behandlung (Kosten)"
+ },
+ "outputVatMap": {
+ "title": "MWST-Code nach Umsatzsatz",
+ "empty": "Fügen Sie oben Ausgangs-MWST-Codes hinzu, um pro Umsatzsatz einen Code festzulegen."
+ },
+ "categoryMap": {
+ "title": "Aufwandkategorie → Konto"
+ },
+ "accounts": {
+ "title": "Kontenplan"
+ },
+ "vatCodes": {
+ "title": "MWST-Codes"
+ },
"account": {
- "addTitle": "Konto hinzufügen", "editTitle": "Konto bearbeiten",
- "number": "Kontonummer", "name": "Name", "type": "Art",
+ "addTitle": "Konto hinzufügen",
+ "editTitle": "Konto bearbeiten",
+ "number": "Kontonummer",
+ "name": "Name",
+ "type": "Art",
"confirmDelete": "Dieses Konto löschen?"
},
"vat": {
- "addTitle": "MWST-Code hinzufügen", "editTitle": "MWST-Code bearbeiten",
- "code": "Code", "name": "Name", "rate": "Satz %", "direction": "Richtung",
- "account": "MWST-Konto", "noAccount": "— keines —", "confirmDelete": "Diesen MWST-Code löschen?",
+ "addTitle": "MWST-Code hinzufügen",
+ "editTitle": "MWST-Code bearbeiten",
+ "code": "Code",
+ "name": "Name",
+ "rate": "Satz %",
+ "direction": "Richtung",
+ "account": "MWST-Konto",
+ "noAccount": "— keines —",
+ "confirmDelete": "Diesen MWST-Code löschen?",
"legacyRate": "{{rate}}% (nicht konfiguriert)"
},
"export": {
diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index bc48cadc..2de7a660 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -933,7 +933,9 @@
"showGalleryFilterBar": "Show filter bar in galleries",
"showGalleryFilterBarHelp": "Display the search-by-filename and sort controls above grid-layout galleries. Disable for a cleaner layout.",
"enablePhoneField": "Enable phone number field",
- "enablePhoneFieldHelp": "Adds an optional phone number input to the event form. Useful for downstream automations like WhatsApp delivery via n8n. Always optional even when enabled."
+ "enablePhoneFieldHelp": "Adds an optional phone number input to the event form. Useful for downstream automations like WhatsApp delivery via n8n. Always optional even when enabled.",
+ "defaultFeedbackEnabled": "Enable Guest Feedback by default",
+ "defaultFeedbackEnabledHelp": "Pre-check \"Guest Feedback\" when creating new events. Individual feedback options (likes, ratings, comments) can still be customised per event."
},
"imageSecurity": {
"title": "Image Protection",
@@ -1118,9 +1120,10 @@
"statusRevoked": "Revoked",
"statusExpired": "Expired",
"statusActive": "Active",
- "confirmRevoke": "Do you want to revoke this token? This action cannot be undone.",
+ "confirmRevoke": "Revoke \"{{name}}\"? Existing integrations using this token will start getting 401.",
"revoke": "Revoke",
- "empty": "No tokens yet. Generate one above to get started."
+ "empty": "No tokens yet. Generate one above to get started.",
+ "preview": "Preview"
},
"webhooks": {
"title": "Webhooks",
@@ -1134,7 +1137,38 @@
"template": "Template (optional)",
"create": "Create Webhook",
"existing": "Existing webhooks",
- "empty": "No webhooks yet. Create one above to start receiving event notifications."
+ "empty": "No webhooks yet. Create one above to start receiving event notifications.",
+ "namePlaceholder": "e.g. n8n WhatsApp",
+ "urlPlaceholder": "https://n8n.example.com/webhook/picpeak",
+ "showAdvanced": "+ Advanced (filter, template)",
+ "hideAdvanced": "− Hide advanced (filter, template)",
+ "filterHelp": "Dot-path → expected value. All keys must match (AND). Use an array for \"any of\".",
+ "filterError": "Filter must be valid JSON",
+ "templatePlaceholder": "New gallery: ${data.event.event_name} → ${data.event.share_url}",
+ "templateHelp": "Replaces the default JSON envelope as the request body. ${dot.path} substitution from the payload only — no logic, no expressions.",
+ "copyButton": "Copy",
+ "dismiss": "Dismiss",
+ "copied": "Copied",
+ "copyFailed": "Copy failed",
+ "colName": "Name",
+ "colUrl": "URL",
+ "colEvents": "Events",
+ "colLastDelivery": "Last delivery",
+ "colStatus": "Status",
+ "colActions": "Actions",
+ "eventsSubscribed_one": "{{count}} subscribed",
+ "eventsSubscribed_other": "{{count}} subscribed",
+ "statusActive": "Active",
+ "statusDisabled": "Disabled",
+ "toggleToDisable": "Click to disable",
+ "toggleToEnable": "Click to enable",
+ "deliveriesLink": "Deliveries",
+ "delete": "Delete",
+ "confirmDelete": "Delete \"{{name}}\"? Pending deliveries are also removed.",
+ "createError": "Failed to create webhook",
+ "updateError": "Failed to update webhook",
+ "deletedToast": "Webhook deleted",
+ "deleteError": "Failed to delete webhook"
},
"sectionLabel": "Settings section",
"navAriaLabel": "Settings navigation",
@@ -1767,7 +1801,72 @@
"categoryHeroUpdated": "Category hero photo updated",
"publicSiteResetToDefault": "Public site reset to default",
"cmsPageLogoUploaded": "CMS page logo uploaded: {{slug}}",
- "adminPasswordResetByAdmin": "Admin password reset by another admin: {{username}}"
+ "adminPasswordResetByAdmin": "Admin password reset by another admin: {{username}}",
+ "contractCreated": "Contract created: {{contractNumber}}",
+ "contractCreatedFromQuote": "Contract created from quote: {{contractNumber}}",
+ "contractUpdated": "Contract updated: {{contractNumber}}",
+ "contractSent": "Contract sent: {{contractNumber}}",
+ "contractResentSigned": "Signed contract resent: {{contractNumber}}",
+ "contractSignedByCustomer": "Contract signed by customer: {{contractNumber}}",
+ "contractSignedPdfUploaded": "Signed contract PDF uploaded: {{contractNumber}}",
+ "contractSignaturesRestamped": "Contract signatures re-stamped: {{contractNumber}}",
+ "contractCancelled": "Contract cancelled: {{contractNumber}}",
+ "contractConvertedToEvent": "Contract converted to event: {{contractNumber}}",
+ "contractConvertedToEmptyEvent": "Contract converted to empty event: {{contractNumber}}",
+ "contractConvertedToInvoices": "Contract converted to invoices: {{contractNumber}}",
+ "contractConvertedToEmptyInvoice": "Contract converted to empty invoice: {{contractNumber}}",
+ "quoteCreated": "Quote created: {{quoteNumber}}",
+ "quoteSent": "Quote sent: {{quoteNumber}}",
+ "quoteUpdated": "Quote updated: {{quoteNumber}}",
+ "quoteAcceptedByAdmin": "Quote accepted: {{quoteNumber}}",
+ "quoteDeclinedByAdmin": "Quote declined: {{quoteNumber}}",
+ "quoteConverted": "Quote converted: {{quoteNumber}}",
+ "quoteConvertedInvoicesOnly": "Quote converted to invoices: {{quoteNumber}}",
+ "invoiceCreated": "Invoice created: {{invoiceNumber}}",
+ "invoiceSent": "Invoice sent: {{invoiceNumber}}",
+ "invoiceScheduled": "Invoice scheduled: {{invoiceNumber}}",
+ "invoiceCancelled": "Invoice cancelled: {{invoiceNumber}}",
+ "invoiceCancelledViaStorno": "Invoice cancelled via Storno: {{invoiceNumber}}",
+ "invoiceReissued": "Invoice reissued: {{invoiceNumber}}",
+ "invoicePaidAdminNotified": "Invoice marked paid: {{invoiceNumber}}",
+ "invoicePaymentCheckRecorded": "Payment-check recorded for invoice: {{invoiceNumber}}",
+ "invoicePaymentCheckSent": "Payment-check sent for invoice: {{invoiceNumber}}",
+ "invoiceReleasedForDelivery": "Invoice released for delivery: {{invoiceNumber}}",
+ "invoiceReminderSent": "Invoice reminder sent: {{invoiceNumber}}",
+ "stornoSent": "Storno sent: {{invoiceNumber}}",
+ "monthlyBillIssued": "Monthly bill issued for customer",
+ "monthlyBillSkippedEmpty": "Monthly bill skipped (no entries)",
+ "monthlyBillTriggeredManually": "Monthly bill triggered manually",
+ "monthlyBillingItemsQueued": "Monthly billing items queued",
+ "installmentPlanUpdated": "Installment plan updated",
+ "expenseCreated": "Expense created",
+ "expenseUpdated": "Expense updated",
+ "expensePaid": "Expense marked paid",
+ "expenseInvoiced": "Expense invoiced",
+ "hourEntryLogged": "Hour entry logged",
+ "hourEntryUpdated": "Hour entry updated",
+ "hourEntryDeleted": "Hour entry deleted",
+ "hourEntryLoggedToMonthlyDraft": "Hour entry logged to monthly draft",
+ "hourEntriesBilled": "Hour entries billed to customer",
+ "incomingInvoiceCaptured": "Incoming invoice captured",
+ "incomingInvoiceCategorized": "Incoming invoice categorised",
+ "incomingInvoiceUpdated": "Incoming invoice updated",
+ "incomingInvoiceRebilled": "Incoming invoice re-billed to customer",
+ "incomingInvoiceSupplierPayment": "Supplier payment recorded",
+ "incomingMailConfigUpdated": "Incoming mail configuration updated",
+ "customerCreatedPassive": "Passive customer created: {{email}}",
+ "adminUserActivated": "Admin user activated: {{username}}",
+ "adminUserDeleted": "Admin user deleted: {{username}}",
+ "adminPasswordReset": "Admin password reset: {{username}}",
+ "bulkArchiveCompleted": "Bulk archive completed: {{count}} events archived",
+ "emailQueueFlushed": "Email queue flushed",
+ "emailResent": "Creation email resent for {{eventName}}",
+ "emailTemplateCreated": "Email template created: {{template_key}}",
+ "eventDuplicated": "Event duplicated from {{source_event_name}}",
+ "feedbackDeleted": "Feedback deleted",
+ "feedbackModerated": "Feedback moderated",
+ "feedbackSettingsUpdated": "Feedback settings updated for {{eventName}}",
+ "wordFilterAdded": "Word filter added: {{word}}"
},
"notificationToasts": {
"markedAllRead": "All notifications marked as read",
@@ -1964,7 +2063,65 @@
"photo_uploaded": "Photo uploaded to {{eventName}}",
"category_hero_updated": "Category hero photo updated",
"public_site_reset_to_default": "Public site reset to default",
- "cms_page_logo_uploaded": "CMS page logo uploaded: {{slug}}"
+ "cms_page_logo_uploaded": "CMS page logo uploaded: {{slug}}",
+ "contract_created": "Contract created: {{contractNumber}}",
+ "contract_created_from_quote": "Contract created from quote: {{contractNumber}}",
+ "contract_updated": "Contract updated: {{contractNumber}}",
+ "contract_sent": "Contract sent: {{contractNumber}}",
+ "contract_resent_signed": "Signed contract resent: {{contractNumber}}",
+ "contract_signed_by_customer": "Contract signed by customer: {{contractNumber}}",
+ "contract_signed_pdf_uploaded": "Signed contract PDF uploaded: {{contractNumber}}",
+ "contract_signatures_restamped": "Contract signatures re-stamped: {{contractNumber}}",
+ "contract_cancelled": "Contract cancelled: {{contractNumber}}",
+ "contract_converted_to_event": "Contract converted to event: {{contractNumber}}",
+ "contract_converted_to_empty_event": "Contract converted to empty event: {{contractNumber}}",
+ "contract_converted_to_invoices": "Contract converted to invoices: {{contractNumber}}",
+ "contract_converted_to_empty_invoice": "Contract converted to empty invoice: {{contractNumber}}",
+ "quote_created": "Quote created: {{quoteNumber}}",
+ "quote_sent": "Quote sent: {{quoteNumber}}",
+ "quote_updated": "Quote updated: {{quoteNumber}}",
+ "quote_accepted_by_admin": "Quote accepted: {{quoteNumber}}",
+ "quote_declined_by_admin": "Quote declined: {{quoteNumber}}",
+ "quote_converted": "Quote converted: {{quoteNumber}}",
+ "quote_converted_invoices_only": "Quote converted to invoices: {{quoteNumber}}",
+ "invoice_created": "Invoice created: {{invoiceNumber}}",
+ "invoice_sent": "Invoice sent: {{invoiceNumber}}",
+ "invoice_scheduled": "Invoice scheduled: {{invoiceNumber}}",
+ "invoice_cancelled": "Invoice cancelled: {{invoiceNumber}}",
+ "invoice_cancelled_via_storno": "Invoice cancelled via Storno: {{invoiceNumber}}",
+ "invoice_reissued": "Invoice reissued: {{invoiceNumber}}",
+ "invoice_paid_admin_notified": "Invoice marked paid: {{invoiceNumber}}",
+ "invoice_payment_check_recorded": "Payment-check recorded for invoice: {{invoiceNumber}}",
+ "invoice_payment_check_sent": "Payment-check sent for invoice: {{invoiceNumber}}",
+ "invoice_released_for_delivery": "Invoice released for delivery: {{invoiceNumber}}",
+ "invoice_reminder_sent": "Invoice reminder sent: {{invoiceNumber}}",
+ "storno_sent": "Storno sent: {{invoiceNumber}}",
+ "monthly_bill_issued": "Monthly bill issued for customer",
+ "monthly_bill_skipped_empty": "Monthly bill skipped (no entries)",
+ "monthly_bill_triggered_manually": "Monthly bill triggered manually",
+ "monthly_billing_items_queued": "Monthly billing items queued",
+ "installment_plan_updated": "Installment plan updated",
+ "expense_created": "Expense created",
+ "expense_updated": "Expense updated",
+ "expense_paid": "Expense marked paid",
+ "expense_invoiced": "Expense invoiced",
+ "hour_entry_logged": "Hour entry logged",
+ "hour_entry_updated": "Hour entry updated",
+ "hour_entry_deleted": "Hour entry deleted",
+ "hour_entry_logged_to_monthly_draft": "Hour entry logged to monthly draft",
+ "hour_entries_billed": "Hour entries billed to customer",
+ "incoming_invoice_captured": "Incoming invoice captured",
+ "incoming_invoice_categorized": "Incoming invoice categorised",
+ "incoming_invoice_updated": "Incoming invoice updated",
+ "incoming_invoice_rebilled": "Incoming invoice re-billed to customer",
+ "incoming_invoice_supplier_payment": "Supplier payment recorded",
+ "incoming_mail_config_updated": "Incoming mail configuration updated",
+ "customer_created_passive": "Passive customer created: {{email}}",
+ "admin_user_activated": "Admin user activated: {{username}}",
+ "admin_user_deleted": "Admin user deleted: {{username}}",
+ "email_queue_flushed": "Email queue flushed",
+ "email_template_created": "Email template created: {{template_key}}",
+ "event_duplicated": "Event duplicated from {{source_event_name}}"
}
},
"acceptInvitation": {
@@ -3956,10 +4113,15 @@
"settingsSaved": "Mappings saved.",
"saveDefaults": "Save mappings",
"accountType": {
- "asset": "Asset", "liability": "Liability", "equity": "Equity", "revenue": "Revenue", "expense": "Expense"
+ "asset": "Asset",
+ "liability": "Liability",
+ "equity": "Equity",
+ "revenue": "Revenue",
+ "expense": "Expense"
},
"vatDirection": {
- "input": "Input (Vorsteuer)", "output": "Output (Umsatzsteuer)"
+ "input": "Input (Vorsteuer)",
+ "output": "Output (Umsatzsteuer)"
},
"defaults": {
"title": "Default & system accounts",
@@ -3974,20 +4136,40 @@
"ledger_account_per_diem": "Per-diem account",
"ledger_account_rebilled_revenue": "Re-billed expenses (revenue)"
},
- "vatMap": { "title": "VAT code by tax treatment (costs)" },
- "outputVatMap": { "title": "VAT code by revenue rate", "empty": "Add output VAT codes above to configure a code per revenue rate." },
- "categoryMap": { "title": "Expense category → account" },
- "accounts": { "title": "Chart of accounts" },
- "vatCodes": { "title": "VAT codes" },
+ "vatMap": {
+ "title": "VAT code by tax treatment (costs)"
+ },
+ "outputVatMap": {
+ "title": "VAT code by revenue rate",
+ "empty": "Add output VAT codes above to configure a code per revenue rate."
+ },
+ "categoryMap": {
+ "title": "Expense category → account"
+ },
+ "accounts": {
+ "title": "Chart of accounts"
+ },
+ "vatCodes": {
+ "title": "VAT codes"
+ },
"account": {
- "addTitle": "Add account", "editTitle": "Edit account",
- "number": "Account number", "name": "Name", "type": "Type",
+ "addTitle": "Add account",
+ "editTitle": "Edit account",
+ "number": "Account number",
+ "name": "Name",
+ "type": "Type",
"confirmDelete": "Delete this account?"
},
"vat": {
- "addTitle": "Add VAT code", "editTitle": "Edit VAT code",
- "code": "Code", "name": "Name", "rate": "Rate %", "direction": "Direction",
- "account": "VAT account", "noAccount": "— none —", "confirmDelete": "Delete this VAT code?",
+ "addTitle": "Add VAT code",
+ "editTitle": "Edit VAT code",
+ "code": "Code",
+ "name": "Name",
+ "rate": "Rate %",
+ "direction": "Direction",
+ "account": "VAT account",
+ "noAccount": "— none —",
+ "confirmDelete": "Delete this VAT code?",
"legacyRate": "{{rate}}% (not configured)"
},
"export": {
diff --git a/frontend/src/services/admin.service.ts b/frontend/src/services/admin.service.ts
index 09c137d6..f8c40575 100644
--- a/frontend/src/services/admin.service.ts
+++ b/frontend/src/services/admin.service.ts
@@ -445,6 +445,78 @@ export const adminService = {
'event_type_updated': `Event type updated: ${md.name || ''}`,
'event_type_deleted': `Event type deleted: ${md.name || ''}`,
'event_types_reordered': 'Event types reordered',
+ // CRM — Contracts.
+ 'contract_created': `Contract created: ${md.contractNumber || ''}`,
+ 'contract_created_from_quote': `Contract created from quote: ${md.contractNumber || ''}`,
+ 'contract_updated': `Contract updated: ${md.contractNumber || ''}`,
+ 'contract_sent': `Contract sent: ${md.contractNumber || ''}`,
+ 'contract_resent_signed': `Signed contract resent: ${md.contractNumber || ''}`,
+ 'contract_signed_by_customer': `Contract signed by customer: ${md.contractNumber || ''}`,
+ 'contract_signed_pdf_uploaded': `Signed contract PDF uploaded: ${md.contractNumber || ''}`,
+ 'contract_signatures_restamped': `Contract signatures re-stamped: ${md.contractNumber || ''}`,
+ 'contract_cancelled': `Contract cancelled: ${md.contractNumber || ''}`,
+ 'contract_converted_to_event': `Contract converted to event: ${md.contractNumber || ''}`,
+ 'contract_converted_to_empty_event': `Contract converted to empty event: ${md.contractNumber || ''}`,
+ 'contract_converted_to_invoices': `Contract converted to invoices: ${md.contractNumber || ''}`,
+ 'contract_converted_to_empty_invoice': `Contract converted to empty invoice: ${md.contractNumber || ''}`,
+ // CRM — Quotes.
+ 'quote_created': `Quote created: ${md.quoteNumber || ''}`,
+ 'quote_sent': `Quote sent: ${md.quoteNumber || ''}`,
+ 'quote_updated': `Quote updated: ${md.quoteNumber || ''}`,
+ 'quote_accepted_by_admin': `Quote accepted: ${md.quoteNumber || ''}`,
+ 'quote_declined_by_admin': `Quote declined: ${md.quoteNumber || ''}`,
+ 'quote_converted': `Quote converted: ${md.quoteNumber || ''}`,
+ 'quote_converted_invoices_only': `Quote converted to invoices: ${md.quoteNumber || ''}`,
+ // CRM — Invoices / Storno.
+ 'invoice_created': `Invoice created: ${md.invoiceNumber || ''}`,
+ 'invoice_sent': `Invoice sent: ${md.invoiceNumber || ''}`,
+ 'invoice_scheduled': `Invoice scheduled: ${md.invoiceNumber || ''}`,
+ 'invoice_cancelled': `Invoice cancelled: ${md.invoiceNumber || ''}`,
+ 'invoice_cancelled_via_storno': `Invoice cancelled via Storno: ${md.invoiceNumber || ''}`,
+ 'invoice_reissued': `Invoice reissued: ${md.invoiceNumber || ''}`,
+ 'invoice_paid_admin_notified': `Invoice marked paid: ${md.invoiceNumber || ''}`,
+ 'invoice_payment_check_recorded': `Payment-check recorded for invoice: ${md.invoiceNumber || ''}`,
+ 'invoice_payment_check_sent': `Payment-check sent for invoice: ${md.invoiceNumber || ''}`,
+ 'invoice_released_for_delivery': `Invoice released for delivery: ${md.invoiceNumber || ''}`,
+ 'invoice_reminder_sent': `Invoice reminder sent: ${md.invoiceNumber || ''}`,
+ 'storno_sent': `Storno sent: ${md.invoiceNumber || ''}`,
+ // CRM — Monthly billing.
+ 'monthly_bill_issued': 'Monthly bill issued for customer',
+ 'monthly_bill_skipped_empty': 'Monthly bill skipped (no entries)',
+ 'monthly_bill_triggered_manually': 'Monthly bill triggered manually',
+ 'monthly_billing_items_queued': 'Monthly billing items queued',
+ 'installment_plan_updated': 'Installment plan updated',
+ // Accounting — Expenses + Hours + Incoming invoices.
+ 'expense_created': 'Expense created',
+ 'expense_updated': 'Expense updated',
+ 'expense_paid': 'Expense marked paid',
+ 'expense_invoiced': 'Expense invoiced',
+ 'hour_entry_logged': 'Hour entry logged',
+ 'hour_entry_updated': 'Hour entry updated',
+ 'hour_entry_deleted': 'Hour entry deleted',
+ 'hour_entry_logged_to_monthly_draft': 'Hour entry logged to monthly draft',
+ 'hour_entries_billed': 'Hour entries billed to customer',
+ 'incoming_invoice_captured': 'Incoming invoice captured',
+ 'incoming_invoice_categorized': 'Incoming invoice categorised',
+ 'incoming_invoice_updated': 'Incoming invoice updated',
+ 'incoming_invoice_rebilled': 'Incoming invoice re-billed to customer',
+ 'incoming_invoice_supplier_payment': 'Supplier payment recorded',
+ 'incoming_mail_config_updated': 'Incoming mail configuration updated',
+ // Customers + Admin user mgmt.
+ 'customer_created_passive': `Passive customer created: ${md.email || ''}`,
+ 'admin_user_activated': `Admin user activated: ${md.username || ''}`,
+ 'admin_user_deleted': `Admin user deleted: ${md.username || ''}`,
+ 'admin_password_reset': `Admin password reset: ${md.username || ''}`,
+ // Misc / legacy.
+ 'bulk_archive_completed': `Bulk archive completed: ${md.count || 0} events archived`,
+ 'email_queue_flushed': 'Email queue flushed',
+ 'email_resent': `Creation email resent for ${activity.eventName || ''}`,
+ 'email_template_created': `Email template created: ${md.template_key || ''}`,
+ 'event_duplicated': `Event duplicated from ${md.source_event_name || ''}`,
+ 'feedback_deleted': 'Feedback deleted',
+ 'feedback_moderated': 'Feedback moderated',
+ 'feedback_settings_updated': `Feedback settings updated for ${activity.eventName || ''}`,
+ 'word_filter_added': `Word filter added: ${md.word || ''}`,
};
return messages[activity.type] || activity.type;
diff --git a/frontend/src/services/notifications.service.ts b/frontend/src/services/notifications.service.ts
index 1c53cc7e..66129a16 100644
--- a/frontend/src/services/notifications.service.ts
+++ b/frontend/src/services/notifications.service.ts
@@ -288,12 +288,27 @@ export const notificationsService = {
slug: notification.metadata.slug,
});
- default:
- // Log unknown notification types for debugging
+ default: {
+ // Smart fallback: try to resolve admin.notificationMessages.
+ // directly before giving up to the systemActivity template. Most CRM
+ // and accounting activity types follow a stable shape (a doc number /
+ // customer email / count interpolated into a short sentence), so we
+ // add their translation entries to the locale files and rely on this
+ // default to pick them up — no per-type switch case needed.
+ const camelCase = notification.type.replace(/_(\w)/g, (_match, c) => c.toUpperCase());
+ const key = `admin.notificationMessages.${camelCase}`;
+ const translated = t(key, {
+ eventName: notification.eventName,
+ actorName: notification.actorName,
+ ...notification.metadata,
+ });
+ if (translated && translated !== key) return translated as string;
+ // Truly unknown — log and render the legacy systemActivity template.
console.warn('Unknown notification type:', notification.type, notification);
return notification.metadata.message || t('admin.notificationMessages.systemActivity', {
type: notification.type.replace(/_/g, ' ')
});
+ }
}
},