Next.js: Building a Multi-Language Website with i18n

In today’s interconnected world, reaching a global audience is paramount for any website. But simply having a website isn’t enough; you need to speak your audience’s language – literally. This is where internationalization (i18n) and localization (l10n) come into play, and Next.js provides powerful tools to make building multi-language websites a breeze. This tutorial will guide you through the process, from setting up your project to implementing language switching and content translation, so you can offer a seamless experience for users worldwide.

Why Build a Multi-Language Website?

Imagine your website is a bustling marketplace. Now, imagine that everyone speaks a different language. If you only speak one language, you’re excluding a vast portion of potential customers or readers. A multi-language website breaks down these barriers, allowing you to:

  • Expand Your Reach: Tap into new markets and connect with a global audience.
  • Improve User Experience: Make your website more accessible and user-friendly for visitors who speak different languages.
  • Boost SEO: Increase your website’s visibility in search results for different languages.
  • Increase Conversions: By speaking to your audience in their native language, you build trust and encourage engagement.

Next.js, with its built-in features and robust ecosystem of libraries, simplifies the process of creating multi-language websites. Let’s dive in.

Setting Up Your Next.js Project

If you don’t already have a Next.js project, let’s create one. Open your terminal and run the following command:

npx create-next-app my-multilingual-website --typescript

This command creates a new Next.js project named “my-multilingual-website” using TypeScript. Navigate into your project directory:

cd my-multilingual-website

Now, install the necessary dependencies for i18n. We’ll be using next-i18next, a popular library that simplifies i18n implementation in Next.js:

npm install next-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 the configuration for your i18n setup.

Here’s a basic example:


// next-i18next.config.js

const { locales, defaultLocale } = require('./i18n.json');

/** @type {import('next-i18next').UserConfig} */
module.exports = {
  i18n: {
    defaultLocale,
    locales,
  },
};

This configuration file imports the locales and defaultLocale from a separate JSON file (i18n.json), which we’ll create next. This separation makes it easy to manage your language settings.

Create a file named i18n.json in the root of your project and define your locales:


// i18n.json
{
  "locales": ["en", "es", "fr"],
  "defaultLocale": "en"
}

In this example, we’re supporting English (en), Spanish (es), and French (fr), with English as the default language. You can add or remove languages as needed. Ensure that the locales array contains the language codes you want to support, and the defaultLocale matches one of the codes in the locales array.

Creating Translation Files

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

Inside each language directory, create JSON files for your translations. For example, let’s create a file named common.json inside the en, es, and fr directories.

Here’s an example of public/locales/en/common.json:


// public/locales/en/common.json
{
  "title": "My Multi-Language Website",
  "welcome": "Welcome to our website!",
  "about": "About Us",
  "contact": "Contact",
  "language": "Language"
}

Here’s an example of public/locales/es/common.json:


// public/locales/es/common.json
{
  "title": "Mi Sitio Web Multi-Idioma",
  "welcome": "¡Bienvenido a nuestro sitio web!",
  "about": "Acerca de Nosotros",
  "contact": "Contacto",
  "language": "Idioma"
}

And here’s an example of public/locales/fr/common.json:


// public/locales/fr/common.json
{
  "title": "Mon Site Web Multilingue",
  "welcome": "Bienvenue sur notre site web !",
  "about": "À propos de nous",
  "contact": "Contact",
  "language": "Langue"
}

Make sure you translate all the keys for each language. Consistent key names are crucial for easy translation management. Consider using a translation management tool for larger projects to streamline the process.

Using Translations in Your Components

With your translation files in place, you can now use them in your components. next-i18next provides a useTranslation hook to access your translations.

Here’s an example of how to use it in a React component:


// pages/index.tsx
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

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

  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('welcome')}</p>
      <p>{t('about')}</p>
      <p>{t('contact')}</p>
    </div>
  );
};

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

export default Home;

In this example:

  • We import useTranslation from next-i18next.
  • We call useTranslation('common') to access the translations from the common.json file.
  • The t function is used to retrieve the translated strings based on the keys defined in your translation files.
  • getStaticProps is used to pre-render the page with the correct translations. The serverSideTranslations function is essential for server-side rendering and provides the translations to the component. It’s crucial for SEO and initial page load performance. Make sure to include all namespaces (e.g., ‘common’) that your page uses.

Implementing Language Switching

Now, let’s implement a language switcher so users can easily change the language of your website. We’ll use a simple dropdown menu for this example.


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

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

  const changeLanguage = (e: React.ChangeEvent) => {
    const selectedLanguage = e.target.value;
    i18n.changeLanguage(selectedLanguage);
    router.push(router.asPath, undefined, { locale: selectedLanguage });
  };

  return (
    
      {i18n.options.locales.map((locale) => (
        
          {locale}
        
      ))}
    
  );
};

export default LanguageSwitcher;

In this component:

  • We use the useRouter hook from next/router to get access to the router object.
  • We use the useTranslation hook to get access to the i18n instance.
  • The changeLanguage function updates the language using i18n.changeLanguage() and then navigates to the current route with the new locale using router.push(). The locale option in router.push is crucial for informing Next.js about the new language.
  • We render a select element with options for each available language.

Import and use this component in your layout or header component:


// components/Layout.tsx
import LanguageSwitcher from './LanguageSwitcher';

const Layout = ({ children }: { children: React.ReactNode }) => {
  return (
    <div>
      <header>
        
      </header>
      <main>{children}</main>
    </div>
  );
};

export default Layout;

Now, when a user selects a different language, the website will reload with the selected language.

Handling Dynamic Routes

If your website uses dynamic routes (e.g., /posts/[id]), you need to handle them correctly for i18n. next-i18next provides features to automatically translate dynamic routes.

First, enable the i18n configuration in your next.config.js file. If you don’t already have one, create it in the root of your project.


// next.config.js
const { i18n } = require('./next-i18next.config');

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  i18n,
};

module.exports = nextConfig;

Next, you’ll need to configure your dynamic routes. Let’s assume your dynamic route is for blog posts at /posts/[id]. You’ll need to create a file in your pages directory that matches this route (e.g., pages/posts/[id].tsx).

Inside this file, you’ll need to use getStaticPaths and getStaticProps to pre-render the pages for each locale. Here’s a basic example:


// pages/posts/[id].tsx
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useRouter } from 'next/router';

interface PostProps {
  id: string;
  title: string;
  content: string;
}

const Post = ({ id, title, content }: PostProps) => {
  const { t } = useTranslation('common');
  const router = useRouter();
  const { locale } = router;

  return (
    <div>
      <h1>{title}</h1>
      <p>{content}</p>
      <p>{t('language')}: {locale}</p>
    </div>
  );
};

export const getStaticPaths = async ({ locales }) => {
  // In a real application, you would fetch the post IDs from your data source.
  const postIds = ['1', '2']; // Example post IDs

  const paths = locales.flatMap((locale) =>
    postIds.map((id) => ({
      params: {
        id: id,
      },
      locale,
    }))
  );

  return {
    paths,
    fallback: false,
  };
};

export const getStaticProps = async ({ params, locale }) => {
  const { id } = params as { id: string };
  // In a real application, fetch the post data based on the ID and locale
  const postData = {
    id: id,
    title: `Post ${id} - ${locale}`, // Example title
    content: `Content for post ${id} in ${locale}`, // Example content
  };

  return {
    props: {
      ...(await serverSideTranslations(locale, ['common'])),
      ...postData,
    },
  };
};

export default Post;

In this example:

  • getStaticPaths generates the paths for all possible combinations of post IDs and locales. This function is crucial for pre-rendering pages during the build process. It returns an array of path objects, each specifying the parameters (params) and the locale (locale).
  • getStaticProps fetches the data for each post based on the ID and locale. It also includes serverSideTranslations to provide the translations. Remember to fetch the correct data based on the current locale. This is where you would integrate with your CMS or database to retrieve the translated content for each post.
  • The Post component displays the post content, using the useTranslation hook and the post data fetched in getStaticProps.

Remember to adapt the data fetching logic in getStaticPaths and getStaticProps to your specific data source and requirements. This example provides a foundation for handling dynamic routes with i18n.

SEO Considerations

Implementing i18n correctly is crucial for SEO. Here are some best practices:

  • hreflang Tags: Use hreflang tags in your HTML head to tell search engines about the different language versions of your pages. This helps search engines understand the relationship between your different language versions and serve the correct version to users based on their language preferences. You can use the next-seo package or manually add these tags.
  • Canonical URLs: Use canonical URLs to indicate the preferred version of a page, especially if you have duplicate content across different languages. This tells search engines which version of the page you want to be indexed.
  • URL Structure: Consider using different URL structures for each language. Common approaches include:

    • Subdirectories: example.com/en/about, example.com/es/about
    • Subdomains: en.example.com/about, es.example.com/about
    • Top-Level Domains (TLDs): example.com/en/about, example.es/about
  • Sitemap: Generate a sitemap that includes all language versions of your pages. This helps search engines discover and index all your content.
  • Meta Descriptions and Titles: Ensure that your meta descriptions and titles are translated for each language. This is crucial for attracting clicks from search results.
  • Content is Key: The quality of your translated content is paramount. Use professional translation services to ensure accuracy and natural-sounding translations. Poor translations can negatively impact your SEO.

Common Mistakes and How to Fix Them

Here are some common mistakes developers make when implementing i18n in Next.js, and how to avoid them:

  • Incorrect Configuration: Double-check your next-i18next.config.js and i18n.json files to ensure they are correctly configured with your supported locales and default locale. Typos or incorrect file paths can cause issues.
  • Missing Translations: Ensure that you have translations for all the keys in each language. Missing translations will result in the original English text being displayed, which can be confusing for users. Use a translation management tool to help identify missing translations.
  • Incorrect getStaticProps / getServerSideProps Usage: Make sure you are using serverSideTranslations in getStaticProps or getServerSideProps for pages that require server-side rendering or static site generation. This ensures that the translations are available when the page is rendered.
  • Improper Language Switching: Ensure that your language switcher correctly updates the language in the router and persists the selected language. Incorrect implementation can lead to unexpected behavior or broken links. Test your language switcher thoroughly.
  • Ignoring SEO Best Practices: Failing to implement hreflang tags, canonical URLs, and other SEO best practices can negatively impact your website’s search engine rankings. Prioritize SEO considerations throughout the i18n implementation process.

Advanced Topics

Beyond the basics, here are some advanced topics to consider:

  • Pluralization: Handle plural forms in different languages using libraries like i18next-plural-postprocessor.
  • Date and Number Formatting: Format dates, numbers, and currencies according to the user’s locale using libraries like Intl.
  • Right-to-Left (RTL) Support: Support languages that are read from right to left (e.g., Arabic, Hebrew) by dynamically adjusting the layout and styling of your website.
  • Translation Management Systems (TMS): Integrate with a TMS (e.g., Lokalise, Phrase) to streamline the translation process and manage your translations more effectively.
  • Content Delivery Networks (CDNs): Consider using a CDN to serve your translated content and improve performance for users around the world.

Key Takeaways

  • Next.js provides excellent support for building multi-language websites.
  • next-i18next simplifies the i18n implementation process.
  • Proper configuration of locales, translation files, and language switching is crucial.
  • Remember to handle dynamic routes correctly for i18n.
  • Prioritize SEO best practices for optimal search engine rankings.

Building a multi-language website is an investment that pays off by expanding your audience and improving user experience. By following this guide, you can create a localized website that connects with users around the globe. Remember to prioritize clear translations, proper SEO implementation, and a user-friendly language switching mechanism. With these elements in place, your website will be well-equipped to thrive in the global market. The effort you put into internationalization will be directly reflected in your website’s reach and impact.