Quick Answer
To use react-intl with Next.js Pages Router: Install react-intl, create ICU message files, wrap _app.tsx with IntlProvider, load messages in getStaticProps/getServerSideProps, and use FormattedMessage or useIntl for translations.
Prerequisites
- Next.js with Pages Router
- Node.js 18+
- Basic understanding of ICU Message Format
Project Setup
Step 1: Install Dependencies
Terminalnpm install react-intl
Step 2: Configure Next.js i18n
next.config.js:
JavaScript1module.exports = { 2 reactStrictMode: true, 3 i18n: { 4 locales: ['en', 'es', 'fr'], 5 defaultLocale: 'en', 6 }, 7};
Directory Structure
├── lang/
│ ├── en.json
│ ├── es.json
│ └── fr.json
├── lib/
│ └── i18n.ts
├── pages/
│ ├── _app.tsx
│ └── index.tsx
└── next.config.js
Step 3: Create Message Files
lang/en.json:
JSON1{ 2 "app.title": "Welcome to My App", 3 "app.greeting": "Hello, {name}!", 4 "app.items": "{count, plural, =0 {No items} one {# item} other {# items}}", 5 "nav.home": "Home", 6 "nav.about": "About" 7}
Step 4: Create i18n Utilities
lib/i18n.ts:
TypeScript1export const locales = ['en', 'es', 'fr'] as const; 2export type Locale = (typeof locales)[number]; 3 4export const localeNames = { 5 en: 'English', 6 es: 'Español', 7 fr: 'Français', 8}; 9 10export async function loadMessages(locale: string) { 11 try { 12 return (await import(`../lang/\${locale}.json`)).default; 13 } catch { 14 return (await import('../lang/en.json')).default; 15 } 16}
Step 5: Set Up _app.tsx
pages/_app.tsx:
TSX1import type { AppProps } from 'next/app'; 2import { IntlProvider } from 'react-intl'; 3import { useRouter } from 'next/router'; 4 5export default function App({ Component, pageProps }: AppProps) { 6 const router = useRouter(); 7 const locale = router.locale || 'en'; 8 9 return ( 10 <IntlProvider 11 locale={locale} 12 messages={pageProps.messages || {}} 13 onError={(err) => { 14 if (err.code === 'MISSING_TRANSLATION') { 15 console.warn(err.message); 16 return; 17 } 18 throw err; 19 }} 20 > 21 <Component {...pageProps} /> 22 </IntlProvider> 23 ); 24}
Step 6: Create Pages
pages/index.tsx:
TSX1import { GetStaticProps } from 'next'; 2import { FormattedMessage, useIntl } from 'react-intl'; 3import { loadMessages } from '../lib/i18n'; 4import LanguageSwitcher from '../components/LanguageSwitcher'; 5 6export default function Home() { 7 const intl = useIntl(); 8 9 return ( 10 <main> 11 <h1> 12 <FormattedMessage id="app.title" /> 13 </h1> 14 15 <p> 16 <FormattedMessage id="app.greeting" values={{ name: 'World' }} /> 17 </p> 18 19 <p> 20 <FormattedMessage id="app.items" values={{ count: 5 }} /> 21 </p> 22 23 <LanguageSwitcher /> 24 </main> 25 ); 26} 27 28export const getStaticProps: GetStaticProps = async ({ locale }) => { 29 const messages = await loadMessages(locale || 'en'); 30 return { 31 props: { messages }, 32 }; 33};
Step 7: Language Switcher
components/LanguageSwitcher.tsx:
TSX1import Link from 'next/link'; 2import { useRouter } from 'next/router'; 3import { localeNames, Locale } from '../lib/i18n'; 4 5export default function LanguageSwitcher() { 6 const router = useRouter(); 7 const { locales, locale: currentLocale, asPath } = router; 8 9 return ( 10 <div> 11 {locales?.map((locale) => ( 12 <Link 13 key={locale} 14 href={asPath} 15 locale={locale} 16 style={{ 17 padding: '0.5rem', 18 border: currentLocale === locale ? '2px solid blue' : '1px solid gray', 19 margin: '0.25rem', 20 }} 21 > 22 {localeNames[locale as Locale]} 23 </Link> 24 ))} 25 </div> 26 ); 27}
ICU Message Format Features
Pluralization
JSON{ "notifications": "{count, plural, =0 {No notifications} one {# notification} other {# notifications}}" }
Select
JSON{ "status": "{gender, select, male {He} female {She} other {They}} is online" }
Built-in Formatters
TSX1import { FormattedNumber, FormattedDate, FormattedRelativeTime } from 'react-intl'; 2 3<FormattedNumber value={1234.56} style="currency" currency="EUR" /> 4<FormattedDate value={new Date()} dateStyle="full" /> 5<FormattedRelativeTime value={-2} unit="day" />
Rich Text with Tags
JSON{ "terms": "I agree to the <link>Terms of Service</link>" }
TSX1<FormattedMessage 2 id="terms" 3 values={{ 4 link: (chunks) => <a href="/terms">{chunks}</a>, 5 }} 6/>
TypeScript Support
types/intl.d.ts:
TypeScript1import en from '../lang/en.json'; 2 3declare global { 4 namespace FormatjsIntl { 5 interface Message { 6 ids: keyof typeof en; 7 } 8 } 9}
Scaling with IntlPull
Terminal1npx @intlpullhq/cli init 2npx @intlpullhq/cli extract --format icu 3npx @intlpullhq/cli translate --all 4npx @intlpullhq/cli download --output ./lang
