DEV Community

Martin Ratinaud
Martin Ratinaud

Posted on

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 !

Top comments (0)