persist locale on language switch when functional consent given

This commit is contained in:
2026-08-13 00:57:02 +02:00
parent 5f76050fa8
commit 720bf6914b
4 changed files with 67 additions and 31 deletions
+32 -12
View File
@@ -25,18 +25,43 @@ import { isValidLocale, LOCALE_COOKIE_NAME, negotiateLocale } from '../i18n/inde
* import { localeMiddleware } from './ipal.middleware' // created from this factory
* export function proxy(req) {
* const r = localeMiddleware(req)
* if (r.type === 'next') return NextResponse.next()
* const res = NextResponse.redirect(r.location)
* // cookie is optional: only present when the visitor consented to the
* // gating category (functional by default). Guard before setting.
* // Both results may carry an optional cookie — 'next' when the visitor
* // switched language (URL locale differs from the stored one) and consented,
* // 'redirect' on the initial locale negotiation. Set it whenever present.
* const res = r.type === 'next' ? NextResponse.next() : NextResponse.redirect(r.location)
* if (r.cookie) res.cookies.set(r.cookie.name, r.cookie.value)
* return res
* }
*/ export function createLocaleMiddleware({ config, consentCategory = 'functional', consentCookieName = CONSENT_COOKIE, cookieName = LOCALE_COOKIE_NAME }) {
// Whether the locale cookie may be written: 'necessary' is always granted;
// 'functional' (default) requires the visitor to have consented.
function mayPersistLocale(request) {
if (consentCategory === 'necessary') {
return true;
}
const consent = parseConsent(request.cookies.get(consentCookieName)?.value);
return consent?.[consentCategory] === true;
}
return function localeMiddleware(request) {
const { pathname } = request.nextUrl;
// Already locale-prefixed → nothing to do
if (isValidLocale(firstSegment(pathname), config)) {
// Already locale-prefixed (e.g. the visitor switched language by
// navigating to /en). Routing is fine — but if the URL's locale differs
// from the stored cookie, the visitor is *choosing* a language, and we
// should remember it — provided they consented to the gating category.
// Without consent we leave the cookie untouched: the switch works for this
// visit but isn't persisted, which is exactly the functional-cookie rule.
const urlLocale = firstSegment(pathname);
if (isValidLocale(urlLocale, config)) {
const currentCookie = request.cookies.get(cookieName)?.value ?? null;
if (currentCookie !== urlLocale && mayPersistLocale(request)) {
return {
type: 'next',
cookie: {
name: cookieName,
value: urlLocale
}
};
}
return {
type: 'next'
};
@@ -57,15 +82,10 @@ import { isValidLocale, LOCALE_COOKIE_NAME, negotiateLocale } from '../i18n/inde
// Without consent the locale is still detected each request (routing works),
// it just isn't remembered across visits — which is the whole point of
// gating a functional cookie behind consent.
let mayPersist = consentCategory === 'necessary';
if (!mayPersist) {
const consent = parseConsent(request.cookies.get(consentCookieName)?.value);
mayPersist = consent?.[consentCategory] === true;
}
return {
type: 'redirect',
location: url.toString(),
...mayPersist ? {
...mayPersistLocale(request) ? {
cookie: {
name: cookieName,
value: locale