Internationalize NextJs URLs with next-translate (Part 1)
Martin Ratinaud
Posted on March 17, 2023
I18n URLs have been available in all major static sites generator but is somehow lacking in NextJs and this is definitely a pity š.
I'm french and always create my websites in at least french and english.
Here is a detailed explanation on how any developer can achieve this in less than 10 min and finally be able to handle urls such as
- /
- /fr/accueil
TLDR
Repository with all source code can be found on GitHub
Prerequisites
This tutorial is using the excellent next-translate library but can be adapted for next-i18next.
- having next-translate already setup in your project
What will be done
- Generate
rewrites
rules that will make the URLs adapt accordingly to the language selected by the user - Supercharge the
nextTranslate
function to use thoserewrites
- Write a custom
Link
component that will use thoserewrites
to generate corresponding i18n slugs and use them for navigation
Procedure
In all the following steps, files and functions created will be put in a modules/I18n
folder.
This is a practice I came up with after many years of programming and that helps a lot for separating parts of applications (In this case, all I18n related logic).
I will soon write a blog post about it.
Specify permalinks
First thing to be done is to specify the permalinks we want to use.
Let's create a modules/I18n/permalinks.json
{
"/": {
"fr": "/accueil"
}
}
NOTE: this is not an ideal solution for me as it separates the actual page (the jsx file) from the definition of its permalinks and it would be better to have an export const permalinks
from within the page. This problem ias addressed in the part 2 of this article (and you can contact me also if you want more info).
Supercharge nextTranslate
function
The goal here is to transform the permalinks we created into rewrite rules so that NextJS can rewrite correctly the URL depending on the language.
TLDR See commit on GitHub
Create a modules/I18n/next.config.js
with
const nextTranslate = require('next-translate-plugin');
const fs = require('fs');
const permalinks = require('./permalinks.json');
/**
*
* Transforms
{
"/": {
"fr": "/accueil"
}
}
* into
[
{
source: '/fr/accueil',
destination: '/fr',
locale: false
}
]
*/
const permalinksToRewriteRules = (permalinks) =>
Object.entries(permalinks).reduce(
(acc, [originalSlug, permalinks]) => [
...acc,
...Object.entries(permalinks).reduce(
(acc2, [locale, i18nSlug]) => [
...acc2,
{
source: `/${locale}${i18nSlug}`,
destination: `/${locale}${originalSlug}`,
locale: false,
},
],
[]
),
],
[]
);
module.exports = (nextConfig) => {
const nextTranslateConfig = nextTranslate(nextConfig);
return {
...nextTranslateConfig,
async rewrites() {
const existingRewrites = nextTranslateConfig.rewrites
? await nextTranslateConfig.rewrites()
: [];
return [...permalinksToRewriteRules(permalinks), ...existingRewrites];
},
};
};
and replace the call to the function in next.config.js
- const nextTranslate = require('next-translate-plugin')
+ const nextTranslate = require('./src/modules/I18n/next.config');
Awesome, now, if you reload your server, you will be able to access
Now, let's adapt the Link component to take this new URL into account
Adapt Link component
Goal is to be able to navigate directly to the nice URLs defined earlier.
TLDR: See commit on GitHub
A new modules/I18n
component called Link
has to be created and all imports of next/link
has to be modified.
Yes, that's really a pain point, I admit but i could not find a way to do other wise.
This is in fact not a big problem as a simple "search and replace" will work
- import Link from 'next/link';
+ import { Link } from 'modules/I18n';
First, the permalinks
variable has to be exposed to the frontend in order to be used by the Link
component that will be created.
In nextJs, this is done with
return {
...nextTranslateConfig,
+ publicRuntimeConfig: {
+ ...nextTranslateConfig.publicRuntimeConfig,
+ permalinks, // add it to publicRuntimeConfig so it can be used by the Link component
+ },
async rewrites() {
...
How the nextJS built in Link
component works is it will build up the URL from the href
and the passed (or existing) locale
.
This means a link to /
in fr
will lead to /fr
This component will create a map of URLs to route directly to the corresponding correct URL /fr/accueil
import React from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import getConfig from 'next/config';
const { publicRuntimeConfig } = getConfig();
const permalinks: { [key: string]: { [key: string]: string } } =
publicRuntimeConfig.permalinks || {};
/**
* Formats permalinks
{
"/": {
"fr": "/accueil"
}
}
* into
{
"/fr/": "/fr/accueil",
"/en/accueil": "/"
}
*/
export const i18nFallbackUrls: { [key: string]: string } = Object.entries(
permalinks
).reduce(
(acc, [originalSlug, permalinks]) => ({
...acc,
...Object.entries(permalinks || {}).reduce(
(acc2, [locale, permalink]) => ({
...acc2,
[`/${locale}${originalSlug}`]: `/${locale}${permalink}`,
[`/en${permalink}`]: originalSlug,
}),
{}
),
}),
{}
);
const I18nLink = ({ href, locale, ...props }: any) => {
const router = useRouter();
const wantedLocale = locale || router.locale;
let i18nProps: any = {
href,
locale,
};
if (i18nFallbackUrls[`/${wantedLocale}${href}`]) {
i18nProps = {
href: i18nFallbackUrls[`/${wantedLocale}${href}`],
locale: false,
};
}
return <Link {...i18nProps} {...props} />;
};
export default I18nLink;
And voila!. It's all done and here is how it looks like
Reference
Check out the Github Repo
Who am I?
My name is Martin Ratinaud and Iām a senior software engineer and remote enthusiast, contagiously happy and curious.
I create websites like this one for staking crypto and many more...
Check my LinkedIn and get in touch!
Also, I'm looking for a remote job. Hire me !
Posted on March 17, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.