Step-by-Step Guide to Next.js Internationalization (i18n)

Siddharth Sharma
May 29, 2024
14 min read

A well-internationalized app creates a more inclusive user experience – breaking down language barriers and becoming accessible to a wider range of users worldwide. A popular way to implement i18n for your Next.js application is to leverage its inbuilt features.

In this article, we’ve provided a step-by-step guide to localizing a Next.js app using the same approach. 

However, this process requires handling multiple files, which can quickly become complex as your app scales.

As an alternative solution, we also discuss a popular localization tool called Transifex Native, which you can directly integrate into your code. This tool allows seamless over-the-air updates of all translations in real-time and provides a convenient way to manage localization.

Let’s get started!

Internationalization (i18n) Terminologies You Should Know

Before we get started with the actual process, let’s quickly recap some fundamental internationalization terminologies:

Locale: It refers to a specific combination of language, region, and cultural conventions used to customize the user experience in software applications. 

It includes language codes (such as “en” for English, and “fr” for French), country codes (such as “US” for the United States, and “FR” for France), and additional parameters like date formats, currency symbols, and measurement units.

Routing: In the context of internationalization, routing involves directing users to language-specific versions of a website or application based on their preferences or location. Two common routing strategies are used: Sub-path routing and domain routing.

i18n: Internationalization, often represented by i18n, involves developing software applications adaptable to different languages and regions without requiring code changes. 

This includes separating language-specific elements from the core application logic, allowing for easy integration of multiple languages and cultural conventions.

L10n: Localization, or L10N, is the process of adapting a software application or product to the linguistic, cultural, and technical requirements of a specific locale. 

This involves translating text and multimedia elements, formatting dates, numbers, and currency, and adjusting other user interface aspects to align with local preferences and conventions.

How to Internationalize Your Next.js App?

Since version 10.0.0, Next.js offers native support for internationalized (i18n) routing. With built-in support for defining locales, default locale settings, and domain-specific locales, Next.js automatically handles the routing for you.

When a user accesses the application root (typically /), Next.js automatically detects the preferred locale based on the Accept-Language header and the current domain. 

If a locale different from the default is identified:

Let’s walk through the step-by-step process of internationalizing a Next.js example app.

1. Create a Project

If you haven’t already, let’s start by creating a new Next.js app. Here, we are using “my-app” as an example.

To create a new project, use the following command:

npx create-next-app@latest my-app

This command sets up everything automatically for you based on the following prompts:

Would you like to use TypeScript? [No]
Would you like to use ESLint? [Yes]
Would you like to use Tailwind CSS? [No]
Would you like to use `src/` directory? [Yes]
Would you like to use App Router? (recommended) [Yes]
Would you like to customize the default import alias (@/*)? [No]

Here are two system requirements for the installation:

  • Node.js 18.17 or later required.
  • Windows (including WSL), macOS, or Linux required.

2. Choose Your Routing Approach

Next.js offers two primary routing methods:

  • App Routing (using an app directory): Ideal for complex applications with multiple layouts and server-side components.
  • Page Routing (using a pages directory): Simpler for basic applications with a one-to-one mapping between pages and URLs.

3. Install Dependencies

Once you’ve created the project, move to the project directory using:

cd my-app

Now, install the necessary dependencies for internationalization. For this guide, we’ll use `next-intl` due to its ease of use in Next.js environments. Alternatively, you can also use `react-intl` with your Next.js app.

Use the following command to install dependencies:

npm install next-intl

4. Configure i18n Routing

Create a `next.config.js` file in the root directory of your project if it doesn’t exist already.

Now, add the following configuration to enable i18n routing:

module.exports = {
  i18n: {
    locales: ['en', 'fr'], // Add your supported locales
    defaultLocale: 'en', // Set the default locale
  },
  // ... other Next.js configurations
};

Here, we’re setting the default locale to English (en) and specifying the supported locales – English (en) and French (fr).

If you opt for app routing, create a directory named `app` under your `my-app` project root. This directory will house your app components and layouts. 

You don’t need to create an app directory if you’re using page routing. Simply proceed with the next steps.

Note: Regardless of the routing approach, the `next.config.js` configuration is essential for both to enable i18n routing.

5. Create Translation Files

Create a directory named `locales` under your `my-app` project root.

Now, inside`locales`, create JSON files for each supported language (e.g., `en.json`, `fr.json`). Once done, add your translatable strings as key-value pairs in these JSON files.

// en.json

{

  "welcome": "Welcome to my app!",

  "home": "Home",

  "about": "About"

}

// fr.json

{

  "welcome": "Bienvenue sur mon application!",

  "home": "Accueil",

  "about": "À propos"

}

6. Wrap with appWithTranslation

This step differs slightly depending on your routing method. Let’s discuss both. 

For App Routing:

This approach is ideal for complex applications with nested layouts and server-side components.

  • If you’re using app routing (with an app directory), create a `layout.js` file inside the app.
  • Import appWithTranslation from `next-intl` in `layout.js`.
  • Wrap your main content component with `appWithTranslation` to provide translation context throughout the app.

Here’s an example:

import { appWithTranslation } from 'next-intl';

function RootLayout({ children }) {

  return appWithTranslation(children);

}

export default RootLayout;

For Page Routing:

  • In your main `_app.js file` or a specific page component, import `appWithTranslation` from `next-intl`.
  • Wrap your application content (or the specific page component) with `appWithTranslation`.

Here’s an example of page routing:

import { appWithTranslation } from 'next-intl';

function MyApp({ Component, pageProps }) {

  return appWithTranslation(Component, pageProps);

}

export default MyApp;

7. Use the `useTranslation` Hook

Import `useTranslation` from `next-intl` in your components (app or page components depending on your routing).

Now, you can access translated strings using the `t` function provided by `useTranslation`.

Here’s how you can implement it:

import { useTranslation } from 'next-intl';

function MyComponent() {

  const { t } = useTranslation();

  return (

    <div>

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

      <p>

        <a href="/">

          {t('home')}

        </a>

      </p>

      <p>

        <a href="/about">

          {t('about')}

        </a>

      </p>

    </div>

  );

}

For example, in a component named `HomePage.js`, you may use it this way:

import { useTranslation } from 'next-intl';

function HomePage() {

  const { t } = useTranslation();

  return (

    <div>

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

      <p>

        <a href="/">

          {t('home')}

        </a>

      </p>

      <p>

        <a href="/about">

          {t('about')}

        </a>

      </p>

    </div>

  );

}

export default HomePage;

This code retrieves the translated strings for “welcome,” “home,” and “about” from the active locale’s JSON file and displays them within the component.

8. Handle Server Side Rendering

If you have data fetching on the server using `getStaticProps` or `getServerSideProps`, provide the correct locale during data fetching:

  • For app routing, you might pass the locale as a prop to your components.
  • For page routing, you can use these functions to fetch data with the appropriate locale.

9. Use Advanced Features for Your Next.js App

While the basic setup provides core internationalization functionality, you might consider using `i18n.js` and `middleware.js` files for complex scenarios.

– The `i18n.js` file creates custom logic for detecting the user’s preferred locale. To implement this, create a file named `i18n.js` in your project’s root directory. This file can house the custom i18n logic for complex applications.

Here’s a sample `i18n.js` file. You can provide messages and other options depending on the locale of the user.

import {notFound} from 'next/navigation';

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

// Can be imported from a shared config

const locales = ['en', 'de'];

export default getRequestConfig(async ({locale}) => {

  // Validate that the incoming `locale` parameter is valid

  if (!locales.includes(locale as any)) notFound();

  return {

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

  };

});

– The middleware matches a locale for the request and handles redirects and rewrites accordingly. To implement it, create a file named `middleware.js` in your project’s root directory.

Here’s a sample middleware file:

import createMiddleware from 'next-intl/middleware';

export default createMiddleware({

  // A list of all locales that are supported

  locales: ['en', 'de'],

  // Used when no locale matches

  defaultLocale: 'en'

});

export const config = {

  // Match only internationalized pathnames

  matcher: ['/', '/(de|en)/:path*']

};

Simplify Next.js Internationalization Using Transifex

As discussed, internationalizing your Next.js application can involve managing multiple translation files. This can quickly turn complex when scaling your application or adding more locales. 

A simpler internationalization approach that bypasses the hassle of files is to use Transifex Native. This tool is built upon Next.js’ built-in internationalization features, which take care of translation files behind the scenes. Transifex Native also serves translations over the air (OTA), with additional caching and grouping functionality that you control. 

To integrate the tool with your Next.js app now, Sign up for a free trial on Transifex

Once you’ve created your account, follow the next steps to start using Transifex Native to internationalize your Next.js application.

Install Transifex Native

In your Next.js app, install the Transifex Native dependencies for React applications.

Use the following code:

npm install @transifex/native @transifex/react @transifex/cli --save

Update your `next.config.js`

Edit or create a `next.config.js` file in your root folder. Now, add the supported locales and your Transifex Native public token.

// next.config.js

module.exports = {

  i18n: {

    // These are all the locales you want to support in

    // your application

    locales: ['en', 'fr', 'de'],

    // This is the default locale you want to be used when visiting

    // a non-locale prefixed path e.g. `/hello`

    defaultLocale: 'en',

    localeDetection: false,

  },

  publicRuntimeConfig: {

    TxNativePublicToken: 'YOUR-PUBLIC-TX-NATIVE-TOKEN',

  }

}

Create a Transifex Native utility

Add a new `i18n.js` library file in your source folder where the code exists. 

Once created, paste the following code:

// nextjs/i18n.js

import { tx, normalizeLocale } from '@transifex/native';

import getConfig from 'next/config';

const { publicRuntimeConfig } = getConfig();

/**

 * Used by SSR to pass translation to browser

 *

 * @param {*} { locale, locales }

 * @return {*} { locale, locales, translations }

 */

export async function getServerSideTranslations({ locale, locales }) {

  tx.init({

    token: publicRuntimeConfig.TxNativePublicToken,

  });

  // ensure that nextjs locale is in the Transifex format,

  // for example, de-de -> de_DE

  const txLocale = normalizeLocale(locale);

  // load translations over-the-air

  await tx.fetchTranslations(txLocale);

  return {

    locale,

    locales,

    translations: tx.cache.getTranslations(txLocale),

  };

}

/**

 * Initialize client-side Transifex Native instance cache

 *

 * @param {*} { locale, translations }

 */

export function setClientSideTranslations({ locale, translations }) {

  if (!locale || !translations) return;

  tx.init({

    currentLocale: locale,

  });

  tx.cache.update(locale, translations);

}

Uploading Translatable Content

After the strings have been marked for translation in the code, you can upload them to Transifex. 

Use `@transifex/cli` library to collect all translatable strings and push them to Transifex using the following command:

$ npx txjs-cli push <SRC_FOLDER> --token=<PROJECT_TOKEN> --secret=<PROJECT_SECRET>

The `@transifex/native` library can be used to push any content from various sources, such as custom files, database etc.

Here’s how it can be done:

const { tx } = require('@transifex/native');

tx.init({

  token: 'token',

  secret: 'secret',

});

await tx.pushSource({

  'mykey': {

    string: 'My string',

    meta: {

      context: 'content', // optional

      developer_comment: 'developer comment', // optional

      character_limit: 10, // optional

      tags: ['tag1', 'tag2'], // optional

      occurrences: ['file.jsx', 'file2.js'], // optional

    },

  }

});

Use `getServerSideProps` to Load Translations

On the pages you’d like i18n to be translated, load translations server-side, and pass them to the client using `getServerSideProps` and `setClientSideTranslations`.

Here’s a sample code below that you can use: 

// nextjs/pages/index.js

import { useState } from 'react';

import { T, UT } from '@transifex/react';

import { useRouter } from 'next/router';

import { getServerSideTranslations, setClientSideTranslations } from '../i18n';

export default function Home(props) {

  // initialize client-side translation from server side props

  setClientSideTranslations(props); // { locale, locales, translations }

  return (

    <div>

      <T _str="Hello world" />

    </div>

  );

}

export async function getServerSideProps(context) {

  const data = await getServerSideTranslations(context)

  return {

    props: {

      ...data, // { locale, locales, translations }

    }

  }

}

To understand better with a live example, view Transifex Native Sandbox. Want to localize a React application? Refer to our Step-by-Step Guide for React Localization.

Use Advanced Setup to Automate Translations

To get the most out of Transifex, here are some advanced features that you can implement.

Utilize Custom App Space

If you don’t want to set translations on every page, you can also move the code in the Custom App space. This helps you set your translations globally.

To override the default App component provided by Next.js, use the following code to create a `pages/_app.js` file:

import type { AppProps } from 'next/app'

export default function MyApp({ Component, pageProps }: AppProps) {

  return <Component {...pageProps} />

}

The `Component` prop is the active page, so whenever you navigate between routes, `Component` will change to the new page. Therefore, any props you send to `Component` will be received by the page.

Now, use the following code inside the file:

// nextjs/pages/_app.js

import { setClientSideTranslations } from '../i18n';

// This default export is required in a new `pages/_app.js` file.

export default function MyApp({ Component, pageProps }) {

  setClientSideTranslations(pageProps);  

  return <Component {...pageProps} />

}

Use Custom Automatic Refresh Logic

You can program custom automatic refresh logic by making some modifications to the `i18n.js` utility function from the previous example.

This will help your app check for fresh translations at specific intervals.

Here’s a sample example for refreshing translations every 10 minutes. To change the interval, modify the `TRANSLATIONS_TTL_SEC` variable with an interval of your choice.

// ------------ <NEW> ------------

const TRANSLATIONS_TTL_SEC = 10 * 60; // 10 minutes

// ------------ </NEW> -----------

/**

 * Used by SSR to pass translation to browser

 *

 * @export

 * @param {*} { locale, locales }

 * @return {*}

 */

export async function getServerSideTranslations({ locale, locales }) {

  tx.init({

    token: publicRuntimeConfig.TxNativePublicToken,

  });

  // ensure that nextjs locale is in the Transifex format,

  // for example, de-de -> de_DE

  const txLocale = normalizeLocale(locale);

  await tx.fetchTranslations(txLocale);

  // ------------ <NEW> ------------

  // bind a helper object in the Native instance for auto-refresh

  tx._autorefresh = tx._autorefresh || {};

  if (!tx._autorefresh[txLocale]) {

    tx._autorefresh[txLocale] = Date.now();

  }

  // check for stale content in the background

  if (Date.now() - tx._autorefresh[txLocale] > TRANSLATIONS_TTL_SEC * 1000) {

    tx._autorefresh[txLocale] = Date.now();

    tx.fetchTranslations(txLocale, { refresh: true });

  }

  // ------------ </NEW> -----------

  return {

    locale,

    locales,

    translations: tx.cache.getTranslations(txLocale),

  };

}

Implement Content Splitting

If your app deals with a lot of content, you can leverage Content Splitting to get optimal performance and reduce data use.

To tag content directly into the code, use the following:

import React, { Component } from 'react';

import { T } from '@transifex/react';

class Example extends Component {

  render() {

    const user = 'Joe';

    return (

      <div>

        <T _str="Hello world" _tags="homepage" />

        <T _str="Hello {username}" username={user} _tags="homepage" />

      </div>

    );

  }

}

Wrapping Up

By following the steps discussed in our guide, you’ll successfully implement i18n in your Next.js application. 

Whether you choose a basic setup with the next-intl configuration or leverage the automation capabilities of Transifex Native, you’ll be equipped to cater to a global audience.

Leverage Transifex for Next.js Internationalization 

While Next.js provides in-built features for your i18n and L10N processes, it still requires you to handle files manually. This can be a hassle whether you are trying to track translation versions or collaborate with your team. 

Transifex simplifies any issues related to manual translation management by:

  • Allowing you to manage all your translations from a single platform, eliminating the need for scattered files.
  • Automating tasks like translation extraction and file updates, saving you time and effort.
  • Allowing you to invite translators and collaborate efficiently within the Transifex platform.

Transifex also provides AI-powered tools to scale your content localization at a fraction of the cost and time otherwise required. This AI tool combines the power of public LLMs with human-generated context to ensure error-free translations that maintain your brand voice. 

These translations are also SEO-optimized and fit any UI design to suit both the SERPs and your potential customers.

Ready to simplify your Next.js internationalization process? Sign up for a free Transifex account today.

TRANSIFEX
Start your localization journey
Bring your brand to the world and create global experiences with the power of AI.
GET A DEMO
Siddharth Sharma
Siddharth is a freelance content writer, content strategist, and content growth partner. He works in the B2B SaaS and marketing space. He works with agencies around the globe to help scale their approachable customer bases.
FacebookgithubGoogle+Fill 88Twitter