Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
657f50a135 | ||
|
|
720bf6914b |
+8
-4
@@ -32,6 +32,10 @@ export type LocaleMiddlewareResult = {
|
|||||||
location: string;
|
location: string;
|
||||||
type: 'redirect';
|
type: 'redirect';
|
||||||
} | {
|
} | {
|
||||||
|
cookie?: {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
type: 'next';
|
type: 'next';
|
||||||
};
|
};
|
||||||
type CreateLocaleMiddlewareArgs = {
|
type CreateLocaleMiddlewareArgs = {
|
||||||
@@ -68,10 +72,10 @@ type CreateLocaleMiddlewareArgs = {
|
|||||||
* import { localeMiddleware } from './ipal.middleware' // created from this factory
|
* import { localeMiddleware } from './ipal.middleware' // created from this factory
|
||||||
* export function proxy(req) {
|
* export function proxy(req) {
|
||||||
* const r = localeMiddleware(req)
|
* const r = localeMiddleware(req)
|
||||||
* if (r.type === 'next') return NextResponse.next()
|
* // Both results may carry an optional cookie — 'next' when the visitor
|
||||||
* const res = NextResponse.redirect(r.location)
|
* // switched language (URL locale differs from the stored one) and consented,
|
||||||
* // cookie is optional: only present when the visitor consented to the
|
* // 'redirect' on the initial locale negotiation. Set it whenever present.
|
||||||
* // gating category (functional by default). Guard before setting.
|
* const res = r.type === 'next' ? NextResponse.next() : NextResponse.redirect(r.location)
|
||||||
* if (r.cookie) res.cookies.set(r.cookie.name, r.cookie.value)
|
* if (r.cookie) res.cookies.set(r.cookie.name, r.cookie.value)
|
||||||
* return res
|
* return res
|
||||||
* }
|
* }
|
||||||
|
|||||||
+32
-12
@@ -25,18 +25,43 @@ import { isValidLocale, LOCALE_COOKIE_NAME, negotiateLocale } from '../i18n/inde
|
|||||||
* import { localeMiddleware } from './ipal.middleware' // created from this factory
|
* import { localeMiddleware } from './ipal.middleware' // created from this factory
|
||||||
* export function proxy(req) {
|
* export function proxy(req) {
|
||||||
* const r = localeMiddleware(req)
|
* const r = localeMiddleware(req)
|
||||||
* if (r.type === 'next') return NextResponse.next()
|
* // Both results may carry an optional cookie — 'next' when the visitor
|
||||||
* const res = NextResponse.redirect(r.location)
|
* // switched language (URL locale differs from the stored one) and consented,
|
||||||
* // cookie is optional: only present when the visitor consented to the
|
* // 'redirect' on the initial locale negotiation. Set it whenever present.
|
||||||
* // gating category (functional by default). Guard before setting.
|
* const res = r.type === 'next' ? NextResponse.next() : NextResponse.redirect(r.location)
|
||||||
* if (r.cookie) res.cookies.set(r.cookie.name, r.cookie.value)
|
* if (r.cookie) res.cookies.set(r.cookie.name, r.cookie.value)
|
||||||
* return res
|
* return res
|
||||||
* }
|
* }
|
||||||
*/ export function createLocaleMiddleware({ config, consentCategory = 'functional', consentCookieName = CONSENT_COOKIE, cookieName = LOCALE_COOKIE_NAME }) {
|
*/ 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) {
|
return function localeMiddleware(request) {
|
||||||
const { pathname } = request.nextUrl;
|
const { pathname } = request.nextUrl;
|
||||||
// Already locale-prefixed → nothing to do
|
// Already locale-prefixed (e.g. the visitor switched language by
|
||||||
if (isValidLocale(firstSegment(pathname), config)) {
|
// 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 {
|
return {
|
||||||
type: 'next'
|
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),
|
// Without consent the locale is still detected each request (routing works),
|
||||||
// it just isn't remembered across visits — which is the whole point of
|
// it just isn't remembered across visits — which is the whole point of
|
||||||
// gating a functional cookie behind consent.
|
// 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 {
|
return {
|
||||||
type: 'redirect',
|
type: 'redirect',
|
||||||
location: url.toString(),
|
location: url.toString(),
|
||||||
...mayPersist ? {
|
...mayPersistLocale(request) ? {
|
||||||
cookie: {
|
cookie: {
|
||||||
name: cookieName,
|
name: cookieName,
|
||||||
value: locale
|
value: locale
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@intecion/ipal-kit",
|
"name": "@intecion/ipal-kit",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"description": "Intecion Payload Advanced Library — a Payload CMS 3 plugin: i18n, SEO, forms, consent, analytics, blog/archives.",
|
"description": "Intecion Payload Advanced Library — a Payload CMS 3 plugin: i18n, SEO, forms, consent, analytics, blog/archives.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type MiddlewareRequest = {
|
|||||||
*/
|
*/
|
||||||
export type LocaleMiddlewareResult =
|
export type LocaleMiddlewareResult =
|
||||||
| { cookie?: { name: string; value: string }; location: string; type: 'redirect' }
|
| { cookie?: { name: string; value: string }; location: string; type: 'redirect' }
|
||||||
| { type: 'next' }
|
| { cookie?: { name: string; value: string }; type: 'next' }
|
||||||
|
|
||||||
type CreateLocaleMiddlewareArgs = {
|
type CreateLocaleMiddlewareArgs = {
|
||||||
config: I18nConfig
|
config: I18nConfig
|
||||||
@@ -66,10 +66,10 @@ function firstSegment(pathname: string): string {
|
|||||||
* import { localeMiddleware } from './ipal.middleware' // created from this factory
|
* import { localeMiddleware } from './ipal.middleware' // created from this factory
|
||||||
* export function proxy(req) {
|
* export function proxy(req) {
|
||||||
* const r = localeMiddleware(req)
|
* const r = localeMiddleware(req)
|
||||||
* if (r.type === 'next') return NextResponse.next()
|
* // Both results may carry an optional cookie — 'next' when the visitor
|
||||||
* const res = NextResponse.redirect(r.location)
|
* // switched language (URL locale differs from the stored one) and consented,
|
||||||
* // cookie is optional: only present when the visitor consented to the
|
* // 'redirect' on the initial locale negotiation. Set it whenever present.
|
||||||
* // gating category (functional by default). Guard before setting.
|
* const res = r.type === 'next' ? NextResponse.next() : NextResponse.redirect(r.location)
|
||||||
* if (r.cookie) res.cookies.set(r.cookie.name, r.cookie.value)
|
* if (r.cookie) res.cookies.set(r.cookie.name, r.cookie.value)
|
||||||
* return res
|
* return res
|
||||||
* }
|
* }
|
||||||
@@ -80,11 +80,29 @@ export function createLocaleMiddleware({
|
|||||||
consentCookieName = CONSENT_COOKIE,
|
consentCookieName = CONSENT_COOKIE,
|
||||||
cookieName = LOCALE_COOKIE_NAME,
|
cookieName = LOCALE_COOKIE_NAME,
|
||||||
}: CreateLocaleMiddlewareArgs) {
|
}: CreateLocaleMiddlewareArgs) {
|
||||||
|
// Whether the locale cookie may be written: 'necessary' is always granted;
|
||||||
|
// 'functional' (default) requires the visitor to have consented.
|
||||||
|
function mayPersistLocale(request: MiddlewareRequest): boolean {
|
||||||
|
if (consentCategory === 'necessary') {return true}
|
||||||
|
const consent = parseConsent(request.cookies.get(consentCookieName)?.value)
|
||||||
|
return consent?.[consentCategory] === true
|
||||||
|
}
|
||||||
|
|
||||||
return function localeMiddleware(request: MiddlewareRequest): LocaleMiddlewareResult {
|
return function localeMiddleware(request: MiddlewareRequest): LocaleMiddlewareResult {
|
||||||
const { pathname } = request.nextUrl
|
const { pathname } = request.nextUrl
|
||||||
|
|
||||||
// Already locale-prefixed → nothing to do
|
// Already locale-prefixed (e.g. the visitor switched language by
|
||||||
if (isValidLocale(firstSegment(pathname), config)) {
|
// 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' }
|
return { type: 'next' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,16 +124,10 @@ export function createLocaleMiddleware({
|
|||||||
// Without consent the locale is still detected each request (routing works),
|
// Without consent the locale is still detected each request (routing works),
|
||||||
// it just isn't remembered across visits — which is the whole point of
|
// it just isn't remembered across visits — which is the whole point of
|
||||||
// gating a functional cookie behind consent.
|
// 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 {
|
return {
|
||||||
type: 'redirect',
|
type: 'redirect',
|
||||||
location: url.toString(),
|
location: url.toString(),
|
||||||
...(mayPersist ? { cookie: { name: cookieName, value: locale } } : {}),
|
...(mayPersistLocale(request) ? { cookie: { name: cookieName, value: locale } } : {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user