Inksh logoContactAbout usPrivacy
Author name Hemant Bhatt

A concise guide to internationalization in Next.js App Router

Internationalization hero icon

Introduction

In today's interconnected world, ensuring your webapp or SAAS is accessible to a global audience is not just a luxury—it's a necessity. With the app router and middleware, the realm of translation has transformed into an open canvas, allowing you to build the translations system as you want, and with any custom logic that suits your needs. This is a theoretical and concise guide in which i have laid out ways in which translations can take place with Next.js.

Next.js internationalization has 2 major parts:

  1. Route control: This involves changing the default route of the user so that it also include the translational data. The locale is essentially added to the user’s URL. With middleware you can easily customize a basic route like “inksh.in” to something like “inksh.in/en” or “en.inksh.in”. This helps you distinguish the regional users based on their preferred locales.
  2. Image showing how routes travel in nextjs app router
  3. Rendering content: This involves making the content on your page responsive to the URL’s locale data. In simple terms the page’s content for “inksh.in/en” will be different from “inksh.in/it”. One will serve English content and the other will serve Italian content.
  4. Image showing tranlated content

Now lets dive deeper and classify these 2 points further. Route control can further be divided into 2 points. Whereas translated content is pretty straight forward. More about them below.

Route control

  1. Locale injected routes.
  2. Lets say a user lands on “inksh.in”. Based on the user’s language preference, which is set in the accept-language header, we  redirect the user to a particular subroute. For example this can be either inksh.in/en or inksh.in/it

    Image showing route control with locale

    Once the user is in the preferred locale version of the site, our job is to keep the user in that version. For this, all the links that we will render in that locale version of the site, will also match the current locale. So the <Link> component’s href to contact and about-us on “inksh.in/en” will be: inksh.in/en/contact and inksh.in/en/about-us. Essentially we are taking a user to a preffered route and keeping them there.

  3. Domain injected routes.
  4. The second method for route control is domain based. Here we use the user’s domain as a tool to determine which locale the user wants to access. Imagine you have several domains, such as inksh.fr, inksh.in, and inksh.it, with each one catering to a distinct group of international users. Rather than using redirect(like we did in locale injected routes), to handle domain based internationalization, we can use re-write. Re-write is like a magical cloak for your URL. When a user pops in from ‘inksh.fr/about-us’ on a French site, we can sneakily switch the path from /about-us to /fr/about-us behind the scenes through the middleware. Although the user still sees the url as ‘inksh.fr/about-us’, but for the internal files system, the URL would be ‘/fr/about-us’.

    Image showing route control with locale and domain injected routes

Translated content

Now, you might wonder why we went through the trouble of altering the subpath. The reason is to enable dynamic rendering of the translated content. Let's now take advantage the URL's locale information.
The first thing that we will do is create a ‘[lang]’ folder just under the app folder. All our files and folders for the app router will now go into this lang folder. This lang folder will help capture the first subpath of the URLs.

Image showing folder structure with lang at the top level
Now that we’ve made the lang folder as a net to capture the incoming subpath’s locale, it's time to use it for rendering. Now to render the Page.tsx content, we need to destructure the params from the page files. Based on this params, we can conditionally render translated content like so:
export default async function Page({ params: { lang } }) {
return <div>{lang===”fr” ? Bonjour! : Hello! }<div>
}

So if the user comes from /fr, the rendered content will be

<div>Bonjour<div>

And if the user comes from /en, the rendered content would be

<div>Hello<div>

You can then store these translations in a JSON object with language keys and display them as you want. For more production ready use cases you can use custom hooks and utility functions to extract the locale.

Conclusion

By controlling routes through either locale-injected or domain-based methods, you can effectively manage user navigation based on their language preferences. Furthermore, rendering translated content dynamically allows for a seamless experience tailored to each user's specific needs. This guide serves as a foundation for building a robust translation system that can be customized and expanded upon.