@fluenti/next 0.4.0-rc.0 → 0.4.0-rc.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * @module @fluenti/next/middleware\n *\n * Built-in i18n middleware for Next.js App Router.\n *\n * Uses `x-fluenti-locale` request header to pass locale from middleware to\n * server components — avoids `Set-Cookie` on every request (CDN-friendly).\n *\n * Cookie is only used to remember user preference (set by LocaleSwitcher).\n *\n * @example Minimal — locales/sourceLocale/cookieName auto-read from fluenti.config.ts\n * ```ts\n * // src/middleware.ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({ NextResponse })\n *\n * export const config = {\n * matcher: ['/((?!_next|api|favicon).*)'],\n * }\n * ```\n *\n * @example With app/[locale]/ directory structure (rewriteDefaultLocale)\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({ NextResponse, rewriteDefaultLocale: true })\n * ```\n *\n * @example Composing with Clerk\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { clerkMiddleware } from '@clerk/nextjs/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * const i18nMiddleware = createI18nMiddleware({ NextResponse })\n *\n * export default clerkMiddleware(async (auth, req) => {\n * await auth.protect()\n * return i18nMiddleware(req)\n * })\n * ```\n */\n\n// Auto-generated config values are resolved at Next.js build time via withFluenti's\n// webpack/Turbopack alias: @fluenti/next/i18n-config → .fluenti/i18n-config.js\n// This import is kept external (not bundled by tsup) so the alias can take effect.\nimport {\n locales as _configLocales,\n sourceLocale as _configSourceLocale,\n cookieName as _configCookieName,\n} from '@fluenti/next/i18n-config'\n\n/** Header name used to pass resolved locale from middleware to RSC */\nexport const LOCALE_HEADER = 'x-fluenti-locale'\n\nexport interface I18nMiddlewareConfig {\n /** Available locales. If omitted, reads from `fluenti.config.ts`. */\n locales?: string[]\n /** Source/default locale. If omitted, reads from `fluenti.config.ts`. */\n sourceLocale?: string\n /** Cookie name for reading user preference (default: 'locale') */\n cookieName?: string\n /**\n * Locale prefix strategy:\n * - `'always'`: all locales get a URL prefix (e.g. `/en/about`, `/fr/about`)\n * - `'as-needed'`: source locale has no prefix, others do (e.g. `/about`, `/fr/about`)\n *\n * Default: `'as-needed'`\n */\n localePrefix?: 'always' | 'as-needed'\n /**\n * When true, bare paths for the source locale are internally rewritten to include\n * the source locale prefix. Required when using `app/[locale]/` directory structure\n * with `localePrefix: 'as-needed'`.\n *\n * Also changes the handling of explicit source-locale URLs (e.g. `/en/about`):\n * instead of rewriting to `/about`, they are **redirected** to `/about` so the\n * browser follows the canonical URL, which is then rewritten internally.\n *\n * Example: `GET /about` → internally rewritten to `/en/about` (URL stays `/about`)\n * Example: `GET /en/about` → 302 redirect → `/about` → rewritten to `/en/about`\n *\n * Default: `false`\n */\n rewriteDefaultLocale?: boolean\n /**\n * When true, the detected locale is written to a `Set-Cookie` response header so the\n * preference is persisted across requests (useful when locale is detected from\n * `Accept-Language` rather than an existing cookie).\n *\n * Disabled by default to keep responses CDN-cacheable.\n *\n * The cookie is only written when the locale was **detected** (not read from the URL\n * path), and only when it differs from the existing cookie value.\n */\n setCookie?: boolean\n /**\n * Custom locale detection function. Called when no locale is present in the URL path.\n * Return a locale string to override the default cookie → Accept-Language → default chain.\n * Return `undefined` to fall through to built-in detection.\n *\n * Useful for JWT-based preferences, subdomain detection, or any custom logic.\n *\n * @example Subdomain detection\n * ```ts\n * detectLocale: (req) => {\n * const host = req.headers.get('host') ?? ''\n * if (host.startsWith('fr.')) return 'fr'\n * }\n * ```\n *\n * @example JWT claim\n * ```ts\n * detectLocale: (req) => {\n * const token = req.cookies.get('auth')?.value\n * return token ? parseLocaleFromJwt(token) : undefined\n * }\n * ```\n */\n detectLocale?: (req: NextRequest) => string | undefined\n}\n\ntype NextRequest = {\n nextUrl: { pathname: string; search: string; basePath?: string }\n url: string\n cookies: { get(name: string): { value: string } | undefined }\n headers: Headers\n}\n\ntype NextResponseStatic = {\n redirect(url: URL): NextResponseInstance\n rewrite(url: URL, init?: Record<string, unknown>): NextResponseInstance\n next(init?: Record<string, unknown>): NextResponseInstance\n}\n\ntype NextResponseInstance = {\n headers: { set(name: string, value: string): void }\n}\n\n/**\n * Create an i18n middleware function for Next.js.\n *\n * Requires `NextResponse` to be passed in because the middleware module runs\n * in Next.js Edge Runtime where `require('next/server')` is not available.\n *\n * @example\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({ NextResponse })\n * ```\n */\nexport function createI18nMiddleware(config: I18nMiddlewareConfig & { NextResponse: NextResponseStatic }) {\n const { NextResponse } = config\n const resolvedLocales: string[] = config.locales ?? _configLocales\n const resolvedSourceLocale: string = config.sourceLocale ?? _configSourceLocale\n const cookieName = config.cookieName ?? _configCookieName\n const localePrefix = config.localePrefix ?? 'as-needed'\n const rewriteDefaultLocale = config.rewriteDefaultLocale ?? false\n const setCookieEnabled = config.setCookie ?? false\n\n return function i18nMiddleware(request: NextRequest) {\n const locales = resolvedLocales\n const sourceLocale = resolvedSourceLocale\n const { pathname, search } = request.nextUrl\n const basePath = request.nextUrl.basePath ?? ''\n\n // Extract locale from URL path (case-insensitive — /ZH-CN → zh-CN)\n // Note: request.nextUrl.pathname already strips basePath (Next.js behavior)\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n const pathLocale = findLocale(firstSegment, locales)\n\n // Determine the active locale\n let locale: string\n\n if (pathLocale) {\n locale = pathLocale\n } else {\n // No locale in path — try custom detection first, then cookie → Accept-Language → default\n const custom = config.detectLocale?.(request)\n locale =\n custom !== undefined && findLocale(custom, locales) !== null\n ? findLocale(custom, locales)!\n : detectLocale(request, locales, sourceLocale, cookieName)\n }\n\n // Build new request headers preserving originals (auth headers, etc.)\n const requestHeaders = new Headers(request.headers)\n requestHeaders.set(LOCALE_HEADER, locale)\n\n // Case 1: No locale in path → redirect to /{locale}{path}\n // In 'always' mode: redirect all bare paths (including source locale)\n // In 'as-needed' mode: only redirect non-source locales\n if (!pathLocale && (localePrefix === 'always' || locale !== sourceLocale)) {\n const redirectUrl = new URL(\n `${basePath}/${locale}${pathname}${search}`,\n request.url,\n )\n const response = NextResponse.redirect(redirectUrl)\n response.headers.set(LOCALE_HEADER, locale)\n maybeSetCookie(response, request, locale, cookieName, setCookieEnabled, pathLocale)\n return response\n }\n\n // Case 2: as-needed + no prefix + source locale + rewriteDefaultLocale\n // → internally rewrite to /{sourceLocale}{path} so Next.js matches [locale] segment\n // Use `pathname` directly (preserves trailing slash for trailingSlash:true compat)\n if (!pathLocale && locale === sourceLocale && rewriteDefaultLocale) {\n const rewriteUrl = new URL(\n `${basePath}/${sourceLocale}${pathname}${search}`,\n request.url,\n )\n const response = NextResponse.rewrite(rewriteUrl, {\n request: { headers: requestHeaders },\n })\n response.headers.set(LOCALE_HEADER, locale)\n maybeSetCookie(response, request, locale, cookieName, setCookieEnabled, pathLocale)\n return response\n }\n\n // Case 3: as-needed mode, source locale has explicit prefix → strip it\n // rewriteDefaultLocale=false: rewrite to /about (flat app/about/ structure)\n // rewriteDefaultLocale=true: redirect to /about (browser re-requests, Case 2 rewrites)\n if (localePrefix === 'as-needed' && pathLocale === sourceLocale) {\n const pathWithoutLocale = ('/' + segments.slice(2).join('/')).replace(/\\/+/g, '/')\n if (rewriteDefaultLocale) {\n const redirectUrl = new URL(\n `${basePath}${pathWithoutLocale}${search}`,\n request.url,\n )\n const response = NextResponse.redirect(redirectUrl)\n response.headers.set(LOCALE_HEADER, locale)\n return response\n }\n const rewriteUrl = new URL(\n `${basePath}${pathWithoutLocale}${search}`,\n request.url,\n )\n const response = NextResponse.rewrite(rewriteUrl, {\n request: { headers: requestHeaders },\n })\n response.headers.set(LOCALE_HEADER, locale)\n return response\n }\n\n // Case 4: No prefix + source locale (rewriteDefaultLocale=false) → pass through\n // Case 5: Non-source locale with correct prefix → pass through\n const response = NextResponse.next({\n request: { headers: requestHeaders },\n })\n response.headers.set(LOCALE_HEADER, locale)\n maybeSetCookie(response, request, locale, cookieName, setCookieEnabled, pathLocale)\n\n return response\n }\n}\n\n/**\n * Case-insensitive locale lookup. Returns the canonical form from the locales array,\n * or null if not found. Handles BCP 47 case variance (e.g. zh-cn → zh-CN).\n */\nfunction findLocale(candidate: string, locales: string[]): string | null {\n if (!candidate) return null\n const lower = candidate.toLowerCase()\n return locales.find(l => l.toLowerCase() === lower) ?? null\n}\n\n/**\n * Conditionally add a Set-Cookie header to persist the detected locale.\n * Only writes when setCookie is enabled, locale was not from URL path,\n * and cookie value differs from detected locale.\n */\nfunction maybeSetCookie(\n response: NextResponseInstance,\n request: NextRequest,\n locale: string,\n cookieName: string,\n setCookie: boolean,\n pathLocale: string | null,\n): void {\n if (!setCookie || pathLocale) return\n if (request.cookies.get(cookieName)?.value === locale) return\n response.headers.set(\n 'set-cookie',\n `${cookieName}=${encodeURIComponent(locale)};path=/;max-age=31536000;samesite=lax`,\n )\n}\n\n/**\n * Detect locale from request: cookie → Accept-Language → default.\n * All comparisons are case-insensitive (BCP 47).\n */\nfunction detectLocale(\n request: NextRequest,\n locales: string[],\n defaultLocale: string,\n cookieName: string,\n): string {\n // 1. Cookie (user preference) — case-insensitive\n const cookieLocale = request.cookies.get(cookieName)?.value\n if (cookieLocale) {\n const found = findLocale(cookieLocale, locales)\n if (found) return found\n }\n\n // 2. Accept-Language header — case-insensitive\n const acceptLang = request.headers.get('accept-language')\n if (acceptLang) {\n for (const part of acceptLang.split(',')) {\n const lang = part.split(';')[0]!.trim()\n const exact = findLocale(lang, locales)\n if (exact) return exact\n const prefix = lang.split('-')[0]!.toLowerCase()\n const match = locales.find(l => {\n const ll = l.toLowerCase()\n return ll === prefix || ll.startsWith(prefix + '-')\n })\n if (match) return match\n }\n }\n\n // 3. Default\n return defaultLocale\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * @module @fluenti/next/middleware\n *\n * Built-in i18n middleware for Next.js App Router.\n *\n * Uses `x-fluenti-locale` request header to pass locale from middleware to\n * server components — avoids `Set-Cookie` on every request (CDN-friendly).\n *\n * @example Minimal\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({ NextResponse })\n * ```\n *\n * @example With pathnames + alternateLinks + domains\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({\n * NextResponse,\n * rewriteDefaultLocale: true,\n * alternateLinks: true,\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * },\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * ],\n * })\n * ```\n */\n\nimport {\n locales as _configLocales,\n sourceLocale as _configSourceLocale,\n cookieName as _configCookieName,\n} from '@fluenti/next/i18n-config'\n\n/** Header name used to pass resolved locale from middleware to RSC */\nexport const LOCALE_HEADER = 'x-fluenti-locale'\n\nexport interface CookieOptions {\n /** Cookie domain (e.g. '.example.com' for cross-subdomain) */\n domain?: string\n /** Secure flag (default: auto-detect from request URL) */\n secure?: boolean\n /** SameSite attribute (default: 'lax') */\n sameSite?: 'lax' | 'strict' | 'none'\n /** Max age in seconds (default: 31536000 = 1 year) */\n maxAge?: number\n /** Cookie path (default: '/') */\n path?: string\n}\n\nexport interface DomainConfig {\n /** Domain hostname (e.g. 'fr.example.com') */\n domain: string\n /** Default locale for this domain */\n defaultLocale: string\n /** Optional subset of locales available on this domain */\n locales?: string[]\n}\n\nexport interface AlternateLinkEntry {\n href: string\n hreflang: string\n}\n\nexport interface I18nMiddlewareConfig {\n /** Available locales. If omitted, reads from `fluenti.config.ts`. */\n locales?: string[]\n /** Source/default locale. If omitted, reads from `fluenti.config.ts`. */\n sourceLocale?: string\n /** Cookie name for reading user preference (default: 'locale') */\n cookieName?: string\n /**\n * Locale prefix strategy:\n * - `'always'`: all locales get a URL prefix\n * - `'as-needed'`: source locale has no prefix, others do\n * - `'never'`: no locale prefix in URLs; locale determined by detection chain\n *\n * Default: `'as-needed'`\n */\n localePrefix?: 'always' | 'as-needed' | 'never'\n /**\n * When true, bare paths are internally rewritten to include the locale prefix.\n * Required when using `app/[locale]/` directory structure.\n *\n * Default: `false`\n */\n rewriteDefaultLocale?: boolean\n /**\n * When true, detected locale is persisted in a cookie.\n * Disabled by default to keep responses CDN-cacheable.\n */\n setCookie?: boolean\n /** Fine-grained cookie configuration for multi-domain / secure deployments. */\n cookieOptions?: CookieOptions\n /**\n * Set to false to disable automatic locale detection.\n * Bare paths will always use `sourceLocale` instead of detecting from cookie / Accept-Language.\n *\n * Default: `true`\n */\n localeDetection?: boolean\n /**\n * Custom locale detection function. Called when no locale is present in the URL path.\n * Return a locale string to override the default chain, or `undefined` to fall through.\n */\n detectLocale?: (req: NextRequest) => string | undefined\n /**\n * Domain-based locale routing. Each domain maps to a default locale.\n * Domain matching is checked before cookie/Accept-Language detection.\n *\n * @example\n * ```ts\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * { domain: 'example.co.jp', defaultLocale: 'ja' },\n * ]\n * ```\n */\n domains?: DomainConfig[]\n /**\n * Map internal paths to localized paths per locale.\n * Supports dynamic segments: `[param]` and `[...slug]`.\n *\n * @example\n * ```ts\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * }\n * ```\n */\n pathnames?: Record<string, Record<string, string>>\n /**\n * When true, adds `Link` response headers with `rel=\"alternate\"` hreflang\n * and `rel=\"canonical\"` for SEO.\n *\n * Default: `false`\n */\n alternateLinks?: boolean\n /**\n * Custom function to build alternate link entries. Overrides default `alternateLinks` behavior.\n * Return an array of `{ href, hreflang }` entries.\n */\n getAlternateLinks?: (context: {\n pathname: string\n locale: string\n locales: string[]\n origin: string\n basePath: string\n }) => AlternateLinkEntry[]\n /**\n * Called before the middleware returns a response.\n * Modify headers, cookies, or return a replacement response.\n */\n beforeResponse?: (context: {\n response: NextResponseInstance\n request: NextRequest\n locale: string\n type: 'redirect' | 'rewrite' | 'next'\n }) => NextResponseInstance | void | undefined\n}\n\ntype NextRequest = {\n nextUrl: { pathname: string; search: string; basePath?: string }\n url: string\n cookies: { get(name: string): { value: string } | undefined }\n headers: Headers\n}\n\ntype NextResponseStatic<R extends NextResponseInstance = NextResponseInstance> = {\n redirect(url: URL): R\n rewrite(url: URL, init?: Record<string, unknown>): R\n next(init?: Record<string, unknown>): R\n}\n\ntype NextResponseInstance = {\n headers: { set(name: string, value: string): void }\n}\n\n/**\n * Create an i18n middleware function for Next.js.\n */\nexport function createI18nMiddleware<R extends NextResponseInstance = NextResponseInstance>(\n config: I18nMiddlewareConfig & { NextResponse: NextResponseStatic<R> },\n) {\n const { NextResponse } = config\n const resolvedLocales: string[] = config.locales ?? _configLocales\n const resolvedSourceLocale: string = config.sourceLocale ?? _configSourceLocale\n const cookieName = config.cookieName ?? _configCookieName\n const localePrefix = config.localePrefix ?? 'as-needed'\n const rewriteDefaultLocale = config.rewriteDefaultLocale ?? false\n const setCookieEnabled = config.setCookie ?? false\n const cookieOpts: CookieOptions = config.cookieOptions ?? {}\n const localeDetectionEnabled = config.localeDetection ?? true\n const alternateLinksEnabled = config.alternateLinks ?? false\n const pathnamesMap = config.pathnames\n const domainsConfig = config.domains\n const beforeResponse = config.beforeResponse\n\n function finalizeResponse(\n response: R,\n request: NextRequest,\n locale: string,\n type: 'redirect' | 'rewrite' | 'next',\n pathLocale: string | null,\n ): R {\n response.headers.set(LOCALE_HEADER, locale)\n if (setCookieEnabled) {\n maybeSetCookie(response, request, locale, cookieName, pathLocale, cookieOpts)\n }\n if (alternateLinksEnabled || config.getAlternateLinks) {\n const linkHeader = config.getAlternateLinks\n ? buildCustomAlternateLinks(config.getAlternateLinks, request, locale, resolvedLocales)\n : buildAlternateLinks(request, resolvedLocales, resolvedSourceLocale, localePrefix, request.nextUrl.basePath ?? '', pathnamesMap)\n response.headers.set('Link', linkHeader)\n }\n if (beforeResponse) {\n const replacement = beforeResponse({ response, request, locale, type })\n if (replacement) return replacement as R\n }\n return response\n }\n\n return function i18nMiddleware(request: NextRequest): R {\n const locales = resolvedLocales\n const sourceLocale = resolvedSourceLocale\n const { pathname, search } = request.nextUrl\n const basePath = request.nextUrl.basePath ?? ''\n\n // Extract locale from URL path ('never' mode skips this)\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n const pathLocale = localePrefix === 'never' ? null : findLocale(firstSegment, locales)\n\n // Determine the active locale\n let locale: string\n\n if (pathLocale) {\n locale = pathLocale\n } else if (!localeDetectionEnabled) {\n locale = sourceLocale\n } else {\n // Detection chain: custom → domains → cookie → Accept-Language → default\n const custom = config.detectLocale?.(request)\n if (custom !== undefined && findLocale(custom, locales) !== null) {\n locale = findLocale(custom, locales)!\n } else if (domainsConfig) {\n const domainLocale = detectFromDomain(request, domainsConfig, locales)\n locale = domainLocale ?? detectLocale(request, locales, sourceLocale, cookieName)\n } else {\n locale = detectLocale(request, locales, sourceLocale, cookieName)\n }\n }\n\n // Build request headers\n const requestHeaders = new Headers(request.headers)\n requestHeaders.set(LOCALE_HEADER, locale)\n\n // ── 'never' mode ──────────────────────────────────────────────────────\n if (localePrefix === 'never') {\n if (rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${locale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', null,\n )\n }\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', null,\n )\n }\n\n // ── Pathnames mapping ─────────────────────────────────────────────────\n if (pathnamesMap && pathLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n const internalPath = resolveInternalPath(pathWithoutLocale, locale, pathnamesMap)\n\n if (internalPath) {\n const rewriteUrl = new URL(`${basePath}/${locale}${internalPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n const localizedPath = resolveLocalizedPath(pathWithoutLocale, locale, pathnamesMap)\n if (localizedPath && localizedPath !== pathWithoutLocale) {\n const redirectUrl = new URL(`${basePath}/${locale}${localizedPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n }\n\n // ── Case 1: No locale in path → redirect ──────────────────────────────\n if (!pathLocale && (localePrefix === 'always' || locale !== sourceLocale)) {\n // If pathnames configured, redirect to localized path\n let targetPath = pathname\n if (pathnamesMap) {\n const localized = resolveLocalizedPath(pathname, locale, pathnamesMap)\n if (localized) targetPath = localized\n }\n const redirectUrl = new URL(`${basePath}/${locale}${targetPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n\n // ── Case 2: as-needed + source locale + rewriteDefaultLocale ──────────\n if (!pathLocale && locale === sourceLocale && rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${sourceLocale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 3: as-needed, source locale has explicit prefix → strip ──────\n if (localePrefix === 'as-needed' && pathLocale === sourceLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n if (rewriteDefaultLocale) {\n const redirectUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n const rewriteUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 4/5: pass through ────────────────────────────────────────────\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', pathLocale,\n )\n }\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction findLocale(candidate: string, locales: string[]): string | null {\n if (!candidate) return null\n const lower = candidate.toLowerCase()\n return locales.find(l => l.toLowerCase() === lower) ?? null\n}\n\nfunction normalizeSlashes(path: string): string {\n return path.replace(/\\/+/g, '/') || '/'\n}\n\nfunction maybeSetCookie(\n response: NextResponseInstance,\n request: NextRequest,\n locale: string,\n cookieName: string,\n pathLocale: string | null,\n opts: CookieOptions,\n): void {\n if (pathLocale) return\n if (request.cookies.get(cookieName)?.value === locale) return\n\n const parts = [`${cookieName}=${encodeURIComponent(locale)}`]\n parts.push(`path=${opts.path ?? '/'}`)\n parts.push(`max-age=${opts.maxAge ?? 31536000}`)\n parts.push(`samesite=${opts.sameSite ?? 'lax'}`)\n if (opts.domain) parts.push(`domain=${opts.domain}`)\n if (opts.secure ?? request.url.startsWith('https')) parts.push('secure')\n response.headers.set('set-cookie', parts.join(';'))\n}\n\nfunction detectFromDomain(\n request: NextRequest,\n domains: DomainConfig[],\n locales: string[],\n): string | null {\n const host = request.headers.get('host')?.split(':')[0] ?? ''\n for (const d of domains) {\n if (host === d.domain || host.endsWith('.' + d.domain)) {\n const found = findLocale(d.defaultLocale, d.locales ?? locales)\n if (found) return found\n }\n }\n return null\n}\n\nfunction detectLocale(\n request: NextRequest,\n locales: string[],\n defaultLocale: string,\n cookieName: string,\n): string {\n const cookieLocale = request.cookies.get(cookieName)?.value\n if (cookieLocale) {\n const found = findLocale(cookieLocale, locales)\n if (found) return found\n }\n\n const acceptLang = request.headers.get('accept-language')\n if (acceptLang) {\n for (const part of acceptLang.split(',')) {\n const lang = part.split(';')[0]!.trim()\n const exact = findLocale(lang, locales)\n if (exact) return exact\n const prefix = lang.split('-')[0]!.toLowerCase()\n const match = locales.find(l => {\n const ll = l.toLowerCase()\n return ll === prefix || ll.startsWith(prefix + '-')\n })\n if (match) return match\n }\n }\n\n return defaultLocale\n}\n\nfunction stripLocalePrefix(pathname: string, locales: string[]): string {\n const segments = pathname.split('/')\n const first = segments[1] ?? ''\n if (findLocale(first, locales)) {\n return normalizeSlashes('/' + segments.slice(2).join('/'))\n }\n return pathname\n}\n\n/** Match a pathname against a pattern with [param] and [...slug] segments. */\nfunction matchPattern(pattern: string, pathname: string): Record<string, string> | null {\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = pathname.split('/').filter(Boolean)\n\n const params: Record<string, string> = {}\n\n for (let i = 0; i < patternParts.length; i++) {\n const pp = patternParts[i]!\n if (pp.startsWith('[...') && pp.endsWith(']')) {\n // Catch-all: consume the rest\n const key = pp.slice(4, -1)\n params[key] = pathParts.slice(i).join('/')\n return params\n }\n if (pp.startsWith('[') && pp.endsWith(']')) {\n // Dynamic segment\n if (i >= pathParts.length) return null\n params[pp.slice(1, -1)] = pathParts[i]!\n continue\n }\n // Static segment\n if (i >= pathParts.length || pp !== pathParts[i]) return null\n }\n\n if (patternParts.length !== pathParts.length) return null\n return params\n}\n\n/** Substitute params into a pattern. */\nfunction substituteParams(pattern: string, params: Record<string, string>): string {\n return pattern.replace(/\\[\\.\\.\\.(\\w+)\\]|\\[(\\w+)\\]/g, (_, catchAll, param) => {\n const key = catchAll ?? param\n return params[key] ?? ''\n })\n}\n\nfunction resolveInternalPath(\n localizedPath: string,\n locale: string,\n pathnames: Record<string, Record<string, string>>,\n): string | null {\n for (const [internal, mapping] of Object.entries(pathnames)) {\n const localized = mapping[locale]\n if (!localized) continue\n // Exact match\n if (localized === localizedPath) return internal\n // Pattern match\n const params = matchPattern(localized, localizedPath)\n if (params) return substituteParams(internal, params)\n }\n return null\n}\n\nfunction resolveLocalizedPath(\n internalPath: string,\n locale: string,\n pathnames: Record<string, Record<string, string>>,\n): string | null {\n for (const [internal, mapping] of Object.entries(pathnames)) {\n const localized = mapping[locale]\n if (!localized) continue\n // Exact match\n if (internal === internalPath) return localized\n // Pattern match\n const params = matchPattern(internal, internalPath)\n if (params) return substituteParams(localized, params)\n }\n return null\n}\n\nfunction buildAlternateLinks(\n request: NextRequest,\n locales: string[],\n sourceLocale: string,\n localePrefix: 'always' | 'as-needed' | 'never',\n basePath: string,\n pathnames?: Record<string, Record<string, string>>,\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n\n const links = locales.map(loc => {\n let localePath: string\n if (localePrefix === 'never') {\n localePath = cleanPath\n } else if (localePrefix === 'as-needed' && loc === sourceLocale) {\n localePath = cleanPath\n } else {\n localePath = `/${loc}${cleanPath}`\n }\n if (pathnames) {\n const mapped = resolveLocalizedPath(cleanPath, loc, pathnames)\n if (mapped) {\n localePath = localePrefix === 'never' || (localePrefix === 'as-needed' && loc === sourceLocale)\n ? mapped\n : `/${loc}${mapped}`\n }\n }\n return `<${origin}${basePath}${localePath}>; rel=\"alternate\"; hreflang=\"${loc}\"`\n })\n\n // x-default\n const defaultPath = localePrefix === 'always' ? `/${sourceLocale}${cleanPath}` : cleanPath\n links.push(`<${origin}${basePath}${defaultPath}>; rel=\"alternate\"; hreflang=\"x-default\"`)\n\n return links.join(', ')\n}\n\nfunction buildCustomAlternateLinks(\n getAlternateLinks: NonNullable<I18nMiddlewareConfig['getAlternateLinks']>,\n request: NextRequest,\n locale: string,\n locales: string[],\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n const basePath = request.nextUrl.basePath ?? ''\n const entries = getAlternateLinks({ pathname: cleanPath, locale, locales, origin, basePath })\n return entries.map(e => `<${e.href}>; rel=\"alternate\"; hreflang=\"${e.hreflang}\"`).join(', ')\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@fluenti/react`),t=require(`@fluenti/next/i18n-config`),n=require(`next/navigation`);function r(e,t,n){let r=n?.sourceLocale??`en`,i=n?.localePrefix??`as-needed`,a=e.split(`/`),o=a[1]??``,s=(n?.locales?n.locales.includes(o):/^[a-z]{2}(-[A-Za-z]{2,})?$/.test(o))?`/`+a.slice(2).join(`/`):e;return i!==`always`&&t===r?s||`/`:`/${t}${s}`}function i(i){let a=(0,n.useRouter)(),o=(0,n.usePathname)(),{locale:s,setLocale:c,getLocales:l}=(0,e.useI18n)(),u=l(),d=i?.sourceLocale??u[0]??`en`,f=i?.cookieName??t.cookieName,p=i?.localePrefix??`as-needed`;return{switchLocale:e=>{if(!u.includes(e)){typeof process<`u`&&process.env.NODE_ENV!==`production`&&console.warn(`[fluenti] switchLocale: invalid locale "${e}"`);return}document.cookie=`${f}=${encodeURIComponent(e)};path=/;max-age=31536000;samesite=lax`,c(e);let t=r(o,e,{sourceLocale:d,locales:u,localePrefix:p});a.push(t),a.refresh()},currentLocale:s,locales:u,sourceLocale:d}}exports.getLocalePath=r,exports.useLocaleSwitcher=i;
2
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@fluenti/react`),t=require(`@fluenti/next/i18n-config`),n=require(`next/navigation`),r=require(`react`);function i(e){let{locales:t,sourceLocale:n,localePrefix:i=`as-needed`,pathnames:a}=e;function o(e,t){let r=e;if(a){let n=a[e]?.[t];n&&(r=n)}return i===`never`||i===`as-needed`&&t===n?r:`/${t}${r}`}function s(e){if(i===`never`)return e;let n=e.split(`/`),r=(n[1]??``).toLowerCase();return t.some(e=>e.toLowerCase()===r)?`/`+n.slice(2).join(`/`)||`/`:e}let c=(0,r.forwardRef)(function({href:e,locale:t,...i},a){let s=require(`next/link`).default,c=n;try{let{useI18n:e}=require(`@fluenti/react`);c=e().locale}catch{}return(0,r.createElement)(s,{ref:a,href:o(String(e),String(t??c)),...i})});c.displayName=`I18nLink`;function l(){let{useRouter:e}=require(`next/navigation`),{useI18n:t}=require(`@fluenti/react`),n=e(),{locale:r}=t();return{push(e,t){n.push(o(String(e),String(t?.locale??r)))},replace(e,t){n.replace(o(String(e),String(t?.locale??r)))},back:()=>n.back(),forward:()=>n.forward(),refresh:()=>n.refresh(),prefetch:e=>n.prefetch(e)}}function u(e,t){let{redirect:r}=require(`next/navigation`);return r(o(String(e),String(t??n)))}function d(){let{usePathname:e}=require(`next/navigation`);return s(e())}function f(e,t){return o(String(e),String(t))}return{Link:c,useRouter:l,redirect:u,usePathname:d,getPathname:f}}function a(e,t,n){let r=n?.sourceLocale??`en`,i=n?.localePrefix??`as-needed`,a=e.split(`/`),o=a[1]??``,s=(n?.locales?n.locales.includes(o):/^[a-z]{2}(-[A-Za-z]{2,})?$/.test(o))?`/`+a.slice(2).join(`/`):e;return i===`never`||i!==`always`&&t===r?s||`/`:`/${t}${s}`}function o(r){let i=(0,n.useRouter)(),o=(0,n.usePathname)(),{locale:s,setLocale:c,getLocales:l}=(0,e.useI18n)(),u=l(),d=r?.sourceLocale??u[0]??`en`,f=r?.cookieName??t.cookieName,p=r?.localePrefix??`as-needed`;return{switchLocale:e=>{if(!u.includes(e)){typeof process<`u`&&process.env.NODE_ENV!==`production`&&console.warn(`[fluenti] switchLocale: invalid locale "${e}"`);return}document.cookie=`${f}=${encodeURIComponent(e)};path=/;max-age=31536000;samesite=lax`,c(e);let t=a(o,e,{sourceLocale:d,locales:u,localePrefix:p});i.push(t),i.refresh()},currentLocale:s,locales:u,sourceLocale:d}}exports.createNavigation=i,exports.getLocalePath=a,exports.useLocaleSwitcher=o;
3
3
  //# sourceMappingURL=navigation.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.cjs","names":[],"sources":["../src/navigation.ts"],"sourcesContent":["'use client'\n\n/**\n * @module @fluenti/next/navigation\n *\n * Navigation utilities for locale-aware routing in Next.js App Router.\n *\n * @example\n * ```tsx\n * import { useLocaleSwitcher } from '@fluenti/next/navigation'\n *\n * function LanguagePicker() {\n * const { switchLocale, currentLocale, locales } = useLocaleSwitcher()\n * return (\n * <select value={currentLocale} onChange={(e) => switchLocale(e.target.value)}>\n * {locales.map((l) => <option key={l} value={l}>{l}</option>)}\n * </select>\n * )\n * }\n * ```\n */\nimport { useRouter, usePathname } from 'next/navigation'\nimport { useI18n } from '@fluenti/react'\nimport { cookieName as _configCookieName } from '@fluenti/next/i18n-config'\n\nexport interface GetLocalePathOptions {\n /** Source/default locale (no prefix in as-needed mode) */\n sourceLocale?: string\n /**\n * Known locale codes (e.g. ['en', 'fr', 'ja']).\n * When provided, the existing prefix is only stripped when it's an actual locale —\n * preventing false matches on generic 2-letter path segments like /my or /us.\n */\n locales?: string[]\n /**\n * Locale prefix strategy. Matches the middleware `localePrefix` setting.\n * - `'as-needed'` (default): source locale has no prefix (`/about` for en, `/fr/about` for fr)\n * - `'always'`: all locales get a prefix (`/en/about`, `/fr/about`)\n */\n localePrefix?: 'always' | 'as-needed'\n}\n\n/**\n * Get the locale-prefixed path for a given pathname and locale.\n *\n * Pure function — works on both server and client.\n *\n * @example\n * ```ts\n * getLocalePath('/about', 'fr') // → '/fr/about'\n * getLocalePath('/about', 'en') // → '/about' (source locale, no prefix)\n * getLocalePath('/fr/about', 'en') // → '/about'\n * getLocalePath('/fr/about', 'ja') // → '/ja/about'\n * getLocalePath('/about', 'en', { localePrefix: 'always' }) // → '/en/about'\n * ```\n */\nexport function getLocalePath(\n pathname: string,\n locale: string,\n options?: GetLocalePathOptions,\n): string {\n const sourceLocale = options?.sourceLocale ?? 'en'\n const localePrefix = options?.localePrefix ?? 'as-needed'\n\n // Strip existing locale prefix if present\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n\n // Check if the first segment is a locale prefix.\n // If a locales list is provided, do an exact membership check to avoid false positives\n // on generic 2-letter path segments (e.g. /my/page or /us/pricing).\n // Otherwise fall back to the heuristic regex.\n const hasLocalePrefix = options?.locales\n ? options.locales.includes(firstSegment)\n : /^[a-z]{2}(-[A-Za-z]{2,})?$/.test(firstSegment)\n const pathWithoutLocale = hasLocalePrefix\n ? '/' + segments.slice(2).join('/')\n : pathname\n\n // In 'as-needed' mode, source locale gets no prefix\n if (localePrefix !== 'always' && locale === sourceLocale) {\n return pathWithoutLocale || '/'\n }\n\n return `/${locale}${pathWithoutLocale}`\n}\n\n/**\n * Hook for switching locales in Next.js App Router.\n *\n * Sets a cookie to remember user preference, navigates to the new locale path,\n * and triggers a server component refresh.\n */\nexport function useLocaleSwitcher(options?: {\n /** Override the source/default locale instead of inferring from locales[0]. */\n sourceLocale?: string\n /**\n * Cookie name used by the middleware for locale preference.\n * Defaults to the value from `fluenti.config.ts` (auto-read at build time).\n * Must match the middleware `cookieName` option.\n */\n cookieName?: string\n /** Locale prefix strategy — must match the middleware `localePrefix` option. */\n localePrefix?: 'always' | 'as-needed'\n}) {\n const router = useRouter()\n const pathname = usePathname()\n const { locale, setLocale, getLocales } = useI18n()\n\n // Read locales from I18nProvider context (works on client without fs)\n const locales = getLocales()\n const sourceLocale = options?.sourceLocale ?? locales[0] ?? 'en'\n const cookieName = options?.cookieName ?? _configCookieName\n const localePrefix = options?.localePrefix ?? 'as-needed'\n\n const switchLocale = (newLocale: string) => {\n // Validate locale against known locales to prevent cookie injection\n if (!locales.includes(newLocale)) {\n if (typeof process !== 'undefined' && process.env?.['NODE_ENV'] !== 'production') {\n console.warn(`[fluenti] switchLocale: invalid locale \"${newLocale}\"`)\n }\n return\n }\n // 1. Set cookie to remember preference (uses configured cookie name)\n document.cookie = `${cookieName}=${encodeURIComponent(newLocale)};path=/;max-age=31536000;samesite=lax`\n // 2. Update React context\n setLocale(newLocale)\n // 3. Navigate to new locale path\n const newPath = getLocalePath(pathname, newLocale, { sourceLocale, locales, localePrefix })\n router.push(newPath)\n // 4. Refresh server components\n router.refresh()\n }\n\n return {\n switchLocale,\n currentLocale: locale,\n locales,\n sourceLocale,\n }\n}\n"],"mappings":"oLAwDA,SAAgB,EACd,EACA,EACA,EACQ,CACR,IAAM,EAAe,GAAS,cAAgB,KACxC,EAAe,GAAS,cAAgB,YAGxC,EAAW,EAAS,MAAM,IAAI,CAC9B,EAAe,EAAS,IAAM,GAS9B,GAHkB,GAAS,QAC7B,EAAQ,QAAQ,SAAS,EAAa,CACtC,6BAA6B,KAAK,EAAa,EAE/C,IAAM,EAAS,MAAM,EAAE,CAAC,KAAK,IAAI,CACjC,EAOJ,OAJI,IAAiB,UAAY,IAAW,EACnC,GAAqB,IAGvB,IAAI,IAAS,IAStB,SAAgB,EAAkB,EAW/B,CACD,IAAM,GAAA,EAAA,EAAA,YAAoB,CACpB,GAAA,EAAA,EAAA,cAAwB,CACxB,CAAE,SAAQ,YAAW,eAAA,EAAA,EAAA,UAAwB,CAG7C,EAAU,GAAY,CACtB,EAAe,GAAS,cAAgB,EAAQ,IAAM,KACtD,EAAa,GAAS,YAAc,EAAA,WACpC,EAAe,GAAS,cAAgB,YAqB9C,MAAO,CACL,aApBoB,GAAsB,CAE1C,GAAI,CAAC,EAAQ,SAAS,EAAU,CAAE,CAC5B,OAAO,QAAY,KAAA,QAAA,IAAA,WAA6C,cAClE,QAAQ,KAAK,2CAA2C,EAAU,GAAG,CAEvE,OAGF,SAAS,OAAS,GAAG,EAAW,GAAG,mBAAmB,EAAU,CAAC,uCAEjE,EAAU,EAAU,CAEpB,IAAM,EAAU,EAAc,EAAU,EAAW,CAAE,eAAc,UAAS,eAAc,CAAC,CAC3F,EAAO,KAAK,EAAQ,CAEpB,EAAO,SAAS,EAKhB,cAAe,EACf,UACA,eACD"}
1
+ {"version":3,"file":"navigation.cjs","names":[],"sources":["../src/create-navigation.tsx","../src/navigation.ts"],"sourcesContent":["/**\n * @module @fluenti/next/navigation\n *\n * Type-safe navigation factory for Next.js i18n routing.\n *\n * @example\n * ```tsx\n * // src/lib/navigation.ts\n * import { createNavigation } from '@fluenti/next/navigation'\n *\n * export const { Link, useRouter, redirect, usePathname, getPathname } = createNavigation({\n * locales: ['en', 'fr', 'ja'] as const,\n * sourceLocale: 'en',\n * localePrefix: 'as-needed',\n * pathnames: {\n * '/': { fr: '/', ja: '/' },\n * '/about': { fr: '/a-propos', ja: '/about' },\n * '/blog/[slug]': { fr: '/articles/[slug]', ja: '/blog/[slug]' },\n * },\n * })\n * ```\n */\n'use client'\n\nimport { createElement, forwardRef } from 'react'\nimport type { ReactNode } from 'react'\n\n// ── Types ─────────────────────────────────────────────────────────────────\n\nexport interface RoutingConfig<\n L extends string = string,\n P extends string = string,\n> {\n locales: readonly L[]\n sourceLocale: L\n localePrefix?: 'always' | 'as-needed' | 'never'\n pathnames?: Record<P, Partial<Record<L, string>>>\n}\n\ninterface NavigationLinkProps<P extends string = string, L extends string = string> {\n href: P | (string & Record<never, never>)\n locale?: L\n children?: ReactNode\n [key: string]: unknown\n}\n\ninterface TypedRouter<P extends string = string, L extends string = string> {\n push(href: P | (string & Record<never, never>), options?: { locale?: L }): void\n replace(href: P | (string & Record<never, never>), options?: { locale?: L }): void\n back(): void\n forward(): void\n refresh(): void\n prefetch(href: string): void\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────\n\nexport function createNavigation<\n const L extends string,\n const P extends string,\n>(routing: RoutingConfig<L, P>) {\n const { locales, sourceLocale, localePrefix = 'as-needed', pathnames } = routing\n\n function resolvePath(href: string, locale: string): string {\n // Apply pathnames mapping if configured\n let resolved = href\n if (pathnames) {\n const mapped = (pathnames as Record<string, Record<string, string>>)[href]?.[locale]\n if (mapped) resolved = mapped\n }\n\n // Apply locale prefix\n if (localePrefix === 'never') return resolved\n if (localePrefix === 'as-needed' && locale === sourceLocale) return resolved\n return `/${locale}${resolved}`\n }\n\n function stripPrefix(pathname: string): string {\n if (localePrefix === 'never') return pathname\n const segments = pathname.split('/')\n const first = segments[1] ?? ''\n const lower = first.toLowerCase()\n const isLocale = locales.some(l => l.toLowerCase() === lower)\n if (isLocale) return '/' + segments.slice(2).join('/') || '/'\n return pathname\n }\n\n // ── Link component ────────────────────────────────────────────────────\n\n const Link = forwardRef<HTMLAnchorElement, NavigationLinkProps<P, L>>(\n function I18nLink({ href, locale: localeProp, ...rest }, ref) {\n // Dynamic imports to avoid SSR issues — these are resolved at runtime\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const NextLink = require('next/link').default\n let currentLocale = sourceLocale\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useI18n } = require('@fluenti/react')\n const i18n = useI18n()\n currentLocale = i18n.locale\n } catch {\n // Outside I18nProvider, use sourceLocale\n }\n const locale = localeProp ?? currentLocale\n const resolvedHref = resolvePath(String(href), String(locale))\n return createElement(NextLink, { ref, href: resolvedHref, ...rest })\n },\n )\n Link.displayName = 'I18nLink'\n\n // ── useRouter hook ────────────────────────────────────────────────────\n\n function useRouter(): TypedRouter<P, L> {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useRouter: useNextRouter } = require('next/navigation')\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useI18n } = require('@fluenti/react')\n\n const nextRouter = useNextRouter()\n const { locale } = useI18n()\n\n return {\n push(href: P | string, options?: { locale?: L }) {\n nextRouter.push(resolvePath(String(href), String(options?.locale ?? locale)))\n },\n replace(href: P | string, options?: { locale?: L }) {\n nextRouter.replace(resolvePath(String(href), String(options?.locale ?? locale)))\n },\n back: () => nextRouter.back(),\n forward: () => nextRouter.forward(),\n refresh: () => nextRouter.refresh(),\n prefetch: (href: string) => nextRouter.prefetch(href),\n }\n }\n\n // ── redirect function (server-side) ───────────────────────────────────\n\n function redirect(href: P | (string & Record<never, never>), locale?: L): never {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { redirect: nextRedirect } = require('next/navigation') as { redirect: (url: string) => never }\n const resolvedLocale = locale ?? sourceLocale\n return nextRedirect(resolvePath(String(href), String(resolvedLocale)))\n }\n\n // ── usePathname hook ──────────────────────────────────────────────────\n\n function usePathname(): string {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { usePathname: useNextPathname } = require('next/navigation')\n const pathname = useNextPathname()\n return stripPrefix(pathname)\n }\n\n // ── getPathname utility ───────────────────────────────────────────────\n\n function getPathname(href: P | (string & Record<never, never>), locale: L): string {\n return resolvePath(String(href), String(locale))\n }\n\n return { Link, useRouter, redirect, usePathname, getPathname }\n}\n","'use client'\n\n/**\n * @module @fluenti/next/navigation\n *\n * Navigation utilities for locale-aware routing in Next.js App Router.\n *\n * @example\n * ```tsx\n * import { useLocaleSwitcher } from '@fluenti/next/navigation'\n *\n * function LanguagePicker() {\n * const { switchLocale, currentLocale, locales } = useLocaleSwitcher()\n * return (\n * <select value={currentLocale} onChange={(e) => switchLocale(e.target.value)}>\n * {locales.map((l) => <option key={l} value={l}>{l}</option>)}\n * </select>\n * )\n * }\n * ```\n */\nimport { useRouter, usePathname } from 'next/navigation'\nimport { useI18n } from '@fluenti/react'\nimport { cookieName as _configCookieName } from '@fluenti/next/i18n-config'\n\nexport interface GetLocalePathOptions {\n /** Source/default locale (no prefix in as-needed mode) */\n sourceLocale?: string\n /**\n * Known locale codes (e.g. ['en', 'fr', 'ja']).\n * When provided, the existing prefix is only stripped when it's an actual locale —\n * preventing false matches on generic 2-letter path segments like /my or /us.\n */\n locales?: string[]\n /**\n * Locale prefix strategy. Matches the middleware `localePrefix` setting.\n * - `'as-needed'` (default): source locale has no prefix (`/about` for en, `/fr/about` for fr)\n * - `'always'`: all locales get a prefix (`/en/about`, `/fr/about`)\n * - `'never'`: no locale prefix in URLs\n */\n localePrefix?: 'always' | 'as-needed' | 'never'\n}\n\n/**\n * Get the locale-prefixed path for a given pathname and locale.\n *\n * Pure function — works on both server and client.\n *\n * @example\n * ```ts\n * getLocalePath('/about', 'fr') // → '/fr/about'\n * getLocalePath('/about', 'en') // → '/about' (source locale, no prefix)\n * getLocalePath('/fr/about', 'en') // → '/about'\n * getLocalePath('/fr/about', 'ja') // → '/ja/about'\n * getLocalePath('/about', 'en', { localePrefix: 'always' }) // → '/en/about'\n * ```\n */\nexport function getLocalePath(\n pathname: string,\n locale: string,\n options?: GetLocalePathOptions,\n): string {\n const sourceLocale = options?.sourceLocale ?? 'en'\n const localePrefix = options?.localePrefix ?? 'as-needed'\n\n // Strip existing locale prefix if present\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n\n // Check if the first segment is a locale prefix.\n // If a locales list is provided, do an exact membership check to avoid false positives\n // on generic 2-letter path segments (e.g. /my/page or /us/pricing).\n // Otherwise fall back to the heuristic regex.\n const hasLocalePrefix = options?.locales\n ? options.locales.includes(firstSegment)\n : /^[a-z]{2}(-[A-Za-z]{2,})?$/.test(firstSegment)\n const pathWithoutLocale = hasLocalePrefix\n ? '/' + segments.slice(2).join('/')\n : pathname\n\n // 'never' mode: no prefix for any locale\n if (localePrefix === 'never') {\n return pathWithoutLocale || '/'\n }\n\n // In 'as-needed' mode, source locale gets no prefix\n if (localePrefix !== 'always' && locale === sourceLocale) {\n return pathWithoutLocale || '/'\n }\n\n return `/${locale}${pathWithoutLocale}`\n}\n\n/**\n * Hook for switching locales in Next.js App Router.\n *\n * Sets a cookie to remember user preference, navigates to the new locale path,\n * and triggers a server component refresh.\n */\nexport function useLocaleSwitcher(options?: {\n /** Override the source/default locale instead of inferring from locales[0]. */\n sourceLocale?: string\n /**\n * Cookie name used by the middleware for locale preference.\n * Defaults to the value from `fluenti.config.ts` (auto-read at build time).\n * Must match the middleware `cookieName` option.\n */\n cookieName?: string\n /** Locale prefix strategy — must match the middleware `localePrefix` option. */\n localePrefix?: 'always' | 'as-needed' | 'never'\n}) {\n const router = useRouter()\n const pathname = usePathname()\n const { locale, setLocale, getLocales } = useI18n()\n\n // Read locales from I18nProvider context (works on client without fs)\n const locales = getLocales()\n const sourceLocale = options?.sourceLocale ?? locales[0] ?? 'en'\n const cookieName = options?.cookieName ?? _configCookieName\n const localePrefix = options?.localePrefix ?? 'as-needed'\n\n const switchLocale = (newLocale: string) => {\n // Validate locale against known locales to prevent cookie injection\n if (!locales.includes(newLocale)) {\n if (typeof process !== 'undefined' && process.env?.['NODE_ENV'] !== 'production') {\n console.warn(`[fluenti] switchLocale: invalid locale \"${newLocale}\"`)\n }\n return\n }\n // 1. Set cookie to remember preference (uses configured cookie name)\n document.cookie = `${cookieName}=${encodeURIComponent(newLocale)};path=/;max-age=31536000;samesite=lax`\n // 2. Update React context\n setLocale(newLocale)\n // 3. Navigate to new locale path\n const newPath = getLocalePath(pathname, newLocale, { sourceLocale, locales, localePrefix })\n router.push(newPath)\n // 4. Refresh server components\n router.refresh()\n }\n\n return {\n switchLocale,\n currentLocale: locale,\n locales,\n sourceLocale,\n }\n}\n\n// Re-export createNavigation from the same subpath for convenience\nexport { createNavigation } from './create-navigation'\nexport type { RoutingConfig } from './create-navigation'\n"],"mappings":"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"}
@@ -11,8 +11,9 @@ export interface GetLocalePathOptions {
11
11
  * Locale prefix strategy. Matches the middleware `localePrefix` setting.
12
12
  * - `'as-needed'` (default): source locale has no prefix (`/about` for en, `/fr/about` for fr)
13
13
  * - `'always'`: all locales get a prefix (`/en/about`, `/fr/about`)
14
+ * - `'never'`: no locale prefix in URLs
14
15
  */
15
- localePrefix?: 'always' | 'as-needed';
16
+ localePrefix?: 'always' | 'as-needed' | 'never';
16
17
  }
17
18
  /**
18
19
  * Get the locale-prefixed path for a given pathname and locale.
@@ -45,11 +46,13 @@ export declare function useLocaleSwitcher(options?: {
45
46
  */
46
47
  cookieName?: string;
47
48
  /** Locale prefix strategy — must match the middleware `localePrefix` option. */
48
- localePrefix?: 'always' | 'as-needed';
49
+ localePrefix?: 'always' | 'as-needed' | 'never';
49
50
  }): {
50
51
  switchLocale: (newLocale: string) => void;
51
52
  currentLocale: string;
52
53
  locales: string[];
53
54
  sourceLocale: string;
54
55
  };
56
+ export { createNavigation } from './create-navigation';
57
+ export type { RoutingConfig } from './create-navigation';
55
58
  //# sourceMappingURL=navigation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB;;;;OAIG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAA;CACtC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,oBAAoB,GAC7B,MAAM,CAyBR;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE;IAC1C,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,YAAY,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAA;CACtC;8BAWkC,MAAM;;;;EAyBxC"}
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAA;CAChD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,oBAAoB,GAC7B,MAAM,CA8BR;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE;IAC1C,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,YAAY,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAA;CAChD;8BAWkC,MAAM;;;;EAyBxC;AAGD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
@@ -3,13 +3,84 @@
3
3
  import { useI18n as e } from "@fluenti/react";
4
4
  import { cookieName as t } from "@fluenti/next/i18n-config";
5
5
  import { usePathname as n, useRouter as r } from "next/navigation";
6
+ import { createElement as i, forwardRef as a } from "react";
7
+ //#region \0rolldown/runtime.js
8
+ var o = /* @__PURE__ */ ((e) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(e, { get: (e, t) => (typeof require < "u" ? require : e)[t] }) : e)(function(e) {
9
+ if (typeof require < "u") return require.apply(this, arguments);
10
+ throw Error("Calling `require` for \"" + e + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
11
+ });
12
+ //#endregion
13
+ //#region src/create-navigation.tsx
14
+ function s(e) {
15
+ let { locales: t, sourceLocale: n, localePrefix: r = "as-needed", pathnames: s } = e;
16
+ function c(e, t) {
17
+ let i = e;
18
+ if (s) {
19
+ let n = s[e]?.[t];
20
+ n && (i = n);
21
+ }
22
+ return r === "never" || r === "as-needed" && t === n ? i : `/${t}${i}`;
23
+ }
24
+ function l(e) {
25
+ if (r === "never") return e;
26
+ let n = e.split("/"), i = (n[1] ?? "").toLowerCase();
27
+ return t.some((e) => e.toLowerCase() === i) ? "/" + n.slice(2).join("/") || "/" : e;
28
+ }
29
+ let u = a(function({ href: e, locale: t, ...r }, a) {
30
+ let s = o("next/link").default, l = n;
31
+ try {
32
+ let { useI18n: e } = o("@fluenti/react");
33
+ l = e().locale;
34
+ } catch {}
35
+ return i(s, {
36
+ ref: a,
37
+ href: c(String(e), String(t ?? l)),
38
+ ...r
39
+ });
40
+ });
41
+ u.displayName = "I18nLink";
42
+ function d() {
43
+ let { useRouter: e } = o("next/navigation"), { useI18n: t } = o("@fluenti/react"), n = e(), { locale: r } = t();
44
+ return {
45
+ push(e, t) {
46
+ n.push(c(String(e), String(t?.locale ?? r)));
47
+ },
48
+ replace(e, t) {
49
+ n.replace(c(String(e), String(t?.locale ?? r)));
50
+ },
51
+ back: () => n.back(),
52
+ forward: () => n.forward(),
53
+ refresh: () => n.refresh(),
54
+ prefetch: (e) => n.prefetch(e)
55
+ };
56
+ }
57
+ function f(e, t) {
58
+ let { redirect: r } = o("next/navigation");
59
+ return r(c(String(e), String(t ?? n)));
60
+ }
61
+ function p() {
62
+ let { usePathname: e } = o("next/navigation");
63
+ return l(e());
64
+ }
65
+ function m(e, t) {
66
+ return c(String(e), String(t));
67
+ }
68
+ return {
69
+ Link: u,
70
+ useRouter: d,
71
+ redirect: f,
72
+ usePathname: p,
73
+ getPathname: m
74
+ };
75
+ }
76
+ //#endregion
6
77
  //#region src/navigation.ts
7
- function i(e, t, n) {
78
+ function c(e, t, n) {
8
79
  let r = n?.sourceLocale ?? "en", i = n?.localePrefix ?? "as-needed", a = e.split("/"), o = a[1] ?? "", s = (n?.locales ? n.locales.includes(o) : /^[a-z]{2}(-[A-Za-z]{2,})?$/.test(o)) ? "/" + a.slice(2).join("/") : e;
9
- return i !== "always" && t === r ? s || "/" : `/${t}${s}`;
80
+ return i === "never" || i !== "always" && t === r ? s || "/" : `/${t}${s}`;
10
81
  }
11
- function a(a) {
12
- let o = r(), s = n(), { locale: c, setLocale: l, getLocales: u } = e(), d = u(), f = a?.sourceLocale ?? d[0] ?? "en", p = a?.cookieName ?? t, m = a?.localePrefix ?? "as-needed";
82
+ function l(i) {
83
+ let a = r(), o = n(), { locale: s, setLocale: l, getLocales: u } = e(), d = u(), f = i?.sourceLocale ?? d[0] ?? "en", p = i?.cookieName ?? t, m = i?.localePrefix ?? "as-needed";
13
84
  return {
14
85
  switchLocale: (e) => {
15
86
  if (!d.includes(e)) {
@@ -17,19 +88,19 @@ function a(a) {
17
88
  return;
18
89
  }
19
90
  document.cookie = `${p}=${encodeURIComponent(e)};path=/;max-age=31536000;samesite=lax`, l(e);
20
- let t = i(s, e, {
91
+ let t = c(o, e, {
21
92
  sourceLocale: f,
22
93
  locales: d,
23
94
  localePrefix: m
24
95
  });
25
- o.push(t), o.refresh();
96
+ a.push(t), a.refresh();
26
97
  },
27
- currentLocale: c,
98
+ currentLocale: s,
28
99
  locales: d,
29
100
  sourceLocale: f
30
101
  };
31
102
  }
32
103
  //#endregion
33
- export { i as getLocalePath, a as useLocaleSwitcher };
104
+ export { s as createNavigation, c as getLocalePath, l as useLocaleSwitcher };
34
105
 
35
106
  //# sourceMappingURL=navigation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.js","names":[],"sources":["../src/navigation.ts"],"sourcesContent":["'use client'\n\n/**\n * @module @fluenti/next/navigation\n *\n * Navigation utilities for locale-aware routing in Next.js App Router.\n *\n * @example\n * ```tsx\n * import { useLocaleSwitcher } from '@fluenti/next/navigation'\n *\n * function LanguagePicker() {\n * const { switchLocale, currentLocale, locales } = useLocaleSwitcher()\n * return (\n * <select value={currentLocale} onChange={(e) => switchLocale(e.target.value)}>\n * {locales.map((l) => <option key={l} value={l}>{l}</option>)}\n * </select>\n * )\n * }\n * ```\n */\nimport { useRouter, usePathname } from 'next/navigation'\nimport { useI18n } from '@fluenti/react'\nimport { cookieName as _configCookieName } from '@fluenti/next/i18n-config'\n\nexport interface GetLocalePathOptions {\n /** Source/default locale (no prefix in as-needed mode) */\n sourceLocale?: string\n /**\n * Known locale codes (e.g. ['en', 'fr', 'ja']).\n * When provided, the existing prefix is only stripped when it's an actual locale —\n * preventing false matches on generic 2-letter path segments like /my or /us.\n */\n locales?: string[]\n /**\n * Locale prefix strategy. Matches the middleware `localePrefix` setting.\n * - `'as-needed'` (default): source locale has no prefix (`/about` for en, `/fr/about` for fr)\n * - `'always'`: all locales get a prefix (`/en/about`, `/fr/about`)\n */\n localePrefix?: 'always' | 'as-needed'\n}\n\n/**\n * Get the locale-prefixed path for a given pathname and locale.\n *\n * Pure function — works on both server and client.\n *\n * @example\n * ```ts\n * getLocalePath('/about', 'fr') // → '/fr/about'\n * getLocalePath('/about', 'en') // → '/about' (source locale, no prefix)\n * getLocalePath('/fr/about', 'en') // → '/about'\n * getLocalePath('/fr/about', 'ja') // → '/ja/about'\n * getLocalePath('/about', 'en', { localePrefix: 'always' }) // → '/en/about'\n * ```\n */\nexport function getLocalePath(\n pathname: string,\n locale: string,\n options?: GetLocalePathOptions,\n): string {\n const sourceLocale = options?.sourceLocale ?? 'en'\n const localePrefix = options?.localePrefix ?? 'as-needed'\n\n // Strip existing locale prefix if present\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n\n // Check if the first segment is a locale prefix.\n // If a locales list is provided, do an exact membership check to avoid false positives\n // on generic 2-letter path segments (e.g. /my/page or /us/pricing).\n // Otherwise fall back to the heuristic regex.\n const hasLocalePrefix = options?.locales\n ? options.locales.includes(firstSegment)\n : /^[a-z]{2}(-[A-Za-z]{2,})?$/.test(firstSegment)\n const pathWithoutLocale = hasLocalePrefix\n ? '/' + segments.slice(2).join('/')\n : pathname\n\n // In 'as-needed' mode, source locale gets no prefix\n if (localePrefix !== 'always' && locale === sourceLocale) {\n return pathWithoutLocale || '/'\n }\n\n return `/${locale}${pathWithoutLocale}`\n}\n\n/**\n * Hook for switching locales in Next.js App Router.\n *\n * Sets a cookie to remember user preference, navigates to the new locale path,\n * and triggers a server component refresh.\n */\nexport function useLocaleSwitcher(options?: {\n /** Override the source/default locale instead of inferring from locales[0]. */\n sourceLocale?: string\n /**\n * Cookie name used by the middleware for locale preference.\n * Defaults to the value from `fluenti.config.ts` (auto-read at build time).\n * Must match the middleware `cookieName` option.\n */\n cookieName?: string\n /** Locale prefix strategy — must match the middleware `localePrefix` option. */\n localePrefix?: 'always' | 'as-needed'\n}) {\n const router = useRouter()\n const pathname = usePathname()\n const { locale, setLocale, getLocales } = useI18n()\n\n // Read locales from I18nProvider context (works on client without fs)\n const locales = getLocales()\n const sourceLocale = options?.sourceLocale ?? locales[0] ?? 'en'\n const cookieName = options?.cookieName ?? _configCookieName\n const localePrefix = options?.localePrefix ?? 'as-needed'\n\n const switchLocale = (newLocale: string) => {\n // Validate locale against known locales to prevent cookie injection\n if (!locales.includes(newLocale)) {\n if (typeof process !== 'undefined' && process.env?.['NODE_ENV'] !== 'production') {\n console.warn(`[fluenti] switchLocale: invalid locale \"${newLocale}\"`)\n }\n return\n }\n // 1. Set cookie to remember preference (uses configured cookie name)\n document.cookie = `${cookieName}=${encodeURIComponent(newLocale)};path=/;max-age=31536000;samesite=lax`\n // 2. Update React context\n setLocale(newLocale)\n // 3. Navigate to new locale path\n const newPath = getLocalePath(pathname, newLocale, { sourceLocale, locales, localePrefix })\n router.push(newPath)\n // 4. Refresh server components\n router.refresh()\n }\n\n return {\n switchLocale,\n currentLocale: locale,\n locales,\n sourceLocale,\n }\n}\n"],"mappings":";;;;;AAwDA,SAAgB,EACd,GACA,GACA,GACQ;CACR,IAAM,IAAe,GAAS,gBAAgB,MACxC,IAAe,GAAS,gBAAgB,aAGxC,IAAW,EAAS,MAAM,IAAI,EAC9B,IAAe,EAAS,MAAM,IAS9B,KAHkB,GAAS,UAC7B,EAAQ,QAAQ,SAAS,EAAa,GACtC,6BAA6B,KAAK,EAAa,IAE/C,MAAM,EAAS,MAAM,EAAE,CAAC,KAAK,IAAI,GACjC;AAOJ,QAJI,MAAiB,YAAY,MAAW,IACnC,KAAqB,MAGvB,IAAI,IAAS;;AAStB,SAAgB,EAAkB,GAW/B;CACD,IAAM,IAAS,GAAW,EACpB,IAAW,GAAa,EACxB,EAAE,WAAQ,cAAW,kBAAe,GAAS,EAG7C,IAAU,GAAY,EACtB,IAAe,GAAS,gBAAgB,EAAQ,MAAM,MACtD,IAAa,GAAS,cAAc,GACpC,IAAe,GAAS,gBAAgB;AAqB9C,QAAO;EACL,eApBoB,MAAsB;AAE1C,OAAI,CAAC,EAAQ,SAAS,EAAU,EAAE;AAChC,IAAI,OAAO,UAAY,OAAA,QAAA,IAAA,aAA6C,gBAClE,QAAQ,KAAK,2CAA2C,EAAU,GAAG;AAEvE;;AAKF,GAFA,SAAS,SAAS,GAAG,EAAW,GAAG,mBAAmB,EAAU,CAAC,wCAEjE,EAAU,EAAU;GAEpB,IAAM,IAAU,EAAc,GAAU,GAAW;IAAE;IAAc;IAAS;IAAc,CAAC;AAG3F,GAFA,EAAO,KAAK,EAAQ,EAEpB,EAAO,SAAS;;EAKhB,eAAe;EACf;EACA;EACD"}
1
+ {"version":3,"file":"navigation.js","names":[],"sources":["../src/create-navigation.tsx","../src/navigation.ts"],"sourcesContent":["/**\n * @module @fluenti/next/navigation\n *\n * Type-safe navigation factory for Next.js i18n routing.\n *\n * @example\n * ```tsx\n * // src/lib/navigation.ts\n * import { createNavigation } from '@fluenti/next/navigation'\n *\n * export const { Link, useRouter, redirect, usePathname, getPathname } = createNavigation({\n * locales: ['en', 'fr', 'ja'] as const,\n * sourceLocale: 'en',\n * localePrefix: 'as-needed',\n * pathnames: {\n * '/': { fr: '/', ja: '/' },\n * '/about': { fr: '/a-propos', ja: '/about' },\n * '/blog/[slug]': { fr: '/articles/[slug]', ja: '/blog/[slug]' },\n * },\n * })\n * ```\n */\n'use client'\n\nimport { createElement, forwardRef } from 'react'\nimport type { ReactNode } from 'react'\n\n// ── Types ─────────────────────────────────────────────────────────────────\n\nexport interface RoutingConfig<\n L extends string = string,\n P extends string = string,\n> {\n locales: readonly L[]\n sourceLocale: L\n localePrefix?: 'always' | 'as-needed' | 'never'\n pathnames?: Record<P, Partial<Record<L, string>>>\n}\n\ninterface NavigationLinkProps<P extends string = string, L extends string = string> {\n href: P | (string & Record<never, never>)\n locale?: L\n children?: ReactNode\n [key: string]: unknown\n}\n\ninterface TypedRouter<P extends string = string, L extends string = string> {\n push(href: P | (string & Record<never, never>), options?: { locale?: L }): void\n replace(href: P | (string & Record<never, never>), options?: { locale?: L }): void\n back(): void\n forward(): void\n refresh(): void\n prefetch(href: string): void\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────\n\nexport function createNavigation<\n const L extends string,\n const P extends string,\n>(routing: RoutingConfig<L, P>) {\n const { locales, sourceLocale, localePrefix = 'as-needed', pathnames } = routing\n\n function resolvePath(href: string, locale: string): string {\n // Apply pathnames mapping if configured\n let resolved = href\n if (pathnames) {\n const mapped = (pathnames as Record<string, Record<string, string>>)[href]?.[locale]\n if (mapped) resolved = mapped\n }\n\n // Apply locale prefix\n if (localePrefix === 'never') return resolved\n if (localePrefix === 'as-needed' && locale === sourceLocale) return resolved\n return `/${locale}${resolved}`\n }\n\n function stripPrefix(pathname: string): string {\n if (localePrefix === 'never') return pathname\n const segments = pathname.split('/')\n const first = segments[1] ?? ''\n const lower = first.toLowerCase()\n const isLocale = locales.some(l => l.toLowerCase() === lower)\n if (isLocale) return '/' + segments.slice(2).join('/') || '/'\n return pathname\n }\n\n // ── Link component ────────────────────────────────────────────────────\n\n const Link = forwardRef<HTMLAnchorElement, NavigationLinkProps<P, L>>(\n function I18nLink({ href, locale: localeProp, ...rest }, ref) {\n // Dynamic imports to avoid SSR issues — these are resolved at runtime\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const NextLink = require('next/link').default\n let currentLocale = sourceLocale\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useI18n } = require('@fluenti/react')\n const i18n = useI18n()\n currentLocale = i18n.locale\n } catch {\n // Outside I18nProvider, use sourceLocale\n }\n const locale = localeProp ?? currentLocale\n const resolvedHref = resolvePath(String(href), String(locale))\n return createElement(NextLink, { ref, href: resolvedHref, ...rest })\n },\n )\n Link.displayName = 'I18nLink'\n\n // ── useRouter hook ────────────────────────────────────────────────────\n\n function useRouter(): TypedRouter<P, L> {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useRouter: useNextRouter } = require('next/navigation')\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useI18n } = require('@fluenti/react')\n\n const nextRouter = useNextRouter()\n const { locale } = useI18n()\n\n return {\n push(href: P | string, options?: { locale?: L }) {\n nextRouter.push(resolvePath(String(href), String(options?.locale ?? locale)))\n },\n replace(href: P | string, options?: { locale?: L }) {\n nextRouter.replace(resolvePath(String(href), String(options?.locale ?? locale)))\n },\n back: () => nextRouter.back(),\n forward: () => nextRouter.forward(),\n refresh: () => nextRouter.refresh(),\n prefetch: (href: string) => nextRouter.prefetch(href),\n }\n }\n\n // ── redirect function (server-side) ───────────────────────────────────\n\n function redirect(href: P | (string & Record<never, never>), locale?: L): never {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { redirect: nextRedirect } = require('next/navigation') as { redirect: (url: string) => never }\n const resolvedLocale = locale ?? sourceLocale\n return nextRedirect(resolvePath(String(href), String(resolvedLocale)))\n }\n\n // ── usePathname hook ──────────────────────────────────────────────────\n\n function usePathname(): string {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { usePathname: useNextPathname } = require('next/navigation')\n const pathname = useNextPathname()\n return stripPrefix(pathname)\n }\n\n // ── getPathname utility ───────────────────────────────────────────────\n\n function getPathname(href: P | (string & Record<never, never>), locale: L): string {\n return resolvePath(String(href), String(locale))\n }\n\n return { Link, useRouter, redirect, usePathname, getPathname }\n}\n","'use client'\n\n/**\n * @module @fluenti/next/navigation\n *\n * Navigation utilities for locale-aware routing in Next.js App Router.\n *\n * @example\n * ```tsx\n * import { useLocaleSwitcher } from '@fluenti/next/navigation'\n *\n * function LanguagePicker() {\n * const { switchLocale, currentLocale, locales } = useLocaleSwitcher()\n * return (\n * <select value={currentLocale} onChange={(e) => switchLocale(e.target.value)}>\n * {locales.map((l) => <option key={l} value={l}>{l}</option>)}\n * </select>\n * )\n * }\n * ```\n */\nimport { useRouter, usePathname } from 'next/navigation'\nimport { useI18n } from '@fluenti/react'\nimport { cookieName as _configCookieName } from '@fluenti/next/i18n-config'\n\nexport interface GetLocalePathOptions {\n /** Source/default locale (no prefix in as-needed mode) */\n sourceLocale?: string\n /**\n * Known locale codes (e.g. ['en', 'fr', 'ja']).\n * When provided, the existing prefix is only stripped when it's an actual locale —\n * preventing false matches on generic 2-letter path segments like /my or /us.\n */\n locales?: string[]\n /**\n * Locale prefix strategy. Matches the middleware `localePrefix` setting.\n * - `'as-needed'` (default): source locale has no prefix (`/about` for en, `/fr/about` for fr)\n * - `'always'`: all locales get a prefix (`/en/about`, `/fr/about`)\n * - `'never'`: no locale prefix in URLs\n */\n localePrefix?: 'always' | 'as-needed' | 'never'\n}\n\n/**\n * Get the locale-prefixed path for a given pathname and locale.\n *\n * Pure function — works on both server and client.\n *\n * @example\n * ```ts\n * getLocalePath('/about', 'fr') // → '/fr/about'\n * getLocalePath('/about', 'en') // → '/about' (source locale, no prefix)\n * getLocalePath('/fr/about', 'en') // → '/about'\n * getLocalePath('/fr/about', 'ja') // → '/ja/about'\n * getLocalePath('/about', 'en', { localePrefix: 'always' }) // → '/en/about'\n * ```\n */\nexport function getLocalePath(\n pathname: string,\n locale: string,\n options?: GetLocalePathOptions,\n): string {\n const sourceLocale = options?.sourceLocale ?? 'en'\n const localePrefix = options?.localePrefix ?? 'as-needed'\n\n // Strip existing locale prefix if present\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n\n // Check if the first segment is a locale prefix.\n // If a locales list is provided, do an exact membership check to avoid false positives\n // on generic 2-letter path segments (e.g. /my/page or /us/pricing).\n // Otherwise fall back to the heuristic regex.\n const hasLocalePrefix = options?.locales\n ? options.locales.includes(firstSegment)\n : /^[a-z]{2}(-[A-Za-z]{2,})?$/.test(firstSegment)\n const pathWithoutLocale = hasLocalePrefix\n ? '/' + segments.slice(2).join('/')\n : pathname\n\n // 'never' mode: no prefix for any locale\n if (localePrefix === 'never') {\n return pathWithoutLocale || '/'\n }\n\n // In 'as-needed' mode, source locale gets no prefix\n if (localePrefix !== 'always' && locale === sourceLocale) {\n return pathWithoutLocale || '/'\n }\n\n return `/${locale}${pathWithoutLocale}`\n}\n\n/**\n * Hook for switching locales in Next.js App Router.\n *\n * Sets a cookie to remember user preference, navigates to the new locale path,\n * and triggers a server component refresh.\n */\nexport function useLocaleSwitcher(options?: {\n /** Override the source/default locale instead of inferring from locales[0]. */\n sourceLocale?: string\n /**\n * Cookie name used by the middleware for locale preference.\n * Defaults to the value from `fluenti.config.ts` (auto-read at build time).\n * Must match the middleware `cookieName` option.\n */\n cookieName?: string\n /** Locale prefix strategy — must match the middleware `localePrefix` option. */\n localePrefix?: 'always' | 'as-needed' | 'never'\n}) {\n const router = useRouter()\n const pathname = usePathname()\n const { locale, setLocale, getLocales } = useI18n()\n\n // Read locales from I18nProvider context (works on client without fs)\n const locales = getLocales()\n const sourceLocale = options?.sourceLocale ?? locales[0] ?? 'en'\n const cookieName = options?.cookieName ?? _configCookieName\n const localePrefix = options?.localePrefix ?? 'as-needed'\n\n const switchLocale = (newLocale: string) => {\n // Validate locale against known locales to prevent cookie injection\n if (!locales.includes(newLocale)) {\n if (typeof process !== 'undefined' && process.env?.['NODE_ENV'] !== 'production') {\n console.warn(`[fluenti] switchLocale: invalid locale \"${newLocale}\"`)\n }\n return\n }\n // 1. Set cookie to remember preference (uses configured cookie name)\n document.cookie = `${cookieName}=${encodeURIComponent(newLocale)};path=/;max-age=31536000;samesite=lax`\n // 2. Update React context\n setLocale(newLocale)\n // 3. Navigate to new locale path\n const newPath = getLocalePath(pathname, newLocale, { sourceLocale, locales, localePrefix })\n router.push(newPath)\n // 4. Refresh server components\n router.refresh()\n }\n\n return {\n switchLocale,\n currentLocale: locale,\n locales,\n sourceLocale,\n }\n}\n\n// Re-export createNavigation from the same subpath for convenience\nexport { createNavigation } from './create-navigation'\nexport type { RoutingConfig } from './create-navigation'\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"read-config.d.ts","sourceRoot":"","sources":["../src/read-config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAErE;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,gBAAgB,GAC3B,oBAAoB,CAyBtB"}
1
+ {"version":3,"file":"read-config.d.ts","sourceRoot":"","sources":["../src/read-config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAErE;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,gBAAgB,GAC3B,oBAAoB,CA4BtB"}
package/dist/types.d.ts CHANGED
@@ -8,6 +8,16 @@ import { FluentiBuildConfig } from '@fluenti/core/internal';
8
8
  export interface WithFluentConfig {
9
9
  /** fluenti.config.ts path or inline config */
10
10
  config?: string | FluentiBuildConfig;
11
+ /**
12
+ * Shorthand for `config: { locales }`. Specify target locales without a separate config file.
13
+ * Ignored if `config` is also provided.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * export default withFluenti({ locales: ['en', 'ja', 'zh-CN'] })({ reactStrictMode: true })
18
+ * ```
19
+ */
20
+ locales?: string[];
11
21
  /** Custom serverModule path (skip auto-generation) */
12
22
  serverModule?: string;
13
23
  /** Where to generate the serverModule (default: .fluenti) */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAEhE;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAAA;IAIpC,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6DAA6D;IAC7D,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAE3B;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAA;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,4CAA4C;IAC5C,aAAa,EAAE,kBAAkB,CAAA;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAEhE;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAAA;IACpC;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAIlB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6DAA6D;IAC7D,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAE3B;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAA;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,4CAA4C;IAC5C,aAAa,EAAE,kBAAkB,CAAA;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B"}
@@ -6,18 +6,16 @@ type NextConfig = Record<string, any>;
6
6
  * Adds a webpack loader that transforms `t\`\`` and `t()` calls,
7
7
  * and generates a server module for RSC i18n.
8
8
  *
9
- * @example
9
+ * @example Minimal — no fluenti.config.ts needed
10
10
  * ```ts
11
- * // next.config.ts — function style (recommended)
12
11
  * import { withFluenti } from '@fluenti/next'
13
- * export default withFluenti()({ reactStrictMode: true })
12
+ * export default withFluenti({ locales: ['en', 'ja'] })({ reactStrictMode: true })
14
13
  * ```
15
14
  *
16
- * @example
15
+ * @example With fluenti.config.ts (advanced)
17
16
  * ```ts
18
- * // next.config.ts — direct style
19
17
  * import { withFluenti } from '@fluenti/next'
20
- * export default withFluenti({ reactStrictMode: true })
18
+ * export default withFluenti()({ reactStrictMode: true })
21
19
  * ```
22
20
  */
23
21
  export declare function withFluenti(fluentConfig?: WithFluentConfig): (nextConfig?: NextConfig) => NextConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"with-fluenti.d.ts","sourceRoot":"","sources":["../src/with-fluenti.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAQ/C,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAErC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,YAAY,CAAC,EAAE,gBAAgB,GAAG,CAAC,UAAU,CAAC,EAAE,UAAU,KAAK,UAAU,CAAA;AACrG,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAAA"}
1
+ {"version":3,"file":"with-fluenti.d.ts","sourceRoot":"","sources":["../src/with-fluenti.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAQ/C,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAErC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,YAAY,CAAC,EAAE,gBAAgB,GAAG,CAAC,UAAU,CAAC,EAAE,UAAU,KAAK,UAAU,CAAA;AACrG,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluenti/next",
3
- "version": "0.4.0-rc.0",
3
+ "version": "0.4.0-rc.2",
4
4
  "type": "module",
5
5
  "description": "Next.js plugin for Fluenti — withFluenti, I18nProvider, t`` transforms for App Router and Pages Router",
6
6
  "homepage": "https://fluenti.dev",
@@ -107,8 +107,8 @@
107
107
  "dependencies": {
108
108
  "jiti": "^2",
109
109
  "picomatch": "^4",
110
- "@fluenti/core": "0.4.0-rc.0",
111
- "@fluenti/react": "0.4.0-rc.0"
110
+ "@fluenti/core": "0.4.0-rc.2",
111
+ "@fluenti/react": "0.4.0-rc.2"
112
112
  },
113
113
  "devDependencies": {
114
114
  "@types/node": "^25",