Next.js
Generate translated message files in the build step and serve them from Server Components. Works with the App Router and the Pages Router, because the output is just JSON.
1. Add the translate step
npm install -D @shipi18n/cli @anthropic-ai/sdk
{
"scripts": {
"i18n": "shipi18n translate messages/en.json -t es,fr,de --incremental",
"build": "npm run i18n && next build"
}
}Set ANTHROPIC_API_KEY in your build environment — on Vercel that is a project environment variable, not something bundled into the app.
2. Load messages per locale
// app/[locale]/layout.jsx
export default async function LocaleLayout({ children, params: { locale } }) {
const messages = (await import(`../../messages/${locale}.json`)).default
return (
<html lang={locale}>
<body>
<Provider locale={locale} messages={messages}>{children}</Provider>
</body>
</html>
)
}3. Pre-render every locale
export function generateStaticParams() {
return ['en', 'es', 'fr', 'de'].map((locale) => ({ locale }))
}Middleware for locale detection
// middleware.js
import { NextResponse } from 'next/server'
const locales = ['en', 'es', 'fr', 'de']
export function middleware(request) {
const { pathname } = request.nextUrl
if (locales.some((l) => pathname.startsWith(`/${l}`))) return
const preferred = request.headers.get('accept-language')?.split(',')[0]?.split('-')[0]
const locale = locales.includes(preferred) ? preferred : 'en'
return NextResponse.redirect(new URL(`/${locale}${pathname}`, request.url))
}
export const config = { matcher: ['/((?!_next|api|.*\..*).*)'] }Tips
- Commit generated messages so a deploy never makes a model call.
- A missing module for one locale means that language is not in your
-tlist yet. - The key is only ever used during
next build; nothing reaches the client bundle.