Making a multilingual site with Next.js - Part 1
Tags: webdev, react, nextjs, typescript
Published: 2020-07-30 15:08:34

See the original article [here](https://blog.elvessousa.com.br/en/post/next-intl).

In the last article I showed you how to make a simple multilingual website in Gatsby. This time, I decided to try the same with Next.js. As with Gatsby, it was not easy to find information on how to create a website with multiple languages as well. As I did that time, I'll also describe here the process I used to achieve this goal.

### Initial setup

To set up the project in Next.js just use this command in the terminal:

```shell
yarn create next-app next-intl
```

If you are using NPM, use this other one.

```shell
npx create-next-app next-intl
```

In this code, I am using TypeScript. If you don't want to use it, just skip these next steps.

#### Configuring TypeScript

Next.js already provides support for TypeScript from the factory. Just create an empty `tsconfig.json` file at the root of the project. When starting the development environment using the command `yarn dev` or`npm run dev`, the file `tsconfig.json` will be detected and if it does not find the typing dependencies for development, it will show a message telling which dependencies need be installed and giving the command for you to copy and paste to add them. It will probably be something as shown below.

```shell
yarn add typescript @types/node @types/react -D
```

Or:

```shell
npm install --save-dev typescript @types/node @types/react
```

Ready. The setup is complete.

### Project structure

#### Directories

I leave the following suggestion for the project's directory and file structure:

- **components**: React components.
- **intl**: here will be the files for the translation of the website content
- **lib**: in this directory will be the script to read the `*.md` files and which makes the information available for use on the pages.
- **pages**: Next.js uses this folder as the directions for the application. The file `teste.tsx` is available as`http://localhost: 3000/teste`
- **posts**: Markdown files that containing article texts.
- **styles**: styles for the page.
- **public**: this folder is used by default by Next.js for public files: images, icons, etc.

Feel free to use the structure that makes the most sense for you.

#### Structure of the pages (routes)

Any JavaScript file (`.js`,`.jsx`, or `.tsx` in my case) placed inside the `/pages` folder will be converted into an accessible route automatically. In this example, there are the files `index.tsx` for the home page and`about.tsx` for an about page.

I am not going to describe these files in detail here, just what is needed for the translation. Anyway, I'll show examples later and the code will be available in the repository linked at the end of this article.

The page for listing articles and viewing the content of each article will be arranged differently. I'll explain it later.

#### Content structure (posts)

Within the `/posts` folder, two more directories will be created: `/en` and `/pt` for files in English and Portuguese. Feel free to use the language that suits you. Markdown files with content in the respective languages will be placed inside them.

### Dictionary: terms for translation

For the translation, the first file that we are going to create is the one that has the terms to be translated on the website. Think of this file as if it were a kind of dictionary: just look for a definition to get the meaning. That's the mechanics.

```ts
export const LangStrings = {
  en: {
    about: "About",
    articles: "Articles",
    home: "Home",
    slogan: "An example site showcasing a bilingual site with GatsbyJS.",
  },
  pt: {
    about: "Sobre",
    articles: "Artigos",
    home: "Início",
    slogan: "Um site bilíngue de exemplo feito com GatsbyJS.",
  },
}
```

That simple. I saved this file as `Strings.ts` in the`/intl` directory mentioned earlier. If your project grows a lot, I recommend breaking it into different files.

### Language context

In React there is a thing called context, which is state information available to the entire application. Without it, all information has to be passed via property to each component using it, which can hinder development if there are many levels of hierarchy.

Ideally, the language should be available for the entire project, globally. For that, a context must be created. Below, the code I used to create it:

```tsx
import { createContext, useState } from "react"

export const defaultLocale = "en"
export const locales = ["pt", "en"]
export const LanguageContext = createContext([])

export const LanguageProvider: React.FC = ({ children }) => {
  const [locale, setLocale] = useState("pt")

  return (
    <LanguageContext.Provider value={[locale, setLocale]}>
      {children}
    </LanguageContext.Provider>
  )
}
```

The code is quite simple, but enough for the example in this article. The `defaultLocale` defines the website's default language, English in this case. `locales` lists available languages on the site. To add more, just add another ISO abbreviation to the list and provide the terms for translation in the file `Strings.ts`.

To make the context available in the application, a file called `_app.tsx` is created inside the `/pages` folder with the following code:

```tsx
import { AppPropsType } from "next/dist/next-server/lib/utils"
import { LanguageProvider } from "../intl/LanguageProvider"

import "./styles/layout.css"

export default function App({ Component, pageProps, router }: AppPropsType) {
  return (
    <LanguageProvider>
      <Component {...pageProps} key={router.route} />
    </LanguageProvider>
  )
}
```

This file is a special one, as it does not turn into a page, but affects the entire site. Next understands it as the entry point for the React application and things done here are available globally.

We add in it the created context of languages, importing the component `<LanguageProvider />` and involving the main component of the application with it. From now on the context will be available for other pages and components.

### Customized translation hook

As no translation library is being used for this project, to ease the use of the translated terms, a "hook" was created, a custom function called `useTranslation`. In React, the word "use" is added as a prefix in a hook function name, it is a convention. Here is the code:

```tsx
import { useContext } from "react"

import { LanguageContext, defaultLocale } from "./LanguageProvider"
import { LangStrings } from "./Strings"

export default function useTranslation() {
  const [locale] = useContext(LanguageContext)

  function t(key: string) {
    if (!LangStrings[locale][key]) {
      console.warn(`No string '${key}' for locale '${locale}'`)
    }

    return LangStrings[locale][key] || LangStrings[defaultLocale][key] || ""
  }

  return { t, locale }
}
```

Essentially, this hook reads the "dictionary" created, using the current page language, informed by the context I mentioned before. Several translation libraries use this same method, each one in its own way. What the code above does is to import the `LangStrings` dictionary and the`LangContext` context and with this information, return the translation according to the current language, or with the default language informed through the constant `defaultLocale`.

### Translating page content

My final `index.tsx` looks like this:

```tsx
import { NextPage } from "next"
import Link from "next/link"

import Layout from "../components/Layout"
import useTranslation from "../intl/useTranslation"

const Home: NextPage = () => {
  const { t } = useTranslation()

  return (
    <Layout title={t("home")} className="home">
      <section className="hero">
        <div className="message">
          <h1>Next INTL</h1>
          <p>{t("slogan")}</p>
          <Link href="/about">
            <a className="button">{t("about")}</a>
          </Link>
        </div>
      </section>
    </Layout>
  )
}

export default Home
```

Notice that in all areas where it is good to have a translation, use the function `t("term")` that comes from the created hook.

From now on, just do this in every component you want to translate: import the `useTranslation` hook, make the `t()` function available for use with `const {t} = useTranslation ()` and use the `t()` function with an existing definition in the dictionary.

### Translating dates

For dates, I used the `.toLocaleDateString` method. Below is an example for article dates:

```tsx
<span>{new Date(post.date).toLocaleDateString(locale, dateOptions)}</span>
```

Being `post.date` the text with the date, `locale` the acronym of the ISO language `dateOptions` the options for displaying the date.

```tsx
const dateOptions = {
  year: "numeric",
  month: "long",
  day: "numeric",
}
```

In the example above, `dateOption` makes the date appear like this: _July 21, 2020_. More information about these options can be found in the MDN[^1].

This article is getting big, so that's it for now! In the next few days I will post the second part of this article, with the creation of content in Markdown and listing of articles. See you later!

### Links
- [Reference](https://w11i.me/how-to-build-multilingual-website-in-next-js)
- [This article in Portuguese](https://blog.elvessousa.com.br/post/next-multilingue)
- [GitHub repo](https://github.com/elvessousa/next-intl)
- [Site made using this code](https://next-intl-ten.vercel.app/)

[^1]: [toLocaleDateString in MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString)

---

If this article helped you in some way, consider [donating](https://www.buymeacoffee.com/elvessousa). This will help me to create more content like this!