next-intlayer 9.1.3 → 9.3.0

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.
@@ -4,12 +4,16 @@ let _intlayer_core_localization = require("@intlayer/core/localization");
4
4
  let _intlayer_core_utils = require("@intlayer/core/utils");
5
5
  let _intlayer_config_built = require("@intlayer/config/built");
6
6
  let _intlayer_config_defaultValues = require("@intlayer/config/defaultValues");
7
+ let _intlayer_config_logger = require("@intlayer/config/logger");
7
8
  let next_server = require("next/server");
8
9
 
9
10
  //#region src/proxy/intlayerProxy.ts
10
11
  const { locales, defaultLocale } = _intlayer_config_built.internationalization ?? {};
11
12
  const { basePath, mode, rewrite, domains, enableProxy } = _intlayer_config_built.routing ?? {};
12
- const isProxyEnabled = process.env.INTLAYER_ROUTING_ENABLE_PROXY !== "false" && (enableProxy ?? true);
13
+ const proxyMode = (0, _intlayer_core_localization.resolveProxyMode)(enableProxy);
14
+ const isDevServer = process.env.NODE_ENV === "development";
15
+ const canUseStorageLocale = (0, _intlayer_core_localization.isProxyStorageLocaleEnabled)(proxyMode, isDevServer);
16
+ if (isDevServer && proxyMode !== "disabled") (0, _intlayer_config_logger.getAppLogger)({ log: _intlayer_config_built.log })((0, _intlayer_core_localization.formatProxyEnabledMessage)(!canUseStorageLocale), { level: "info" });
13
17
  const effectiveMode = mode ?? _intlayer_config_defaultValues.ROUTING_MODE;
14
18
  const noPrefix = !(process.env.INTLAYER_ROUTING_MODE && process.env.INTLAYER_ROUTING_MODE !== "no-prefix") && effectiveMode === "no-prefix" || !(process.env.INTLAYER_ROUTING_MODE && process.env.INTLAYER_ROUTING_MODE !== "search-params") && effectiveMode === "search-params";
15
19
  const prefixDefault = !(process.env.INTLAYER_ROUTING_MODE && process.env.INTLAYER_ROUTING_MODE !== "prefix-all") && effectiveMode === "prefix-all";
@@ -70,7 +74,7 @@ const appendLocaleSearchIfNeeded = (search, locale) => {
70
74
  *
71
75
  */
72
76
  const intlayerProxy = (request, _event, _response) => {
73
- if (!isProxyEnabled) return next_server.NextResponse.next();
77
+ if (proxyMode === "disabled") return next_server.NextResponse.next();
74
78
  const pathname = request.nextUrl.pathname;
75
79
  const localLocale = getLocalLocale(request);
76
80
  if (noPrefix) return handleNoPrefix(request, localLocale, pathname);
@@ -97,13 +101,20 @@ const intlayerProxy = (request, _event, _response) => {
97
101
  /**
98
102
  * Retrieves the locale from the request cookies if available and valid.
99
103
  *
104
+ * Returns `undefined` when the stored locale is not allowed to drive locale
105
+ * resolution (auto mode on a dev server), which makes every caller fall through
106
+ * to `Accept-Language` detection and then the default locale.
107
+ *
100
108
  * @param request - The incoming Next.js request object.
101
109
  * @returns - The locale found in the cookies, or undefined if not found or invalid.
102
110
  */
103
- const getLocalLocale = (request) => (0, _intlayer_core_utils.getLocaleFromStorageServer)({
104
- getCookie: (name) => request.cookies.get(name)?.value ?? null,
105
- getHeader: (name) => request.headers.get(name) ?? null
106
- });
111
+ const getLocalLocale = (request) => {
112
+ if (!canUseStorageLocale) return void 0;
113
+ return (0, _intlayer_core_utils.getLocaleFromStorageServer)({
114
+ getCookie: (name) => request.cookies.get(name)?.value ?? null,
115
+ getHeader: (name) => request.headers.get(name) ?? null
116
+ });
117
+ };
107
118
  /**
108
119
  * Handles the case where URLs do not have locale prefixes.
109
120
  */
@@ -1 +1 @@
1
- {"version":3,"file":"intlayerProxy.cjs","names":["internationalization","routing","ROUTING_MODE","NextResponse","localeDetector"],"sources":["../../../src/proxy/intlayerProxy.ts"],"sourcesContent":["import { internationalization, routing } from '@intlayer/config/built';\nimport { ROUTING_MODE } from '@intlayer/config/defaultValues';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\nimport {\n getCanonicalPath,\n getDomainHostname,\n getDomainOrigin,\n getInternalPath,\n getLocaleFromDomain,\n getLocalizedPath,\n getRewriteRules,\n type LocaleDomainMap,\n} from '@intlayer/core/localization';\nimport {\n getLocaleFromStorageServer,\n setLocaleInStorageServer,\n} from '@intlayer/core/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport {\n type NextFetchEvent,\n type NextRequest,\n NextResponse,\n} from 'next/server';\nimport { localeDetector } from './localeDetector';\n\n/**\n * Controls whether locale detection occurs during Next.js prefetch requests\n * - true: Detect and apply locale during prefetch\n * - false: Use default locale during prefetch (recommended)\n *\n * This setting affects how Next.js handles locale prefetching:\n *\n * Example scenario:\n * - User's browser language is 'fr'\n * - Current page is /fr/about\n * - Link prefetches /about\n *\n * With `detectLocaleOnPrefetchNoPrefix:true`\n * - Prefetch detects 'fr' locale from browser\n * - Redirects prefetch to /fr/about\n *\n * With `detectLocaleOnPrefetchNoPrefix:false` (default)\n * - Prefetch uses default locale\n * - Redirects prefetch to /en/about (assuming 'en' is default)\n *\n * When to use true:\n * - Your app uses non-localized internal links (e.g. <a href=\"/about\">)\n * - You want consistent locale detection behavior between regular and prefetch requests\n *\n * When to use false (default):\n * - Your app uses locale-prefixed links (e.g. <a href=\"/fr/about\">)\n * - You want to optimize prefetching performance\n * - You want to avoid potential redirect loops\n */\nconst DEFAULT_DETECT_LOCALE_ON_PREFETCH_NO_PREFIX = false;\n\nconst { locales, defaultLocale } = internationalization ?? {};\nconst { basePath, mode, rewrite, domains, enableProxy } = routing ?? {};\n\n// Whether the locale-routing proxy is enabled (default: true). When disabled,\n// `intlayerProxy` becomes a pass-through so apps can handle routing themselves.\n// The env var is injected at build time so bundlers can tree-shake this branch.\nconst isProxyEnabled =\n process.env.INTLAYER_ROUTING_ENABLE_PROXY !== 'false' &&\n (enableProxy ?? true);\n\n// Note: cookie names are resolved inside LocaleStorage based on configuration\n\n// Derived flags from routing.mode\nconst effectiveMode = mode ?? ROUTING_MODE;\nconst noPrefix =\n (!(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'no-prefix'\n ) &&\n effectiveMode === 'no-prefix') ||\n (!(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'search-params'\n ) &&\n effectiveMode === 'search-params');\nconst prefixDefault =\n !(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'prefix-all'\n ) && effectiveMode === 'prefix-all';\n\nconst internalPrefix = !noPrefix;\n\nconst rewriteRules =\n process.env.INTLAYER_ROUTING_REWRITE_RULES !== 'false'\n ? getRewriteRules(rewrite, 'url')\n : undefined;\n\n/**\n * Detects if the request is a prefetch request from Next.js.\n *\n * Next.js prefetch requests are identified by:\n * - purpose: 'prefetch' (standard prefetch header)\n * - next-router-prefetch: '1' (Next.js app-router prefetch)\n *\n * Note: `next-url` and `x-nextjs-data` are intentionally NOT used here.\n * Both are also sent on real client-side navigations (RSC navigation\n * requests and pages-router data requests respectively), so treating them\n * as prefetch would force such navigations to the default locale instead\n * of the user's stored locale.\n *\n * During prefetch, we should ignore cookie-based locale detection\n * to prevent unwanted redirects when users are switching locales.\n *\n * @param request - The incoming Next.js request object.\n * @returns - True if the request is a prefetch request, false otherwise.\n */\nconst isPrefetchRequest = (request: NextRequest): boolean => {\n const purpose = request.headers.get('purpose');\n const nextRouterPrefetch = request.headers.get('next-router-prefetch');\n\n return purpose === 'prefetch' || nextRouterPrefetch === '1';\n};\n\n// Ensure locale is reflected in search params when routing mode is 'search-params'\nconst appendLocaleSearchIfNeeded = (\n search: string | undefined,\n locale: Locale\n): string | undefined => {\n if (\n (process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'search-params') ||\n effectiveMode !== 'search-params'\n )\n return search;\n const params = new URLSearchParams(search ?? '');\n params.set('locale', locale);\n return `?${params.toString()}`;\n};\n\n/**\n * Proxy that handles the internationalization layer\n *\n * Usage:\n *\n * ```ts\n * // ./src/proxy.ts\n *\n * export { intlayerProxy as proxy } from '@intlayer/next/proxy';\n *\n * // applies this proxy only to files in the app directory\n * export const config = {\n * matcher: '/((?!api|static|.*\\\\..*|_next).*)',\n * };\n * ```\n *\n * Main proxy function for handling internationalization.\n *\n * @param request - The incoming Next.js request object.\n * @param event - The Next.js fetch event (optional).\n * @param response - The Next.js response object (optional).\n * @returns - The response to be returned to the client.\n *\n */\nexport const intlayerProxy = (\n request: NextRequest,\n _event?: NextFetchEvent,\n _response?: NextResponse\n): NextResponse => {\n // When the proxy is disabled, pass the request through untouched.\n if (!isProxyEnabled) {\n return NextResponse.next();\n }\n\n const pathname = request.nextUrl.pathname;\n\n const localLocale = getLocalLocale(request);\n\n if (noPrefix) {\n return handleNoPrefix(request, localLocale, pathname);\n }\n\n const pathLocale = getPathLocale(pathname);\n\n // Domain routing: if the path locale is mapped to a different domain, redirect there.\n // e.g. intlayer.org/zh/about → https://intlayer.zh/about\n if (\n process.env.INTLAYER_ROUTING_DOMAINS !== 'false' &&\n pathLocale &&\n domains\n ) {\n const localeDomain = domains[pathLocale];\n\n if (localeDomain) {\n const domainHost = getDomainHostname(localeDomain);\n\n if (domainHost !== request.nextUrl.hostname) {\n const rawPath = pathname.slice(`/${pathLocale}`.length) || '/';\n const targetOrigin = getDomainOrigin(localeDomain);\n\n return NextResponse.redirect(\n new URL(`${rawPath}${request.nextUrl.search}`, targetOrigin)\n );\n }\n }\n }\n\n // Domain routing: if the current hostname is exclusively mapped to one locale,\n // treat it as that locale's domain — no URL prefix needed.\n // e.g. intlayer.zh/about → internally rewrite to /zh/about\n if (process.env.INTLAYER_ROUTING_DOMAINS !== 'false' && !pathLocale) {\n const domainLocale = getLocaleFromDomain(\n request.nextUrl.hostname,\n domains as LocaleDomainMap\n );\n\n if (domainLocale) {\n const canonicalPath = getCanonicalPath(\n pathname,\n domainLocale,\n rewriteRules\n );\n\n // Never emit a trailing slash (`/zh/`): Next.js trailing-slash\n // normalisation would redirect it back and forth with this proxy.\n const internalPath = getInternalPath(canonicalPath, domainLocale);\n\n return rewriteUrl(\n request,\n internalPath + (request.nextUrl.search ?? ''),\n domainLocale\n );\n }\n }\n\n return handlePrefix(request, localLocale, pathLocale, pathname);\n};\n\n/**\n * Retrieves the locale from the request cookies if available and valid.\n *\n * @param request - The incoming Next.js request object.\n * @returns - The locale found in the cookies, or undefined if not found or invalid.\n */\nconst getLocalLocale = (request: NextRequest): Locale | undefined =>\n getLocaleFromStorageServer({\n getCookie: (name: string) => request.cookies.get(name)?.value ?? null,\n getHeader: (name: string) => request.headers.get(name) ?? null,\n });\n\n/**\n * Handles the case where URLs do not have locale prefixes.\n */\nconst handleNoPrefix = (\n request: NextRequest,\n localLocale: Locale | undefined,\n pathname: string\n): NextResponse => {\n const pathLocale = getPathLocale(pathname);\n\n if (pathLocale) {\n const pathWithoutLocale = pathname.slice(`/${pathLocale}`.length) || '/';\n\n const canonicalPath = getCanonicalPath(\n pathWithoutLocale,\n pathLocale,\n rewriteRules\n );\n\n const search = appendLocaleSearchIfNeeded(\n request.nextUrl.search,\n pathLocale\n );\n\n const redirectPath = search\n ? `${canonicalPath}${search}`\n : `${canonicalPath}${request.nextUrl.search ?? ''}`;\n\n // Persist the explicitly-requested locale: stripping the prefix drops the\n // only locale signal from the URL, so without this the follow-up request\n // would fall back to cookie / Accept-Language detection and could resolve\n // a different locale.\n return redirectUrl(request, redirectPath, pathLocale);\n }\n\n if (\n !(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'search-params'\n ) &&\n effectiveMode === 'search-params'\n ) {\n const existingSearchParams = new URLSearchParams(request.nextUrl.search);\n const existingLocale = existingSearchParams.get('locale');\n\n const isExistingValid = locales?.includes(existingLocale as Locale);\n\n let locale = (localLocale ??\n (isExistingValid ? (existingLocale as Locale) : undefined) ??\n localeDetector?.(request) ??\n defaultLocale) as Locale;\n\n if (!locales?.includes(locale as Locale)) {\n locale = defaultLocale as Locale;\n }\n\n const canonicalPath = getCanonicalPath(\n pathname,\n locale as Locale,\n rewriteRules\n );\n\n if (existingLocale === locale) {\n const internalPath = internalPrefix\n ? getInternalPath(canonicalPath, locale as Locale)\n : canonicalPath;\n const rewritePath = `${internalPath}${request.nextUrl.search ?? ''}`;\n return rewriteUrl(request, rewritePath, locale as Locale);\n }\n\n const search = appendLocaleSearchIfNeeded(\n request.nextUrl.search,\n locale as Locale\n );\n // Use original pathname for redirect to preserve user's URL input, just adding params\n const redirectPath = search\n ? `${pathname}${search}`\n : `${pathname}${request.nextUrl.search ?? ''}`;\n\n return redirectUrl(request, redirectPath);\n }\n\n // effectiveMode === 'no-prefix'\n let locale = (localLocale ??\n localeDetector?.(request) ??\n defaultLocale) as Locale;\n\n if (!locales?.includes(locale as Locale)) {\n locale = defaultLocale as Locale;\n }\n\n const canonicalPath = getCanonicalPath(\n pathname,\n locale as Locale,\n rewriteRules\n );\n\n const internalPath = internalPrefix\n ? getInternalPath(canonicalPath, locale as Locale)\n : canonicalPath;\n const search = appendLocaleSearchIfNeeded(\n request.nextUrl.search,\n locale as Locale\n );\n const rewritePath = search\n ? `${internalPath}${search}`\n : `${internalPath}${request.nextUrl.search ?? ''}`;\n\n return rewriteUrl(request, rewritePath, locale as Locale);\n};\n\n/**\n * Checks whether a pathname starts with the given locale as a full path\n * segment (`/fr` or `/fr/...`). A bare `startsWith('/fr')` would also match\n * unrelated paths like `/friends`, causing wrong prefix stripping and\n * self-redirect loops.\n *\n * @param pathname - The pathname to test.\n * @param locale - The locale to look for as the first path segment.\n * @returns - True if the first path segment is exactly the locale.\n */\nconst hasLocaleSegmentPrefix = (pathname: string, locale: Locale): boolean =>\n pathname === `/${locale}` || pathname.startsWith(`/${locale}/`);\n\n/**\n * Extracts the locale from the URL pathname if present.\n *\n * @param pathname - The pathname from the request URL.\n * @returns - The locale found in the pathname, or undefined if not found.\n */\nconst getPathLocale = (pathname: string): Locale | undefined =>\n (locales as Locale[] | undefined)?.find((locale) =>\n hasLocaleSegmentPrefix(pathname, locale)\n );\n\n/**\n * Handles the case where URLs have locale prefixes.\n *\n * @param request - The incoming Next.js request object.\n * @param localLocale - The locale from the cookie.\n * @param pathLocale - The locale extracted from the pathname.\n * @param pathname - The pathname from the request URL.\n * @param basePathTrailingSlash - Indicates if the basePath ends with a slash.\n * @returns - The response to be returned to the client.\n */\nconst handlePrefix = (\n request: NextRequest,\n localLocale: Locale | undefined,\n pathLocale: Locale | undefined,\n pathname: string\n): NextResponse => {\n if (!pathLocale) {\n const isPrefetch = isPrefetchRequest(request);\n if (isPrefetch && !DEFAULT_DETECT_LOCALE_ON_PREFETCH_NO_PREFIX) {\n return handleMissingPathLocale(\n request,\n defaultLocale as Locale,\n pathname\n );\n }\n return handleMissingPathLocale(request, localLocale, pathname);\n }\n\n return handleExistingPathLocale(request, pathLocale, pathname);\n};\n\n/**\n * Handles requests where the locale is missing from the URL pathname.\n *\n * @param request - The incoming Next.js request object.\n * @param localLocale - The locale from the cookie.\n * @param pathname - The pathname from the request URL.\n * @param basePathTrailingSlash - Indicates if the basePath ends with a slash.\n * @returns - The response to be returned to the client.\n */\nconst handleMissingPathLocale = (\n request: NextRequest,\n localLocale: Locale | undefined,\n pathname: string\n): NextResponse => {\n let locale = (localLocale ??\n localeDetector?.(request) ??\n defaultLocale) as Locale;\n\n if (!locales?.includes(locale as Locale)) {\n locale = defaultLocale as Locale;\n }\n\n // Resolve to canonical path.\n // If user visits /a-propos (implied 'fr'), we resolve to /about\n const canonicalPath = getCanonicalPath(pathname, locale, rewriteRules);\n\n // Determine target localized path for redirection\n // /about + 'fr' -> /a-propos\n const targetLocalizedPathResult = getLocalizedPath(\n canonicalPath,\n locale,\n rewriteRules\n );\n const targetLocalizedPath =\n typeof targetLocalizedPathResult === 'string'\n ? targetLocalizedPathResult\n : targetLocalizedPathResult.path;\n\n const newPath = constructPath(\n locale,\n targetLocalizedPath,\n basePath as string,\n appendLocaleSearchIfNeeded(request.nextUrl.search, locale)\n );\n\n // Never emit a trailing slash (`/en/` for canonicalPath `/`): Next.js\n // trailing-slash normalisation would redirect it back and forth with this\n // proxy. `getInternalPath` collapses the root path to `/${locale}`.\n return prefixDefault || locale !== defaultLocale\n ? redirectUrl(request, newPath)\n : rewriteUrl(\n request,\n internalPrefix ? getInternalPath(canonicalPath, locale) : canonicalPath,\n locale\n ); // Rewrite must use Canonical\n};\n\n/**\n * Handles requests where the locale exists in the URL pathname.\n *\n * @param request - The incoming Next.js request object.\n * @param localLocale - The locale from the cookie.\n * @param pathLocale - The locale extracted from the pathname.\n * @param pathname - The pathname from the request URL.\n * @returns - The response to be returned to the client.\n */\nconst handleExistingPathLocale = (\n request: NextRequest,\n pathLocale: Locale,\n pathname: string\n): NextResponse => {\n const rawPath = pathname.slice(`/${pathLocale}`.length) || '/';\n\n // 1. Identify the Canonical Path (Internal Next.js path)\n // Ex: /a-propos (from URL) -> /about (Canonical)\n const canonicalPath = getCanonicalPath(rawPath, pathLocale, rewriteRules);\n\n // By skipping the forced localLocale check, we allow the explicit pathLocale\n // to take precedence, which correctly updates the header/cookie when navigating.\n\n // Rewrite Logic\n // We must rewrite to the Next.js internal structure: /[locale]/[canonicalPath]\n // Ex: Rewrite /fr/a-propos -> /fr/about\n\n // 2. Redirect to localized path if needed (Canonical -> Localized)\n // Ex: /fr/about -> /fr/a-propos\n const targetLocalizedPathResult = getLocalizedPath(\n canonicalPath,\n pathLocale,\n rewriteRules\n );\n const targetLocalizedPath =\n typeof targetLocalizedPathResult === 'string'\n ? targetLocalizedPathResult\n : targetLocalizedPathResult.path;\n const isRewritten =\n typeof targetLocalizedPathResult === 'string'\n ? false\n : targetLocalizedPathResult.isRewritten;\n\n if (isRewritten && targetLocalizedPath !== rawPath) {\n const newPath = constructPath(\n pathLocale,\n targetLocalizedPath,\n basePath as string,\n appendLocaleSearchIfNeeded(request.nextUrl.search, pathLocale)\n );\n return redirectUrl(request, newPath);\n }\n\n // Never emit a trailing slash (`/fr/` for the bare `/fr` URL): rewriting\n // `/fr` to `/fr/` makes Next.js issue a trailing-slash normalisation\n // redirect back to `/fr`, which this proxy rewrites again — an infinite\n // redirect loop. `getInternalPath` collapses the root path to `/${locale}`.\n const internalUrl = internalPrefix\n ? getInternalPath(canonicalPath, pathLocale)\n : canonicalPath;\n\n // Only handle redirect if we are strictly managing default locale prefixing\n // Fix: pass `canonicalPath` (the path *without* the locale prefix, e.g. /pricing)\n // instead of `pathname` (the full path including prefix, e.g. /en/pricing).\n // Previously this caused an infinite redirect loop in prefix-no-default mode\n // because handleDefaultLocaleRedirect built the redirect target from its third\n // argument, which reproduced the same URL on every response.\n if (!prefixDefault && pathLocale === defaultLocale) {\n return handleDefaultLocaleRedirect(request, pathLocale, canonicalPath);\n }\n\n const search = request.nextUrl.search;\n return rewriteUrl(request, internalUrl + (search ?? ''), pathLocale);\n};\n\n/**\n * Handles the scenario where the locale in the cookie does not match the locale in the URL pathname.\n *\n * @param request - The incoming Next.js request object.\n * @param pathname - The pathname from the request URL.\n * @param pathLocale - The locale extracted from the pathname.\n * @param localLocale - The locale from the cookie.\n * @param basePath - The base path of the application.\n * @returns - The new URL path with the correct locale.\n */\n// Function handleCookieLocaleMismatch was removed because the URL locale should take precedence over the stored locale.\n\n/**\n * The key fix for 404s without [locale] folders\n */\nconst handleDefaultLocaleRedirect = (\n request: NextRequest,\n pathLocale: Locale,\n canonicalPath: string // Internal path (e.g. /about)\n): NextResponse => {\n // Always called with !prefixDefault && pathLocale === defaultLocale (pre-validated by caller).\n // Redirect to strip the default-locale prefix from the URL.\n const targetLocalizedPathResult = getLocalizedPath(\n canonicalPath,\n pathLocale,\n rewriteRules\n );\n const targetLocalizedPath =\n typeof targetLocalizedPathResult === 'string'\n ? targetLocalizedPathResult\n : targetLocalizedPathResult.path;\n\n const basePathValue = (basePath as string) || '';\n const basePathTrailingSlash = basePathValue.endsWith('/');\n let finalPath = targetLocalizedPath;\n if (finalPath.startsWith('/')) finalPath = finalPath.slice(1);\n\n const fullPath = `${basePathValue}${basePathTrailingSlash ? '' : '/'}${finalPath}`;\n\n const searchWithLocale = appendLocaleSearchIfNeeded(\n request.nextUrl.search,\n pathLocale\n );\n\n // Persist the explicitly-requested default locale. Stripping the prefix\n // (e.g. /es → /) drops the only locale signal from the URL, so without this\n // the follow-up request to the canonical path would fall back to\n // Accept-Language detection and could resolve a different locale (e.g. /en).\n return redirectUrl(\n request,\n fullPath + (searchWithLocale ?? request.nextUrl.search ?? ''),\n pathLocale\n );\n};\n\n/**\n * Constructs a new path by combining the locale, path, basePath, and search parameters.\n *\n * @param locale - The locale to include in the path.\n * @param path - The original path from the request.\n * @param basePath - The base path of the application.\n * @param [search] - The query string from the request URL (optional).\n * @returns - The constructed new path.\n */\nconst constructPath = (\n locale: Locale,\n path: string,\n basePath: string,\n search?: string\n): string => {\n // Remove existing locale prefix from path if it was passed by mistake,\n // though we usually pass localized paths here now.\n const pathWithoutPrefix = hasLocaleSegmentPrefix(path, locale)\n ? path.slice(`/${locale}`.length) || '/'\n : path;\n\n if (\n (!(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'no-prefix'\n ) &&\n effectiveMode === 'no-prefix') ||\n (!(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'search-params'\n ) &&\n effectiveMode === 'search-params')\n ) {\n // `search` is either undefined or already has a leading '?' (from\n // appendLocaleSearchIfNeeded / request.nextUrl.search), so append as-is.\n return `${pathWithoutPrefix}${search ?? ''}`;\n }\n\n // Prefix handling\n const pathWithLocalePrefix = hasLocaleSegmentPrefix(path, locale)\n ? path\n : `${locale}${path.startsWith('/') ? '' : '/'}${path}`;\n\n const basePathValue = basePath || '';\n const basePathTrailingSlash = basePathValue.endsWith('/');\n const newPath = `${basePathValue}${basePathTrailingSlash ? '' : '/'}${pathWithLocalePrefix}`;\n\n // Clean double slashes\n const cleanPath = newPath.replace(/\\/+/g, '/');\n\n // Never emit a trailing slash (`/fr/` for the root path): the framework's\n // trailing-slash normalisation would redirect it back and forth with this\n // proxy, creating an infinite redirect loop.\n return cleanPath !== '/' && cleanPath.endsWith('/')\n ? cleanPath.slice(0, -1)\n : cleanPath;\n};\n\n/**\n * This handles the internal path Next.js sees.\n * To support optional [locale] folders, we need to decide if we\n * keep the locale prefix or strip it.\n */\nconst rewriteUrl = (\n request: NextRequest,\n newPath: string,\n locale: Locale\n): NextResponse => {\n const search = request.nextUrl.search;\n\n // Next.js strips `basePath` from `request.nextUrl.pathname` before the\n // middleware runs, so every path computed from it (e.g. `/en/about`) lacks\n // the basePath prefix. When we pass that as an absolute path to `new URL`,\n // it replaces the entire path after the origin, silently discarding the\n // basePath (e.g. `new URL('/en/', 'http://host/weather/')` →\n // `http://host/en/`). Prepending the configured basePath restores the\n // correct mount-point so rewrites resolve under the app root.\n const basePathValue = (basePath as string) || '';\n const pathWithBase =\n basePathValue && !newPath.startsWith(basePathValue)\n ? `${basePathValue}${newPath}`\n : newPath;\n\n const pathWithSearch =\n search && !pathWithBase.includes('?')\n ? `${pathWithBase}${search}`\n : pathWithBase;\n\n const requestHeaders = new Headers(request.headers);\n setLocaleInStorageServer(locale, {\n setHeader: (name: string, value: string) => {\n requestHeaders.set(name, value);\n },\n });\n\n const targetUrl = new URL(pathWithSearch, request.url);\n\n // If the target URL is exactly the current request URL,\n // we just want to `next()` to avoid losing headers on a redundant rewrite.\n const response =\n targetUrl.href === request.nextUrl.href\n ? NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n })\n : NextResponse.rewrite(targetUrl, {\n request: {\n headers: requestHeaders,\n },\n });\n\n setLocaleInStorageServer(locale, {\n setHeader: (name: string, value: string) => {\n response.headers.set(name, value);\n },\n });\n return response;\n};\n\n/**\n * Redirects the request to the new path.\n *\n * @param request - The incoming Next.js request object.\n * @param newPath - The new path to redirect to.\n * @param persistLocale - When provided, the locale is written to storage\n * (cookie/header, per config) on the redirect response so the follow-up\n * request resolves the same locale instead of re-running detection.\n * @returns - The redirect response.\n */\nconst redirectUrl = (\n request: NextRequest,\n newPath: string,\n persistLocale?: Locale\n): NextResponse => {\n const search = request.nextUrl.search;\n const pathWithSearch =\n search && !newPath.includes('?') ? `${newPath}${search}` : newPath;\n\n const target = new URL(pathWithSearch, request.url);\n\n // Prevent open redirect: if the resolved origin differs from the request\n // origin, strip it back to a same-origin URL using only the path/search/hash.\n const safeTarget =\n target.origin === request.nextUrl.origin\n ? target\n : new URL(\n `${target.pathname}${target.search}${target.hash}`,\n request.url\n );\n\n const response = NextResponse.redirect(safeTarget);\n\n if (persistLocale) {\n persistLocaleOnResponse(response, persistLocale);\n }\n\n return response;\n};\n\n/**\n * Writes the resolved locale to the outgoing response's storage (cookie and/or\n * header, according to `routing.storage`). Only the cookie survives a client\n * redirect, so this is what carries an explicitly-selected locale across a\n * prefix-stripping redirect. Enabled cookie/header targets are resolved by\n * {@link setLocaleInStorageServer} from the config; disabled ones are no-ops.\n *\n * @param response - The outgoing Next.js response to attach storage to.\n * @param locale - The locale to persist.\n */\nconst persistLocaleOnResponse = (\n response: NextResponse,\n locale: Locale\n): void => {\n setLocaleInStorageServer(locale, {\n setCookieStore: (name, value, attributes) => {\n response.cookies.set(name, value, {\n path: attributes.path,\n domain: attributes.domain,\n expires:\n typeof attributes.expires === 'number'\n ? new Date(attributes.expires)\n : attributes.expires,\n secure: attributes.secure,\n sameSite: attributes.sameSite,\n httpOnly: attributes.httpOnly,\n });\n },\n setHeader: (name, value) => {\n response.headers.set(name, value);\n },\n });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"intlayerProxy.cjs","names":["internationalization","routing","ROUTING_MODE","NextResponse","localeDetector"],"sources":["../../../src/proxy/intlayerProxy.ts"],"sourcesContent":["import { internationalization, log, routing } from '@intlayer/config/built';\nimport { ROUTING_MODE } from '@intlayer/config/defaultValues';\nimport { getAppLogger } from '@intlayer/config/logger';\nimport {\n formatProxyEnabledMessage,\n getCanonicalPath,\n getDomainHostname,\n getDomainOrigin,\n getInternalPath,\n getLocaleFromDomain,\n getLocalizedPath,\n getRewriteRules,\n isProxyStorageLocaleEnabled,\n type LocaleDomainMap,\n resolveProxyMode,\n} from '@intlayer/core/localization';\nimport {\n getLocaleFromStorageServer,\n setLocaleInStorageServer,\n} from '@intlayer/core/utils';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport {\n type NextFetchEvent,\n type NextRequest,\n NextResponse,\n} from 'next/server';\nimport { localeDetector } from './localeDetector';\n\n/**\n * Controls whether locale detection occurs during Next.js prefetch requests\n * - true: Detect and apply locale during prefetch\n * - false: Use default locale during prefetch (recommended)\n *\n * This setting affects how Next.js handles locale prefetching:\n *\n * Example scenario:\n * - User's browser language is 'fr'\n * - Current page is /fr/about\n * - Link prefetches /about\n *\n * With `detectLocaleOnPrefetchNoPrefix:true`\n * - Prefetch detects 'fr' locale from browser\n * - Redirects prefetch to /fr/about\n *\n * With `detectLocaleOnPrefetchNoPrefix:false` (default)\n * - Prefetch uses default locale\n * - Redirects prefetch to /en/about (assuming 'en' is default)\n *\n * When to use true:\n * - Your app uses non-localized internal links (e.g. <a href=\"/about\">)\n * - You want consistent locale detection behavior between regular and prefetch requests\n *\n * When to use false (default):\n * - Your app uses locale-prefixed links (e.g. <a href=\"/fr/about\">)\n * - You want to optimize prefetching performance\n * - You want to avoid potential redirect loops\n */\nconst DEFAULT_DETECT_LOCALE_ON_PREFETCH_NO_PREFIX = false;\n\nconst { locales, defaultLocale } = internationalization ?? {};\nconst { basePath, mode, rewrite, domains, enableProxy } = routing ?? {};\n\n// Resolved behaviour of the locale-routing proxy. `disabled` turns\n// `intlayerProxy` into a pass-through so apps can handle routing themselves.\n// The env var backing this is injected at build time so bundlers can tree-shake\n// the guarded branches.\nconst proxyMode = resolveProxyMode(enableProxy);\n\n// Next.js inlines NODE_ENV into every bundle, edge middleware included, so this\n// is both reliable and statically eliminable. `next build` is the only command\n// that injects `INTLAYER_ROUTING_ENABLE_PROXY`, meaning a dev server always\n// reaches `resolveProxyMode` through the configuration value.\n//\n// Matched against `development` rather than \"not production\" on purpose: only\n// `next dev` runs a dev server. Any other value (`test`, a custom staging env)\n// has no dev server in play and must keep the full production behaviour.\nconst isDevServer = process.env.NODE_ENV === 'development';\n\n// In auto mode, a dev server keeps locale routing URL-driven: the stored locale\n// is not used as a redirect source, so a stale cookie cannot keep pulling every\n// navigation to another locale while developing.\nconst canUseStorageLocale = isProxyStorageLocaleEnabled(proxyMode, isDevServer);\n\n// Announce the proxy the way the Vite plugin does on `configureServer`. Next\n// has no server-start hook for middleware, so this runs when the middleware\n// module is first evaluated — at the first request `next dev` routes through\n// it. Restricted to the dev server on purpose: in production this module is\n// re-evaluated on every edge cold start, and the line would be pure noise.\nif (isDevServer && proxyMode !== 'disabled') {\n getAppLogger({ log })(formatProxyEnabledMessage(!canUseStorageLocale), {\n level: 'info',\n });\n}\n\n// Note: cookie names are resolved inside LocaleStorage based on configuration\n\n// Derived flags from routing.mode\nconst effectiveMode = mode ?? ROUTING_MODE;\nconst noPrefix =\n (!(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'no-prefix'\n ) &&\n effectiveMode === 'no-prefix') ||\n (!(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'search-params'\n ) &&\n effectiveMode === 'search-params');\nconst prefixDefault =\n !(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'prefix-all'\n ) && effectiveMode === 'prefix-all';\n\nconst internalPrefix = !noPrefix;\n\nconst rewriteRules =\n process.env.INTLAYER_ROUTING_REWRITE_RULES !== 'false'\n ? getRewriteRules(rewrite, 'url')\n : undefined;\n\n/**\n * Detects if the request is a prefetch request from Next.js.\n *\n * Next.js prefetch requests are identified by:\n * - purpose: 'prefetch' (standard prefetch header)\n * - next-router-prefetch: '1' (Next.js app-router prefetch)\n *\n * Note: `next-url` and `x-nextjs-data` are intentionally NOT used here.\n * Both are also sent on real client-side navigations (RSC navigation\n * requests and pages-router data requests respectively), so treating them\n * as prefetch would force such navigations to the default locale instead\n * of the user's stored locale.\n *\n * During prefetch, we should ignore cookie-based locale detection\n * to prevent unwanted redirects when users are switching locales.\n *\n * @param request - The incoming Next.js request object.\n * @returns - True if the request is a prefetch request, false otherwise.\n */\nconst isPrefetchRequest = (request: NextRequest): boolean => {\n const purpose = request.headers.get('purpose');\n const nextRouterPrefetch = request.headers.get('next-router-prefetch');\n\n return purpose === 'prefetch' || nextRouterPrefetch === '1';\n};\n\n// Ensure locale is reflected in search params when routing mode is 'search-params'\nconst appendLocaleSearchIfNeeded = (\n search: string | undefined,\n locale: Locale\n): string | undefined => {\n if (\n (process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'search-params') ||\n effectiveMode !== 'search-params'\n )\n return search;\n const params = new URLSearchParams(search ?? '');\n params.set('locale', locale);\n return `?${params.toString()}`;\n};\n\n/**\n * Proxy that handles the internationalization layer\n *\n * Usage:\n *\n * ```ts\n * // ./src/proxy.ts\n *\n * export { intlayerProxy as proxy } from '@intlayer/next/proxy';\n *\n * // applies this proxy only to files in the app directory\n * export const config = {\n * matcher: '/((?!api|static|.*\\\\..*|_next).*)',\n * };\n * ```\n *\n * Main proxy function for handling internationalization.\n *\n * @param request - The incoming Next.js request object.\n * @param event - The Next.js fetch event (optional).\n * @param response - The Next.js response object (optional).\n * @returns - The response to be returned to the client.\n *\n */\nexport const intlayerProxy = (\n request: NextRequest,\n _event?: NextFetchEvent,\n _response?: NextResponse\n): NextResponse => {\n // When the proxy is disabled, pass the request through untouched.\n if (proxyMode === 'disabled') {\n return NextResponse.next();\n }\n\n const pathname = request.nextUrl.pathname;\n\n const localLocale = getLocalLocale(request);\n\n if (noPrefix) {\n return handleNoPrefix(request, localLocale, pathname);\n }\n\n const pathLocale = getPathLocale(pathname);\n\n // Domain routing: if the path locale is mapped to a different domain, redirect there.\n // e.g. intlayer.org/zh/about → https://intlayer.zh/about\n if (\n process.env.INTLAYER_ROUTING_DOMAINS !== 'false' &&\n pathLocale &&\n domains\n ) {\n const localeDomain = domains[pathLocale];\n\n if (localeDomain) {\n const domainHost = getDomainHostname(localeDomain);\n\n if (domainHost !== request.nextUrl.hostname) {\n const rawPath = pathname.slice(`/${pathLocale}`.length) || '/';\n const targetOrigin = getDomainOrigin(localeDomain);\n\n return NextResponse.redirect(\n new URL(`${rawPath}${request.nextUrl.search}`, targetOrigin)\n );\n }\n }\n }\n\n // Domain routing: if the current hostname is exclusively mapped to one locale,\n // treat it as that locale's domain — no URL prefix needed.\n // e.g. intlayer.zh/about → internally rewrite to /zh/about\n if (process.env.INTLAYER_ROUTING_DOMAINS !== 'false' && !pathLocale) {\n const domainLocale = getLocaleFromDomain(\n request.nextUrl.hostname,\n domains as LocaleDomainMap\n );\n\n if (domainLocale) {\n const canonicalPath = getCanonicalPath(\n pathname,\n domainLocale,\n rewriteRules\n );\n\n // Never emit a trailing slash (`/zh/`): Next.js trailing-slash\n // normalisation would redirect it back and forth with this proxy.\n const internalPath = getInternalPath(canonicalPath, domainLocale);\n\n return rewriteUrl(\n request,\n internalPath + (request.nextUrl.search ?? ''),\n domainLocale\n );\n }\n }\n\n return handlePrefix(request, localLocale, pathLocale, pathname);\n};\n\n/**\n * Retrieves the locale from the request cookies if available and valid.\n *\n * Returns `undefined` when the stored locale is not allowed to drive locale\n * resolution (auto mode on a dev server), which makes every caller fall through\n * to `Accept-Language` detection and then the default locale.\n *\n * @param request - The incoming Next.js request object.\n * @returns - The locale found in the cookies, or undefined if not found or invalid.\n */\nconst getLocalLocale = (request: NextRequest): Locale | undefined => {\n if (!canUseStorageLocale) return undefined;\n\n return getLocaleFromStorageServer({\n getCookie: (name: string) => request.cookies.get(name)?.value ?? null,\n getHeader: (name: string) => request.headers.get(name) ?? null,\n });\n};\n\n/**\n * Handles the case where URLs do not have locale prefixes.\n */\nconst handleNoPrefix = (\n request: NextRequest,\n localLocale: Locale | undefined,\n pathname: string\n): NextResponse => {\n const pathLocale = getPathLocale(pathname);\n\n if (pathLocale) {\n const pathWithoutLocale = pathname.slice(`/${pathLocale}`.length) || '/';\n\n const canonicalPath = getCanonicalPath(\n pathWithoutLocale,\n pathLocale,\n rewriteRules\n );\n\n const search = appendLocaleSearchIfNeeded(\n request.nextUrl.search,\n pathLocale\n );\n\n const redirectPath = search\n ? `${canonicalPath}${search}`\n : `${canonicalPath}${request.nextUrl.search ?? ''}`;\n\n // Persist the explicitly-requested locale: stripping the prefix drops the\n // only locale signal from the URL, so without this the follow-up request\n // would fall back to cookie / Accept-Language detection and could resolve\n // a different locale.\n return redirectUrl(request, redirectPath, pathLocale);\n }\n\n if (\n !(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'search-params'\n ) &&\n effectiveMode === 'search-params'\n ) {\n const existingSearchParams = new URLSearchParams(request.nextUrl.search);\n const existingLocale = existingSearchParams.get('locale');\n\n const isExistingValid = locales?.includes(existingLocale as Locale);\n\n let locale = (localLocale ??\n (isExistingValid ? (existingLocale as Locale) : undefined) ??\n localeDetector?.(request) ??\n defaultLocale) as Locale;\n\n if (!locales?.includes(locale as Locale)) {\n locale = defaultLocale as Locale;\n }\n\n const canonicalPath = getCanonicalPath(\n pathname,\n locale as Locale,\n rewriteRules\n );\n\n if (existingLocale === locale) {\n const internalPath = internalPrefix\n ? getInternalPath(canonicalPath, locale as Locale)\n : canonicalPath;\n const rewritePath = `${internalPath}${request.nextUrl.search ?? ''}`;\n return rewriteUrl(request, rewritePath, locale as Locale);\n }\n\n const search = appendLocaleSearchIfNeeded(\n request.nextUrl.search,\n locale as Locale\n );\n // Use original pathname for redirect to preserve user's URL input, just adding params\n const redirectPath = search\n ? `${pathname}${search}`\n : `${pathname}${request.nextUrl.search ?? ''}`;\n\n return redirectUrl(request, redirectPath);\n }\n\n // effectiveMode === 'no-prefix'\n let locale = (localLocale ??\n localeDetector?.(request) ??\n defaultLocale) as Locale;\n\n if (!locales?.includes(locale as Locale)) {\n locale = defaultLocale as Locale;\n }\n\n const canonicalPath = getCanonicalPath(\n pathname,\n locale as Locale,\n rewriteRules\n );\n\n const internalPath = internalPrefix\n ? getInternalPath(canonicalPath, locale as Locale)\n : canonicalPath;\n const search = appendLocaleSearchIfNeeded(\n request.nextUrl.search,\n locale as Locale\n );\n const rewritePath = search\n ? `${internalPath}${search}`\n : `${internalPath}${request.nextUrl.search ?? ''}`;\n\n return rewriteUrl(request, rewritePath, locale as Locale);\n};\n\n/**\n * Checks whether a pathname starts with the given locale as a full path\n * segment (`/fr` or `/fr/...`). A bare `startsWith('/fr')` would also match\n * unrelated paths like `/friends`, causing wrong prefix stripping and\n * self-redirect loops.\n *\n * @param pathname - The pathname to test.\n * @param locale - The locale to look for as the first path segment.\n * @returns - True if the first path segment is exactly the locale.\n */\nconst hasLocaleSegmentPrefix = (pathname: string, locale: Locale): boolean =>\n pathname === `/${locale}` || pathname.startsWith(`/${locale}/`);\n\n/**\n * Extracts the locale from the URL pathname if present.\n *\n * @param pathname - The pathname from the request URL.\n * @returns - The locale found in the pathname, or undefined if not found.\n */\nconst getPathLocale = (pathname: string): Locale | undefined =>\n (locales as Locale[] | undefined)?.find((locale) =>\n hasLocaleSegmentPrefix(pathname, locale)\n );\n\n/**\n * Handles the case where URLs have locale prefixes.\n *\n * @param request - The incoming Next.js request object.\n * @param localLocale - The locale from the cookie.\n * @param pathLocale - The locale extracted from the pathname.\n * @param pathname - The pathname from the request URL.\n * @param basePathTrailingSlash - Indicates if the basePath ends with a slash.\n * @returns - The response to be returned to the client.\n */\nconst handlePrefix = (\n request: NextRequest,\n localLocale: Locale | undefined,\n pathLocale: Locale | undefined,\n pathname: string\n): NextResponse => {\n if (!pathLocale) {\n const isPrefetch = isPrefetchRequest(request);\n if (isPrefetch && !DEFAULT_DETECT_LOCALE_ON_PREFETCH_NO_PREFIX) {\n return handleMissingPathLocale(\n request,\n defaultLocale as Locale,\n pathname\n );\n }\n return handleMissingPathLocale(request, localLocale, pathname);\n }\n\n return handleExistingPathLocale(request, pathLocale, pathname);\n};\n\n/**\n * Handles requests where the locale is missing from the URL pathname.\n *\n * @param request - The incoming Next.js request object.\n * @param localLocale - The locale from the cookie.\n * @param pathname - The pathname from the request URL.\n * @param basePathTrailingSlash - Indicates if the basePath ends with a slash.\n * @returns - The response to be returned to the client.\n */\nconst handleMissingPathLocale = (\n request: NextRequest,\n localLocale: Locale | undefined,\n pathname: string\n): NextResponse => {\n let locale = (localLocale ??\n localeDetector?.(request) ??\n defaultLocale) as Locale;\n\n if (!locales?.includes(locale as Locale)) {\n locale = defaultLocale as Locale;\n }\n\n // Resolve to canonical path.\n // If user visits /a-propos (implied 'fr'), we resolve to /about\n const canonicalPath = getCanonicalPath(pathname, locale, rewriteRules);\n\n // Determine target localized path for redirection\n // /about + 'fr' -> /a-propos\n const targetLocalizedPathResult = getLocalizedPath(\n canonicalPath,\n locale,\n rewriteRules\n );\n const targetLocalizedPath =\n typeof targetLocalizedPathResult === 'string'\n ? targetLocalizedPathResult\n : targetLocalizedPathResult.path;\n\n const newPath = constructPath(\n locale,\n targetLocalizedPath,\n basePath as string,\n appendLocaleSearchIfNeeded(request.nextUrl.search, locale)\n );\n\n // Never emit a trailing slash (`/en/` for canonicalPath `/`): Next.js\n // trailing-slash normalisation would redirect it back and forth with this\n // proxy. `getInternalPath` collapses the root path to `/${locale}`.\n return prefixDefault || locale !== defaultLocale\n ? redirectUrl(request, newPath)\n : rewriteUrl(\n request,\n internalPrefix ? getInternalPath(canonicalPath, locale) : canonicalPath,\n locale\n ); // Rewrite must use Canonical\n};\n\n/**\n * Handles requests where the locale exists in the URL pathname.\n *\n * @param request - The incoming Next.js request object.\n * @param localLocale - The locale from the cookie.\n * @param pathLocale - The locale extracted from the pathname.\n * @param pathname - The pathname from the request URL.\n * @returns - The response to be returned to the client.\n */\nconst handleExistingPathLocale = (\n request: NextRequest,\n pathLocale: Locale,\n pathname: string\n): NextResponse => {\n const rawPath = pathname.slice(`/${pathLocale}`.length) || '/';\n\n // 1. Identify the Canonical Path (Internal Next.js path)\n // Ex: /a-propos (from URL) -> /about (Canonical)\n const canonicalPath = getCanonicalPath(rawPath, pathLocale, rewriteRules);\n\n // By skipping the forced localLocale check, we allow the explicit pathLocale\n // to take precedence, which correctly updates the header/cookie when navigating.\n\n // Rewrite Logic\n // We must rewrite to the Next.js internal structure: /[locale]/[canonicalPath]\n // Ex: Rewrite /fr/a-propos -> /fr/about\n\n // 2. Redirect to localized path if needed (Canonical -> Localized)\n // Ex: /fr/about -> /fr/a-propos\n const targetLocalizedPathResult = getLocalizedPath(\n canonicalPath,\n pathLocale,\n rewriteRules\n );\n const targetLocalizedPath =\n typeof targetLocalizedPathResult === 'string'\n ? targetLocalizedPathResult\n : targetLocalizedPathResult.path;\n const isRewritten =\n typeof targetLocalizedPathResult === 'string'\n ? false\n : targetLocalizedPathResult.isRewritten;\n\n if (isRewritten && targetLocalizedPath !== rawPath) {\n const newPath = constructPath(\n pathLocale,\n targetLocalizedPath,\n basePath as string,\n appendLocaleSearchIfNeeded(request.nextUrl.search, pathLocale)\n );\n return redirectUrl(request, newPath);\n }\n\n // Never emit a trailing slash (`/fr/` for the bare `/fr` URL): rewriting\n // `/fr` to `/fr/` makes Next.js issue a trailing-slash normalisation\n // redirect back to `/fr`, which this proxy rewrites again — an infinite\n // redirect loop. `getInternalPath` collapses the root path to `/${locale}`.\n const internalUrl = internalPrefix\n ? getInternalPath(canonicalPath, pathLocale)\n : canonicalPath;\n\n // Only handle redirect if we are strictly managing default locale prefixing\n // Fix: pass `canonicalPath` (the path *without* the locale prefix, e.g. /pricing)\n // instead of `pathname` (the full path including prefix, e.g. /en/pricing).\n // Previously this caused an infinite redirect loop in prefix-no-default mode\n // because handleDefaultLocaleRedirect built the redirect target from its third\n // argument, which reproduced the same URL on every response.\n if (!prefixDefault && pathLocale === defaultLocale) {\n return handleDefaultLocaleRedirect(request, pathLocale, canonicalPath);\n }\n\n const search = request.nextUrl.search;\n return rewriteUrl(request, internalUrl + (search ?? ''), pathLocale);\n};\n\n/**\n * Handles the scenario where the locale in the cookie does not match the locale in the URL pathname.\n *\n * @param request - The incoming Next.js request object.\n * @param pathname - The pathname from the request URL.\n * @param pathLocale - The locale extracted from the pathname.\n * @param localLocale - The locale from the cookie.\n * @param basePath - The base path of the application.\n * @returns - The new URL path with the correct locale.\n */\n// Function handleCookieLocaleMismatch was removed because the URL locale should take precedence over the stored locale.\n\n/**\n * The key fix for 404s without [locale] folders\n */\nconst handleDefaultLocaleRedirect = (\n request: NextRequest,\n pathLocale: Locale,\n canonicalPath: string // Internal path (e.g. /about)\n): NextResponse => {\n // Always called with !prefixDefault && pathLocale === defaultLocale (pre-validated by caller).\n // Redirect to strip the default-locale prefix from the URL.\n const targetLocalizedPathResult = getLocalizedPath(\n canonicalPath,\n pathLocale,\n rewriteRules\n );\n const targetLocalizedPath =\n typeof targetLocalizedPathResult === 'string'\n ? targetLocalizedPathResult\n : targetLocalizedPathResult.path;\n\n const basePathValue = (basePath as string) || '';\n const basePathTrailingSlash = basePathValue.endsWith('/');\n let finalPath = targetLocalizedPath;\n if (finalPath.startsWith('/')) finalPath = finalPath.slice(1);\n\n const fullPath = `${basePathValue}${basePathTrailingSlash ? '' : '/'}${finalPath}`;\n\n const searchWithLocale = appendLocaleSearchIfNeeded(\n request.nextUrl.search,\n pathLocale\n );\n\n // Persist the explicitly-requested default locale. Stripping the prefix\n // (e.g. /es → /) drops the only locale signal from the URL, so without this\n // the follow-up request to the canonical path would fall back to\n // Accept-Language detection and could resolve a different locale (e.g. /en).\n return redirectUrl(\n request,\n fullPath + (searchWithLocale ?? request.nextUrl.search ?? ''),\n pathLocale\n );\n};\n\n/**\n * Constructs a new path by combining the locale, path, basePath, and search parameters.\n *\n * @param locale - The locale to include in the path.\n * @param path - The original path from the request.\n * @param basePath - The base path of the application.\n * @param [search] - The query string from the request URL (optional).\n * @returns - The constructed new path.\n */\nconst constructPath = (\n locale: Locale,\n path: string,\n basePath: string,\n search?: string\n): string => {\n // Remove existing locale prefix from path if it was passed by mistake,\n // though we usually pass localized paths here now.\n const pathWithoutPrefix = hasLocaleSegmentPrefix(path, locale)\n ? path.slice(`/${locale}`.length) || '/'\n : path;\n\n if (\n (!(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'no-prefix'\n ) &&\n effectiveMode === 'no-prefix') ||\n (!(\n process.env.INTLAYER_ROUTING_MODE &&\n process.env.INTLAYER_ROUTING_MODE !== 'search-params'\n ) &&\n effectiveMode === 'search-params')\n ) {\n // `search` is either undefined or already has a leading '?' (from\n // appendLocaleSearchIfNeeded / request.nextUrl.search), so append as-is.\n return `${pathWithoutPrefix}${search ?? ''}`;\n }\n\n // Prefix handling\n const pathWithLocalePrefix = hasLocaleSegmentPrefix(path, locale)\n ? path\n : `${locale}${path.startsWith('/') ? '' : '/'}${path}`;\n\n const basePathValue = basePath || '';\n const basePathTrailingSlash = basePathValue.endsWith('/');\n const newPath = `${basePathValue}${basePathTrailingSlash ? '' : '/'}${pathWithLocalePrefix}`;\n\n // Clean double slashes\n const cleanPath = newPath.replace(/\\/+/g, '/');\n\n // Never emit a trailing slash (`/fr/` for the root path): the framework's\n // trailing-slash normalisation would redirect it back and forth with this\n // proxy, creating an infinite redirect loop.\n return cleanPath !== '/' && cleanPath.endsWith('/')\n ? cleanPath.slice(0, -1)\n : cleanPath;\n};\n\n/**\n * This handles the internal path Next.js sees.\n * To support optional [locale] folders, we need to decide if we\n * keep the locale prefix or strip it.\n */\nconst rewriteUrl = (\n request: NextRequest,\n newPath: string,\n locale: Locale\n): NextResponse => {\n const search = request.nextUrl.search;\n\n // Next.js strips `basePath` from `request.nextUrl.pathname` before the\n // middleware runs, so every path computed from it (e.g. `/en/about`) lacks\n // the basePath prefix. When we pass that as an absolute path to `new URL`,\n // it replaces the entire path after the origin, silently discarding the\n // basePath (e.g. `new URL('/en/', 'http://host/weather/')` →\n // `http://host/en/`). Prepending the configured basePath restores the\n // correct mount-point so rewrites resolve under the app root.\n const basePathValue = (basePath as string) || '';\n const pathWithBase =\n basePathValue && !newPath.startsWith(basePathValue)\n ? `${basePathValue}${newPath}`\n : newPath;\n\n const pathWithSearch =\n search && !pathWithBase.includes('?')\n ? `${pathWithBase}${search}`\n : pathWithBase;\n\n const requestHeaders = new Headers(request.headers);\n setLocaleInStorageServer(locale, {\n setHeader: (name: string, value: string) => {\n requestHeaders.set(name, value);\n },\n });\n\n const targetUrl = new URL(pathWithSearch, request.url);\n\n // If the target URL is exactly the current request URL,\n // we just want to `next()` to avoid losing headers on a redundant rewrite.\n const response =\n targetUrl.href === request.nextUrl.href\n ? NextResponse.next({\n request: {\n headers: requestHeaders,\n },\n })\n : NextResponse.rewrite(targetUrl, {\n request: {\n headers: requestHeaders,\n },\n });\n\n setLocaleInStorageServer(locale, {\n setHeader: (name: string, value: string) => {\n response.headers.set(name, value);\n },\n });\n return response;\n};\n\n/**\n * Redirects the request to the new path.\n *\n * @param request - The incoming Next.js request object.\n * @param newPath - The new path to redirect to.\n * @param persistLocale - When provided, the locale is written to storage\n * (cookie/header, per config) on the redirect response so the follow-up\n * request resolves the same locale instead of re-running detection.\n * @returns - The redirect response.\n */\nconst redirectUrl = (\n request: NextRequest,\n newPath: string,\n persistLocale?: Locale\n): NextResponse => {\n const search = request.nextUrl.search;\n const pathWithSearch =\n search && !newPath.includes('?') ? `${newPath}${search}` : newPath;\n\n const target = new URL(pathWithSearch, request.url);\n\n // Prevent open redirect: if the resolved origin differs from the request\n // origin, strip it back to a same-origin URL using only the path/search/hash.\n const safeTarget =\n target.origin === request.nextUrl.origin\n ? target\n : new URL(\n `${target.pathname}${target.search}${target.hash}`,\n request.url\n );\n\n const response = NextResponse.redirect(safeTarget);\n\n if (persistLocale) {\n persistLocaleOnResponse(response, persistLocale);\n }\n\n return response;\n};\n\n/**\n * Writes the resolved locale to the outgoing response's storage (cookie and/or\n * header, according to `routing.storage`). Only the cookie survives a client\n * redirect, so this is what carries an explicitly-selected locale across a\n * prefix-stripping redirect. Enabled cookie/header targets are resolved by\n * {@link setLocaleInStorageServer} from the config; disabled ones are no-ops.\n *\n * @param response - The outgoing Next.js response to attach storage to.\n * @param locale - The locale to persist.\n */\nconst persistLocaleOnResponse = (\n response: NextResponse,\n locale: Locale\n): void => {\n setLocaleInStorageServer(locale, {\n setCookieStore: (name, value, attributes) => {\n response.cookies.set(name, value, {\n path: attributes.path,\n domain: attributes.domain,\n expires:\n typeof attributes.expires === 'number'\n ? new Date(attributes.expires)\n : attributes.expires,\n secure: attributes.secure,\n sameSite: attributes.sameSite,\n httpOnly: attributes.httpOnly,\n });\n },\n setHeader: (name, value) => {\n response.headers.set(name, value);\n },\n });\n};\n"],"mappings":"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"}
@@ -0,0 +1,190 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
3
+ let _intlayer_config_logger = require("@intlayer/config/logger");
4
+ let node_path = require("node:path");
5
+ let _intlayer_config_colors = require("@intlayer/config/colors");
6
+ _intlayer_config_colors = require_runtime.__toESM(_intlayer_config_colors);
7
+ let _intlayer_config_utils = require("@intlayer/config/utils");
8
+ let _intlayer_engine_utils = require("@intlayer/engine/utils");
9
+ let node_fs = require("node:fs");
10
+ let node_fs_promises = require("node:fs/promises");
11
+
12
+ //#region src/server/prepareSwcOptimization.ts
13
+ /**
14
+ * Opt-in to the SWC plugin's per-file tracing.
15
+ *
16
+ * The plugin transforms one file at a time with no cross-file state, so all it
17
+ * can report is a line per file — hundreds of them on a real project, and with
18
+ * no counterpart in the Vite build output. The purge and minify summaries that
19
+ * `log.mode` does control are emitted by the pipeline itself, in Node; this
20
+ * variable is purely a debugging aid on top.
21
+ */
22
+ const SWC_LOG_LEVEL_ENV_VAR = "INTLAYER_SWC_LOG_LEVEL";
23
+ /** Sentinel coordinating the pipeline across the processes of one build. */
24
+ const PURGE_SENTINEL_FILE_NAME = "intlayer-swc-purge.lock";
25
+ /** Where the serialised field-rename tables are cached between processes. */
26
+ const FIELD_RENAME_MAP_FILE_NAME = "swc-field-rename-map.json";
27
+ /**
28
+ * Next.js evaluates `next.config.*` in the main build process and again in each
29
+ * Turbopack/webpack worker. The dictionary rewrite must happen exactly once —
30
+ * a second run would derive the short aliases from the already-renamed JSON —
31
+ * so the sentinel window has to cover a whole build startup.
32
+ */
33
+ const PURGE_CACHE_TIMEOUT_MS = 30 * 1e3;
34
+ /** Absolute path of a file in the intlayer cache directory. */
35
+ const getCacheFilePath = (intlayerConfig, fileName) => (0, node_path.join)(intlayerConfig.system.baseDir, ".intlayer", "cache", fileName);
36
+ /**
37
+ * Resolves the verbosity the `@intlayer/swc` plugin should report at.
38
+ *
39
+ * Off unless {@link SWC_LOG_LEVEL_ENV_VAR} asks for tracing: `log.mode` governs
40
+ * the purge and minify reporting, which the pipeline emits in Node so the
41
+ * Next.js build reads the same as the Vite one.
42
+ */
43
+ const resolveSwcLogLevel = (intlayerConfig) => {
44
+ if (intlayerConfig.log.mode === "disabled") return "off";
45
+ const requestedLogLevel = process.env[SWC_LOG_LEVEL_ENV_VAR];
46
+ return requestedLogLevel === "info" || requestedLogLevel === "debug" ? requestedLogLevel : "off";
47
+ };
48
+ /**
49
+ * Whether the purge / minify pipeline should run for this build.
50
+ *
51
+ * Compat-adapter callers disable it: their descriptors reach `withIntlayer`
52
+ * only in the SWC plugin's lossy wire format, so the usage analyser would not
53
+ * recognise `useTranslation(...)` & co. and would purge fields those call sites
54
+ * still read.
55
+ */
56
+ const getIsPurgePipelineEnabled = (intlayerConfig, swcExtraCallers, isSwcPluginUsable) => {
57
+ const { purge, minify, optimize } = intlayerConfig.build;
58
+ if (optimize === false) return false;
59
+ if (!purge && !minify) return false;
60
+ if (!isSwcPluginUsable) return false;
61
+ if (swcExtraCallers && swcExtraCallers.length > 0) {
62
+ (0, _intlayer_config_logger.getAppLogger)(intlayerConfig)([
63
+ "Dictionary purge and minification are",
64
+ (0, _intlayer_config_logger.colorize)("disabled", _intlayer_config_colors.GREY_DARK),
65
+ "because compat-adapter callers are configured — their call sites are",
66
+ "not visible to the usage analyser."
67
+ ], { level: "warn" });
68
+ return false;
69
+ }
70
+ return true;
71
+ };
72
+ /**
73
+ * Exports this module needs from `@intlayer/babel`. Checked one by one so a
74
+ * version predating the purge pipeline is rejected like a missing package,
75
+ * instead of failing later with `runIntlayerPurgePipeline is not a function`.
76
+ */
77
+ const REQUIRED_BABEL_EXPORTS = [
78
+ "getPurgePluginOptions",
79
+ "runIntlayerPurgePipeline",
80
+ "serializeFieldRenameMap"
81
+ ];
82
+ /**
83
+ * Lazily loads `@intlayer/babel`, or `null` when it is not installed or too old
84
+ * to expose the purge pipeline.
85
+ */
86
+ const loadIntlayerBabel = (intlayerConfig) => {
87
+ try {
88
+ const intlayerBabel = (intlayerConfig.build?.require ?? (0, _intlayer_config_utils.getProjectRequire)())("@intlayer/babel");
89
+ if (!REQUIRED_BABEL_EXPORTS.every((exportName) => typeof intlayerBabel?.[exportName] === "function")) return null;
90
+ return intlayerBabel;
91
+ } catch {
92
+ return null;
93
+ }
94
+ };
95
+ /**
96
+ * Whether the purge / minify pipeline can run at all, i.e. whether a usable
97
+ * `@intlayer/babel` is resolvable from the project.
98
+ *
99
+ * Used by `withIntlayer` to keep the build report honest: announcing
100
+ * `Dictionary minification enabled` while the pipeline cannot load would describe a
101
+ * pass that never happens.
102
+ */
103
+ const getIsPurgePipelineAvailable = (intlayerConfig) => loadIntlayerBabel(intlayerConfig) !== null;
104
+ /**
105
+ * Runs the purge / minify pipeline and writes the resulting field-rename
106
+ * tables to the cache file so every process of the build reads the same ones.
107
+ */
108
+ const runPurgePipelineOnce = async (intlayerConfig, configOptions, dictionaries, fieldRenameMapFilePath) => {
109
+ const appLogger = (0, _intlayer_config_logger.getAppLogger)(intlayerConfig);
110
+ const intlayerBabel = loadIntlayerBabel(intlayerConfig);
111
+ if (!intlayerBabel) {
112
+ appLogger([
113
+ "Dictionary purge and minification are",
114
+ (0, _intlayer_config_logger.colorize)("disabled", _intlayer_config_colors.GREY_DARK),
115
+ "because",
116
+ (0, _intlayer_config_logger.colorize)("@intlayer/babel", _intlayer_config_colors.GREY_LIGHT),
117
+ "is missing or too old to expose the purge pipeline — install it in",
118
+ "this project to enable them."
119
+ ], { level: "warn" });
120
+ return;
121
+ }
122
+ const { getPurgePluginOptions, runIntlayerPurgePipeline, serializeFieldRenameMap } = intlayerBabel;
123
+ let fieldRenameMap;
124
+ try {
125
+ fieldRenameMap = serializeFieldRenameMap(runIntlayerPurgePipeline(getPurgePluginOptions({
126
+ configOptions,
127
+ dictionaries
128
+ })));
129
+ } catch (pipelineError) {
130
+ appLogger([
131
+ "Dictionary purge and minification",
132
+ (0, _intlayer_config_logger.colorize)("failed", _intlayer_config_colors.RED),
133
+ "— the compiled dictionaries are left untouched.",
134
+ pipelineError instanceof Error ? `(${pipelineError.message})` : String(pipelineError)
135
+ ], { level: "error" });
136
+ return;
137
+ }
138
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(fieldRenameMapFilePath), { recursive: true });
139
+ await (0, node_fs_promises.writeFile)(fieldRenameMapFilePath, JSON.stringify(fieldRenameMap), "utf-8");
140
+ };
141
+ /** Reads the cached field-rename tables, or `{}` when none were written. */
142
+ const readFieldRenameMapFile = (fieldRenameMapFilePath) => {
143
+ try {
144
+ return JSON.parse((0, node_fs.readFileSync)(fieldRenameMapFilePath, "utf-8"));
145
+ } catch {
146
+ return {};
147
+ }
148
+ };
149
+ /** Removes the cached field-rename tables, ignoring a missing file. */
150
+ const removeFieldRenameMapFile = (fieldRenameMapFilePath) => {
151
+ try {
152
+ (0, node_fs.rmSync)(fieldRenameMapFilePath, { force: true });
153
+ } catch {}
154
+ };
155
+ /**
156
+ * Prepares the parts of the build optimisation the `@intlayer/swc` plugin
157
+ * cannot do itself, and returns the field-rename tables it needs.
158
+ *
159
+ * Removing unused content fields (`build.purge`) and assigning short field
160
+ * aliases (`build.minify`) require reading every component source file and
161
+ * rewriting the compiled dictionary JSON — file I/O and cross-file state that a
162
+ * per-file Wasm transform has no access to. That analysis therefore runs here,
163
+ * in Node, through `@intlayer/babel`; the plugin only receives the resulting
164
+ * tables and rewrites the matching source accesses
165
+ * (`content.title` → `content.a`).
166
+ *
167
+ * The work is coordinated by a sentinel file: Next.js loads `next.config.*` in
168
+ * several processes per build, and running the minify pass twice would derive
169
+ * the aliases from the already-renamed dictionaries. Processes that lose the
170
+ * race read the tables the winner cached.
171
+ *
172
+ * @returns The field-rename tables, keyed by dictionary key. Empty when the
173
+ * pipeline is disabled, unavailable, or renamed nothing.
174
+ */
175
+ const prepareSwcOptimization = async (intlayerConfig, params) => {
176
+ const { configOptions, dictionaries, swcExtraCallers, isSwcPluginUsable } = params;
177
+ const fieldRenameMapFilePath = getCacheFilePath(intlayerConfig, FIELD_RENAME_MAP_FILE_NAME);
178
+ if (!getIsPurgePipelineEnabled(intlayerConfig, swcExtraCallers, isSwcPluginUsable)) {
179
+ removeFieldRenameMapFile(fieldRenameMapFilePath);
180
+ return {};
181
+ }
182
+ await (0, _intlayer_engine_utils.runOnce)(getCacheFilePath(intlayerConfig, PURGE_SENTINEL_FILE_NAME), () => runPurgePipelineOnce(intlayerConfig, configOptions, dictionaries, fieldRenameMapFilePath), { cacheTimeoutMs: PURGE_CACHE_TIMEOUT_MS });
183
+ return readFieldRenameMapFile(fieldRenameMapFilePath);
184
+ };
185
+
186
+ //#endregion
187
+ exports.getIsPurgePipelineAvailable = getIsPurgePipelineAvailable;
188
+ exports.prepareSwcOptimization = prepareSwcOptimization;
189
+ exports.resolveSwcLogLevel = resolveSwcLogLevel;
190
+ //# sourceMappingURL=prepareSwcOptimization.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepareSwcOptimization.cjs","names":["ANSIColors"],"sources":["../../../src/server/prepareSwcOptimization.ts"],"sourcesContent":["import { readFileSync, rmSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type {\n getPurgePluginOptions as GetPurgePluginOptions,\n runIntlayerPurgePipeline as RunIntlayerPurgePipeline,\n SerializedFieldRenameMap,\n serializeFieldRenameMap as SerializeFieldRenameMap,\n} from '@intlayer/babel';\nimport type { SwcExtraCallerConfig } from '@intlayer/config/callers';\nimport * as ANSIColors from '@intlayer/config/colors';\nimport { colorize, getAppLogger } from '@intlayer/config/logger';\nimport type { GetConfigurationOptions } from '@intlayer/config/node';\nimport { getProjectRequire } from '@intlayer/config/utils';\nimport { runOnce } from '@intlayer/engine/utils';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport type { Dictionary } from '@intlayer/types/dictionary';\n\n/**\n * Field-rename tables keyed by dictionary key, forwarded to the\n * `@intlayer/swc` plugin's `fieldRenameMap` option.\n */\nexport type FieldRenameMapByDictionaryKey = Record<\n string,\n SerializedFieldRenameMap\n>;\n\n/** Verbosity forwarded to the `@intlayer/swc` plugin's `logLevel` option. */\nexport type SwcLogLevel = 'off' | 'info' | 'debug';\n\n/**\n * Subset of `@intlayer/babel` this module needs. The package is only an\n * optional runtime dependency, so it is required lazily and never imported at\n * module scope.\n */\ntype IntlayerBabelModule = {\n getPurgePluginOptions: typeof GetPurgePluginOptions;\n runIntlayerPurgePipeline: typeof RunIntlayerPurgePipeline;\n serializeFieldRenameMap: typeof SerializeFieldRenameMap;\n};\n\n/**\n * Opt-in to the SWC plugin's per-file tracing.\n *\n * The plugin transforms one file at a time with no cross-file state, so all it\n * can report is a line per file — hundreds of them on a real project, and with\n * no counterpart in the Vite build output. The purge and minify summaries that\n * `log.mode` does control are emitted by the pipeline itself, in Node; this\n * variable is purely a debugging aid on top.\n */\nconst SWC_LOG_LEVEL_ENV_VAR = 'INTLAYER_SWC_LOG_LEVEL';\n\n/** Sentinel coordinating the pipeline across the processes of one build. */\nconst PURGE_SENTINEL_FILE_NAME = 'intlayer-swc-purge.lock';\n\n/** Where the serialised field-rename tables are cached between processes. */\nconst FIELD_RENAME_MAP_FILE_NAME = 'swc-field-rename-map.json';\n\n/**\n * Next.js evaluates `next.config.*` in the main build process and again in each\n * Turbopack/webpack worker. The dictionary rewrite must happen exactly once —\n * a second run would derive the short aliases from the already-renamed JSON —\n * so the sentinel window has to cover a whole build startup.\n */\nconst PURGE_CACHE_TIMEOUT_MS = 30 * 1000;\n\n/** Absolute path of a file in the intlayer cache directory. */\nconst getCacheFilePath = (\n intlayerConfig: IntlayerConfig,\n fileName: string\n): string =>\n join(intlayerConfig.system.baseDir, '.intlayer', 'cache', fileName);\n\n/**\n * Resolves the verbosity the `@intlayer/swc` plugin should report at.\n *\n * Off unless {@link SWC_LOG_LEVEL_ENV_VAR} asks for tracing: `log.mode` governs\n * the purge and minify reporting, which the pipeline emits in Node so the\n * Next.js build reads the same as the Vite one.\n */\nexport const resolveSwcLogLevel = (\n intlayerConfig: IntlayerConfig\n): SwcLogLevel => {\n if (intlayerConfig.log.mode === 'disabled') return 'off';\n\n const requestedLogLevel = process.env[SWC_LOG_LEVEL_ENV_VAR];\n\n return requestedLogLevel === 'info' || requestedLogLevel === 'debug'\n ? requestedLogLevel\n : 'off';\n};\n\n/**\n * Whether the purge / minify pipeline should run for this build.\n *\n * Compat-adapter callers disable it: their descriptors reach `withIntlayer`\n * only in the SWC plugin's lossy wire format, so the usage analyser would not\n * recognise `useTranslation(...)` & co. and would purge fields those call sites\n * still read.\n */\nconst getIsPurgePipelineEnabled = (\n intlayerConfig: IntlayerConfig,\n swcExtraCallers: SwcExtraCallerConfig[] | undefined,\n isSwcPluginUsable: boolean\n): boolean => {\n const { purge, minify, optimize } = intlayerConfig.build;\n\n if (optimize === false) return false;\n if (!purge && !minify) return false;\n\n // The pipeline rewrites the compiled dictionaries in place — dropping unused\n // fields and renaming the rest to short aliases. Only the SWC plugin rewrites\n // the matching source accesses, so running this without it would ship\n // dictionaries whose keys no longer match the code reading them.\n if (!isSwcPluginUsable) return false;\n\n // `editor.enabled` is deliberately not checked here: the pipeline stands down\n // on its own and explains why, the same way the Vite build does.\n\n if (swcExtraCallers && swcExtraCallers.length > 0) {\n getAppLogger(intlayerConfig)(\n [\n 'Dictionary purge and minification are',\n colorize('disabled', ANSIColors.GREY_DARK),\n 'because compat-adapter callers are configured — their call sites are',\n 'not visible to the usage analyser.',\n ],\n { level: 'warn' }\n );\n return false;\n }\n\n return true;\n};\n\n/**\n * Exports this module needs from `@intlayer/babel`. Checked one by one so a\n * version predating the purge pipeline is rejected like a missing package,\n * instead of failing later with `runIntlayerPurgePipeline is not a function`.\n */\nconst REQUIRED_BABEL_EXPORTS = [\n 'getPurgePluginOptions',\n 'runIntlayerPurgePipeline',\n 'serializeFieldRenameMap',\n] as const satisfies readonly (keyof IntlayerBabelModule)[];\n\n/**\n * Lazily loads `@intlayer/babel`, or `null` when it is not installed or too old\n * to expose the purge pipeline.\n */\nconst loadIntlayerBabel = (\n intlayerConfig: IntlayerConfig\n): IntlayerBabelModule | null => {\n try {\n const requireFunction =\n intlayerConfig.build?.require ?? getProjectRequire();\n\n const intlayerBabel = requireFunction(\n '@intlayer/babel'\n ) as Partial<IntlayerBabelModule>;\n\n const hasEveryRequiredExport = REQUIRED_BABEL_EXPORTS.every(\n (exportName) => typeof intlayerBabel?.[exportName] === 'function'\n );\n\n if (!hasEveryRequiredExport) return null;\n\n return intlayerBabel as IntlayerBabelModule;\n } catch {\n return null;\n }\n};\n\n/**\n * Whether the purge / minify pipeline can run at all, i.e. whether a usable\n * `@intlayer/babel` is resolvable from the project.\n *\n * Used by `withIntlayer` to keep the build report honest: announcing\n * `Dictionary minification enabled` while the pipeline cannot load would describe a\n * pass that never happens.\n */\nexport const getIsPurgePipelineAvailable = (\n intlayerConfig: IntlayerConfig\n): boolean => loadIntlayerBabel(intlayerConfig) !== null;\n\n/**\n * Runs the purge / minify pipeline and writes the resulting field-rename\n * tables to the cache file so every process of the build reads the same ones.\n */\nconst runPurgePipelineOnce = async (\n intlayerConfig: IntlayerConfig,\n configOptions: GetConfigurationOptions | undefined,\n dictionaries: Dictionary[] | undefined,\n fieldRenameMapFilePath: string\n): Promise<void> => {\n const appLogger = getAppLogger(intlayerConfig);\n\n const intlayerBabel = loadIntlayerBabel(intlayerConfig);\n\n if (!intlayerBabel) {\n // Not verbose: the build asked for purge / minify and is silently getting\n // neither, which is exactly the kind of thing a default build must report.\n appLogger(\n [\n 'Dictionary purge and minification are',\n colorize('disabled', ANSIColors.GREY_DARK),\n 'because',\n colorize('@intlayer/babel', ANSIColors.GREY_LIGHT),\n 'is missing or too old to expose the purge pipeline — install it in',\n 'this project to enable them.',\n ],\n { level: 'warn' }\n );\n return;\n }\n\n const {\n getPurgePluginOptions,\n runIntlayerPurgePipeline,\n serializeFieldRenameMap,\n } = intlayerBabel;\n\n // `runOnce` discards whatever this callback throws, so a pipeline failure\n // would otherwise leave the build with no dictionaries rewritten and no\n // explanation of why.\n let fieldRenameMap: FieldRenameMapByDictionaryKey;\n\n try {\n // The pipeline reports what it purged and minified through the intlayer\n // logger, matching the Vite build output.\n const pruneContext = runIntlayerPurgePipeline(\n getPurgePluginOptions({ configOptions, dictionaries })\n );\n\n fieldRenameMap = serializeFieldRenameMap(pruneContext);\n } catch (pipelineError) {\n appLogger(\n [\n 'Dictionary purge and minification',\n colorize('failed', ANSIColors.RED),\n '— the compiled dictionaries are left untouched.',\n pipelineError instanceof Error\n ? `(${pipelineError.message})`\n : String(pipelineError),\n ],\n { level: 'error' }\n );\n return;\n }\n\n await mkdir(dirname(fieldRenameMapFilePath), { recursive: true });\n await writeFile(\n fieldRenameMapFilePath,\n JSON.stringify(fieldRenameMap),\n 'utf-8'\n );\n};\n\n/** Reads the cached field-rename tables, or `{}` when none were written. */\nconst readFieldRenameMapFile = (\n fieldRenameMapFilePath: string\n): FieldRenameMapByDictionaryKey => {\n try {\n return JSON.parse(\n readFileSync(fieldRenameMapFilePath, 'utf-8')\n ) as FieldRenameMapByDictionaryKey;\n } catch {\n return {};\n }\n};\n\n/** Removes the cached field-rename tables, ignoring a missing file. */\nconst removeFieldRenameMapFile = (fieldRenameMapFilePath: string): void => {\n try {\n rmSync(fieldRenameMapFilePath, { force: true });\n } catch {\n // Nothing to clean up.\n }\n};\n\ntype PrepareSwcOptimizationParams = {\n /** Options forwarded to the intlayer configuration loader. */\n configOptions?: GetConfigurationOptions;\n /** Pre-loaded dictionaries, to avoid a second `getDictionaries` call. */\n dictionaries?: Dictionary[];\n /** Compat-adapter callers declared by the consuming plugin, if any. */\n swcExtraCallers?: SwcExtraCallerConfig[];\n /**\n * Whether the `@intlayer/swc` plugin is installed *and* the resolved Next.js\n * version can load it. False leaves the compiled dictionaries untouched.\n */\n isSwcPluginUsable: boolean;\n};\n\n/**\n * Prepares the parts of the build optimisation the `@intlayer/swc` plugin\n * cannot do itself, and returns the field-rename tables it needs.\n *\n * Removing unused content fields (`build.purge`) and assigning short field\n * aliases (`build.minify`) require reading every component source file and\n * rewriting the compiled dictionary JSON — file I/O and cross-file state that a\n * per-file Wasm transform has no access to. That analysis therefore runs here,\n * in Node, through `@intlayer/babel`; the plugin only receives the resulting\n * tables and rewrites the matching source accesses\n * (`content.title` → `content.a`).\n *\n * The work is coordinated by a sentinel file: Next.js loads `next.config.*` in\n * several processes per build, and running the minify pass twice would derive\n * the aliases from the already-renamed dictionaries. Processes that lose the\n * race read the tables the winner cached.\n *\n * @returns The field-rename tables, keyed by dictionary key. Empty when the\n * pipeline is disabled, unavailable, or renamed nothing.\n */\nexport const prepareSwcOptimization = async (\n intlayerConfig: IntlayerConfig,\n params: PrepareSwcOptimizationParams\n): Promise<FieldRenameMapByDictionaryKey> => {\n const { configOptions, dictionaries, swcExtraCallers, isSwcPluginUsable } =\n params;\n\n const fieldRenameMapFilePath = getCacheFilePath(\n intlayerConfig,\n FIELD_RENAME_MAP_FILE_NAME\n );\n\n // A stale file from an earlier build would rename source accesses that the\n // dictionaries no longer match.\n if (\n !getIsPurgePipelineEnabled(\n intlayerConfig,\n swcExtraCallers,\n isSwcPluginUsable\n )\n ) {\n removeFieldRenameMapFile(fieldRenameMapFilePath);\n return {};\n }\n\n await runOnce(\n getCacheFilePath(intlayerConfig, PURGE_SENTINEL_FILE_NAME),\n () =>\n runPurgePipelineOnce(\n intlayerConfig,\n configOptions,\n dictionaries,\n fieldRenameMapFilePath\n ),\n { cacheTimeoutMs: PURGE_CACHE_TIMEOUT_MS }\n );\n\n return readFieldRenameMapFile(fieldRenameMapFilePath);\n};\n"],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let _intlayer_config_utils = require("@intlayer/config/utils");
3
+
4
+ //#region src/server/swcPluginCompatibility.ts
5
+ /**
6
+ * Oldest Next.js release whose bundled SWC can load the `@intlayer/swc` Wasm
7
+ * plugin.
8
+ *
9
+ * A Wasm plugin only loads in a host that speaks its `swc_ecma_ast` schema.
10
+ * Next.js 16.1.0 is the first release built on SWC's forward-compatible plugin
11
+ * ABI — the AST travels as self-describing CBOR instead of rkyv, so a plugin
12
+ * compiled against an older `swc_core` keeps loading in newer hosts. Every
13
+ * earlier release uses the rkyv ABI, which requires an exact schema match and
14
+ * rejects the plugin outright, failing the build with `failed to invoke
15
+ * plugin`.
16
+ *
17
+ * Verified by driving `transformSync` on the shipped `@next/swc` binaries with
18
+ * the plugin attached:
19
+ *
20
+ * | Next.js | swc_ecma_ast | plugin loads |
21
+ * | ------- | ------------ | ------------ |
22
+ * | 14.2.x | 0.112.7 | no |
23
+ * | 15.5.x | 14.0.0 | no |
24
+ * | 16.0.x | 16.0.0 | no |
25
+ * | 16.1.x | 19.0.0 | yes |
26
+ * | 16.2.x | 20.0.1 | yes |
27
+ * | 16.3.x | 25.0.0 | yes |
28
+ *
29
+ * Keep in sync with the `swc_core` pin in
30
+ * `packages/@intlayer/swc/Cargo.toml`: the plugin must stay on the
31
+ * `swc_ecma_ast` version this Next.js release ships, never a newer one, or the
32
+ * floor moves up with it.
33
+ */
34
+ const MINIMUM_SWC_PLUGIN_NEXT_VERSION = "16.1.0";
35
+ /**
36
+ * Whether `nextVersion` bundles an SWC able to load the `@intlayer/swc` Wasm
37
+ * plugin.
38
+ *
39
+ * Pre-releases of a supported version (`16.1.0-canary.4`, `16.3.0-preview.5`)
40
+ * count as supported: they track the release they lead to, and opting into a
41
+ * canary already means opting into its churn.
42
+ *
43
+ * @param nextVersion - Version string read from the project's `next/package.json`.
44
+ * @returns `true` when the plugin can be registered in `experimental.swcPlugins`.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * getIsSwcPluginSupported('16.3.0'); // true
49
+ * getIsSwcPluginSupported('15.5.18'); // false
50
+ * ```
51
+ */
52
+ const getIsSwcPluginSupported = (nextVersion) => (0, _intlayer_config_utils.compareVersions)(nextVersion, "≥", MINIMUM_SWC_PLUGIN_NEXT_VERSION);
53
+
54
+ //#endregion
55
+ exports.MINIMUM_SWC_PLUGIN_NEXT_VERSION = MINIMUM_SWC_PLUGIN_NEXT_VERSION;
56
+ exports.getIsSwcPluginSupported = getIsSwcPluginSupported;
57
+ //# sourceMappingURL=swcPluginCompatibility.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swcPluginCompatibility.cjs","names":[],"sources":["../../../src/server/swcPluginCompatibility.ts"],"sourcesContent":["import { compareVersions } from '@intlayer/config/utils';\n\n/**\n * Oldest Next.js release whose bundled SWC can load the `@intlayer/swc` Wasm\n * plugin.\n *\n * A Wasm plugin only loads in a host that speaks its `swc_ecma_ast` schema.\n * Next.js 16.1.0 is the first release built on SWC's forward-compatible plugin\n * ABI — the AST travels as self-describing CBOR instead of rkyv, so a plugin\n * compiled against an older `swc_core` keeps loading in newer hosts. Every\n * earlier release uses the rkyv ABI, which requires an exact schema match and\n * rejects the plugin outright, failing the build with `failed to invoke\n * plugin`.\n *\n * Verified by driving `transformSync` on the shipped `@next/swc` binaries with\n * the plugin attached:\n *\n * | Next.js | swc_ecma_ast | plugin loads |\n * | ------- | ------------ | ------------ |\n * | 14.2.x | 0.112.7 | no |\n * | 15.5.x | 14.0.0 | no |\n * | 16.0.x | 16.0.0 | no |\n * | 16.1.x | 19.0.0 | yes |\n * | 16.2.x | 20.0.1 | yes |\n * | 16.3.x | 25.0.0 | yes |\n *\n * Keep in sync with the `swc_core` pin in\n * `packages/@intlayer/swc/Cargo.toml`: the plugin must stay on the\n * `swc_ecma_ast` version this Next.js release ships, never a newer one, or the\n * floor moves up with it.\n */\nexport const MINIMUM_SWC_PLUGIN_NEXT_VERSION = '16.1.0';\n\n/**\n * Whether `nextVersion` bundles an SWC able to load the `@intlayer/swc` Wasm\n * plugin.\n *\n * Pre-releases of a supported version (`16.1.0-canary.4`, `16.3.0-preview.5`)\n * count as supported: they track the release they lead to, and opting into a\n * canary already means opting into its churn.\n *\n * @param nextVersion - Version string read from the project's `next/package.json`.\n * @returns `true` when the plugin can be registered in `experimental.swcPlugins`.\n *\n * @example\n * ```ts\n * getIsSwcPluginSupported('16.3.0'); // true\n * getIsSwcPluginSupported('15.5.18'); // false\n * ```\n */\nexport const getIsSwcPluginSupported = (nextVersion: string): boolean =>\n compareVersions(nextVersion, '≥', MINIMUM_SWC_PLUGIN_NEXT_VERSION);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,kCAAkC;;;;;;;;;;;;;;;;;;AAmB/C,MAAa,2BAA2B,4DACtB,aAAa,KAAK,+BAA+B"}