@fluenti/next 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/generate-server-module.d.ts.map +1 -1
- package/dist/i18n-config.cjs +2 -0
- package/dist/i18n-config.cjs.map +1 -0
- package/dist/i18n-config.d.ts +33 -0
- package/dist/i18n-config.d.ts.map +1 -0
- package/dist/i18n-config.js +6 -0
- package/dist/i18n-config.js.map +1 -0
- package/dist/index.cjs +41 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +122 -111
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +1 -1
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.d.ts +60 -13
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +52 -24
- package/dist/middleware.js.map +1 -1
- package/dist/navigation.cjs +1 -1
- package/dist/navigation.cjs.map +1 -1
- package/dist/navigation.d.ts +19 -4
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +17 -15
- package/dist/navigation.js.map +1 -1
- package/dist/provider.cjs.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/with-fluenti.d.ts +1 -1
- package/dist/with-fluenti.d.ts.map +1 -1
- package/package.json +18 -4
package/dist/middleware.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.cjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * @module @fluenti/next/middleware\n *\n * Built-in i18n middleware for Next.js App Router.\n *\n * Uses `x-fluenti-locale` request header to pass locale from middleware to\n * server components — avoids `Set-Cookie` on every request (CDN-friendly).\n *\n * Cookie is only used to remember user preference (set by LocaleSwitcher).\n *\n * @example\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 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/** 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\ntype NextRequest = {\n nextUrl: { pathname: string; search: 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 *\n * @example With explicit locales\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({\n * NextResponse,\n * locales: ['en', 'ja', 'zh-CN'],\n * sourceLocale: 'en',\n * })\n * ```\n */\nexport function createI18nMiddleware(config: I18nMiddlewareConfig & { NextResponse: NextResponseStatic }) {\n const { NextResponse } = config\n const resolvedLocales: string[] = config.locales ?? ['en']\n const resolvedSourceLocale: string = config.sourceLocale ?? 'en'\n const cookieName = config.cookieName ?? 'locale'\n const localePrefix = config.localePrefix ?? 'as-needed'\n\n return function i18nMiddleware(request: NextRequest) {\n const locales = resolvedLocales\n const sourceLocale = resolvedSourceLocale\n const { pathname } = request.nextUrl\n\n // Extract locale from URL path\n // Note: request.nextUrl.pathname already strips basePath (Next.js behavior)\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n const pathLocale = locales.includes(firstSegment) ? firstSegment : null\n\n // Determine the active locale\n let locale: string\n\n if (pathLocale) {\n locale = pathLocale\n } else {\n // No locale in path — detect from cookie → Accept-Language → default\n locale = 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 `/${locale}${pathname}${request.nextUrl.search}`,\n request.url,\n )\n const response = NextResponse.redirect(redirectUrl)\n response.headers.set(LOCALE_HEADER, locale)\n return response\n }\n\n // Case 2: as-needed mode, default locale has prefix → rewrite without prefix\n if (localePrefix === 'as-needed' && pathLocale === sourceLocale) {\n const pathWithoutLocale = '/' + segments.slice(2).join('/')\n const rewriteUrl = new URL(\n `${pathWithoutLocale}${request.nextUrl.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 3: No locale in path, source locale → pass through with header\n // Case 4: Non-default locale with correct prefix → pass through\n const response = NextResponse.next({\n request: { headers: requestHeaders },\n })\n response.headers.set(LOCALE_HEADER, locale)\n\n return response\n }\n}\n\n/**\n * Detect locale from request: cookie → Accept-Language → default.\n */\nfunction detectLocale(\n request: NextRequest,\n locales: string[],\n defaultLocale: string,\n cookieName: string,\n): string {\n // 1. Cookie (user preference)\n const cookieLocale = request.cookies.get(cookieName)?.value\n if (cookieLocale && locales.includes(cookieLocale)) {\n return cookieLocale\n }\n\n // 2. Accept-Language header\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 if (locales.includes(lang)) return lang\n const prefix = lang.split('-')[0]!\n const match = locales.find(l => l === prefix || l.startsWith(prefix + '-'))\n if (match) return match\n }\n }\n\n // 3. Default\n return defaultLocale\n}\n"],"mappings":"mEAuCA,IAAa,EAAgB,mBA8D7B,SAAgB,EAAqB,EAAqE,CACxG,GAAM,CAAE,gBAAiB,EACnB,EAA4B,EAAO,SAAW,CAAC,KAAK,CACpD,EAA+B,EAAO,cAAgB,KACtD,EAAa,EAAO,YAAc,SAClC,EAAe,EAAO,cAAgB,YAE5C,OAAO,SAAwB,EAAsB,CACnD,IAAM,EAAU,EACV,EAAe,EACf,CAAE,YAAa,EAAQ,QAIvB,EAAW,EAAS,MAAM,IAAI,CAC9B,EAAe,EAAS,IAAM,GAC9B,EAAa,EAAQ,SAAS,EAAa,CAAG,EAAe,KAG/D,EAEJ,AAIE,EAJE,GAIO,EAAa,EAAS,EAAS,EAAc,EAAW,CAInE,IAAM,EAAiB,IAAI,QAAQ,EAAQ,QAAQ,CAMnD,GALA,EAAe,IAAI,EAAe,EAAO,CAKrC,CAAC,IAAe,IAAiB,UAAY,IAAW,GAAe,CACzE,IAAM,EAAc,IAAI,IACtB,IAAI,IAAS,IAAW,EAAQ,QAAQ,SACxC,EAAQ,IACT,CACK,EAAW,EAAa,SAAS,EAAY,CAEnD,OADA,EAAS,QAAQ,IAAI,EAAe,EAAO,CACpC,EAIT,GAAI,IAAiB,aAAe,IAAe,EAAc,CAC/D,IAAM,EAAoB,IAAM,EAAS,MAAM,EAAE,CAAC,KAAK,IAAI,CACrD,EAAa,IAAI,IACrB,GAAG,IAAoB,EAAQ,QAAQ,SACvC,EAAQ,IACT,CACK,EAAW,EAAa,QAAQ,EAAY,CAChD,QAAS,CAAE,QAAS,EAAgB,CACrC,CAAC,CAEF,OADA,EAAS,QAAQ,IAAI,EAAe,EAAO,CACpC,EAKT,IAAM,EAAW,EAAa,KAAK,CACjC,QAAS,CAAE,QAAS,EAAgB,CACrC,CAAC,CAGF,OAFA,EAAS,QAAQ,IAAI,EAAe,EAAO,CAEpC,GAOX,SAAS,EACP,EACA,EACA,EACA,EACQ,CAER,IAAM,EAAe,EAAQ,QAAQ,IAAI,EAAW,EAAE,MACtD,GAAI,GAAgB,EAAQ,SAAS,EAAa,CAChD,OAAO,EAIT,IAAM,EAAa,EAAQ,QAAQ,IAAI,kBAAkB,CACzD,GAAI,EACF,IAAK,IAAM,KAAQ,EAAW,MAAM,IAAI,CAAE,CACxC,IAAM,EAAO,EAAK,MAAM,IAAI,CAAC,GAAI,MAAM,CACvC,GAAI,EAAQ,SAAS,EAAK,CAAE,OAAO,EACnC,IAAM,EAAS,EAAK,MAAM,IAAI,CAAC,GACzB,EAAQ,EAAQ,KAAK,GAAK,IAAM,GAAU,EAAE,WAAW,EAAS,IAAI,CAAC,CAC3E,GAAI,EAAO,OAAO,EAKtB,OAAO"}
|
|
1
|
+
{"version":3,"file":"middleware.cjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * @module @fluenti/next/middleware\n *\n * Built-in i18n middleware for Next.js App Router.\n *\n * Uses `x-fluenti-locale` request header to pass locale from middleware to\n * server components — avoids `Set-Cookie` on every request (CDN-friendly).\n *\n * 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}=${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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"}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Cookie is only used to remember user preference (set by LocaleSwitcher).
|
|
10
10
|
*
|
|
11
|
-
* @example
|
|
11
|
+
* @example Minimal — locales/sourceLocale/cookieName auto-read from fluenti.config.ts
|
|
12
12
|
* ```ts
|
|
13
13
|
* // src/middleware.ts
|
|
14
14
|
* import { NextResponse } from 'next/server'
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
* }
|
|
22
22
|
* ```
|
|
23
23
|
*
|
|
24
|
+
* @example With app/[locale]/ directory structure (rewriteDefaultLocale)
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { NextResponse } from 'next/server'
|
|
27
|
+
* import { createI18nMiddleware } from '@fluenti/next/middleware'
|
|
28
|
+
*
|
|
29
|
+
* export default createI18nMiddleware({ NextResponse, rewriteDefaultLocale: true })
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
24
32
|
* @example Composing with Clerk
|
|
25
33
|
* ```ts
|
|
26
34
|
* import { NextResponse } from 'next/server'
|
|
@@ -52,11 +60,62 @@ export interface I18nMiddlewareConfig {
|
|
|
52
60
|
* Default: `'as-needed'`
|
|
53
61
|
*/
|
|
54
62
|
localePrefix?: 'always' | 'as-needed';
|
|
63
|
+
/**
|
|
64
|
+
* When true, bare paths for the source locale are internally rewritten to include
|
|
65
|
+
* the source locale prefix. Required when using `app/[locale]/` directory structure
|
|
66
|
+
* with `localePrefix: 'as-needed'`.
|
|
67
|
+
*
|
|
68
|
+
* Also changes the handling of explicit source-locale URLs (e.g. `/en/about`):
|
|
69
|
+
* instead of rewriting to `/about`, they are **redirected** to `/about` so the
|
|
70
|
+
* browser follows the canonical URL, which is then rewritten internally.
|
|
71
|
+
*
|
|
72
|
+
* Example: `GET /about` → internally rewritten to `/en/about` (URL stays `/about`)
|
|
73
|
+
* Example: `GET /en/about` → 302 redirect → `/about` → rewritten to `/en/about`
|
|
74
|
+
*
|
|
75
|
+
* Default: `false`
|
|
76
|
+
*/
|
|
77
|
+
rewriteDefaultLocale?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* When true, the detected locale is written to a `Set-Cookie` response header so the
|
|
80
|
+
* preference is persisted across requests (useful when locale is detected from
|
|
81
|
+
* `Accept-Language` rather than an existing cookie).
|
|
82
|
+
*
|
|
83
|
+
* Disabled by default to keep responses CDN-cacheable.
|
|
84
|
+
*
|
|
85
|
+
* The cookie is only written when the locale was **detected** (not read from the URL
|
|
86
|
+
* path), and only when it differs from the existing cookie value.
|
|
87
|
+
*/
|
|
88
|
+
setCookie?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Custom locale detection function. Called when no locale is present in the URL path.
|
|
91
|
+
* Return a locale string to override the default cookie → Accept-Language → default chain.
|
|
92
|
+
* Return `undefined` to fall through to built-in detection.
|
|
93
|
+
*
|
|
94
|
+
* Useful for JWT-based preferences, subdomain detection, or any custom logic.
|
|
95
|
+
*
|
|
96
|
+
* @example Subdomain detection
|
|
97
|
+
* ```ts
|
|
98
|
+
* detectLocale: (req) => {
|
|
99
|
+
* const host = req.headers.get('host') ?? ''
|
|
100
|
+
* if (host.startsWith('fr.')) return 'fr'
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @example JWT claim
|
|
105
|
+
* ```ts
|
|
106
|
+
* detectLocale: (req) => {
|
|
107
|
+
* const token = req.cookies.get('auth')?.value
|
|
108
|
+
* return token ? parseLocaleFromJwt(token) : undefined
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
detectLocale?: (req: NextRequest) => string | undefined;
|
|
55
113
|
}
|
|
56
114
|
type NextRequest = {
|
|
57
115
|
nextUrl: {
|
|
58
116
|
pathname: string;
|
|
59
117
|
search: string;
|
|
118
|
+
basePath?: string;
|
|
60
119
|
};
|
|
61
120
|
url: string;
|
|
62
121
|
cookies: {
|
|
@@ -89,18 +148,6 @@ type NextResponseInstance = {
|
|
|
89
148
|
*
|
|
90
149
|
* export default createI18nMiddleware({ NextResponse })
|
|
91
150
|
* ```
|
|
92
|
-
*
|
|
93
|
-
* @example With explicit locales
|
|
94
|
-
* ```ts
|
|
95
|
-
* import { NextResponse } from 'next/server'
|
|
96
|
-
* import { createI18nMiddleware } from '@fluenti/next/middleware'
|
|
97
|
-
*
|
|
98
|
-
* export default createI18nMiddleware({
|
|
99
|
-
* NextResponse,
|
|
100
|
-
* locales: ['en', 'ja', 'zh-CN'],
|
|
101
|
-
* sourceLocale: 'en',
|
|
102
|
-
* })
|
|
103
|
-
* ```
|
|
104
151
|
*/
|
|
105
152
|
export declare function createI18nMiddleware(config: I18nMiddlewareConfig & {
|
|
106
153
|
NextResponse: NextResponseStatic;
|
package/dist/middleware.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAWH,sEAAsE;AACtE,eAAO,MAAM,aAAa,qBAAqB,CAAA;AAE/C,MAAM,WAAW,oBAAoB;IACnC,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAA;IACrC;;;;;;;;;;;;;OAaG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,MAAM,GAAG,SAAS,CAAA;CACxD;AAED,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChE,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAA;KAAE,CAAA;IAC7D,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,oBAAoB,CAAA;IACxC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,oBAAoB,CAAA;IACvE,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,oBAAoB,CAAA;CAC3D,CAAA;AAED,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CACpD,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG;IAAE,YAAY,EAAE,kBAAkB,CAAA;CAAE,IASvE,SAAS,WAAW,0BA+FpD"}
|
package/dist/middleware.js
CHANGED
|
@@ -1,36 +1,64 @@
|
|
|
1
|
+
import { cookieName as e, locales as t, sourceLocale as n } from "@fluenti/next/i18n-config";
|
|
1
2
|
//#region src/middleware.ts
|
|
2
|
-
var
|
|
3
|
-
function
|
|
4
|
-
let { NextResponse:
|
|
5
|
-
return function(
|
|
6
|
-
let
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return i.headers.set(e, m), i;
|
|
3
|
+
var r = "x-fluenti-locale";
|
|
4
|
+
function i(i) {
|
|
5
|
+
let { NextResponse: c } = i, l = i.locales ?? t, u = i.sourceLocale ?? n, d = i.cookieName ?? e, f = i.localePrefix ?? "as-needed", p = i.rewriteDefaultLocale ?? !1, m = i.setCookie ?? !1;
|
|
6
|
+
return function(e) {
|
|
7
|
+
let t = l, n = u, { pathname: h, search: g } = e.nextUrl, _ = e.nextUrl.basePath ?? "", v = h.split("/"), y = a(v[1] ?? "", t), b;
|
|
8
|
+
if (y) b = y;
|
|
9
|
+
else {
|
|
10
|
+
let r = i.detectLocale?.(e);
|
|
11
|
+
b = r !== void 0 && a(r, t) !== null ? a(r, t) : s(e, t, n, d);
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
let x = new Headers(e.headers);
|
|
14
|
+
if (x.set(r, b), !y && (f === "always" || b !== n)) {
|
|
15
|
+
let t = new URL(`${_}/${b}${h}${g}`, e.url), n = c.redirect(t);
|
|
16
|
+
return n.headers.set(r, b), o(n, e, b, d, m, y), n;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
if (!y && b === n && p) {
|
|
19
|
+
let t = new URL(`${_}/${n}${h}${g}`, e.url), i = c.rewrite(t, { request: { headers: x } });
|
|
20
|
+
return i.headers.set(r, b), o(i, e, b, d, m, y), i;
|
|
21
|
+
}
|
|
22
|
+
if (f === "as-needed" && y === n) {
|
|
23
|
+
let t = ("/" + v.slice(2).join("/")).replace(/\/+/g, "/");
|
|
24
|
+
if (p) {
|
|
25
|
+
let n = new URL(`${_}${t}${g}`, e.url), i = c.redirect(n);
|
|
26
|
+
return i.headers.set(r, b), i;
|
|
27
|
+
}
|
|
28
|
+
let n = new URL(`${_}${t}${g}`, e.url), i = c.rewrite(n, { request: { headers: x } });
|
|
29
|
+
return i.headers.set(r, b), i;
|
|
30
|
+
}
|
|
31
|
+
let S = c.next({ request: { headers: x } });
|
|
32
|
+
return S.headers.set(r, b), o(S, e, b, d, m, y), S;
|
|
19
33
|
};
|
|
20
34
|
}
|
|
21
|
-
function
|
|
35
|
+
function a(e, t) {
|
|
36
|
+
if (!e) return null;
|
|
37
|
+
let n = e.toLowerCase();
|
|
38
|
+
return t.find((e) => e.toLowerCase() === n) ?? null;
|
|
39
|
+
}
|
|
40
|
+
function o(e, t, n, r, i, a) {
|
|
41
|
+
!i || a || t.cookies.get(r)?.value !== n && e.headers.set("set-cookie", `${r}=${n};path=/;max-age=31536000;samesite=lax`);
|
|
42
|
+
}
|
|
43
|
+
function s(e, t, n, r) {
|
|
22
44
|
let i = e.cookies.get(r)?.value;
|
|
23
|
-
if (i
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
if (i) {
|
|
46
|
+
let e = a(i, t);
|
|
47
|
+
if (e) return e;
|
|
48
|
+
}
|
|
49
|
+
let o = e.headers.get("accept-language");
|
|
50
|
+
if (o) for (let e of o.split(",")) {
|
|
51
|
+
let n = e.split(";")[0].trim(), r = a(n, t);
|
|
52
|
+
if (r) return r;
|
|
53
|
+
let i = n.split("-")[0].toLowerCase(), o = t.find((e) => {
|
|
54
|
+
let t = e.toLowerCase();
|
|
55
|
+
return t === i || t.startsWith(i + "-");
|
|
56
|
+
});
|
|
57
|
+
if (o) return o;
|
|
30
58
|
}
|
|
31
59
|
return n;
|
|
32
60
|
}
|
|
33
61
|
//#endregion
|
|
34
|
-
export {
|
|
62
|
+
export { r as LOCALE_HEADER, i as createI18nMiddleware };
|
|
35
63
|
|
|
36
64
|
//# sourceMappingURL=middleware.js.map
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * @module @fluenti/next/middleware\n *\n * Built-in i18n middleware for Next.js App Router.\n *\n * Uses `x-fluenti-locale` request header to pass locale from middleware to\n * server components — avoids `Set-Cookie` on every request (CDN-friendly).\n *\n * Cookie is only used to remember user preference (set by LocaleSwitcher).\n *\n * @example\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 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/** 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\ntype NextRequest = {\n nextUrl: { pathname: string; search: 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 *\n * @example With explicit locales\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({\n * NextResponse,\n * locales: ['en', 'ja', 'zh-CN'],\n * sourceLocale: 'en',\n * })\n * ```\n */\nexport function createI18nMiddleware(config: I18nMiddlewareConfig & { NextResponse: NextResponseStatic }) {\n const { NextResponse } = config\n const resolvedLocales: string[] = config.locales ?? ['en']\n const resolvedSourceLocale: string = config.sourceLocale ?? 'en'\n const cookieName = config.cookieName ?? 'locale'\n const localePrefix = config.localePrefix ?? 'as-needed'\n\n return function i18nMiddleware(request: NextRequest) {\n const locales = resolvedLocales\n const sourceLocale = resolvedSourceLocale\n const { pathname } = request.nextUrl\n\n // Extract locale from URL path\n // Note: request.nextUrl.pathname already strips basePath (Next.js behavior)\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n const pathLocale = locales.includes(firstSegment) ? firstSegment : null\n\n // Determine the active locale\n let locale: string\n\n if (pathLocale) {\n locale = pathLocale\n } else {\n // No locale in path — detect from cookie → Accept-Language → default\n locale = 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 `/${locale}${pathname}${request.nextUrl.search}`,\n request.url,\n )\n const response = NextResponse.redirect(redirectUrl)\n response.headers.set(LOCALE_HEADER, locale)\n return response\n }\n\n // Case 2: as-needed mode, default locale has prefix → rewrite without prefix\n if (localePrefix === 'as-needed' && pathLocale === sourceLocale) {\n const pathWithoutLocale = '/' + segments.slice(2).join('/')\n const rewriteUrl = new URL(\n `${pathWithoutLocale}${request.nextUrl.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 3: No locale in path, source locale → pass through with header\n // Case 4: Non-default locale with correct prefix → pass through\n const response = NextResponse.next({\n request: { headers: requestHeaders },\n })\n response.headers.set(LOCALE_HEADER, locale)\n\n return response\n }\n}\n\n/**\n * Detect locale from request: cookie → Accept-Language → default.\n */\nfunction detectLocale(\n request: NextRequest,\n locales: string[],\n defaultLocale: string,\n cookieName: string,\n): string {\n // 1. Cookie (user preference)\n const cookieLocale = request.cookies.get(cookieName)?.value\n if (cookieLocale && locales.includes(cookieLocale)) {\n return cookieLocale\n }\n\n // 2. Accept-Language header\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 if (locales.includes(lang)) return lang\n const prefix = lang.split('-')[0]!\n const match = locales.find(l => l === prefix || l.startsWith(prefix + '-'))\n if (match) return match\n }\n }\n\n // 3. Default\n return defaultLocale\n}\n"],"mappings":";AAuCA,IAAa,IAAgB;AA8D7B,SAAgB,EAAqB,GAAqE;CACxG,IAAM,EAAE,oBAAiB,GACnB,IAA4B,EAAO,WAAW,CAAC,KAAK,EACpD,IAA+B,EAAO,gBAAgB,MACtD,IAAa,EAAO,cAAc,UAClC,IAAe,EAAO,gBAAgB;AAE5C,QAAO,SAAwB,GAAsB;EACnD,IAAM,IAAU,GACV,IAAe,GACf,EAAE,gBAAa,EAAQ,SAIvB,IAAW,EAAS,MAAM,IAAI,EAC9B,IAAe,EAAS,MAAM,IAC9B,IAAa,EAAQ,SAAS,EAAa,GAAG,IAAe,MAG/D;AAEJ,EAIE,IAJE,KAIO,EAAa,GAAS,GAAS,GAAc,EAAW;EAInE,IAAM,IAAiB,IAAI,QAAQ,EAAQ,QAAQ;AAMnD,MALA,EAAe,IAAI,GAAe,EAAO,EAKrC,CAAC,MAAe,MAAiB,YAAY,MAAW,IAAe;GACzE,IAAM,IAAc,IAAI,IACtB,IAAI,IAAS,IAAW,EAAQ,QAAQ,UACxC,EAAQ,IACT,EACK,IAAW,EAAa,SAAS,EAAY;AAEnD,UADA,EAAS,QAAQ,IAAI,GAAe,EAAO,EACpC;;AAIT,MAAI,MAAiB,eAAe,MAAe,GAAc;GAC/D,IAAM,IAAoB,MAAM,EAAS,MAAM,EAAE,CAAC,KAAK,IAAI,EACrD,IAAa,IAAI,IACrB,GAAG,IAAoB,EAAQ,QAAQ,UACvC,EAAQ,IACT,EACK,IAAW,EAAa,QAAQ,GAAY,EAChD,SAAS,EAAE,SAAS,GAAgB,EACrC,CAAC;AAEF,UADA,EAAS,QAAQ,IAAI,GAAe,EAAO,EACpC;;EAKT,IAAM,IAAW,EAAa,KAAK,EACjC,SAAS,EAAE,SAAS,GAAgB,EACrC,CAAC;AAGF,SAFA,EAAS,QAAQ,IAAI,GAAe,EAAO,EAEpC;;;AAOX,SAAS,EACP,GACA,GACA,GACA,GACQ;CAER,IAAM,IAAe,EAAQ,QAAQ,IAAI,EAAW,EAAE;AACtD,KAAI,KAAgB,EAAQ,SAAS,EAAa,CAChD,QAAO;CAIT,IAAM,IAAa,EAAQ,QAAQ,IAAI,kBAAkB;AACzD,KAAI,EACF,MAAK,IAAM,KAAQ,EAAW,MAAM,IAAI,EAAE;EACxC,IAAM,IAAO,EAAK,MAAM,IAAI,CAAC,GAAI,MAAM;AACvC,MAAI,EAAQ,SAAS,EAAK,CAAE,QAAO;EACnC,IAAM,IAAS,EAAK,MAAM,IAAI,CAAC,IACzB,IAAQ,EAAQ,MAAK,MAAK,MAAM,KAAU,EAAE,WAAW,IAAS,IAAI,CAAC;AAC3E,MAAI,EAAO,QAAO;;AAKtB,QAAO"}
|
|
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}=${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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"}
|
package/dist/navigation.cjs
CHANGED
|
@@ -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(`next/navigation`);function
|
|
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=>{document.cookie=`${f}=${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;
|
|
3
3
|
//# sourceMappingURL=navigation.cjs.map
|
package/dist/navigation.cjs.map
CHANGED
|
@@ -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'\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\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')
|
|
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 // 1. Set cookie to remember preference (uses configured cookie name)\n document.cookie = `${cookieName}=${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,YAc9C,MAAO,CACL,aAboB,GAAsB,CAE1C,SAAS,OAAS,GAAG,EAAW,GAAG,EAAU,uCAE7C,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"}
|
package/dist/navigation.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ export interface GetLocalePathOptions {
|
|
|
7
7
|
* preventing false matches on generic 2-letter path segments like /my or /us.
|
|
8
8
|
*/
|
|
9
9
|
locales?: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Locale prefix strategy. Matches the middleware `localePrefix` setting.
|
|
12
|
+
* - `'as-needed'` (default): source locale has no prefix (`/about` for en, `/fr/about` for fr)
|
|
13
|
+
* - `'always'`: all locales get a prefix (`/en/about`, `/fr/about`)
|
|
14
|
+
*/
|
|
15
|
+
localePrefix?: 'always' | 'as-needed';
|
|
10
16
|
}
|
|
11
17
|
/**
|
|
12
18
|
* Get the locale-prefixed path for a given pathname and locale.
|
|
@@ -15,10 +21,11 @@ export interface GetLocalePathOptions {
|
|
|
15
21
|
*
|
|
16
22
|
* @example
|
|
17
23
|
* ```ts
|
|
18
|
-
* getLocalePath('/about', 'fr')
|
|
19
|
-
* getLocalePath('/about', 'en')
|
|
20
|
-
* getLocalePath('/fr/about', 'en')
|
|
21
|
-
* getLocalePath('/fr/about', 'ja')
|
|
24
|
+
* getLocalePath('/about', 'fr') // → '/fr/about'
|
|
25
|
+
* getLocalePath('/about', 'en') // → '/about' (source locale, no prefix)
|
|
26
|
+
* getLocalePath('/fr/about', 'en') // → '/about'
|
|
27
|
+
* getLocalePath('/fr/about', 'ja') // → '/ja/about'
|
|
28
|
+
* getLocalePath('/about', 'en', { localePrefix: 'always' }) // → '/en/about'
|
|
22
29
|
* ```
|
|
23
30
|
*/
|
|
24
31
|
export declare function getLocalePath(pathname: string, locale: string, options?: GetLocalePathOptions): string;
|
|
@@ -31,6 +38,14 @@ export declare function getLocalePath(pathname: string, locale: string, options?
|
|
|
31
38
|
export declare function useLocaleSwitcher(options?: {
|
|
32
39
|
/** Override the source/default locale instead of inferring from locales[0]. */
|
|
33
40
|
sourceLocale?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Cookie name used by the middleware for locale preference.
|
|
43
|
+
* Defaults to the value from `fluenti.config.ts` (auto-read at build time).
|
|
44
|
+
* Must match the middleware `cookieName` option.
|
|
45
|
+
*/
|
|
46
|
+
cookieName?: string;
|
|
47
|
+
/** Locale prefix strategy — must match the middleware `localePrefix` option. */
|
|
48
|
+
localePrefix?: 'always' | 'as-needed';
|
|
34
49
|
}): {
|
|
35
50
|
switchLocale: (newLocale: string) => void;
|
|
36
51
|
currentLocale: string;
|
package/dist/navigation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"
|
|
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;;;;EAkBxC"}
|
package/dist/navigation.js
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
"use client";
|
|
3
3
|
import { useI18n as e } from "@fluenti/react";
|
|
4
|
-
import {
|
|
4
|
+
import { cookieName as t } from "@fluenti/next/i18n-config";
|
|
5
|
+
import { usePathname as n, useRouter as r } from "next/navigation";
|
|
5
6
|
//#region src/navigation.ts
|
|
6
|
-
function
|
|
7
|
-
let r = n?.sourceLocale ?? "en", i = e.split("/"),
|
|
8
|
-
return t === r ?
|
|
7
|
+
function i(e, t, n) {
|
|
8
|
+
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}`;
|
|
9
10
|
}
|
|
10
|
-
function
|
|
11
|
-
let
|
|
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";
|
|
12
13
|
return {
|
|
13
14
|
switchLocale: (e) => {
|
|
14
|
-
document.cookie =
|
|
15
|
-
let t =
|
|
16
|
-
sourceLocale:
|
|
17
|
-
locales:
|
|
15
|
+
document.cookie = `${p}=${e};path=/;max-age=31536000;samesite=lax`, l(e);
|
|
16
|
+
let t = i(s, e, {
|
|
17
|
+
sourceLocale: f,
|
|
18
|
+
locales: d,
|
|
19
|
+
localePrefix: m
|
|
18
20
|
});
|
|
19
|
-
|
|
21
|
+
o.push(t), o.refresh();
|
|
20
22
|
},
|
|
21
|
-
currentLocale:
|
|
22
|
-
locales:
|
|
23
|
-
sourceLocale:
|
|
23
|
+
currentLocale: c,
|
|
24
|
+
locales: d,
|
|
25
|
+
sourceLocale: f
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
//#endregion
|
|
27
|
-
export {
|
|
29
|
+
export { i as getLocalePath, a as useLocaleSwitcher };
|
|
28
30
|
|
|
29
31
|
//# sourceMappingURL=navigation.js.map
|
package/dist/navigation.js.map
CHANGED
|
@@ -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'\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\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')
|
|
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 // 1. Set cookie to remember preference (uses configured cookie name)\n document.cookie = `${cookieName}=${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;AAc9C,QAAO;EACL,eAboB,MAAsB;AAI1C,GAFA,SAAS,SAAS,GAAG,EAAW,GAAG,EAAU,wCAE7C,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"}
|
package/dist/provider.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.cjs","names":[],"sources":["../src/client-provider.tsx"],"sourcesContent":["'use client'\n\nimport type { ReactNode } from 'react'\nimport { I18nProvider } from '@fluenti/react'\nimport type { AllMessages, DateFormatOptions, NumberFormatOptions, Locale } from '@fluenti/core'\n\nexport interface ClientI18nProviderProps {\n locale: string\n fallbackLocale: string\n messages: AllMessages\n fallbackChain?: Record<string, Locale[]>\n dateFormats?: DateFormatOptions\n numberFormats?: NumberFormatOptions\n children: ReactNode\n}\n\n/**\n * Client-side I18nProvider wrapper.\n * Used internally by I18nProvider to hydrate client components.\n */\nexport function ClientI18nProvider({\n locale,\n fallbackLocale,\n messages,\n fallbackChain,\n dateFormats,\n numberFormats,\n children,\n}: ClientI18nProviderProps) {\n return (\n <I18nProvider\n locale={locale}\n fallbackLocale={fallbackLocale}\n messages={messages}\n {...(fallbackChain ? { fallbackChain } : {})}\n {...(dateFormats ? { dateFormats } : {})}\n {...(numberFormats ? { numberFormats } : {})}\n >\n {children}\n </I18nProvider>\n )\n}\n"],"mappings":"kIAoBA,SAAgB,EAAmB,CACjC,SACA,iBACA,WACA,gBACA,cACA,gBACA,YAC0B,CAC1B,
|
|
1
|
+
{"version":3,"file":"provider.cjs","names":[],"sources":["../src/client-provider.tsx"],"sourcesContent":["'use client'\n\nimport type { ReactNode } from 'react'\nimport { I18nProvider } from '@fluenti/react'\nimport type { AllMessages, DateFormatOptions, NumberFormatOptions, Locale } from '@fluenti/core'\n\nexport interface ClientI18nProviderProps {\n locale: string\n fallbackLocale: string\n messages: AllMessages\n fallbackChain?: Record<string, Locale[]>\n dateFormats?: DateFormatOptions\n numberFormats?: NumberFormatOptions\n children: ReactNode\n}\n\n/**\n * Client-side I18nProvider wrapper.\n * Used internally by I18nProvider to hydrate client components.\n */\nexport function ClientI18nProvider({\n locale,\n fallbackLocale,\n messages,\n fallbackChain,\n dateFormats,\n numberFormats,\n children,\n}: ClientI18nProviderProps) {\n return (\n <I18nProvider\n locale={locale}\n fallbackLocale={fallbackLocale}\n messages={messages}\n {...(fallbackChain ? { fallbackChain } : {})}\n {...(dateFormats ? { dateFormats } : {})}\n {...(numberFormats ? { numberFormats } : {})}\n >\n {children}\n </I18nProvider>\n )\n}\n"],"mappings":"kIAoBA,SAAgB,EAAmB,CACjC,SACA,iBACA,WACA,gBACA,cACA,gBACA,YAC0B,CAC1B,OACE,EAAA,EAAA,KAAC,EAAA,aAAD,CACU,SACQ,iBACN,WACV,GAAK,EAAgB,CAAE,gBAAe,CAAG,EAAE,CAC3C,GAAK,EAAc,CAAE,cAAa,CAAG,EAAE,CACvC,GAAK,EAAgB,CAAE,gBAAe,CAAG,EAAE,CAE1C,WACY,CAAA"}
|
package/dist/types.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface WithFluentConfig {
|
|
|
10
10
|
config?: string | FluentiBuildConfig;
|
|
11
11
|
/** Custom serverModule path (skip auto-generation) */
|
|
12
12
|
serverModule?: string;
|
|
13
|
-
/** Where to generate the serverModule (default:
|
|
13
|
+
/** Where to generate the serverModule (default: .fluenti) */
|
|
14
14
|
serverModuleOutDir?: string;
|
|
15
15
|
/**
|
|
16
16
|
* Path to a module that default-exports an async function returning the locale string.
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/with-fluenti.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-fluenti.d.ts","sourceRoot":"","sources":["../src/with-fluenti.ts"],"names":[],"mappings":"
|
|
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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluenti/next",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Next.js plugin for Fluenti — withFluenti, I18nProvider, t`` transforms for App Router and Pages Router",
|
|
6
6
|
"homepage": "https://fluenti.dev",
|
|
@@ -80,6 +80,20 @@
|
|
|
80
80
|
"types": "./dist/navigation.d.ts",
|
|
81
81
|
"default": "./dist/navigation.cjs"
|
|
82
82
|
}
|
|
83
|
+
},
|
|
84
|
+
"./loader": {
|
|
85
|
+
"import": "./dist/loader.js",
|
|
86
|
+
"require": "./dist/loader.cjs"
|
|
87
|
+
},
|
|
88
|
+
"./i18n-config": {
|
|
89
|
+
"import": {
|
|
90
|
+
"types": "./dist/i18n-config.d.ts",
|
|
91
|
+
"default": "./dist/i18n-config.js"
|
|
92
|
+
},
|
|
93
|
+
"require": {
|
|
94
|
+
"types": "./dist/i18n-config.d.ts",
|
|
95
|
+
"default": "./dist/i18n-config.cjs"
|
|
96
|
+
}
|
|
83
97
|
}
|
|
84
98
|
},
|
|
85
99
|
"files": [
|
|
@@ -93,15 +107,15 @@
|
|
|
93
107
|
"dependencies": {
|
|
94
108
|
"jiti": "^2",
|
|
95
109
|
"picomatch": "^4",
|
|
96
|
-
"@fluenti/core": "0.3.
|
|
97
|
-
"@fluenti/react": "0.3.
|
|
110
|
+
"@fluenti/core": "0.3.4",
|
|
111
|
+
"@fluenti/react": "0.3.4"
|
|
98
112
|
},
|
|
99
113
|
"devDependencies": {
|
|
100
114
|
"@types/node": "^25",
|
|
101
115
|
"@types/react": "^19.0.0",
|
|
102
116
|
"@types/react-dom": "^19.0.0",
|
|
103
117
|
"@vitest/coverage-v8": "^4",
|
|
104
|
-
"next": "^
|
|
118
|
+
"next": "^16",
|
|
105
119
|
"react": "^19.0.0",
|
|
106
120
|
"react-dom": "^19.0.0",
|
|
107
121
|
"typescript": "^5.9",
|