cms.c2sgmbh/src/middleware.ts
Martin Porwoll da735cab46 feat: add Products and ProductCategories collections with CI/CD pipeline
- Add Products collection with comprehensive fields (pricing, inventory, SEO, CTA)
- Add ProductCategories collection with hierarchical structure
- Implement CI/CD pipeline with GitHub Actions (lint, typecheck, test, build, e2e)
- Add access control test utilities and unit tests
- Fix Posts API to include category field for backwards compatibility
- Update ESLint config with ignores for migrations and admin components
- Add centralized access control functions in src/lib/access
- Add db-direct.sh utility script for database access

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-12 21:36:26 +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 { 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).*)'],
}