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:
+
+ - 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.
+
+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:
+
+ - 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
`,
+ 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,