Internationalize NextJs URLs with next-translate (Part 2)

martinratinaud

Martin Ratinaud

Posted on March 17, 2023

Internationalize NextJs URLs with next-translate (Part 2)

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;
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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 = () => {
...

Enter fullscreen mode Exit fullscreen mode

🎉 Voila! (again)

Image description

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 !

💖 💪 🙅 🚩
martinratinaud
Martin Ratinaud

Posted on March 17, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

What was your win this week?
weeklyretro What was your win this week?

November 29, 2024

Where GitOps Meets ClickOps
devops Where GitOps Meets ClickOps

November 29, 2024

How to Use KitOps with MLflow
beginners How to Use KitOps with MLflow

November 29, 2024

Modern C++ for LeetCode 🧑‍💻🚀
leetcode Modern C++ for LeetCode 🧑‍💻🚀

November 29, 2024