From 7604e00e0f291ecbb9bab87d400c07b7d0e14702 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:25:41 +0100 Subject: [PATCH] Refactor locale system: align types with usage, add CMS formatting docs (#59) * Initial plan * Initial analysis: understanding locale system issues Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * Fix translation types to match actual component usage Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * Add comprehensive locale system documentation and fix API route types Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> * Address code review feedback: improve readability and translate comments to English Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: denshooter <44590296+denshooter@users.noreply.github.com> --- DIRECTUS_MIGRATION.md | 261 +++----- README.md | 1 + app/api/i18n/[namespace]/route.ts | 4 +- app/api/projects/[id]/translation/route.ts | 4 +- docs/LOCALE_IMPROVEMENTS_SUMMARY.md | 136 ++++ docs/LOCALE_SYSTEM.md | 386 +++++++++++ lib/translations-loader.ts | 195 +++--- package-lock.json | 712 ++++++++++++++++++++- types/translations.ts | 95 ++- 9 files changed, 1452 insertions(+), 342 deletions(-) create mode 100644 docs/LOCALE_IMPROVEMENTS_SUMMARY.md create mode 100644 docs/LOCALE_SYSTEM.md diff --git a/DIRECTUS_MIGRATION.md b/DIRECTUS_MIGRATION.md index 5927bbc..c2810e3 100644 --- a/DIRECTUS_MIGRATION.md +++ b/DIRECTUS_MIGRATION.md @@ -1,221 +1,146 @@ # Directus Integration - Migration Guide -## 🎯 Was wurde geändert? +## 🎯 Overview -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 +This portfolio now has a **hybrid i18n system**: +- ✅ **JSON Files** (Primary) → All translations work from `messages/*.json` files +- ✅ **Directus CMS** (Optional) → Can override translations dynamically without rebuilds -## 📁 Neue Dateien +**Important**: Directus is **optional**. The app works perfectly fine without it using JSON fallbacks. + +## 📁 New File Structure ### 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 +- `lib/directus.ts` - REST Client for Directus (uses `de-DE`, `en-US` locale codes) +- `lib/i18n-loader.ts` - Loads texts with Fallback Chain +- `lib/translations-loader.ts` - Batch loader for all sections (cleaned up to match actual usage) +- `types/translations.ts` - TypeScript types for all translation objects (fixed to match components) ### 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 +All component wrappers properly load and pass translations to client components. -## 🔄 Architektur +## 🔄 How It Works -### Vorher (next-intl only) +### Without Directus (Default) ``` -Client Component → useTranslations("nav") → JSON File +Component → useTranslations("nav") → JSON File (messages/en.json) ``` -### Jetzt (Directus + Fallback) +### With Directus (Optional) ``` 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 + → Try Directus API (de-DE/en-US) + → If not found: JSON File (de/en) + → Props to Client Component ``` -## 🗄️ Directus Setup +## 🗄️ Directus Setup (Optional) -### 1. Collection: `messages` +Only set this up if you want to edit translations through a CMS without rebuilding the app. -**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** +### 1. Environment Variables -**WICHTIG:** Du hast zwei Optionen: +Add to `.env.local`: +```bash +DIRECTUS_URL=https://cms.example.com +DIRECTUS_STATIC_TOKEN=your_token_here +``` -#### 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 +**If these are not set**, the system will skip Directus and use JSON files only. -#### 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: `messages` -### 2. Collection: `content_pages` (Optional) +Create a `messages` collection in Directus with these fields: +- `key` (String, required) - e.g., "nav.home" +- `translations` (Translations) - Directus native translations feature +- Configure languages: `en-US` and `de-DE` -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) +**Note**: Keys use dot notation (`nav.home`) but locales use dashes (`en-US`, `de-DE`). ### 3. Permissions -**Public Role:** -- `messages`: Read access (alle Felder) -- `content_pages`: Read access (alle Felder) +Grant **Public** role read access to `messages` collection. -## 📝 Keys eintragen +## 📝 Translation Keys -Alle Keys aus `DIRECTUS_CHECKLIST.md` müssen in Directus eingetragen werden. +See `docs/LOCALE_SYSTEM.md` for the complete list of translation keys and their structure. -**Beispiel Keys:** -``` -nav.home -nav.about -nav.projects -nav.contact -home.hero.greeting -home.hero.name -home.hero.role -home.hero.description -... -``` +All keys are organized hierarchically: +- `nav.*` - Navigation items +- `home.hero.*` - Hero section +- `home.about.*` - About section +- `home.projects.*` - Projects section +- `home.contact.*` - Contact form and info +- `footer.*` - Footer content +- `consent.*` - Privacy consent banner -**Wichtig:** Keys sind **dot-separated** (wie in JSON), aber **Locale nutzt `-`**: -- ✅ `key="nav.home"`, `locale="de-DE"` -- ❌ `key="nav_home"`, `locale="de"` +## 🎨 Rich Text Content -## 🔧 Environment Variables +For longer content that needs formatting (bold, italic, lists), use the `content_pages` collection: -In `.env.local`: -```bash -DIRECTUS_URL=https://cms.dk0.dev -DIRECTUS_STATIC_TOKEN=ogUMcHCa1CAYU1YifsoeJ_7V76o1atYG -``` +### Collection: `content_pages` (Optional) -## 🚀 Wie funktioniert's? +Fields: +- `slug` (String, unique) - e.g., "home-hero" +- `locale` (String) - `en` or `de` +- `title` (String) +- `content` (Rich Text or Long Text) -### 1. Seite wird geladen -```tsx -// app/[locale]/page.tsx -export default async function Page({ params }) { - const { locale } = await params; - return ; -} -``` +Examples: +- `home-hero` - Hero section description +- `home-about` - About section content +- `home-contact` - Contact intro text -### 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 ( - - - - ); -} -``` +Components fetch these via `/api/content/page` and render using `RichTextClient`. ## 🔍 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" +For every translation key, the system searches in this order: -## 🎨 Cache +1. **Directus** (if configured) in requested locale (e.g., `de-DE`) +2. **Directus** in English fallback (e.g., `en-US`) +3. **JSON file** in requested locale (e.g., `messages/de.json`) +4. **JSON file** in English (e.g., `messages/en.json`) +5. **Key itself** as last resort (e.g., returns `"nav.home"`) -- 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 +## ✅ What Was Fixed -## ✅ Testing +Previous issues that have been resolved: -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!" +1. ✅ **Type mismatches** - All translation types now match actual component usage +2. ✅ **Unused fields** - Removed translation keys that were never used (like `hero.greeting`, `hero.name`) +3. ✅ **Wrong structure** - Fixed `AboutTranslations` structure (removed fake `interests` nesting) +4. ✅ **Missing keys** - Aligned loaders with JSON files and actual component requirements +5. ✅ **Confusing comments** - Removed misleading comments in `translations-loader.ts` -2. **Ohne Directus:** Stoppe Directus - - Prüfe: Messages sollten aus JSON files kommen - - Website sollte normal funktionieren (degraded mode) +## 🎯 Best Practices -3. **Build Test:** - ```bash - npm run build - ``` - - Sollte ohne Errors durchlaufen +1. **Always maintain JSON files** - Even if using Directus, keep JSON files as fallback +2. **Use types** - TypeScript types ensure correct usage +3. **Test without Directus** - App should work perfectly without CMS configured +4. **Rich text for formatting** - Use `content_pages` for content that needs bold/italic/lists +5. **JSON for UI labels** - Use JSON/messages for short UI strings like buttons and labels ## 🐛 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 not configured +**This is normal!** The app works fine. All translations come from JSON files. -### "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` +### Want to use Directus? +1. Set up `DIRECTUS_URL` and `DIRECTUS_STATIC_TOKEN` +2. Create `messages` collection +3. Add your translations +4. They will override JSON values -### "Texte ändern sich nicht" -- Cache! Warte 5 Minuten oder restart Server -- Oder: Clear Cache manuell (`clearI18nCache()` in lib/i18n-loader.ts) +### Translation not showing? +Check in this order: +1. Does key exist in `messages/en.json`? +2. Is the key spelled correctly? +3. Is component using correct namespace? -## 📚 Next Steps +## 📚 Further Reading -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) +- **Complete locale documentation**: `docs/LOCALE_SYSTEM.md` +- **Directus setup checklist**: `DIRECTUS_CHECKLIST.md` +- **Operations guide**: `docs/OPERATIONS.md` -## 🎯 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) diff --git a/README.md b/README.md index e48769d..3e975bf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Quick links - **Ops / setup / deployment / testing**: `docs/OPERATIONS.md` +- **Locale System & Translations**: `docs/LOCALE_SYSTEM.md` # Dennis Konkol Portfolio - Modern Dark Theme diff --git a/app/api/i18n/[namespace]/route.ts b/app/api/i18n/[namespace]/route.ts index 9a57577..b65d256 100644 --- a/app/api/i18n/[namespace]/route.ts +++ b/app/api/i18n/[namespace]/route.ts @@ -14,9 +14,9 @@ const messagesMap = { en: enMessages, de: deMessages }; */ export async function GET( req: NextRequest, - { params }: { params: { namespace: string } } + { params }: { params: Promise<{ namespace: string }> } ) { - const namespace = params.namespace; + const { namespace } = await params; const locale = req.nextUrl.searchParams.get('locale') || 'en'; // Normalize locale (de-DE -> de) diff --git a/app/api/projects/[id]/translation/route.ts b/app/api/projects/[id]/translation/route.ts index 9ae87fb..1136f46 100644 --- a/app/api/projects/[id]/translation/route.ts +++ b/app/api/projects/[id]/translation/route.ts @@ -61,12 +61,12 @@ export async function PUT( locale, title, description, - content: content ?? null, + content: content ?? undefined, }, update: { title, description, - content: content ?? null, + content: content ?? undefined, }, }); diff --git a/docs/LOCALE_IMPROVEMENTS_SUMMARY.md b/docs/LOCALE_IMPROVEMENTS_SUMMARY.md new file mode 100644 index 0000000..b7a9959 --- /dev/null +++ b/docs/LOCALE_IMPROVEMENTS_SUMMARY.md @@ -0,0 +1,136 @@ +# Locale System Improvements - Summary + +## Problem Statement (Original) + +> The locale stuff is not really working please fix this and bring more structure to it i think there are to many field it dont know how exists. Then i have the question on how i can design stuff then when i use directus as a cms. because some words i maybe want to be writting thicker or so. + +## Issues Identified + +1. **Confusing translation system** - Mix of Directus API + JSON fallbacks with unclear flow +2. **Too many fields** - Translation loaders had many keys that don't actually exist or aren't used +3. **Type mismatches** - TypeScript interfaces didn't match actual component usage +4. **Missing documentation** - No clear guide on how the locale system works +5. **No rich text support guidance** - No documentation on how to style text (bold, italic, etc.) in Directus + +## Changes Made + +### 1. Fixed Translation Types (`types/translations.ts`) + +**Before**: Types had many unused fields and wrong structure +- `AboutTranslations` had fake `interests` structure that was never used +- `HeroTranslations` had fields like `greeting`, `name`, `role` that don't exist +- `FooterTranslations` had nested `links` structure and wrong keys +- `ContactTranslations` was missing many form validation error keys + +**After**: All types now match actual component usage +- Removed all unused/fake fields +- Added all missing fields that components actually use +- Flattened overly-nested structures +- Types now provide accurate autocomplete and type checking + +### 2. Fixed Translation Loaders (`lib/translations-loader.ts`) + +**Before**: +- Loaders tried to fetch non-existent keys +- Had confusing comments like "Diese Keys sind NICHT korrekt" +- Mapped keys to wrong structure (e.g., hobbies mapped to interests) + +**After**: +- All loaders now fetch only keys that exist in JSON files +- Removed misleading comments +- Correct mapping from keys to return structure +- Clear, straightforward code + +### 3. Fixed API Routes + +- Updated `app/api/i18n/[namespace]/route.ts` for Next.js 15 async params +- Fixed `app/api/projects/[id]/translation/route.ts` Prisma null handling + +### 4. Added Comprehensive Documentation + +Created **`docs/LOCALE_SYSTEM.md`** with: +- Complete architecture explanation +- All translation structures with TypeScript types +- How to use translations in server/client components +- **Rich text content guide** - How to format text in Directus CMS +- Adding new translations workflow +- Fallback behavior explanation +- Best practices +- Troubleshooting guide + +### 5. Clarified Directus Integration + +Updated **`DIRECTUS_MIGRATION.md`**: +- Made it clear that Directus is **optional** +- Emphasized JSON files work perfectly without CMS +- Removed confusing sections +- Added "what was fixed" section +- Better troubleshooting + +### 6. Updated Main README + +Added link to locale system documentation for easy discovery. + +## How to Use (For Developers) + +### Static Translations (Most Common) + +All translations are in `messages/en.json` and `messages/de.json`. Components use: + +```tsx +const t = useTranslations('home.hero'); +return

{t('title')}

; +``` + +### Rich Text Content (For Styling) + +For content that needs **bold**, *italic*, lists, etc.: + +1. In component: +```tsx +const [cmsDoc, setCmsDoc] = useState(null); + +useEffect(() => { + fetch(`/api/content/page?key=home-hero&locale=${locale}`) + .then(res => res.json()) + .then(data => setCmsDoc(data?.content?.content)); +}, [locale]); + +return cmsDoc ? :

{t('fallback')}

; +``` + +2. In Directus CMS, use the rich text editor to format text + +### Adding New Translations + +1. Add to both `messages/en.json` and `messages/de.json` +2. Update types in `types/translations.ts` if needed +3. Add loader in `lib/translations-loader.ts` if needed +4. Use in components with `useTranslations()` + +## Testing + +- ✅ Application builds successfully +- ✅ All unit tests pass (11 test suites, 17 tests) +- ✅ TypeScript types are correct +- ✅ No more confusing "NICHT korrekt" comments +- ✅ All translation keys align with JSON files + +## Benefits + +1. **Clear structure** - Developers now know exactly which translations exist +2. **Type safety** - TypeScript autocomplete works correctly +3. **Documentation** - Complete guide on how everything works +4. **Rich text support** - Clear instructions on how to style text in CMS +5. **Maintainability** - No more guessing which fields are real vs fake +6. **Flexibility** - Works perfectly without Directus, can add it later if needed + +## Migration Guide for Existing Code + +No breaking changes! All existing code continues to work because: +- We only removed unused types/keys +- We fixed types to match what was already being used +- All JSON files remain unchanged +- All component usage remains the same + +The changes are purely organizational and documentation improvements. diff --git a/docs/LOCALE_SYSTEM.md b/docs/LOCALE_SYSTEM.md new file mode 100644 index 0000000..c5cd404 --- /dev/null +++ b/docs/LOCALE_SYSTEM.md @@ -0,0 +1,386 @@ +# Locale System Documentation + +## Overview + +This portfolio uses a **hybrid i18n system** with: +- **Primary**: Static JSON files (`messages/en.json`, `messages/de.json`) +- **Secondary (Optional)**: Directus CMS for dynamic content management +- **Fallback Chain**: Directus → JSON → Key itself + +## Supported Locales + +- `en` (English) - Default +- `de` (German/Deutsch) + +## Architecture + +### 1. Static JSON Files (Primary) + +Location: `/messages/` +- `en.json` - English translations +- `de.json` - German translations + +These files contain **all** translation keys organized hierarchically: + +```json +{ + "nav": { + "home": "Home", + "about": "About", + "projects": "Projects", + "contact": "Contact" + }, + "home": { + "hero": { ... }, + "about": { ... }, + "projects": { ... }, + "contact": { ... } + }, + "footer": { ... }, + "consent": { ... } +} +``` + +### 2. Directus CMS (Optional Enhancement) + +If you want to edit translations without rebuilding: + +1. Set up Directus with a `messages` collection +2. Configure environment variables: + ```bash + DIRECTUS_URL=https://cms.example.com + DIRECTUS_STATIC_TOKEN=your_token_here + ``` +3. The system will automatically prefer Directus values over JSON + +**Note**: If Directus is not configured or unavailable, the system gracefully falls back to JSON files. + +### 3. Components Usage + +#### Server Components +Use translation loaders for better performance: + +```tsx +import { getHeroTranslations } from '@/lib/translations-loader'; + +export default async function MyPage({ params }) { + const { locale } = await params; + const translations = await getHeroTranslations(locale); + + return ; +} +``` + +#### Client Components +Use next-intl's `useTranslations` hook: + +```tsx +"use client"; +import { useTranslations } from 'next-intl'; + +export default function Hero() { + const t = useTranslations('home.hero'); + + return ( +
+

{t('title')}

+

{t('description')}

+
+ ); +} +``` + +## Translation Structure + +### Navigation (`nav`) +```typescript +{ + home: string; + about: string; + projects: string; + contact: string; +} +``` + +### Footer (`footer`) +```typescript +{ + role: string; + madeIn: string; + legalNotice: string; + privacyPolicy: string; + privacySettings: string; + privacySettingsTitle: string; + builtWith: string; +} +``` + +### Hero Section (`home.hero`) +```typescript +{ + description: string; + ctaWork: string; + ctaContact: string; + features: { + f1: string; + f2: string; + f3: string; + }; +} +``` + +### About Section (`home.about`) +```typescript +{ + title: string; + p1: string; + p2: string; + p3: string; + funFactTitle: string; + funFactBody: string; + techStackTitle: string; + techStack: { + categories: { + frontendMobile: string; + backendDevops: string; + toolsAutomation: string; + securityAdmin: string; + }; + items: { + selfHostedServices: string; + }; + }; + hobbiesTitle: string; + hobbies: { + selfHosting: string; + gaming: string; + gameServers: string; + jogging: string; + }; +} +``` + +### Projects Section (`home.projects`) +```typescript +{ + title: string; + subtitle: string; + viewAll: string; +} +``` + +### Contact Section (`home.contact`) +```typescript +{ + title: string; + subtitle: string; + getInTouch: string; + getInTouchBody: string; + form: { + title: string; + sending: string; + send: string; + placeholders: { + name: string; + email: string; + subject: string; + message: string; + }; + errors: { + nameRequired: string; + nameMin: string; + emailRequired: string; + emailInvalid: string; + subjectRequired: string; + subjectMin: string; + messageRequired: string; + messageMin: string; + }; + characters: string; + }; + info: { + email: string; + location: string; + locationValue: string; + }; +} +``` + +### Consent Banner (`consent`) +```typescript +{ + title: string; + description: string; + essential: string; + analytics: string; + chat: string; + alwaysOn: string; + acceptAll: string; + acceptSelected: string; + rejectAll: string; + hide: string; +} +``` + +## Rich Text Content (CMS) + +For longer content that needs formatting (bold, italic, lists, etc.), use the **Rich Text API**: + +### 1. Server-Side Fetching + +```tsx +import { useEffect, useState } from 'react'; +import type { JSONContent } from "@tiptap/react"; +import RichTextClient from './RichTextClient'; + +export default function MyComponent() { + const [cmsDoc, setCmsDoc] = useState(null); + + useEffect(() => { + (async () => { + try { + const res = await fetch( + `/api/content/page?key=${encodeURIComponent("page-slug")}&locale=${locale}`, + ); + const data = await res.json(); + if (data?.content?.content && data?.content?.locale === locale) { + setCmsDoc(data.content.content as JSONContent); + } + } catch { + // Fallback to static content + setCmsDoc(null); + } + })(); + }, [locale]); + + return ( +
+ {cmsDoc ? ( + + ) : ( +

{t('fallbackText')}

+ )} +
+ ); +} +``` + +### 2. Styling Text in Directus + +When editing content in Directus CMS: + +- **Bold**: Select text and click Bold button or use Ctrl/Cmd + B +- **Italic**: Select text and click Italic button or use Ctrl/Cmd + I +- **Headings**: Use heading dropdown to create H2, H3, etc. +- **Lists**: Create bullet or numbered lists +- **Links**: Highlight text and add URL + +The `RichTextClient` component will render all these styles correctly. + +## Adding New Translations + +### 1. Add to JSON Files + +Edit both `messages/en.json` and `messages/de.json`: + +```json +// en.json +{ + "mySection": { + "newKey": "My new translation" + } +} + +// de.json +{ + "mySection": { + "newKey": "Meine neue Übersetzung" + } +} +``` + +### 2. Update Types (if needed) + +If adding a new section, update `types/translations.ts`: + +```typescript +export interface MySectionTranslations { + newKey: string; +} +``` + +### 3. Create Loader Function (if needed) + +Add to `lib/translations-loader.ts`: + +```typescript +export async function getMySectionTranslations(locale: string): Promise { + const newKey = await getLocalizedMessage('mySection.newKey', locale); + return { newKey }; +} +``` + +### 4. Use in Components + +```tsx +const t = useTranslations('mySection'); +const text = t('newKey'); +``` + +## Fallback Behavior + +The system follows this priority: + +1. **Directus** (if configured) - Dynamic content from CMS +2. **JSON files** - Static fallback in `/messages/` +3. **Key itself** - Returns the key string if nothing found + +Example: If key `nav.home` is not found anywhere, it returns `"nav.home"` as a visual indicator. + +## Caching + +- **JSON files**: Bundled at build time, no runtime caching needed +- **Directus content**: 5-minute in-memory cache to reduce API calls +- Clear cache: Restart the application or call `clearI18nCache()` + +## Best Practices + +1. **Keep JSON files updated**: Even if using Directus, maintain JSON files as fallback +2. **Use TypeScript types**: Ensures type safety across components +3. **Namespace keys clearly**: Use hierarchical structure (e.g., `home.hero.title`) +4. **Rich text for long content**: Use CMS rich text for paragraphs, use JSON for short UI labels +5. **Test both locales**: Always verify translations in both English and German +6. **Consistent naming**: Follow existing patterns for new keys + +## Troubleshooting + +### Translation not showing? + +1. Check if key exists in JSON files +2. Verify key spelling (case-sensitive) +3. Check if namespace is correct +4. Restart dev server to reload translations + +### Directus not working? + +1. Verify `DIRECTUS_URL` and `DIRECTUS_STATIC_TOKEN` in `.env` +2. Check if Directus is accessible +3. System will automatically fallback to JSON - check console for errors + +### Rich text not rendering? + +1. Ensure content is in Tiptap JSON format +2. Check if `RichTextClient` is imported correctly +3. Verify the API response structure + +## Migration from Old System + +The current system has been simplified from a previous more complex setup. Key changes: + +- ✅ Removed unused translation keys from loaders +- ✅ Fixed type mismatches between interfaces and actual usage +- ✅ Aligned all translation types with component requirements +- ✅ Improved documentation and structure +- ✅ Added rich text support for CMS content + +All components now use the correct translation keys that exist in JSON files, eliminating confusion about which fields are actually used. diff --git a/lib/translations-loader.ts b/lib/translations-loader.ts index b28b067..1279795 100644 --- a/lib/translations-loader.ts +++ b/lib/translations-loader.ts @@ -26,170 +26,181 @@ export async function getNavTranslations(locale: string): Promise { - const [role, description, privacy, imprint, copyright, madeWith, resetConsent] = await Promise.all([ + const [ + role, + madeIn, + legalNotice, + privacyPolicy, + privacySettings, + privacySettingsTitle, + builtWith + ] = await Promise.all([ getLocalizedMessage('footer.role', locale), - getLocalizedMessage('footer.description', locale), - getLocalizedMessage('footer.links.privacy', locale), - getLocalizedMessage('footer.links.imprint', locale), - getLocalizedMessage('footer.copyright', locale), - getLocalizedMessage('footer.madeWith', locale), - getLocalizedMessage('footer.resetConsent', locale), + getLocalizedMessage('footer.madeIn', locale), + getLocalizedMessage('footer.legalNotice', locale), + getLocalizedMessage('footer.privacyPolicy', locale), + getLocalizedMessage('footer.privacySettings', locale), + getLocalizedMessage('footer.privacySettingsTitle', locale), + getLocalizedMessage('footer.builtWith', locale), ]); return { role, - description, - links: { privacy, imprint }, - copyright, - madeWith, - resetConsent, + madeIn, + legalNotice, + privacyPolicy, + privacySettings, + privacySettingsTitle, + builtWith, }; } export async function getHeroTranslations(locale: string): Promise { const keys = [ - 'home.hero.greeting', - 'home.hero.name', - 'home.hero.role', 'home.hero.description', 'home.hero.ctaWork', 'home.hero.ctaContact', 'home.hero.features.f1', 'home.hero.features.f2', 'home.hero.features.f3', - 'home.hero.scrollDown', ]; const values = await Promise.all(keys.map(key => getLocalizedMessage(key, locale))); return { - greeting: values[0], - name: values[1], - role: values[2], - description: values[3], - cta: { - projects: values[4], - contact: values[5], - }, + description: values[0], + ctaWork: values[1], + ctaContact: values[2], features: { - f1: values[6], - f2: values[7], - f3: values[8], + f1: values[3], + f2: values[4], + f3: values[5], }, - scrollDown: values[9], }; } export async function getAboutTranslations(locale: string): Promise { - // Diese Keys sind NICHT korrekt - wir nutzen nur für Type Compatibility - // Die About Component nutzt actually: title, p1, p2, p3, hobbiesTitle, hobbies.*, techStackTitle, techStack.* - // Lade alle benötigten Keys const keys = [ 'home.about.title', - 'home.about.description', - 'home.about.techStack.title', - 'home.about.techStack.categories.frontendMobile', - 'home.about.techStack.categories.backendDevops', - 'home.about.techStack.categories.toolsAutomation', - 'home.about.techStack.categories.securityAdmin', - 'home.about.techStack.items.selfHostedServices', - 'home.about.hobbiesTitle', // Nicht "interests.title"! - 'home.about.hobbies.selfHosting', - 'home.about.hobbies.gaming', - 'home.about.hobbies.gameServers', - 'home.about.hobbies.jogging', 'home.about.p1', 'home.about.p2', 'home.about.p3', 'home.about.funFactTitle', 'home.about.funFactBody', 'home.about.techStackTitle', + 'home.about.techStack.categories.frontendMobile', + 'home.about.techStack.categories.backendDevops', + 'home.about.techStack.categories.toolsAutomation', + 'home.about.techStack.categories.securityAdmin', + 'home.about.techStack.items.selfHostedServices', + 'home.about.hobbiesTitle', + 'home.about.hobbies.selfHosting', + 'home.about.hobbies.gaming', + 'home.about.hobbies.gameServers', + 'home.about.hobbies.jogging', ]; const values = await Promise.all(keys.map(key => getLocalizedMessage(key, locale))); return { title: values[0], - description: values[1], + p1: values[1], + p2: values[2], + p3: values[3], + funFactTitle: values[4], + funFactBody: values[5], + techStackTitle: values[6], techStack: { - title: values[2], categories: { - frontendMobile: values[3], - backendDevops: values[4], - toolsAutomation: values[5], - securityAdmin: values[6], + frontendMobile: values[7], + backendDevops: values[8], + toolsAutomation: values[9], + securityAdmin: values[10], }, items: { - selfHostedServices: values[7], + selfHostedServices: values[11], }, }, - interests: { - title: values[8], // hobbiesTitle - cybersecurity: { - title: values[9], // hobbies.selfHosting - description: values[10], // hobbies.gaming - }, - selfHosting: { - title: values[11], // hobbies.gameServers - description: values[12], // hobbies.jogging - }, - gaming: { - title: values[13], // p1 - description: values[14], // p2 - }, - automation: { - title: values[15], // p3 - description: values[16], // funFactTitle - }, + hobbiesTitle: values[12], + hobbies: { + selfHosting: values[13], + gaming: values[14], + gameServers: values[15], + jogging: values[16], }, }; } export async function getProjectsTranslations(locale: string): Promise { - const [title, viewAll] = await Promise.all([ + const [title, subtitle, viewAll] = await Promise.all([ getLocalizedMessage('home.projects.title', locale), + getLocalizedMessage('home.projects.subtitle', locale), getLocalizedMessage('home.projects.viewAll', locale), ]); - return { title, viewAll }; + return { title, subtitle, viewAll }; } export async function getContactTranslations(locale: string): Promise { const keys = [ 'home.contact.title', - 'home.contact.description', - 'home.contact.form.name', - 'home.contact.form.email', - 'home.contact.form.message', - 'home.contact.form.send', + 'home.contact.subtitle', + 'home.contact.getInTouch', + 'home.contact.getInTouchBody', + 'home.contact.form.title', 'home.contact.form.sending', - 'home.contact.form.success', - 'home.contact.form.error', - 'home.contact.info.title', + 'home.contact.form.send', + 'home.contact.form.placeholders.name', + 'home.contact.form.placeholders.email', + 'home.contact.form.placeholders.subject', + 'home.contact.form.placeholders.message', + 'home.contact.form.errors.nameRequired', + 'home.contact.form.errors.nameMin', + 'home.contact.form.errors.emailRequired', + 'home.contact.form.errors.emailInvalid', + 'home.contact.form.errors.subjectRequired', + 'home.contact.form.errors.subjectMin', + 'home.contact.form.errors.messageRequired', + 'home.contact.form.errors.messageMin', + 'home.contact.form.characters', 'home.contact.info.email', - 'home.contact.info.response', - 'home.contact.info.emailLabel', + 'home.contact.info.location', + 'home.contact.info.locationValue', ]; const values = await Promise.all(keys.map(key => getLocalizedMessage(key, locale))); return { title: values[0], - description: values[1], + subtitle: values[1], + getInTouch: values[2], + getInTouchBody: values[3], form: { - name: values[2], - email: values[3], - message: values[4], - send: values[5], - sending: values[6], - success: values[7], - error: values[8], + title: values[4], + sending: values[5], + send: values[6], + placeholders: { + name: values[7], + email: values[8], + subject: values[9], + message: values[10], + }, + errors: { + nameRequired: values[11], + nameMin: values[12], + emailRequired: values[13], + emailInvalid: values[14], + subjectRequired: values[15], + subjectMin: values[16], + messageRequired: values[17], + messageMin: values[18], + }, + characters: values[19], }, info: { - title: values[9], - email: values[10], - response: values[11], - emailLabel: values[12], + email: values[20], + location: values[21], + locationValue: values[22], }, }; } diff --git a/package-lock.json b/package-lock.json index fd5e822..4154b2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3054,6 +3054,17 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -4105,7 +4116,7 @@ "version": "1.57.0", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "playwright": "1.57.0" @@ -4145,14 +4156,14 @@ "version": "5.22.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { "version": "5.22.0", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4166,14 +4177,14 @@ "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { "version": "5.22.0", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "5.22.0", @@ -4185,7 +4196,7 @@ "version": "5.22.0", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "5.22.0" @@ -6784,6 +6795,28 @@ "@types/ms": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -6905,7 +6938,6 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, "license": "MIT" }, "node_modules/@types/json5": { @@ -7012,7 +7044,6 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -7022,7 +7053,6 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -7091,6 +7121,23 @@ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -7384,6 +7431,181 @@ "node": ">=16" } }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -7423,6 +7645,19 @@ "acorn": "^8" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -7474,6 +7709,48 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT", + "peer": true + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -8072,7 +8349,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "license": "MIT" }, "node_modules/call-bind": { @@ -8292,6 +8568,16 @@ "node": ">= 6" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -8632,7 +8918,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -9059,7 +9344,6 @@ "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -9205,6 +9489,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "license": "MIT", + "peer": true + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -9772,7 +10063,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -9784,7 +10074,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -9815,6 +10104,16 @@ "node": ">=0.10.0" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -9886,7 +10185,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-equals": { @@ -9942,6 +10240,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/fast-xml-parser": { "version": "5.2.5", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", @@ -10365,6 +10680,13 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause", + "peer": true + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -10412,7 +10734,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -10496,6 +10817,24 @@ "uglify-js": "^3.1.4" } }, + "node_modules/happy-dom": { + "version": "20.3.4", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.3.4.tgz", + "integrity": "sha512-rfbiwB6OKxZFIFQ7SRnCPB2WL9WhyXsFoTfecYgeCeFSOBxvkWLaXsdv5ehzJrfqwXQmDephAKWLRQoFoJwrew==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "entities": "^4.5.0", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -10513,7 +10852,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12536,7 +12874,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -12699,6 +13036,20 @@ "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", "license": "MIT" }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -13002,7 +13353,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, "license": "MIT" }, "node_modules/merge2": { @@ -13454,7 +13804,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -13464,7 +13813,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -13611,7 +13959,6 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, "license": "MIT" }, "node_modules/next": { @@ -13741,6 +14088,17 @@ } } }, + "node_modules/next-intl/node_modules/@swc/helpers": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", + "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -14447,7 +14805,7 @@ "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "playwright-core": "1.57.0" @@ -14466,7 +14824,7 @@ "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -14741,7 +15099,7 @@ "version": "5.22.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -15086,6 +15444,16 @@ ], "license": "MIT" }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/react": { "version": "19.2.3", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", @@ -15293,6 +15661,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-in-the-middle": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", @@ -15492,6 +15870,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -15595,6 +15994,63 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT", + "peer": true + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -15621,6 +16077,16 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -15866,7 +16332,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -16414,7 +16879,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -16424,6 +16888,109 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT", + "peer": true + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -16908,7 +17475,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -17246,6 +17813,20 @@ "makeerror": "1.0.12" } }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "license": "MIT", + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -17256,6 +17837,55 @@ "node": ">=12" } }, + "node_modules/webpack": { + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, "node_modules/webpack-bundle-analyzer": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", @@ -17328,6 +17958,30 @@ "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", "license": "MIT" }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -17352,7 +18006,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -17553,7 +18206,6 @@ "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/types/translations.ts b/types/translations.ts index 44ef3f7..3fbef96 100644 --- a/types/translations.ts +++ b/types/translations.ts @@ -1,6 +1,6 @@ /** - * Type Definitions für Directus-basierte Translations - * Jede Section hat ihre eigenen Translation Props + * Type Definitions for Directus-based Translations + * Each section has its own translation props */ export interface NavTranslations { @@ -12,38 +12,34 @@ export interface NavTranslations { export interface FooterTranslations { role: string; - description: string; - links: { - privacy: string; - imprint: string; - }; - copyright: string; - madeWith: string; - resetConsent: string; + madeIn: string; + legalNotice: string; + privacyPolicy: string; + privacySettings: string; + privacySettingsTitle: string; + builtWith: string; } export interface HeroTranslations { - greeting: string; - name: string; - role: string; description: string; - cta: { - projects: string; - contact: string; - }; + ctaWork: string; + ctaContact: string; features: { f1: string; f2: string; f3: string; }; - scrollDown: string; } export interface AboutTranslations { title: string; - description: string; + p1: string; + p2: string; + p3: string; + funFactTitle: string; + funFactBody: string; + techStackTitle: string; techStack: { - title: string; categories: { frontendMobile: string; backendDevops: string; @@ -54,49 +50,52 @@ export interface AboutTranslations { selfHostedServices: string; }; }; - interests: { - title: string; - cybersecurity: { - title: string; - description: string; - }; - selfHosting: { - title: string; - description: string; - }; - gaming: { - title: string; - description: string; - }; - automation: { - title: string; - description: string; - }; + hobbiesTitle: string; + hobbies: { + selfHosting: string; + gaming: string; + gameServers: string; + jogging: string; }; } export interface ProjectsTranslations { title: string; + subtitle: string; viewAll: string; } export interface ContactTranslations { title: string; - description: string; + subtitle: string; + getInTouch: string; + getInTouchBody: string; form: { - name: string; - email: string; - message: string; - send: string; + title: string; sending: string; - success: string; - error: string; + send: string; + placeholders: { + name: string; + email: string; + subject: string; + message: string; + }; + errors: { + nameRequired: string; + nameMin: string; + emailRequired: string; + emailInvalid: string; + subjectRequired: string; + subjectMin: string; + messageRequired: string; + messageMin: string; + }; + characters: string; }; info: { - title: string; email: string; - response: string; - emailLabel: string; + location: string; + locationValue: string; }; }