WEBSITE:‣
GitHub:‣
在开发个人网站的时候,实现国际化几乎是必不可少的功能,今天给大家分享一下如何使用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
在服务器端和客户端之间没有正确同步语言设置。为了解决这个问题,可以尝试以下步骤:
- 确保服务器端和客户端的语言设置一致: 确保在服务器端和客户端都设置了正确的语言。
- 使用
next-i18next
提供的serverSideTranslations
函数: 在页面组件中使用serverSideTranslations
函数来获取翻译文件。
- 确保翻译文件的路径正确: 确保翻译文件路径和内容没有错误。
如果以上几个步骤都没有解决,在
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;