Skip to main content
shipi18n logo|Documentation

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 -t list yet.
  • The key is only ever used during next build; nothing reaches the client bundle.