mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 13:21:08 +02:00
- Add uload project with apps/web structure
- Reorganize from flat to monorepo structure
- Remove PocketBase binary and local data
- Update to pnpm and @uload/web namespace
- Add picture project to monorepo
- Remove embedded git repository
- Unify all package names to @{project}/{app} schema:
- @maerchenzauber/* (was @storyteller/*)
- @manacore/* (was manacore-*, manacore)
- @manadeck/* (was web, backend, manadeck)
- @memoro/* (was memoro-web, landing, memoro)
- @picture/* (already unified)
- @uload/web
- Add convenient dev scripts for all apps:
- pnpm dev:{project}:web
- pnpm dev:{project}:landing
- pnpm dev:{project}:mobile
- pnpm dev:{project}:backend
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
12 KiB
12 KiB
Plan: Mehrsprachige E-Mail-Templates für uLoad
Executive Summary
Implementierung eines Systems für deutsche und englische E-Mail-Templates mit automatischer Spracherkennung basierend auf Nutzerpräferenzen.
🎯 Ziele
- Primär: Deutsche und englische E-Mail-Templates
- Automatische Sprachauswahl basierend auf Nutzerpräferenz
- Fallback: Englisch als Standard
- Erweiterbar: Einfach neue Sprachen hinzufügen
📊 Ansätze (von einfach zu komplex)
Ansatz 1: PocketBase Collection-Erweiterung (⭐ Empfohlen)
Konzept: Nutzer-Sprachpräferenz in der users Collection speichern
Implementation:
- Users Collection erweitern:
// Neues Feld in users collection
{
name: "language",
type: "select",
values: ["de", "en"],
default: "de"
}
- Custom E-Mail-Service erstellen:
// src/lib/server/email-service.ts
import { pb } from '$lib/pocketbase';
export async function sendLocalizedEmail(
userId: string,
type: 'verification' | 'reset' | 'change',
data: any
) {
// Nutzer-Sprache abrufen
const user = await pb.collection('users').getOne(userId);
const lang = user.language || 'de';
// Template basierend auf Sprache wählen
const template = getTemplate(type, lang);
// E-Mail senden
await sendEmail({
to: user.email,
subject: template.subject,
html: template.body(data)
});
}
Vorteile:
- ✅ Einfache Implementation
- ✅ Nutzer kann Sprache in Settings ändern
- ✅ Funktioniert mit PocketBase
Nachteile:
- ❌ Erfordert Custom Email Handler
- ❌ PocketBase Standard-Mails umgehen
Ansatz 2: Browser-Sprache + IP-Geolocation
Konzept: Automatische Spracherkennung ohne Nutzer-Input
Implementation:
// src/routes/register/+page.server.ts
export const actions = {
register: async ({ request, getClientAddress }) => {
const clientIp = getClientAddress();
const acceptLanguage = request.headers.get('accept-language');
// Sprache ermitteln
const language = detectLanguage(acceptLanguage, clientIp);
// Bei User-Erstellung speichern
await pb.collection('users').create({
email,
password,
language // 'de' oder 'en'
});
}
};
function detectLanguage(acceptLanguage: string, ip: string): string {
// 1. Browser-Sprache prüfen
if (acceptLanguage?.startsWith('de')) return 'de';
if (acceptLanguage?.startsWith('en')) return 'en';
// 2. IP-Geolocation (optional)
// const country = await getCountryFromIP(ip);
// if (['DE', 'AT', 'CH'].includes(country)) return 'de';
// 3. Fallback
return 'en';
}
Vorteile:
- ✅ Automatisch ohne Nutzer-Aktion
- ✅ Nutzerfreundlich
Nachteile:
- ❌ Nicht immer akkurat
- ❌ Zusätzliche Geolocation-API nötig
Ansatz 3: Custom PocketBase Extension (Advanced)
Konzept: PocketBase mit Go erweitern für native Unterstützung
Struktur:
// pb_hooks/email_localization.go
package main
import (
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnRecordBeforeCreateRequest("users").Add(func(e *core.RecordCreateEvent) error {
// Sprache aus Request Headers
lang := e.HttpContext.Request().Header.Get("Accept-Language")
e.Record.Set("language", parseLanguage(lang))
return nil
})
app.OnMailerBeforeRecordVerificationSend().Add(func(e *core.MailerRecordEvent) error {
user := e.Record
lang := user.GetString("language")
// Template basierend auf Sprache
if lang == "de" {
e.Message.Subject = "Bestätige deine E-Mail"
e.Message.HTML = germanTemplate(e.Record, e.Token)
} else {
e.Message.Subject = "Verify your email"
e.Message.HTML = englishTemplate(e.Record, e.Token)
}
return nil
})
app.Start()
}
Vorteile:
- ✅ Native PocketBase Integration
- ✅ Alle Standard-Features bleiben
- ✅ Beste Performance
Nachteile:
- ❌ Komplexe Implementation
- ❌ Go-Kenntnisse erforderlich
- ❌ Custom PocketBase Build
🚀 Empfohlene Lösung: Hybrid-Ansatz
Phase 1: Quick Win (1-2 Tage)
- User Collection erweitern:
ALTER TABLE users ADD COLUMN language TEXT DEFAULT 'de';
- Settings-Page Update:
<!-- src/routes/(app)/settings/+page.svelte -->
<select name="language" bind:value={user.language}>
<option value="de">🇩🇪 Deutsch</option>
<option value="en">🇬🇧 English</option>
</select>
- Template-Struktur:
docs/mail/templates/
├── de/
│ ├── verification.html
│ ├── reset.html
│ └── change.html
└── en/
├── verification.html
├── reset.html
└── change.html
Phase 2: Custom Email Handler (3-5 Tage)
// src/lib/server/email/index.ts
import { readFileSync } from 'fs';
import { compile } from 'handlebars';
export class EmailService {
private templates: Map<string, Function> = new Map();
constructor() {
this.loadTemplates();
}
private loadTemplates() {
const languages = ['de', 'en'];
const types = ['verification', 'reset', 'change'];
for (const lang of languages) {
for (const type of types) {
const path = `./templates/${lang}/${type}.html`;
const template = readFileSync(path, 'utf-8');
const compiled = compile(template);
this.templates.set(`${lang}-${type}`, compiled);
}
}
}
async send(userId: string, type: string, data: any) {
const user = await pb.collection('users').getOne(userId);
const lang = user.language || 'de';
const template = this.templates.get(`${lang}-${type}`);
if (!template) throw new Error('Template not found');
const html = template({
...data,
user,
appUrl: PUBLIC_APP_URL,
currentYear: new Date().getFullYear()
});
await this.sendViaBrevo({
to: user.email,
subject: this.getSubject(lang, type),
html
});
}
private getSubject(lang: string, type: string): string {
const subjects = {
'de-verification': 'Bestätige deine E-Mail für uLoad 🔗',
'en-verification': 'Verify your email for uLoad 🔗',
'de-reset': 'Passwort zurücksetzen für uLoad 🔐',
'en-reset': 'Reset your password for uLoad 🔐'
// ...
};
return subjects[`${lang}-${type}`];
}
}
Phase 3: API Routes (1 Tag)
// src/routes/api/auth/verify/+server.ts
import { EmailService } from '$lib/server/email';
export async function POST({ request }) {
const { userId, token } = await request.json();
const emailService = new EmailService();
await emailService.send(userId, 'verification', { token });
return json({ success: true });
}
🌍 Sprach-Detection Strategien
1. Bei Registrierung:
// Prioritäten:
1. URL-Parameter: ?lang=de
2. Browser Accept-Language Header
3. IP-Geolocation
4. Default: de (für DACH-Region)
2. Implementation:
export function detectUserLanguage(request: Request): 'de' | 'en' {
// 1. Check URL param
const url = new URL(request.url);
const urlLang = url.searchParams.get('lang');
if (urlLang === 'de' || urlLang === 'en') return urlLang;
// 2. Check browser language
const acceptLang = request.headers.get('accept-language') || '';
const browserLang = acceptLang.split(',')[0].split('-')[0].toLowerCase();
if (browserLang === 'de') return 'de';
if (browserLang === 'en') return 'en';
// 3. Check cookie (if user changed language before)
const cookies = parseCookies(request.headers.get('cookie'));
if (cookies.lang === 'de' || cookies.lang === 'en') return cookies.lang;
// 4. Default
return 'de'; // DACH-focused app
}
📁 Datei-Struktur
src/
├── lib/
│ ├── server/
│ │ ├── email/
│ │ │ ├── index.ts # EmailService class
│ │ │ ├── templates.ts # Template loader
│ │ │ └── brevo.ts # Brevo API wrapper
│ │ └── i18n/
│ │ ├── detector.ts # Language detection
│ │ └── translations.ts # Email translations
│ └── stores/
│ └── language.ts # Client-side language store
├── routes/
│ ├── api/
│ │ ├── user/language/+server.ts # Update language preference
│ │ └── email/send/+server.ts # Custom email sender
│ └── (app)/
│ └── settings/
│ └── +page.svelte # Language selector
└── hooks.server.ts # Language detection on request
🔄 Migration Plan
Woche 1:
- Users Collection um
languageFeld erweitern - Settings-Page mit Sprachauswahl
- Deutsche & englische Templates erstellen
Woche 2:
- EmailService Klasse implementieren
- API Routes für Custom Emails
- Auto-Detection bei Registrierung
Woche 3:
- Testing mit verschiedenen Sprachen
- Fallback-Mechanismen
- Documentation
💡 Quick Start (Minimal Version)
Für einen schnellen Start ohne große Änderungen:
1. Zwei PocketBase Instanzen:
# Deutsche Version
PB_LANG=de ./pocketbase serve --http=127.0.0.1:8090
# Englische Version
PB_LANG=en ./pocketbase serve --http=127.0.0.1:8091
2. Nginx Proxy:
server {
listen 80;
server_name api.ulo.ad;
# Route basierend auf Accept-Language
if ($http_accept_language ~* "^de") {
proxy_pass http://127.0.0.1:8090;
}
proxy_pass http://127.0.0.1:8091; # Default EN
}
Vorteil: Keine Code-Änderungen nötig Nachteil: Doppelte Wartung
🎨 Template Management
Option 1: Handlebars Templates
<!-- templates/de/verification.hbs -->
<h1>Willkommen {{user.name}}!</h1>
<p>Bitte bestätige deine E-Mail:</p>
<a href='{{appUrl}}/verify?token={{token}}'>Bestätigen</a>
Option 2: React Email (Modern)
// emails/Verification.tsx
export function VerificationEmail({ user, token, lang }) {
const t = translations[lang];
return (
<Html>
<Head />
<Body>
<Container>
<Heading>
{t.welcome} {user.name}!
</Heading>
<Text>{t.pleaseVerify}</Text>
<Button href={`${APP_URL}/verify?token=${token}`}>{t.verifyButton}</Button>
</Container>
</Body>
</Html>
);
}
Option 3: JSON-basierte Templates
{
"de": {
"verification": {
"subject": "Bestätige deine E-Mail",
"heading": "Willkommen {name}!",
"body": "Bitte bestätige deine E-Mail-Adresse.",
"button": "E-Mail bestätigen"
}
},
"en": {
"verification": {
"subject": "Verify your email",
"heading": "Welcome {name}!",
"body": "Please verify your email address.",
"button": "Verify Email"
}
}
}
🚦 Entscheidungsmatrix
| Kriterium | Ansatz 1 | Ansatz 2 | Ansatz 3 |
|---|---|---|---|
| Aufwand | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Flexibilität | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Wartbarkeit | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| Performance | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Zukunftssicher | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
Empfehlung: Starte mit Ansatz 1, plane für Ansatz 3
✅ Nächste Schritte
- Sofort (Tag 1):
- Language Feld zu users Collection hinzufügen
- Settings-Page um Sprachauswahl erweitern
- Kurzfristig (Woche 1):
- Templates in DE/EN erstellen
- Template-Loader implementieren
- Mittelfristig (Woche 2-3):
- EmailService mit Sprach-Support
- Auto-Detection bei Registration
- Langfristig (Monat 2):
- Weitere Sprachen (FR, ES, IT)
- A/B Testing für Templates
- Analytics pro Sprache
📚 Ressourcen
Erstellt: 15. Januar 2025
Status: Bereit zur Diskussion
Priorität: Mittel
Geschätzter Aufwand: 5-10 Tage für vollständige Implementation