From eba8a68d3f89bd1798f620deb031915298ed8ed6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 23:03:52 +0000 Subject: [PATCH 1/7] Initial plan From fe8bf38a51faaca49ffa374bffaaa94abcdd876b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Oct 2025 23:16:12 +0000 Subject: [PATCH 2/7] feat: implement i18n with next-intl for Chinese support Co-authored-by: sirily11 <32106111+sirily11@users.noreply.github.com> --- app/{ => [locale]}/(auth)/actions.ts | 0 .../(auth)/api/auth/[...nextauth]/route.ts | 0 .../(auth)/api/auth/guest/route.ts | 0 .../(auth)/api/auth/link/telegram/actions.ts | 0 .../(auth)/api/auth/link/telegram/route.ts | 0 app/{ => [locale]}/(auth)/auth.config.ts | 0 app/{ => [locale]}/(auth)/auth.ts | 0 app/{ => [locale]}/(auth)/login/page.tsx | 23 ++-- app/{ => [locale]}/(auth)/register/page.tsx | 33 +++--- app/{ => [locale]}/(chat)/actions-mcp.ts | 0 app/{ => [locale]}/(chat)/actions.spec.ts | 0 app/{ => [locale]}/(chat)/actions.ts | 0 app/{ => [locale]}/(chat)/api/chat/route.ts | 0 app/{ => [locale]}/(chat)/api/chat/schema.ts | 0 .../(chat)/api/document/route.ts | 0 .../api/documents/[id]/download/route.ts | 0 .../(chat)/api/documents/[id]/route.ts | 0 .../api/documents/complete-upload/route.ts | 0 .../(chat)/api/documents/route.ts | 0 .../(chat)/api/files/upload/route.ts | 0 .../(chat)/api/history/route.ts | 0 .../(chat)/api/prompts/route.spec.ts | 0 .../(chat)/api/prompts/route.ts | 0 .../(chat)/api/suggestions/route.ts | 0 app/{ => [locale]}/(chat)/api/user/route.ts | 0 app/{ => [locale]}/(chat)/api/vote/route.ts | 0 app/{ => [locale]}/(chat)/chat/[id]/page.tsx | 0 .../(chat)/jobs/[id]/results/actions.ts | 0 .../jobs/[id]/results/job-control-buttons.tsx | 0 .../jobs/[id]/results/page-refresher.tsx | 0 .../(chat)/jobs/[id]/results/page.tsx | 0 .../(chat)/jobs/actions.spec.ts | 0 app/{ => [locale]}/(chat)/jobs/actions.ts | 0 app/{ => [locale]}/(chat)/jobs/layout.tsx | 0 app/{ => [locale]}/(chat)/jobs/page.tsx | 0 app/{ => [locale]}/(chat)/layout.tsx | 0 app/{ => [locale]}/(chat)/opengraph-image.png | Bin app/{ => [locale]}/(chat)/page.tsx | 0 .../(chat)/profile/actions.spec.ts | 0 app/{ => [locale]}/(chat)/profile/actions.ts | 0 app/{ => [locale]}/(chat)/profile/page.tsx | 0 app/{ => [locale]}/(chat)/twitter-image.png | Bin app/{ => [locale]}/globals.css | 0 app/[locale]/layout.tsx | 84 +++++++++++++++ app/{ => [locale]}/loading.tsx | 0 app/layout.tsx | 81 +++------------ components/greeting.tsx | 10 +- components/language-selector.tsx | 45 ++++++++ components/suggested-actions.tsx | 28 ++--- lib/i18n/config.ts | 11 ++ lib/i18n/request.ts | 17 +++ lib/i18n/routing.ts | 16 +++ locales/en/common.json | 52 ++++++++++ locales/zh-CN/common.json | 52 ++++++++++ locales/zh-TW/common.json | 52 ++++++++++ middleware.ts | 57 ++++++---- next.config.ts | 5 +- package.json | 1 + pnpm-lock.yaml | 98 ++++++++++++++++++ 59 files changed, 543 insertions(+), 122 deletions(-) rename app/{ => [locale]}/(auth)/actions.ts (100%) rename app/{ => [locale]}/(auth)/api/auth/[...nextauth]/route.ts (100%) rename app/{ => [locale]}/(auth)/api/auth/guest/route.ts (100%) rename app/{ => [locale]}/(auth)/api/auth/link/telegram/actions.ts (100%) rename app/{ => [locale]}/(auth)/api/auth/link/telegram/route.ts (100%) rename app/{ => [locale]}/(auth)/auth.config.ts (100%) rename app/{ => [locale]}/(auth)/auth.ts (100%) rename app/{ => [locale]}/(auth)/login/page.tsx (80%) rename app/{ => [locale]}/(auth)/register/page.tsx (76%) rename app/{ => [locale]}/(chat)/actions-mcp.ts (100%) rename app/{ => [locale]}/(chat)/actions.spec.ts (100%) rename app/{ => [locale]}/(chat)/actions.ts (100%) rename app/{ => [locale]}/(chat)/api/chat/route.ts (100%) rename app/{ => [locale]}/(chat)/api/chat/schema.ts (100%) rename app/{ => [locale]}/(chat)/api/document/route.ts (100%) rename app/{ => [locale]}/(chat)/api/documents/[id]/download/route.ts (100%) rename app/{ => [locale]}/(chat)/api/documents/[id]/route.ts (100%) rename app/{ => [locale]}/(chat)/api/documents/complete-upload/route.ts (100%) rename app/{ => [locale]}/(chat)/api/documents/route.ts (100%) rename app/{ => [locale]}/(chat)/api/files/upload/route.ts (100%) rename app/{ => [locale]}/(chat)/api/history/route.ts (100%) rename app/{ => [locale]}/(chat)/api/prompts/route.spec.ts (100%) rename app/{ => [locale]}/(chat)/api/prompts/route.ts (100%) rename app/{ => [locale]}/(chat)/api/suggestions/route.ts (100%) rename app/{ => [locale]}/(chat)/api/user/route.ts (100%) rename app/{ => [locale]}/(chat)/api/vote/route.ts (100%) rename app/{ => [locale]}/(chat)/chat/[id]/page.tsx (100%) rename app/{ => [locale]}/(chat)/jobs/[id]/results/actions.ts (100%) rename app/{ => [locale]}/(chat)/jobs/[id]/results/job-control-buttons.tsx (100%) rename app/{ => [locale]}/(chat)/jobs/[id]/results/page-refresher.tsx (100%) rename app/{ => [locale]}/(chat)/jobs/[id]/results/page.tsx (100%) rename app/{ => [locale]}/(chat)/jobs/actions.spec.ts (100%) rename app/{ => [locale]}/(chat)/jobs/actions.ts (100%) rename app/{ => [locale]}/(chat)/jobs/layout.tsx (100%) rename app/{ => [locale]}/(chat)/jobs/page.tsx (100%) rename app/{ => [locale]}/(chat)/layout.tsx (100%) rename app/{ => [locale]}/(chat)/opengraph-image.png (100%) rename app/{ => [locale]}/(chat)/page.tsx (100%) rename app/{ => [locale]}/(chat)/profile/actions.spec.ts (100%) rename app/{ => [locale]}/(chat)/profile/actions.ts (100%) rename app/{ => [locale]}/(chat)/profile/page.tsx (100%) rename app/{ => [locale]}/(chat)/twitter-image.png (100%) rename app/{ => [locale]}/globals.css (100%) create mode 100644 app/[locale]/layout.tsx rename app/{ => [locale]}/loading.tsx (100%) create mode 100644 components/language-selector.tsx create mode 100644 lib/i18n/config.ts create mode 100644 lib/i18n/request.ts create mode 100644 lib/i18n/routing.ts create mode 100644 locales/en/common.json create mode 100644 locales/zh-CN/common.json create mode 100644 locales/zh-TW/common.json diff --git a/app/(auth)/actions.ts b/app/[locale]/(auth)/actions.ts similarity index 100% rename from app/(auth)/actions.ts rename to app/[locale]/(auth)/actions.ts diff --git a/app/(auth)/api/auth/[...nextauth]/route.ts b/app/[locale]/(auth)/api/auth/[...nextauth]/route.ts similarity index 100% rename from app/(auth)/api/auth/[...nextauth]/route.ts rename to app/[locale]/(auth)/api/auth/[...nextauth]/route.ts diff --git a/app/(auth)/api/auth/guest/route.ts b/app/[locale]/(auth)/api/auth/guest/route.ts similarity index 100% rename from app/(auth)/api/auth/guest/route.ts rename to app/[locale]/(auth)/api/auth/guest/route.ts diff --git a/app/(auth)/api/auth/link/telegram/actions.ts b/app/[locale]/(auth)/api/auth/link/telegram/actions.ts similarity index 100% rename from app/(auth)/api/auth/link/telegram/actions.ts rename to app/[locale]/(auth)/api/auth/link/telegram/actions.ts diff --git a/app/(auth)/api/auth/link/telegram/route.ts b/app/[locale]/(auth)/api/auth/link/telegram/route.ts similarity index 100% rename from app/(auth)/api/auth/link/telegram/route.ts rename to app/[locale]/(auth)/api/auth/link/telegram/route.ts diff --git a/app/(auth)/auth.config.ts b/app/[locale]/(auth)/auth.config.ts similarity index 100% rename from app/(auth)/auth.config.ts rename to app/[locale]/(auth)/auth.config.ts diff --git a/app/(auth)/auth.ts b/app/[locale]/(auth)/auth.ts similarity index 100% rename from app/(auth)/auth.ts rename to app/[locale]/(auth)/auth.ts diff --git a/app/(auth)/login/page.tsx b/app/[locale]/(auth)/login/page.tsx similarity index 80% rename from app/(auth)/login/page.tsx rename to app/[locale]/(auth)/login/page.tsx index 87f9f06..e6af4cb 100644 --- a/app/(auth)/login/page.tsx +++ b/app/[locale]/(auth)/login/page.tsx @@ -1,6 +1,5 @@ 'use client'; -import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useActionState, useEffect, useState } from 'react'; import { toast } from '@/components/toast'; @@ -10,9 +9,13 @@ import { SubmitButton } from '@/components/submit-button'; import { login, type LoginActionState } from '../actions'; import { useSession } from 'next-auth/react'; +import { useTranslations } from 'next-intl'; +import { Link } from '@/lib/i18n/routing'; export default function Page() { const router = useRouter(); + // Use translations from the auth namespace + const t = useTranslations('auth'); const [email, setEmail] = useState(''); const [isSuccessful, setIsSuccessful] = useState(false); @@ -31,12 +34,12 @@ export default function Page() { if (state.status === 'failed') { toast({ type: 'error', - description: 'Invalid credentials!', + description: t('invalidCredentials'), }); } else if (state.status === 'invalid_data') { toast({ type: 'error', - description: 'Failed validating your submission!', + description: t('invalidData'), }); } else if (state.status === 'success') { setIsSuccessful(true); @@ -55,20 +58,24 @@ export default function Page() {
-

Sign In

+

+ {t('signIn')} +

- {"Don't have an account? "} + {t('noAccount')} - Sign up + {t('signUp')} - {' for free.'} + {t('forFree')}

- Sign in + + {t('signIn')} +
diff --git a/app/(auth)/register/page.tsx b/app/[locale]/(auth)/register/page.tsx similarity index 76% rename from app/(auth)/register/page.tsx rename to app/[locale]/(auth)/register/page.tsx index 69bd361..d7b38f5 100644 --- a/app/(auth)/register/page.tsx +++ b/app/[locale]/(auth)/register/page.tsx @@ -1,6 +1,5 @@ 'use client'; -import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useActionState, useEffect, useState } from 'react'; @@ -10,9 +9,13 @@ import { SubmitButton } from '@/components/submit-button'; import { register, type RegisterActionState } from '../actions'; import { toast } from '@/components/toast'; import { useSession } from 'next-auth/react'; +import { useTranslations } from 'next-intl'; +import { Link } from '@/lib/i18n/routing'; export default function Page() { const router = useRouter(); + // Use translations from the auth namespace + const t = useTranslations('auth'); const [email, setEmail] = useState(''); const [isSuccessful, setIsSuccessful] = useState(false); @@ -28,21 +31,21 @@ export default function Page() { useEffect(() => { if (state.status === 'user_exists') { - toast({ type: 'error', description: 'Account already exists!' }); + toast({ type: 'error', description: t('userExists') }); } else if (state.status === 'failed') { - toast({ type: 'error', description: 'Failed to create account!' }); + toast({ type: 'error', description: t('failedToCreate') }); } else if (state.status === 'invalid_data') { toast({ type: 'error', - description: 'Failed validating your submission!', + description: t('invalidData'), }); } else if (state.status === 'passwords_dont_match') { toast({ type: 'error', - description: 'Passwords do not match!', + description: t('passwordsDontMatch'), }); } else if (state.status === 'success') { - toast({ type: 'success', description: 'Account created successfully!' }); + toast({ type: 'success', description: t('accountCreated') }); setIsSuccessful(true); updateSession(); router.refresh(); @@ -59,29 +62,31 @@ export default function Page() {
-

Sign Up

+

+ {t('signUp')} +

- {'Already have an account? '} + {t('hasAccount')} - Sign in + {t('signIn')} - {' instead.'} + {t('instead')}

- Sign Up + {t('signUp')}

- {'Already have an account? '} + {t('hasAccount')} - Sign in + {t('signIn')} - {' instead.'} + {t('instead')}

diff --git a/app/(chat)/actions-mcp.ts b/app/[locale]/(chat)/actions-mcp.ts similarity index 100% rename from app/(chat)/actions-mcp.ts rename to app/[locale]/(chat)/actions-mcp.ts diff --git a/app/(chat)/actions.spec.ts b/app/[locale]/(chat)/actions.spec.ts similarity index 100% rename from app/(chat)/actions.spec.ts rename to app/[locale]/(chat)/actions.spec.ts diff --git a/app/(chat)/actions.ts b/app/[locale]/(chat)/actions.ts similarity index 100% rename from app/(chat)/actions.ts rename to app/[locale]/(chat)/actions.ts diff --git a/app/(chat)/api/chat/route.ts b/app/[locale]/(chat)/api/chat/route.ts similarity index 100% rename from app/(chat)/api/chat/route.ts rename to app/[locale]/(chat)/api/chat/route.ts diff --git a/app/(chat)/api/chat/schema.ts b/app/[locale]/(chat)/api/chat/schema.ts similarity index 100% rename from app/(chat)/api/chat/schema.ts rename to app/[locale]/(chat)/api/chat/schema.ts diff --git a/app/(chat)/api/document/route.ts b/app/[locale]/(chat)/api/document/route.ts similarity index 100% rename from app/(chat)/api/document/route.ts rename to app/[locale]/(chat)/api/document/route.ts diff --git a/app/(chat)/api/documents/[id]/download/route.ts b/app/[locale]/(chat)/api/documents/[id]/download/route.ts similarity index 100% rename from app/(chat)/api/documents/[id]/download/route.ts rename to app/[locale]/(chat)/api/documents/[id]/download/route.ts diff --git a/app/(chat)/api/documents/[id]/route.ts b/app/[locale]/(chat)/api/documents/[id]/route.ts similarity index 100% rename from app/(chat)/api/documents/[id]/route.ts rename to app/[locale]/(chat)/api/documents/[id]/route.ts diff --git a/app/(chat)/api/documents/complete-upload/route.ts b/app/[locale]/(chat)/api/documents/complete-upload/route.ts similarity index 100% rename from app/(chat)/api/documents/complete-upload/route.ts rename to app/[locale]/(chat)/api/documents/complete-upload/route.ts diff --git a/app/(chat)/api/documents/route.ts b/app/[locale]/(chat)/api/documents/route.ts similarity index 100% rename from app/(chat)/api/documents/route.ts rename to app/[locale]/(chat)/api/documents/route.ts diff --git a/app/(chat)/api/files/upload/route.ts b/app/[locale]/(chat)/api/files/upload/route.ts similarity index 100% rename from app/(chat)/api/files/upload/route.ts rename to app/[locale]/(chat)/api/files/upload/route.ts diff --git a/app/(chat)/api/history/route.ts b/app/[locale]/(chat)/api/history/route.ts similarity index 100% rename from app/(chat)/api/history/route.ts rename to app/[locale]/(chat)/api/history/route.ts diff --git a/app/(chat)/api/prompts/route.spec.ts b/app/[locale]/(chat)/api/prompts/route.spec.ts similarity index 100% rename from app/(chat)/api/prompts/route.spec.ts rename to app/[locale]/(chat)/api/prompts/route.spec.ts diff --git a/app/(chat)/api/prompts/route.ts b/app/[locale]/(chat)/api/prompts/route.ts similarity index 100% rename from app/(chat)/api/prompts/route.ts rename to app/[locale]/(chat)/api/prompts/route.ts diff --git a/app/(chat)/api/suggestions/route.ts b/app/[locale]/(chat)/api/suggestions/route.ts similarity index 100% rename from app/(chat)/api/suggestions/route.ts rename to app/[locale]/(chat)/api/suggestions/route.ts diff --git a/app/(chat)/api/user/route.ts b/app/[locale]/(chat)/api/user/route.ts similarity index 100% rename from app/(chat)/api/user/route.ts rename to app/[locale]/(chat)/api/user/route.ts diff --git a/app/(chat)/api/vote/route.ts b/app/[locale]/(chat)/api/vote/route.ts similarity index 100% rename from app/(chat)/api/vote/route.ts rename to app/[locale]/(chat)/api/vote/route.ts diff --git a/app/(chat)/chat/[id]/page.tsx b/app/[locale]/(chat)/chat/[id]/page.tsx similarity index 100% rename from app/(chat)/chat/[id]/page.tsx rename to app/[locale]/(chat)/chat/[id]/page.tsx diff --git a/app/(chat)/jobs/[id]/results/actions.ts b/app/[locale]/(chat)/jobs/[id]/results/actions.ts similarity index 100% rename from app/(chat)/jobs/[id]/results/actions.ts rename to app/[locale]/(chat)/jobs/[id]/results/actions.ts diff --git a/app/(chat)/jobs/[id]/results/job-control-buttons.tsx b/app/[locale]/(chat)/jobs/[id]/results/job-control-buttons.tsx similarity index 100% rename from app/(chat)/jobs/[id]/results/job-control-buttons.tsx rename to app/[locale]/(chat)/jobs/[id]/results/job-control-buttons.tsx diff --git a/app/(chat)/jobs/[id]/results/page-refresher.tsx b/app/[locale]/(chat)/jobs/[id]/results/page-refresher.tsx similarity index 100% rename from app/(chat)/jobs/[id]/results/page-refresher.tsx rename to app/[locale]/(chat)/jobs/[id]/results/page-refresher.tsx diff --git a/app/(chat)/jobs/[id]/results/page.tsx b/app/[locale]/(chat)/jobs/[id]/results/page.tsx similarity index 100% rename from app/(chat)/jobs/[id]/results/page.tsx rename to app/[locale]/(chat)/jobs/[id]/results/page.tsx diff --git a/app/(chat)/jobs/actions.spec.ts b/app/[locale]/(chat)/jobs/actions.spec.ts similarity index 100% rename from app/(chat)/jobs/actions.spec.ts rename to app/[locale]/(chat)/jobs/actions.spec.ts diff --git a/app/(chat)/jobs/actions.ts b/app/[locale]/(chat)/jobs/actions.ts similarity index 100% rename from app/(chat)/jobs/actions.ts rename to app/[locale]/(chat)/jobs/actions.ts diff --git a/app/(chat)/jobs/layout.tsx b/app/[locale]/(chat)/jobs/layout.tsx similarity index 100% rename from app/(chat)/jobs/layout.tsx rename to app/[locale]/(chat)/jobs/layout.tsx diff --git a/app/(chat)/jobs/page.tsx b/app/[locale]/(chat)/jobs/page.tsx similarity index 100% rename from app/(chat)/jobs/page.tsx rename to app/[locale]/(chat)/jobs/page.tsx diff --git a/app/(chat)/layout.tsx b/app/[locale]/(chat)/layout.tsx similarity index 100% rename from app/(chat)/layout.tsx rename to app/[locale]/(chat)/layout.tsx diff --git a/app/(chat)/opengraph-image.png b/app/[locale]/(chat)/opengraph-image.png similarity index 100% rename from app/(chat)/opengraph-image.png rename to app/[locale]/(chat)/opengraph-image.png diff --git a/app/(chat)/page.tsx b/app/[locale]/(chat)/page.tsx similarity index 100% rename from app/(chat)/page.tsx rename to app/[locale]/(chat)/page.tsx diff --git a/app/(chat)/profile/actions.spec.ts b/app/[locale]/(chat)/profile/actions.spec.ts similarity index 100% rename from app/(chat)/profile/actions.spec.ts rename to app/[locale]/(chat)/profile/actions.spec.ts diff --git a/app/(chat)/profile/actions.ts b/app/[locale]/(chat)/profile/actions.ts similarity index 100% rename from app/(chat)/profile/actions.ts rename to app/[locale]/(chat)/profile/actions.ts diff --git a/app/(chat)/profile/page.tsx b/app/[locale]/(chat)/profile/page.tsx similarity index 100% rename from app/(chat)/profile/page.tsx rename to app/[locale]/(chat)/profile/page.tsx diff --git a/app/(chat)/twitter-image.png b/app/[locale]/(chat)/twitter-image.png similarity index 100% rename from app/(chat)/twitter-image.png rename to app/[locale]/(chat)/twitter-image.png diff --git a/app/globals.css b/app/[locale]/globals.css similarity index 100% rename from app/globals.css rename to app/[locale]/globals.css diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx new file mode 100644 index 0000000..47cb3d0 --- /dev/null +++ b/app/[locale]/layout.tsx @@ -0,0 +1,84 @@ +import { ThemeProvider } from '@/components/theme-provider'; +import { Toaster } from 'sonner'; +import { Analytics } from '@vercel/analytics/next'; +import { SpeedInsights } from '@vercel/speed-insights/next'; +import { SessionProvider } from 'next-auth/react'; +import { NextIntlClientProvider } from 'next-intl'; +import { getMessages } from 'next-intl/server'; +import './globals.css'; + +// Fallback font configuration for build environments without internet access +const geist = { + variable: '--font-geist', +}; + +const geistMono = { + variable: '--font-geist-mono', +}; + +const LIGHT_THEME_COLOR = 'hsl(0 0% 100%)'; +const DARK_THEME_COLOR = 'hsl(240deg 10% 3.92%)'; +const THEME_COLOR_SCRIPT = `\ +(function() { + var html = document.documentElement; + var meta = document.querySelector('meta[name="theme-color"]'); + if (!meta) { + meta = document.createElement('meta'); + meta.setAttribute('name', 'theme-color'); + document.head.appendChild(meta); + } + function updateThemeColor() { + var isDark = html.classList.contains('dark'); + meta.setAttribute('content', isDark ? '${DARK_THEME_COLOR}' : '${LIGHT_THEME_COLOR}'); + } + var observer = new MutationObserver(updateThemeColor); + observer.observe(html, { attributes: true, attributeFilter: ['class'] }); + updateThemeColor(); +})();`; + +export default async function LocaleLayout({ + children, + params, +}: Readonly<{ + children: React.ReactNode; + params: Promise<{ locale: string }>; +}>) { + const { locale } = await params; + // Providing all messages to the client side is the easiest way to get started + const messages = await getMessages(); + + return ( + + +