How to Translate a Next.js App: Complete Guide

Learn how to localize your Next.js app using next-intl or next-i18next. This comprehensive guide covers routing, string translation, pluralization, date formatting, and best practices for Ne

  • date icon

    Monday, Nov 17, 2025

How to Translate a Next.js App: Complete Guide

Translating your Next.js app is essential for reaching a global audience. Next.js provides excellent built-in support for internationalization (i18n) through routing and can be enhanced with libraries like next-intl or next-i18next. This guide will walk you through the process of translating a Next.js app.

Understanding Next.js Localization

Next.js supports internationalization through:

  1. Built-in i18n routing - Automatic locale detection and routing
  2. next-intl - Modern library built for Next.js App Router
  3. next-i18next - Popular library for Pages Router

We’ll cover both approaches in this guide.

Method 1: Using next-intl (App Router - Recommended)

next-intl is the recommended solution for Next.js 13+ with App Router.

Step 1: Install Dependencies

npm install next-intl

Or with yarn:

yarn add next-intl

Step 2: Create Translation Files

Create a messages directory in your project root:

messages/en.json (English - default):

{
  "app": {
    "welcome": "Welcome to our app!",
    "title": "My Next.js App"
  },
  "button": {
    "submit": "Submit",
    "cancel": "Cancel",
    "delete": "Delete"
  },
  "error": {
    "network": "Network error. Please try again.",
    "notFound": "Page not found"
  },
  "items": {
    "count": "{count, plural, =0 {No items} one {# item} other {# items}}"
  },
  "user": {
    "greeting": "Hello, {name}! You have {count, plural, =0 {no messages} one {# message} other {# messages}}."
  }
}

messages/es.json (Spanish):

{
  "app": {
    "welcome": "¡Bienvenido a nuestra aplicación!",
    "title": "Mi Aplicación Next.js"
  },
  "button": {
    "submit": "Enviar",
    "cancel": "Cancelar",
    "delete": "Eliminar"
  },
  "error": {
    "network": "Error de red. Por favor, inténtalo de nuevo.",
    "notFound": "Página no encontrada"
  },
  "items": {
    "count": "{count, plural, =0 {No hay elementos} one {# elemento} other {# elementos}}"
  },
  "user": {
    "greeting": "¡Hola, {name}! Tienes {count, plural, =0 {no hay mensajes} one {# mensaje} other {# mensajes}}."
  }
}

messages/fr.json (French):

{
  "app": {
    "welcome": "Bienvenue dans notre application!",
    "title": "Mon Application Next.js"
  },
  "button": {
    "submit": "Soumettre",
    "cancel": "Annuler",
    "delete": "Supprimer"
  },
  "error": {
    "network": "Erreur réseau. Veuillez réessayer.",
    "notFound": "Page non trouvée"
  },
  "items": {
    "count": "{count, plural, =0 {Aucun élément} one {# élément} other {# éléments}}"
  },
  "user": {
    "greeting": "Bonjour, {name}! Vous avez {count, plural, =0 {aucun message} one {# message} other {# messages}}."
  }
}

Step 3: Configure next-intl

Create i18n.ts in your project root:

i18n.ts:

import { getRequestConfig } from 'next-intl/server';
import { notFound } from 'next/navigation';

export const locales = ['en', 'es', 'fr'] as const;
export const defaultLocale = 'en' as const;

export default getRequestConfig(async ({ locale }) => {
  if (!locales.includes(locale as any)) notFound();

  return {
    messages: (await import(`./messages/${locale}.json`)).default
  };
});

Step 4: Update next.config.js

next.config.js:

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

const withNextIntl = createNextIntlPlugin();

/** @type {import('next').NextConfig} */
const nextConfig = {};

module.exports = withNextIntl(nextConfig);

Step 5: Create Middleware

Create middleware.ts in your project root:

middleware.ts:

import createMiddleware from 'next-intl/middleware';
import { locales, defaultLocale } from './i18n';

export default createMiddleware({
  locales,
  defaultLocale,
  localePrefix: 'always' // or 'as-needed'
});

export const config = {
  matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};

Step 6: Update App Router Structure

Restructure your app directory to include [locale]:

app/
  [locale]/
    layout.tsx
    page.tsx
    about/
      page.tsx

app/[locale]/layout.tsx:

import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { locales } from '@/i18n';

export function generateStaticParams() {
  return locales.map((locale) => ({ locale }));
}

export default async function LocaleLayout({
  children,
  params: { locale }
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  if (!locales.includes(locale as any)) {
    notFound();
  }

  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

app/[locale]/page.tsx:

import { useTranslations } from 'next-intl';

export default function HomePage() {
  const t = useTranslations();

  return (
    <div>
      <h1>{t('app.welcome')}</h1>
      <p>{t('app.title')}</p>
    </div>
  );
}

Step 7: Use Translations in Components

Server Components:

import { useTranslations } from 'next-intl';

export default function ServerComponent() {
  const t = useTranslations();
  
  return <h1>{t('app.welcome')}</h1>;
}

Client Components:

'use client';

import { useTranslations } from 'next-intl';

export default function ClientComponent() {
  const t = useTranslations();
  
  return <h1>{t('app.welcome')}</h1>;
}

Step 8: String Interpolation

import { useTranslations } from 'next-intl';

export default function UserGreeting({ name, messageCount }: { name: string; messageCount: number }) {
  const t = useTranslations('user');
  
  return (
    <p>
      {t('greeting', { name, count: messageCount })}
    </p>
  );
}

Step 9: Pluralization

import { useTranslations } from 'next-intl';

export default function ItemCount({ count }: { count: number }) {
  const t = useTranslations('items');
  
  return <p>{t('count', { count })}</p>;
}

Step 10: Format Dates and Numbers

import { useTranslations, useFormatter } from 'next-intl';

export default function DateDisplay({ date }: { date: Date }) {
  const format = useFormatter();
  
  return (
    <p>
      {format.dateTime(date, {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        weekday: 'long'
      })}
    </p>
  );
}

export default function NumberDisplay({ number }: { number: number }) {
  const format = useFormatter();
  
  return (
    <div>
      <p>Number: {format.number(number)}</p>
      <p>Currency: {format.number(number, { style: 'currency', currency: 'USD' })}</p>
    </div>
  );
}

Step 11: Language Switcher

'use client';

import { usePathname, useRouter } from 'next/navigation';
import { useLocale } from 'next-intl';

export default function LanguageSwitcher() {
  const router = useRouter();
  const pathname = usePathname();
  const locale = useLocale();

  const switchLocale = (newLocale: string) => {
    const newPathname = pathname.replace(`/${locale}`, `/${newLocale}`);
    router.push(newPathname);
  };

  return (
    <div>
      <button onClick={() => switchLocale('en')}>English</button>
      <button onClick={() => switchLocale('es')}>Español</button>
      <button onClick={() => switchLocale('fr')}>Français</button>
    </div>
  );
}

Method 2: Using next-i18next (Pages Router)

For Next.js Pages Router, use next-i18next.

Step 1: Install Dependencies

npm install next-i18next react-i18next i18next

Step 2: Create Translation Files

public/locales/en/common.json:

{
  "welcome": "Welcome to our app!",
  "button": {
    "submit": "Submit",
    "cancel": "Cancel"
  }
}

public/locales/es/common.json:

{
  "welcome": "¡Bienvenido a nuestra aplicación!",
  "button": {
    "submit": "Enviar",
    "cancel": "Cancelar"
  }
}

Step 3: Configure next-i18next

next-i18next.config.js:

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es', 'fr'],
  },
  localePath: './public/locales',
};

next.config.js:

const { i18n } = require('./next-i18next.config');

module.exports = {
  i18n,
};

Step 4: Create Custom App

pages/_app.js:

import { appWithTranslation } from 'next-i18next';

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default appWithTranslation(MyApp);

Step 5: Use Translations in Pages

pages/index.js:

import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

export default function HomePage() {
  const { t } = useTranslation('common');

  return <h1>{t('welcome')}</h1>;
}

export async function getStaticProps({ locale }) {
  return {
    props: {
      ...(await serverSideTranslations(locale, ['common'])),
    },
  };
}

Method 3: Built-in Next.js i18n Routing

Next.js has built-in i18n support for Pages Router.

Step 1: Configure next.config.js

next.config.js:

module.exports = {
  i18n: {
    locales: ['en', 'es', 'fr'],
    defaultLocale: 'en',
    localeDetection: true, // Automatically detect user's locale
  },
};

Step 2: Create Translation Files

locales/en.json:

{
  "welcome": "Welcome to our app!"
}

locales/es.json:

{
  "welcome": "¡Bienvenido a nuestra aplicación!"
}

Step 3: Use in Pages

pages/index.js:

import { useRouter } from 'next/router';
import translations from '../locales';

export default function HomePage() {
  const router = useRouter();
  const { locale } = router;
  const t = translations[locale];

  return <h1>{t.welcome}</h1>;
}

Best Practices

1. Use Descriptive Translation Keys

Bad:

{
  "msg1": "Submit"
}

Good:

{
  "button": {
    "submit": "Submit"
  }
}

2. Organize by Feature

{
  "auth": {
    "login": "Login",
    "logout": "Logout"
  },
  "dashboard": {
    "title": "Dashboard"
  }
}

3. Use Namespaces

For large apps, split translations into namespaces:

next-i18next:

export async function getStaticProps({ locale }) {
  return {
    props: {
      ...(await serverSideTranslations(locale, ['common', 'auth', 'dashboard'])),
    },
  };
}

next-intl:

const t = useTranslations('auth');

4. Handle Missing Translations

const t = useTranslations();
const text = t('some.key', { defaultValue: 'Default text' });

5. SEO Considerations

Set proper lang attribute and hreflang tags:

app/[locale]/layout.tsx:

export default function LocaleLayout({
  children,
  params: { locale }
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  return (
    <html lang={locale}>
      <head>
        <link rel="alternate" hrefLang="en" href="/en" />
        <link rel="alternate" hrefLang="es" href="/es" />
        <link rel="alternate" hrefLang="fr" href="/fr" />
      </head>
      <body>{children}</body>
    </html>
  );
}

6. Static Generation with Locales

Generate static pages for all locales:

next-intl:

export function generateStaticParams() {
  return locales.map((locale) => ({ locale }));
}

next-i18next:

export async function getStaticPaths() {
  return {
    paths: locales.map((locale) => ({ params: { locale } })),
    fallback: false,
  };
}

Common Pitfalls

1. Not Configuring Middleware

For App Router with next-intl, always create middleware:

// middleware.ts is required

2. Forgetting Locale in URL Structure

Make sure your routes include locale:

/en/about
/es/about
/fr/about

3. Not Handling Client/Server Components

Use 'use client' for client components:

'use client';
import { useTranslations } from 'next-intl';

4. Hardcoding Strings

Bad:

<h1>Welcome</h1>

Good:

const t = useTranslations();
<h1>{t('app.welcome')}</h1>

Advanced: Dynamic Language Switching

next-intl:

'use client';

import { useRouter, usePathname } from 'next/navigation';
import { useLocale } from 'next-intl';

export function LanguageSwitcher() {
  const router = useRouter();
  const pathname = usePathname();
  const locale = useLocale();

  const changeLocale = (newLocale: string) => {
    const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);
    router.push(newPath);
    router.refresh();
  };

  return (
    <select value={locale} onChange={(e) => changeLocale(e.target.value)}>
      <option value="en">English</option>
      <option value="es">Español</option>
      <option value="fr">Français</option>
    </select>
  );
}

Advanced: RTL Support

import { useLocale } from 'next-intl';
import { useEffect } from 'react';

export default function RTLHandler() {
  const locale = useLocale();
  const isRTL = ['ar', 'he', 'fa'].includes(locale);

  useEffect(() => {
    document.documentElement.dir = isRTL ? 'rtl' : 'ltr';
    document.documentElement.lang = locale;
  }, [locale, isRTL]);

  return null;
}

Advanced: TypeScript Support

types/next-intl.d.ts:

type Messages = typeof import('./messages/en.json');

declare global {
  interface IntlMessages extends Messages {}
}

Conclusion

Localizing your Next.js app can be done through multiple approaches:

  1. next-intl (Recommended for App Router) - Modern, built for Next.js 13+
  2. next-i18next (For Pages Router) - Popular, well-documented
  3. Built-in i18n routing (Pages Router) - Simple but limited

Choose the method that best fits your Next.js version and requirements. By following these practices, you’ll create an app that provides a native experience for users worldwide, significantly expanding your potential user base.

Streamline Your Next.js Localization Workflow

Managing translations for multiple languages can become complex as your app grows. Consider using a translation management platform to:

  • Collaborate with translators
  • Keep translations in sync with your codebase
  • Automate the translation workflow
  • Maintain consistency across all languages
  • Generate translation files automatically
  • Integrate with your CI/CD pipeline

Ready to take your Next.js app global? Explore AZbox’s localization platform and streamline your translation workflow:

View AZbox Plans and Pricing

Blog

Latest Posts

Discover our latest articles and updates.

Why Translations Have Always Been a Problem in Software Development
date icon

Sunday, Dec 21, 2025

Why Translations Have Always Been a Problem in Software Development

For decades, software developers have struggled with translations and localization. What should be a straightforward pro

Read More
Cómo Traducir una App Flutter con Azbox: Guía Completa
date icon

Saturday, Dec 20, 2025

Cómo Traducir una App Flutter con Azbox: Guía Completa

Traducir tu app Flutter es esencial para llegar a una audiencia global. Azbox proporciona un potente SDK de Flutter que

Read More
How to Translate a Flutter App with Azbox: Complete Guide
date icon

Saturday, Dec 20, 2025

How to Translate a Flutter App with Azbox: Complete Guide

Translating your Flutter app is essential for reaching a global audience. Azbox provides a powerful Flutter SDK that sim

Read More
cta-image

Start Global Growth Today

Join hundreds of successful companies already using AZbox to reach customers worldwide. Start with a free trial, no credit card required.

Get Started - It's Free