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>
This commit is contained in:
136
docs/LOCALE_IMPROVEMENTS_SUMMARY.md
Normal file
136
docs/LOCALE_IMPROVEMENTS_SUMMARY.md
Normal file
@@ -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 <h1>{t('title')}</h1>;
|
||||
```
|
||||
|
||||
### Rich Text Content (For Styling)
|
||||
|
||||
For content that needs **bold**, *italic*, lists, etc.:
|
||||
|
||||
1. In component:
|
||||
```tsx
|
||||
const [cmsDoc, setCmsDoc] = useState<JSONContent | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`/api/content/page?key=home-hero&locale=${locale}`)
|
||||
.then(res => res.json())
|
||||
.then(data => setCmsDoc(data?.content?.content));
|
||||
}, [locale]);
|
||||
|
||||
return cmsDoc ? <RichTextClient doc={cmsDoc} /> : <p>{t('fallback')}</p>;
|
||||
```
|
||||
|
||||
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.
|
||||
386
docs/LOCALE_SYSTEM.md
Normal file
386
docs/LOCALE_SYSTEM.md
Normal file
@@ -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 <HeroClient translations={translations} />;
|
||||
}
|
||||
```
|
||||
|
||||
#### 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 (
|
||||
<div>
|
||||
<h1>{t('title')}</h1>
|
||||
<p>{t('description')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 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<JSONContent | null>(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 (
|
||||
<div>
|
||||
{cmsDoc ? (
|
||||
<RichTextClient doc={cmsDoc} className="prose prose-stone max-w-none" />
|
||||
) : (
|
||||
<p>{t('fallbackText')}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 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<MySectionTranslations> {
|
||||
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.
|
||||
Reference in New Issue
Block a user