emailr_
Alle Artikel
usecase·9 Min.

E-Mail-Lokalisierung: Strategien für mehrere Sprachen

Lokalisierungi18nglobal

Globale Produkte brauchen lokalisierte E-Mails. Sprache, Formatierung und kulturelle Aspekte beeinflussen die Interaktion. So baust du ein lokalisiertes E-Mail-System.

Lokalisierungskomponenten

  • Sprache/Übersetzungen
  • Formatierung von Datum und Uhrzeit
  • Formatierung von Zahlen und Währungen
  • RTL-Unterstützung (right-to-left)
  • Kulturelle Aspekte

Template-Lokalisierung

Übersetzungsstruktur

interface LocalizedTemplate {
  templateId: string;
  translations: {
    [locale: string]: {
      subject: string;
      preheader?: string;
      content: Record<string, string>;
    };
  };
  fallbackLocale: string;
}

const welcomeEmail: LocalizedTemplate = {
  templateId: 'welcome',
  fallbackLocale: 'en',
  translations: {
    en: {
      subject: 'Welcome to &#123;&#123;appName&#125;&#125;',
      preheader: 'Get started with your new account',
      content: {
        greeting: 'Hi &#123;&#123;name&#125;&#125;,',
        body: 'Thanks for signing up!',
        cta: 'Get started'
      }
    },
    es: {
      subject: 'Bienvenido a &#123;&#123;appName&#125;&#125;',
      preheader: 'Comienza con tu nueva cuenta',
      content: {
        greeting: 'Hola &#123;&#123;name&#125;&#125;,',
        body: '¡Gracias por registrarte!',
        cta: 'Comenzar'
      }
    }
  }
};

Locale-Erkennung

function getUserLocale(user: User): string {
  // Priority: explicit preference > browser > IP geolocation > default
  return (
    user.preferredLocale ||
    user.browserLocale ||
    user.geoLocale ||
    'en'
  );
}

async function sendLocalizedEmail(user: User, templateId: string, data: any) {
  const locale = getUserLocale(user);
  const template = await getLocalizedTemplate(templateId, locale);
  
  await sendEmail({
    to: user.email,
    subject: renderTemplate(template.subject, data),
    html: renderTemplate(template.html, data),
    headers: {
      'Content-Language': locale
    }
  });
}

Formatierung von Datum und Uhrzeit

Zeitzonenbewusste Formatierung

function formatDateTime(date: Date, locale: string, timezone: string): string {
  return new Intl.DateTimeFormat(locale, {
    dateStyle: 'long',
    timeStyle: 'short',
    timeZone: timezone
  }).format(date);
}

// Examples:
// en-US, America/New_York: "January 15, 2026 at 3:30 PM"
// de-DE, Europe/Berlin: "15. Januar 2026 um 21:30"
// ja-JP, Asia/Tokyo: "2026年1月16日 5:30"

Relative Zeitangaben

function formatRelativeTime(date: Date, locale: string): string {
  const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
  const diff = date.getTime() - Date.now();
  const days = Math.round(diff / (1000 * 60 * 60 * 24));
  
  if (Math.abs(days) < 1) {
    const hours = Math.round(diff / (1000 * 60 * 60));
    return rtf.format(hours, 'hour');
  }
  
  return rtf.format(days, 'day');
}

// "in 2 days" (en), "dans 2 jours" (fr), "2日後" (ja)

Währungs- und Zahlenformatierung

function formatCurrency(amount: number, currency: string, locale: string): string {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: currency
  }).format(amount);
}

// formatCurrency(1234.56, 'USD', 'en-US') → "$1,234.56"
// formatCurrency(1234.56, 'EUR', 'de-DE') → "1.234,56 €"
// formatCurrency(1234.56, 'JPY', 'ja-JP') → "¥1,235"

RTL-Unterstützung

Erkennung von RTL-Sprachen

const rtlLocales = ['ar', 'he', 'fa', 'ur'];

function isRTL(locale: string): boolean {
  return rtlLocales.includes(locale.split('-')[0]);
}

RTL-E-Mail-Vorlage

<!DOCTYPE html>
<html lang="&#123;&#123;locale&#125;&#125;" dir="&#123;&#123;#if isRTL&#125;&#125;rtl&#123;&#123;else&#125;&#125;ltr&#123;&#123;/if&#125;&#125;">
<head>
  <style>
    &#123;&#123;#if isRTL&#125;&#125;
    body {
      direction: rtl;
      text-align: right;
    }
    .button {
      /* Flip margins for RTL */
      margin-left: 0;
      margin-right: auto;
    }
    &#123;&#123;/if&#125;&#125;
  </style>
</head>
<body>
  <!-- Content automatically flows RTL -->
</body>
</html>

Kulturelle Überlegungen

Grußformeln je Kultur

const greetings = {
  en: { formal: 'Dear &#123;&#123;name&#125;&#125;', casual: 'Hi &#123;&#123;name&#125;&#125;' },
  de: { formal: 'Sehr geehrte/r &#123;&#123;name&#125;&#125;', casual: 'Hallo &#123;&#123;name&#125;&#125;' },
  ja: { formal: '&#123;&#123;name&#125;&#125;様', casual: '&#123;&#123;name&#125;&#125;さん' },
  es: { formal: 'Estimado/a &#123;&#123;name&#125;&#125;', casual: 'Hola &#123;&#123;name&#125;&#125;' }
};

function getGreeting(locale: string, formality: 'formal' | 'casual'): string {
  return greetings[locale]?.[formality] || greetings.en[formality];
}

Bevorzugte Datumsformate

const datePreferences = {
  'en-US': 'MM/DD/YYYY',
  'en-GB': 'DD/MM/YYYY',
  'de-DE': 'DD.MM.YYYY',
  'ja-JP': 'YYYY年MM月DD日'
};

Übersetzungs-Workflow

Übersetzungen verwalten

// Translation file structure
// locales/
//   en/
//     emails.json
//   es/
//     emails.json

interface TranslationFile {
  [key: string]: string | TranslationFile;
}

// emails.json
{
  "welcome": {
    "subject": "Welcome to &#123;&#123;appName&#125;&#125;",
    "greeting": "Hi &#123;&#123;name&#125;&#125;,",
    "body": "Thanks for joining us!",
    "cta": "Get started"
  }
}

Fallback-Kette

async function getTranslation(key: string, locale: string): Promise<string> {
  // Try exact locale (e.g., 'en-GB')
  let translation = await loadTranslation(key, locale);
  if (translation) return translation;
  
  // Try language only (e.g., 'en')
  const language = locale.split('-')[0];
  translation = await loadTranslation(key, language);
  if (translation) return translation;
  
  // Fall back to default
  return loadTranslation(key, 'en');
}

Lokalisierung testen

describe('Email Localization', () => {
  const locales = ['en', 'es', 'de', 'ja', 'ar'];
  
  locales.forEach(locale => {
    it(`renders welcome email in ${locale}`, async () => {
      const html = await renderEmail('welcome', locale, testData);
      
      // Check language attribute
      expect(html).toContain(`lang="${locale}"`);
      
      // Check RTL if applicable
      if (isRTL(locale)) {
        expect(html).toContain('dir="rtl"');
      }
      
      // Check no missing translations
      expect(html).not.toContain('&#123;&#123;');
    });
  });
});

Best Practices

  1. ICU Message Format verwenden - Behandelt Plural, Geschlecht usw.
  2. Benutzerpräferenz für Locale speichern - Nicht jedes Mal raten
  3. Alle Locales testen - Automatisierte Rendering-Tests
  4. Fehlende Übersetzungen handhaben - Elegante Fallbacks
  5. Textexpansion berücksichtigen - Deutsch ist ~30 % länger als Englisch
  6. Kulturelle Normen respektieren - Formalität, Datumsformate usw.

Lokalisierung ist mehr als Übersetzung. Sie sorgt dafür, dass Nutzer das Gefühl haben, dein Produkt sei für sie gemacht.

e_

Geschrieben vom emailr-Team

Wir bauen Email-Infrastruktur für Entwickler

Bereit zum Senden?

Hol dir deinen API-Schlüssel und sende deine erste E-Mail in unter 5 Minuten. Keine Kreditkarte erforderlich.