Type-safe Internationalization in Next.js using next-internationalization library

Introduction

In software development, internationalization is the procedure in which a product is designed and developed to accommodate different cultures, regions, and languages. Internationalization is often abbreviated as i18n.

Internationalization not only handles language support but also, currency conversion, text translation, and number formats.

Internalization is not to be confused with localization.

Why should you care about internationalization as a web developer?

  1. User Experience best practice.
  2. Reach a wider audience in different regions.

Next.js is a React.js web framework that comes with features such as data fetching, built-in optimization, server actions, and routing including i18n routing.

In this article, we will use the next-internationalization library to see how we can achieve i18n routing, content translation, and date conversion.

Installation

Bootstrap a new next.js application with Typescript and Tailwind CSS. I am using pnpm but any other package manager of your choice will do just fine.

pnpx create-next-app@latest

Configure your Next.js project to use Typescript, Tailwind CSS, and App directory.

What is your project named? frontend-masters-i18n
Would you like to use TypeScript? Yes
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? Yes
Would you like to use the `src/` directory? No
Would you like to use App Router? (recommended) Yes
Would you like to customize the default import alias? No

Install next-international package.

pnpm install next-international

Configuration

create a server.ts and a client.ts files in your locale directory

//server.ts
import { createI18nServer } from 'next-international/server'

export const { getI18n, getScopedI18n, getStaticParams } = createI18nServer({
  en: () => import('./en'),

  fr: () => import('./fr'),

  es: () => import('./es'),
})

This client script will be useful for changing the locale as we will see in the header component

//client.ts
import { createI18nClient } from 'next-international/client'

export const { useCurrentLocale, useChangeLocale } = createI18nClient({
  en: () => import('./en'),

  fr: () => import('./fr'),

  es: () => import('./es'),
})

Create the following locale files for each locale.

//en.ts
export default {

main: {

title: 'The Wonders of the Web',

paragraph:

'The internet, a complex network of interconnected digital spaces, stands as an extraordinary accomplishment of humanity. It provides us with endless opportunities to learn, connect, and access information effortlessly.',

conclusion: 'The web is great. The web makes lives better,

},

} as const;
//es.ts
export default {
  main: {
    title: 'Las maravillas de la web',

    paragraph:
      'Internet, una red compleja de espacios digitales interconectados, constituye un logro extraordinario de la humanidad. Nos brinda infinitas oportunidades para aprender, conectarnos y acceder a información sin esfuerzo.',

    conclusion: 'La web es genial. La web mejora la vida',
  },
} as const
//fr.ts
export default {

main: {

title: 'Les merveilles du Web',

paragraph:

'Internet, un réseau complexe d'espaces numériques interconnectés, constitue une réalisation extraordinaire de l'humanité. Cela nous offre des opportunités infinies d'apprendre, de nous connecter et d'accéder à l'information sans effort.',

conclusion: 'Le Web est génial. Le Web rend la vie meilleure',

},

} as const;

Create the following middleware.ts file in the root directory(app dir).

This code sets an i18n middleware that handles i18n of content based on the requested locale. It defines three supported locales, default locale as English(en), middleware function, and configuration for route matching. You can learn more about locale naming conventions from this IBM resource.

import { createI18nMiddleware } from 'next-international/middleware'

import { NextRequest } from 'next/server'

const I18nMiddleware = createI18nMiddleware({
  locales: ['en', 'fr', 'es'],

  defaultLocale: 'en',
})

export function middleware(request: NextRequest) {
  return I18nMiddleware(request)
}

export const config = {
  matcher: ['/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)'],
}

Demo

Create a header.tsx file in your app directory and import it into your layout. tsx file. In a real-world scenario, you would a select component in your header or footer instead of buttons. An example of a select component from shadcnUI.

'use client';

import { useChangeLocale, useCurrentLocale } from '../locales/client';

export function Header() {

const currentLocale = useCurrentLocale();

const changeLocale = useChangeLocale();

return (

<header>

<p>Current locale: {currentLocale}</p>

<hr />

<button

type='button'

onClick={() => changeLocale('en')}>

EN

</button>

<button

type='button'

onClick={() => changeLocale('fr')}>

FR

</button>

<button

type='button'

onClick={() => changeLocale('es')}>

ES

</button>

</header>

);

}

Now Let us create a demo page with a title section, paragraph section, conclusion section, and header section that will be used to change the locale.

import { getI18n } from "../../locales/server";

export default async function Page() {

const data = await getI18n();

return (

<div>

<main className='text-gray-700'>

<h1 className=' '>{data('main.title')}</h1>

<p>{data('main.paragraph')}</p>

<p>{data('main.conclusion')}</p>

</main>

</div>

);

}

Conclusion

Run the demo and see what you get. This is how you achieve type-safe i18n routing in Next.js using next-international. Let me know what your thoughts are below.