Files
Portfolio/docs/i18n/README.md
T
damjan_savicandClaude Sonnet 4.5 2abfcbe217 auto-claude: subtask-1-3 - Document how to add new translation keys
Added comprehensive documentation for adding translation keys:
- Step-by-step guide with namespace identification
- Detailed JSON structure explanation with real examples
- Namespace organization patterns (meta, common, navigation, pages, etc.)
- Code examples using useTranslations() hook
- Advanced usage: dynamic keys, string interpolation, rich content
- Best practices for key naming and structure consistency
- Examples for arrays, objects, and nested translations

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-25 06:36:36 +01:00

943 lines
24 KiB
Markdown

# Internationalization (i18n) Documentation
Complete guide to the multi-language support system in the portfolio application.
## Overview
This portfolio supports **3 languages** with full internationalization:
| Language | Code | Flag | Region |
|----------|------|------|--------|
| **German** (default) | `de` | 🇩🇪 | Germany |
| **English** | `en` | 🇬🇧 | United States |
| **Serbian** | `sr` | 🇷🇸 | Serbia |
### Tech Stack
- **next-intl** - Next.js internationalization library
- **JSON-based translations** - Simple, maintainable translation files
- **Type-safe locale handling** - TypeScript ensures locale consistency
- **SEO optimized** - Hreflang tags, Content-Language headers, localized metadata
### Key Features
- ✅ Automatic locale detection from URL path
- ✅ Fallback to default locale (German)
- ✅ Server-side translation loading
- ✅ Type-safe locale validation
- ✅ SEO-friendly URL structure (`/de/`, `/en/`, `/sr/`)
- ✅ Localized metadata for social sharing
- ✅ Support for nested translation keys
- ✅ Legacy migration path (locales-old)
## Quick Start
### Using Translations in Components
```tsx
import { useTranslations } from 'next-intl';
export default function MyComponent() {
const t = useTranslations('common');
return (
<div>
<h1>{t('nav.home')}</h1>
<p>{t('siteDescription')}</p>
</div>
);
}
```
### Adding a New Translation Key
#### Step-by-Step Guide
Follow this process to add new translation keys to the application:
##### 1. Identify the Appropriate Namespace
Before adding a new translation key, determine where it should live based on its purpose:
- **`meta`** - Site metadata, author information, SEO keywords
- **`common`** - Shared UI elements (buttons, navigation, errors, cookies)
- **`navigation`** - All navigation-related text (main menu, footer links, mobile menu)
- **`footer`** - Footer content (sections, legal links, copyright)
- **`pages`** - Page-specific content organized by route
- `pages.home` - Homepage content
- `pages.about` - About page content
- `pages.contact` - Contact page content
- `pages.privacy` - Privacy policy content
- `pages.terms` - Terms and conditions content
- `pages.notfound` - 404 page content
- **`portfolio`** - Portfolio page content and filters
- **`blog`** - Blog page content and UI elements
- **`login`** - Login page content
- **`dashboard`** - Dashboard content and metrics
**Best Practice:** Use `common` for elements that appear across multiple pages. Use page-specific namespaces for content unique to a single page.
##### 2. Open All Three Translation Files
You must update all three locale files to maintain consistency:
```bash
# Open in your editor:
src/messages/de.json # German (default)
src/messages/en.json # English
src/messages/sr.json # Serbian
```
##### 3. Add the Translation Key
Add your key to the **exact same location** in each file. The JSON structure must be identical across all locales.
**Example: Adding a new action button**
```json
// de.json
{
"common": {
"actions": {
"readMore": "Weiterlesen",
"viewMore": "Mehr anzeigen",
"viewAll": "Alle anzeigen",
"download": "Herunterladen" // ← New key
}
}
}
// en.json
{
"common": {
"actions": {
"readMore": "Read More",
"viewMore": "View More",
"viewAll": "View All",
"download": "Download" // ← New key
}
}
}
// sr.json
{
"common": {
"actions": {
"readMore": "Procitaj vise",
"viewMore": "Prikazi vise",
"viewAll": "Prikazi sve",
"download": "Preuzmi" // ← New key
}
}
}
```
**Example: Adding a new page section**
```json
// de.json
{
"pages": {
"services": { // ← New namespace
"hero": {
"title": "Unsere Dienstleistungen",
"subtitle": "Maßgeschneiderte Lösungen für Ihr Unternehmen"
}
}
}
}
// en.json
{
"pages": {
"services": { // ← New namespace
"hero": {
"title": "Our Services",
"subtitle": "Tailored solutions for your business"
}
}
}
}
// sr.json
{
"pages": {
"services": { // ← New namespace
"hero": {
"title": "Naše usluge",
"subtitle": "Prilagođena rešenja za vaše poslovanje"
}
}
}
}
```
##### 4. Use the Translation in Your Component
Import the `useTranslations` hook and specify the namespace:
```tsx
import { useTranslations } from 'next-intl';
export default function MyComponent() {
// Option 1: Access top-level namespace
const t = useTranslations('common.actions');
return (
<button className="btn-primary">
{t('download')}
</button>
);
}
```
**Multiple namespaces in one component:**
```tsx
import { useTranslations } from 'next-intl';
export default function ServicesPage() {
const tHero = useTranslations('pages.services.hero');
const tCommon = useTranslations('common.actions');
return (
<div>
<h1>{tHero('title')}</h1>
<p>{tHero('subtitle')}</p>
<button>{tCommon('readMore')}</button>
</div>
);
}
```
##### 5. Verify Your Changes
1. **Build the application** to ensure no TypeScript errors:
```bash
npm run build
```
2. **Test in all three languages:**
- German: `http://localhost:3000/de`
- English: `http://localhost:3000/en`
- Serbian: `http://localhost:3000/sr`
3. **Check for missing translations** - The browser console will warn if a key is missing
#### JSON Structure & Organization
##### Hierarchical Namespace Pattern
Translation files use a **deeply nested JSON structure** to organize translations logically:
```json
{
"namespace": { // Top-level category
"subnamespace": { // Feature or section
"key": "value", // Leaf node (actual translation)
"nested": {
"key": "value" // Deeper nesting allowed
}
}
}
}
```
**Real example from the codebase:**
```json
{
"pages": { // Namespace: Page content
"home": { // Subnamespace: Homepage
"hero": { // Section: Hero section
"title": "FULLSTACK DEVELOPER",
"subtitle": "AI AGENTS | VOICE AI",
"navigation": { // Nested object
"experience": "EXPERIENCE",
"skills": "SKILLS"
}
}
}
}
}
```
**Access in code:**
```tsx
const t = useTranslations('pages.home.hero');
console.log(t('title')); // "FULLSTACK DEVELOPER"
console.log(t('navigation.experience')); // "EXPERIENCE"
```
##### Special Data Types
**Arrays** - For lists of items:
```json
{
"meta": {
"site": {
"keywords": [
"Fullstack Developer",
"AI Developer Germany",
"Process Automation"
]
}
}
}
```
**Usage:**
```tsx
const t = useTranslations('meta.site');
const keywords = t.raw('keywords'); // Returns array
```
**Objects** - For structured data:
```json
{
"pages": {
"home": {
"experience": {
"positions": [
{
"role": "Process Automation Specialist",
"company": "Everlast Consulting GmbH",
"period": "12/2024 - PRESENT",
"highlights": [
"Development of AI agents",
"Web scraping solutions"
]
}
]
}
}
}
}
```
**Usage:**
```tsx
const t = useTranslations('pages.home.experience');
const positions = t.raw('positions'); // Returns array of objects
positions.map((position) => (
<div key={position.role}>
<h3>{position.role}</h3>
<p>{position.company}</p>
<span>{position.period}</span>
<ul>
{position.highlights.map((highlight) => (
<li key={highlight}>{highlight}</li>
))}
</ul>
</div>
));
```
#### Advanced Usage Examples
##### Dynamic Translation Keys
Access translations dynamically based on variables:
```tsx
import { useTranslations } from 'next-intl';
export default function CategoryFilter({ categories }: { categories: string[] }) {
const t = useTranslations('portfolio.filters.categories');
return (
<div>
{categories.map((category) => (
<button key={category}>
{t(category as any)} // Dynamic key lookup
</button>
))}
</div>
);
}
// If categories = ["Data Science", "AI Development"]
// Output: "Data Science" → "Data Science" (en) or "Data Science" (de)
// "AI Development" → "AI Development" (en) or "KI-Entwicklung" (de)
```
##### String Interpolation
Use variables in translations:
```tsx
import { useTranslations } from 'next-intl';
export default function Pagination({ start, end, total }: PaginationProps) {
const t = useTranslations('blog.ui.pagination');
// Translation: "Showing {start}-{end} of {total} articles"
return (
<p>
{t('showing', { start, end, total })}
</p>
);
}
```
**In JSON:**
```json
{
"blog": {
"ui": {
"pagination": {
"showing": "Showing {start}-{end} of {total} articles"
}
}
}
}
```
##### Accessing Rich Content
For translations containing HTML or rich content:
```tsx
import { useTranslations } from 'next-intl';
export default function RichText() {
const t = useTranslations('pages.about');
return (
<div>
{/* Simple text */}
<h1>{t('hero.title')}</h1>
{/* Raw access for objects/arrays */}
<div>
{t.raw('hero.expertise.processAutomation')}
</div>
</div>
);
}
```
#### Common Patterns & Best Practices
##### ✅ DO: Keep Structure Consistent
```json
// Good: Same structure across all locales
// de.json
{ "common": { "actions": { "save": "Speichern" } } }
// en.json
{ "common": { "actions": { "save": "Save" } } }
// sr.json
{ "common": { "actions": { "save": "Sačuvaj" } } }
```
##### ❌ DON'T: Mix Structures
```json
// Bad: Different structures
// de.json
{ "common": { "actions": { "save": "Speichern" } } }
// en.json
{ "common": { "save": "Save" } } // ❌ Missing "actions" level
```
##### ✅ DO: Use Semantic Naming
```json
{
"common": {
"actions": {
"submit": "Submit", // ✅ Clear, semantic
"cancel": "Cancel",
"delete": "Delete"
}
}
}
```
##### ❌ DON'T: Use Generic Names
```json
{
"common": {
"button1": "Submit", // ❌ Non-descriptive
"btn": "Cancel", // ❌ Abbreviated
"text1": "Delete" // ❌ Generic
}
}
```
##### ✅ DO: Group Related Translations
```json
{
"pages": {
"contact": {
"contactForm": { // ✅ Grouped by feature
"title": "Contact Me",
"name": { "label": "Name", "placeholder": "Your name" },
"email": { "label": "Email", "placeholder": "your@email.com" },
"submit": "Send Message"
}
}
}
}
```
##### ✅ DO: Reuse Common Translations
```tsx
// Good: Reuse common translations
import { useTranslations } from 'next-intl';
export default function MyForm() {
const tCommon = useTranslations('common.actions');
return (
<form>
<button type="submit">{tCommon('submit')}</button>
<button type="button">{tCommon('cancel')}</button>
</form>
);
}
```
## Documentation Structure
This documentation is organized into the following sections:
### 📁 Directory Structure & Organization
The i18n system is organized into three main directories, each serving a specific purpose in the translation infrastructure.
#### Overview of Directory Structure
```
src/
├── i18n/ # Configuration Layer
│ ├── config.ts # ✅ Locale definitions & metadata
│ ├── request.ts # ✅ next-intl integration
│ └── locales-old/ # ⚠️ DEPRECATED - Do NOT use
│ ├── de.ts
│ ├── en.ts
│ └── sr.ts
└── messages/ # ✅ Active Translation Files
├── de.json # German translations
├── en.json # English translations
└── sr.json # Serbian translations
```
---
#### `src/i18n/` - Configuration Directory
This directory contains the core configuration files that define how the i18n system operates.
**Purpose:** Centralized configuration for locale definitions, metadata, and next-intl integration.
##### `config.ts` - Locale Metadata & Settings
**Location:** `src/i18n/config.ts`
This is the **single source of truth** for all locale-related configuration.
**What it contains:**
1. **Locales Array** - Defines all supported languages
```typescript
export const locales = ['de', 'en', 'sr'] as const;
```
2. **Default Locale** - Fallback language
```typescript
export const defaultLocale = 'de' as const;
```
3. **Locale Type** - TypeScript type for type-safe locale handling
```typescript
export type Locale = (typeof locales)[number];
```
4. **Display Names** - Human-readable language names
```typescript
export const localeNames: Record<Locale, string> = {
de: 'Deutsch',
en: 'English',
sr: 'Srpski',
};
```
5. **Locale Flags** - Emoji flags for UI
```typescript
export const localeFlags: Record<Locale, string> = {
de: '🇩🇪',
en: '🇬🇧',
sr: '🇷🇸',
};
```
6. **Extended Metadata** - SEO and regional data
```typescript
export const localeMetadata: Record<Locale, {
language: string; // ISO 639-1 code (e.g., 'de')
region: string; // ISO 3166-1 code (e.g., 'DE')
hreflang: string; // For <link rel="alternate"> tags
ogLocale: string; // For Open Graph meta tags
script: string; // Writing system (e.g., 'Latn')
territory: string; // Full country name
currency: string; // ISO 4217 code (e.g., 'EUR')
}> = {
de: {
language: 'de',
region: 'DE',
hreflang: 'de-DE',
ogLocale: 'de_DE',
script: 'Latn',
territory: 'Germany',
currency: 'EUR',
},
// ... en, sr
};
```
7. **SEO Keywords** - Localized keywords for each language
```typescript
export const seoKeywords: Record<Locale, string[]> = {
de: ['Fullstack Developer', 'KI Entwickler', 'Köln', ...],
en: ['AI Developer', 'Remote Developer Europe', 'London', ...],
sr: ['AI Programer Srbija', 'Beograd', 'Novi Sad', ...],
};
```
**When to modify this file:**
- ✅ Adding a new language
- ✅ Updating SEO keywords
- ✅ Changing metadata for Open Graph or hreflang
- ❌ Adding translation strings (use `src/messages/` instead)
---
##### `request.ts` - next-intl Integration
**Location:** `src/i18n/request.ts`
This file integrates the translation system with next-intl and handles runtime locale resolution.
**What it does:**
```typescript
import { getRequestConfig } from 'next-intl/server';
import { locales, type Locale } from './config';
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
// Validate locale and fallback to default if invalid
if (!locale || !locales.includes(locale as Locale)) {
locale = 'de';
}
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
};
});
```
**Key responsibilities:**
1. **Locale Validation** - Ensures only valid locales are used
2. **Fallback Logic** - Defaults to German (`de`) if locale is invalid or missing
3. **Dynamic Import** - Loads the appropriate translation JSON file at runtime
4. **Type Safety** - Uses the `Locale` type from `config.ts` for validation
**Flow:**
1. Receives `requestLocale` from next-intl (extracted from URL)
2. Validates against the `locales` array
3. Falls back to `de` if validation fails
4. Dynamically imports the corresponding JSON file from `src/messages/`
5. Returns locale and messages to next-intl
**When to modify this file:**
- ✅ Changing fallback locale logic
- ✅ Adding custom locale resolution rules
- ✅ Implementing locale persistence (cookies, etc.)
- ❌ Most use cases don't require changes
---
#### `src/messages/` - Active Translation Files ✅
**Location:** `src/messages/`
This directory contains the **active, production-ready** translation files in JSON format.
**Files:**
- `de.json` - German translations (default)
- `en.json` - English translations
- `sr.json` - Serbian translations
**Why JSON?**
- ✅ Simple, human-readable format
- ✅ Easy to edit without TypeScript knowledge
- ✅ Smaller bundle size (dynamic imports)
- ✅ Better IDE support for JSON
- ✅ Easier integration with translation tools
**Structure:**
Each file contains nested JSON objects organized by namespace:
```json
{
"meta": {
"site": { "name": "...", "title": "..." },
"author": { "name": "...", "role": "..." },
"social": { "linkedin": "...", "github": "..." }
},
"common": {
"nav": { "home": "...", "about": "..." },
"actions": { "readMore": "...", "submit": "..." }
},
"pages": {
"home": { "hero": { "title": "..." } },
"about": { "title": "..." }
}
}
```
**Best Practices:**
1. **Keep keys synchronized** - All three files should have identical structure
2. **Use nested namespaces** - Organize by feature or page (e.g., `common`, `pages.home`)
3. **Descriptive keys** - Use semantic names like `hero.title` instead of `text1`
4. **No hardcoded text** - All user-facing text should be in these files
**When to modify these files:**
- ✅ Adding new translation keys
- ✅ Updating existing translations
- ✅ Fixing typos or improving wording
- ✅ Adding new pages or features
---
#### `src/i18n/locales-old/` - Legacy System ⚠️ DEPRECATED
**Location:** `src/i18n/locales-old/`
This directory contains **deprecated** TypeScript-based translations from a previous implementation.
**Files:**
- `de.ts` - Old German translations
- `en.ts` - Old English translations
- `sr.ts` - Old Serbian translations
**⚠️ IMPORTANT:** **Do NOT use these files!**
**Why it exists:**
- These files were part of an older TypeScript-based translation system
- Kept temporarily during migration for reference
- **Not loaded by the application** - they are completely inactive
**Why the migration happened:**
- JSON is simpler and more maintainable than TypeScript objects
- Better performance with dynamic imports
- Easier for non-developers to edit translations
- Better tooling support for JSON translation files
**What to do:**
- ❌ Do NOT add new keys here
- ❌ Do NOT reference these files in code
- ✅ Use `src/messages/*.json` for all translations
- ✅ These files can be safely deleted once migration is verified complete
---
#### File Relationships
```mermaid
graph TD
A[config.ts] -->|Defines locales| B[request.ts]
B -->|Loads messages| C[messages/*.json]
B -->|Validates locale| A
D[Components] -->|useTranslations| C
E[locales-old/*] -.->|DEPRECATED| F[Not Used]
```
**Summary:**
| Directory/File | Status | Purpose | When to Edit |
|----------------|--------|---------|--------------|
| `src/i18n/config.ts` | ✅ Active | Locale definitions & metadata | Adding locales, SEO keywords |
| `src/i18n/request.ts` | ✅ Active | next-intl integration | Rarely (fallback logic) |
| `src/messages/*.json` | ✅ Active | Translation strings | Frequently (new translations) |
| `src/i18n/locales-old/` | ⚠️ Deprecated | Legacy TypeScript translations | Never (can be deleted) |
---
### 🔑 Adding Translation Keys
> Step-by-step guide to adding and managing translations
>
> **Topics covered:**
> - JSON structure and namespaces
> - Nested translation keys
> - Adding keys across all locales
> - Code examples using `useTranslations()`
> - Best practices for key naming
### ⚙️ Configuration & Setup
> Understanding request.ts and next-intl integration
>
> **Topics covered:**
> - `getRequestConfig()` function
> - Locale validation and fallback logic
> - Dynamic message loading
> - `createNextIntlPlugin()` in next.config.ts
> - Server-side vs. client-side translations
### 🌐 Locale Configuration & Metadata
> Detailed breakdown of locale settings and SEO data
>
> **Topics covered:**
> - Locales array and default locale
> - Locale names and flags
> - Metadata for hreflang and Open Graph
> - SEO keywords per locale
> - Currency and territory settings
### 🔍 SEO & Hreflang Implementation
> How multilingual SEO is implemented
>
> **Topics covered:**
> - Hreflang tags in layout.tsx
> - alternates.languages configuration
> - Content-Language HTTP headers
> - Localized meta tags and Open Graph
> - Sitemap generation for multiple locales
### 📦 Legacy Migration (locales-old)
> Understanding the deprecated TypeScript-based translation system
>
> **Topics covered:**
> - What locales-old contains
> - Why the migration happened
> - Do NOT use these files
> - Migration from TypeScript to JSON
> - Safe removal considerations
### 🛤️ Routing & URL Structure
> How locale-based routing works with Next.js App Router
>
> **Topics covered:**
> - `[locale]` dynamic route segment
> - URL patterns: `/de/`, `/en/`, `/sr/`
> - Locale detection in middleware
> - `generateStaticParams()` for static generation
> - Locale switching and redirects
### 💡 Usage Examples & Best Practices
> Practical examples and common patterns
>
> **Topics covered:**
> - `useTranslations()` hook patterns
> - Accessing nested keys
> - Formatting dates and numbers
> - Pluralization (if implemented)
> - Common pitfalls and solutions
> - Performance considerations
## Architecture Overview
```
Portfolio i18n System
├── Configuration Layer
│ ├── src/i18n/config.ts # Locale definitions & metadata
│ └── src/i18n/request.ts # next-intl integration
├── Translation Files (Active)
│ ├── src/messages/de.json # German translations
│ ├── src/messages/en.json # English translations
│ └── src/messages/sr.json # Serbian translations
├── Legacy System (Deprecated)
│ └── src/i18n/locales-old/ # Old TypeScript translations
├── Routing Layer
│ ├── src/app/[locale]/layout.tsx # Locale-based layout
│ ├── src/middleware.ts # Locale detection
│ └── next.config.ts # createNextIntlPlugin
└── Application Layer
└── Components using useTranslations()
```
## Translation File Structure
Current structure of active translation files:
```json
{
"meta": {
"site": { ... },
"author": { ... },
"social": { ... }
},
"common": {
"siteTitle": "...",
"nav": { ... },
"actions": { ... },
"errors": { ... },
"cookies": { ... }
},
"home": { ... },
"about": { ... },
"portfolio": { ... },
"blog": { ... },
"contact": { ... }
}
```
## Supported Locales
| Locale | Language | hreflang | OG Locale | Currency | Region |
|--------|----------|----------|-----------|----------|--------|
| `de` | Deutsch | de-DE | de_DE | EUR | Germany |
| `en` | English | en-US | en_US | USD | United States |
| `sr` | Srpski | sr-RS | sr_RS | RSD | Serbia |
## Important Notes
⚠️ **Legacy System**: The `src/i18n/locales-old/` directory contains deprecated TypeScript-based translations from a previous implementation. **Do NOT use these files.** All new translations should go in `src/messages/*.json`.
✅ **Active System**: Use only the JSON files in `src/messages/` for all translation work.
🔒 **Type Safety**: The locale type is derived from the `locales` array in `config.ts`, ensuring compile-time validation of locale codes.
🌍 **Default Locale**: German (`de`) is the default locale. Invalid or missing locale parameters will fallback to German.
## Getting Help
- Check specific sections above for detailed topics
- Review code examples in the pattern files
- Examine existing translation keys in `src/messages/de.json`
- Look at component usage in `src/app/[locale]/page.tsx`
---
**Framework:** next-intl + Next.js 15 App Router
**Default Locale:** German (de)
**Supported Locales:** de, en, sr
**Translation Format:** JSON