# Directus Integration - Migration Guide ## 🎯 Was wurde geändert? Das Portfolio nutzt jetzt **Directus als CMS** für alle Texte. Die Integration ist **hybrid**: - ✅ **Directus** (primär) → Texte werden aus Directus CMS geladen - ✅ **JSON Fallback** (sekundär) → Falls Directus nicht erreichbar, nutzen wir messages/*.json ## 📁 Neue Dateien ### Core Infrastructure - `lib/directus.ts` - REST Client für Directus (nutzt `de-DE`, `en-US`) - `lib/i18n-loader.ts` - Lädt Texte mit Fallback-Chain - `lib/translations-loader.ts` - Batch-Loader für alle Sections - `types/translations.ts` - TypeScript Types für alle Translation Objects ### Components - `app/components/Header.server.tsx` - Server Wrapper für Header - `app/components/HeaderClient.tsx` - Client Implementation mit Props - `app/components/ClientWrappers.tsx` - Wrapper für Hero, About, Projects, Contact, Footer - `app/_ui/HomePageServer.tsx` - Server Component lädt alle Translations ## 🔄 Architektur ### Vorher (next-intl only) ``` Client Component → useTranslations("nav") → JSON File ``` ### Jetzt (Directus + Fallback) ``` Server Component → getNavTranslations(locale) → Directus API (de-DE/en-US) → Falls nicht gefunden: JSON File (de/en) → Props an Client Component Client Component → Nutzt translations aus Props ``` ## 🗄️ Directus Setup ### 1. Collection: `messages` **Felder:** - `id` (Primary Key, UUID, auto) - `key` (String, required) - z.B. "nav.home" - `locale` (String, required) - **WICHTIG:** `de-DE` oder `en-US` (mit `-`) - `value` (Text, required) - Der übersetzte Text - `translations` (Translations) - **Directus Native Translations Feature** **WICHTIG:** Du hast zwei Optionen: #### Option A: Directus Native Translations (Empfohlen) 1. Aktiviere "Translations" für `messages` Collection 2. Definiere `de-DE` und `en-US` als Languages 3. Felder: `key` (unique), `value` (translatable) 4. Pro Key nur ein Eintrag, Directus managed Translations intern #### Option B: Flat Structure (Einfacher) 1. Keine Translations Feature 2. Felder: `key` + `locale` + `value` 3. Pro Key/Locale Kombination ein Eintrag 4. Beispiel: - Row 1: key="nav.home", locale="de-DE", value="Startseite" - Row 2: key="nav.home", locale="en-US", value="Home" ### 2. Collection: `content_pages` (Optional) Für längere Inhalte (z.B. Datenschutz, Impressum): **Felder:** - `id` (Primary Key, UUID) - `slug` (String, unique) - z.B. "privacy-policy" - `locale` (String) - `de-DE` oder `en-US` - `title` (String) - `content` (Rich Text oder Long Text) ### 3. Permissions **Public Role:** - `messages`: Read access (alle Felder) - `content_pages`: Read access (alle Felder) ## 📝 Keys eintragen Alle Keys aus `DIRECTUS_CHECKLIST.md` müssen in Directus eingetragen werden. **Beispiel Keys:** ``` nav.home nav.about nav.projects nav.contact home.hero.greeting home.hero.name home.hero.role home.hero.description ... ``` **Wichtig:** Keys sind **dot-separated** (wie in JSON), aber **Locale nutzt `-`**: - ✅ `key="nav.home"`, `locale="de-DE"` - ❌ `key="nav_home"`, `locale="de"` ## 🔧 Environment Variables In `.env.local`: ```bash DIRECTUS_URL=https://cms.dk0.dev DIRECTUS_STATIC_TOKEN=ogUMcHCa1CAYU1YifsoeJ_7V76o1atYG ``` ## 🚀 Wie funktioniert's? ### 1. Seite wird geladen ```tsx // app/[locale]/page.tsx export default async function Page({ params }) { const { locale } = await params; return ; } ``` ### 2. Server Component lädt Translations ```tsx // app/_ui/HomePageServer.tsx export default async function HomePageServer({ locale }) { const heroT = await getHeroTranslations(locale); // ... return ; } ``` ### 3. Translation Loader fetcht von Directus ```tsx // lib/translations-loader.ts export async function getHeroTranslations(locale: string) { // Batch-Load aus Directus // locale='de' wird zu 'de-DE' gemapped const values = await Promise.all([...]); return { greeting, name, role, ... }; } ``` ### 4. Client Component nutzt Props ```tsx // app/components/ClientWrappers.tsx export function HeroClient({ locale, translations }) { // Konvertiert zu next-intl Format return ( ); } ``` ## 🔍 Fallback Chain Für jeden Key wird gesucht: 1. **Directus (requested locale)** - z.B. `de-DE` 2. **Directus (EN fallback)** - Falls nicht gefunden: `en-US` 3. **JSON (normalized locale)** - Falls Directus down: `messages/de.json` 4. **JSON (EN fallback)** - Falls Key nicht existiert: `messages/en.json` 5. **Key selbst** - Als letzter Fallback: return "nav.home" ## 🎨 Cache - In-Memory Cache mit 5 min TTL - Cache Key: `msg:${key}:${locale}` - Läuft im Server Memory (nicht persistent) - Bei Deploy/Restart wird Cache geleert ## ✅ Testing 1. **Mit Directus:** Trage einen Test-Key ein: - Key: `test` - Locale: `de-DE` - Value: `Hallo von Directus!` - Prüfe: `await getLocalizedMessage('test', 'de')` → "Hallo von Directus!" 2. **Ohne Directus:** Stoppe Directus - Prüfe: Messages sollten aus JSON files kommen - Website sollte normal funktionieren (degraded mode) 3. **Build Test:** ```bash npm run build ``` - Sollte ohne Errors durchlaufen ## 🐛 Troubleshooting ### "Key nicht gefunden" - Prüfe Directus GUI: Key exakt gleich? (`nav.home` nicht `nav_home`) - Prüfe Locale: `de-DE` oder `en-US` (mit `-`)? - Prüfe Permissions: Public role hat Read access? ### "Directus nicht erreichbar" - Prüfe `DIRECTUS_URL` in .env - Prüfe Token: `DIRECTUS_STATIC_TOKEN` - Test: `curl -H "Authorization: Bearer TOKEN" https://cms.dk0.dev/items/messages` ### "Texte ändern sich nicht" - Cache! Warte 5 Minuten oder restart Server - Oder: Clear Cache manuell (`clearI18nCache()` in lib/i18n-loader.ts) ## 📚 Next Steps 1. **Directus deployen** (Docker auf IONOS) 2. **Collections erstellen** (messages, content_pages) 3. **Keys eintragen** (aus DIRECTUS_CHECKLIST.md) 4. **Testen** (dev environment) 5. **Production** (wenn alles funktioniert) ## 🎯 Benefits - ✅ **Keine Rebuilds** für Text-Änderungen - ✅ **Non-Tech Editor** kann Texte ändern (Directus GUI) - ✅ **Graceful Degradation** (JSON Fallback) - ✅ **Type Safety** (TypeScript Types für alle Translations) - ✅ **Performance** (Server-side caching, parallel loading)