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.
Final site
Initial setup
To set up the project in Next.js just use this command in the terminal:
yarn create next-app next-intl
If you are using NPM, use this other one.
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
ornpm 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.
yarn add typescript @types/node @types/react -D
Or:
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 ashttp://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 andabout.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.
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:
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:
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:
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 theLangContext
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:
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:
<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.
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 MDN1.
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!