i18n Integration

All landing components use next-intl for translations.

Setup

Text is defined in locale files at src/locales/:

src/locales/
├── en.json    # English (default)
├── de.json    # German
└── fr.json    # French

Translation Structure

Landing page translations are nested under landing:

{
  "landing": {
    "navbar": {
      "features": "Features",
      "pricing": "Pricing",
      "faq": "FAQ",
      "signIn": "Sign In",
      "getStarted": "Get Started"
    },
    "hero": {
      "title": "Build your app faster with {brand}",
      "subtitle": "Production-ready template with auth, billing, and more.",
      "primaryCta": "Get Started Free",
      "secondaryCta": "View Demo"
    },
    "features": {
      "sectionTitle": "Everything you need",
      "sectionSubtitle": "All the features to launch your app",
      "authentication": {
        "title": "Authentication",
        "description": "JWT-based auth with refresh tokens"
      }
    },
    "pricing": {
      "title": "Simple pricing",
      "subtitle": "No hidden fees"
    },
    "faq": {
      "title": "FAQ",
      "q1": {
        "question": "What is Starterbase?",
        "answer": "A production-ready app template..."
      }
    }
  }
}

Using Translations

Components use the useTranslations hook:

import { useTranslations } from 'next-intl';

export default function Hero() {
  const t = useTranslations('landing.hero');

  return (
    <h1>{t('title', { brand: 'Starterbase' })}</h1>
  );
}

With Interpolation

Pass variables to translations:

// en.json: "title": "Build your app faster with {brand}"
{t('title', { brand: 'Starterbase' })}

With Pluralization

{
  "users": "{count, plural, =0 {No users} =1 {One user} other {# users}}"
}
{t('users', { count: 10 })} // "10 users"

Nested Namespaces

For deeply nested translations:

// Access landing.features.authentication.title
const t = useTranslations('landing.features');
{t('authentication.title')}

// Or use the full path
const t = useTranslations('landing');
{t('features.authentication.title')}

Adding New Translations

  1. Add keys to all locale files:
// en.json
{
  "landing": {
    "newSection": {
      "title": "New Section",
      "description": "Description in English"
    }
  }
}

// de.json
{
  "landing": {
    "newSection": {
      "title": "Neuer Abschnitt",
      "description": "Beschreibung auf Deutsch"
    }
  }
}
  1. Use in component:
const t = useTranslations('landing.newSection');
<h2>{t('title')}</h2>

Configuration

next.config.js

const withNextIntl = require('next-intl/plugin')();

module.exports = withNextIntl({
  // other config
});

i18n.ts

// src/i18n.ts
import { getRequestConfig } from 'next-intl/server';

export default getRequestConfig(async ({ locale }) => ({
  messages: (await import(`./locales/${locale}.json`)).default,
}));

Troubleshooting

Translation keys showing instead of text

  1. Verify key exists in locale file
  2. Check namespace matches in component
  3. Ensure next-intl is configured

Missing translations

Add fallback handling:

const t = useTranslations('landing.hero');
{t('title', {}, { fallback: 'Default Title' })}

Type Safety

Generate types from your locale files:

// src/types/i18n.d.ts
type Messages = typeof import('../locales/en.json');

declare interface IntlMessages extends Messages {}