Files
Portfolio/docs/i18n

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

  1. Open all three locale files:

    • src/messages/de.json
    • src/messages/en.json
    • src/messages/sr.json
  2. Add your key to the same namespace in each file:

{
  "common": {
    "actions": {
      "newAction": "Neue Aktion"  // de
      "newAction": "New Action"    // en
      "newAction": "Nova Akcija"   // sr
    }
  }
}
  1. 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:

  1. Locales Array - Defines all supported languages

    export const locales = ['de', 'en', 'sr'] as const;
    
  2. Default Locale - Fallback language

    export const defaultLocale = 'de' as const;
    
  3. Locale Type - TypeScript type for type-safe locale handling

    export type Locale = (typeof locales)[number];
    
  4. Display Names - Human-readable language names

    export const localeNames: Record<Locale, string> = {
      de: 'Deutsch',
      en: 'English',
      sr: 'Srpski',
    };
    
  5. Locale Flags - Emoji flags for UI

    export const localeFlags: Record<Locale, string> = {
      de: '🇩🇪',
      en: '🇬🇧',
      sr: '🇷🇸',
    };
    
  6. 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
    };
    
  7. 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:

  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:

{
  "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

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