Setup Guide
Configure internationalization in your MakerKit application with middleware, locales, and translation files.
This guide walks through adding a new language to your MakerKit application. The i18n system is already configured out of the box with English; you just need to enable additional locales.
Quick Setup (Add a New Language)
Adding Spanish support takes three steps:
1. Enable the Locale
Edit packages/i18n/src/locales.tsx:
import { defaultLocale } from './default-locale';export const locales: string[] = [ defaultLocale, 'es', // Spanish - uncomment or add this // 'fr', // French // 'de', // German];2. Create Translation Files
Copy the English translations and translate them:
# Create the Spanish locale foldermkdir -p apps/web/i18n/messages/es# Copy all English files as templatescp apps/web/i18n/messages/en/*.json apps/web/i18n/messages/es/Then edit each JSON file in apps/web/i18n/messages/es/ to add Spanish translations.
3. Restart the Dev Server
pnpm devVisit /es/dashboard to see your Spanish translations. The language selector will automatically appear in user settings when multiple locales are configured.
Configuration Files Explained
Middleware (apps/web/proxy.ts)
The middleware handles locale detection and routing:
import createNextIntlMiddleware from 'next-intl/middleware';import { routing } from '@kit/i18n/routing';async function proxy(request: NextRequest) { // Create the i18n middleware with your routing config const handleI18nRouting = createNextIntlMiddleware(routing); // Process the request const response = handleI18nRouting(request); // ... additional middleware logic (auth, CSP, etc.) return response;}The middleware:
- Detects locale from URL, cookies, or
Accept-Languageheader - Redirects to the localized URL when needed
- Sets the
NEXT_LOCALEcookie for subsequent requests
Request Configuration (apps/web/i18n/request.ts)
This file defines which namespaces to load and how:
import { getRequestConfig } from 'next-intl/server';import { routing } from '@kit/i18n/routing';// Define your translation namespacesconst namespaces = [ 'common', 'auth', 'account', 'organizations', 'billing', 'marketing', 'settings', 'goodbye', 'errors',] as const;export default getRequestConfig(async ({ requestLocale }) => { let locale = await requestLocale; // Validate locale, fallback to default if (!locale || !routing.locales.includes(locale as never)) { locale = routing.defaultLocale; } // Load all namespace files const messages = await loadMessages(locale); return { locale, messages, timeZone: 'UTC', // Error handling for missing translations onError(error) { if (process.env.NODE_ENV === 'development') { console.warn(`[Dev Only] i18n error: ${error.message}`); } }, getMessageFallback(info) { return info.key; // Return the key as fallback }, };});Root Layout (apps/web/app/[locale]/layout.tsx)
The layout loads messages and provides them to the app:
import { hasLocale } from 'next-intl';import { getMessages } from 'next-intl/server';import { notFound } from 'next/navigation';import { RootProviders } from '@components/root-providers';import { routing } from '@kit/i18n/routing';export default async function LocaleLayout({ children, params }) { const { locale } = await params; // Validate locale if (!hasLocale(routing.locales, locale)) { notFound(); } // Load all messages for this locale const messages = await getMessages({ locale }); return ( <html lang={locale}> <body> <RootProviders locale={locale} messages={messages}> {children} </RootProviders> </body> </html> );}Environment Variables
NEXT_PUBLIC_DEFAULT_LOCALE
Set the default locale (used when no locale is detected):
NEXT_PUBLIC_DEFAULT_LOCALE=enIf not set, defaults to 'en'.
Changing the Default Locale
To make Spanish the default:
First, update the environment variable:
.env
NEXT_PUBLIC_DEFAULT_LOCALE=esEnsure Spanish is first in your locales array:
packages/i18n/src/locales.tsx
export const locales: string[] = [ defaultLocale, // Now 'es' from env 'en', 'fr',];Now /dashboard serves Spanish, and /en/dashboard serves English.
Locale Detection Order
The middleware detects locale in this order:
- URL path -
/es/dashboardexplicitly requests Spanish - Cookie -
NEXT_LOCALEcookie from previous preference - Accept-Language header - Browser's language preference
- Default locale - Fallback from
NEXT_PUBLIC_DEFAULT_LOCALE
Disabling Auto-Detection
If you want to disable browser-based detection:
export const routing = defineRouting({ locales, defaultLocale, localePrefix: 'as-needed', localeDetection: false, // Disable auto-detection});With detection disabled, users always start on the default locale unless they explicitly navigate to a prefixed URL.
TypeScript Configuration
For type-safe translations, create a declaration file:
import type en from '@/i18n/messages/en/common.json';type Messages = typeof en;declare global { interface IntlMessages extends Messages {}}This enables autocomplete for translation keys in your IDE.
Common Setup Issues
Missing Translations Warning
If you see warnings about missing translations:
[Dev Only] i18n error: Missing message: auth.newKeyAdd the missing key to all locale files:
{ "newKey": "Your translation here"}Locale Not Found (404)
If /es/dashboard returns 404:
- Check that
'es'is in yourlocalesarray - Verify the middleware matcher doesn't exclude the path
- Ensure you restarted the dev server after adding the locale
Middleware Not Running
Check the matcher in apps/web/proxy.ts:
export const config = { matcher: [ '/((?!_next/static|_next/image|images|api/*).*)', ],};Add any paths that should be excluded from i18n routing.
Translations Not Updating
In development, translation files are cached. Try:
- Hard refresh (Cmd+Shift+R)
- Clear
.nextcache:rm -rf .next - Restart the dev server
Production Checklist
Before deploying a multilingual app:
- [ ] All translation files exist for each locale
- [ ] No missing translation keys (check dev console)
- [ ]
NEXT_PUBLIC_DEFAULT_LOCALEis set correctly - [ ] Email templates have translations (see Email Translations)
- [ ] Language selector is visible in settings
- [ ] SEO metadata is translated (titles, descriptions)
- [ ] Date/number formatting uses locale-aware APIs
Frequently Asked Questions
How do I change the default language?
Why isn't my new locale working?
How do I disable automatic locale detection?
Where is the i18n middleware configured?
How do I get TypeScript autocomplete for translation keys?
Previous: Overview | Next: Using Translations