# 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.