加载翻译

使用 provider 管理消息

问题

将诸如 'Hello World' 这样的文本直接硬编码到应用组件中,会导致内容与代码耦合。要显示不同的语言,开发者必须复制组件或添加 if/else 逻辑,这使得翻译难以扩展,并且每增加一段新文本都需要修改代码。

解决方案

使用 react-intlIntlProvider 提供翻译。在服务端的根布局中加载翻译消息,将其传递给客户端的 provider 组件,然后在其他客户端组件中通过 useIntl hook 获取。

步骤

1. 安装 react-intl

首先,将 react-intl 作为依赖添加到你的项目中。

npm install react-intl

2. 创建扁平化翻译文件

创建一个 dictionaries 文件夹。在其中为每种语言添加一个 JSON 文件。react-intl 最适合使用扁平的键值结构。

// dictionaries/en.json
{
  "home.title": "Home Page",
  "home.welcome": "Hello, welcome to our site!",
  "about.title": "About Us"
}
// dictionaries/es.json
{
  "home.title": "Página de Inicio",
  "home.welcome": "¡Hola, bienvenido a nuestro sitio!",
  "about.title": "Sobre Nosotros"
}

3. 创建加载字典的函数

创建一个辅助函数,根据 lang 参数在服务端加载对应的字典文件。

// app/get-dictionary.ts
import 'server-only';

// Define the type for our flat message object
type Messages = Record<string, string>;

const dictionaries: { [key: string]: () => Promise<Messages> } = {
  en: () => import('@/dictionaries/en.json').then((module) => module.default),
  es: () => import('@/dictionaries/es.json').then((module) => module.default),
  // fr: () => import('@/dictionaries/fr.json').then((module) => module.default),
};

export const getDictionary = async (lang: string) => {
  const load = dictionaries[lang];
  if (load) {
    return load();
  }
  // Fallback to English
  return dictionaries.en();
};

4. 创建客户端 provider

IntlProvider 是一个使用 React Context 的客户端组件。我们需要为其创建一个包装器,以便能够接收从服务端加载的消息。

// app/components/IntlClientProvider.tsx
'use client';

import { IntlProvider } from 'react-intl';

type Props = {
  children: React.ReactNode;
  locale: string;
  messages: Record<string, string>; // Flat messages object
};

export default function IntlClientProvider({
  children,
  locale,
  messages,
}: Props) {
  return (
    <IntlProvider messages={messages} locale={locale} defaultLocale="en">
      {children}
    </IntlProvider>
  );
}

5. 更新根布局

app/[lang]/layout.tsx 修改为 async 组件。它会加载消息并将其传递给 IntlClientProvider

// app/[lang]/layout.tsx
import { getDictionary } from '@/app/get-dictionary';
import IntlClientProvider from '@/app/components/IntlClientProvider';

export async function generateStaticParams() {
  return [{ lang: 'en' }, { lang: 'es' }];
}

export default async function RootLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { lang: string };
}) {
  // Load messages on the server
  const messages = await getDictionary(params.lang);

  return (
    <html lang={params.lang}>
      <body>
        {/* Pass messages to the client provider */}
        <IntlClientProvider locale={params.lang} messages={messages}>
          {children}
        </IntlClientProvider>
      </body>
    </html>
  );
}

6. 在客户端组件中使用翻译

现在,您可以在任何客户端组件中使用 useIntl hook。服务端组件无法使用此 hook。

创建一个新的客户端组件以显示翻译文本:

// app/components/HomePageContent.tsx
'use client';

import { useIntl } from 'react-intl';

export default function HomePageContent() {
  const intl = useIntl();

  return (
    <div>
      <h1>{intl.formatMessage({ id: 'home.title' })}</h1>
      <p>{intl.formatMessage({ id: 'home.welcome' })}</p>
    </div>
  );
}

7. 将组件添加到页面中

最后,将新的客户端组件添加到您的页面。

// app/[lang]/page.tsx
import HomePageContent from '@/app/components/HomePageContent';

export default function Home() {
  // This page is a Server Component
  return (
    <div>
      {/* It renders the Client Component */}
      <HomePageContent />
    </div>
  );
}