Next.js & Code Internationalization: A Beginner’s Guide

In today’s interconnected world, reaching a global audience is more critical than ever. Imagine your website attracting users from diverse linguistic backgrounds. Without proper internationalization (i18n), your content might be lost in translation, leading to a frustrating user experience and potentially limiting your reach. Next.js, a powerful React framework, offers robust features to make your web applications multilingual, ensuring they resonate with users worldwide. This guide will walk you through the process of internationalizing your Next.js application, from the basics to more advanced techniques.

Why Internationalization Matters

Internationalization, often abbreviated as i18n (where 18 represents the number of letters between ‘i’ and ‘n’), is the process of designing and developing applications that can be adapted to various languages and regions without requiring engineering changes. It’s about making your application adaptable, not just translated. This means considering aspects like:

  • Language: Displaying text in the user’s preferred language.
  • Regional formats: Adapting date, time, number, and currency formats.
  • Cultural considerations: Addressing differences in content presentation and user interface elements.

By internationalizing your Next.js application, you’re not just translating words; you’re creating a more inclusive and user-friendly experience for a global audience. This can lead to increased engagement, conversions, and brand loyalty.

Setting Up Your Next.js Project

Before diving into i18n, let’s ensure you have a Next.js project set up. If you don’t already have one, create a new project using the following command in your terminal:

npx create-next-app my-i18n-app

Navigate into your project directory:

cd my-i18n-app

Now, let’s install the necessary packages. We’ll be using `next-i18next`, a popular and well-maintained library for i18n in Next.js. Install it using npm or yarn:

npm install next-i18next react-i18next i18next --save

or

yarn add next-i18next react-i18next i18next

Configuring next-i18next

Next, we need to configure `next-i18next`. Create a file named `next-i18next.config.js` in the root of your project. This file will hold your i18n configuration settings. Here’s a basic example:

// next-i18next.config.js
module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es'],
  },
}

In this configuration:

  • `defaultLocale`: Specifies the default language of your application (English in this case).
  • `locales`: An array of supported locales (English and Spanish).

Setting Up Translation Files

Now, let’s create our translation files. `next-i18next` uses a specific directory structure to organize these files. Create a directory named `public/locales` in your project. Inside this directory, create subdirectories for each language you support (e.g., `en`, `es`).

Inside each language directory, create a JSON file for each namespace. A namespace is a way to group related translations. For this example, we’ll create a `common.json` file in both `en` and `es` directories. These files will contain our actual translations.

Here’s what `public/locales/en/common.json` might look like:

{
  "title": "Welcome to My Website",
  "description": "This is a sample internationalized website.",
  "button": "Change Language"
}

And here’s what `public/locales/es/common.json` might look like:

{
  "title": "Bienvenido a mi sitio web",
  "description": "Este es un sitio web de muestra internacionalizado.",
  "button": "Cambiar idioma"
}

Note: The keys (`title`, `description`, `button`) are the same in both files, but the values are the translated text.

Using Translations in Your Components

Now that we have our configuration and translation files set up, let’s use the translations in our components. `next-i18next` provides a `useTranslation` hook that allows us to access the translations.

Open your `pages/index.js` file (or the page you want to internationalize) and modify it as follows:

import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

function Home() {
  const { t } = useTranslation('common'); // Specify the namespace

  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
      <button>{t('button')}</button>
    </div>
  );
}

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...(await serverSideTranslations(locale, ['common'])),
  },
});

export default Home;

In this code:

  • We import `useTranslation` from `next-i18next`.
  • We call `useTranslation(‘common’)` to access the translations from the `common` namespace. The `t` function is used to translate the text based on the current locale.
  • `getStaticProps` is used to pre-render the page with the translations. We use `serverSideTranslations` to load the translations on the server. This is important for SEO.

Adding a Language Switcher

To allow users to switch between languages, you’ll need to create a language switcher. This can be a simple dropdown or a set of buttons. Here’s a basic example using a dropdown:

// components/LanguageSwitcher.js
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';

function LanguageSwitcher() {
  const router = useRouter();
  const { i18n } = useTranslation();

  const changeLanguage = (e) => {
    const newLocale = e.target.value;
    router.push(router.pathname, router.pathname, {
      locale: newLocale,
    });
  };

  return (
    
      English
      Español
    
  );
}

export default LanguageSwitcher;

In this component:

  • We use the `useRouter` hook from `next/router` to access the router object.
  • We use the `useTranslation` hook to get access to the `i18n` object.
  • The `changeLanguage` function updates the route with the new locale. The `router.push` function is used to navigate to the same page with a different locale.
  • We render a select element with options for each supported language.

Import and use this component in your `pages/index.js` (or any other page):

import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import LanguageSwitcher from '../components/LanguageSwitcher';

function Home() {
  const { t } = useTranslation('common');

  return (
    <div>
      
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
      <button>{t('button')}</button>
    </div>
  );
}

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...(await serverSideTranslations(locale, ['common'])),
  },
});

export default Home;

Handling Different Date and Number Formats

Different locales have different formats for dates, times, numbers, and currencies. `next-i18next` leverages the power of `i18next` and its plugins to handle these regional differences.

First, install the `i18next-icu` plugin:

npm install i18next-icu --save

Then, initialize the plugin in your `next-i18next.config.js` file:

// next-i18next.config.js
const { initReactI18next } = require('react-i18next');
const i18next = require('i18next');
const ICU = require('i18next-icu');

i18next
  .use(initReactI18next)
  .use(ICU);

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es'],
  },
  // ... other configurations
  
  // Add this configuration
  serializeConfig: false,
}

Now, you can format dates, times, numbers, and currencies using the ICU format. You’ll need to install the ICU formatter as well:

npm install intl-messageformat --save

In your translation files, use the ICU format to specify how to format dates, numbers, and currencies. For example, in `public/locales/en/common.json`:

{
  "date": "Today is {date, date, short}",
  "price": "Price: {price, currency, USD}"
}

And in `public/locales/es/common.json`:

{
  "date": "Hoy es {date, date, short}",
  "price": "Precio: {price, currency, EUR}"
}

In your component, use the `t` function to render the formatted values:

import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

function Home() {
  const { t } = useTranslation('common');
  const today = new Date();
  const price = 123.45;

  return (
    <div>
      <p>{t('date', { date: today })}</p>
      <p>{t('price', { price })}</p>
    </div>
  );
}

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...(await serverSideTranslations(locale, ['common'])),
  },
});

export default Home;

Handling Plurals

Different languages have different pluralization rules. `i18next` provides a mechanism to handle plurals using the ICU MessageFormat syntax.

In your translation files, you can define plural forms using the `plural` key. For example, in `public/locales/en/common.json`:

{
  "itemCount": "You have {{count}} item |||| You have {{count}} items"
}

And in `public/locales/es/common.json`:

{
  "itemCount": "Tienes {{count}} artículo |||| Tienes {{count}} artículos"
}

In your component, use the `t` function to render the pluralized text:

import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

function Home() {
  const { t } = useTranslation('common');
  const count = 2;

  return (
    <div>
      <p>{t('itemCount', { count })}</p>
    </div>
  );
}

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...(await serverSideTranslations(locale, ['common'])),
  },
});

export default Home;

SEO Considerations

When implementing i18n, it’s crucial to consider SEO. Search engines need to understand the language and region of your content to serve it to the appropriate users.

  • hreflang tags: Use `hreflang` tags in your “ to specify the language and region of each page version. This helps search engines understand the relationship between different language versions of your content.
  • Sitemap: Create a sitemap that includes all language versions of your pages.
  • URL structure: Choose an appropriate URL structure for your language versions. Common options include using subdirectories (e.g., `example.com/en/`, `example.com/es/`), subdomains (e.g., `en.example.com`, `es.example.com`), or the top-level domain (e.g., `example.com.es`). Subdirectories are generally preferred for SEO.

Here’s an example of how to use `hreflang` tags in your `pages/_document.js` file:

// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { useRouter } from 'next/router';

class MyDocument extends Document {
  render() {
    const router = useRouter();
    const { asPath, defaultLocale, locales } = router;

    return (
      
        
          {locales.map((locale) => {
            const hrefLang = `/${locale}${asPath}`;
            const href = `/${locale}${asPath}`;
            return (
              
            );
          })}
          
        
        
          <Main />
          
        
      
    );
  }
}

export default MyDocument;

Remember to adapt the `href` attributes to match your chosen URL structure.

Common Mistakes and How to Fix Them

Here are some common mistakes developers make when implementing i18n and how to avoid them:

  • Forgetting to use `serverSideTranslations` for SSR: If you don’t use `serverSideTranslations` in pages that are server-side rendered (e.g., pages using `getStaticProps` or `getServerSideProps`), your translations won’t be available on the initial page load, leading to a flash of untranslated content.
  • Incorrect namespace usage: Make sure you specify the correct namespace when calling `useTranslation`. If you’re getting `t` is not a function errors, double-check your namespace.
  • Not handling pluralization correctly: Use the ICU MessageFormat syntax for pluralization to ensure correct plural forms for different languages.
  • Ignoring SEO considerations: Failing to implement `hreflang` tags and a proper sitemap can negatively impact your SEO.
  • Hardcoding language codes: Avoid hardcoding language codes in your components. Use the `i18n.language` property from the `useTranslation` hook to dynamically render content based on the current locale.

Key Takeaways

  • Internationalization is essential for reaching a global audience.
  • Next.js and `next-i18next` provide a powerful and flexible solution for i18n.
  • Use `serverSideTranslations` for server-side rendering to avoid a flash of untranslated content.
  • Consider SEO best practices, including `hreflang` tags and a sitemap.
  • Handle date, time, number, and currency formats using the ICU format.
  • Remember to test your application thoroughly in all supported languages.

FAQ

  1. How do I add a new language to my Next.js application? Add a new directory in your `public/locales` directory for the new language (e.g., `public/locales/fr`). Create a `common.json` file (or other namespace files) in the new language directory with the translations. Update the `locales` array in your `next-i18next.config.js` file to include the new language code.
  2. How do I handle RTL (Right-to-Left) languages? You’ll need to use CSS to handle the layout differences. You can use the `i18n.language` property from `useTranslation` to dynamically apply CSS classes. For example, you can add a class like `rtl` to the `body` element and then use CSS to style the layout accordingly. You may also need to reverse the direction of text using CSS `direction: rtl;`
  3. What is the difference between `getStaticProps` and `getServerSideProps` in the context of i18n? Both are used for pre-rendering pages. `getStaticProps` is used for static site generation, while `getServerSideProps` is used for server-side rendering. When using `next-i18next`, you’ll typically use `serverSideTranslations` in both functions to load the translations. The choice between them depends on your content and update frequency. If the content is static and doesn’t change frequently, `getStaticProps` is preferred. If the content is dynamic and changes frequently, `getServerSideProps` might be more suitable.
  4. How can I test my i18n implementation? Test your application in all supported languages. Use your language switcher to switch between languages and verify that the content is translated correctly. Check for any layout issues or text overflows. Use browser developer tools to inspect the HTML and ensure that the correct language attributes are set.

Internationalizing your Next.js application is a journey, not just a task. By embracing i18n, you’re not just translating words; you’re opening doors to a global audience and fostering a more inclusive and engaging user experience. Keep experimenting, learning, and refining your approach, and you’ll build web applications that resonate with users from every corner of the world. By paying attention to detail and following best practices, you can create a truly global web presence.