安装next-inti
pnpm install next-intl
目录结构
首先需要在next.config.ts 去引入一下next-intl
import createNextIntlPlugin from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default withNextIntl(nextConfig);
在app目录下新建 config.ts
// app/config.ts
export const locales = ["en", "cn"] as const;
export type Locales = typeof locales;
在app目录下新建messages语言存放文件夹
在app目录下新建 i18n.ts
// app/config.ts
import { getRequestConfig } from "next-intl/server";
import { notFound } from "next/navigation";
import { locales } from "./config";
export default getRequestConfig(async ({ locale }) => {
if (!locales.includes(locale as any)) notFound();
return {
messages: (await import(`./messages/${locale}.json`)).default,
};
});
-
getRequestConfig来自next-intl/server:这个函数是用于在服务器端渲染(SSR)期间动态加载语言特定消息的关键函数。 -
locales来自./config:这个变量可能从一个单独的配置文件中导入支持的语言环境的数组(例如,['en', 'es', 'fr']) -
return { messages: (await import(./messages/${locale}.json)).default };:这是配置的核心。它定义了如何加载特定于语言的消息: -
messages:这个键由next-intl库用于访问加载的消息。 -
await import(./messages/${locale}.json):这个动态地导入一个包含指定locale的翻译消息的 JSON 文件。文件路径通过将./messages/与locale连接并添加.json扩展名来构建。
在app目录下新建 middleware.ts
import createMiddleware from "next-intl/middleware";
import { locales } from "./config";
export default createMiddleware({
locales,
defaultLocale: "en",
});
export const config = {
// Match only internationalized pathnames
matcher: ["/", "/(cn|en)/:path*"],
};
-
定义支持语言: 通过
locales数组指定应用程序支持的语言。 -
设置默认语言: 如果用户未设置语言偏好,则使用
defaultLocale作为默认语言。 -
匹配国际化路径: 中间件只处理配置的路径,确保只有需要国际化的页面被处理。
-
处理语言切换: 当用户访问不同的语言路径时,中间件会根据路径中的语言代码来切换语言。
使用国际化
我们需要在app路由下面新建[locale目录] 用来存放所有的目录文件 把最外面的 以及layout.tsx 移入进来
重新启动项目,会发现项目的路径会添加上国际化的标识
使用next-intl 的useTranslations 方法去替换原本语言
import { Button } from "@/components/ui/button";
import { useTranslations } from "next-intl";
export default function Home({ params: { locale } }: Props) {
const t = useTranslations();
return (
<div>
<Button size="sm" variant="secondary">
{t("file")}
</Button>
</div>
);
}
使用Link的时候会自动添加上国际化的前缀
当我们要使用next的Link组件进行路由跳转的时候,会发现没有带上国际化的前缀,这个时候就要用next-intl提供的路由方法去替换原生的方法
//app/config.ts
import { Pathnames, LocalePrefix } from "next-intl/routing";
export const locales = ["en", "cn"] as const;
export type Locales = typeof locales;
export const pathnames: Pathnames<Locales> = {
"/": "/",
"/pathnames": "/pathnames",
};
export const localePrefix: LocalePrefix<Locales> = "always";
添加navigation.ts去替换原生的路由跳转方法
import { createNavigation } from "next-intl/navigation";
import { localePrefix, locales, pathnames } from "./config";
export const { Link, getPathname, redirect, usePathname, useRouter } =
createNavigation({
locales,
pathnames,
localePrefix,
});
在之前的page.tsx 中使用
import { Button } from "@/components/ui/button";
import { useTranslations } from "next-intl";
import { Link } from "@/navigation";
export default function Home({ params: { locale } }: Props) {
const t = useTranslations();
return (
<div>
<Button size="sm" variant="secondary">
{t("file")}
</Button>
<Link href="/editor/123">跳转</Link>
</div>
);
}
编写国家化切换组件
"use client";
import { cn } from "@/lib/utils";
import { usePathname, useRouter } from "next/navigation";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useLocale } from "next-intl";
import { locales } from "@/config";
const langIcon = (
<svg
viewBox="0 0 24 24"
focusable="false"
width="1em"
height="1em"
fill="currentColor"
aria-hidden="true"
>
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z "
className="css-c4d79v"
/>
</svg>
);
const LANG_MAP = {
en: {
label: "English",
icon: "🇺🇸",
},
cn: {
label: "中文",
icon: "🇨🇳",
},
} as const;
export default function LocaleSwitch() {
const currentLocale = useLocale();
const router = useRouter();
const pathname = usePathname();
const changeLocale = (locale: string) => {
if (locale === currentLocale) return;
const newPath = pathname.replace(`/${currentLocale}`, `/${locale}`);
return router.push(newPath);
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>{langIcon}</DropdownMenuTrigger>
<DropdownMenuContent className="w-44">
{locales.map((locale) => (
<DropdownMenuItem
key={locale}
onClick={() => changeLocale(locale)}
className={cn(currentLocale === locale && "font-bold")}
>
<div className="flex items-center">
<span className="mr-2">{LANG_MAP[locale].icon}</span>
<span>{LANG_MAP[locale].label}</span>
</div>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
网站title的也随着国际化以及打包分别打包出对应语言的包
import { Inter } from "next/font/google";
import "../globals.css";
import {
getMessages,
getTranslations,
setRequestLocale,
} from "next-intl/server";
import { NextIntlClientProvider } from "next-intl";
import { locales } from "@/config";
const inter = Inter({ subsets: ["latin"] });
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
export async function generateMetadata({
params: { locale },
}: {
params: { locale: string };
}) {
const t = await getTranslations({
locale,
});
return {
title: t("title"),
};
}
export default async function RootLayout({
children,
params: { locale },
}: Readonly<{
children: React.ReactNode;
params: { locale: string };
}>) {
setRequestLocale(locale);
const messages = await getMessages();
return (
<html lang={locale}>
<body className={inter.className}>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
import { Button } from "@/components/ui/button";
import { useTranslations } from "next-intl";
import { Link } from "@/navigation";
import { setRequestLocale } from "next-intl/server";
type Props = {
params: {
locale: string;
};
};
export default function Home({ params: { locale } }: Props) {
setRequestLocale(locale);
const t = useTranslations();
return (
<div>
<Button size="sm" variant="secondary">
{t("file")}
</Button>
<Link href="/editor/123">跳转</Link>
</div>
);
}