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

{t('nav.home')}

{t('siteDescription')}

); } ``` ### 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 ( ); } ``` **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 (

{tHero('title')}

{tHero('subtitle')}

); } ``` ##### 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) => (

{position.role}

{position.company}

{position.period}
)); ``` #### 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 (
{categories.map((category) => ( ))}
); } // 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 (

{t('showing', { start, end, total })}

); } ``` **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 (
{/* Simple text */}

{t('hero.title')}

{/* Raw access for objects/arrays */}
{t.raw('hero.expertise.processAutomation')}
); } ``` #### 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 (
); } ``` ## 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 = { de: 'Deutsch', en: 'English', sr: 'Srpski', }; ``` 5. **Locale Flags** - Emoji flags for UI ```typescript export const localeFlags: Record = { de: 'πŸ‡©πŸ‡ͺ', en: 'πŸ‡¬πŸ‡§', sr: 'πŸ‡·πŸ‡Έ', }; ``` 6. **Extended Metadata** - SEO and regional data ```typescript export const localeMetadata: Record 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 = { 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