Building a Language Switcher for Next.js

In this article, we create a Language dropdown to switch to another language using Next.js and next-i18n.

·4 min read
Cover Image for Building a Language Switcher for Next.js

next-i18n-next is a popular Next.js library for managing translations and i18n in your application or website.

In this article, I want to show you how I made a dynamic language switcher for the Makerkit Next.js template and how you can copy and reuse it in your own applications.

My goal was to automatically load, display and translate the available languages based on the Next.js i18n configuration: this is finally possible thanks to the fantastic Intl Browser API.

For example, let's assume the below is your Next.js i18n configuration:

const config = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'it'],
  },
  fallbackLng: {
    default: ['en'],
  },
  localePath: resolve('./public/locales'),
};

The component automatically renders a dropdown with two languages: Italian and English. Furthermore, the language names will be translated into the currently selected language.

Building a Language Selector for Next.js

Let's name our component LanguageSwitcher. This component will also emit a callback with the selected language to its parent, which can be helpful if you want to persist the choice somewhere.

const LanguageSwitcher: React.FC<{
  onChange?: (locale: string) => unknown;
}> = ({ onChange }) => {}

export default LanguageSwitcher;

Let's define some of the constants we're going to use:

const { i18n } = useTranslation();
const { language: currentLanguage } = i18n;
const router = useRouter();
const locales = router.locales ?? [currentLanguage];

Thanks to router.locales, we can retrieve the languages added to the Next.js configuration. This helps us make the component configuration-less.

Next, we're going to instantiate an Intl.DisplayNames instance: this powerful API will return the language name in the selected language:

 const languageNames = useMemo(() => {
  return new Intl.DisplayNames([currentLanguage], {
    type: 'language',
  });
}, [currentLanguage]);

We can use the Intl.DisplayNames API in the following way:

console.log(
  languageNames.of(lang)
)

For example, if currentLanguage is English and lang is it (Italian), the above will display Inglese, English in Italian.

Let's now define what happens when a language is switched:

  1. Using router.push, we can pass the selected locale. This will cause Next.js to re-render the page with the new locale's translations
  2. We emit the onChange callback if it's passed from the parent component
  3. We set the local state with setValue
const [value, setValue] = useState({
  value: i18n.language,
  label: capitalize(languageNames.of(currentLanguage) ?? currentLanguage),
});

const switchToLocale = useCallback(
  (locale: string) => {
    const path = router.asPath;

    return router.push(path, path, { locale });
  },
  [router]
);

const languageChanged = useCallback(
  async (option: ListBoxOptionModel<string>) => {
    setValue(option);

    const locale = option.value;

    if (onChange) {
      onChange(locale);
    }

    await switchToLocale(locale);
  },
  [switchToLocale, onChange]
);

And now it's time to render our component with a selector element: you can use your own, the native HTML select, or Makerkit's ListBox:

return (
  <ListBox value={value} setValue={languageChanged}>
    {locales.map((locale) => {
      const label = capitalize(languageNames.of(locale) ?? locale);

      const option = {
        value: locale,
        label,
      };

      return <ListBoxOption key={locale} option={option} />;
    })}
  </ListBox>
);

Of course, you should adapt the above to the HTML component you have access to.

Below, you can find a demo:

Loading video...

Full Source Code

Below is the full source code of the component:

import { useRouter } from 'next/router';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';

import ListBox, { ListBoxOptionModel } from '~/core/ui/ListBox/ListBox';
import ListBoxOption from '~/core/ui/ListBox/ListBoxOption';

const LanguageSwitcher: React.FC<{
  onChange?: (locale: string) => unknown;
}> = ({ onChange }) => {
  const { i18n } = useTranslation();
  const { language: currentLanguage } = i18n;
  const router = useRouter();
  const locales = router.locales ?? [currentLanguage];

  const languageNames = useMemo(() => {
    return new Intl.DisplayNames([currentLanguage], {
      type: 'language',
    });
  }, [currentLanguage]);

  const [value, setValue] = useState({
    value: i18n.language,
    label: capitalize(languageNames.of(currentLanguage) ?? currentLanguage),
  });

  const switchToLocale = useCallback(
    (locale: string) => {
      const path = router.asPath;

      return router.push(path, path, { locale });
    },
    [router]
  );

  const languageChanged = useCallback(
    async (option: ListBoxOptionModel<string>) => {
      setValue(option);

      const locale = option.value;

      if (onChange) {
        onChange(locale);
      }

      await switchToLocale(locale);
    },
    [switchToLocale, onChange]
  );

  return (
    <ListBox value={value} setValue={languageChanged}>
      {locales.map((locale) => {
        const label = capitalize(languageNames.of(locale) ?? locale);
        const option = {
          value: locale,
          label,
        };

        return <ListBoxOption key={locale} option={option} />;
      })}
    </ListBox>
  );
};

function capitalize(lang: string) {
  return lang.slice(0, 1).toUpperCase() + lang.slice(1);
}

export default LanguageSwitcher;

Stay informed with our latest resources for building a SaaS

Subscribe to our newsletter to receive updatesor

Read more about

Cover Image for Authenticating users with Remix and Supabase

Authenticating users with Remix and Supabase

·16 min read
Learn how to use Remix and Supabase to authenticate users in your application.
Cover Image for How Makerkit helps boost your SaaS SEO

How Makerkit helps boost your SaaS SEO

·4 min read
Learn how Makerkit can help boost your SaaS SEO thanks to its optimized codebase and SEO-friendly features.
Cover Image for How to sell code with Gumroad and Github

How to sell code with Gumroad and Github

·7 min read
Sell and monetize your code by giving private access to your Github repositories using Gumroad
Cover Image for Migrating to Next.js Server Components Layouts

Migrating to Next.js Server Components Layouts

·6 min read
A simple guide to migrating your _app.tsx component to the new Server Components released with Next.js 13
Cover Image for Getting Started with Next.js Server Components

Getting Started with Next.js Server Components

·8 min read
A simple introduction to using Server Components and the new Layouts Folder Structure with Next.js 13
Cover Image for Counting a collection's documents with Firebase Firestore

Counting a collection's documents with Firebase Firestore

·2 min read
In this article, we learn how to count the number of documents in a Firestore collection using a custom React.js hook.