feat: /en/* 改为直接 404(URL 即语言,无默认语言别名)

- 设计契约变更:/ 与 /* 英文站、/zh 与 /zh/* 中文站、/en 与 /en/* 非法路径
  直接 404(品牌 404 页),不再 307 规范化到无前缀形式
- proxy rewrite 到内部不存在路径渲染 not-found(404),URL 保持原样
- LanguageSwitcher 同步修复:切英文生成无前缀路径(此前产生 /en/ 会撞 404)
- 回归:/en/* 全部 404;/、/login、/zh/* 全部 200;语言切换双向闭环正常;
  mock 登录正常
This commit is contained in:
AI Directory Platform
2026-09-10 07:39:16 +00:00
parent 86d0d9c7ed
commit 55f7ea5a04
2 changed files with 14 additions and 12 deletions
@@ -2,7 +2,7 @@
import { useLocale } from 'next-intl'; import { useLocale } from 'next-intl';
import { useRouter, usePathname } from 'next/navigation'; import { useRouter, usePathname } from 'next/navigation';
import { locales, localeNames } from '@/i18n.config'; import { locales, localeNames, defaultLocale } from '@/i18n.config';
import type { Locale } from '@/i18n.config'; import type { Locale } from '@/i18n.config';
import { useState, useRef, useEffect } from 'react'; import { useState, useRef, useEffect } from 'react';
import { ChevronDown, Globe, Check } from 'lucide-react'; import { ChevronDown, Globe, Check } from 'lucide-react';
@@ -15,6 +15,7 @@ export function LanguageSwitcher() {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
// URL 即语言:默认语言(en)无前缀,其他语言带 /{locale};去掉当前路径上的语言段后再拼目标前缀
const switchLocale = (newLocale: Locale) => { const switchLocale = (newLocale: Locale) => {
if (newLocale === locale) { if (newLocale === locale) {
setIsOpen(false); setIsOpen(false);
@@ -31,7 +32,8 @@ export function LanguageSwitcher() {
} }
const normalizedPath = path === '/' ? '' : path; const normalizedPath = path === '/' ? '' : path;
router.push(`/${newLocale}${normalizedPath}`); const prefix = newLocale === defaultLocale ? '' : `/${newLocale}`;
router.push(`${prefix}${normalizedPath}`);
setIsOpen(false); setIsOpen(false);
}; };
+10 -10
View File
@@ -6,20 +6,20 @@ const intlMiddleware = createIntlMiddleware(routing);
/** /**
* # proxy * # proxy
* 说明:站点入口中间件。 * 说明:站点入口中间件。URL 即语言(设计契约):
* - 显式规范化:/en 与 /en/* 307 → 去前缀形式(/ 与 /*)。 * - / 与 /* …… 英文站(默认语言,无前缀)
* 底座契约:默认语言 URL 不带前缀;历史/外部 /en/* 链接永远落到规范 URL, * - /zh 与 /zh/* …… 中文站
* 行为不依赖 next-intl 内部实现。中文 /zh/* 由 intlMiddleware 正常处理。 * - /en 与 /en/* …… 非法路径,直接 404(品牌 404 页)。
* 默认语言不提供带前缀的别名,避免 /en/* 与 /* 产生重复内容与重定向链。
*/ */
export function proxy(request: NextRequest) { export function proxy(request: NextRequest) {
const { pathname, search } = request.nextUrl; const { pathname } = request.nextUrl;
if (pathname === '/en' || pathname.startsWith('/en/')) { if (pathname === '/en' || pathname.startsWith('/en/')) {
const target = pathname === '/en' ? '/' : pathname.slice(3); // rewrite 到不存在的内部路径 → Next 渲染 not-found(404),URL 保持原样
return NextResponse.redirect( const url = request.nextUrl.clone();
new URL(`${target}${search}`, request.url), url.pathname = '/__en_not_available__';
307 return NextResponse.rewrite(url, { status: 404 });
);
} }
return intlMiddleware(request); return intlMiddleware(request);