05.【个人网站】基于i18n在Next项目中搭建国际化网站

258 阅读3分钟

WEBSITE:www.jessieontheroad.com/ GitHub:github.com/Jessie-jzn

在开发个人网站的时候,实现国际化几乎是必不可少的功能,今天给大家分享一下如何使用i18n在Next项目中实现国际化。

安装依赖

npm install next react-i18next i18next next-i18next

项目结构

.
├── next.config.js
├── next-i18next.config.js
├── i18n.js
├── package.json
├── pages
│   ├── _app.js
│   └── index.js
└── public
    └── locales
        ├── en
        │   └── common.json
        └── zh
            └── common.json

配置文件

next-i18next.config.js

// next-i18next.config.js
const path = require("path");

module.exports = {
  i18n: {
    defaultLocale: "zh",
    locales: ["en", "zh"],
  },
  localePath: path.resolve("./public/locales"),
  // react: { useSuspense: false },
};

next.config.js

const { i18n } = require('./next-i18next.config');

module.exports = {
  i18n,
};

i18n.js

// i18n.js
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import nextI18NextConfig from "./next-i18next.config";
import en from "./public/locales/en/common.json";
import zh from "./public/locales/zh/common.json";

i18n.use(initReactI18next).init({
  resources: {
    en: { translation: en },
    zh: { translation: zh },
  },
  fallbackLng: nextI18NextConfig.i18n.defaultLocale,
  lng: nextI18NextConfig.i18n.defaultLocale,
  keySeparator: false,
  interpolation: {
    escapeValue: false,
  },
});

export default i18n;

应用程序入口

pages/_app.js

import { appWithTranslation } from 'next-i18next';
import '../styles/globals.css';

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default appWithTranslation(MyApp);

pages/index.js

import { GetStaticProps } from "next";
import NotionService from "@/lib/notion/NotionServer";
import { NOTION_HOME_ID } from "@/lib/constants";
import NotionPage from "@/components/NotionPage";

import { serverSideTranslations } from "next-i18next/serverSideTranslations";

const notionService = new NotionService();
export const getStaticProps: GetStaticProps = async ({ locale }: any) => {
  const post = await notionService.getPage(NOTION_HOME_ID);

  return {
    props: {
      post,
      ...(await serverSideTranslations(locale, ["common"])),
    },
    revalidate: 10,
  };
};
const Home = ({ post }: any) => {
  return (
    <>
      <NotionPage recordMap={post} />
    </>
  );
};
export default Home;

国际化文件

public/locales/en/common.json

{
  "home": "Home",
  "post": "Post",
  "tags": "Tags",
  "projects": "Projects",
  "about": "About",
  "contact": "Contact Me"
}

public/locales/zh/common.json

{
  "home": "首页",
  "post": "文章",
  "tags": "标签",
  "projects": "项目",
  "about": "关于我",
  "contact": "联系"
}

组件使用

示例组件 ExampleComponent.js

import { useTranslation } from 'react-i18next';

const ExampleComponent = () => {
  const { t } = useTranslation();

  return <h1>{t('welcome')}</h1>;
};

export default ExampleComponent;

语言切换器 LanguageSwitch.tsx

import { useState } from "react";
import { useRouter } from "next/router";

const LanguageSwitcher = () => {
  const [dropdownVisible, setDropdownVisible] = useState(false);
  const router = useRouter();
  const { locale, locales, pathname, asPath, query } = router;

  const toggleDropdown = () => {
    setDropdownVisible(!dropdownVisible);
  };

  const changeLanguage = (e) => {
    const selectedLocale = e.target.getAttribute("data-lang");
    router.push({ pathname, query }, asPath, { locale: selectedLocale });
    setDropdownVisible(false); // Hide dropdown after selecting a language
  };

  return (
    <div className="relative inline-block text-left">
      <button
        className="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
        onClick={toggleDropdown}
      >
        <span className="mr-2">{locale === "en" ? "🇺🇸" : "🇨🇳"}</span>
        {locale === "en" ? "English" : "中文"}
      </button>
      {dropdownVisible && (
        <div className="origin-top-right z-10 absolute right-0 mt-2 w-30 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5">
          {locales?.map((loc) => (
            <div
              key={loc}
              data-lang={loc}
              onClick={changeLanguage}
              className="flex items-center px-4 py-2 text-sm text-gray-700 cursor-pointer hover:bg-gray-100"
            >
              <span className="mr-2">{loc === "en" ? "🇺🇸" : "🇨🇳"}</span>
              {loc === "en" ? "English" : "中文"}
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

export default LanguageSwitcher;

踩坑

在实际开发的时候会遇到这样的报错

Error: Text content does not match server-rendered HTML.
Text content did not match. Server: "欢迎" Client: "welcome"

这个错误通常是因为在服务器端渲染(SSR)期间生成的内容与在客户端渲染(CSR)期间生成的内容不匹配。可能的原因是 i18next 在服务器端和客户端之间没有正确同步语言设置。

为了解决这个问题,可以尝试以下步骤:

  1. 确保服务器端和客户端的语言设置一致: 确保在服务器端和客户端都设置了正确的语言。
  2. 使用 next-i18next 提供的 serverSideTranslations 函数: 在页面组件中使用 serverSideTranslations 函数来获取翻译文件。
  3. 确保翻译文件的路径正确: 确保翻译文件路径和内容没有错误。

如果以上几个步骤都没有解决,在home.tsx中的getStaticProps加入...(await serverSideTranslations(locale, ["common"]))

import { GetStaticProps } from "next";
import NotionService from "@/lib/notion/NotionServer";
import { NOTION_HOME_ID } from "@/lib/constants";
import NotionPage from "@/components/NotionPage";

import { serverSideTranslations } from "next-i18next/serverSideTranslations";

const notionService = new NotionService();
export const getStaticProps: GetStaticProps = async ({ locale }: any) => {
  const post = await notionService.getPage(NOTION_HOME_ID);

  return {
    props: {
      post,
      ...(await serverSideTranslations(locale, ["common"])),
    },
    revalidate: 10,
  };
};
const Home = ({ post }: any) => {
  return (
    <>
      <NotionPage recordMap={post} />
    </>
  );
};
export default Home;