cms.c2sgmbh/src/middleware.ts
Martin Porwoll 51c340e9e7 feat: add i18n, SEO, and frontend infrastructure
Localization:
- Add middleware for locale detection/routing
- Add [locale] dynamic route structure
- Add i18n utility library (DE/EN support)

SEO & Discovery:
- Add robots.ts for search engine directives
- Add sitemap.ts for XML sitemap generation
- Add structuredData.ts for JSON-LD schemas

Utilities:
- Add search.ts for full-text search functionality
- Add tenantAccess.ts for multi-tenant access control
- Add envValidation.ts for environment validation

Frontend:
- Update layout.tsx with locale support
- Update page.tsx for localized content
- Add API routes for frontend functionality
- Add instrumentation.ts for monitoring

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 08:19:35 +00:00

101 lines
2.8 KiB
TypeScript

// src/middleware.ts
// Next.js Middleware for locale detection and routing
import { NextRequest, NextResponse } from 'next/server'
import { locales, defaultLocale, isValidLocale, type Locale } from '@/lib/i18n'
// Paths that should not be affected by locale routing
const PUBLIC_FILE = /\.(.*)$/
const EXCLUDED_PATHS = ['/admin', '/api', '/_next', '/favicon.ico', '/robots.txt', '/sitemap.xml']
/**
* Detect user's preferred locale from Accept-Language header
*/
function getPreferredLocale(request: NextRequest): Locale {
const acceptLanguage = request.headers.get('accept-language')
if (!acceptLanguage) {
return defaultLocale
}
// Parse Accept-Language header
const languages = acceptLanguage
.split(',')
.map((lang) => {
const [code, priority] = lang.trim().split(';q=')
return {
code: code.split('-')[0].toLowerCase(), // Get primary language code
priority: priority ? parseFloat(priority) : 1,
}
})
.sort((a, b) => b.priority - a.priority)
// Find first matching locale
for (const { code } of languages) {
if (isValidLocale(code)) {
return code
}
}
return defaultLocale
}
/**
* Get locale from cookie
*/
function getLocaleFromCookie(request: NextRequest): Locale | null {
const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value
if (cookieLocale && isValidLocale(cookieLocale)) {
return cookieLocale
}
return null
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
// Skip locale routing for excluded paths and public files
if (
EXCLUDED_PATHS.some((path) => pathname.startsWith(path)) ||
PUBLIC_FILE.test(pathname)
) {
return NextResponse.next()
}
// Check if pathname already has a valid locale prefix
const pathnameLocale = pathname.split('/')[1]
if (isValidLocale(pathnameLocale)) {
// Valid locale in URL, set cookie and continue
const response = NextResponse.next()
response.cookies.set('NEXT_LOCALE', pathnameLocale, {
maxAge: 60 * 60 * 24 * 365, // 1 year
path: '/',
})
return response
}
// No locale in URL, redirect to preferred locale
const cookieLocale = getLocaleFromCookie(request)
const preferredLocale = cookieLocale || getPreferredLocale(request)
// Build new URL with locale prefix
const newUrl = new URL(request.url)
newUrl.pathname = `/${preferredLocale}${pathname === '/' ? '' : pathname}`
// Redirect to localized URL
const response = NextResponse.redirect(newUrl)
response.cookies.set('NEXT_LOCALE', preferredLocale, {
maxAge: 60 * 60 * 24 * 365, // 1 year
path: '/',
})
return response
}
export const config = {
// Match all paths except static files and API routes
matcher: ['/((?!api|_next/static|_next/image|admin|favicon.ico).*)'],
}