next-intlayer
Version:
Simplify internationalization i18n in Next.js with context providers, hooks, locale detection, and multilingual content integration.
1 lines • 20 kB
Source Map (JSON)
{"version":3,"file":"intlayerProxy.cjs","names":["configuration","DefaultValues","localeDetector","basePath","NextResponse"],"sources":["../../../src/proxy/intlayerProxy.ts"],"sourcesContent":["import configuration from '@intlayer/config/built';\nimport { DefaultValues } from '@intlayer/config/client';\nimport { getLocaleFromStorage, setLocaleInStorage } from '@intlayer/core';\nimport type { Locale } from '@intlayer/types';\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 { internationalization, routing } = configuration ?? {};\nconst { locales, defaultLocale } = internationalization ?? {};\nconst { basePath, mode } = routing ?? {};\n\n// Note: cookie names are resolved inside LocaleStorage based on configuration\n\n// Derived flags from routing.mode\nconst effectiveMode = mode ?? DefaultValues.Routing.ROUTING_MODE;\nconst noPrefix =\n effectiveMode === 'no-prefix' || effectiveMode === 'search-params';\nconst prefixDefault = effectiveMode === 'prefix-all';\n\n/**\n * Detects if the request is a prefetch request from Next.js.\n *\n * Next.js prefetch requests can be identified by several headers:\n * - purpose: 'prefetch' (standard prefetch header)\n * - next-router-prefetch: '1' (Next.js router prefetch)\n * - next-url: present (Next.js internal navigation)\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 const nextUrl = request.headers.get('next-url');\n const xNextjsData = request.headers.get('x-nextjs-data');\n\n return (\n purpose === 'prefetch' ||\n nextRouterPrefetch === '1' ||\n !!nextUrl ||\n !!xNextjsData\n );\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 (effectiveMode !== 'search-params') return search;\n\n const params = new URLSearchParams(search ?? '');\n\n params.set('locale', locale);\n\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 */\nexport const intlayerProxy = (\n request: NextRequest,\n _event?: NextFetchEvent,\n _response?: NextResponse\n): NextResponse => {\n const pathname = request.nextUrl.pathname;\n\n const localLocale = getLocalLocale(request);\n\n if (\n noPrefix // If the application is configured not to use locale prefixes in URLs\n ) {\n return handleNoPrefix(request, localLocale, pathname);\n }\n\n const pathLocale = getPathLocale(pathname);\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 getLocaleFromStorage({\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 const locale = localLocale ?? defaultLocale;\n\n // If user typed /fr/about but mode is no-prefix,\n // we REDIRECT to /about (clean URL)\n if (pathLocale) {\n const pathWithoutLocale = pathname.slice(`/${pathLocale}`.length) || '/';\n const search = appendLocaleSearchIfNeeded(\n request.nextUrl.search,\n pathLocale\n );\n return redirectUrl(request, `${pathWithoutLocale}${search}`);\n }\n\n // Handle search-params redirect if locale is missing from URL\n if (effectiveMode === 'search-params') {\n const currentParam = request.nextUrl.searchParams.get('locale');\n if (currentParam !== locale) {\n const search = appendLocaleSearchIfNeeded(request.nextUrl.search, locale);\n return redirectUrl(request, `${pathname}${search}`);\n }\n }\n\n // INTERNAL REWRITE\n // We rewrite to the clean pathname.\n // If they have a [locale] folder, rewriteUrl (above) will handle adding it back.\n return rewriteUrl(request, pathname, locale);\n};\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.find(\n (locale) => pathname.startsWith(`/${locale}/`) || 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 (\n !pathLocale // If the URL does not contain a locale prefix\n ) {\n const isPrefetch = isPrefetchRequest(request);\n\n if (isPrefetch && !DEFAULT_DETECT_LOCALE_ON_PREFETCH_NO_PREFIX) {\n return handleMissingPathLocale(request, defaultLocale, pathname);\n }\n\n return handleMissingPathLocale(request, localLocale, pathname);\n }\n\n // If the URL contains a locale prefix\n return handleExistingPathLocale(request, localLocale, 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)) {\n locale = defaultLocale;\n }\n\n // Determine if we should redirect or rewrite\n // If we are in 'prefix-all', we MUST redirect / -> /en/\n // If we are in 'prefix-no-default' and locale is NOT default, redirect / -> /fr/\n const shouldRedirect = prefixDefault || locale !== defaultLocale;\n\n if (shouldRedirect) {\n const newPath = constructPath(\n locale,\n pathname,\n basePath,\n appendLocaleSearchIfNeeded(request.nextUrl.search, locale)\n );\n return redirectUrl(request, newPath);\n }\n\n // --- THE FIX FOR / 404 ---\n // If we are at the root (or any path) and it's the default locale\n // (or we are in no-prefix mode), rewrite to the actual physical pathname.\n return rewriteUrl(request, pathname, locale);\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 */\n/**\n * Handles requests where the locale exists in the URL pathname.\n */\nconst handleExistingPathLocale = (\n request: NextRequest,\n localLocale: Locale | undefined,\n pathLocale: Locale,\n pathname: string\n): NextResponse => {\n // 1. If cookie locale differs from path locale, redirect to the cookie locale\n // (Standard Intlayer behavior)\n if (localLocale && localLocale !== pathLocale) {\n const newPath = handleCookieLocaleMismatch(\n request,\n pathname,\n pathLocale,\n localLocale,\n basePath\n );\n return redirectUrl(request, newPath);\n }\n\n // 2. Handle the rewrite logic\n return handleDefaultLocaleRedirect(request, pathLocale, pathname);\n};\n\n/**\n * The key fix for 404s without [locale] folders\n */\nconst handleDefaultLocaleRedirect = (\n request: NextRequest,\n pathLocale: Locale,\n pathname: string\n): NextResponse => {\n // Determine if we need to remove the prefix for the default locale\n const isDefaultAndNoPrefix = !prefixDefault && pathLocale === defaultLocale;\n\n if (isDefaultAndNoPrefix) {\n const pathWithoutLocale = pathname.slice(`/${pathLocale}`.length) || '/';\n // ... (rest of your existing search param logic)\n return redirectUrl(request, `${basePath}${pathWithoutLocale}`);\n }\n\n // --- THE FIX ---\n // If the path contains a locale (like /fr) but we DON'T have a [locale] folder,\n // we rewrite the path to its \"clean\" version internally.\n\n // Strip the /fr prefix from the pathname for the internal rewrite\n const internalPathname = pathname.slice(`/${pathLocale}`.length) || '/';\n\n const searchWithLocale = appendLocaleSearchIfNeeded(\n request.nextUrl.search,\n pathLocale\n );\n\n const rewritePath = searchWithLocale\n ? `${internalPathname}${searchWithLocale}`\n : internalPathname;\n\n // Internal rewrite: Next.js sees / (or /path), user sees /fr/path\n return rewriteUrl(request, rewritePath, 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 */\nconst handleCookieLocaleMismatch = (\n request: NextRequest,\n pathname: string,\n pathLocale: Locale,\n localLocale: Locale,\n basePath: string\n): string => {\n // Replace the pathLocale in the pathname with the localLocale\n const newPath = pathname.replace(`/${pathLocale}`, `/${localLocale}`);\n\n return constructPath(\n localLocale,\n newPath,\n basePath,\n appendLocaleSearchIfNeeded(request.nextUrl.search, localLocale)\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 const normalizedPath = path.startsWith('/') ? path : `/${path}`;\n let finalPath = normalizedPath;\n\n // If we are in a mode that doesn't want prefixes in the URL\n if (effectiveMode === 'no-prefix' || effectiveMode === 'search-params') {\n // Strip the locale from the path if it exists\n // This allows /fr/about to be treated as /about internally\n for (const loc of locales) {\n if (\n normalizedPath.startsWith(`/${loc}/`) ||\n normalizedPath === `/${loc}`\n ) {\n finalPath = normalizedPath.slice(`/${loc}`.length) || '/';\n break;\n }\n }\n } else {\n // Prefix modes: ensure the locale IS there\n finalPath = normalizedPath.startsWith(`/${locale}`)\n ? normalizedPath\n : `/${locale}${normalizedPath}`;\n }\n\n const cleanBasePath = basePath.replace(/\\/$/, '');\n const result = `${cleanBasePath}${finalPath}`;\n\n return search\n ? `${result}${search.startsWith('?') ? '' : '?'}${search}`\n : result;\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 url = request.nextUrl.clone();\n const pathname = newPath.split('?')[0];\n\n // We determine if we should internally prefix based on the routing mode.\n // If user has [locale] folder, 'prefix-all' or 'prefix-no-default' usually works.\n // If user does NOT have [locale] folder, they usually use 'no-prefix'.\n\n const shouldInternallyPrefix =\n effectiveMode !== 'no-prefix' && effectiveMode !== 'search-params';\n\n if (shouldInternallyPrefix) {\n // If the path doesn't already have the locale, we add it for internal routing\n if (!getPathLocale(pathname)) {\n url.pathname = `/${locale}${pathname === '/' ? '' : pathname}`;\n } else {\n url.pathname = pathname;\n }\n } else {\n // For no-prefix or search-params, we ensure the internal path is CLEAN\n const pathLocale = getPathLocale(pathname);\n if (pathLocale) {\n url.pathname = pathname.slice(`/${pathLocale}`.length) || '/';\n } else {\n url.pathname = pathname;\n }\n }\n\n const response = NextResponse.rewrite(url);\n\n // CRITICAL: Set the locale in a header.\n // If the [locale] folder is missing, the Intlayer server-component\n // will read this header as a fallback to prevent \"Missing Tags\"\n setLocaleInStorage(locale, {\n setHeader: (name: string, value: string) => {\n response.headers.set(name, value);\n },\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 * @returns - The redirect response.\n */\nconst redirectUrl = (request: NextRequest, newPath: string): NextResponse => {\n // Ensure we preserve the original search params if they were present and not explicitly included in newPath\n const search = request.nextUrl.search;\n const pathWithSearch =\n search && !newPath.includes('?') ? `${newPath}${search}` : newPath;\n\n return NextResponse.redirect(new URL(pathWithSearch, request.url));\n};\n"],"mappings":"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"}