next-i18next
Version:
The easiest way to translate your NextJs apps.
1 lines • 14.5 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","names":["NextResponse"],"sources":["../../../src/appRouter/config.ts","../../../src/appRouter/proxy/languageDetector.ts","../../../src/appRouter/proxy/index.ts"],"sourcesContent":["import type { I18nConfig, NormalizedConfig } from './types'\n\nexport function defineConfig(config: I18nConfig): I18nConfig {\n return config\n}\n\nexport function normalizeConfig(userConfig: I18nConfig): NormalizedConfig {\n // Support legacy format: { i18n: { defaultLocale, locales } }\n const supportedLngs = userConfig.supportedLngs ??\n userConfig.i18n?.locales?.filter((l: string) => l !== 'default') ??\n ['en']\n const fallbackLng = userConfig.fallbackLng ??\n userConfig.i18n?.defaultLocale ??\n supportedLngs[0]\n\n if (!fallbackLng) {\n throw new Error('next-i18next: fallbackLng (or i18n.defaultLocale) is required')\n }\n if (supportedLngs.length === 0) {\n throw new Error('next-i18next: supportedLngs (or i18n.locales) must contain at least one language')\n }\n\n const defaultNS = userConfig.defaultNS ?? 'common'\n\n return {\n supportedLngs,\n fallbackLng,\n defaultNS,\n ns: userConfig.ns ?? [defaultNS],\n localeInPath: userConfig.localeInPath ?? true,\n hideDefaultLocale: userConfig.hideDefaultLocale ?? false,\n localePath: userConfig.localePath ?? '/locales',\n localeStructure: userConfig.localeStructure ?? '{{lng}}/{{ns}}',\n localeExtension: userConfig.localeExtension ?? 'json',\n cookieName: userConfig.cookieName ?? 'i18next',\n headerName: userConfig.headerName ?? 'x-i18next-current-language',\n cookieMaxAge: userConfig.cookieMaxAge ?? 365 * 24 * 60 * 60,\n ignoredPaths: userConfig.ignoredPaths ?? ['/api', '/_next', '/static'],\n basePath: userConfig.basePath,\n resources: userConfig.resources,\n resourceLoader: userConfig.resourceLoader,\n use: userConfig.use ?? [],\n i18nextOptions: (userConfig.i18nextOptions ?? {}) as Record<string, any>,\n nonExplicitSupportedLngs: userConfig.nonExplicitSupportedLngs ?? false,\n // Preserve legacy fields\n i18n: userConfig.i18n,\n serializeConfig: userConfig.serializeConfig,\n reloadOnPrerender: userConfig.reloadOnPrerender,\n }\n}\n","/**\n * Edge-safe Accept-Language parser and language matcher.\n * No external dependencies — runs in Edge Runtime, Node.js, and browser.\n */\n\nexport function parseAcceptLanguage(header: string | null | undefined): string[] {\n if (!header) return []\n return header\n .split(',')\n .map(part => {\n const [lang, qPart] = part.trim().split(';')\n const q = qPart?.trim().startsWith('q=')\n ? parseFloat(qPart.trim().slice(2))\n : 1.0\n return { lang: lang.trim(), q: isNaN(q) ? 0 : q }\n })\n .filter(item => item.lang && item.q > 0)\n .sort((a, b) => b.q - a.q)\n .map(item => item.lang)\n}\n\n/**\n * Find a matching supported language for a given code, respecting nonExplicitSupportedLngs.\n *\n * Matching order (mirrors i18next's LanguageUtils.getBestMatchFromCodes):\n * 1. Exact match (case-insensitive)\n * 2. Preferred prefix → supported base (e.g. preferred 'en-US' matches supported 'en')\n * 3. If nonExplicitSupportedLngs: preferred base → supported region\n * (e.g. preferred 'en' matches supported 'en-US')\n */\nexport function findSupportedMatch(\n code: string,\n supportedLanguages: readonly string[],\n nonExplicitSupportedLngs: boolean,\n): string | undefined {\n const lower = code.toLowerCase()\n\n // 1. Exact match (case-insensitive)\n const exact = supportedLanguages.find(l => l.toLowerCase() === lower)\n if (exact) return exact\n\n // 2. Preferred prefix → supported base (e.g. 'en-US' → 'en')\n const prefix = lower.split('-')[0]\n const partial = supportedLanguages.find(l => l.toLowerCase() === prefix)\n if (partial) return partial\n\n // 3. Reverse match: preferred base → supported region (e.g. 'en' → 'en-US')\n if (nonExplicitSupportedLngs) {\n const reverse = supportedLanguages.find(\n l => l.toLowerCase().split('-')[0] === prefix\n )\n if (reverse) return reverse\n }\n\n return undefined\n}\n\nexport function matchLanguage(\n acceptLanguages: string[],\n supportedLanguages: readonly string[],\n defaultLanguage: string,\n nonExplicitSupportedLngs = false,\n): string {\n for (const preferred of acceptLanguages) {\n const match = findSupportedMatch(preferred, supportedLanguages, nonExplicitSupportedLngs)\n if (match) return match\n }\n return defaultLanguage\n}\n","import { NextRequest, NextResponse } from 'next/server'\nimport type { I18nConfig } from '../types'\nimport { normalizeConfig } from '../config'\nimport { parseAcceptLanguage, matchLanguage, findSupportedMatch } from './languageDetector'\n\n// Re-export config utilities for Edge-safe usage (no react-i18next dependency)\nexport { defineConfig, normalizeConfig } from '../config'\nexport type { I18nConfig, NormalizedConfig, ResourceLoader } from '../types'\n\nfunction findLocaleInPath(\n pathname: string,\n supportedLngs: readonly string[],\n nonExplicitSupportedLngs: boolean,\n): string | undefined {\n // Extract the first path segment\n const match = pathname.match(/^\\/([^/]+)/)\n if (!match) return undefined\n return findSupportedMatch(match[1], supportedLngs, nonExplicitSupportedLngs)\n}\n\nexport function createProxy(userConfig: I18nConfig) {\n const config = normalizeConfig(userConfig)\n const nonExplicit = config.nonExplicitSupportedLngs\n // Normalize basePath: ensure leading slash, strip trailing slash\n const basePath = config.basePath\n ? ('/' + config.basePath.replace(/^\\/+/, '').replace(/\\/+$/, ''))\n : undefined\n\n return function middleware(req: NextRequest): NextResponse {\n const { pathname, search } = req.nextUrl\n\n // When basePath is set, only handle requests under that prefix\n if (basePath) {\n if (pathname !== basePath && !pathname.startsWith(basePath + '/')) {\n return NextResponse.next()\n }\n }\n\n // Skip ignored paths\n for (const ignored of config.ignoredPaths) {\n if (pathname.startsWith(ignored)) {\n return NextResponse.next()\n }\n }\n\n // Skip common static file extensions\n if (/\\.(ico|png|jpg|jpeg|svg|gif|webp|css|js|map|woff2?|ttf|eot)$/.test(pathname)) {\n return NextResponse.next()\n }\n\n // Detect language from cookie, then Accept-Language header, then default\n let lng: string | undefined\n const cookieValue = req.cookies.get(config.cookieName)?.value\n if (cookieValue) {\n lng = matchLanguage([cookieValue], config.supportedLngs, config.fallbackLng, nonExplicit)\n }\n if (!lng) {\n lng = matchLanguage(\n parseAcceptLanguage(req.headers.get('Accept-Language')),\n config.supportedLngs,\n config.fallbackLng,\n nonExplicit,\n )\n }\n if (!lng) {\n lng = config.fallbackLng\n }\n\n // For locale-in-path detection, strip basePath prefix so we look at the right segment\n const pathForLocale = basePath ? pathname.slice(basePath.length) || '/' : pathname\n const lngInPath = findLocaleInPath(pathForLocale, config.supportedLngs, nonExplicit)\n\n if (config.localeInPath) {\n const prefix = basePath ?? ''\n const pathAfterBase = basePath ? pathname.slice(basePath.length) : pathname\n\n // hideDefaultLocale: redirect explicit default-locale paths to the clean URL\n if (config.hideDefaultLocale && lngInPath === config.fallbackLng) {\n const pathWithoutLocale = pathAfterBase.replace(/^\\/[^/]+/, '') || '/'\n const redirectUrl = new URL(`${prefix}${pathWithoutLocale}${search}`, req.url)\n const response = NextResponse.redirect(redirectUrl)\n response.cookies.set(config.cookieName, config.fallbackLng, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n return response\n }\n\n // Set custom header for server components to read\n const headers = new Headers(req.headers)\n headers.set(config.headerName, lngInPath || lng)\n\n // Redirect if no locale in path\n if (!lngInPath) {\n if (config.hideDefaultLocale) {\n // Rewrite internally to the default-locale path, keeping the clean URL\n const rewriteUrl = new URL(`${prefix}/${config.fallbackLng}${pathAfterBase}${search}`, req.url)\n headers.set(config.headerName, config.fallbackLng)\n const response = NextResponse.rewrite(rewriteUrl, { request: { headers } })\n response.cookies.set(config.cookieName, config.fallbackLng, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n return response\n }\n\n const redirectUrl = new URL(`${prefix}/${lng}${pathAfterBase}${search}`, req.url)\n const response = NextResponse.redirect(redirectUrl)\n response.cookies.set(config.cookieName, lng, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n return response\n }\n\n // Persist language from referer URL into cookie\n const response = NextResponse.next({ request: { headers } })\n if (req.headers.has('referer')) {\n const refererUrl = new URL(req.headers.get('referer')!)\n const refererPathForLocale = basePath\n ? refererUrl.pathname.slice(basePath.length) || '/'\n : refererUrl.pathname\n const lngInReferer = findLocaleInPath(refererPathForLocale, config.supportedLngs, nonExplicit)\n if (lngInReferer) {\n response.cookies.set(config.cookieName, lngInReferer, {\n path: '/',\n maxAge: config.cookieMaxAge,\n sameSite: 'lax',\n })\n }\n }\n\n return response\n } else {\n // No-locale-path mode: don't redirect, just set the header\n const headers = new Headers(req.headers)\n headers.set(config.headerName, lng)\n\n const response = NextResponse.next({ request: { headers } })\n return response\n }\n }\n}\n\n/**\n * Backwards-compatible alias for createProxy.\n * Use `createProxy` for new projects with Next.js 16+ `proxy.ts`.\n */\nexport const createMiddleware = createProxy\n"],"mappings":"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"}