To add internationalization to a web app, you need to handle two main tasks: managing translations and maintaining separate URLs.

Questions related to your URLs often include:

  • How are locales split up? Will you use a sub-path (/fr vs /en) or domain locale strategy (domain.com vs domain.fr)?
  • If your user changes locale, how do you redirect them to the correct URL?
  • Can you detect the user's preferred locale and redirect them accordingly?

In October 2020, Next.js released a new version of its framework. Among all the new and shiny features, the one which interested me the most was Internationalized Routing. This feature added built-in support for routing in multilingual applications along with language detection. This however leaves you with the last component: managing translations.

Enter next-translate. I have chosen this particular translation library for many reasons. On top of being very easy to use and configure, I love next-translate because it works very well with Next.s automatic page optimization. Meaning, my prerendered static pages can stay this way and next-translate will handle the translation. Woo!!

Now that the introductions are done, let's move on to the nitty-gritty.

Want to become a Next.js pro?

I am putting together an online course on Next.js. If you are interested, sign up and I will keep you updated.

    We won't send you spam. Unsubscribe at any time.

    Step 1: Setting up locale strategy in next.config.js

    As mentioned before, there are two ways you can go about locale strategy: sub-path (/fr, /en, /en-US, ...) or domain routing (.com, .fr, .ca).

    Note: If you are not sure which to choose, consult Google's documentation on using locale-specific URLs detailing the pros and cons for each.

    For this tutorial, you will be going down the sub-path route (pun intended). To do so, you need to update your next.config.js and add your locales.

    In this example, the resulting URLs will be / for anglophones and /fr for francophones.

    module.exports = {
      i18n: {
        locales: ['en', 'fr'],
        defaultLocale: 'en',
      },
    }
    Setup next.config.js

    Step 2: Set up Internationalized Routing in your Next.js pages

    Now that your locale strategy is defined, let's move on to your pages. How does Next.js handle internationalization with its static and dynamic pages?

    Static pages

    Luckily for us, Next.js generates separate versions for each locale. At build time, Next.js will therefore spit out two index.js pages, one in english and one in french.

    Dynamic pages

    However, your dynamic pages need a little bit of love. Say, for example, I have a Next.js application for my blog. I have two pages: index.js listing all the posts and [slug].js for individual post.

    Because I want my pages to load quickly, I want them statically generated. This means that, at built time, separate HTML files (post-1.html, post-2.html, ...) are generated for all my blog posts.

    In multilingual applications, not only would I want all these pages generated, but I would also want a version for each locale. The end result will look something like this:

    .next/ <- contains all the files generated at build time
    │
    └───en/
    |       post-1.html
    |       post-2.html
    └───fr/
    	post-1.html
            post-2.html

    Therefore, in your application, not only do you need to specify all the different paths (meaning /post-1, /post-2, ...), but also the locales.

    What it would look like with my blog example:

    export async function getStaticPaths({ locales }) {
      const paths = []
    
      locales.forEach((locale, i) => {
        posts.forEach((post, i) => {
          paths.push({ params: { slug: post.id }, locale })
        });
      });
    
      return { paths, fallback: false }
    }
    pages/[slug].js

    (Optional) Retrieve the current locale in your Next.js app

    You can see what the current locale is at any point thanks to the router.

    import { useRouter } from 'next/router';
    
    export default function Home() {
      const router = useRouter()
      const { locale } = router
    
      return (
        <div className="center-container">
          <main className="main">
            // ...
    
            <section>
              <div>Current locale: {locale}</div>
            </section>
           
           // ...
          </main>
        </div>
      )
    }
    get the current locale in index.js

    Step 3: Changing locale and redirecting in your Next.js app

    I love that Next.js made it really simple to redirect when changing locale. All you have to do is specify the locale in your Link component. It's pretty straightforward which I love! <3

    import Link from 'next/link';
    
    export default function Home() {
      return (
        <div className="center-container">
           // ...
              <div>
                <Link href="/" locale="en">
                  <a>Change to english</a>
                </Link>
              </div>
              <div>
                <Link href="/" locale="fr">
                  <a>Change to french</a>
                </Link>
              </div>
          // ..
        </div>
      )
    }
    change the locale with the Link component in index.js

    Step 4: Install next-translate

    Now that internationalized routing is implemented, we can move on to the last part of this tutorial: translations.

    To start with, let's add the library.

    npm install next-translate
    
    # or
    
    yarn add next-translate

    Step 5: Update next.config.js

    Then, update your next.config.js to use next-translate.

    const nextTranslate = require('next-translate')
    
    module.exports = nextTranslate()
    Use next-translate in next.config.js

    Step 6: Set up your locales and namespaces

    The last part of your configuration is to create an i18n.json and add your locales. One feature I really is being able to split your translations into separate files. This allows you to have page-specific translations.

    For the sake of this tutorial however, I will only have one namespace called common which will be applied to my entire app ("*").

    {
      "locales": ["en", "fr"],
      "defaultLocale": "en",
      "pages": {
        "*": ["common"]
      }
    }
    Create a i18n.json

    Step 7: Creating translations files

    For your next step, you then create a locales folder where you will add separate folders for all your locales. In each folder, you need to create a json file for all your namespaces.

    Since I only have one namespace (common), I will go ahead and create one json file called common.json for each locale folder.

    This is what I have in the end:

    locales/ 
    │
    └───en/
    │       common.json
    └───fr/
            common.json

    Inside your json files, add all your translations.

    {
      "homepage_title": "Blog in Next.js",
      "homepage_description": "This example shows a multilingual blog built in Next.js with next-translate"
    }
    Add translation in locales/en/common.json

    Step 8: Displaying translated content

    Finally, you can display your translated text by getting the t function from useTranslation. By passing the translation key, you can then retrieve the translated value and display it.

    import useTranslation from 'next-translate/useTranslation'
    
    export default function Home() {
      const { t } = useTranslation('common')
    
      return (
        <div className="center-container">
          <main className="main">
            <h1 className="title">
              {t('homepage_title')}
            </h1>
    
            <p className="description">
              {t('homepage_description')}
            </p>
          </main>
        </div>
      )
    }
    Display translated content in index.js

    Et voilà!

    Your application is now officially multilingual. Along with displaying translated content, your app also handles internationalized routing thanks to Next.js built-in support.

    For more content about Next.js, check out: