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
- Create the NextJS application
- Dependencies and Adding translations files
- Setting up
i18next
- Setup pages
- 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.