前言
前段时间 , 幸运的获得一个远程 offer , 主要业务是做 AI 出海应用 ,团队阵容十分强大:清华博士哥哥负责算法和产品 😱, 还有擅长架构和算法的 Full Stack Developer 阿伦🥰 , 他们真的很好 , 很贴心 ,即使是远程 ,我也能感受到团队合作的融入感 ~ 另一个的远程也是 :从业二十年的大厂大佬 , 平易近人 , 果然真正的大佬就是上善若水 ,没有任何傲慢与优越感 ~
这段时间我使用 Nextjs 框架做一些开发 ,由于是出海应用 , 所以国际化十分重要,在 Nextjs 中如何实现国际化? , 它的底层原理是什么 ?
本文涉及的代码参考与官网和公开资源 , 不涉及团队具体代码 , 哈哈 , 这点很重要🤡
什么是国际化 ?
我们在 header(头部导航栏)可以看到一个按钮供我们选择语言 ,这样理论上世界各国人民都能以本土语言使用网站(展示网站为 monica)
我们还要了解一些概念 :
- 【国际化】就是i18n(Internationalizatio)
- 【本地化】就是L10n(localization)
- 【locale】它定义了软件运行时的语言环境,通过特定命名规则(language [_territory [.codeset]][@modifier])来确定语言、地域、字符集及修正值等信息 ,比如 :
zh_CN.GB2312
就表示中国地区的汉语
着重聊一下【locale】吧 ~
- 在语言显示与交互方面,能让软件依据用户所在地区或设置偏好,将界面、菜单、提示等内容以对应语言呈现,如设为 "zh_CN",软件就以中文展示,提升跨国软件对不同用户的易用性。
- 在日期和时间格式上,鉴于不同地区表示方式不同,locale 确保软件按当地习惯正确显示,方便用户在日程安排等功能中准确理解。
- 涉及数字和货币格式时,由于各地数字分隔符、小数点使用及货币符号、格式有差异,locale 让金融、电商类软件能精准显示金额。
- 在字符处理方面,因各语言字符排序规则不同,locale 为软件提供排序及文本处理规则,提升多语言文本操作的准确性。
- 此外,软件可借助 locale 识别当地节假日,用于日历、考勤软件,还能适配特殊度量单位习惯,满足不同地区用户对功能的本地化需求 。
如何实现国际化 ?
方式很多 , 我使用的是 next-intl , 原因是 : 我不想破坏应用程序的目录结构和路由 ~
Gitee: gitee.com/luli1314520...
效果下动图
初始项目
初始化 nextjs 项目
代码结构
如何利用 next-intl
实现国际化
1. 配置支持的语言和默认语言
在 src/i18n/request.ts
文件中,定义了支持的语言列表、语言名称映射和默认语言:
ts
export type Locale = 'en' | 'zh';
// 支持的语言列表
export const locales: Locale[] = ['en', 'zh'];
// 语言名称映射
export const localeNames: Record<Locale, string> = {
en: 'English',
zh: '中文'
};
// 默认语言
export const defaultLocale: Locale = 'zh';
2. 获取请求中的语言
在 src/i18n/request-locale.ts
文件中,实现了一个异步函数 requestLocale
,用于从请求头中获取语言信息,如果获取失败则使用默认语言:
ts
import {headers} from 'next/headers';
import {defaultLocale} from './request';
export async function requestLocale() {
try {
const headersList = await headers();
return await headersList.get('X-NEXT-INTL-LOCALE') || defaultLocale;
} catch {
return defaultLocale;
}
}
3. 配置请求信息
在 src/i18n/index.ts
文件中,使用 getRequestConfig
函数配置请求信息,包括语言、消息文件、时区等:
ts
import {getRequestConfig} from 'next-intl/server';
import {locales, defaultLocale} from './request';
import {requestLocale} from './request-locale';
export default getRequestConfig(async () => {
const locale = await requestLocale();
const validLocale = locales.includes(locale as any) ? locale : defaultLocale;
return {
locale: validLocale,
messages: (await import(`../../messages/${validLocale}.json`)).default,
timeZone: 'Asia/Shanghai',
now: new Date()
};
});
4. 配置中间件
在 src/middleware.ts
文件中,使用 next-intl
的 createMiddleware
函数创建中间件,配置默认语言、支持的语言、语言前缀和语言检测等:
ts
import createMiddleware from 'next-intl/middleware';
import {locales, defaultLocale} from './i18n/request';
// 配置需要匹配的路径
export const config = {
// 匹配所有路径除了 api, _next/static, _next/image, favicon.ico 等
matcher: ['/((?!api|_next|_vercel|.*\..*).*)', '/']
};
export default createMiddleware({
// 默认语言
defaultLocale,
// 支持的语言
locales,
// 总是在URL中显示语言前缀
localePrefix: 'always',
// 禁用自动语言检测,使用URL中的语言参数
localeDetection: false
});
5. 布局文件中提供国际化上下文
在 src/app/[locale]/layout.tsx
文件中,使用 NextIntlClientProvider
组件提供国际化上下文,包括语言和消息文件:
ts
import '@/app/globals.css';
import { Inter } from 'next/font/google';
import { notFound } from 'next/navigation';
import { NextIntlClientProvider } from 'next-intl';
import { locales } from '@/i18n/request';
import { requestLocale } from '@/i18n/request-locale';
const inter = Inter({ subsets: ['latin'] });
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
export default async function LocaleLayout({
children,
params
}: {
children: React.ReactNode;
params: { locale: string };
}) {
const locale = await requestLocale() || params.locale;
let messages;
try {
messages = (await import(`../../../messages/${locale}.json`)).default;
} catch (error) {
notFound();
}
return (
<html lang={locale}>
<body className={inter.className}>
<NextIntlClientProvider
locale={locale}
messages={messages}
timeZone="Asia/Shanghai"
>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
6. 在页面中使用国际化
在 src/app/[locale]/page.tsx
文件中,使用 useTranslations
钩子获取翻译函数,并使用该函数进行文本翻译:
ts
import { useTranslations } from 'next-intl';
import LanguageSwitcher from '@/components/LanguageSwitcher';
export default function Home() {
const t = useTranslations('home');
const features = [
{ key: 'routing', label: t('features.list.routing') },
{ key: 'seo', label: t('features.list.seo') },
{ key: 'performance', label: t('features.list.performance') },
{ key: 'typescript', label: t('features.list.typescript') }
];
return (
<main className="min-h-screen bg-gray-50">
<div className="max-w-4xl mx-auto px-4 py-8">
<div className="flex justify-between items-center mb-8">
<h1 className="text-4xl font-bold text-gray-900">{t("title")}</h1>
<LanguageSwitcher />
</div>
<p className="text-xl text-gray-600 mb-12">{t("description")}</p>
<h2 className="text-2xl font-semibold text-gray-800 mb-6">{t("features.title")}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{features.map(({ key, label }) => (
<div key={key} className="bg-white p-6 rounded-lg shadow-sm hover:shadow-md transition-shadow">
<p className="text-lg text-gray-700">{label}</p>
</div>
))}
</div>
</div>
</main>
);
}
7. 切换语言组件
在 src/components/LanguageSwitcher.tsx
文件中,实现了一个语言切换组件:
ts
'use client';
import { useLocale } from 'next-intl';
import { usePathname, useRouter } from 'next/navigation';
import { locales, localeNames, type Locale } from '@/i18n/request';
export default function LanguageSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const handleChange = (newLocale: string) => {
// 替换URL中的语言代码
const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);
router.push(newPath);
};
return (
<select
value={locale}
onChange={(e) => handleChange(e.target.value)}
className="bg-transparent border rounded px-2 py-1"
>
{locales.map((loc) => (
<option key={loc} value={loc}>
{localeNames[loc as Locale]}
</option>
))}
</select>
);
}
当用户点击切换语言时,会触发 handleChange
函数,该函数会执行以下操作:
- 替换URL中的语言代码 :使用
pathname.replace
方法将当前URL中的语言代码替换为用户选择的新语言代码。 - 路由跳转 :使用
router.push
方法跳转到新的URL,此时页面会重新加载,并且next-intl
会根据新的语言代码加载对应的消息文件,从而实现语言的切换。
通过以上步骤和代码实现了 next-intl
的国际化功能,并且在用户点击切换语言时能够正确切换页面的语言显示。