Internationalize NextJs URLs with next-translate (Part 2)
Published: 2023-03-17 14:10:55


Now that it's finally possible to [translate URLs with NextJs](https://dev.to/martinratinaud/internationalize-nextjs-urls-with-next-translate-49d8)

What would be really awesome would be to add directly `permalinks` within the javascript page.

Something like

```diff
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](https://github.com/martinratinaud/next-translate-i18n-routes/tree/urls_in_page_const)** (branch `urls_in_page_const`)

## Prerequisites

Follow the [part 1](https://dev.to/martinratinaud/internationalize-nextjs-urls-with-next-translate-49d8)

## 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](https://github.com/martinratinaud/next-translate-i18n-routes/commit/f554eea443bfefb9d12316d74c332f7d33c20339)

#### 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:

```js
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`

```diff
- 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

```diff
import { LanguageSwitcher, useTranslation, Link } from 'modules/I18n';

+ export const permalinks: { [key: string]: string } = {
+   en: '/',
+   fr: '/accueil',
+ };

const HomePage = () => {
...

```

🎉 Voila! (again)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q00jizzw1d0hlkx79etj.gif)


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](https://www.stakingcrypto.io/) and [many more...](https://www.indiehackers.com/martinratinaud)

Check my [LinkedIn](https://fr.linkedin.com/in/martinratinaud) and get in touch!

Also, I'm looking for a remote job. Hire me !