feat(i18n): add english locale and middleware for browser language detection
This commit is contained in:
@@ -28,7 +28,7 @@ export async function generateMetadata(
|
|||||||
const doc = await payload.findByID({
|
const doc = await payload.findByID({
|
||||||
collection: ('collection' in route ? route.collection : 'pages') as 'pages',
|
collection: ('collection' in route ? route.collection : 'pages') as 'pages',
|
||||||
id: route.doc.id as string,
|
id: route.doc.id as string,
|
||||||
locale: locale as 'all' | 'pl' | 'de',
|
locale: locale as 'all' | 'pl' | 'de' | 'en',
|
||||||
})
|
})
|
||||||
|
|
||||||
// Retrieve meta object injected by @payloadcms/plugin-seo
|
// Retrieve meta object injected by @payloadcms/plugin-seo
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export const i18n = {
|
|||||||
locales: [
|
locales: [
|
||||||
{ code: 'pl', label: 'Polski' },
|
{ code: 'pl', label: 'Polski' },
|
||||||
{ code: 'de', label: 'Deutsch' },
|
{ code: 'de', label: 'Deutsch' },
|
||||||
|
{ code: 'en', label: 'English' },
|
||||||
],
|
],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
import type { NextRequest } from 'next/server'
|
||||||
|
import { i18n } from './i18n.config'
|
||||||
|
|
||||||
|
function getLocale(request: NextRequest): string {
|
||||||
|
const acceptLanguage = request.headers.get('accept-language')
|
||||||
|
const defaultLocale = i18n.defaultLocale
|
||||||
|
const supportedLocales = i18n.locales.map((l) => l.code)
|
||||||
|
|
||||||
|
if (!acceptLanguage) {
|
||||||
|
return defaultLocale
|
||||||
|
}
|
||||||
|
|
||||||
|
// Very basic parser for Accept-Language header
|
||||||
|
// Example header: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5
|
||||||
|
const languages = acceptLanguage
|
||||||
|
.split(',')
|
||||||
|
.map((lang) => {
|
||||||
|
const [code, qValue] = lang.split(';q=')
|
||||||
|
const quality = qValue ? parseFloat(qValue) : 1.0
|
||||||
|
return { code: code.trim().split('-')[0].toLowerCase(), quality } // Extract primary subtag (e.g., 'en-US' -> 'en')
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.quality - a.quality)
|
||||||
|
|
||||||
|
// Find the first matching supported locale
|
||||||
|
for (const { code } of languages) {
|
||||||
|
if (supportedLocales.includes(code as any)) {
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultLocale
|
||||||
|
}
|
||||||
|
|
||||||
|
export function middleware(request: NextRequest) {
|
||||||
|
const { pathname } = request.nextUrl
|
||||||
|
const locales = i18n.locales.map((l) => l.code)
|
||||||
|
|
||||||
|
// Bypass paths that should not be localized
|
||||||
|
const bypassPaths = [
|
||||||
|
'/api',
|
||||||
|
'/_next',
|
||||||
|
'/admin',
|
||||||
|
'/favicon.ico',
|
||||||
|
'/robots.txt',
|
||||||
|
'/sitemap.xml',
|
||||||
|
'/site.webmanifest',
|
||||||
|
'/icon', // standard icons
|
||||||
|
]
|
||||||
|
|
||||||
|
if (bypassPaths.some((path) => pathname.startsWith(path))) {
|
||||||
|
return NextResponse.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is any supported locale in the pathname
|
||||||
|
const pathnameHasLocale = locales.some(
|
||||||
|
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (pathnameHasLocale) {
|
||||||
|
return NextResponse.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect if there is no locale
|
||||||
|
const locale = getLocale(request)
|
||||||
|
request.nextUrl.pathname = `/${locale}${pathname}`
|
||||||
|
|
||||||
|
return NextResponse.redirect(request.nextUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
// Matcher ignoring `/_next/` and `/api/`
|
||||||
|
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
||||||
|
}
|
||||||
@@ -95,7 +95,7 @@ export interface Config {
|
|||||||
db: {
|
db: {
|
||||||
defaultIDType: number;
|
defaultIDType: number;
|
||||||
};
|
};
|
||||||
fallbackLocale: ('false' | 'none' | 'null') | false | null | ('pl' | 'de') | ('pl' | 'de')[];
|
fallbackLocale: ('false' | 'none' | 'null') | false | null | ('pl' | 'de' | 'en') | ('pl' | 'de' | 'en')[];
|
||||||
globals: {
|
globals: {
|
||||||
company: Company;
|
company: Company;
|
||||||
header: Header;
|
header: Header;
|
||||||
@@ -114,7 +114,7 @@ export interface Config {
|
|||||||
'cookie-settings': CookieSettingsSelect<false> | CookieSettingsSelect<true>;
|
'cookie-settings': CookieSettingsSelect<false> | CookieSettingsSelect<true>;
|
||||||
notifications: NotificationsSelect<false> | NotificationsSelect<true>;
|
notifications: NotificationsSelect<false> | NotificationsSelect<true>;
|
||||||
};
|
};
|
||||||
locale: 'pl' | 'de';
|
locale: 'pl' | 'de' | 'en';
|
||||||
widgets: {
|
widgets: {
|
||||||
collections: CollectionsWidget;
|
collections: CollectionsWidget;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user