Files
portfolio/DIRECTUS_MIGRATION.md
Copilot 7604e00e0f 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>
2026-01-22 21:25:41 +01:00

4.9 KiB

Directus Integration - Migration Guide

🎯 Overview

This portfolio now has a hybrid i18n system:

  • JSON Files (Primary) → All translations work from messages/*.json files
  • Directus CMS (Optional) → Can override translations dynamically without rebuilds

Important: Directus is optional. The app works perfectly fine without it using JSON fallbacks.

📁 New File Structure

Core Infrastructure

  • lib/directus.ts - REST Client for Directus (uses de-DE, en-US locale codes)
  • lib/i18n-loader.ts - Loads texts with Fallback Chain
  • lib/translations-loader.ts - Batch loader for all sections (cleaned up to match actual usage)
  • types/translations.ts - TypeScript types for all translation objects (fixed to match components)

Components

All component wrappers properly load and pass translations to client components.

🔄 How It Works

Without Directus (Default)

Component → useTranslations("nav") → JSON File (messages/en.json)

With Directus (Optional)

Server Component → getNavTranslations(locale)
                → Try Directus API (de-DE/en-US)
                → If not found: JSON File (de/en)
                → Props to Client Component

🗄️ Directus Setup (Optional)

Only set this up if you want to edit translations through a CMS without rebuilding the app.

1. Environment Variables

Add to .env.local:

DIRECTUS_URL=https://cms.example.com
DIRECTUS_STATIC_TOKEN=your_token_here

If these are not set, the system will skip Directus and use JSON files only.

2. Collection: messages

Create a messages collection in Directus with these fields:

  • key (String, required) - e.g., "nav.home"
  • translations (Translations) - Directus native translations feature
  • Configure languages: en-US and de-DE

Note: Keys use dot notation (nav.home) but locales use dashes (en-US, de-DE).

3. Permissions

Grant Public role read access to messages collection.

📝 Translation Keys

See docs/LOCALE_SYSTEM.md for the complete list of translation keys and their structure.

All keys are organized hierarchically:

  • nav.* - Navigation items
  • home.hero.* - Hero section
  • home.about.* - About section
  • home.projects.* - Projects section
  • home.contact.* - Contact form and info
  • footer.* - Footer content
  • consent.* - Privacy consent banner

🎨 Rich Text Content

For longer content that needs formatting (bold, italic, lists), use the content_pages collection:

Collection: content_pages (Optional)

Fields:

  • slug (String, unique) - e.g., "home-hero"
  • locale (String) - en or de
  • title (String)
  • content (Rich Text or Long Text)

Examples:

  • home-hero - Hero section description
  • home-about - About section content
  • home-contact - Contact intro text

Components fetch these via /api/content/page and render using RichTextClient.

🔍 Fallback Chain

For every translation key, the system searches in this order:

  1. Directus (if configured) in requested locale (e.g., de-DE)
  2. Directus in English fallback (e.g., en-US)
  3. JSON file in requested locale (e.g., messages/de.json)
  4. JSON file in English (e.g., messages/en.json)
  5. Key itself as last resort (e.g., returns "nav.home")

What Was Fixed

Previous issues that have been resolved:

  1. Type mismatches - All translation types now match actual component usage
  2. Unused fields - Removed translation keys that were never used (like hero.greeting, hero.name)
  3. Wrong structure - Fixed AboutTranslations structure (removed fake interests nesting)
  4. Missing keys - Aligned loaders with JSON files and actual component requirements
  5. Confusing comments - Removed misleading comments in translations-loader.ts

🎯 Best Practices

  1. Always maintain JSON files - Even if using Directus, keep JSON files as fallback
  2. Use types - TypeScript types ensure correct usage
  3. Test without Directus - App should work perfectly without CMS configured
  4. Rich text for formatting - Use content_pages for content that needs bold/italic/lists
  5. JSON for UI labels - Use JSON/messages for short UI strings like buttons and labels

🐛 Troubleshooting

Directus not configured

This is normal! The app works fine. All translations come from JSON files.

Want to use Directus?

  1. Set up DIRECTUS_URL and DIRECTUS_STATIC_TOKEN
  2. Create messages collection
  3. Add your translations
  4. They will override JSON values

Translation not showing?

Check in this order:

  1. Does key exist in messages/en.json?
  2. Is the key spelled correctly?
  3. Is component using correct namespace?

📚 Further Reading

  • Complete locale documentation: docs/LOCALE_SYSTEM.md
  • Directus setup checklist: DIRECTUS_CHECKLIST.md
  • Operations guide: docs/OPERATIONS.md