This post is also available in Spanish here: https://www.infoxicator.com/es/traduciendo-mi-blog-con-next-js
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.
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!.