diff --git a/backend/migrations/026_fix_german_email_templates.js b/backend/migrations/026_fix_german_email_templates.js new file mode 100644 index 0000000..51276a6 --- /dev/null +++ b/backend/migrations/026_fix_german_email_templates.js @@ -0,0 +1,234 @@ +exports.up = async function(knex) { + // Update gallery_created template with proper German translation + await knex('email_templates') + .where('template_key', 'gallery_created') + .update({ + subject_de: 'Ihre Fotogalerie ist bereit!', + body_html_de: `

Galerie erfolgreich erstellt

+

Liebe(r) {{host_name}},

+

Ihre Fotogalerie "{{event_name}}" wurde erfolgreich erstellt!

+{{#if welcome_message}} +
+

Persönliche Nachricht:

+

{{welcome_message}}

+
+{{/if}} +

Galerie-Details:

+ +

Teilen Sie diesen Link und das Passwort mit Ihren Gästen, damit diese die Fotos ansehen und herunterladen können.

+

+ Wichtig: Diese Galerie läuft am {{expiry_date}} ab. Nach diesem Datum werden die Fotos archiviert und sind nicht mehr zugänglich. +

+Galerie anzeigen +

Mit freundlichen Grüßen,
Ihr Foto-Sharing-Team

`, + body_text_de: `Galerie erfolgreich erstellt + +Liebe(r) {{host_name}}, + +Ihre Fotogalerie "{{event_name}}" wurde erfolgreich erstellt! + +{{#if welcome_message}} +Persönliche Nachricht: +{{welcome_message}} + +{{/if}} +Galerie-Details: +- Veranstaltungsdatum: {{event_date}} +- Galerie-Link: {{gallery_link}} +- Passwort: {{gallery_password}} +- Ablaufdatum: {{expiry_date}} + +Teilen Sie diesen Link und das Passwort mit Ihren Gästen, damit diese die Fotos ansehen und herunterladen können. + +WICHTIG: Diese Galerie läuft am {{expiry_date}} ab. Nach diesem Datum werden die Fotos archiviert und sind nicht mehr zugänglich. + +Mit freundlichen Grüßen, +Ihr Foto-Sharing-Team` + }); + + // Update expiration_warning template with proper German translation + await knex('email_templates') + .where('template_key', 'expiration_warning') + .update({ + subject_de: 'Ihre Fotogalerie läuft bald ab', + body_html_de: `

Galerie läuft bald ab

+

Liebe(r) {{host_name}},

+

Ihre Fotogalerie "{{event_name}}" läuft in {{days_remaining}} Tagen ab.

+

Nach Ablauf wird die Galerie archiviert und ist für Gäste nicht mehr zugänglich. Bitte stellen Sie sicher, dass alle gewünschten Fotos heruntergeladen wurden.

+

Ablaufdatum: {{expiry_date}}

+Galerie jetzt besuchen +

+ Erinnerung: Nach dem {{expiry_date}} können Ihre Gäste nicht mehr auf die Galerie zugreifen. +

+

Mit freundlichen Grüßen,
Ihr Foto-Sharing-Team

`, + body_text_de: `Galerie läuft bald ab + +Liebe(r) {{host_name}}, + +Ihre Fotogalerie "{{event_name}}" läuft in {{days_remaining}} Tagen ab. + +Nach Ablauf wird die Galerie archiviert und ist für Gäste nicht mehr zugänglich. Bitte stellen Sie sicher, dass alle gewünschten Fotos heruntergeladen wurden. + +Ablaufdatum: {{expiry_date}} + +Galerie-Link: {{gallery_link}} + +ERINNERUNG: Nach dem {{expiry_date}} können Ihre Gäste nicht mehr auf die Galerie zugreifen. + +Mit freundlichen Grüßen, +Ihr Foto-Sharing-Team` + }); + + // Update gallery_expired template with proper German translation + await knex('email_templates') + .where('template_key', 'gallery_expired') + .update({ + subject_de: 'Ihre Fotogalerie {{event_name}} ist abgelaufen', + body_html_de: `

Galerie abgelaufen

+

Liebe(r) {{host_name}},

+

Ihre Fotogalerie "{{event_name}}" ist am {{expiry_date}} abgelaufen und wurde archiviert.

+

Die Galerie ist nicht mehr für Gäste zugänglich. Alle Fotos wurden sicher in unserem Archivsystem gespeichert.

+

Wenn Sie wieder Zugriff auf die archivierten Fotos benötigen, wenden Sie sich bitte an unseren Support:

+

+ Kontakt:
+ E-Mail: {{admin_email}}
+ {{#if support_phone}}Telefon: {{support_phone}}{{/if}} +

+

Vielen Dank für die Nutzung unseres Foto-Sharing-Services!

+

Mit freundlichen Grüßen,
Ihr Foto-Sharing-Team

`, + body_text_de: `Galerie abgelaufen + +Liebe(r) {{host_name}}, + +Ihre Fotogalerie "{{event_name}}" ist am {{expiry_date}} abgelaufen und wurde archiviert. + +Die Galerie ist nicht mehr für Gäste zugänglich. Alle Fotos wurden sicher in unserem Archivsystem gespeichert. + +Wenn Sie wieder Zugriff auf die archivierten Fotos benötigen, wenden Sie sich bitte an unseren Support: + +E-Mail: {{admin_email}} +{{#if support_phone}}Telefon: {{support_phone}}{{/if}} + +Vielen Dank für die Nutzung unseres Foto-Sharing-Services! + +Mit freundlichen Grüßen, +Ihr Foto-Sharing-Team` + }); + + // Update archive_complete template with proper German translation + await knex('email_templates') + .where('template_key', 'archive_complete') + .update({ + subject_de: 'Archivierung abgeschlossen: {{event_name}}', + body_html_de: `

Archivierung abgeschlossen

+

Liebe(r) {{host_name}},

+

Die Fotogalerie "{{event_name}}" wurde erfolgreich archiviert.

+

Archiv-Details:

+ +

Das Archiv wird sicher in unserem System aufbewahrt. Bei Bedarf können Sie sich an unseren Support wenden, um Zugriff auf die archivierten Fotos zu erhalten.

+

+ ✓ Erfolgreich archiviert: Ihre Fotos sind sicher gespeichert und können bei Bedarf wiederhergestellt werden. +

+

Kontakt für Archivzugriff:
+E-Mail: {{admin_email}}

+

Mit freundlichen Grüßen,
Ihr Foto-Sharing-Team

`, + body_text_de: `Archivierung abgeschlossen + +Liebe(r) {{host_name}}, + +Die Fotogalerie "{{event_name}}" wurde erfolgreich archiviert. + +Archiv-Details: +- Archivgröße: {{archive_size}} +- Archivierungsdatum: {{archive_date}} +- Anzahl der Fotos: {{photo_count}} + +Das Archiv wird sicher in unserem System aufbewahrt. Bei Bedarf können Sie sich an unseren Support wenden, um Zugriff auf die archivierten Fotos zu erhalten. + +✓ ERFOLGREICH ARCHIVIERT: Ihre Fotos sind sicher gespeichert und können bei Bedarf wiederhergestellt werden. + +Kontakt für Archivzugriff: +E-Mail: {{admin_email}} + +Mit freundlichen Grüßen, +Ihr Foto-Sharing-Team` + }); + + // Also update the non-language-specific fields to match German for consistency + await knex('email_templates') + .where('template_key', 'gallery_created') + .update({ + subject: knex.raw('subject_de'), + body_html: knex.raw('body_html_de'), + body_text: knex.raw('body_text_de') + }); + + await knex('email_templates') + .where('template_key', 'expiration_warning') + .update({ + subject: knex.raw('subject_de'), + body_html: knex.raw('body_html_de'), + body_text: knex.raw('body_text_de') + }); + + await knex('email_templates') + .where('template_key', 'gallery_expired') + .update({ + subject: knex.raw('subject_de'), + body_html: knex.raw('body_html_de'), + body_text: knex.raw('body_text_de') + }); + + await knex('email_templates') + .where('template_key', 'archive_complete') + .update({ + subject: knex.raw('subject_de'), + body_html: knex.raw('body_html_de'), + body_text: knex.raw('body_text_de') + }); +}; + +exports.down = async function(knex) { + // Revert to previous German translations + // This is a simplified rollback - in production you might want to store the old values + await knex('email_templates') + .where('template_key', 'gallery_created') + .update({ + subject: knex.raw('subject_en'), + body_html: knex.raw('body_html_en'), + body_text: knex.raw('body_text_en') + }); + + await knex('email_templates') + .where('template_key', 'expiration_warning') + .update({ + subject: knex.raw('subject_en'), + body_html: knex.raw('body_html_en'), + body_text: knex.raw('body_text_en') + }); + + await knex('email_templates') + .where('template_key', 'gallery_expired') + .update({ + subject: knex.raw('subject_en'), + body_html: knex.raw('body_html_en'), + body_text: knex.raw('body_text_en') + }); + + await knex('email_templates') + .where('template_key', 'archive_complete') + .update({ + subject: knex.raw('subject_en'), + body_html: knex.raw('body_html_en'), + body_text: knex.raw('body_text_en') + }); +}; \ No newline at end of file diff --git a/backend/migrations/027_add_language_preferences.js b/backend/migrations/027_add_language_preferences.js new file mode 100644 index 0000000..7640e38 --- /dev/null +++ b/backend/migrations/027_add_language_preferences.js @@ -0,0 +1,41 @@ +exports.up = async function(knex) { + // Add language column to events table if it doesn't exist + const hasLanguageInEvents = await knex.schema.hasColumn('events', 'language'); + if (!hasLanguageInEvents) { + await knex.schema.alterTable('events', function(table) { + table.string('language', 5).defaultTo('en'); + }); + } + + // Add default_language to email_configs if it doesn't exist + const hasDefaultLanguage = await knex.schema.hasColumn('email_configs', 'default_language'); + if (!hasDefaultLanguage) { + await knex.schema.alterTable('email_configs', function(table) { + table.string('default_language', 5).defaultTo('en'); + }); + } + + // Set default language to German for the existing email config + await knex('email_configs') + .update({ + default_language: 'de' + }); +}; + +exports.down = async function(knex) { + // Remove language column from events table + const hasLanguageInEvents = await knex.schema.hasColumn('events', 'language'); + if (hasLanguageInEvents) { + await knex.schema.alterTable('events', function(table) { + table.dropColumn('language'); + }); + } + + // Remove default_language from email_configs + const hasDefaultLanguage = await knex.schema.hasColumn('email_configs', 'default_language'); + if (hasDefaultLanguage) { + await knex.schema.alterTable('email_configs', function(table) { + table.dropColumn('default_language'); + }); + } +}; \ No newline at end of file diff --git a/backend/src/services/emailProcessor.js b/backend/src/services/emailProcessor.js index 0f5b6a8..21a17d2 100644 --- a/backend/src/services/emailProcessor.js +++ b/backend/src/services/emailProcessor.js @@ -59,15 +59,37 @@ async function initializeTransporter(forceReinit = false) { } // Get the appropriate language for a recipient -async function getRecipientLanguage(email) { - // For now, check if the email domain ends with .de - // In the future, this could check user preferences - if (email && email.endsWith('.de')) { - return 'de'; +async function getRecipientLanguage(email, eventId = null) { + // First priority: Check event language setting if eventId is provided + if (eventId) { + try { + const event = await db('events').where('id', eventId).first(); + if (event && event.language) { + return event.language; + } + } catch (error) { + logger.error('Error fetching event language:', error); + } } - // Check if there's a saved preference for this email - // This could be expanded to check user preferences in the database + // Second priority: Check email configs for default language + try { + const emailConfig = await db('email_configs').first(); + if (emailConfig && emailConfig.default_language) { + return emailConfig.default_language; + } + } catch (error) { + logger.error('Error fetching email config language:', error); + } + + // Third priority: Check if the email domain suggests German + if (email) { + const germanDomains = ['.de', '.at', '.ch', '.li']; + const domain = email.toLowerCase(); + if (germanDomains.some(d => domain.endsWith(d))) { + return 'de'; + } + } return 'en'; // Default to English } @@ -300,8 +322,8 @@ async function sendTemplateEmail(to, templateKey, variables) { throw new Error('Email configuration not found'); } - // Determine recipient language - const language = await getRecipientLanguage(to); + // Determine recipient language (pass eventId if available in variables) + const language = await getRecipientLanguage(to, variables.eventId || null); // Process template with variables const { subject, htmlBody, textBody } = await processTemplate(template, variables, language); @@ -410,6 +432,8 @@ async function processEmailQueue() { // Queue an email for sending async function queueEmail(eventId, recipientEmail, emailType, emailData) { try { + // Add eventId to emailData for language detection + emailData.eventId = eventId; await db('email_queue').insert({ event_id: eventId, recipient_email: recipientEmail,