@fluenti/next 0.4.0-rc.3 → 0.4.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/middleware.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.cjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * @module @fluenti/next/middleware\n *\n * Built-in i18n middleware for Next.js App Router.\n *\n * Uses `x-fluenti-locale` request header to pass locale from middleware to\n * server components — avoids `Set-Cookie` on every request (CDN-friendly).\n *\n * @example Minimal\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({ NextResponse })\n * ```\n *\n * @example With pathnames + alternateLinks + domains\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({\n * NextResponse,\n * rewriteDefaultLocale: true,\n * alternateLinks: true,\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * },\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * ],\n * })\n * ```\n */\n\nimport {\n locales as _configLocales,\n sourceLocale as _configSourceLocale,\n cookieName as _configCookieName,\n} from '@fluenti/next/i18n-config'\n\n/** Header name used to pass resolved locale from middleware to RSC */\nexport const LOCALE_HEADER = 'x-fluenti-locale'\n\nexport interface CookieOptions {\n /** Cookie domain (e.g. '.example.com' for cross-subdomain) */\n domain?: string\n /** Secure flag (default: auto-detect from request URL) */\n secure?: boolean\n /** SameSite attribute (default: 'lax') */\n sameSite?: 'lax' | 'strict' | 'none'\n /** Max age in seconds (default: 31536000 = 1 year) */\n maxAge?: number\n /** Cookie path (default: '/') */\n path?: string\n}\n\nexport interface DomainConfig {\n /** Domain hostname (e.g. 'fr.example.com') */\n domain: string\n /** Default locale for this domain */\n defaultLocale: string\n /** Optional subset of locales available on this domain */\n locales?: string[]\n}\n\nexport interface AlternateLinkEntry {\n href: string\n hreflang: string\n}\n\nexport interface I18nMiddlewareConfig {\n /** Available locales. If omitted, reads from `fluenti.config.ts`. */\n locales?: string[]\n /** Source/default locale. If omitted, reads from `fluenti.config.ts`. */\n sourceLocale?: string\n /** Cookie name for reading user preference (default: 'locale') */\n cookieName?: string\n /**\n * Locale prefix strategy:\n * - `'always'`: all locales get a URL prefix\n * - `'as-needed'`: source locale has no prefix, others do\n * - `'never'`: no locale prefix in URLs; locale determined by detection chain\n *\n * Default: `'as-needed'`\n */\n localePrefix?: 'always' | 'as-needed' | 'never'\n /**\n * When true, bare paths are internally rewritten to include the locale prefix.\n * Required when using `app/[locale]/` directory structure.\n *\n * Default: `false`\n */\n rewriteDefaultLocale?: boolean\n /**\n * When true, detected locale is persisted in a cookie.\n * Disabled by default to keep responses CDN-cacheable.\n */\n setCookie?: boolean\n /** Fine-grained cookie configuration for multi-domain / secure deployments. */\n cookieOptions?: CookieOptions\n /**\n * Set to false to disable automatic locale detection.\n * Bare paths will always use `sourceLocale` instead of detecting from cookie / Accept-Language.\n *\n * Default: `true`\n */\n localeDetection?: boolean\n /**\n * Custom locale detection function. Called when no locale is present in the URL path.\n * Return a locale string to override the default chain, or `undefined` to fall through.\n */\n detectLocale?: (req: NextRequest) => string | undefined\n /**\n * Domain-based locale routing. Each domain maps to a default locale.\n * Domain matching is checked before cookie/Accept-Language detection.\n *\n * @example\n * ```ts\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * { domain: 'example.co.jp', defaultLocale: 'ja' },\n * ]\n * ```\n */\n domains?: DomainConfig[]\n /**\n * Map internal paths to localized paths per locale.\n * Supports dynamic segments: `[param]` and `[...slug]`.\n *\n * @example\n * ```ts\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * }\n * ```\n */\n pathnames?: Record<string, Record<string, string>>\n /**\n * When true, adds `Link` response headers with `rel=\"alternate\"` hreflang\n * and `rel=\"canonical\"` for SEO.\n *\n * Default: `false`\n */\n alternateLinks?: boolean\n /**\n * Custom function to build alternate link entries. Overrides default `alternateLinks` behavior.\n * Return an array of `{ href, hreflang }` entries.\n */\n getAlternateLinks?: (context: {\n pathname: string\n locale: string\n locales: string[]\n origin: string\n basePath: string\n }) => AlternateLinkEntry[]\n /**\n * Called before the middleware returns a response.\n * Modify headers, cookies, or return a replacement response.\n */\n beforeResponse?: (context: {\n response: NextResponseInstance\n request: NextRequest\n locale: string\n type: 'redirect' | 'rewrite' | 'next'\n }) => NextResponseInstance | void | undefined\n}\n\n/** Minimal request shape required by the middleware. Pass the real NextRequest for full type access in detectLocale. */\ntype NextRequest = {\n nextUrl: { pathname: string; search: string; basePath?: string }\n url: string\n cookies: { get(name: string): { value: string } | undefined }\n headers: Headers\n [key: string]: unknown // Allow accessing .geo, .ip, etc. without type assertion\n}\n\ntype NextResponseStatic<R extends NextResponseInstance = NextResponseInstance> = {\n redirect(url: URL): R\n rewrite(url: URL, init?: Record<string, unknown>): R\n next(init?: Record<string, unknown>): R\n}\n\ntype NextResponseInstance = {\n headers: { set(name: string, value: string): void }\n}\n\n/**\n * Create an i18n middleware function for Next.js.\n */\nexport function createI18nMiddleware<R extends NextResponseInstance = NextResponseInstance>(\n config: I18nMiddlewareConfig & { NextResponse: NextResponseStatic<R> },\n) {\n const { NextResponse } = config\n const resolvedLocales: string[] = config.locales ?? _configLocales\n const resolvedSourceLocale: string = config.sourceLocale ?? _configSourceLocale\n const cookieName = config.cookieName ?? _configCookieName\n const localePrefix = config.localePrefix ?? 'as-needed'\n const rewriteDefaultLocale = config.rewriteDefaultLocale ?? false\n const setCookieEnabled = config.setCookie ?? false\n const cookieOpts: CookieOptions = config.cookieOptions ?? {}\n const localeDetectionEnabled = config.localeDetection ?? true\n const alternateLinksEnabled = config.alternateLinks ?? false\n const pathnamesMap = config.pathnames\n const domainsConfig = config.domains\n const beforeResponse = config.beforeResponse\n\n function finalizeResponse(\n response: R,\n request: NextRequest,\n locale: string,\n type: 'redirect' | 'rewrite' | 'next',\n pathLocale: string | null,\n ): R {\n response.headers.set(LOCALE_HEADER, locale)\n if (setCookieEnabled) {\n maybeSetCookie(response, request, locale, cookieName, pathLocale, cookieOpts)\n }\n if (alternateLinksEnabled || config.getAlternateLinks) {\n const linkHeader = config.getAlternateLinks\n ? buildCustomAlternateLinks(config.getAlternateLinks, request, locale, resolvedLocales)\n : buildAlternateLinks(request, resolvedLocales, resolvedSourceLocale, localePrefix, request.nextUrl.basePath ?? '', pathnamesMap)\n response.headers.set('Link', linkHeader)\n }\n if (beforeResponse) {\n const replacement = beforeResponse({ response, request, locale, type })\n if (replacement) return replacement as R\n }\n return response\n }\n\n return function i18nMiddleware(request: NextRequest): R {\n const locales = resolvedLocales\n const sourceLocale = resolvedSourceLocale\n const { pathname, search } = request.nextUrl\n const basePath = request.nextUrl.basePath ?? ''\n\n // Extract locale from URL path ('never' mode skips this)\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n const pathLocale = localePrefix === 'never' ? null : findLocale(firstSegment, locales)\n\n // Determine the active locale\n let locale: string\n\n if (pathLocale) {\n locale = pathLocale\n } else if (!localeDetectionEnabled) {\n locale = sourceLocale\n } else {\n // Detection chain: custom → domains → cookie → Accept-Language → default\n const custom = config.detectLocale?.(request)\n if (custom !== undefined && findLocale(custom, locales) !== null) {\n locale = findLocale(custom, locales)!\n } else if (domainsConfig) {\n const domainLocale = detectFromDomain(request, domainsConfig, locales)\n locale = domainLocale ?? detectLocale(request, locales, sourceLocale, cookieName)\n } else {\n locale = detectLocale(request, locales, sourceLocale, cookieName)\n }\n }\n\n // Build request headers\n const requestHeaders = new Headers(request.headers)\n requestHeaders.set(LOCALE_HEADER, locale)\n\n // ── 'never' mode ──────────────────────────────────────────────────────\n if (localePrefix === 'never') {\n if (rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${locale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', null,\n )\n }\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', null,\n )\n }\n\n // ── Pathnames mapping ─────────────────────────────────────────────────\n if (pathnamesMap && pathLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n const internalPath = resolveInternalPath(pathWithoutLocale, locale, pathnamesMap)\n\n if (internalPath) {\n const rewriteUrl = new URL(`${basePath}/${locale}${internalPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n const localizedPath = resolveLocalizedPath(pathWithoutLocale, locale, pathnamesMap)\n if (localizedPath && localizedPath !== pathWithoutLocale) {\n const redirectUrl = new URL(`${basePath}/${locale}${localizedPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n }\n\n // ── Case 1: No locale in path → redirect ──────────────────────────────\n if (!pathLocale && (localePrefix === 'always' || locale !== sourceLocale)) {\n // If pathnames configured, redirect to localized path\n let targetPath = pathname\n if (pathnamesMap) {\n const localized = resolveLocalizedPath(pathname, locale, pathnamesMap)\n if (localized) targetPath = localized\n }\n const redirectUrl = new URL(`${basePath}/${locale}${targetPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n\n // ── Case 2: as-needed + source locale + rewriteDefaultLocale ──────────\n if (!pathLocale && locale === sourceLocale && rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${sourceLocale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 3: as-needed, source locale has explicit prefix → strip ──────\n if (localePrefix === 'as-needed' && pathLocale === sourceLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n if (rewriteDefaultLocale) {\n const redirectUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n const rewriteUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 4/5: pass through ────────────────────────────────────────────\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', pathLocale,\n )\n }\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction findLocale(candidate: string, locales: string[]): string | null {\n if (!candidate) return null\n const lower = candidate.toLowerCase()\n return locales.find(l => l.toLowerCase() === lower) ?? null\n}\n\nfunction normalizeSlashes(path: string): string {\n return path.replace(/\\/+/g, '/') || '/'\n}\n\nfunction maybeSetCookie(\n response: NextResponseInstance,\n request: NextRequest,\n locale: string,\n cookieName: string,\n pathLocale: string | null,\n opts: CookieOptions,\n): void {\n if (pathLocale) return\n if (request.cookies.get(cookieName)?.value === locale) return\n\n const parts = [`${cookieName}=${encodeURIComponent(locale)}`]\n parts.push(`path=${opts.path ?? '/'}`)\n parts.push(`max-age=${opts.maxAge ?? 31536000}`)\n parts.push(`samesite=${opts.sameSite ?? 'lax'}`)\n if (opts.domain) parts.push(`domain=${opts.domain}`)\n if (opts.secure ?? request.url.startsWith('https')) parts.push('secure')\n response.headers.set('set-cookie', parts.join(';'))\n}\n\nfunction detectFromDomain(\n request: NextRequest,\n domains: DomainConfig[],\n locales: string[],\n): string | null {\n const host = request.headers.get('host')?.split(':')[0] ?? ''\n for (const d of domains) {\n if (host === d.domain || host.endsWith('.' + d.domain)) {\n const found = findLocale(d.defaultLocale, d.locales ?? locales)\n if (found) return found\n }\n }\n return null\n}\n\nfunction detectLocale(\n request: NextRequest,\n locales: string[],\n defaultLocale: string,\n cookieName: string,\n): string {\n const cookieLocale = request.cookies.get(cookieName)?.value\n if (cookieLocale) {\n const found = findLocale(cookieLocale, locales)\n if (found) return found\n }\n\n const acceptLang = request.headers.get('accept-language')\n if (acceptLang) {\n for (const part of acceptLang.split(',')) {\n const lang = part.split(';')[0]!.trim()\n const exact = findLocale(lang, locales)\n if (exact) return exact\n const prefix = lang.split('-')[0]!.toLowerCase()\n const match = locales.find(l => {\n const ll = l.toLowerCase()\n return ll === prefix || ll.startsWith(prefix + '-')\n })\n if (match) return match\n }\n }\n\n return defaultLocale\n}\n\nfunction stripLocalePrefix(pathname: string, locales: string[]): string {\n const segments = pathname.split('/')\n const first = segments[1] ?? ''\n if (findLocale(first, locales)) {\n return normalizeSlashes('/' + segments.slice(2).join('/'))\n }\n return pathname\n}\n\n// Path resolution utilities imported from shared routing module\n// (bundled by tsup, safe for Edge Runtime)\nimport { resolveInternalPath, resolveLocalizedPath } from './routing'\n\nfunction buildAlternateLinks(\n request: NextRequest,\n locales: string[],\n sourceLocale: string,\n localePrefix: 'always' | 'as-needed' | 'never',\n basePath: string,\n pathnames?: Record<string, Record<string, string>>,\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n\n const links = locales.map(loc => {\n let localePath: string\n if (localePrefix === 'never') {\n localePath = cleanPath\n } else if (localePrefix === 'as-needed' && loc === sourceLocale) {\n localePath = cleanPath\n } else {\n localePath = `/${loc}${cleanPath}`\n }\n if (pathnames) {\n const mapped = resolveLocalizedPath(cleanPath, loc, pathnames)\n if (mapped) {\n localePath = localePrefix === 'never' || (localePrefix === 'as-needed' && loc === sourceLocale)\n ? mapped\n : `/${loc}${mapped}`\n }\n }\n return `<${origin}${basePath}${localePath}>; rel=\"alternate\"; hreflang=\"${loc}\"`\n })\n\n // x-default\n const defaultPath = localePrefix === 'always' ? `/${sourceLocale}${cleanPath}` : cleanPath\n links.push(`<${origin}${basePath}${defaultPath}>; rel=\"alternate\"; hreflang=\"x-default\"`)\n\n return links.join(', ')\n}\n\nfunction buildCustomAlternateLinks(\n getAlternateLinks: NonNullable<I18nMiddlewareConfig['getAlternateLinks']>,\n request: NextRequest,\n locale: string,\n locales: string[],\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n const basePath = request.nextUrl.basePath ?? ''\n const entries = getAlternateLinks({ pathname: cleanPath, locale, locales, origin, basePath })\n return entries.map(e => `<${e.href}>; rel=\"alternate\"; hreflang=\"${e.hreflang}\"`).join(', ')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"middleware.cjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * @module @fluenti/next/middleware\n *\n * Built-in i18n middleware for Next.js App Router.\n *\n * Uses `x-fluenti-locale` request header to pass locale from middleware to\n * server components — avoids `Set-Cookie` on every request (CDN-friendly).\n *\n * @example Minimal\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({ NextResponse })\n * ```\n *\n * @example With pathnames + alternateLinks + domains\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({\n * NextResponse,\n * rewriteDefaultLocale: true,\n * alternateLinks: true,\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * },\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * ],\n * })\n * ```\n */\n\nimport {\n locales as _configLocales,\n sourceLocale as _configSourceLocale,\n cookieName as _configCookieName,\n} from '@fluenti/next/i18n-config'\n\n/** Header name used to pass resolved locale from middleware to RSC */\nexport const LOCALE_HEADER = 'x-fluenti-locale'\n\nexport interface CookieOptions {\n /** Cookie domain (e.g. '.example.com' for cross-subdomain) */\n domain?: string\n /** Secure flag (default: auto-detect from request URL) */\n secure?: boolean\n /** SameSite attribute (default: 'lax') */\n sameSite?: 'lax' | 'strict' | 'none'\n /** Max age in seconds (default: 31536000 = 1 year) */\n maxAge?: number\n /** Cookie path (default: '/') */\n path?: string\n}\n\nexport interface DomainConfig {\n /** Domain hostname (e.g. 'fr.example.com') */\n domain: string\n /** Default locale for this domain */\n defaultLocale: string\n /** Optional subset of locales available on this domain */\n locales?: string[]\n}\n\nexport interface AlternateLinkEntry {\n href: string\n hreflang: string\n}\n\nexport interface I18nMiddlewareConfig {\n /** Available locales. If omitted, reads from `fluenti.config.ts`. */\n locales?: string[]\n /** Source/default locale. If omitted, reads from `fluenti.config.ts`. */\n sourceLocale?: string\n /** Cookie name for reading user preference (default: 'locale') */\n cookieName?: string\n /**\n * Locale prefix strategy:\n * - `'always'`: all locales get a URL prefix\n * - `'as-needed'`: source locale has no prefix, others do\n * - `'never'`: no locale prefix in URLs; locale determined by detection chain\n *\n * Default: `'as-needed'`\n */\n localePrefix?: 'always' | 'as-needed' | 'never'\n /**\n * When true, bare paths are internally rewritten to include the locale prefix.\n * Required when using `app/[locale]/` directory structure.\n *\n * Default: `false`\n */\n rewriteDefaultLocale?: boolean\n /**\n * When true, detected locale is persisted in a cookie.\n * Disabled by default to keep responses CDN-cacheable.\n */\n setCookie?: boolean\n /** Fine-grained cookie configuration for multi-domain / secure deployments. */\n cookieOptions?: CookieOptions\n /**\n * Set to false to disable automatic locale detection.\n * Bare paths will always use `sourceLocale` instead of detecting from cookie / Accept-Language.\n *\n * Default: `true`\n */\n localeDetection?: boolean\n /**\n * Custom locale detection function. Called when no locale is present in the URL path.\n * Return a locale string to override the default chain, or `undefined` to fall through.\n */\n detectLocale?: (req: NextRequest) => string | undefined\n /**\n * Domain-based locale routing. Each domain maps to a default locale.\n * Domain matching is checked before cookie/Accept-Language detection.\n *\n * @example\n * ```ts\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * { domain: 'example.co.jp', defaultLocale: 'ja' },\n * ]\n * ```\n */\n domains?: DomainConfig[]\n /**\n * Map internal paths to localized paths per locale.\n * Supports dynamic segments: `[param]` and `[...slug]`.\n *\n * @example\n * ```ts\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * }\n * ```\n */\n pathnames?: Record<string, Record<string, string>>\n /**\n * When true, adds `Link` response headers with `rel=\"alternate\"` hreflang\n * and `rel=\"canonical\"` for SEO.\n *\n * Default: `false`\n */\n alternateLinks?: boolean\n /**\n * Custom function to build alternate link entries. Overrides default `alternateLinks` behavior.\n * Return an array of `{ href, hreflang }` entries.\n */\n getAlternateLinks?: (context: {\n pathname: string\n locale: string\n locales: string[]\n origin: string\n basePath: string\n }) => AlternateLinkEntry[]\n /**\n * Called before the middleware returns a response.\n * Modify headers, cookies, or return a replacement response.\n */\n beforeResponse?: (context: {\n response: NextResponseInstance\n request: NextRequest\n locale: string\n type: 'redirect' | 'rewrite' | 'next'\n }) => NextResponseInstance | void | undefined\n}\n\n/** Minimal request shape required by the middleware. Compatible with Next.js NextRequest. */\ntype NextRequest = {\n nextUrl: { pathname: string; search: string; basePath?: string }\n url: string\n cookies: { get(name: string): { value: string } | undefined }\n headers: Headers\n}\n\ntype NextResponseStatic<R extends NextResponseInstance = NextResponseInstance> = {\n redirect(url: URL): R\n rewrite(url: URL, init?: Record<string, unknown>): R\n next(init?: Record<string, unknown>): R\n}\n\ntype NextResponseInstance = {\n headers: { set(name: string, value: string): void }\n}\n\n/**\n * Create an i18n middleware function for Next.js.\n */\nexport function createI18nMiddleware<R extends NextResponseInstance = NextResponseInstance>(\n config: I18nMiddlewareConfig & { NextResponse: NextResponseStatic<R> },\n) {\n const { NextResponse } = config\n const resolvedLocales: string[] = config.locales ?? _configLocales\n const resolvedSourceLocale: string = config.sourceLocale ?? _configSourceLocale\n const cookieName = config.cookieName ?? _configCookieName\n const localePrefix = config.localePrefix ?? 'as-needed'\n const rewriteDefaultLocale = config.rewriteDefaultLocale ?? false\n const setCookieEnabled = config.setCookie ?? false\n const cookieOpts: CookieOptions = config.cookieOptions ?? {}\n const localeDetectionEnabled = config.localeDetection ?? true\n const alternateLinksEnabled = config.alternateLinks ?? false\n const pathnamesMap = config.pathnames\n const domainsConfig = config.domains\n const beforeResponse = config.beforeResponse\n\n function finalizeResponse(\n response: R,\n request: NextRequest,\n locale: string,\n type: 'redirect' | 'rewrite' | 'next',\n pathLocale: string | null,\n ): R {\n response.headers.set(LOCALE_HEADER, locale)\n if (setCookieEnabled) {\n maybeSetCookie(response, request, locale, cookieName, pathLocale, cookieOpts)\n }\n if (alternateLinksEnabled || config.getAlternateLinks) {\n const linkHeader = config.getAlternateLinks\n ? buildCustomAlternateLinks(config.getAlternateLinks, request, locale, resolvedLocales)\n : buildAlternateLinks(request, resolvedLocales, resolvedSourceLocale, localePrefix, request.nextUrl.basePath ?? '', pathnamesMap)\n response.headers.set('Link', linkHeader)\n }\n if (beforeResponse) {\n const replacement = beforeResponse({ response, request, locale, type })\n if (replacement) return replacement as R\n }\n return response\n }\n\n return function i18nMiddleware(request: NextRequest): R {\n const locales = resolvedLocales\n const sourceLocale = resolvedSourceLocale\n const { pathname, search } = request.nextUrl\n const basePath = request.nextUrl.basePath ?? ''\n\n // Extract locale from URL path ('never' mode skips this)\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n const pathLocale = localePrefix === 'never' ? null : findLocale(firstSegment, locales)\n\n // Determine the active locale\n let locale: string\n\n if (pathLocale) {\n locale = pathLocale\n } else if (!localeDetectionEnabled) {\n locale = sourceLocale\n } else {\n // Detection chain: custom → domains → cookie → Accept-Language → default\n const custom = config.detectLocale?.(request)\n if (custom !== undefined && findLocale(custom, locales) !== null) {\n locale = findLocale(custom, locales)!\n } else if (domainsConfig) {\n const domainLocale = detectFromDomain(request, domainsConfig, locales)\n locale = domainLocale ?? detectLocale(request, locales, sourceLocale, cookieName)\n } else {\n locale = detectLocale(request, locales, sourceLocale, cookieName)\n }\n }\n\n // Build request headers\n const requestHeaders = new Headers(request.headers)\n requestHeaders.set(LOCALE_HEADER, locale)\n\n // ── 'never' mode ──────────────────────────────────────────────────────\n if (localePrefix === 'never') {\n if (rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${locale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', null,\n )\n }\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', null,\n )\n }\n\n // ── Pathnames mapping ─────────────────────────────────────────────────\n if (pathnamesMap && pathLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n const internalPath = resolveInternalPath(pathWithoutLocale, locale, pathnamesMap)\n\n if (internalPath) {\n const rewriteUrl = new URL(`${basePath}/${locale}${internalPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n const localizedPath = resolveLocalizedPath(pathWithoutLocale, locale, pathnamesMap)\n if (localizedPath && localizedPath !== pathWithoutLocale) {\n const redirectUrl = new URL(`${basePath}/${locale}${localizedPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n }\n\n // ── Case 1: No locale in path → redirect ──────────────────────────────\n if (!pathLocale && (localePrefix === 'always' || locale !== sourceLocale)) {\n // If pathnames configured, redirect to localized path\n let targetPath = pathname\n if (pathnamesMap) {\n const localized = resolveLocalizedPath(pathname, locale, pathnamesMap)\n if (localized) targetPath = localized\n }\n const redirectUrl = new URL(`${basePath}/${locale}${targetPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n\n // ── Case 2: as-needed + source locale + rewriteDefaultLocale ──────────\n if (!pathLocale && locale === sourceLocale && rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${sourceLocale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 3: as-needed, source locale has explicit prefix → strip ──────\n if (localePrefix === 'as-needed' && pathLocale === sourceLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n if (rewriteDefaultLocale) {\n const redirectUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n const rewriteUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 4/5: pass through ────────────────────────────────────────────\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', pathLocale,\n )\n }\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction findLocale(candidate: string, locales: string[]): string | null {\n if (!candidate) return null\n const lower = candidate.toLowerCase()\n return locales.find(l => l.toLowerCase() === lower) ?? null\n}\n\nfunction normalizeSlashes(path: string): string {\n return path.replace(/\\/+/g, '/') || '/'\n}\n\nfunction maybeSetCookie(\n response: NextResponseInstance,\n request: NextRequest,\n locale: string,\n cookieName: string,\n pathLocale: string | null,\n opts: CookieOptions,\n): void {\n if (pathLocale) return\n if (request.cookies.get(cookieName)?.value === locale) return\n\n const parts = [`${cookieName}=${encodeURIComponent(locale)}`]\n parts.push(`path=${opts.path ?? '/'}`)\n parts.push(`max-age=${opts.maxAge ?? 31536000}`)\n parts.push(`samesite=${opts.sameSite ?? 'lax'}`)\n if (opts.domain) parts.push(`domain=${opts.domain}`)\n if (opts.secure ?? request.url.startsWith('https')) parts.push('secure')\n response.headers.set('set-cookie', parts.join(';'))\n}\n\nfunction detectFromDomain(\n request: NextRequest,\n domains: DomainConfig[],\n locales: string[],\n): string | null {\n const host = request.headers.get('host')?.split(':')[0] ?? ''\n for (const d of domains) {\n if (host === d.domain || host.endsWith('.' + d.domain)) {\n const found = findLocale(d.defaultLocale, d.locales ?? locales)\n if (found) return found\n }\n }\n return null\n}\n\nfunction detectLocale(\n request: NextRequest,\n locales: string[],\n defaultLocale: string,\n cookieName: string,\n): string {\n const cookieLocale = request.cookies.get(cookieName)?.value\n if (cookieLocale) {\n const found = findLocale(cookieLocale, locales)\n if (found) return found\n }\n\n const acceptLang = request.headers.get('accept-language')\n if (acceptLang) {\n for (const part of acceptLang.split(',')) {\n const lang = part.split(';')[0]!.trim()\n const exact = findLocale(lang, locales)\n if (exact) return exact\n const prefix = lang.split('-')[0]!.toLowerCase()\n const match = locales.find(l => {\n const ll = l.toLowerCase()\n return ll === prefix || ll.startsWith(prefix + '-')\n })\n if (match) return match\n }\n }\n\n return defaultLocale\n}\n\nfunction stripLocalePrefix(pathname: string, locales: string[]): string {\n const segments = pathname.split('/')\n const first = segments[1] ?? ''\n if (findLocale(first, locales)) {\n return normalizeSlashes('/' + segments.slice(2).join('/'))\n }\n return pathname\n}\n\n// Path resolution utilities imported from shared routing module\n// (bundled by tsup, safe for Edge Runtime)\nimport { resolveInternalPath, resolveLocalizedPath } from './routing'\n\nfunction buildAlternateLinks(\n request: NextRequest,\n locales: string[],\n sourceLocale: string,\n localePrefix: 'always' | 'as-needed' | 'never',\n basePath: string,\n pathnames?: Record<string, Record<string, string>>,\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n\n const links = locales.map(loc => {\n let localePath: string\n if (localePrefix === 'never') {\n localePath = cleanPath\n } else if (localePrefix === 'as-needed' && loc === sourceLocale) {\n localePath = cleanPath\n } else {\n localePath = `/${loc}${cleanPath}`\n }\n if (pathnames) {\n const mapped = resolveLocalizedPath(cleanPath, loc, pathnames)\n if (mapped) {\n localePath = localePrefix === 'never' || (localePrefix === 'as-needed' && loc === sourceLocale)\n ? mapped\n : `/${loc}${mapped}`\n }\n }\n return `<${origin}${basePath}${localePath}>; rel=\"alternate\"; hreflang=\"${loc}\"`\n })\n\n // x-default\n const defaultPath = localePrefix === 'always' ? `/${sourceLocale}${cleanPath}` : cleanPath\n links.push(`<${origin}${basePath}${defaultPath}>; rel=\"alternate\"; hreflang=\"x-default\"`)\n\n return links.join(', ')\n}\n\nfunction buildCustomAlternateLinks(\n getAlternateLinks: NonNullable<I18nMiddlewareConfig['getAlternateLinks']>,\n request: NextRequest,\n locale: string,\n locales: string[],\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n const basePath = request.nextUrl.basePath ?? ''\n const entries = getAlternateLinks({ pathname: cleanPath, locale, locales, origin, basePath })\n return entries.map(e => `<${e.href}>; rel=\"alternate\"; hreflang=\"${e.hreflang}\"`).join(', ')\n}\n"],"mappings":"6JA0b0D,CA/Y1D,IAAa,EAAgB,mBAoJ7B,SAAgB,EACd,EACA,CACA,GAAM,CAAE,gBAAiB,EACnB,EAA4B,EAAO,SAAW,EAAA,QAC9C,EAA+B,EAAO,cAAgB,EAAA,aACtD,EAAa,EAAO,YAAc,EAAA,WAClC,EAAe,EAAO,cAAgB,YACtC,EAAuB,EAAO,sBAAwB,GACtD,EAAmB,EAAO,WAAa,GACvC,EAA4B,EAAO,eAAiB,EAAE,CACtD,EAAyB,EAAO,iBAAmB,GACnD,EAAwB,EAAO,gBAAkB,GACjD,EAAe,EAAO,UACtB,EAAgB,EAAO,QACvB,EAAiB,EAAO,eAE9B,SAAS,EACP,EACA,EACA,EACA,EACA,EACG,CAKH,GAJA,EAAS,QAAQ,IAAI,EAAe,EAAO,CACvC,GACF,EAAe,EAAU,EAAS,EAAQ,EAAY,EAAY,EAAW,CAE3E,GAAyB,EAAO,kBAAmB,CACrD,IAAM,EAAa,EAAO,kBACtB,EAA0B,EAAO,kBAAmB,EAAS,EAAQ,EAAgB,CACrF,EAAoB,EAAS,EAAiB,EAAsB,EAAc,EAAQ,QAAQ,UAAY,GAAI,EAAa,CACnI,EAAS,QAAQ,IAAI,OAAQ,EAAW,CAE1C,GAAI,EAAgB,CAClB,IAAM,EAAc,EAAe,CAAE,WAAU,UAAS,SAAQ,OAAM,CAAC,CACvE,GAAI,EAAa,OAAO,EAE1B,OAAO,EAGT,OAAO,SAAwB,EAAyB,CACtD,IAAM,EAAU,EACV,EAAe,EACf,CAAE,WAAU,UAAW,EAAQ,QAC/B,EAAW,EAAQ,QAAQ,UAAY,GAGvC,EAAW,EAAS,MAAM,IAAI,CAC9B,EAAe,EAAS,IAAM,GAC9B,EAAa,IAAiB,QAAU,KAAO,EAAW,EAAc,EAAQ,CAGlF,EAEJ,GAAI,EACF,EAAS,UACA,CAAC,EACV,EAAS,MACJ,CAEL,IAAM,EAAS,EAAO,eAAe,EAAQ,CAC7C,AAME,EANE,IAAW,IAAA,IAAa,EAAW,EAAQ,EAAQ,GAAK,KACjD,EAAW,EAAQ,EAAQ,CAC3B,EACY,EAAiB,EAAS,EAAe,EAAQ,EAC7C,EAAa,EAAS,EAAS,EAAc,EAAW,CAExE,EAAa,EAAS,EAAS,EAAc,EAAW,CAKrE,IAAM,EAAiB,IAAI,QAAQ,EAAQ,QAAQ,CAInD,GAHA,EAAe,IAAI,EAAe,EAAO,CAGrC,IAAiB,QAAS,CAC5B,GAAI,EAAsB,CACxB,IAAM,EAAa,IAAI,IAAI,GAAG,EAAS,GAAG,IAAS,IAAW,IAAU,EAAQ,IAAI,CACpF,OAAO,EACL,EAAa,QAAQ,EAAY,CAAE,QAAS,CAAE,QAAS,EAAgB,CAAE,CAAC,CAC1E,EAAS,EAAQ,UAAW,KAC7B,CAEH,OAAO,EACL,EAAa,KAAK,CAAE,QAAS,CAAE,QAAS,EAAgB,CAAE,CAAC,CAC3D,EAAS,EAAQ,OAAQ,KAC1B,CAIH,GAAI,GAAgB,EAAY,CAC9B,IAAM,EAAoB,EAAiB,IAAM,EAAS,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC,CACvE,EAAe,EAAA,EAAoB,EAAmB,EAAQ,EAAa,CAEjF,GAAI,EAAc,CAChB,IAAM,EAAa,IAAI,IAAI,GAAG,EAAS,GAAG,IAAS,IAAe,IAAU,EAAQ,IAAI,CACxF,OAAO,EACL,EAAa,QAAQ,EAAY,CAAE,QAAS,CAAE,QAAS,EAAgB,CAAE,CAAC,CAC1E,EAAS,EAAQ,UAAW,EAC7B,CAGH,IAAM,EAAgB,EAAA,EAAqB,EAAmB,EAAQ,EAAa,CACnF,GAAI,GAAiB,IAAkB,EAAmB,CACxD,IAAM,EAAc,IAAI,IAAI,GAAG,EAAS,GAAG,IAAS,IAAgB,IAAU,EAAQ,IAAI,CAC1F,OAAO,EACL,EAAa,SAAS,EAAY,CAClC,EAAS,EAAQ,WAAY,EAC9B,EAKL,GAAI,CAAC,IAAe,IAAiB,UAAY,IAAW,GAAe,CAEzE,IAAI,EAAa,EACjB,GAAI,EAAc,CAChB,IAAM,EAAY,EAAA,EAAqB,EAAU,EAAQ,EAAa,CAClE,IAAW,EAAa,GAE9B,IAAM,EAAc,IAAI,IAAI,GAAG,EAAS,GAAG,IAAS,IAAa,IAAU,EAAQ,IAAI,CACvF,OAAO,EACL,EAAa,SAAS,EAAY,CAClC,EAAS,EAAQ,WAAY,EAC9B,CAIH,GAAI,CAAC,GAAc,IAAW,GAAgB,EAAsB,CAClE,IAAM,EAAa,IAAI,IAAI,GAAG,EAAS,GAAG,IAAe,IAAW,IAAU,EAAQ,IAAI,CAC1F,OAAO,EACL,EAAa,QAAQ,EAAY,CAAE,QAAS,CAAE,QAAS,EAAgB,CAAE,CAAC,CAC1E,EAAS,EAAQ,UAAW,EAC7B,CAIH,GAAI,IAAiB,aAAe,IAAe,EAAc,CAC/D,IAAM,EAAoB,EAAiB,IAAM,EAAS,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC,CAC7E,GAAI,EAAsB,CACxB,IAAM,EAAc,IAAI,IAAI,GAAG,IAAW,IAAoB,IAAU,EAAQ,IAAI,CACpF,OAAO,EACL,EAAa,SAAS,EAAY,CAClC,EAAS,EAAQ,WAAY,EAC9B,CAEH,IAAM,EAAa,IAAI,IAAI,GAAG,IAAW,IAAoB,IAAU,EAAQ,IAAI,CACnF,OAAO,EACL,EAAa,QAAQ,EAAY,CAAE,QAAS,CAAE,QAAS,EAAgB,CAAE,CAAC,CAC1E,EAAS,EAAQ,UAAW,EAC7B,CAIH,OAAO,EACL,EAAa,KAAK,CAAE,QAAS,CAAE,QAAS,EAAgB,CAAE,CAAC,CAC3D,EAAS,EAAQ,OAAQ,EAC1B,EAML,SAAS,EAAW,EAAmB,EAAkC,CACvE,GAAI,CAAC,EAAW,OAAO,KACvB,IAAM,EAAQ,EAAU,aAAa,CACrC,OAAO,EAAQ,KAAK,GAAK,EAAE,aAAa,GAAK,EAAM,EAAI,KAGzD,SAAS,EAAiB,EAAsB,CAC9C,OAAO,EAAK,QAAQ,OAAQ,IAAI,EAAI,IAGtC,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACM,CAEN,GADI,GACA,EAAQ,QAAQ,IAAI,EAAW,EAAE,QAAU,EAAQ,OAEvD,IAAM,EAAQ,CAAC,GAAG,EAAW,GAAG,mBAAmB,EAAO,GAAG,CAC7D,EAAM,KAAK,QAAQ,EAAK,MAAQ,MAAM,CACtC,EAAM,KAAK,WAAW,EAAK,QAAU,UAAW,CAChD,EAAM,KAAK,YAAY,EAAK,UAAY,QAAQ,CAC5C,EAAK,QAAQ,EAAM,KAAK,UAAU,EAAK,SAAS,EAChD,EAAK,QAAU,EAAQ,IAAI,WAAW,QAAQ,GAAE,EAAM,KAAK,SAAS,CACxE,EAAS,QAAQ,IAAI,aAAc,EAAM,KAAK,IAAI,CAAC,CAGrD,SAAS,EACP,EACA,EACA,EACe,CACf,IAAM,EAAO,EAAQ,QAAQ,IAAI,OAAO,EAAE,MAAM,IAAI,CAAC,IAAM,GAC3D,IAAK,IAAM,KAAK,EACd,GAAI,IAAS,EAAE,QAAU,EAAK,SAAS,IAAM,EAAE,OAAO,CAAE,CACtD,IAAM,EAAQ,EAAW,EAAE,cAAe,EAAE,SAAW,EAAQ,CAC/D,GAAI,EAAO,OAAO,EAGtB,OAAO,KAGT,SAAS,EACP,EACA,EACA,EACA,EACQ,CACR,IAAM,EAAe,EAAQ,QAAQ,IAAI,EAAW,EAAE,MACtD,GAAI,EAAc,CAChB,IAAM,EAAQ,EAAW,EAAc,EAAQ,CAC/C,GAAI,EAAO,OAAO,EAGpB,IAAM,EAAa,EAAQ,QAAQ,IAAI,kBAAkB,CACzD,GAAI,EACF,IAAK,IAAM,KAAQ,EAAW,MAAM,IAAI,CAAE,CACxC,IAAM,EAAO,EAAK,MAAM,IAAI,CAAC,GAAI,MAAM,CACjC,EAAQ,EAAW,EAAM,EAAQ,CACvC,GAAI,EAAO,OAAO,EAClB,IAAM,EAAS,EAAK,MAAM,IAAI,CAAC,GAAI,aAAa,CAC1C,EAAQ,EAAQ,KAAK,GAAK,CAC9B,IAAM,EAAK,EAAE,aAAa,CAC1B,OAAO,IAAO,GAAU,EAAG,WAAW,EAAS,IAAI,EACnD,CACF,GAAI,EAAO,OAAO,EAItB,OAAO,EAGT,SAAS,EAAkB,EAAkB,EAA2B,CACtE,IAAM,EAAW,EAAS,MAAM,IAAI,CAKpC,OAHI,EADU,EAAS,IAAM,GACP,EAAQ,CACrB,EAAiB,IAAM,EAAS,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC,CAErD,EAOT,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACQ,CACR,IAAM,EAAS,IAAI,IAAI,EAAQ,IAAI,CAAC,OAC9B,EAAY,EAAkB,EAAQ,QAAQ,SAAU,EAAQ,CAEhE,EAAQ,EAAQ,IAAI,GAAO,CAC/B,IAAI,EAQJ,GAPA,AAKE,EALE,IAAiB,SAEV,IAAiB,aAAe,IAAQ,EADpC,EAIA,IAAI,IAAM,IAErB,EAAW,CACb,IAAM,EAAS,EAAA,EAAqB,EAAW,EAAK,EAAU,CAC1D,IACF,EAAa,IAAiB,SAAY,IAAiB,aAAe,IAAQ,EAC9E,EACA,IAAI,IAAM,KAGlB,MAAO,IAAI,IAAS,IAAW,EAAW,gCAAgC,EAAI,IAC9E,CAGI,EAAc,IAAiB,SAAW,IAAI,IAAe,IAAc,EAGjF,OAFA,EAAM,KAAK,IAAI,IAAS,IAAW,EAAY,0CAA0C,CAElF,EAAM,KAAK,KAAK,CAGzB,SAAS,EACP,EACA,EACA,EACA,EACQ,CACR,IAAM,EAAS,IAAI,IAAI,EAAQ,IAAI,CAAC,OAIpC,OADgB,EAAkB,CAAE,SAFlB,EAAkB,EAAQ,QAAQ,SAAU,EAAQ,CAEb,SAAQ,UAAS,SAAQ,SADjE,EAAQ,QAAQ,UAAY,GAC+C,CAAC,CAC9E,IAAI,GAAK,IAAI,EAAE,KAAK,gCAAgC,EAAE,SAAS,GAAG,CAAC,KAAK,KAAK"}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -156,7 +156,7 @@ export interface I18nMiddlewareConfig {
|
|
|
156
156
|
type: 'redirect' | 'rewrite' | 'next';
|
|
157
157
|
}) => NextResponseInstance | void | undefined;
|
|
158
158
|
}
|
|
159
|
-
/** Minimal request shape required by the middleware.
|
|
159
|
+
/** Minimal request shape required by the middleware. Compatible with Next.js NextRequest. */
|
|
160
160
|
type NextRequest = {
|
|
161
161
|
nextUrl: {
|
|
162
162
|
pathname: string;
|
|
@@ -170,7 +170,6 @@ type NextRequest = {
|
|
|
170
170
|
} | undefined;
|
|
171
171
|
};
|
|
172
172
|
headers: Headers;
|
|
173
|
-
[key: string]: unknown;
|
|
174
173
|
};
|
|
175
174
|
type NextResponseStatic<R extends NextResponseInstance = NextResponseInstance> = {
|
|
176
175
|
redirect(url: URL): R;
|
package/dist/middleware.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAQH,sEAAsE;AACtE,eAAO,MAAM,aAAa,qBAAqB,CAAA;AAE/C,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAA;IACpC,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,qCAAqC;IACrC,aAAa,EAAE,MAAM,CAAA;IACrB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAA;IAC/C;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,MAAM,GAAG,SAAS,CAAA;IACvD;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;IACxB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClD;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC5B,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,EAAE,CAAA;QACjB,MAAM,EAAE,MAAM,CAAA;QACd,QAAQ,EAAE,MAAM,CAAA;KACjB,KAAK,kBAAkB,EAAE,CAAA;IAC1B;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE;QACzB,QAAQ,EAAE,oBAAoB,CAAA;QAC9B,OAAO,EAAE,WAAW,CAAA;QACpB,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAAA;KACtC,KAAK,oBAAoB,GAAG,IAAI,GAAG,SAAS,CAAA;CAC9C;AAED,
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * @module @fluenti/next/middleware\n *\n * Built-in i18n middleware for Next.js App Router.\n *\n * Uses `x-fluenti-locale` request header to pass locale from middleware to\n * server components — avoids `Set-Cookie` on every request (CDN-friendly).\n *\n * @example Minimal\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({ NextResponse })\n * ```\n *\n * @example With pathnames + alternateLinks + domains\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({\n * NextResponse,\n * rewriteDefaultLocale: true,\n * alternateLinks: true,\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * },\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * ],\n * })\n * ```\n */\n\nimport {\n locales as _configLocales,\n sourceLocale as _configSourceLocale,\n cookieName as _configCookieName,\n} from '@fluenti/next/i18n-config'\n\n/** Header name used to pass resolved locale from middleware to RSC */\nexport const LOCALE_HEADER = 'x-fluenti-locale'\n\nexport interface CookieOptions {\n /** Cookie domain (e.g. '.example.com' for cross-subdomain) */\n domain?: string\n /** Secure flag (default: auto-detect from request URL) */\n secure?: boolean\n /** SameSite attribute (default: 'lax') */\n sameSite?: 'lax' | 'strict' | 'none'\n /** Max age in seconds (default: 31536000 = 1 year) */\n maxAge?: number\n /** Cookie path (default: '/') */\n path?: string\n}\n\nexport interface DomainConfig {\n /** Domain hostname (e.g. 'fr.example.com') */\n domain: string\n /** Default locale for this domain */\n defaultLocale: string\n /** Optional subset of locales available on this domain */\n locales?: string[]\n}\n\nexport interface AlternateLinkEntry {\n href: string\n hreflang: string\n}\n\nexport interface I18nMiddlewareConfig {\n /** Available locales. If omitted, reads from `fluenti.config.ts`. */\n locales?: string[]\n /** Source/default locale. If omitted, reads from `fluenti.config.ts`. */\n sourceLocale?: string\n /** Cookie name for reading user preference (default: 'locale') */\n cookieName?: string\n /**\n * Locale prefix strategy:\n * - `'always'`: all locales get a URL prefix\n * - `'as-needed'`: source locale has no prefix, others do\n * - `'never'`: no locale prefix in URLs; locale determined by detection chain\n *\n * Default: `'as-needed'`\n */\n localePrefix?: 'always' | 'as-needed' | 'never'\n /**\n * When true, bare paths are internally rewritten to include the locale prefix.\n * Required when using `app/[locale]/` directory structure.\n *\n * Default: `false`\n */\n rewriteDefaultLocale?: boolean\n /**\n * When true, detected locale is persisted in a cookie.\n * Disabled by default to keep responses CDN-cacheable.\n */\n setCookie?: boolean\n /** Fine-grained cookie configuration for multi-domain / secure deployments. */\n cookieOptions?: CookieOptions\n /**\n * Set to false to disable automatic locale detection.\n * Bare paths will always use `sourceLocale` instead of detecting from cookie / Accept-Language.\n *\n * Default: `true`\n */\n localeDetection?: boolean\n /**\n * Custom locale detection function. Called when no locale is present in the URL path.\n * Return a locale string to override the default chain, or `undefined` to fall through.\n */\n detectLocale?: (req: NextRequest) => string | undefined\n /**\n * Domain-based locale routing. Each domain maps to a default locale.\n * Domain matching is checked before cookie/Accept-Language detection.\n *\n * @example\n * ```ts\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * { domain: 'example.co.jp', defaultLocale: 'ja' },\n * ]\n * ```\n */\n domains?: DomainConfig[]\n /**\n * Map internal paths to localized paths per locale.\n * Supports dynamic segments: `[param]` and `[...slug]`.\n *\n * @example\n * ```ts\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * }\n * ```\n */\n pathnames?: Record<string, Record<string, string>>\n /**\n * When true, adds `Link` response headers with `rel=\"alternate\"` hreflang\n * and `rel=\"canonical\"` for SEO.\n *\n * Default: `false`\n */\n alternateLinks?: boolean\n /**\n * Custom function to build alternate link entries. Overrides default `alternateLinks` behavior.\n * Return an array of `{ href, hreflang }` entries.\n */\n getAlternateLinks?: (context: {\n pathname: string\n locale: string\n locales: string[]\n origin: string\n basePath: string\n }) => AlternateLinkEntry[]\n /**\n * Called before the middleware returns a response.\n * Modify headers, cookies, or return a replacement response.\n */\n beforeResponse?: (context: {\n response: NextResponseInstance\n request: NextRequest\n locale: string\n type: 'redirect' | 'rewrite' | 'next'\n }) => NextResponseInstance | void | undefined\n}\n\n/** Minimal request shape required by the middleware. Pass the real NextRequest for full type access in detectLocale. */\ntype NextRequest = {\n nextUrl: { pathname: string; search: string; basePath?: string }\n url: string\n cookies: { get(name: string): { value: string } | undefined }\n headers: Headers\n [key: string]: unknown // Allow accessing .geo, .ip, etc. without type assertion\n}\n\ntype NextResponseStatic<R extends NextResponseInstance = NextResponseInstance> = {\n redirect(url: URL): R\n rewrite(url: URL, init?: Record<string, unknown>): R\n next(init?: Record<string, unknown>): R\n}\n\ntype NextResponseInstance = {\n headers: { set(name: string, value: string): void }\n}\n\n/**\n * Create an i18n middleware function for Next.js.\n */\nexport function createI18nMiddleware<R extends NextResponseInstance = NextResponseInstance>(\n config: I18nMiddlewareConfig & { NextResponse: NextResponseStatic<R> },\n) {\n const { NextResponse } = config\n const resolvedLocales: string[] = config.locales ?? _configLocales\n const resolvedSourceLocale: string = config.sourceLocale ?? _configSourceLocale\n const cookieName = config.cookieName ?? _configCookieName\n const localePrefix = config.localePrefix ?? 'as-needed'\n const rewriteDefaultLocale = config.rewriteDefaultLocale ?? false\n const setCookieEnabled = config.setCookie ?? false\n const cookieOpts: CookieOptions = config.cookieOptions ?? {}\n const localeDetectionEnabled = config.localeDetection ?? true\n const alternateLinksEnabled = config.alternateLinks ?? false\n const pathnamesMap = config.pathnames\n const domainsConfig = config.domains\n const beforeResponse = config.beforeResponse\n\n function finalizeResponse(\n response: R,\n request: NextRequest,\n locale: string,\n type: 'redirect' | 'rewrite' | 'next',\n pathLocale: string | null,\n ): R {\n response.headers.set(LOCALE_HEADER, locale)\n if (setCookieEnabled) {\n maybeSetCookie(response, request, locale, cookieName, pathLocale, cookieOpts)\n }\n if (alternateLinksEnabled || config.getAlternateLinks) {\n const linkHeader = config.getAlternateLinks\n ? buildCustomAlternateLinks(config.getAlternateLinks, request, locale, resolvedLocales)\n : buildAlternateLinks(request, resolvedLocales, resolvedSourceLocale, localePrefix, request.nextUrl.basePath ?? '', pathnamesMap)\n response.headers.set('Link', linkHeader)\n }\n if (beforeResponse) {\n const replacement = beforeResponse({ response, request, locale, type })\n if (replacement) return replacement as R\n }\n return response\n }\n\n return function i18nMiddleware(request: NextRequest): R {\n const locales = resolvedLocales\n const sourceLocale = resolvedSourceLocale\n const { pathname, search } = request.nextUrl\n const basePath = request.nextUrl.basePath ?? ''\n\n // Extract locale from URL path ('never' mode skips this)\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n const pathLocale = localePrefix === 'never' ? null : findLocale(firstSegment, locales)\n\n // Determine the active locale\n let locale: string\n\n if (pathLocale) {\n locale = pathLocale\n } else if (!localeDetectionEnabled) {\n locale = sourceLocale\n } else {\n // Detection chain: custom → domains → cookie → Accept-Language → default\n const custom = config.detectLocale?.(request)\n if (custom !== undefined && findLocale(custom, locales) !== null) {\n locale = findLocale(custom, locales)!\n } else if (domainsConfig) {\n const domainLocale = detectFromDomain(request, domainsConfig, locales)\n locale = domainLocale ?? detectLocale(request, locales, sourceLocale, cookieName)\n } else {\n locale = detectLocale(request, locales, sourceLocale, cookieName)\n }\n }\n\n // Build request headers\n const requestHeaders = new Headers(request.headers)\n requestHeaders.set(LOCALE_HEADER, locale)\n\n // ── 'never' mode ──────────────────────────────────────────────────────\n if (localePrefix === 'never') {\n if (rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${locale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', null,\n )\n }\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', null,\n )\n }\n\n // ── Pathnames mapping ─────────────────────────────────────────────────\n if (pathnamesMap && pathLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n const internalPath = resolveInternalPath(pathWithoutLocale, locale, pathnamesMap)\n\n if (internalPath) {\n const rewriteUrl = new URL(`${basePath}/${locale}${internalPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n const localizedPath = resolveLocalizedPath(pathWithoutLocale, locale, pathnamesMap)\n if (localizedPath && localizedPath !== pathWithoutLocale) {\n const redirectUrl = new URL(`${basePath}/${locale}${localizedPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n }\n\n // ── Case 1: No locale in path → redirect ──────────────────────────────\n if (!pathLocale && (localePrefix === 'always' || locale !== sourceLocale)) {\n // If pathnames configured, redirect to localized path\n let targetPath = pathname\n if (pathnamesMap) {\n const localized = resolveLocalizedPath(pathname, locale, pathnamesMap)\n if (localized) targetPath = localized\n }\n const redirectUrl = new URL(`${basePath}/${locale}${targetPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n\n // ── Case 2: as-needed + source locale + rewriteDefaultLocale ──────────\n if (!pathLocale && locale === sourceLocale && rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${sourceLocale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 3: as-needed, source locale has explicit prefix → strip ──────\n if (localePrefix === 'as-needed' && pathLocale === sourceLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n if (rewriteDefaultLocale) {\n const redirectUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n const rewriteUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 4/5: pass through ────────────────────────────────────────────\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', pathLocale,\n )\n }\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction findLocale(candidate: string, locales: string[]): string | null {\n if (!candidate) return null\n const lower = candidate.toLowerCase()\n return locales.find(l => l.toLowerCase() === lower) ?? null\n}\n\nfunction normalizeSlashes(path: string): string {\n return path.replace(/\\/+/g, '/') || '/'\n}\n\nfunction maybeSetCookie(\n response: NextResponseInstance,\n request: NextRequest,\n locale: string,\n cookieName: string,\n pathLocale: string | null,\n opts: CookieOptions,\n): void {\n if (pathLocale) return\n if (request.cookies.get(cookieName)?.value === locale) return\n\n const parts = [`${cookieName}=${encodeURIComponent(locale)}`]\n parts.push(`path=${opts.path ?? '/'}`)\n parts.push(`max-age=${opts.maxAge ?? 31536000}`)\n parts.push(`samesite=${opts.sameSite ?? 'lax'}`)\n if (opts.domain) parts.push(`domain=${opts.domain}`)\n if (opts.secure ?? request.url.startsWith('https')) parts.push('secure')\n response.headers.set('set-cookie', parts.join(';'))\n}\n\nfunction detectFromDomain(\n request: NextRequest,\n domains: DomainConfig[],\n locales: string[],\n): string | null {\n const host = request.headers.get('host')?.split(':')[0] ?? ''\n for (const d of domains) {\n if (host === d.domain || host.endsWith('.' + d.domain)) {\n const found = findLocale(d.defaultLocale, d.locales ?? locales)\n if (found) return found\n }\n }\n return null\n}\n\nfunction detectLocale(\n request: NextRequest,\n locales: string[],\n defaultLocale: string,\n cookieName: string,\n): string {\n const cookieLocale = request.cookies.get(cookieName)?.value\n if (cookieLocale) {\n const found = findLocale(cookieLocale, locales)\n if (found) return found\n }\n\n const acceptLang = request.headers.get('accept-language')\n if (acceptLang) {\n for (const part of acceptLang.split(',')) {\n const lang = part.split(';')[0]!.trim()\n const exact = findLocale(lang, locales)\n if (exact) return exact\n const prefix = lang.split('-')[0]!.toLowerCase()\n const match = locales.find(l => {\n const ll = l.toLowerCase()\n return ll === prefix || ll.startsWith(prefix + '-')\n })\n if (match) return match\n }\n }\n\n return defaultLocale\n}\n\nfunction stripLocalePrefix(pathname: string, locales: string[]): string {\n const segments = pathname.split('/')\n const first = segments[1] ?? ''\n if (findLocale(first, locales)) {\n return normalizeSlashes('/' + segments.slice(2).join('/'))\n }\n return pathname\n}\n\n// Path resolution utilities imported from shared routing module\n// (bundled by tsup, safe for Edge Runtime)\nimport { resolveInternalPath, resolveLocalizedPath } from './routing'\n\nfunction buildAlternateLinks(\n request: NextRequest,\n locales: string[],\n sourceLocale: string,\n localePrefix: 'always' | 'as-needed' | 'never',\n basePath: string,\n pathnames?: Record<string, Record<string, string>>,\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n\n const links = locales.map(loc => {\n let localePath: string\n if (localePrefix === 'never') {\n localePath = cleanPath\n } else if (localePrefix === 'as-needed' && loc === sourceLocale) {\n localePath = cleanPath\n } else {\n localePath = `/${loc}${cleanPath}`\n }\n if (pathnames) {\n const mapped = resolveLocalizedPath(cleanPath, loc, pathnames)\n if (mapped) {\n localePath = localePrefix === 'never' || (localePrefix === 'as-needed' && loc === sourceLocale)\n ? mapped\n : `/${loc}${mapped}`\n }\n }\n return `<${origin}${basePath}${localePath}>; rel=\"alternate\"; hreflang=\"${loc}\"`\n })\n\n // x-default\n const defaultPath = localePrefix === 'always' ? `/${sourceLocale}${cleanPath}` : cleanPath\n links.push(`<${origin}${basePath}${defaultPath}>; rel=\"alternate\"; hreflang=\"x-default\"`)\n\n return links.join(', ')\n}\n\nfunction buildCustomAlternateLinks(\n getAlternateLinks: NonNullable<I18nMiddlewareConfig['getAlternateLinks']>,\n request: NextRequest,\n locale: string,\n locales: string[],\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n const basePath = request.nextUrl.basePath ?? ''\n const entries = getAlternateLinks({ pathname: cleanPath, locale, locales, origin, basePath })\n return entries.map(e => `<${e.href}>; rel=\"alternate\"; hreflang=\"${e.hreflang}\"`).join(', ')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * @module @fluenti/next/middleware\n *\n * Built-in i18n middleware for Next.js App Router.\n *\n * Uses `x-fluenti-locale` request header to pass locale from middleware to\n * server components — avoids `Set-Cookie` on every request (CDN-friendly).\n *\n * @example Minimal\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({ NextResponse })\n * ```\n *\n * @example With pathnames + alternateLinks + domains\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({\n * NextResponse,\n * rewriteDefaultLocale: true,\n * alternateLinks: true,\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * },\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * ],\n * })\n * ```\n */\n\nimport {\n locales as _configLocales,\n sourceLocale as _configSourceLocale,\n cookieName as _configCookieName,\n} from '@fluenti/next/i18n-config'\n\n/** Header name used to pass resolved locale from middleware to RSC */\nexport const LOCALE_HEADER = 'x-fluenti-locale'\n\nexport interface CookieOptions {\n /** Cookie domain (e.g. '.example.com' for cross-subdomain) */\n domain?: string\n /** Secure flag (default: auto-detect from request URL) */\n secure?: boolean\n /** SameSite attribute (default: 'lax') */\n sameSite?: 'lax' | 'strict' | 'none'\n /** Max age in seconds (default: 31536000 = 1 year) */\n maxAge?: number\n /** Cookie path (default: '/') */\n path?: string\n}\n\nexport interface DomainConfig {\n /** Domain hostname (e.g. 'fr.example.com') */\n domain: string\n /** Default locale for this domain */\n defaultLocale: string\n /** Optional subset of locales available on this domain */\n locales?: string[]\n}\n\nexport interface AlternateLinkEntry {\n href: string\n hreflang: string\n}\n\nexport interface I18nMiddlewareConfig {\n /** Available locales. If omitted, reads from `fluenti.config.ts`. */\n locales?: string[]\n /** Source/default locale. If omitted, reads from `fluenti.config.ts`. */\n sourceLocale?: string\n /** Cookie name for reading user preference (default: 'locale') */\n cookieName?: string\n /**\n * Locale prefix strategy:\n * - `'always'`: all locales get a URL prefix\n * - `'as-needed'`: source locale has no prefix, others do\n * - `'never'`: no locale prefix in URLs; locale determined by detection chain\n *\n * Default: `'as-needed'`\n */\n localePrefix?: 'always' | 'as-needed' | 'never'\n /**\n * When true, bare paths are internally rewritten to include the locale prefix.\n * Required when using `app/[locale]/` directory structure.\n *\n * Default: `false`\n */\n rewriteDefaultLocale?: boolean\n /**\n * When true, detected locale is persisted in a cookie.\n * Disabled by default to keep responses CDN-cacheable.\n */\n setCookie?: boolean\n /** Fine-grained cookie configuration for multi-domain / secure deployments. */\n cookieOptions?: CookieOptions\n /**\n * Set to false to disable automatic locale detection.\n * Bare paths will always use `sourceLocale` instead of detecting from cookie / Accept-Language.\n *\n * Default: `true`\n */\n localeDetection?: boolean\n /**\n * Custom locale detection function. Called when no locale is present in the URL path.\n * Return a locale string to override the default chain, or `undefined` to fall through.\n */\n detectLocale?: (req: NextRequest) => string | undefined\n /**\n * Domain-based locale routing. Each domain maps to a default locale.\n * Domain matching is checked before cookie/Accept-Language detection.\n *\n * @example\n * ```ts\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * { domain: 'example.co.jp', defaultLocale: 'ja' },\n * ]\n * ```\n */\n domains?: DomainConfig[]\n /**\n * Map internal paths to localized paths per locale.\n * Supports dynamic segments: `[param]` and `[...slug]`.\n *\n * @example\n * ```ts\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * }\n * ```\n */\n pathnames?: Record<string, Record<string, string>>\n /**\n * When true, adds `Link` response headers with `rel=\"alternate\"` hreflang\n * and `rel=\"canonical\"` for SEO.\n *\n * Default: `false`\n */\n alternateLinks?: boolean\n /**\n * Custom function to build alternate link entries. Overrides default `alternateLinks` behavior.\n * Return an array of `{ href, hreflang }` entries.\n */\n getAlternateLinks?: (context: {\n pathname: string\n locale: string\n locales: string[]\n origin: string\n basePath: string\n }) => AlternateLinkEntry[]\n /**\n * Called before the middleware returns a response.\n * Modify headers, cookies, or return a replacement response.\n */\n beforeResponse?: (context: {\n response: NextResponseInstance\n request: NextRequest\n locale: string\n type: 'redirect' | 'rewrite' | 'next'\n }) => NextResponseInstance | void | undefined\n}\n\n/** Minimal request shape required by the middleware. Compatible with Next.js NextRequest. */\ntype NextRequest = {\n nextUrl: { pathname: string; search: string; basePath?: string }\n url: string\n cookies: { get(name: string): { value: string } | undefined }\n headers: Headers\n}\n\ntype NextResponseStatic<R extends NextResponseInstance = NextResponseInstance> = {\n redirect(url: URL): R\n rewrite(url: URL, init?: Record<string, unknown>): R\n next(init?: Record<string, unknown>): R\n}\n\ntype NextResponseInstance = {\n headers: { set(name: string, value: string): void }\n}\n\n/**\n * Create an i18n middleware function for Next.js.\n */\nexport function createI18nMiddleware<R extends NextResponseInstance = NextResponseInstance>(\n config: I18nMiddlewareConfig & { NextResponse: NextResponseStatic<R> },\n) {\n const { NextResponse } = config\n const resolvedLocales: string[] = config.locales ?? _configLocales\n const resolvedSourceLocale: string = config.sourceLocale ?? _configSourceLocale\n const cookieName = config.cookieName ?? _configCookieName\n const localePrefix = config.localePrefix ?? 'as-needed'\n const rewriteDefaultLocale = config.rewriteDefaultLocale ?? false\n const setCookieEnabled = config.setCookie ?? false\n const cookieOpts: CookieOptions = config.cookieOptions ?? {}\n const localeDetectionEnabled = config.localeDetection ?? true\n const alternateLinksEnabled = config.alternateLinks ?? false\n const pathnamesMap = config.pathnames\n const domainsConfig = config.domains\n const beforeResponse = config.beforeResponse\n\n function finalizeResponse(\n response: R,\n request: NextRequest,\n locale: string,\n type: 'redirect' | 'rewrite' | 'next',\n pathLocale: string | null,\n ): R {\n response.headers.set(LOCALE_HEADER, locale)\n if (setCookieEnabled) {\n maybeSetCookie(response, request, locale, cookieName, pathLocale, cookieOpts)\n }\n if (alternateLinksEnabled || config.getAlternateLinks) {\n const linkHeader = config.getAlternateLinks\n ? buildCustomAlternateLinks(config.getAlternateLinks, request, locale, resolvedLocales)\n : buildAlternateLinks(request, resolvedLocales, resolvedSourceLocale, localePrefix, request.nextUrl.basePath ?? '', pathnamesMap)\n response.headers.set('Link', linkHeader)\n }\n if (beforeResponse) {\n const replacement = beforeResponse({ response, request, locale, type })\n if (replacement) return replacement as R\n }\n return response\n }\n\n return function i18nMiddleware(request: NextRequest): R {\n const locales = resolvedLocales\n const sourceLocale = resolvedSourceLocale\n const { pathname, search } = request.nextUrl\n const basePath = request.nextUrl.basePath ?? ''\n\n // Extract locale from URL path ('never' mode skips this)\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n const pathLocale = localePrefix === 'never' ? null : findLocale(firstSegment, locales)\n\n // Determine the active locale\n let locale: string\n\n if (pathLocale) {\n locale = pathLocale\n } else if (!localeDetectionEnabled) {\n locale = sourceLocale\n } else {\n // Detection chain: custom → domains → cookie → Accept-Language → default\n const custom = config.detectLocale?.(request)\n if (custom !== undefined && findLocale(custom, locales) !== null) {\n locale = findLocale(custom, locales)!\n } else if (domainsConfig) {\n const domainLocale = detectFromDomain(request, domainsConfig, locales)\n locale = domainLocale ?? detectLocale(request, locales, sourceLocale, cookieName)\n } else {\n locale = detectLocale(request, locales, sourceLocale, cookieName)\n }\n }\n\n // Build request headers\n const requestHeaders = new Headers(request.headers)\n requestHeaders.set(LOCALE_HEADER, locale)\n\n // ── 'never' mode ──────────────────────────────────────────────────────\n if (localePrefix === 'never') {\n if (rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${locale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', null,\n )\n }\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', null,\n )\n }\n\n // ── Pathnames mapping ─────────────────────────────────────────────────\n if (pathnamesMap && pathLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n const internalPath = resolveInternalPath(pathWithoutLocale, locale, pathnamesMap)\n\n if (internalPath) {\n const rewriteUrl = new URL(`${basePath}/${locale}${internalPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n const localizedPath = resolveLocalizedPath(pathWithoutLocale, locale, pathnamesMap)\n if (localizedPath && localizedPath !== pathWithoutLocale) {\n const redirectUrl = new URL(`${basePath}/${locale}${localizedPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n }\n\n // ── Case 1: No locale in path → redirect ──────────────────────────────\n if (!pathLocale && (localePrefix === 'always' || locale !== sourceLocale)) {\n // If pathnames configured, redirect to localized path\n let targetPath = pathname\n if (pathnamesMap) {\n const localized = resolveLocalizedPath(pathname, locale, pathnamesMap)\n if (localized) targetPath = localized\n }\n const redirectUrl = new URL(`${basePath}/${locale}${targetPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n\n // ── Case 2: as-needed + source locale + rewriteDefaultLocale ──────────\n if (!pathLocale && locale === sourceLocale && rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${sourceLocale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 3: as-needed, source locale has explicit prefix → strip ──────\n if (localePrefix === 'as-needed' && pathLocale === sourceLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n if (rewriteDefaultLocale) {\n const redirectUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n const rewriteUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 4/5: pass through ────────────────────────────────────────────\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', pathLocale,\n )\n }\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction findLocale(candidate: string, locales: string[]): string | null {\n if (!candidate) return null\n const lower = candidate.toLowerCase()\n return locales.find(l => l.toLowerCase() === lower) ?? null\n}\n\nfunction normalizeSlashes(path: string): string {\n return path.replace(/\\/+/g, '/') || '/'\n}\n\nfunction maybeSetCookie(\n response: NextResponseInstance,\n request: NextRequest,\n locale: string,\n cookieName: string,\n pathLocale: string | null,\n opts: CookieOptions,\n): void {\n if (pathLocale) return\n if (request.cookies.get(cookieName)?.value === locale) return\n\n const parts = [`${cookieName}=${encodeURIComponent(locale)}`]\n parts.push(`path=${opts.path ?? '/'}`)\n parts.push(`max-age=${opts.maxAge ?? 31536000}`)\n parts.push(`samesite=${opts.sameSite ?? 'lax'}`)\n if (opts.domain) parts.push(`domain=${opts.domain}`)\n if (opts.secure ?? request.url.startsWith('https')) parts.push('secure')\n response.headers.set('set-cookie', parts.join(';'))\n}\n\nfunction detectFromDomain(\n request: NextRequest,\n domains: DomainConfig[],\n locales: string[],\n): string | null {\n const host = request.headers.get('host')?.split(':')[0] ?? ''\n for (const d of domains) {\n if (host === d.domain || host.endsWith('.' + d.domain)) {\n const found = findLocale(d.defaultLocale, d.locales ?? locales)\n if (found) return found\n }\n }\n return null\n}\n\nfunction detectLocale(\n request: NextRequest,\n locales: string[],\n defaultLocale: string,\n cookieName: string,\n): string {\n const cookieLocale = request.cookies.get(cookieName)?.value\n if (cookieLocale) {\n const found = findLocale(cookieLocale, locales)\n if (found) return found\n }\n\n const acceptLang = request.headers.get('accept-language')\n if (acceptLang) {\n for (const part of acceptLang.split(',')) {\n const lang = part.split(';')[0]!.trim()\n const exact = findLocale(lang, locales)\n if (exact) return exact\n const prefix = lang.split('-')[0]!.toLowerCase()\n const match = locales.find(l => {\n const ll = l.toLowerCase()\n return ll === prefix || ll.startsWith(prefix + '-')\n })\n if (match) return match\n }\n }\n\n return defaultLocale\n}\n\nfunction stripLocalePrefix(pathname: string, locales: string[]): string {\n const segments = pathname.split('/')\n const first = segments[1] ?? ''\n if (findLocale(first, locales)) {\n return normalizeSlashes('/' + segments.slice(2).join('/'))\n }\n return pathname\n}\n\n// Path resolution utilities imported from shared routing module\n// (bundled by tsup, safe for Edge Runtime)\nimport { resolveInternalPath, resolveLocalizedPath } from './routing'\n\nfunction buildAlternateLinks(\n request: NextRequest,\n locales: string[],\n sourceLocale: string,\n localePrefix: 'always' | 'as-needed' | 'never',\n basePath: string,\n pathnames?: Record<string, Record<string, string>>,\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n\n const links = locales.map(loc => {\n let localePath: string\n if (localePrefix === 'never') {\n localePath = cleanPath\n } else if (localePrefix === 'as-needed' && loc === sourceLocale) {\n localePath = cleanPath\n } else {\n localePath = `/${loc}${cleanPath}`\n }\n if (pathnames) {\n const mapped = resolveLocalizedPath(cleanPath, loc, pathnames)\n if (mapped) {\n localePath = localePrefix === 'never' || (localePrefix === 'as-needed' && loc === sourceLocale)\n ? mapped\n : `/${loc}${mapped}`\n }\n }\n return `<${origin}${basePath}${localePath}>; rel=\"alternate\"; hreflang=\"${loc}\"`\n })\n\n // x-default\n const defaultPath = localePrefix === 'always' ? `/${sourceLocale}${cleanPath}` : cleanPath\n links.push(`<${origin}${basePath}${defaultPath}>; rel=\"alternate\"; hreflang=\"x-default\"`)\n\n return links.join(', ')\n}\n\nfunction buildCustomAlternateLinks(\n getAlternateLinks: NonNullable<I18nMiddlewareConfig['getAlternateLinks']>,\n request: NextRequest,\n locale: string,\n locales: string[],\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n const basePath = request.nextUrl.basePath ?? ''\n const entries = getAlternateLinks({ pathname: cleanPath, locale, locales, origin, basePath })\n return entries.map(e => `<${e.href}>; rel=\"alternate\"; hreflang=\"${e.hreflang}\"`).join(', ')\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluenti/next",
|
|
3
|
-
"version": "0.4.0-rc.
|
|
3
|
+
"version": "0.4.0-rc.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Next.js plugin for Fluenti — withFluenti, I18nProvider, t`` transforms for App Router and Pages Router",
|
|
6
6
|
"homepage": "https://fluenti.dev",
|
|
@@ -107,8 +107,8 @@
|
|
|
107
107
|
"dependencies": {
|
|
108
108
|
"jiti": "^2",
|
|
109
109
|
"picomatch": "^4",
|
|
110
|
-
"@fluenti/core": "0.4.0-rc.
|
|
111
|
-
"@fluenti/react": "0.4.0-rc.
|
|
110
|
+
"@fluenti/core": "0.4.0-rc.4",
|
|
111
|
+
"@fluenti/react": "0.4.0-rc.4"
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
114
|
"@types/node": "^25",
|