国际化(internationalization,i18n)在项目中是必备的,接下来是一个标准的国际化(Internationalization)配置,支持多语言切换功能。
包名 | 版本 | 作用 |
---|---|---|
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 类型定义 |
{
"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> => { ... }
功能:
// en-US/common.ts
export default {
welcome: 'Welcome',
apps: 'Apps',
// ...
} as const
// zh-Hans/common.ts
export default {
welcome: '欢迎',
apps: '应用',
// ...
} as const
import { useTranslation } from '@/i18n/server'
// 在服务端组件中
const MyServerComponent = async () => {
const { t } = await useTranslation('en-US', 'common')
return <h1>{t('welcome')}</h1>
}
import { getLocaleOnServer } from '@/i18n/server'
const Layout = async ({ children }) => {
const locale = await getLocaleOnServer()
return (
<html lang={locale}>
{children}
</html>
)
}
// 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>
</>
)
}
展示
locale
CookieAccept-Language
头@formatjs/intl-localematcher
匹配最佳语言// 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
i18n/
└── ja-JP/
├── common.ts
└── home.ts
// i18n/ja-JP/common.ts
export default {
welcome: 'ようこそ',
apps: 'アプリ',
// ...
} as const
const locale = await getLocaleOnServer()
console.log('Detected locale:', locale)
const { t, i18n } = await useTranslation('en-US', 'common')
console.log('Available namespaces:', i18n.reportNamespaces.getUsedNamespaces())
const { t } = await useTranslation('en-US', 'common')
console.log('Translation test:', t('welcome'))
as const
确保类型安全这个国际化配置为项目提供了完整的多语言支持,支持服务端渲染和客户端交互。