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,
},
};
locales: Array of supported language codes.
defaultLocale: Fallback when no locale is specified.
localeDetection: Automatically detects user’s locale via headers or cookies.
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
hreflang tags: Inform search engines of alternate language URLs.
<Head> <link rel="alternate" hrefLang="en" href="https://example.com/en" /> <link rel="alternate" hrefLang="es" href="https://example.com/es" /> </Head>
lang attribute: Set
<html lang="${locale}">
in_document.js
:// pages/_document.js import Document, { Html, Head, Main, NextScript } from 'next/document'; class MyDocument extends Document { render() { return ( <Html lang={this.props.__NEXT_DATA__.locale}> <Head /> <body> <Main /> <NextScript /> </body> </Html> ); } } export default MyDocument;
Right-to-Left (RTL) support: Add conditional CSS or Tailwind utilities to support RTL languages like Arabic:
[dir="rtl"] { direction: rtl; text-align: right; }
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.