A quick walkthrough on translations with i18-next for NextJS

A quick walkthrough on translations with i18-next for NextJS

If you talk to a man in a language he understands, that goes to his head. If you talk to him in his own language, that goes to his heart. β€”Nelson Mandela

In this blog post we'll go through using NextJS translations with i18-next library, but is possible on NextJS to implement your own translation mechanism (πŸ˜† check conclusions section haha) without help of any library, the locale key is always available while using const { locale } = useRouter().

Content

  1. Create the NextJS application
  2. Dependencies and Adding translations files
  3. Setting up i18next
  4. Setup pages
  5. Conclusions

Create the NextJS application

Let's do a regular NextJS site initialization with typescript support, app will be called i18-nextjs-sample:

npx create-next-app i18-nextjs-sample

Will be prompted to install typescript dependencies and types

yarn add --dev typescript @types/react

Run to double-check initialization is fine:

yarn dev

Dependencies and Adding translations files

Time for set up i18next, of course they support NextJS . Get installed:

yarn add next-i18next

Under public folder let's create the following json files, common.json will be responsible for stuff shared across the site and home.json for specific stuff on index page:

└── public
    └── locales
        β”œβ”€β”€ en
        |   └── common.json
        |   └── home.json
        └── es
            └── common.json
            └── home.json

The English common.json to contain:

{
  "title": "Setting up Translations on NextJS"
}

The Spanish common.json to contain:

{
  "title": "Configurando multi-lenguages en NextJS"
}

The English home.json to contain:

{
  "welcome": "Welcome to <a href=\"https://nextjs.org\">Next.js!</a>"
}

The Spanish home.json to contain:

{
  "welcome": "Bienvenido a <a href=\"https://nextjs.org\">Next.js!</a>"
}

Setting up i18next

First, create next-i18next.config.js on the root

module.exports = {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es'],
  },
}

Create next.config.js in the root, if does not exist and set the i18n as a property provided by NextJS, this property also allows subdomain ( e.g: es.yourdomain.com ) localization but for now, let's keep it simple with default subpath ( e.g: yourdomain.com/es) mode :

const { i18n } = require('./next-i18next.config')

module.exports = {
  i18n,
}

Setup pages

In this example we'll set just the home page depending on common and specific home translations, the first thing is to configure the generated _app component with the appWithTranslation, this is basically the one that adds the provider under the hood for the entire application:

import { appWithTranslation } from "next-i18next";
import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default appWithTranslation(MyApp);

Then we have to go to each page, in this case only home, and set static props, as we know, NextJS generates static pages, so this helps on that part of the app cycle, we will tell that the home page will use common and home translations, for any component under the home tree, these values will be available:

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...(await serverSideTranslations(locale, ["common", "home"])),
  },
})

On the same home component we can now use the translations:

import Head from "next/head";
import Image from "next/image";
import { useTranslation } from "react-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import styles from "../styles/Home.module.css";

export default function Home() {
  const { t } = useTranslation();
  return (
    <div className={styles.container}>
      <Head>
        <title>{t("title")}</title>
      </Head>

      <main className={styles.main}>
        <h1
          className={styles.title}
          dangerouslySetInnerHTML={{ __html: t("home:welcome") }}
        />
      </main>

      <footer className={styles.footer}>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by{" "}
          <span className={styles.logo}>
            <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
          </span>
        </a>
      </footer>
    </div>
  );
}

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...(await serverSideTranslations(locale, ["common", "home"])),
  },
});

You noticed the following title key, by default it uses common.json strings:

const { t } = useTranslation();
...
<title>{t("title")}</title>
...

but also might have noticed, where we explicitly relied on namespacing, remember we created one file called home.json:

t("home:welcome")

The i18-next is very flexible on this aspect, because if you have a component that only depends on home.json, only we pass the namespace as a parameter in the hook, and t will default to home and no namespace is required when calling it on the jsx:

 const { t } = useTranslation('home')

Conclusions

As we saw is really simple to manage translations and split across different files, we define criteria on how to split them, by pages, by sections, by components, etc... We used i18-next before but also you can write your own mechanism as I said early.

Ignore this πŸ˜„ : I did a while ago for learning purpose πŸ€” a hook that obtained the locale key from Next and wrote my own t function :

export const useTranslation = () => {
  const { locale } = useRouter()
  const translations = locales[locale]
  return {
    t: (key: string) =>
      key
        .split('.')
        .reduce(
          (previous, current) => (previous ? previous[current] : key),
          translations
        ),
  }
}

But using a library offers a lot of advantages, documentation, support and so on, change language (e.g i18n.changeLanguage('es')), string interpolation... so better going that way πŸ˜† . Hope this post helps someone. Thanks.