在使用 Next.js 开发多语言网站时,我们通常会遇到一个重要问题:如何实现多语言(i18n) 🌍。
一开始,很多开发者都会选择使用 useTranslations
🤔,这是一个简单直接的工具。但是,这种方式只能在 客户端组件(Client Component) 中使用,而客户端组件对 SEO 不友好 ,这让人非常头疼。
通过查阅官方文档,我发现 next-intl/server
提供的getTranslations
和NextIntlClientProvider
。
它们可以很好地解决这个问题,同时兼顾 国际化 和 SEO 的需求。
这篇文章将从这个问题入手,帮助你理解 useTranslations
和 getTranslations
的区别,以及如何在实际项目中正确使用它们 💡。
问题:为什么客户端组件的 SEO 表现不好?
1. 什么是客户端组件?
客户端组件是需要加载 JavaScript 并在浏览器端渲染的组件。它们依赖 React 的交互功能,比如 useState
和 useEffect
。当我们使用 useTranslations
时,就把组件变成了客户端组件。
2. SEO 的问题
客户端组件在初始加载时不会直接生成完整的 HTML,而是生成一个空白的 HTML 框架,然后通过 JavaScript 在浏览器中渲染内容。这会带来以下问题:
- 搜索引擎爬虫无法获取完整内容:比如页面的标题、描述等内容可能无法被爬取。
- 初始加载变慢:需要加载额外的 JavaScript,影响用户体验。
3. 为什么 Next.js 推荐服务端组件?
Next.js 的服务端组件(Server Components)会在服务器端生成完整的 HTML,然后将其发送到浏览器。这不仅对 SEO 友好,而且减少了客户端的 JavaScript 负担。
解决方案:服务端的 getTranslations
什么是 getTranslations
?
getTranslations
是一个用于服务端组件的工具,它允许我们在服务端异步获取翻译内容。这样,我们可以在服务端完成国际化处理,并直接返回完整的 HTML,避免了 SEO 和性能问题。
对比:useTranslations
vs getTranslations
特性 | useTranslations | getTranslations |
---|---|---|
适用场景 | 客户端组件 | 服务端组件 |
SEO 表现 | 较差,内容在客户端渲染 | 优秀,内容在服务端渲染 |
是否支持异步 | 不支持,依赖上下文 | 支持,直接加载翻译文件 |
使用复杂度 | 简单,适合小型项目 | 需要服务端处理,但性能更优 |
具体用法
1. 使用 useTranslations
(客户端组件)
当你需要在组件中使用交互功能,比如动态切换语言或表单提交时,可以使用 useTranslations
。
示例:导航栏的国际化
// 文件:Navbar.tsx
'use client'; // 指定为客户端组件
import {useTranslations} from 'next-intl';
function Navbar() {
const t = useTranslations('Navbar'); // 读取命名空间 Navbar 的翻译
return (
<nav>
<a href="/">{t('home')}</a>
<a href="/about">{t('about')}</a>
</nav>
);
}
export default Navbar;
假设翻译文件 en.json
:
{
"Navbar": {
"home": "Home",
"about": "About"
}
}
效果:
- 渲染完成后,导航栏会显示翻译内容。
- 适合需要动态更新内容的场景。
问题:
- 页面初始加载时,HTML 中的导航内容是空的,这对 SEO 不友好。
2. 使用 getTranslations
(服务端组件)
当你的页面内容是静态的,或者对 SEO 友好性要求较高时,应使用 getTranslations
。
示例:首页的国际化
// 文件:page.tsx
import {getTranslations} from 'next-intl/server';
export default async function HomePage() {
const t = await getTranslations('HomePage'); // 异步获取翻译
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
假设翻译文件 en.json
:
{
"HomePage": {
"title": "Welcome to our website",
"description": "This is the best place to find amazing content."
}
}
效果:
- 页面加载时,服务器直接返回翻译后的 HTML。
- 搜索引擎爬虫可以直接读取完整内容。
- 对 SEO 和性能更友好。
问题:客户端国际化的挑战
- 非序列化的配置无法直接传递到客户端
- React 的限制要求从服务端传递到客户端的数据必须是 可序列化的,即 JSON 格式的数据。
- 配置中如
onError
回调函数或defaultTranslationValues
等非序列化的数据类型,无法直接通过服务端传递到客户端。
- 服务端和客户端的翻译内容可能不一致
- 如果页面的主内容由服务端渲染,而部分组件需要在客户端动态翻译,服务端和客户端之间的翻译内容可能出现不一致。
- 这会导致页面内容的显示错误或用户体验下降。
- 多个客户端组件需要共享翻译上下文
- 当页面中有多个客户端组件需要使用相同的翻译配置时,缺乏统一的上下文可能导致重复配置或数据不一致。
什么时候需要使用 NextIntlClientProvider
?
场景 1:页面主要内容由服务端渲染,但包含客户端动态交互
例如:首页的标题和描述由服务端渲染以保证 SEO,但页面内有一个语言切换器需要客户端交互。
场景 2:需要处理非序列化的配置
例如:需要在客户端组件中实现自定义的错误回调(onError
)、占位内容回退(getMessageFallback
)或默认富文本格式(defaultTranslationValues
)。
场景 3:多个客户端组件需要共享翻译上下文
例如:页面中有多个客户端组件(如动态表单和交互式菜单),需要统一的翻译配置和内容。
场景示例:服务端渲染页面 + 客户端语言切换器
以下是一个常见的场景:
- 博客首页的标题和描述需要通过服务端渲染,以确保搜索引擎可以抓取完整内容。
- 同时,页面需要一个动态语言切换器,用户可以在客户端切换语言。
通过结合服务端组件和 NextIntlClientProvider
,我们可以实现既对 SEO 友好又具有动态交互功能的多语言支持。
实现步骤
1. 服务端部分:通过 IntlProvider
传递翻译内容
在服务端布局文件中,加载翻译内容并传递到 IntlProvider
中。
// 文件:layout.tsx
import IntlProvider from './IntlProvider';
import {getLocale, getMessages, getTimeZone, getNow} from 'next-intl/server';
export default async function RootLayout({children}) {
const locale = await getLocale(); // 获取当前语言
const messages = await getMessages(); // 获取翻译内容
const timeZone = await getTimeZone(); // 获取时区
const now = await getNow(); // 获取当前时间
return (
<html lang={locale}>
<body>
<IntlProvider
locale={locale}
messages={messages}
timeZone={timeZone}
now={now}
>
{children}
</IntlProvider>
</body>
</html>
);
}
2. 客户端部分:自定义 IntlProvider
在客户端组件中,通过 NextIntlClientProvider
自定义翻译配置和错误回调。
// 文件:IntlProvider.tsx
'use client';
import {NextIntlClientProvider} from 'next-intl';
export default function IntlProvider({locale, messages, now, timeZone}) {
return (
<NextIntlClientProvider
locale={locale} // 当前语言
messages={messages} // 翻译内容
now={now} // 当前时间
timeZone={timeZone} // 当前时区
defaultTranslationValues={{
i: (text) => <i>{text}</i>, // 定义富文本格式
}}
onError={(error) => console.error('Translation error:', error)} // 错误处理回调
getMessageFallback={({namespace, key}) => `${namespace}.${key}`} // 自定义占位内容
/>
);
}
3. 客户端语言切换器
通过一个动态语言切换器实现客户端的交互功能。
// 文件:LanguageSwitcher.tsx
'use client';
import {useState} from 'react';
function LanguageSwitcher() {
const [language, setLanguage] = useState('en'); // 默认语言
const handleLanguageChange = (e) => {
const newLanguage = e.target.value;
setLanguage(newLanguage);
console.log(`Switched to ${newLanguage}`);
// 在实际项目中,可以通过路由跳转实现语言切换
};
return (
<select value={language} onChange={handleLanguageChange}>
<option value="en">English</option>
<option value="de">Deutsch</option>
</select>
);
}
export default LanguageSwitcher;
效果分析
- 服务端渲染的内容
- 首页的标题和描述直接在服务端生成,并包含在 HTML 中,提升 SEO 表现。
- 客户端动态语言切换
- 语言切换器在客户端动态工作,不会影响页面主内容的 SEO。
- 非序列化配置
- 通过
NextIntlClientProvider
,客户端组件可以使用自定义的错误回调和占位内容。
- 通过
总结 🌟
useTranslations
:适用于动态交互的客户端组件,但对 SEO 不友好 ❌。getTranslations
:适用于服务端渲染的静态内容,SEO 表现优秀 ✅。NextIntlClientProvider
:在以下场景非常重要 :- 页面主内容由服务端渲染,但包含客户端动态交互 💻。
- 需要在客户端处理非序列化的配置(如错误回调) ⚙️。
- 多个客户端组件需要共享翻译上下文 🌐。
通过合理使用 NextIntlClientProvider
,可以实现 SEO 友好、动态交互 和 数据一致性 的多语言支持 🧩。