Files
FastGPT/document/middleware.ts
Archer 2fd4b6030b feat(i18n): Fix language loss in navigation and add language selector (#6467)
* feat(docs): enable i18n language selector

* docs(i18n): translate introduction page to English

* fix(i18n): fix language switching issue by always showing locale prefix

* fix(docs): use relative paths for internal links to preserve language

* refactor(i18n): add getLocalizedPath helper to simplify URL generation

* refactor(i18n): make getLocalizedPath respect hideLocale config

* feat(i18n): fallback to default language when translation missing, keep URL unchanged

* feat(i18n): fix language loss in navigation and add language selector

- Set hideLocale to 'never' to always show language prefix
- Add localized-navigation.ts with useLocalizedRouter hook
- Update all navigation points to preserve language:
  1. Tab navigation (already using getLocalizedPath)
  2. Sidebar navigation (handled by Fumadocs)
  3. Home/404 redirects (using getLocalizedPath)
  4. MDX Redirect component (using useLocalizedRouter)
  5. Old page redirects (updated not-found.tsx)
  6. Document links (custom LocalizedLink component)
- Configure language selector in layout.config.tsx
- Add LOCALIZED_NAVIGATION.md documentation

* fix(i18n): fix type errors and useEffect dependencies

* refactor(i18n): move redirects to middleware for SSR support

- Move old path redirects from client-side (not-found.tsx) to server-side (middleware.ts)
- Use 301 permanent redirects for better SEO
- Preserve language prefix in redirects
- Fix SSR issue caused by client-side redirects

* refactor(i18n): clean up not-found.tsx, remove duplicate redirect maps

- Remove duplicate exactMap and prefixMap (now in middleware)
- Keep dynamic meta.json lookup for unknown pages
- Simplify to only handle fallback cases
- Two-layer approach: middleware (SSR) + not-found (dynamic)

* refactor(i18n): simplify not-found to always redirect to introduction

- Remove dynamic meta.json lookup
- Always redirect to introduction page on 404
- Ensures no 404 pages are shown
- Keep language prefix in redirect

* fix(i18n): fix middleware type error with ts-expect-error

- Add @ts-expect-error for Fumadocs middleware signature mismatch
- Fix syntax error in config matcher (remove literal \n)

---------

Co-authored-by: archer <archer@archerdeMac-mini.local>
2026-02-26 16:29:03 +08:00

68 lines
2.4 KiB
TypeScript

import { createI18nMiddleware } from 'fumadocs-core/i18n';
import { i18n } from '@/lib/i18n';
import { NextRequest, NextResponse } from 'next/server';
// Old path redirects mapping
const exactMap: Record<string, string> = {
'/docs': '/docs/introduction',
'/docs/intro': '/docs/introduction',
'/docs/guide/dashboard/workflow/coreferenceresolution':
'/docs/introduction/guide/dashboard/workflow/coreferenceResolution',
'/docs/guide/admin/sso_dingtalk':
'/docs/introduction/guide/admin/sso#/docs/introduction/guide/admin/sso#钉钉',
'/docs/guide/knowledge_base/rag': '/docs/introduction/guide/knowledge_base/RAG',
'/docs/commercial/intro/': '/docs/introduction/commercial',
'/docs/upgrading/intro/': '/docs/upgrading',
'/docs/introduction/shopping_cart/intro/': '/docs/introduction/commercial'
};
const prefixMap: Record<string, string> = {
'/docs/development': '/docs/introduction/development',
'/docs/FAQ': '/docs/faq',
'/docs/guide': '/docs/introduction/guide',
'/docs/shopping_cart': '/docs/introduction/shopping_cart',
'/docs/agreement': '/docs/protocol',
'/docs/introduction/development/openapi': '/docs/openapi'
};
const i18nMiddleware = createI18nMiddleware(i18n);
export default function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Extract language from pathname
let lang = i18n.defaultLanguage;
let pathWithoutLang = pathname;
for (const language of i18n.languages) {
if (pathname.startsWith(`/${language}`)) {
lang = language;
pathWithoutLang = pathname.slice(`/${language}`.length);
break;
}
}
// Check exact match redirects
if (exactMap[pathWithoutLang]) {
const newUrl = new URL(`/${lang}${exactMap[pathWithoutLang]}`, request.url);
return NextResponse.redirect(newUrl, 301);
}
// Check prefix match redirects
for (const [oldPrefix, newPrefix] of Object.entries(prefixMap)) {
if (pathWithoutLang.startsWith(oldPrefix)) {
const rest = pathWithoutLang.slice(oldPrefix.length);
const newUrl = new URL(`/${lang}${newPrefix}${rest}`, request.url);
return NextResponse.redirect(newUrl, 301);
}
}
// Continue with i18n middleware
// @ts-expect-error - Fumadocs middleware signature mismatch
return i18nMiddleware(request);
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico|.*\\.svg|.*\\.png|deploy/.*).*)']
};