Added support for single localization
This commit is contained in:
@@ -1,16 +1,15 @@
|
||||
import type { I18nConfig } from './types.js'
|
||||
|
||||
import type { I18nConfig } from '../i18n/index.js'
|
||||
import { negotiateLocale, isValidLocale, LOCALE_COOKIE_NAME } from '../i18n/index.js'
|
||||
import { CONSENT_COOKIE, parseConsent } from '../consent/storage.js'
|
||||
import { isValidLocale, LOCALE_COOKIE_NAME, negotiateLocale } from '../i18n/index.js'
|
||||
|
||||
/**
|
||||
* Minimal request shape the middleware reads. Kept structural so the plugin
|
||||
* doesn't hard-depend on next/server types; a Next.js `NextRequest` satisfies it.
|
||||
*/
|
||||
type MiddlewareRequest = {
|
||||
nextUrl: { pathname: string; search: string; clone: () => URL }
|
||||
cookies: { get: (name: string) => { value: string } | undefined }
|
||||
headers: { get: (name: string) => null | string }
|
||||
nextUrl: { clone: () => URL; pathname: string; search: string }
|
||||
headers: { get: (name: string) => string | null }
|
||||
url: string
|
||||
}
|
||||
|
||||
@@ -20,11 +19,13 @@ type MiddlewareRequest = {
|
||||
* `next` means let the request pass through untouched.
|
||||
*/
|
||||
export type LocaleMiddlewareResult =
|
||||
| { cookie?: { name: string; value: string }; location: string; type: 'redirect' }
|
||||
| { cookie?: { name: string; value: string }; type: 'next' }
|
||||
| { type: 'next'; cookie?: { name: string; value: string } }
|
||||
| { type: 'redirect'; location: string; cookie?: { name: string; value: string } }
|
||||
|
||||
type CreateLocaleMiddlewareArgs = {
|
||||
config: I18nConfig
|
||||
/** Cookie name for the locale choice. Defaults to LOCALE_COOKIE_NAME. */
|
||||
cookieName?: string
|
||||
/**
|
||||
* Consent category that gates *persisting* the locale cookie. The locale is
|
||||
* always detected (routing works regardless), but the choice is only written
|
||||
@@ -32,11 +33,9 @@ type CreateLocaleMiddlewareArgs = {
|
||||
* 'functional'. Pass 'necessary' to always persist (treat locale as strictly
|
||||
* necessary), which restores the pre-consent behaviour.
|
||||
*/
|
||||
consentCategory?: 'functional' | 'necessary'
|
||||
consentCategory?: 'necessary' | 'functional'
|
||||
/** 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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,14 +75,14 @@ function firstSegment(pathname: string): string {
|
||||
*/
|
||||
export function createLocaleMiddleware({
|
||||
config,
|
||||
cookieName = LOCALE_COOKIE_NAME,
|
||||
consentCategory = 'functional',
|
||||
consentCookieName = CONSENT_COOKIE,
|
||||
cookieName = LOCALE_COOKIE_NAME,
|
||||
}: 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}
|
||||
if (consentCategory === 'necessary') return true
|
||||
const consent = parseConsent(request.cookies.get(consentCookieName)?.value)
|
||||
return consent?.[consentCategory] === true
|
||||
}
|
||||
@@ -91,6 +90,15 @@ export function createLocaleMiddleware({
|
||||
return function localeMiddleware(request: MiddlewareRequest): LocaleMiddlewareResult {
|
||||
const { pathname } = request.nextUrl
|
||||
|
||||
// Single-locale sites have no /pl, /en prefix and no negotiation — one
|
||||
// language, no redirect. The middleware becomes a pass-through: paths stay
|
||||
// as-is (/o-nas), nothing to detect or persist. (Projects that are truly
|
||||
// single-locale usually don't even mount this middleware, but guarding here
|
||||
// makes it safe if they do.)
|
||||
if (config.locales.length === 1) {
|
||||
return { type: 'next' }
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -108,9 +116,9 @@ export function createLocaleMiddleware({
|
||||
|
||||
// Resolve the locale to use
|
||||
const locale = negotiateLocale({
|
||||
cookieLocale: request.cookies.get(cookieName)?.value ?? null,
|
||||
acceptLanguage: request.headers.get('accept-language'),
|
||||
config,
|
||||
cookieLocale: request.cookies.get(cookieName)?.value ?? null,
|
||||
})
|
||||
|
||||
// Redirect to the locale-prefixed path, preserving the rest
|
||||
|
||||
@@ -71,6 +71,13 @@ export function buildLocalizedPath({
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Single-locale sites have no /pl, /en prefix — the language segment is
|
||||
// dropped entirely (path is /o-nas, not /pl/o-nas). Detected automatically:
|
||||
// one configured locale means one language, so no prefix is needed. The
|
||||
// project's folder structure matches (app/[[...slug]] without [locale]).
|
||||
const singleLocale = config.locales.length === 1
|
||||
const localeSegment = singleLocale ? '' : `/${locale}`
|
||||
|
||||
if (prefix) {
|
||||
const segment = prefix[locale]
|
||||
// No archive slug in this locale means the entry is unreachable there —
|
||||
@@ -79,14 +86,15 @@ export function buildLocalizedPath({
|
||||
if (!segment) {
|
||||
return undefined
|
||||
}
|
||||
return `/${locale}/${segment}/${slug}`
|
||||
return `${localeSegment}/${segment}/${slug}`
|
||||
}
|
||||
|
||||
if (slug === homeSlug) {
|
||||
return `/${locale}`
|
||||
// Home collapses to the root: '/' for single-locale, '/pl' otherwise.
|
||||
return localeSegment || '/'
|
||||
}
|
||||
|
||||
return `/${locale}/${slug}`
|
||||
return `${localeSegment}/${slug}`
|
||||
}
|
||||
|
||||
type SwitchLocaleArgs = {
|
||||
|
||||
Reference in New Issue
Block a user