Internationalize NextJs URLs with next-translate (Part 2)
Martin Ratinaud
Posted on March 17, 2023
Now that it's finally possible to translate URLs with NextJs
What would be really awesome would be to add directly permalinks
within the javascript page.
Something like
import { LanguageSwitcher, useTranslation } from 'modules/I18n';
+ export const permalinks = {
+ en: '/',
+ fr: '/accueil',
+ };
const HomePage = () => {
const { t } = useTranslation();
return (
<section>
<LanguageSwitcher />
<br />
<br />
<div>{t('common:variable-example', { count: 42 })}</div>
</section>
);
};
export default HomePage;
Ok, let's do this then
TLDR
Repository with all source code can be found on GitHub (branch urls_in_page_const
)
Prerequisites
Follow the part 1
What will be done
Instead of having all permalinks stored in a permalinks.json
file, simply remove this file and export a constant directly from the page itself.
It will be a lot clearer for developers and is the approach commonly used in static blogs sites (usually a permalinks
entry in a frontmatter
variable)
Procedure
Idea here is to loop over all page files and extract the corresponding permalinks.
TLDR: See commit on GitHub
Add some utils
For listing all pages and read the exported const, let's use the fact that all pages are listed in the pages
folder.
Simply deep scan them and read their content.
Here, I used a very "not perfect" hack that does the job.
As the codebase is in typescript but next.config.js
is a js
file, it is not possible to simply import the files and read the permalinks
object.
I thus used ChatGPT to come up with the perfect regexp to do so 🤩.
When NextJS will support next.config.ts
, this can be easily improved but it does the work for now.
Create a modules/I18n/utils/index.js
file which will extract the permalinks from the pages:
const fs = require('fs');
const path = require('path');
const listFilesRecursively = (dir) => {
const filesAndFolders = fs.readdirSync(dir, { withFileTypes: true });
return filesAndFolders.flatMap((entry) => {
const entryPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
return listFilesRecursively(entryPath);
}
return entryPath;
});
};
const parseJavascriptJson = (jsonString) => {
const validJsonString = jsonString
.replace(/(\s*[\{\},]\s*)(\w+)(\s*):/g, '$1"$2"$3:') // Add quotes around keys
.replace(/'/g, '"') // Replace single quotes with double quotes
.replace(/,\s*}/g, '}'); // Remove trailing commas
try {
const jsonObj = JSON.parse(validJsonString);
return jsonObj;
} catch (error) {
console.error('Error parsing JSON:', error);
}
};
const getPagePermalinks = () => {
const pagePermalinks = {};
const containingFolder = ['src', 'pages'];
const filenames = listFilesRecursively(
path.join(process.cwd(), ...containingFolder)
);
filenames.forEach((filepath) => {
const content = fs.readFileSync(filepath).toString();
// Read the file and extract the permalinks object
// This could be done with a require, but it would not work with typescript
// files
// This is definitely a quick working hack 😎
const match = /(?<=const permalinks[^=]*= )\{[\s\S]*?\};/gm.exec(content);
const jsonString = match && match[0].replace(';', '');
if (jsonString) {
const relativeFilePath = filepath.replace(
path.join(process.cwd(), ...containingFolder),
''
);
const parsedUri = path.parse(relativeFilePath);
const uri = `${parsedUri.dir}${
parsedUri.name === 'index' ? '' : parsedUri.name
}`;
// Depending on the eslint rules, javascript object may not be straightly convertible in JSON, so parse it
const routes = parseJavascriptJson(jsonString);
pagePermalinks[uri] = routes;
}
});
return pagePermalinks;
};
module.exports = { getPagePermalinks };
Then, use this function instead of the permalinks.json
file in next.config.js
- const permalinks = require('./permalinks.json');
+ const { getPagePermalinks } = require('./utils');
+ const permalinks = getPagePermalinks();
Finally, remove the permalinks.json
file and add the export to the pages/index.tsx
page
import { LanguageSwitcher, useTranslation, Link } from 'modules/I18n';
+ export const permalinks: { [key: string]: string } = {
+ en: '/',
+ fr: '/accueil',
+ };
const HomePage = () => {
...
🎉 Voila! (again)
You can now enjoy handling permalinks directly from your pages components and I find it awesome.
What about you?
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.