Интернационализация URL-адресов NextJs с помощью next-translate (часть 1)

Интернационализация URL-адресов NextJs с помощью next-translate (часть 1)

I18n URL были доступны во всех основных генераторах статических сайтов, но почему-то отсутствуют в NextJs, и это определенно жаль 😔.

Я француз и всегда создаю свои сайты как минимум на французском и английском языках.

Вот подробное объяснение того, как любой разработчик может добиться этого менее чем за 10 минут и наконец-то иметь возможность работать с такими урлами, как

TLDR

Репозиторий со всем исходным кодом можно найти на GitHub

Предварительные условия

В этом руководстве используется отличная библиотека next-translate, но ее можно адаптировать для next-i18next.

наличие next-translate, уже установленного в вашем проекте.

Что будет сделано

  • Генерировать правила переписывания, которые заставят URL-адреса адаптироваться в соответствии с языком, выбранным пользователем
  • Переоснастить функцию nextTranslate для использования этих перезаписей
  • Написать пользовательский компонент Link, который будет использовать эти правила переписывания для создания соответствующих i18n-слогов и использовать их для навигации.

Процедура

Во всех последующих шагах созданные файлы и функции будут помещены в папку modules/I18n.Это практика, к которой я пришел после многих лет программирования и которая очень помогает разделять части приложения (в данном случае всю логику, связанную с I18n).Скоро я напишу об этом статью в блоге.

Укажите пермалинки

Первое, что необходимо сделать, это указать пермалинки, которые мы хотим использовать.Давайте создадим modules/I18n/permalinks.json

{
  "https://dev.to/": {
    "fr": "/accueil"
  }
}

ПРИМЕЧАНИЕ: это не идеальное решение для меня, так как оно отделяет фактическую страницу (jsx файл) от определения ее пермалинков, и было бы лучше иметь экспорт const пермалинков изнутри страницы. Эта проблема рассматривается в части 2 этой статьи (и вы также можете связаться со мной, если вам нужна дополнительная информация).

Усиление функции nextTranslate

Цель здесь - преобразовать созданные нами пермалинки в правила перезаписи, чтобы NextJS мог правильно переписывать URL в зависимости от языка.

TLDR См. коммит на GitHub

Создайте modules/I18n/next.config.js с

const nextTranslate = require('next-translate-plugin');
const fs = require('fs');
const permalinks = require('./permalinks.json');

/**
 * 
 * Transforms
{
  "https://dev.to/": {
    "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];
    },
  };
};

и замените вызов функции в файле next.config.js

- const nextTranslate = require('next-translate-plugin')
+ const nextTranslate = require('./src/modules/I18n/next.config');

Отлично, теперь, если вы перезагрузите свой сервер, вы сможете получить доступ к странице

  • http://localhost:3000/fr/accueil

Теперь давайте адаптируем компонент Link, чтобы он учитывал этот новый URL

Цель состоит в том, чтобы иметь возможность переходить непосредственно к красивым URL, определенным ранее.

TLDR: См. коммит на GitHub

Необходимо создать новый компонент modules/I18n под названием Link и изменить все импорты next/link.

Да, это действительно больная тема, я признаю, но я не смог найти способ сделать по-другому.

На самом деле это не такая уж большая проблема, так как простой ”поиск и замена” будет работать

- import Link from 'next/link';
+ import { Link } from 'modules/I18n';

Во-первых, переменная permalinks должна быть открыта для фронтенда, чтобы ее мог использовать создаваемый компонент Link.

В nextJs это делается с помощью

  return {
    ...nextTranslateConfig,
+    publicRuntimeConfig: {
+      ...nextTranslateConfig.publicRuntimeConfig,
+      permalinks, // add it to publicRuntimeConfig so it can be used by the Link component
+    },
    async rewrites() {
    ...

Встроенный в nextJS компонент Link работает следующим образом: он строит URL из href и переданной (или существующей) локали.

Это означает, что ссылка на / в fr приведет к /fr.

Этот компонент создаст карту URL для прямого перехода к соответствующему правильному 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
{
  "https://dev.to/": {
    "fr": "/accueil"
  }
}
 * into
{
  "/fr/": "/fr/accueil",
  "/en/accueil": "https://dev.to/"
} 
 */
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;

И вуаля! Все готово, и вот как это выглядит.

Image description

Ссылка

Посмотрите Github Repo