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
- 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"
}
}
}
- 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
- Verify key exists in locale file
- Check namespace matches in component
- Ensure
next-intlis 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 {}