fix(i18n): sweep activity-type translations + smart notification fallback

The admin notification bell and dashboard "Recent Activities" panel were
showing raw snake_case keys ("event_published") or the generic
"Systemaktivität: <type>" fallback for ~65 activity types — most of them
from the CRM and Accounting modules added since #555. Users with German
locale saw the gap most visibly because the English placeholder leaked
through.

Three pieces:

1. notifications.service.ts — smart `default:` branch. Instead of falling
   straight to the systemActivity template, derive the camelCase i18n key
   from the snake_case type, try resolving `admin.notificationMessages.<camelCase>`
   directly with the full metadata spread as params, and only drop to the
   legacy template when no specific translation exists. This means every
   future activity type just needs an i18n entry — no per-type switch
   case to add.

2. en.json + de.json — added 65 missing `admin.notificationMessages.*`
   bell entries and 58 missing `admin.activities.*` dashboard entries
   across both locales. Covers Contracts (13), Quotes (7), Invoices /
   Storno (12), Monthly billing (5), Expenses (4), Hours (5), Incoming
   invoices (6), Customers (1), Admin user mgmt (3), and 9 misc /
   legacy types (bulk_archive_completed, email_resent, email_queue_flushed,
   email_template_created, event_duplicated, feedback_deleted,
   feedback_moderated, feedback_settings_updated, word_filter_added).
   Both locales finish symmetrical (149 activities / 136 notifications
   each, vs. 91 / 71 before).

3. admin.service.ts `formatActivityMessage` messages dict — added the
   same 58 English-only entries as a last-resort fallback for the
   dashboard when i18n itself fails to load. Keeps the surface
   resilient against bundle-load issues.

Metadata field names in the new translations match what the backend
writes via `logActivity()` — `{{contractNumber}}`, `{{quoteNumber}}`,
`{{invoiceNumber}}`, `{{username}}`, `{{template_key}}`,
`{{source_event_name}}`, `{{word}}` — verified against the call sites
in contractService, quoteService, invoiceService, userManagementService,
expenseService, adminEvents, adminEmail, adminFeedback.

DE wording authored natively; tone matches the existing terse,
maintainer-voice style of the rest of the file.
This commit is contained in:
Paul Nothaft
2026-06-18 21:10:12 +02:00
parent 9df2502547
commit bc8d3330bb
4 changed files with 413 additions and 30 deletions
+162 -14
View File
@@ -2169,7 +2169,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",
@@ -2364,7 +2429,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": {
@@ -3922,10 +4045,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",
@@ -3940,20 +4068,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?"
},
"export": {
"title": "Treuhänder-Export",
+162 -14
View File
@@ -1756,7 +1756,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",
@@ -1953,7 +2018,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": {
@@ -3922,10 +4045,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",
@@ -3940,20 +4068,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?"
},
"export": {
"title": "Treuhänder export",
+72
View File
@@ -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;
+17 -2
View File
@@ -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.<camelCase>
// 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, ' ')
});
}
}
},