feat(frontend): add next-intl bilingual i18n for core paths

Wire next-intl without locale routes, add zh-CN/en catalogs, language
switcher, and migrate layout/auth/settings UI copy. Document i18n rules
in AGENTS.md and keep static export builds working.
This commit is contained in:
ryan
2026-07-24 16:25:41 +08:00
parent 9c1369186f
commit 1625cfb2fb
23 changed files with 1638 additions and 355 deletions
+35 -33
View File
@@ -1,6 +1,7 @@
'use client';
import Link from 'next/link';
import { useTranslations } from 'next-intl';
import {
Card,
CardContent,
@@ -9,39 +10,40 @@ import {
} from '@/components/ui/card';
import { Bell, Key, Palette, UserRound } from 'lucide-react';
/* 设置项 */
const settingsItems = [
{
title: '个人资料',
description: '查看您的账户信息',
icon: UserRound,
href: '/settings/profile',
category: '个人设置',
},
{
title: '访问令牌 (AccessToken)',
description: '管理用于 API 访问的个人 Token',
icon: Key,
href: '/settings/access-token',
category: '个人设置',
},
{
title: '通知设置',
description: '设置您的通知偏好',
icon: Bell,
href: '/settings/notifications',
category: '账户设置',
},
{
title: '外观设置',
description: '自定义界面主题 and 显示',
icon: Palette,
href: '/settings/appearance',
category: '个人设置',
},
];
export default function SettingsPage() {
const t = useTranslations('settings');
const settingsItems = [
{
title: t('items.profile'),
description: t('items.profileDesc'),
icon: UserRound,
href: '/settings/profile',
category: t('categories.personal'),
},
{
title: t('items.accessToken'),
description: t('items.accessTokenDesc'),
icon: Key,
href: '/settings/access-token',
category: t('categories.personal'),
},
{
title: t('items.notifications'),
description: t('items.notificationsDesc'),
icon: Bell,
href: '/settings/notifications',
category: t('categories.account'),
},
{
title: t('items.appearance'),
description: t('items.appearanceDesc'),
icon: Palette,
href: '/settings/appearance',
category: t('categories.personal'),
},
];
const groupedSettings = settingsItems.reduce(
(acc, item) => {
if (!acc[item.category]) {
@@ -55,7 +57,7 @@ export default function SettingsPage() {
return (
<div className='space-y-6 py-6'>
<div className='font-semibold text-lg'>设置</div>
<div className='font-semibold text-lg'>{t('title')}</div>
{Object.entries(groupedSettings).map(([category, items]) => (
<div key={category} className='space-y-4'>
+27 -16
View File
@@ -1,5 +1,6 @@
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { getLocale, getMessages, getTranslations } from 'next-intl/server';
import { Toaster } from '@/components/ui/sonner';
import { ThemeProvider } from '@/components/layout/theme-provider';
import { CustomThemeProvider } from '@/lib/theme';
@@ -7,8 +8,10 @@ import { BellRingProvider } from '@/contexts/bell-ring-context';
import { NotificationSettingsProvider } from '@/contexts/notification-settings-context';
import { UserProvider } from '@/contexts/user-context';
import { AppQueryProvider } from '@/components/providers/query-provider';
import { AppIntlProvider } from '@/components/providers/intl-provider';
import { SiteTitleUpdater } from '@/components/providers/title-updater';
import { RobotsMeta } from '@/components/layout/robots-meta';
import type { AppLocale } from '@/i18n/config';
import './globals.css';
const inter = Inter({
@@ -17,19 +20,25 @@ const inter = Inter({
display: 'swap',
});
export const metadata: Metadata = {
title: 'Wavelet Platform',
description: '通用二次开发标准范式平台',
};
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations('metadata');
return {
title: t('title'),
description: t('description'),
};
}
export default function RootLayout({
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const locale = (await getLocale()) as AppLocale;
const messages = await getMessages();
return (
<html
lang='zh-CN'
lang={locale}
className={`hide-scrollbar font-sans ${inter.variable}`}
suppressHydrationWarning
>
@@ -42,16 +51,18 @@ export default function RootLayout({
>
<CustomThemeProvider>
<AppQueryProvider>
<SiteTitleUpdater />
<RobotsMeta />
<UserProvider>
<NotificationSettingsProvider>
<BellRingProvider>
{children}
<Toaster position='top-center' />
</BellRingProvider>
</NotificationSettingsProvider>
</UserProvider>
<AppIntlProvider locale={locale} messages={messages}>
<SiteTitleUpdater />
<RobotsMeta />
<UserProvider>
<NotificationSettingsProvider>
<BellRingProvider>
{children}
<Toaster position='top-center' />
</BellRingProvider>
</NotificationSettingsProvider>
</UserProvider>
</AppIntlProvider>
</AppQueryProvider>
</CustomThemeProvider>
</ThemeProvider>