Infoxicator.com

How I translated my Next.js Blog

Published
cover image

English is my second language and before I was proficient, I always found it really difficult to find tech resources in my own language. That’s why I decided to translate my blog and make all my content available in Spanish.

translate-gif

Internationalization with Next.js

Next.js has made Internationalization (i18n) a breeze with one of the most recent advanced features available in version 10: Internationalized Routing.

From the two options provided by Next.js, I have decided to use Sub-path Routing instead of Domain Routing since I don’t want to create a subdomain for the Spanish version of my blog.

Basic Configuration

next.config.js

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

This is the only setup required to enable “Automatic Language Detection“.

The automatic language detection system provided by Next.js will redirect users to the /es path prefix if their browsers are set to Spanish (or any of its regional variations) as their default language. Next.js will look at the Accept-Language HTTP Header and try to set the correct language, however, if the language doesn’t match, it will use the default language. For example, if users have German (DE-de) as their browser language, it will set the current language to English (en).

Managing Locale Files

Next.js doesn’t prescribe a way of managing your locale data or what i18n library you should use (or if you need a library at all).

For small projects (like in my case) a simple key-value pair JSON file does the job, however, for a large application, a more robust solution is recommended to avoid things like a bloated bundle size.

I have created a folder called locale and created a single JSON file per language. i.e. locale/en.json and locale/es.json

{
  "greeting": "Hola amigos!"
}

We could use the key to render the value of the translated language without any library, however, I want to use react-intl since it is one of the most popular i18n libraries out there.

Add the following configuration to your _app.js

import '../styles/index.css'
import { IntlProvider } from 'react-intl';
import { useRouter } from "next/router"

const languages = {
  en: require('../locale/en.json'),
  es: require('../locale/es.json')
};

function MyApp({ Component, pageProps }) {
  const router = useRouter()
  const { locale, defaultLocale } = router;
  const messages = languages[locale];
  return <IntlProvider messages={messages} locale={locale} defaultLocale={defaultLocale}>
      <Component {...pageProps} />
      </IntlProvider>
}

export default MyApp

In the snippet above, we are wrapping our entire application with the IntlProvider and passing the messages and the locale that we obtained from the useRouter() hook. Now we can use react-intl components like FormatedMessage throughout our application.

import { FormattedMessage } from 'react-intl'

export default function Greeting() {
return (
  <h1><FormattedMessage id="greeting" /></h1>
)}

Changing The Language

When switching languages, I want to persist the user’s selection so if they visit my blog again it will set the language to their preferred language rather than the locale detected automatically by Next.js.

To achieve this, we can use the Next.js Locale Cookie:

import { useRouter } from "next/router"
import { useCookies } from 'react-cookie';

export default function LanguageSwitcher() {
  const [ cookie, setCookie ] = useCookies(['NEXT_LOCALE']);
  const router = useRouter();
  const { locale } = router;

  const switchLanguage = (e) => {
    const locale = e.target.value;
    router.push('/','/', { locale });
    if(cookie.NEXT_LOCALE !== locale){
      setCookie("NEXT_LOCALE", locale, { path: "/" });
    }
  }

  return (
    <select
      onChange={switchLanguage}
      defaultValue={locale}
    >
      <option value="en">EN</option>
      <option value="es">ES</option>
    </select>
  );
}

getStaticProps For Each Locale

To render the list of blog posts in the selected language, the locale parameter will be available in getStaticProps so I can pass it to my data fetching function.

For Example:

export async function getStaticProps({ locale }) {
  const allPosts = await getAllPostsForHome(locale)
  return {
    props: { allPosts },
  }
}

Search Engine Optimization

Next.js will add a global lang HTML attribute to your site with the current locale, however, if you have a custom _document.js file make sure that you remove any hardcoded lang values you might have set there.

To tell search engines about alternative versions of your posts in different languages, we must set a hreflang meta tag for each language (including the original language) to the head section of our blog posts page.

For Example:

import Head from 'next/head'

export default function Post({ post }) {
return (
...
  <article>
    <Head>
        <link rel="alternate" hreflang={locale} href={`${SITE_URL}${locale}/${post?.slug}`} />
        <link rel="alternate" hreflang={altLocale} href={`${SITE_URL}${altLocale}/${altPost?.slug}`} />
    </Head>
  </article>
...
)}

Conclusion

Internationalization (i(18 letters)n) used to be a complex task, however, with the aid of Meta-Frameworks like Next.js and tools like react-intl, providing localised text and translated data to our users has never been easier!.

I hope you enjoy my (translated) content and to my Spanish speaking friends out there, nos vemos pronto!.


Recent Posts

Is React going anywhere?

Earlier this year I had an interesting conversation with a CTO of a price comparison website (e-commerce) and he mentioned that they are moving away from React. “Wait, what?”… was my Reaction (pun 👊 intended)… please tell me more! “Yeah, it is not working for us, we are moving away for performance reasons, e-commerce is[…]

React Router 6 Deferred Fetch

React Router 6 introduced the “deferred” API that allows you to “await” for critical data and “defer” optional data when calling your loaders.

React Router 6.4 Code-Splitting

Single Page Applications that are unable to migrate can benefit from all of the goodies 🎁 that Remix provides.