Go Back

Apr 17, 2025

Internationalization (i18n) and Localization

we’ll explore Next.js’s built-in i18n support, external libraries, content translation workflows, and best practices for localization.

Internationalization (i18n) and Localization

1. Built-In Next.js i18n Routing

Next.js supports internationalized routing out of the box. Configure your locales in next.config.js:

// next.config.js
module.exports = {
  i18n: {
    locales: ['en', 'es', 'fr'],
    defaultLocale: 'en',
    localeDetection: true,
  },
};

With this config, Next.js generates routes like /es/about and /fr/about for a page defined in pages/about.js.

2. Translating Static Content

For static pages, extract strings into JSON resource files:

/locales
  /en
    common.json
  /es
    common.json

Example common.json:

{
  "siteTitle": "Welcome to My Site",
  "aboutText": "This is a Next.js site."
}

Use a lightweight helper, e.g., next-intl:

npm install next-intl

Wrap your app:

// pages/_app.js
import { NextIntlProvider } from 'next-intl';

export default function App({ Component, pageProps }) {
  return (
    <NextIntlProvider messages={pageProps.messages}>
      <Component {...pageProps} />
    </NextIntlProvider>
  );
}

Load messages in getStaticProps:

export async function getStaticProps({ locale }) {
  return {
    props: {
      messages: (await import(`../locales/${locale}/common.json`)).default,
    },
  };
}

Consume translations:

import { useTranslations } from 'next-intl';

export default function Home() {
  const t = useTranslations('common');
  return <h1>{t('siteTitle')}</h1>;
}

3. Dynamic Content and API Data

When fetching content from a headless CMS (e.g., Strapi, Contentful), request locale-specific data:

export async function getStaticProps({ locale }) {
  const res = await fetch(`https://api.example.com/posts?locale=${locale}`);
  const posts = await res.json();
  return { props: { posts } };
}

Dynamic routes also adapt: pages/posts/[slug].js will map to /en/posts/hello-world and /es/posts/hola-mundo automatically.

4. Locale Switcher Component

Provide users an easy way to switch languages:

import { useRouter } from 'next/router';
import Link from 'next/link';

export default function LocaleSwitcher() {
  const { locales, locale, asPath } = useRouter();
  return (
    <div>
      {locales.map((loc) => (
        <Link key={loc} href={asPath} locale={loc}>
          <a style={{ marginRight: 8, fontWeight: loc === locale ? 'bold' : 'normal' }}>
            {loc.toUpperCase()}
          </a>
        </Link>
      ))}
    </div>
  );
}

Place LocaleSwitcher in your layout or header for global access.

5. Formatting Dates, Numbers, and Plurals

Leverage the built-in Intl API:

export default function PostDate({ date }) {
  const formatted = new Intl.DateTimeFormat(undefined, { dateStyle: 'long' }).format(new Date(date));
  return <time dateTime={date}>{formatted}</time>;
}

For plurals and complex formatting, libraries like react-intl offer rich APIs:

import { FormattedMessage } from 'react-intl';

<FormattedMessage
  id="comments"
  defaultMessage="{count, plural, =0 {No comments} one {# comment} other {# comments}}"
  values={{ count: comments.length }}
/>

6. SEO and Accessibility Considerations

With these patterns, your Next.js application will delight users worldwide by delivering localized content, proper formatting, and seamless language switching. In Next Article, we’ll cover deploying and scaling your internationalized app globally.