国际化(internationalization,i18n)在项目中是必备的,接下来是一个标准的国际化(Internationalization)配置,支持多语言切换功能。
核心特性
- 多语言支持:支持英文(en-US)和中文(zh-Hans)
- 服务端渲染:支持 SSR 和 SSG 的国际化
- 自动语言检测:基于浏览器语言偏好和 Cookie 自动检测
- 类型安全:完整的 TypeScript 类型支持
- 动态加载:按需加载翻译资源,优化性能
依赖包
核心依赖
包名 | 版本 | 作用 |
---|---|---|
i18next |
^23.16.4 | 国际化核心库,提供翻译功能 |
react-i18next |
^15.1.0 | React 集成库,提供 React 组件和 Hooks |
i18next-resources-to-backend |
^1.2.1 | 动态加载翻译资源的插件 |
语言检测依赖
包名 | 版本 | 作用 |
---|---|---|
negotiator |
^0.6.3 | HTTP 语言协商,解析 Accept-Language 头 |
@formatjs/intl-localematcher |
^0.5.6 | 语言匹配算法,选择最佳匹配的语言 |
类型定义
包名 | 版本 | 作用 |
---|---|---|
@types/negotiator |
^0.6.3 | Negotiator 的 TypeScript 类型定义 |
package.json
{
"name": "next-base",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"react": "19.1.0",
"react-dom": "19.1.0",
"next": "15.4.2",
...
"react-i18next": "^15.1.0",
"i18next": "^23.16.4",
"i18next-resources-to-backend": "^1.2.1",
"negotiator": "^0.6.3",
"@formatjs/intl-localematcher": "^0.5.6"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4",
"eslint": "^9",
"eslint-config-next": "15.4.2",
"@eslint/eslintrc": "^3",
...
"@types/negotiator": "^0.6.3"
}
}
文件结构
i18n/
├── README.md # 文档
├── ARCHITECTURE.md # 架构设计说明
├── index.ts # 导出配置和类型
├── server.ts # 服务端国际化工具
├── en-US/ # 英文翻译文件
│ ├── common.ts # 通用翻译
│ └── apps.ts # 应用页面翻译
└── zh-Hans/ # 中文翻译文件
├── common.ts # 通用翻译
└── apps.ts # 应用页面翻译
核心文件说明
index.ts
- 配置导出
export type Locale = 'en-US' | 'zh-Hans'
export const i18n = {
defaultLocale: 'en-US' as const,
locales: ['en-US', 'zh-Hans'] as const,
} as const
功能:
- 定义支持的语言类型
- 配置默认语言和可用语言列表
- 导出类型安全的配置对象
server.ts
- 服务端工具
// 初始化 i18next 实例
const initI18next = async (lng: Locale, ns: string) => { ... }
// 获取翻译函数
export async function useTranslation(lng: Locale, ns = '', options = {}) { ... }
// 服务端语言检测
export const getLocaleOnServer = async (): Promise<Locale> => { ... }
功能:
- 语言检测:从 Cookie 和 HTTP 头中检测用户语言偏好
- 翻译初始化:动态加载翻译资源并初始化 i18next
- 服务端翻译:在服务端组件中使用翻译功能
翻译文件结构
// en-US/common.ts
export default {
welcome: 'Welcome',
apps: 'Apps',
// ...
} as const
// zh-Hans/common.ts
export default {
welcome: '欢迎',
apps: '应用',
// ...
} as const
使用方法
1. 在服务端组件中使用
import { useTranslation } from '@/i18n/server'
// 在服务端组件中
const MyServerComponent = async () => {
const { t } = await useTranslation('en-US', 'common')
return <h1>{t('welcome')}</h1>
}
2. 获取用户语言
import { getLocaleOnServer } from '@/i18n/server'
const Layout = async ({ children }) => {
const locale = await getLocaleOnServer()
return (
<html lang={locale}>
{children}
</html>
)
}
3. 添加新的翻译
- 创建翻译文件:
// i18n/en-US/home.ts
export default {
title: 'Home Page',
description: 'Welcome to our website',
} as const
// i18n/zh-Hans/home.ts
export default {
title: '首页',
description: '欢迎访问我们的网站',
} as const
- 使用翻译:
const { t } = await useTranslation('en-US', 'home') return <h1>{t('title')}</h1>
切换语言页面实现
page.tsx
import { useTranslation } from '@/i18n/server'
import { pathToLocaleMap } from '@/i18n'
import LanguageSwitcher from '@/app/components/LanguageSwitcher'
import { notFound } from 'next/navigation'
import { Metadata } from 'next'
interface AppsPageProps {
params: {
locale: string;
};
}
// 生成动态元数据
export async function generateMetadata({ params }: AppsPageProps): Promise<Metadata> {
const locale = pathToLocaleMap[params.locale as keyof typeof pathToLocaleMap]
if (!locale) {
return {}
}
const { t: tApps } = await useTranslation(locale, 'apps')
const title = tApps('title')
const description = tApps('description')
return {
title,
description,
openGraph: {
title,
description,
locale: locale,
},
alternates: {
canonical: `/${params.locale}/apps`,
languages: {
'en': '/en/apps',
'zh': '/zh/apps',
},
},
}
}
export default async function AppsPage({ params }: AppsPageProps) {
// 验证语言参数
const locale = pathToLocaleMap[params.locale as keyof typeof pathToLocaleMap]
if (!locale) {
notFound()
}
const { t: tCommon } = await useTranslation(locale, 'common')
const { t: tApps } = await useTranslation(locale, 'apps')
// 结构化数据
const structuredData = {
"@context": "https://schema.org",
"@type": "WebPage",
"name": tApps('title'),
"description": tApps('description'),
"inLanguage": locale,
"url": `https://yourdomain.com/${params.locale}/apps`,
"alternateName": {
"en": "Applications",
"zh": "应用程序"
}
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
<div className="p-8 max-w-4xl mx-auto">
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-3xl font-bold">{tApps('title')}</h1>
<p className="text-gray-600 mt-2">{tApps('description')}</p>
</div>
<LanguageSwitcher />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
{/* 统计卡片 */}
<div className="bg-white p-6 rounded-lg shadow-md">
<h3 className="text-lg font-semibold mb-4">{tApps('stats.total')}</h3>
<div className="space-y-2">
<div className="flex justify-between">
<span>{tApps('stats.active')}:</span>
<span className="font-bold text-green-600">12</span>
</div>
<div className="flex justify-between">
<span>{tApps('stats.inactive')}:</span>
<span className="font-bold text-gray-600">3</span>
</div>
</div>
</div>
{/* 功能特性 */}
<div className="bg-white p-6 rounded-lg shadow-md">
<h3 className="text-lg font-semibold mb-4">{tApps('features.title')}</h3>
<ul className="space-y-2">
{tApps('features.list.0') && <li className="flex items-center">
<span className="text-green-500 mr-2">✓</span>
{tApps('features.list.0')}
</li>}
{tApps('features.list.1') && <li className="flex items-center">
<span className="text-green-500 mr-2">✓</span>
{tApps('features.list.1')}
</li>}
{tApps('features.list.2') && <li className="flex items-center">
<span className="text-green-500 mr-2">✓</span>
{tApps('features.list.2')}
</li>}
{tApps('features.list.3') && <li className="flex items-center">
<span className="text-green-500 mr-2">✓</span>
{tApps('features.list.3')}
</li>}
</ul>
</div>
</div>
{/* 操作按钮 */}
<div className="bg-white p-6 rounded-lg shadow-md">
<h3 className="text-lg font-semibold mb-4">操作</h3>
<div className="flex flex-wrap gap-3">
<button className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
{tApps('actions.create')}
</button>
<button className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors">
{tApps('actions.edit')}
</button>
<button className="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors">
{tApps('actions.view')}
</button>
<button className="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors">
{tApps('actions.delete')}
</button>
</div>
</div>
{/* 语言链接 */}
<div className="mt-6 p-4 bg-blue-50 rounded-lg">
<h3 className="text-lg font-medium mb-2">其他语言版本:</h3>
<div className="flex gap-4">
<a href="/en/apps" className="text-blue-600 hover:underline">🇺🇸 English</a>
<a href="/zh/apps" className="text-blue-600 hover:underline">🇨🇳 中文</a>
</div>
</div>
{/* 调试信息 */}
<div className="mt-8 p-4 bg-gray-100 rounded-lg">
<h3 className="text-lg font-medium mb-2">调试信息:</h3>
<p><strong>当前语言:</strong> {locale}</p>
<p><strong>URL路径:</strong> /{params.locale}/apps</p>
<p><strong>页面标题:</strong> {tApps('title')}</p>
<p><strong>欢迎信息:</strong> {tCommon('welcome')}</p>
<p><strong>SEO优化:</strong> ✅ 支持搜索引擎索引</p>
</div>
</div>
</>
)
}
展示
语言检测流程
- Cookie 检查:首先检查
locale
Cookie - HTTP 头解析:解析
Accept-Language
头 - 语言匹配:使用
@formatjs/intl-localematcher
匹配最佳语言 - 默认回退:如果没有匹配,使用默认语言
添加新语言
1. 更新配置
// i18n/index.ts
export type Locale = 'en-US' | 'zh-Hans' | 'ja-JP'
export const i18n = {
defaultLocale: 'en-US' as const,
locales: ['en-US', 'zh-Hans', 'ja-JP'] as const,
} as const
2. 创建翻译文件
i18n/
└── ja-JP/
├── common.ts
└── home.ts
3. 添加翻译内容
// i18n/ja-JP/common.ts
export default {
welcome: 'ようこそ',
apps: 'アプリ',
// ...
} as const
性能优化
- 动态加载:翻译文件按需加载,不会增加初始包大小
- 缓存机制:i18next 会缓存已加载的翻译资源
- 类型安全:TypeScript 确保翻译键的正确性
调试技巧
1. 检查语言检测
const locale = await getLocaleOnServer()
console.log('Detected locale:', locale)
2. 验证翻译加载
const { t, i18n } = await useTranslation('en-US', 'common')
console.log('Available namespaces:', i18n.reportNamespaces.getUsedNamespaces())
3. 测试翻译
const { t } = await useTranslation('en-US', 'common')
console.log('Translation test:', t('welcome'))
相关文档
贡献指南
- 添加新翻译时,确保所有语言都有对应的翻译
- 使用
as const
确保类型安全 - 翻译键名使用 camelCase 命名
- 测试所有支持的语言
这个国际化配置为项目提供了完整的多语言支持,支持服务端渲染和客户端交互。