Я использую встроенную поддержку i18n (интернационализация) на Next.js.

Next.js имеет встроенную поддержку интернационализированной (i18n) маршрутизации, начиная с версии 10.0.0. Вы можете указать список локалей, локаль по умолчанию и локали для конкретного домена, и Next.js автоматически выполнит маршрутизацию.
— Интернационализированная маршрутизация https://nextjs.org/docs/advanced-features/i18n-routing

Добавить конфигурацию

Добавьте i18n конфигурацию в next.config.js файл. Напишите все локали, которые вы хотите поддерживать, в locales . А также запишите локаль по умолчанию в defaultLocale .

module.exports = {
  i18n: {
    locales: ['en', 'ja'],
    defaultLocale: 'ja',
  },
}

Существует две стратегии обработки локали: маршрутизация подпути и маршрутизация домена. Маршрутизация подпути — /en /fr , Маршрутизация домена — example.com example.fr .

На этом сайте я выбрал Sub-path Routing.

Например, страница БЛОГ ( pages/blog.js ) будет такой.

  • /blog
  • /en/blog

После добавления этой конфигурации Next.js попытается автоматически определить ее в зависимости от среды пользователя.

Вы также можете проверить другие настройки в официальной документации Next.js (Internationalized Routing).

Создать переключатель

Создание Switcher позволяет пользователю менять язык.

Вы можете получить текущую локаль с помощью useRouter . А также вы можете получить список локалей, которые вы поддерживаете, на своем веб-сайте или в приложении.

import Link from "next/link";
import { useRouter } from "next/router";

export const LocaleSwitcher = () => {
  const { locale, locales, asPath } = useRouter()
  return (
    <div>
      {locales && locales.map(localeName => (
        <Link key={localeName} href={asPath} passHref locale={localeName}>
          <a className={locale === localeName ? "current" : "" }>
            {localeName}
          </a>
        </Link>
      ))}
    </div>
  )
}

Поместите в любом месте на вашем веб-сайте или в приложении.

Структура файлового каталога

Я создаю страницу сообщения и статическую страницу с файлом уценки. Я добавил file-name.locale.md для отображения локализованной страницы.

src/
└── contents/
    ├── pages/
    |   ├── about.en.md
    |   └── about.md
    |
    └── posts/
        ├── blog/
        |   ├── blog-post.en.md
        |   └── blog-post.md
        |
        └── tips/
            ├── tips-post.en.md
            └── tips-post.md

Создать компонент

На /src/pages/about.tsx (страница О нас) этого сайта,

  • Получите содержимое с помощью getStaticProps
  • Получить данные из файла уценки с помощью getPageData()
  • Конвертировать в HTML и отображать данные с отмеченным

Это фрагмент кода с этого сайта. Получите locale в getStaticProps и перейдите к getPageData(), чтобы получить файл уценки.

import { GetStaticProps, NextPage } from "next";
import { marked } from "marked";

import { getPageData } from "@utils/post";
import { Page } from "@libs/types";

const About: NextPage<{ post: Page }> = ({ post }) => {
  return (
    <div
      className={post.data.slug}
      dangerouslySetInnerHTML={{ __html: marked(post.content) }}
    />
  )
}

export default About

export const getStaticProps: GetStaticProps = async ({ locale }) => {
  const post = await getPageData("pages", "about", locale)

  return {
    props: {
      post,
    }
  }
}

Это пример для getPageData() . В locale === "ja" ? postSlug : postSlug + "." + locale, какой файл уценки я получу, зависит от текущей локали: about.md или about.en.md.

import fs from "fs";
import path from "path";
import matter from "gray-matter";

export function getPageData(
  type: string, 
  postIdentifier: string,
  locale: string = "ja",
) {
  // .md を除いたファイル名を取得
  const postSlug = postIdentifier.replace(/\.md$/, "");
  // locale に応じたファイルのパスを取得
  const filePath = path.join(contentsDirectory, type, `${locale === "ja" ? postSlug : postSlug + "." + locale}.md`);
  const readFile = fs.readFileSync(filePath, "utf-8");
  const { data, content } = matter(readFile);

  const postData = {
    slug: postSlug,
    data,
    content
  };

  return postData
}

投稿ページや記事の一覧ページも同様に、基本的には getStaticProps で локаль を取得し、 markdown ファイルを取得する функция に渡して、 локаль に応じたファイルを取得して表示します。

Страница со списком сообщений

Я добавил locale в файл уценки для удобства.

---
title: My Blog Post
locale: en
date: "2022-02-11"
category: "blog"
---

...

Получение локали с getStaticProps и переход к getPosts() для получения файла уценки.

export const getStaticProps: GetStaticProps = async ({ locale }) => {
  const tipsPosts = await getTypePosts("tips", locale)

  const sortedPosts = tipsPosts.sort((postA, postB) => postA.data.date > postB.data.date ? -1 : 1)

  return {
    props: {
      posts: sortedPosts,
    },
    revalidate: 30
  }
}

После получения всего файла верните файл уценки, который имеет ту же локаль с текущей локалью, используя filter() .

export function getPosts(
  type: string,
  locale: string = "ja"
) {
    // get the files from specific directory
  const files = getPostsDirectory(type);

  // get the data contains locale from files
  const allPosts = files.map(file => {
    return getPostMeta(file, type, locale)
  });

  let filteredPosts = [];
  // filter the file which has same locale with current locale
  if (locale === "ja") {
    filteredPosts = allPosts.filter(post => post.data.locale === "ja")
  } else { /** en */
    filteredPosts = allPosts.filter(post => post.data.locale === "en")
  };

  return filteredPosts
}

Страница сведений о публикации

Отображение страницы сведений о публикации с использованием динамических маршрутов Next.js.

src/
└── pages/
    └── tips/
        └── [slug].tsx

In [slug].tsx

  • Получить список путей с помощью getPostPaths()
  • Создать список путей с getStaticPaths для рендеринга
  • Получить данные из файла уценки с помощью getPostByPath()
  • Получите контент с помощью getStaticProps
  • Конвертировать в HTML и отображать данные с отмеченным

Пример: getStaticPaths в [slug].tsx

export const getStaticPaths: GetStaticPaths = async ({ locales }) => {
    // Get the path list
  const postPaths = getPostsPaths("tips")

  let paths: any[] = []
  // Generate pass list with map() for rendering
  postPaths.map((path: string) => {
    locales?.map((locale: string) => {
      paths.push({
        params: { slug: `${path}` },
        locale,
      })
    })
  })

  return {
    paths,
    fallback: "blocking"
  }
}

И paths будет таким

{ params: { slug: 'tips-post1' }, locale: 'ja' },
{ params: { slug: 'tips-post1' }, locale: 'en' }

Пример: getPostByPath() для получения файла уценки

export function getPostByPath(
  path: any,
  type: string,
  locale: string = "ja",
) {
  let postData: Post = initialPost;
  const files = getPostsDirectory(type);

  // get the path
  const filteredFiles = files.filter(file => {
    return file.includes(path)
  })

  filteredFiles.map((file) => {
    const tempData = getPost(file, type, locale)
    // return the data from markdown
    // if markdown has the same locale with current locale
    // if it's not, return false
    if (tempData.data.locale === locale) {
      postData = getPost(file, type, locale)
    } else {
      return false
    }
  })

  return postData
}

Вернитесь к файлу [slug].tsx, получите локаль в getStaticProps и перейдите к getPostByPath(), получите данные из уценки, а затем передайте данные компоненту для отображения данных.

export const getStaticProps: GetStaticProps = async ({ locale, params }) => {
  const post = await getPostByPath(params!.slug, "tips", locale)

  return {
    props: {
      post: post,
      slug: params!.slug,
    },
    revalidate: 600
  }

Если страница еще не доступна на английском языке

В этом случае добавьте <meta name="robots" content="noindex" /> к тегу заголовка.

Пример: [slug].tsx

На этом сайте для удобства я добавил localized: boolean в файл уценки. А также отображать сообщение.

import Head from "next/head";

const TipsPost: NextPage<PostProps> = ({ post, slug, tags }) => {
  return(
    <>
      <Head>
        {locale === "en" && !post.data.localized && <meta name="robots" content="noindex" />}
      </Head>
      <article>
        <p>This page is not available in English yet.</p>
      <article>
    </>
  )
}

Рекомендации

Первоначально опубликовано 14 февраля 2023 г. на https://chocolat5.com.