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 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 (
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+ );
+}
diff --git a/app/loading.tsx b/app/[locale]/loading.tsx
similarity index 100%
rename from app/loading.tsx
rename to app/[locale]/loading.tsx
diff --git a/app/layout.tsx b/app/layout.tsx
index 97e4b50..2b1b370 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,11 +1,7 @@
-import { ThemeProvider } from '@/components/theme-provider';
import type { Metadata } from 'next';
-import { Toaster } from 'sonner';
-import { Analytics } from '@vercel/analytics/next';
-import { SpeedInsights } from '@vercel/speed-insights/next';
-import { SessionProvider } from 'next-auth/react';
import { getBrandName } from '@/lib/utils';
-import './globals.css';
+import { routing } from '@/lib/i18n/routing';
+import { notFound } from 'next/navigation';
export const metadata: Metadata = {
metadataBase: new URL('https://chat.vercel.ai'),
@@ -17,70 +13,23 @@ export const viewport = {
maximumScale: 1, // Disable auto-zoom on mobile Safari
};
-// 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 function generateStaticParams() {
+ return routing.locales.map((locale) => ({ locale }));
+}
export default async function RootLayout({
children,
+ params,
}: Readonly<{
children: React.ReactNode;
+ params: Promise<{ locale: string }>;
}>) {
- return (
-
-
-
-
-
-
-
- {children}
-
-
-
-
-
- );
+ const { locale } = await params;
+
+ // Ensure that the incoming `locale` is valid
+ if (!routing.locales.includes(locale as any)) {
+ notFound();
+ }
+
+ return children;
}
diff --git a/components/greeting.tsx b/components/greeting.tsx
index 9543c77..2d45a60 100644
--- a/components/greeting.tsx
+++ b/components/greeting.tsx
@@ -1,6 +1,12 @@
+'use client';
+
import { motion } from 'framer-motion';
+import { useTranslations } from 'next-intl';
export const Greeting = () => {
+ // Use translations from the common namespace
+ const t = useTranslations('greeting');
+
return (
{
transition={{ delay: 0.5 }}
className="text-2xl font-semibold"
>
- Hello there!
+ {t('hello')}
{
transition={{ delay: 0.6 }}
className="text-2xl text-zinc-500"
>
- How can I help you today?
+ {t('help')}
);
diff --git a/components/language-selector.tsx b/components/language-selector.tsx
new file mode 100644
index 0000000..318205c
--- /dev/null
+++ b/components/language-selector.tsx
@@ -0,0 +1,45 @@
+'use client';
+
+import { useTranslations } from 'next-intl';
+import { usePathname, useRouter } from '@/lib/i18n/routing';
+import { locales, localeNames, type Locale } from '@/lib/i18n/config';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { Languages } from 'lucide-react';
+import { useParams } from 'next/navigation';
+
+export function LanguageSelector() {
+ const t = useTranslations('language');
+ const router = useRouter();
+ const pathname = usePathname();
+ const params = useParams();
+ const currentLocale = (params.locale as Locale) || 'en';
+
+ const handleLocaleChange = (newLocale: string) => {
+ // Navigate to the same page with the new locale
+ router.replace(pathname, { locale: newLocale });
+ };
+
+ return (
+
+
+
+
+ );
+}
diff --git a/components/suggested-actions.tsx b/components/suggested-actions.tsx
index 430ab7a..b890b4c 100644
--- a/components/suggested-actions.tsx
+++ b/components/suggested-actions.tsx
@@ -5,6 +5,7 @@ import { Button } from './ui/button';
import { memo } from 'react';
import type { UseChatHelpers } from '@ai-sdk/react';
import type { VisibilityType } from './visibility-selector';
+import { useTranslations } from 'next-intl';
interface SuggestedActionsProps {
chatId: string;
@@ -17,26 +18,29 @@ function PureSuggestedActions({
append,
selectedVisibilityType,
}: SuggestedActionsProps) {
+ // Use translations from the suggestedActions namespace
+ const t = useTranslations('suggestedActions');
+
const suggestedActions = [
{
- title: 'What are the advantages',
- label: 'of using Next.js?',
- action: 'What are the advantages of using Next.js?',
+ title: t('nextjs.title'),
+ label: t('nextjs.label'),
+ action: t('nextjs.action'),
},
{
- title: 'Write code to',
- label: `demonstrate djikstra's algorithm`,
- action: `Write code to demonstrate djikstra's algorithm`,
+ title: t('algorithm.title'),
+ label: t('algorithm.label'),
+ action: t('algorithm.action'),
},
{
- title: 'Help me write an essay',
- label: `about silicon valley`,
- action: `Help me write an essay about silicon valley`,
+ title: t('essay.title'),
+ label: t('essay.label'),
+ action: t('essay.action'),
},
{
- title: 'What is the weather',
- label: 'in San Francisco?',
- action: 'What is the weather in San Francisco?',
+ title: t('weather.title'),
+ label: t('weather.label'),
+ action: t('weather.action'),
},
];
diff --git a/lib/i18n/config.ts b/lib/i18n/config.ts
new file mode 100644
index 0000000..bdc2a39
--- /dev/null
+++ b/lib/i18n/config.ts
@@ -0,0 +1,11 @@
+// i18n configuration for the application
+export const locales = ['en', 'zh-CN', 'zh-TW'] as const;
+export type Locale = (typeof locales)[number];
+
+export const defaultLocale: Locale = 'en';
+
+export const localeNames: Record
= {
+ en: 'English',
+ 'zh-CN': '简体中文',
+ 'zh-TW': '繁體中文',
+};
diff --git a/lib/i18n/request.ts b/lib/i18n/request.ts
new file mode 100644
index 0000000..bcc6fb4
--- /dev/null
+++ b/lib/i18n/request.ts
@@ -0,0 +1,17 @@
+import { getRequestConfig } from 'next-intl/server';
+import { routing } from './routing';
+
+export default getRequestConfig(async ({ requestLocale }) => {
+ // This typically corresponds to the `[locale]` segment
+ let locale = await requestLocale;
+
+ // Ensure that a valid locale is used
+ if (!locale || !routing.locales.includes(locale as any)) {
+ locale = routing.defaultLocale;
+ }
+
+ return {
+ locale,
+ messages: (await import(`../../locales/${locale}/common.json`)).default,
+ };
+});
diff --git a/lib/i18n/routing.ts b/lib/i18n/routing.ts
new file mode 100644
index 0000000..f30a15f
--- /dev/null
+++ b/lib/i18n/routing.ts
@@ -0,0 +1,16 @@
+import { defineRouting } from 'next-intl/routing';
+import { createNavigation } from 'next-intl/navigation';
+import { locales, defaultLocale } from './config';
+
+export const routing = defineRouting({
+ // A list of all locales that are supported
+ locales: locales,
+
+ // Used when no locale matches
+ defaultLocale: defaultLocale,
+});
+
+// Lightweight wrappers around Next.js' navigation APIs
+// that will consider the routing configuration
+export const { Link, redirect, usePathname, useRouter, getPathname } =
+ createNavigation(routing);
diff --git a/locales/en/common.json b/locales/en/common.json
new file mode 100644
index 0000000..16d21ac
--- /dev/null
+++ b/locales/en/common.json
@@ -0,0 +1,52 @@
+{
+ "greeting": {
+ "hello": "Hello there!",
+ "help": "How can I help you today?"
+ },
+ "suggestedActions": {
+ "nextjs": {
+ "title": "What are the advantages",
+ "label": "of using Next.js?",
+ "action": "What are the advantages of using Next.js?"
+ },
+ "algorithm": {
+ "title": "Write code to",
+ "label": "demonstrate djikstra's algorithm",
+ "action": "Write code to demonstrate djikstra's algorithm"
+ },
+ "essay": {
+ "title": "Help me write an essay",
+ "label": "about silicon valley",
+ "action": "Help me write an essay about silicon valley"
+ },
+ "weather": {
+ "title": "What is the weather",
+ "label": "in San Francisco?",
+ "action": "What is the weather in San Francisco?"
+ }
+ },
+ "auth": {
+ "signIn": "Sign In",
+ "signUp": "Sign up",
+ "signOut": "Sign Out",
+ "noAccount": "Don't have an account? ",
+ "forFree": " for free.",
+ "hasAccount": "Already have an account? ",
+ "instead": " instead.",
+ "invalidCredentials": "Invalid credentials!",
+ "invalidData": "Failed validating your submission!",
+ "userExists": "Account already exists!",
+ "failedToCreate": "Failed to create account!",
+ "passwordsDontMatch": "Passwords do not match!",
+ "accountCreated": "Account created successfully!",
+ "email": "Email",
+ "password": "Password",
+ "register": "Register"
+ },
+ "language": {
+ "select": "Select Language",
+ "en": "English",
+ "zh-CN": "简体中文",
+ "zh-TW": "繁體中文"
+ }
+}
diff --git a/locales/zh-CN/common.json b/locales/zh-CN/common.json
new file mode 100644
index 0000000..9b92a2e
--- /dev/null
+++ b/locales/zh-CN/common.json
@@ -0,0 +1,52 @@
+{
+ "greeting": {
+ "hello": "您好!",
+ "help": "我今天能帮您什么忙?"
+ },
+ "suggestedActions": {
+ "nextjs": {
+ "title": "使用 Next.js 的",
+ "label": "优势有哪些?",
+ "action": "使用 Next.js 的优势有哪些?"
+ },
+ "algorithm": {
+ "title": "编写代码来",
+ "label": "演示 Dijkstra 算法",
+ "action": "编写代码来演示 Dijkstra 算法"
+ },
+ "essay": {
+ "title": "帮我写一篇关于",
+ "label": "硅谷的文章",
+ "action": "帮我写一篇关于硅谷的文章"
+ },
+ "weather": {
+ "title": "旧金山的",
+ "label": "天气如何?",
+ "action": "旧金山的天气如何?"
+ }
+ },
+ "auth": {
+ "signIn": "登录",
+ "signUp": "注册",
+ "signOut": "退出",
+ "noAccount": "还没有账户?",
+ "forFree": "免费注册。",
+ "hasAccount": "已有账户?",
+ "instead": "立即登录。",
+ "invalidCredentials": "凭据无效!",
+ "invalidData": "验证提交失败!",
+ "userExists": "账户已存在!",
+ "failedToCreate": "创建账户失败!",
+ "passwordsDontMatch": "密码不匹配!",
+ "accountCreated": "账户创建成功!",
+ "email": "邮箱",
+ "password": "密码",
+ "register": "注册"
+ },
+ "language": {
+ "select": "选择语言",
+ "en": "English",
+ "zh-CN": "简体中文",
+ "zh-TW": "繁體中文"
+ }
+}
diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json
new file mode 100644
index 0000000..b9db082
--- /dev/null
+++ b/locales/zh-TW/common.json
@@ -0,0 +1,52 @@
+{
+ "greeting": {
+ "hello": "您好!",
+ "help": "我今天能幫您什麼忙?"
+ },
+ "suggestedActions": {
+ "nextjs": {
+ "title": "使用 Next.js 的",
+ "label": "優勢有哪些?",
+ "action": "使用 Next.js 的優勢有哪些?"
+ },
+ "algorithm": {
+ "title": "編寫代碼來",
+ "label": "演示 Dijkstra 算法",
+ "action": "編寫代碼來演示 Dijkstra 算法"
+ },
+ "essay": {
+ "title": "幫我寫一篇關於",
+ "label": "矽谷的文章",
+ "action": "幫我寫一篇關於矽谷的文章"
+ },
+ "weather": {
+ "title": "舊金山的",
+ "label": "天氣如何?",
+ "action": "舊金山的天氣如何?"
+ }
+ },
+ "auth": {
+ "signIn": "登入",
+ "signUp": "註冊",
+ "signOut": "登出",
+ "noAccount": "還沒有帳戶?",
+ "forFree": "免費註冊。",
+ "hasAccount": "已有帳戶?",
+ "instead": "立即登入。",
+ "invalidCredentials": "憑據無效!",
+ "invalidData": "驗證提交失敗!",
+ "userExists": "帳戶已存在!",
+ "failedToCreate": "創建帳戶失敗!",
+ "passwordsDontMatch": "密碼不匹配!",
+ "accountCreated": "帳戶創建成功!",
+ "email": "郵箱",
+ "password": "密碼",
+ "register": "註冊"
+ },
+ "language": {
+ "select": "選擇語言",
+ "en": "English",
+ "zh-CN": "简体中文",
+ "zh-TW": "繁體中文"
+ }
+}
diff --git a/middleware.ts b/middleware.ts
index d00fa1a..eef55b2 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -1,6 +1,11 @@
import { NextResponse, type NextRequest } from 'next/server';
import { getToken } from 'next-auth/jwt';
import { guestRegex, isDevelopmentEnvironment } from './lib/constants';
+import createMiddleware from 'next-intl/middleware';
+import { routing } from './lib/i18n/routing';
+
+// Create i18n middleware
+const handleI18nRouting = createMiddleware(routing);
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
@@ -21,44 +26,58 @@ export async function middleware(request: NextRequest) {
return NextResponse.next();
}
+ // Handle i18n routing first
+ const i18nResponse = handleI18nRouting(request);
+
const token = await getToken({
req: request,
secret: process.env.AUTH_SECRET,
secureCookie: !isDevelopmentEnvironment,
});
+ // Get pathname without locale prefix for auth checks
+ const pathnameWithoutLocale = pathname.replace(
+ /^\/(en|zh-CN|zh-TW)(\/|$)/,
+ '/',
+ );
+
// if no token and not in login route, redirect to login
if (
!token &&
- !pathname.startsWith('/login') &&
- !pathname.startsWith('/register')
+ !pathnameWithoutLocale.startsWith('/login') &&
+ !pathnameWithoutLocale.startsWith('/register')
) {
- return NextResponse.redirect(new URL('/login', request.url));
+ // Extract locale from pathname
+ const localeMatch = pathname.match(/^\/(en|zh-CN|zh-TW)/);
+ const locale = localeMatch ? localeMatch[1] : '';
+ const loginPath = locale ? `/${locale}/login` : '/login';
+ return NextResponse.redirect(new URL(loginPath, request.url));
}
const isGuest = guestRegex.test(token?.email ?? '');
- if (token && !isGuest && ['/login', '/register'].includes(pathname)) {
- return NextResponse.redirect(new URL('/', request.url));
+ if (
+ token &&
+ !isGuest &&
+ ['/login', '/register'].includes(pathnameWithoutLocale)
+ ) {
+ // Extract locale from pathname
+ const localeMatch = pathname.match(/^\/(en|zh-CN|zh-TW)/);
+ const locale = localeMatch ? localeMatch[1] : '';
+ const homePath = locale ? `/${locale}` : '/';
+ return NextResponse.redirect(new URL(homePath, request.url));
}
- return NextResponse.next();
+ return i18nResponse;
}
export const config = {
matcher: [
- '/',
- '/chat/:id',
- '/api/:path*',
- '/login',
- '/register',
-
- /*
- * Match all request paths except for the ones starting with:
- * - _next/static (static files)
- * - _next/image (image optimization files)
- * - favicon.ico, sitemap.xml, robots.txt (metadata files)
- */
- '/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
+ // Match all pathnames except for
+ // - … if they start with `/api`, `/_next` or `/_vercel`
+ // - … the ones containing a dot (e.g. `favicon.ico`)
+ '/((?!api|_next|_vercel|.*\\..*).*)',
+ // However, match all pathnames within `/api`, except for the ones containing a dot
+ '/api/(.*)',
],
};
diff --git a/next.config.ts b/next.config.ts
index 6b2ca43..9031243 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,4 +1,7 @@
import type { NextConfig } from 'next';
+import createNextIntlPlugin from 'next-intl/plugin';
+
+const withNextIntl = createNextIntlPlugin('./lib/i18n/request.ts');
const nextConfig: NextConfig = {
experimental: {
@@ -16,4 +19,4 @@ const nextConfig: NextConfig = {
// export default withRspack(nextConfig);
-export default nextConfig;
+export default withNextIntl(nextConfig);
diff --git a/package.json b/package.json
index 7f26fbd..e18cd15 100644
--- a/package.json
+++ b/package.json
@@ -114,6 +114,7 @@
"nanoid": "^5.0.8",
"next": "15.4.0-canary.92",
"next-auth": "5.0.0-beta.29",
+ "next-intl": "^4.3.9",
"next-themes": "^0.3.0",
"nunjucks": "^3.2.4",
"orderedmap": "^2.1.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 129dc77..8e6742a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -269,6 +269,9 @@ importers:
next-auth:
specifier: 5.0.0-beta.29
version: 5.0.0-beta.29(@simplewebauthn/browser@13.1.0)(@simplewebauthn/server@13.1.1)(next@15.4.0-canary.92(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)
+ next-intl:
+ specifier: ^4.3.9
+ version: 4.3.9(next@15.4.0-canary.92(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)(typescript@5.8.2)
next-themes:
specifier: ^0.3.0
version: 0.3.0(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)
@@ -2046,6 +2049,24 @@ packages:
'@floating-ui/utils@0.2.9':
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
+ '@formatjs/ecma402-abstract@2.3.5':
+ resolution: {integrity: sha512-1HTESOq1IUa23g1lFZEGIXsfZKZOwWmB9RROwGn+xariiQnd++wwTMvlRAbZ8wtXRHFUamJPxsKcxpSzeCvFWQ==}
+
+ '@formatjs/fast-memoize@2.2.7':
+ resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==}
+
+ '@formatjs/icu-messageformat-parser@2.11.3':
+ resolution: {integrity: sha512-H/KfWSosaiDiOaW4nHe1Fn4Cgzm+oFQ8giTmB5RJzTBNSMmd+j2NVrvvZHAmlxJHcuOelzKBLjQ2EDcyH4NSWw==}
+
+ '@formatjs/icu-skeleton-parser@1.8.15':
+ resolution: {integrity: sha512-qNrKxWJmnWxin5U4A4Evy7C0rgRiNw3IqXu9OGuT31B8lDxBGl+OgT8kcq0ZVKK0gqA4l4SQB9x+SFAvLT5hcQ==}
+
+ '@formatjs/intl-localematcher@0.5.10':
+ resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==}
+
+ '@formatjs/intl-localematcher@0.6.2':
+ resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==}
+
'@gar/promisify@1.1.3':
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
@@ -3351,6 +3372,9 @@ packages:
'@rushstack/eslint-patch@1.11.0':
resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==}
+ '@schummar/icu-type-parser@1.21.5':
+ resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
+
'@sevinf/maybe@0.5.0':
resolution: {integrity: sha512-ARhyoYDnY1LES3vYI0fiG6e9esWfTNcXcO6+MPJJXcnyMV3bim4lnFt45VXouV7y82F4x3YH8nOQ6VztuvUiWg==}
@@ -5752,6 +5776,9 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
+ intl-messageformat@10.7.17:
+ resolution: {integrity: sha512-0Ugaf65B2J76rb31drgNF1l6bGEDkbIiYc2Glx6jaZINHnwa5kDRGy8KXYuA+/8P4G0c9prAFhfVhQJJfzUuvQ==}
+
ip-address@9.0.5:
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
engines: {node: '>= 12'}
@@ -6758,6 +6785,16 @@ packages:
nodemailer:
optional: true
+ next-intl@4.3.9:
+ resolution: {integrity: sha512-4oSROHlgy8a5Qr2vH69wxo9F6K0uc6nZM2GNzqSe6ET79DEzOmBeSijCRzD5txcI4i+XTGytu4cxFsDXLKEDpQ==}
+ peerDependencies:
+ next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
+ typescript: ^5.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
next-themes@0.3.0:
resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==}
peerDependencies:
@@ -8322,6 +8359,11 @@ packages:
'@types/react':
optional: true
+ use-intl@4.3.9:
+ resolution: {integrity: sha512-bZu+h13HIgOvsoGleQtUe4E6gM49CRm+AH36KnJVB/qb1+Beo7jr7HNrR8YWH8oaOkQfGNm6vh0HTepxng8UTg==}
+ peerDependencies:
+ react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
+
use-sidecar@1.1.3:
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'}
@@ -10489,6 +10531,36 @@ snapshots:
'@floating-ui/utils@0.2.9': {}
+ '@formatjs/ecma402-abstract@2.3.5':
+ dependencies:
+ '@formatjs/fast-memoize': 2.2.7
+ '@formatjs/intl-localematcher': 0.6.2
+ decimal.js: 10.5.0
+ tslib: 2.8.1
+
+ '@formatjs/fast-memoize@2.2.7':
+ dependencies:
+ tslib: 2.8.1
+
+ '@formatjs/icu-messageformat-parser@2.11.3':
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.5
+ '@formatjs/icu-skeleton-parser': 1.8.15
+ tslib: 2.8.1
+
+ '@formatjs/icu-skeleton-parser@1.8.15':
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.5
+ tslib: 2.8.1
+
+ '@formatjs/intl-localematcher@0.5.10':
+ dependencies:
+ tslib: 2.8.1
+
+ '@formatjs/intl-localematcher@0.6.2':
+ dependencies:
+ tslib: 2.8.1
+
'@gar/promisify@1.1.3':
optional: true
@@ -11969,6 +12041,8 @@ snapshots:
'@rushstack/eslint-patch@1.11.0': {}
+ '@schummar/icu-type-parser@1.21.5': {}
+
'@sevinf/maybe@0.5.0': {}
'@simplewebauthn/browser@13.1.0': {}
@@ -14833,6 +14907,13 @@ snapshots:
hasown: 2.0.2
side-channel: 1.1.0
+ intl-messageformat@10.7.17:
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.5
+ '@formatjs/fast-memoize': 2.2.7
+ '@formatjs/icu-messageformat-parser': 2.11.3
+ tslib: 2.8.1
+
ip-address@9.0.5:
dependencies:
jsbn: 1.1.0
@@ -16280,6 +16361,16 @@ snapshots:
'@simplewebauthn/browser': 13.1.0
'@simplewebauthn/server': 13.1.1
+ next-intl@4.3.9(next@15.4.0-canary.92(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)(typescript@5.8.2):
+ dependencies:
+ '@formatjs/intl-localematcher': 0.5.10
+ negotiator: 1.0.0
+ next: 15.4.0-canary.92(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021)
+ react: 19.0.0-rc-45804af1-20241021
+ use-intl: 4.3.9(react@19.0.0-rc-45804af1-20241021)
+ optionalDependencies:
+ typescript: 5.8.2
+
next-themes@0.3.0(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021):
dependencies:
react: 19.0.0-rc-45804af1-20241021
@@ -18096,6 +18187,13 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.18
+ use-intl@4.3.9(react@19.0.0-rc-45804af1-20241021):
+ dependencies:
+ '@formatjs/fast-memoize': 2.2.7
+ '@schummar/icu-type-parser': 1.21.5
+ intl-messageformat: 10.7.17
+ react: 19.0.0-rc-45804af1-20241021
+
use-sidecar@1.1.3(@types/react@18.3.18)(react@19.0.0-rc-45804af1-20241021):
dependencies:
detect-node-es: 1.1.0
From ac35811df95b4af23f87dc9e680c343db4b695f2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 3 Oct 2025 23:21:12 +0000
Subject: [PATCH 3/7] fix: update import paths after moving to [locale]
directory
Co-authored-by: sirily11 <32106111+sirily11@users.noreply.github.com>
---
.../(auth)/api/auth/[...nextauth]/route.ts | 2 +-
app/[locale]/(auth)/api/auth/guest/route.ts | 2 +-
.../(auth)/api/auth/link/telegram/actions.ts | 2 +-
app/[locale]/(auth)/login/page.tsx | 4 +-
app/[locale]/(chat)/actions-mcp.ts | 2 +-
app/[locale]/(chat)/actions.spec.ts | 2 +-
app/[locale]/(chat)/api/chat/route.ts | 2 +-
app/[locale]/(chat)/api/document/route.ts | 2 +-
.../api/documents/[id]/download/route.ts | 2 +-
.../(chat)/api/documents/[id]/route.ts | 2 +-
.../api/documents/complete-upload/route.ts | 2 +-
app/[locale]/(chat)/api/documents/route.ts | 2 +-
app/[locale]/(chat)/api/files/upload/route.ts | 2 +-
app/[locale]/(chat)/api/history/route.ts | 2 +-
app/[locale]/(chat)/api/prompts/route.spec.ts | 2 +-
app/[locale]/(chat)/api/prompts/route.ts | 2 +-
app/[locale]/(chat)/api/suggestions/route.ts | 2 +-
app/[locale]/(chat)/api/user/route.ts | 2 +-
app/[locale]/(chat)/api/vote/route.ts | 2 +-
app/[locale]/(chat)/chat/[id]/page.tsx | 2 +-
.../(chat)/jobs/[id]/results/actions.ts | 2 +-
.../(chat)/jobs/[id]/results/page.tsx | 2 +-
app/[locale]/(chat)/jobs/actions.spec.ts | 29 +++--
app/[locale]/(chat)/jobs/actions.ts | 2 +-
app/[locale]/(chat)/jobs/page.tsx | 2 +-
app/[locale]/(chat)/profile/actions.spec.ts | 14 ++-
app/[locale]/(chat)/profile/actions.ts | 2 +-
app/[locale]/(chat)/profile/page.tsx | 2 +-
.../webauthn/registration-options/route.ts | 2 +-
.../registration-verification/route.ts | 2 +-
app/layout.tsx | 2 +-
artifacts/flowchart/client.tsx | 2 +-
components/artifact.spec.tsx | 105 ++++++++++------
components/chat.spec.tsx | 84 +++++++------
components/input/multimodal-input.tsx | 2 +-
components/input/prompt-dialog.tsx | 2 +-
components/input/send-button.spec.tsx | 109 ++++++----------
components/job-card.tsx | 2 +-
components/job-result-card.tsx | 2 +-
components/jobs-list.tsx | 2 +-
components/message-editor.tsx | 2 +-
components/model-selector.spec.tsx | 28 ++++-
components/model-selector.tsx | 2 +-
components/profile/delete-account-card.tsx | 5 +-
components/profile/linking-tab.tsx | 2 +-
components/profile/password-reset-form.tsx | 5 +-
components/profile/sign-in-methods-card.tsx | 5 +-
components/prompt-form.tsx | 2 +-
components/sign-out-form.tsx | 2 +-
components/suggested-actions.spec.tsx | 119 +++++++++++-------
components/tool-invocation-header.spec.tsx | 118 +++++++++--------
components/web-search-result.spec.tsx | 67 ++++++----
hooks/use-chat-visibility.ts | 2 +-
hooks/use-mcp-tools-search.ts | 2 +-
lib/ai/entitlements.ts | 2 +-
lib/document/actions/action_server.ts | 2 +-
56 files changed, 437 insertions(+), 341 deletions(-)
diff --git a/app/[locale]/(auth)/api/auth/[...nextauth]/route.ts b/app/[locale]/(auth)/api/auth/[...nextauth]/route.ts
index ba24234..9df29b5 100644
--- a/app/[locale]/(auth)/api/auth/[...nextauth]/route.ts
+++ b/app/[locale]/(auth)/api/auth/[...nextauth]/route.ts
@@ -1 +1 @@
-export { GET, POST } from '@/app/(auth)/auth';
+export { GET, POST } from '@/app/[locale]/(auth)/auth';
diff --git a/app/[locale]/(auth)/api/auth/guest/route.ts b/app/[locale]/(auth)/api/auth/guest/route.ts
index 25af1fa..de6a56f 100644
--- a/app/[locale]/(auth)/api/auth/guest/route.ts
+++ b/app/[locale]/(auth)/api/auth/guest/route.ts
@@ -1,4 +1,4 @@
-import { signIn } from '@/app/(auth)/auth';
+import { signIn } from '@/app/[locale]/(auth)/auth';
import { isDevelopmentEnvironment } from '@/lib/constants';
import { getToken } from 'next-auth/jwt';
import { NextResponse } from 'next/server';
diff --git a/app/[locale]/(auth)/api/auth/link/telegram/actions.ts b/app/[locale]/(auth)/api/auth/link/telegram/actions.ts
index 31afac0..74d40c8 100644
--- a/app/[locale]/(auth)/api/auth/link/telegram/actions.ts
+++ b/app/[locale]/(auth)/api/auth/link/telegram/actions.ts
@@ -1,6 +1,6 @@
'use server';
-import { auth } from '@/app/(auth)/auth';
+import { auth } from '@/app/[locale]/(auth)/auth';
import {
getUserTelegramLink,
unlinkTelegramFromUser,
diff --git a/app/[locale]/(auth)/login/page.tsx b/app/[locale]/(auth)/login/page.tsx
index e6af4cb..ed171f5 100644
--- a/app/[locale]/(auth)/login/page.tsx
+++ b/app/[locale]/(auth)/login/page.tsx
@@ -73,9 +73,7 @@ export default function Page() {