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
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
-
Open all three locale files:
src/messages/de.jsonsrc/messages/en.jsonsrc/messages/sr.json
-
Add your key to the same namespace in each file:
{
"common": {
"actions": {
"newAction": "Neue Aktion" // de
"newAction": "New Action" // en
"newAction": "Nova Akcija" // sr
}
}
}
- Use it in your component:
const t = useTranslations('common.actions');
return <button>{t('newAction')}</button>;
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:
-
Locales Array - Defines all supported languages
export const locales = ['de', 'en', 'sr'] as const; -
Default Locale - Fallback language
export const defaultLocale = 'de' as const; -
Locale Type - TypeScript type for type-safe locale handling
export type Locale = (typeof locales)[number]; -
Display Names - Human-readable language names
export const localeNames: Record<Locale, string> = { de: 'Deutsch', en: 'English', sr: 'Srpski', }; -
Locale Flags - Emoji flags for UI
export const localeFlags: Record<Locale, string> = { de: '🇩🇪', en: '🇬🇧', sr: '🇷🇸', }; -
Extended Metadata - SEO and regional data
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 }; -
SEO Keywords - Localized keywords for each language
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:
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:
- Locale Validation - Ensures only valid locales are used
- Fallback Logic - Defaults to German (
de) if locale is invalid or missing - Dynamic Import - Loads the appropriate translation JSON file at runtime
- Type Safety - Uses the
Localetype fromconfig.tsfor validation
Flow:
- Receives
requestLocalefrom next-intl (extracted from URL) - Validates against the
localesarray - Falls back to
deif validation fails - Dynamically imports the corresponding JSON file from
src/messages/ - 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 translationssr.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:
{
"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:
- Keep keys synchronized - All three files should have identical structure
- Use nested namespaces - Organize by feature or page (e.g.,
common,pages.home) - Descriptive keys - Use semantic names like
hero.titleinstead oftext1 - 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 translationsen.ts- Old English translationssr.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/*.jsonfor all translations - ✅ These files can be safely deleted once migration is verified complete
File Relationships
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:
{
"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