gate locale behind functional consent

This commit is contained in:
2026-08-12 18:03:11 +02:00
parent 195d4169f5
commit 4bf50514bf
212 changed files with 173 additions and 840 deletions
+20 -8
View File
@@ -20,12 +20,12 @@ type MiddlewareRequest = {
url: string;
};
/**
* What the factory returns — the caller (in Next next-middleware.ts) decides how to
* What the factory returns — the caller (in Next middleware.ts) decides how to
* act: `redirect` means send a 307 to `location` and set the locale cookie;
* `next` means let the request pass through untouched.
*/
export type LocaleMiddlewareResult = {
cookie: {
cookie?: {
name: string;
value: string;
};
@@ -36,6 +36,16 @@ export type LocaleMiddlewareResult = {
};
type CreateLocaleMiddlewareArgs = {
config: I18nConfig;
/**
* Consent category that gates *persisting* the locale cookie. The locale is
* always detected (routing works regardless), but the choice is only written
* to a cookie once the visitor has consented to this category. Defaults to
* 'functional'. Pass 'necessary' to always persist (treat locale as strictly
* necessary), which restores the pre-consent behaviour.
*/
consentCategory?: 'functional' | 'necessary';
/** Name of the consent cookie to read. Defaults to CONSENT_COOKIE. */
consentCookieName?: string;
/** Cookie name for the locale choice. Defaults to LOCALE_COOKIE_NAME. */
cookieName?: string;
};
@@ -48,23 +58,25 @@ type CreateLocaleMiddlewareArgs = {
* comes from negotiateLocale (cookie → Accept-Language → default)
* - the chosen locale is written to a cookie so the next visit is stable
*
* The plugin returns a decision; the thin next-middleware.ts in the client project
* The plugin returns a decision; the thin middleware.ts in the client project
* turns it into a NextResponse. This keeps all logic in the plugin while
* respecting that next-middleware.ts must physically live in the client app.
* respecting that middleware.ts must physically live in the client app.
*
* @example
* // next-middleware.ts (client project) — one wiring file, no logic:
* // middleware.ts (client project) — one wiring file, no logic:
* import { NextResponse } from 'next/server'
* import { localeMiddleware } from './ipal.middleware' // created from this factory
* export function middleware(req) {
* export function proxy(req) {
* const r = localeMiddleware(req)
* if (r.type === 'next') return NextResponse.next()
* const res = NextResponse.redirect(r.location)
* res.cookies.set(r.cookie.name, r.cookie.value)
* // cookie is optional: only present when the visitor consented to the
* // gating category (functional by default). Guard before setting.
* if (r.cookie) res.cookies.set(r.cookie.name, r.cookie.value)
* return res
* }
*/
export declare function createLocaleMiddleware({ config, cookieName, }: CreateLocaleMiddlewareArgs): (request: MiddlewareRequest) => LocaleMiddlewareResult;
export declare function createLocaleMiddleware({ config, consentCategory, consentCookieName, cookieName, }: CreateLocaleMiddlewareArgs): (request: MiddlewareRequest) => LocaleMiddlewareResult;
/**
* Default Next.js middleware matcher: run on everything except API routes, the
* admin panel, Next internals, and files with an extension (static assets).