From bc8d3330bbe02041760063dcbf5cb5984945a25f Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Thu, 18 Jun 2026 21:10:12 +0200 Subject: [PATCH] fix(i18n): sweep activity-type translations + smart notification fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The admin notification bell and dashboard "Recent Activities" panel were showing raw snake_case keys ("event_published") or the generic "Systemaktivität: " 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.` 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. --- frontend/src/i18n/locales/de.json | 176 ++++++++++++++++-- frontend/src/i18n/locales/en.json | 176 ++++++++++++++++-- frontend/src/services/admin.service.ts | 72 +++++++ .../src/services/notifications.service.ts | 19 +- 4 files changed, 413 insertions(+), 30 deletions(-) diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 55121b32..73b997d7 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -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", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 033971bf..5be78430 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -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", 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, ' ') }); + } } },